Функции календаря и времени на одном регистре

На этапе разработки
Работаю я значит метрологом, работа хорошая, спокойная.Но вот не задача, некоторые приборы уж очень долго греются(где-то часа 4).Придешь в 9-00 включишь, к 13-00 прогрелись, а с 13 до 15 обед, в 17-30 уже надо собираться… так что времени на поверку совсем мало остается. А план-то делать надо.Решил я значит сделать некое подобие таймера, который включает и выключает нагрузку по расписанию и заодно следит за ТВР. Но не об этом сегодня речь.


Сегодня в центре внимания всего две функции, которые я тщетно пытался найти и почему-то не нашел…
Дело в том, что 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 и можно будет сделать часы для гиков и подарить на новый год. Вторая, собственно, антипод первой.
Принимаются пожелания и предложения, либо ссылки на более удачные варианты.
P.S. Есть модель в экселе, там происходило опробование идеи. Если нужен будет, приложу.
  • +1
  • 10 ноября 2012, 03:16
  • tituszx

Комментарии (24)

RSS свернуть / развернуть
STM32 для такой задачи великовата, можно же на STM8L это реализовать, там аппаратный RTC, который сам переводи даты и время, заботится о высокосортных годах, количестве дней в месяце. Если уж хотите на STM32 то попробуйте взять STM32L, там тоже есть RTC.
Ваши функции перевода времени довольно жирные, много операций "*" и "/". Можно использовать эти функции для ввода дат пользователем (а если установка производится с компьютера, то пусть лучше компьютер это считает, а на устройство выдает в формате UNIX-time).
Для вывода даты и времени можно использовать BCD формат, не будет делений при преобразовании числа в строку. Т.е. один раз преобразовываете в BCD, потом по прерыванию инкриментируете и корректируете, всё обойдется только операциями сложения, вычитания и сравнения.
Так же можно дату хранить в виде строки, а в счетчике только время. Долгие расчеты будут проводиться только 1 раз в сутки. Расчеты даты можно сделать проще.
Вашу функцию CounterToFtime в прерывание я бы не стал ставить, даже 1 раз в секунду. Если хотите использовать в прерывании, то не делайте ничего в обработчике кроме вывода и поставьте минимальный приоритет прерыванию.

Хотя… Преждевременная оптимизация — корень всех бед.
0
Ваши функции перевода времени довольно жирные, много операций "*" и "/".
А какая разница, STM32 же. Как минимум умножение там аппаратное.
0
Разница в цене = 15 р. =)

Сейчас можно пихать STM32 туда, где раньше ставили тини2313. Выйдет дешевле, если конечно, не упираемся в энергопотребление.
0
Разница в цене = 15 р. =)
Размер платы и усложнение разводки увеличит сроки разработки и стоимость конечного изделия. Для любительской разработки 15 рулей не деньги, но делать простой таймер на 32-битном контролере, при том что хватило бы PIC12, мне кажется не разумным.
0
Неиспользуемые контакты можно не разводить.
0
Да и STM8L не легче =).
0
Мой вариант на эту тематику: SystemTime.cpp и SystemTime.h

Небольшое обсуждение проблем с 32 разрядным типом для такого применения тут: Модуль mikroBUS: RTC (PCF8523TS)

Примеры использования класса можно найти в исходниках поиском по имени класса. Например, прибавление миллисекунды в обработчике таймера делается так:

CSystemTime::SetTime( CSystemTime::GetTime() + 1 );

Инициализация экземпляра класса делается так:

SYSTEMTIME Time = { 0, 0, 0, 0, 0, 0, 0, 0 };

Вывод данных о времени и дате делается при помощи соответствующих публичных методов класса (см. заголовочный файл). Изначально я ориентировался на 64 битную структуру, но её обработка была неэффективной и занимала много места в ATmega16, поэтому для этого частного варианта оставил 32 разрядную длину структуры.
0
  • avatar
  • uni
  • 10 ноября 2012, 10:06
Забыл дописать инициализацию:


        SYSTEMTIME Time = { 0, 0, 0, 0, 0, 0, 0, 0 };

        CRTC::GetTimeAsSystemTime( & Time );

        CSystemTime::SetTime( & Time );

Структура SYSTEMTIME определена так (не помню откуда взял, скорее всего это windows-структура):

// Specifies a date and time, in coordinated universal time (UTC), 
// using individual members for the month, day, year, weekday, 
// hour, minute, second, and millisecond.
typedef struct _SYSTEMTIME {  
    WORD wYear;  
    WORD wMonth;  
    WORD wDayOfWeek;  
    WORD wDay;  
    WORD wHour;  
    WORD wMinute;  
    WORD wSecond;  
    WORD wMilliseconds;
} SYSTEMTIME,  *PSYSTEMTIME;

CRTC::GetTimeAsSystemTime( & Time ); — это опрос внешних часов, если они были обнаружены. Если часы не обнаружены, то отсчёт времени у меня осуществляется программно. Это примерно как в unix-системах, где есть 2 вида времени: аппаратное и системное.
0
Вариант через JDN, описанный в статье, сушественно эффективнее, не требует таблиц и содержит только арифметику, никаких циклов.
0
А какие минусы увидели?

Мой код это порт из одной ПЛК программы, где использовалось время в стиле юникса. Самому мне хотелось использовать как юникс так и windows варианты представления времени в системе, причём, с отсчетом миллисекунд.

Астрономам миллисекунды не очень нужны, а вот во встраиваемых системах такая точность нужна чаще.
0
А какие минусы увидели?
Я же неписал — код сложный, содержит много таблиц и циклы. В отличие от JDN.

P.S. я думаю вы ошибаетесь насчет астрономов.
0
Да нет, я не ошибаюсь. Ты только думаешь, а я книжки читаю. Рекомендую прочитать/просмотреть: «Астрономия на персональном компьютере», Оливер Монтенбрук, Томас Пфлегер, 2002. Если найдёшь в листингах C++ кода различных астрономических вычислений использование чего-то большего чем секунды, то я возьму своё мнение на счёт астрономов назад.
Ну и сам то подумай, если есть план секундных корректировок всемирного времени, то на кой чёрт им нужны миллисекунды?

Что же касается «сложности» кода, то и тут ты ошибаешься. Нужно отличать места с комментариями от кода (комментарий к C++ начинается с двух слешей). Посмотри внимательно и не пугайся, сколько там таблиц?.. Там нет ничего сложного, кроме того, большая часть кода просто тупо выводит наружу (делает доступным) конкретные значения временных координат.
Ещё ты упоминал какие-то циклы… что-то я не понял что имелось в виду. Где там циклы?

А такие комментарии я оставил, чтобы в дальнейшем доработать код для вывода текстового представления времени в стандартном формате как на ПК.
0
uint32_t CSystemTime::Counter = 0;

Это что-ли циклы :) Это просто переменная, которая хранит время. Зачем иначе мне её делать 32 разрядной? Я ещё счётчиком обозвал, т.к. он постоянно инкрементируется в обработчике таймера с миллисекундным интервалом. Кстати, это единственная переменная в классе, на которой всё и основано. Всё остальное — это просто упаковка и распаковка временных данных.

Юниксовое время в классе инициализируется прямо (можно было это заметить):

void CSystemTime::SetTime( time_t & Time ) {

    Counter = ( uint32_t ) Time;
}

time_t CSystemTime::GetTimeAsUnixTime() {

    return ( time_t ) Counter;

}
0
И ещё, если что-то непонятно, то вот более подробные комментарии к коду: Программируем временные сложности

Описаны устройство и проблемы современного календаря и часов. Предложены приемы
программирования алгоритмов с календарными датами, часами и таймерами. Программы
реализованы на языках ST и FBD стандарта МЭК 61131-3 в системе CoDeSys (3S).

Я портировал эти выражения на C++ с МЭКовского языка для ПЛК.
0
Буду неоригинальным. Кат.
0
fixed
0
Я бы посоветовал автору добавить уточнение, что JDN, не смотря на название, не имеет отношения к Юлианскому календарю.
0
  • avatar
  • evsi
  • 10 ноября 2012, 19:17
На фотке похоже Discovery, у меня на ней без калибровки часы убегают на несколько секунд в день. Календарь я тоже свой писал, по этим же формулам, просто руки не доходили его опубликовать. Для полноты счастья дней недели не хватает, если кому понадобится выложу.

Замаялся для своего проекта с RTC искать кварцы 6pf в Питере, где бы их надыбать, кто подскажет? везде продаются 12.5pf. С 12.5pf часы тоже работают но врут на 40с в день и вообще их не рекомендовано использовать(
0
Попробуй 12,5 пФ последовательно соединить, как раз 6 получится.
0
Если врут стабильно, это не беда. Поправка спасет отца русской демократии:)
0
Врут стабильно на конкретно взятом экземпляре, а думал делитель поменять, да может оказаться что частота будет разниться от устройства к устройству. Лучше поищу 6pf. Насчет соединить последовательно это конечно интересно, но уж совсем отчаяный шаг)
0
Кстати, что за приборы которые так долго греются, не уж то ламповые еще?
0
Нет, транзисторные. Компараторы Р3003, Р3017, П327 да и новые типа В2-43 тоже не меньше двух часов (оптимально 4)
0
Спасибо большое, изобретал аналогичный велосипед, решил использовать Ваш, Вы мне сильно сэкономили время :)
0
  • avatar
  • AVF
  • 24 февраля 2013, 17:35
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.