stm32f1xx - нет аппаратного календаря, UNIX-time

http://we.easyelectronics.ru/Soft/funkcii-kalendarya-i-vremeni-na-odnom-registre.html

Следует учитывать, что RTC в STM32F10x в отличии от внешних микросхем (DS1307) или часов в MCU NXP не имеют аппаратного календаря и представляют всего лишь 32х битный счётчик. С другой стороны это позволяет легко использовать кванты времени отличные от секунды.

 stm32 для часов предоставляет нам 32 разрядный регистр, который можно настроить на увеличение каждую секунду. Вроде бы всё хорошо, берем количество секунд, делим на 60 получаем минуты, еще раз — часы… сутки, месяцы, года… Но есть одно НО. Это високосные годы и различное количество дней в месяцах. Посмотрев на различные реализации, решил именно тут изобрести велосипед.

Итак:
Дано:
32 разрядный регистр, инкремент ежесекундно.
Задача:
Обеспечить функции отсчета времени и календаря основываясь только на данных этого регистра.
Решение:

Сразу приходит на ум UNIX-time. Но вот в плане реализации информации маловато. Ну да ладно. Идем в википедию и читаем про преобразование дат. Так. Значит формулы есть — уже хорошо. Осталось их привести в удобоваримый вид и немного дополнить. После мозговых преобразований имеем:

#define JD0 2451911 // дней до 01 янв 2001 ПН
typedef struct{
uint16_t year;
uint8_t month;
uint8_t day;
uint8_t hour;
uint8_t minute;
uint8_t second;
} ftime_t;

// функция преобразования григорианской даты и времени в значение счетчика
uint32_t FtimeToCounter(ftime_t * ftime)
{
uint8_t a;
uint16_t y;
uint8_t m;
uint32_t JDN;

// Вычисление необходимых коэффициентов
a=(14-ftime->month)/12;
y=ftime->year+4800-a;
m=ftime->month+(12*a)-3;
// Вычисляем значение текущего Юлианского дня
JDN=ftime->day;
JDN+=(153*m+2)/5;
JDN+=365*y;
JDN+=y/4;
JDN+=-y/100;
JDN+=y/400;
JDN+=-32045;
JDN+=-JD0; // так как счетчик у нас нерезиновый, уберем дни которые прошли до 01 янв 2001 
JDN*=86400;     // переводим дни в секунды
JDN+=(ftime->hour*3600); // и дополняем его скундами текущего дня
JDN+=(ftime->minute*60);
JDN+=(ftime->second);
// итого имеем количество секунд с 00-00 01 янв 2001
return JDN;
}

// функция преобразования значение счетчика в григорианскую дату и время 
void CounterToFtime(uint32_t counter,ftime_t * ftime)
{
uint32_t ace;
uint8_t b;
uint8_t d;
uint8_t m;

ace=(counter/86400)+32044+JD0;
b=(4*ace+3)/146097; // может ли произойти потеря точности из-за переполнения 4*ace ??
ace=ace-((146097*b)/4);
d=(4*ace+3)/1461;
ace=ace-((1461*d)/4);
m=(5*ace+2)/153;
ftime->day=ace-((153*m+2)/5)+1;
ftime->month=m+3-(12*(m/10));
ftime->year=100*b+d-4800+(m/10);
ftime->hour=(counter/3600)%24;
ftime->minute=(counter/60)%60;
ftime->second=(counter%60);
}

Теперь про сами функции. Первая преобразует дату и время из структуры ftime_t в значение счетчика секунд, прошедших с 01.01.2001. В принципе может быть выбрана любая другая дата, указывается в смещении JD0 в Юлианских днях, прошедших с 24 ноября 4714 г. до н. э. Если выбрать 1 января 1970, то будет показывать UNIX-time и можно будет сделать часы для гиков и подарить на новый год. Вторая, собственно, антипод первой.

Вариант конвертации из википедии:

Конвертация времени, заголовочный файл xtime.h

typedef	unsigned short			u16_t;
typedef	unsigned long			u32_t;
typedef	signed short			s16_t;
typedef	signed long			s32_t;
 
	// DEF: standard signed format
	// UNDEF: non-standard unsigned format
	#define	_XT_SIGNED
 
#ifdef	_XT_SIGNED
	typedef	s32_t                           xtime_t;
#else
	typedef	u32_t                           xtime_t;
#endif
 
struct tm
{       /* date and time components */
	BYTE	tm_sec;
	BYTE	tm_min;
	BYTE	tm_hour;
	BYTE	tm_mday;
	BYTE	tm_mon;
	u16_t	tm_year;
};
 
void xttotm(struct tm *t, xtime_t secs);
xtime_t xtmtot(struct tm *t);

Конвертация времени: xtime.c

#include "xtime.h"
/* Приводится пример реализации на языке Си функций конвертации между UNIX-временем и обычным представлением в виде даты и времени суток. Пример приведен в стандартном знаковом 32-бит формате. Однако, закомментировав определение _XT_SIGNED, пример соберется в беззнаковом варианте. */ 
#define _TBIAS_DAYS		((70 * (u32_t)365) + 17)
#define _TBIAS_SECS		(_TBIAS_DAYS * (xtime_t)86400)
#define	_TBIAS_YEAR		1900
#define MONTAB(year)		((((year) & 03) || ((year) == 0)) ? mos : lmos)
 
const s16_t	lmos[] = {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335};
const s16_t	mos[] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334};
 
#define	Daysto32(year, mon)	(((year - 1) / 4) + MONTAB(year)[mon])
 
/////////////////////////////////////////////////////////////////////
 
xtime_t xtmtot(struct tm *t)
{	/* convert time structure to scalar time */
s32_t		days;
xtime_t		secs;
s32_t		mon, year;
 
	/* Calculate number of days. */
	mon = t->tm_mon - 1;
	year = t->tm_year - _TBIAS_YEAR;
	days  = Daysto32(year, mon) - 1;
	days += 365 * year;
	days += t->tm_mday;
	days -= _TBIAS_DAYS;
 
	/* Calculate number of seconds. */
	secs  = 3600 * t->tm_hour;
	secs += 60 * t->tm_min;
	secs += t->tm_sec;
 
	secs += (days * (xtime_t)86400);
 
	return (secs);
}
 
/////////////////////////////////////////////////////////////////////
 
void xttotm(struct tm *t, xtime_t secsarg)
{
u32_t		secs;
s32_t		days;
s32_t		mon;
s32_t		year;
s32_t		i;
const s16_t*	pm;
 
	#ifdef	_XT_SIGNED
		if (secsarg >= 0) {
			secs = (u32_t)secsarg;
			days = _TBIAS_DAYS;
		} else {
			secs = (u32_t)secsarg + _TBIAS_SECS;
			days = 0;
		}
	#else
		secs = secsarg;
		days = _TBIAS_DAYS;
	#endif
 
		/* days, hour, min, sec */
	days += secs / 86400;		secs = secs % 86400;
	t->tm_hour = secs / 3600;	secs %= 3600;
	t->tm_min = secs / 60;		t->tm_sec = secs % 60;
 
		/* determine year */
	for (year = days / 365; days < (i = Daysto32(year, 0) + 365*year); ) { --year; }
	days -= i;
	t->tm_year = year + _TBIAS_YEAR;
 
		/* determine month */
	pm = MONTAB(year);
	for (mon = 12; days < pm[--mon]; );
	t->tm_mon = mon + 1;
	t->tm_mday = days - pm[mon] + 1;
}

Обратная связь

Интересуют вопросы реализации алгоритмов, программирования, выбора электроники и прочая информация, постараюсь осветить в отдельных статьях

пишите мне на netdm@mail.ru