Недокументированная и нестандартная служба времени AVR LibC

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Потребовалось мне тут узнавать из Интернета текущее время и дату. Ну, получить длинное число из NTP нетрудно, но потом же надо в дату и время пересчитать! Запустил IDE 1.6.5 и с тоской обнаружил, что там нет службы времени («time.h»). Полез на сайт Atmel в документацию библиотеки AVR LibC и с ещё большей тоской обнаружил, что её нет и там. Те, кто когда-либо работал с NTP, поймут мою тоску –  время там узнать несложно, а вот дату без библиотечных функций – гороху накушаешься. Требуется очень аккуратная программа строк на сто – в общем, полдня возни.

Но, чёрт меня дёрнул запустить IDE 1.6.12 (типа, для очистки совести) – а оно там есть!

Таким образом, в поздних версиях IDE (вернее, библиотеки AVR LibC) имеется недокументированная служба времени. С какой версии IDE она появилась, я выяснять не стал, но точно могу сказать, что в 1.6.5 ещё не было, а в 1.6.12 уже есть.

Попробовал применить, а вот фигвам! Считая, что в типе time_t как и положено лежит UNIX-время, т.е. секунды с 01.01.1970, я ввёл поправку на NTP время (секунды с 01.01.1900) и … получил, что сегодня 2047 год.

Полез в исходники AVR LibC – документации-то нету :( Нарыл следующую информацию – в типе time_t они хранят не UNIX-время как все нормальные люди, а секунды с 01.01.2000. Отсюда и моя ошибка в 30 лет. Правда, они определили константы для пересчёта их времени в UNIX и в NTP – и на том спасибо.

На всякий случай проверил, что хранится в поле tm_year структуры struct tm. Здесь у них всё стандартно – год с 1900, как у всех.

Вот скетч – пример использования службы времени. Работа с NTP сюда не включена, а просто вставил константу 3693471153, которую я получил сегодня от NTP сервера. Она соответствует  времени 15.01.2017 15:12:33. Смотрите в скетче, как из этого числа можно получить структуру struct tm (а в ней уже есть все – время, дата, день недели и т.п.).

В порядке справки, приведу также и структуру struct tm, для тех, кто её не знает.

 

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Виноват, в строке 9 более грамотно использовать тип time_t, а не unsigned long,  хотя в данной реализации это одно и то же. У меня этот тип случайно остался от NTP-шной программы - забыл поменять.

Ворота
Ворота аватар
Offline
Зарегистрирован: 10.01.2016

А в остальном нормальная библиотека? Что там есть, чего нет? Локализации-то поди точно нет.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Ворота пишет:

А в остальном нормальная библиотека? Что там есть, чего нет? Локализации-то поди точно нет.

Вот полный заголовочный файл time.h из avr libc 1.8.1

 

dimax
dimax аватар
Offline
Зарегистрирован: 25.12.2013

Если что, то на Pjrc.com есть рабочая библиотека "time" с прмерами.. 

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Ну, Dimax, здесь всё-таки полная С-шная стандартная библиотека к которой многие уж десятилетия как привыкли. 

Вот, недавно была тема где автор собирался массив времени восхода на весь год иметь, а тут - ввёл свои координаты и получай хоть восход, хоть закат ...

Оно как-то когда это устоявшийся стандарт как-то приятниее.

Клапауций 112
Клапауций 112 аватар
Offline
Зарегистрирован: 01.03.2017

кто как понимает этот кусок комментариев в time.h ? (*ТС, можешь не выскакивать с воплями о платности своих услуг)

01/**
02    Maintain the system time by calling this function at a rate of 1 Hertz.
03 
04    It is anticipated that this function will typically be called from within an
05    Interrupt Service Routine, (though that is not required). It therefore includes code which
06    makes it simple to use from within a 'Naked' ISR, avoiding the cost of saving and restoring
07    all the cpu registers.
08 
09    Such an ISR may resemble the following example...
10    \code
11        ISR(RTC_OVF_vect, ISR_NAKED)
12        {
13            system_tick();
14            reti();
15        }
16    \endcode
17*/

так понимаю, что нужно каждую секунду запускать system_tick(); и reti();

ок.

запускается system_tick(); - и, чего?

1void            system_tick(void);

и, не запускается reti(); - нет о нём ничего в коде.

*по идее, должно быть как-то так

1void  system_tick(void) {rtc_time++};

 

sadman41
Offline
Зарегистрирован: 19.10.2016

Клапауций 112 пишет:
и, не запускается reti(); - нет о нём ничего в коде.

*по идее, должно быть как-то так

1void  system_tick(void) {rtc_time++};

Насчет reti() не знаю, а вот system_tick(), как я понял, наращивает volatile time_t __system_time. Для чего? Возможно для портированного кода, который захочет получить текущее время через стандартную time(), которая и возвращает эту глобальную переменную.

Клапауций 112
Клапауций 112 аватар
Offline
Зарегистрирован: 01.03.2017

sadman41 пишет:

а вот system_tick(), как я понял, наращивает volatile time_t __system_time.

а, как ты это понял? - где находится код, который что-то наращивает?

и, где смотреть __system_time ?

sadman41
Offline
Зарегистрирован: 19.10.2016

avr-libc-2.0.0/libc/time/

time.c

01extern volatile time_t __system_time;
02 
03time_t
04time(time_t * timer)
05{
06    time_t          ret;
07 
08    asm             volatile(
09                               "in __tmp_reg__, __SREG__" "\n\t"
10                                 "cli" "\n\t"
11                 ::
12    );
13    ret = __system_time;
14    asm             volatile(
15                              "out __SREG__, __tmp_reg__" "\n\t"
16                 ::
17    );
18    if (timer)
19        *timer = ret;
20    return ret;
21}

system_tick.S:

01/* $Id: system_tick.S 2348 2013-04-16 23:42:05Z swfltek $ */
02 
03/*
04    Impoved system_tick Credit to Wouter van Gulik.
05*/
06 
07#include <avr/common.h>
08 
09    .global system_tick
10    .type   system_tick, @function
11system_tick:
12    push r24
13    in r24,_SFR_IO_ADDR(SREG)
14    push r24
15    cli
16    lds r24,__system_time+0
17    subi r24, (-1)
18    sts __system_time+0,r24
19    lds r24,__system_time+1
20    sbci r24, (-1)
21    sts __system_time+1,r24
22    lds r24,__system_time+2
23    sbci r24, (-1)
24    sts __system_time+2,r24
25    lds r24,__system_time+3
26    sbci r24, (-1)
27    sts __system_time+3,r24
28    pop r24
29    out _SFR_IO_ADDR(SREG),r24
30    pop r24
31    ret
32    .size   system_tick, .-system_tick

 

Клапауций 112
Клапауций 112 аватар
Offline
Зарегистрирован: 01.03.2017

ок. спасибо.

uni
uni аватар
Offline
Зарегистрирован: 24.09.2015

Я своей функцией пользуюсь для пересчёта из time_t, она входит в состав логгера для telnet сервера (GetTimeAsSystemTime).

Клапауций 112
Клапауций 112 аватар
Offline
Зарегистрирован: 01.03.2017

uni пишет:

Я своей функцией пользуюсь для пересчёта из time_t, она входит в состав логгера для telnet сервера (GetTimeAsSystemTime).

its() ?
 
ua6em
ua6em аватар
Offline
Зарегистрирован: 17.08.2016

Евгений, а как проверить насколько правильно сия библиотека рассчитывает локальное время? Могу к примеру сказать для любого города точное локальное время. Сравним?
 

sadman41
Offline
Зарегистрирован: 19.10.2016

ua6em пишет:
Евгений, а как проверить насколько правильно сия библиотека рассчитывает локальное время? Могу к примеру сказать для любого города точное локальное время. Сравним?

Очень точно рассчитывает. Только UTC offset задайте правильно ;)

uni
uni аватар
Offline
Зарегистрирован: 24.09.2015

Есть специальный онлайн сервис, где можно конвертировать время: Epoch & Unix Timestamp Conversion Tools

На самом деле нет никаких "нестандартных и недокументированных служб". Есть давно стандартизированный набор unix'овых функций для преобразования данных о времени в разные формы (в т.ч. строковый). Поскольку математика работы со временем одна, то нестандартной она быть не может. В моём случае я описал по-русски почти каждое действие. Существует несколько программных реализаций подобных алгоритмов.

Также нужно иметь в виду, что начало отсчёта называется эпохой. Дату 1970 называют эпохой unix. Вы можете выбрать любую удобную для вас дату, имея в виду, что интервал времени в 32-битном числе получается не такой уж и большой. Для эпохи unix в 2030-х годах наступит переполнение для устройств, которые используют 32-битные счётчики. Если же эту дату отодвинуть чуть на попозже, то и время аппокалипсиса тоже отодвинется. Поэтому некоторые товарищи берут за начала отсчёта другие даты. Об этом можно почитать в википедии.

ua6em
ua6em аватар
Offline
Зарегистрирован: 17.08.2016

sadman41 пишет:

ua6em пишет:
Евгений, а как проверить насколько правильно сия библиотека рассчитывает локальное время? Могу к примеру сказать для любого города точное локальное время. Сравним?

Очень точно рассчитывает. Только UTC offset задайте правильно ;)

А код не сможете привести, ну я ни разу не программист, сам точно не напишу

sadman41
Offline
Зарегистрирован: 19.10.2016

uni пишет:
Поэтому некоторые товарищи берут за начала отсчёта другие даты. Об этом можно почитать в википедии.

В качестве примечания: эти некоторые товарищи точкой отсчета в avr-libc версии <time.h> взяли 00:00:00 01/01/2000, базируясь тем самым на "Y2K epoch", что может доставить определенные проблемы при взаимодействии со внешними системами. Нужно своевременно корректировать timestamp на UNIX_OFFSET. Видимо это и есть нестандартность данной реализации, как и указанно в первом посте.

Клапауций 112
Клапауций 112 аватар
Offline
Зарегистрирован: 01.03.2017

нашлась reti() в interrupt.h

1#  define reti()  __asm__ __volatile__ ("reti" ::)

 

sadman41
Offline
Зарегистрирован: 19.10.2016

ua6em пишет:

А код не сможете привести, ну я ни разу не программист, сам точно не напишу

Вот он, в первом посте:

5 #define MOSCOW_TIME     (3 * ONE_HOUR)
06 ...
10     set_zone(MOSCOW_TIME);
11     const struct tm * timeinfo = localtime(&tt);

Учтите, что действие глобальной переменной __utc_offset, устанавливаемой при помощи set_zone() распространяется только на функции localtime_r(), localtime(), mktime() и частично на strftime(). В отношении последней есть примечание: "All conversions are made using the 'C Locale', ignoring the E or O modifiers. Due to the lack of a time zone 'name', the 'Z' conversion is also ignored.

wdrakula
wdrakula аватар
Offline
Зарегистрирован: 15.03.2016

Клапауций 112 пишет:

нашлась reti() в interrupt.h

1#  define reti()  __asm__ __volatile__ ("reti" ::)

 


....гы!
-Рубашка нашлась, Петька! Она под майкой была! ©

ua6em
ua6em аватар
Offline
Зарегистрирован: 17.08.2016

sadman41 пишет:

 

Учтите, что действие глобальной переменной __utc_offset, устанавливаемой при помощи set_zone() распространяется только на функции localtime_r(), localtime(), mktime() и частично на strftime(). В отношении последней есть примечание: "All conversions are made using the 'C Locale', ignoring the E or O modifiers. Due to the lack of a time zone 'name', the 'Z' conversion is also ignored.

А координаты где вводить?

sadman41
Offline
Зарегистрирован: 19.10.2016

ua6em пишет:
А координаты где вводить?

Координаты мерьканских секретных объектов?

http://www.nongnu.org/avr-libc/user-manual/group__avr__time.html - set_position().

Только на таймзону они никак не влияют, если что.

 

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

ua6em пишет:

А координаты где вводить?

Координаты не влияют на локальное время. Они нужны для определения времени восхода, заката и т.п.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Клапауций 112 пишет:

ТС, можешь не выскакивать с воплями о платности своих услуг

Могу и не выскакивать. Молодец, что запомнил!

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

ua6em пишет:

насколько правильно сия библиотека рассчитывает локальное время? 

Ни на сколько вообще. Вы сами задаёте UTC и сами же задаёте часовой пояс - её дело тупо сложить.

Клапауций 112
Клапауций 112 аватар
Offline
Зарегистрирован: 01.03.2017

ЕвгенийП пишет:

Могу и не выскакивать. Молодец, что запомнил!

таки, не выдержал - выскочил.

Клапауций 112
Клапауций 112 аватар
Offline
Зарегистрирован: 01.03.2017

кто в курсе зачем в разных примерах по разному объявляется структура tm ?

1time_t s = time(NULL);
2const struct tm * seconds = localtime(&s); // 1-й вариант
3      struct tm * seconds = localtime(&s); // 2-й вариант
4             tm * seconds = localtime(&s); // 3-й вариант

 

ua6em
ua6em аватар
Offline
Зарегистрирован: 17.08.2016

sadman41 пишет:

ua6em пишет:
А координаты где вводить?

Координаты мерьканских секретных объектов?

http://www.nongnu.org/avr-libc/user-manual/group__avr__time.html - set_position().

Только на таймзону они никак не влияют, если что.

 

Таки и не должны, они влияют на рассчет слежения трекера за спутниками к примеру, но кеплеровские данные всё равно придётся обновлять, хотя бы раз в месяц

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Пример определения времени восхода солнца для двух разных городов.

Результат работы

Замечание: здесь не учитывается высота данного места над уровнем моря, потому время может на несколько минут отличаться от фактического.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Во, блин! Совсем уж собрался писать про ещё одну нестандартность, но всё не так просто.

Была у меня в древнем, как фортран, проекте под Visual Studio (VS) функция для вычисления дня недели 

и ведь, ЧСХ, знаю, что говнокод, всегда говорил и студентам, и детям, и внукам, что так делать не стоит, но вот такая она у меня  - эта функция.  :-( Служила она мне верой и правдой во многих программах.

Решил перетащить в атмеловскую студию (AS) (всё, что сказано ниже, верно и для Arduino IDE). Что-то пошло не так. Ну, эта функция много лет верой и правдой, потому, её подозреваем в самую последнюю очередь. Час траха, чтобы выяснить, что таки она, сука, неправильно считает. Как же так?!? Ещё немного траха и тут выясняется, что структуры tm в VS и в AS - разные!

Т.е. у майкрософта день недели стоит после месяца и года, а у Атмела - перед - твающ...! 

Ну, чё, решил разобраться, кто не прав: Atmel или Microsoft.

Открываем стандарт языка Си (ISO/IEC 9899:2018, § 7.27.1 Library, page. 285) и читаем

The tm structure shall contain at least the following members, in any order. The semantics of the members and their normal ranges are expressed in the comments.

1int tm_sec; // seconds after the minute - - [0, 60]
2int tm_min; // minutes after the hour - - [0, 59]
3int tm_hour; // hours since midnight - - [0, 23]
4int tm_mday; // day of the month - - [1 , 31 ]
5int tm_mon; // months since January - - [0, 1 1 ]
6int tm_year; // years since 1900
7int tm_wday; // days since Sunday - - [0, 6]
8int tm_yday; // days since January 1 - - [0, 365]
9int tm_isdst; // Daylight Saving Time flag

Вывод: и Майкрософт и Атмел, оба правы. Неправ ЕвгенийП, в том, что использует неназначенную инициализацию структуры!


В порядке придирки отмечу, что у Атмела неверно указан диапазон секунд - 0-59, а должно быть 0-60.

Но самый шок был. когда я полез в казалось бы абсолютно кошерный источник "правильного Си" - https://github.com/torvalds/linux/blob/master/include/linux/time.h и обнаружил, что там-то эта структура стандарту не соответствует - отсутствует поле tm_isdst. Вот так, ни хрена себе!

ua6em
ua6em аватар
Offline
Зарегистрирован: 17.08.2016

Надо Торвальду подсказать )))

brokly
brokly аватар
Offline
Зарегистрирован: 08.02.2014

ЕвгенийП пишет:

 

В порядке придирки отмечу, что у Атмела неверно указан диапазон секунд - 0-59, а должно быть 0-60.

Это очень прикольно. Интересно , почему секунд может быть 60, а минут не может быть ?

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

brokly пишет:
Интересно , почему секунд может быть 60, а минут не может быть ?

Ну, дык, бывает "високосная секунда", а "високосной минуты" не бывает - обидели!

brokly
brokly аватар
Offline
Зарегистрирован: 08.02.2014

Вот оно че...