В предыдущей части занятия мы создали проект, настроили модуль RTC и начали готовить запрос на точное мировое время.
Вернёмся в функцию ntp_thread и занесём значение системного таймера в переменную, хранящую старое значение
1 2 |
ntpmsg->li_vn_mode = 0x00 | (4<<3) | 0x03; //version: 4, mode: client old_time_count = osKernelSysTick(); |
Отправим запрос на NTP-сервер, так как кроме первого байта нам ничего в нём заполнять не нужно и очистим буфер
1 2 3 |
old_time_count = osKernelSysTick(); netconn_send(conn,buf); netbuf_delete(buf); |
С данной функцией мы пока не закончили, мы ещё сюда вернёмся.
Теперь займёмся приёмом и перейдём в функцию обратного вызова ntp_receive_callback.
Объявим в ней две переменных структур даты и времени
1 2 3 |
char str[60]; RTC_TimeTypeDef user_sTimeStructure; RTC_DateTypeDef user_sDateStructure; |
Если нам что-то пришло, то попробуем принять пакет
1 2 3 4 5 |
RTC_DateTypeDef user_sDateStructure; if(evt==NETCONN_EVT_RCVPLUS) { recv_err = netconn_recv(conn, &buf); } |
Добавим локальную переменную для режима
1 2 |
char str[60]; u8_t mode; |
Если пришёл нужный нам пакет и причём нужной нам длины, то скопируем в нашу переменную из приемного буфера самый первый байт с помощью специальной функции и, если мы попадём в условие верхнего уровня, то после тела условия нижнего уровня очистим буфер
1 2 3 4 5 6 7 8 9 |
recv_err = netconn_recv(conn, &buf); if (recv_err == ERR_OK) { if (buf->p->tot_len == 48) { pbuf_copy_partial(buf->p, &mode, 1, 0); } netbuf_delete(buf); } |
Оставим в значении режима только младшие три бита, остальные очистим
1 2 |
pbuf_copy_partial(buf->p, &mode, 1, 0); mode &= 0x07; //mode mask |
Добавим ещё две локальных переменных для таймштампа и значения в секундах
1 2 3 |
u8_t mode; u32_t receive_timestamp; u32_t rx_secs; |
Если режим четвёртый (сервер), то заберём старшую часть таймштампа из принятого буфера по 40-му адресу. Это старшая часть нашего времени. Младшая часть находится в следующих завершающих четырёх байтах, это дробная часть секунд, она нам не нужна.
Так как время в таймштампе хранится в перевёрнутом виде, то перевернём его обратно с ног на голову и присвоим переменной для секунд
1 2 3 4 5 6 |
mode &= 0x07; //mode mask if(mode == 0x04) { pbuf_copy_partial(buf->p, &receive_timestamp, 4, 40); rx_secs = lwip_ntohl(receive_timestamp); } |
Подключим глобальную переменную, а также периферию RTC
1 2 3 4 5 |
# include "arch/bpstruct.h" #endif //--------------------------------------------------------------- extern RTC_HandleTypeDef hrtc; extern uint32_t time_count; |
Вернёмся в функцию ntp_receive_callback и покажем на дисплее наши секунды
1 2 3 |
rx_secs = lwip_ntohl(receive_timestamp); sprintf(str,"%10lu: %lu", time_count, rx_secs); TFT_DisplayString(10, 100, (uint8_t *)str, LEFT_MODE); |
Добавим ещё 2 локальные переменные и структуру для хранения полей времени
1 2 3 4 |
u32_t rx_secs; int is_1900_based; u32_t t; struct tm *timestruct; |
Добавим два макроса для типов смещения секунд и для часового пояса
1 2 3 4 5 6 |
#include <time.h> //--------------------------------------------------------------- #define RANGE_SEC_1900_1970 (2208988800UL) #define RANGE_SEC_1970_2036 (2085978496UL) #define TIMEZONE 3 //--------------------------------------------------------------- |
Добавим локальную переменную в нашу функцию ntp_receive_callback
1 2 |
struct tm *timestruct; time_t tim1; |
Разложим наше значение в секундах по полям структуры, применив специальную функцию из библиотеки time.h, перед этим скорректировав временную зону и узнав по самому старшему биту, какой формат секунд у нас пришел, то есть какой из двух типов смещения
1 2 3 4 5 |
TFT_DisplayString(10, 100, (uint8_t *)str, LEFT_MODE); is_1900_based = ((rx_secs & 0x80000000) != 0); t = is_1900_based ? (rx_secs - RANGE_SEC_1900_1970) : (rx_secs + RANGE_SEC_1970_2036); tim1 = t + 60UL*60*TIMEZONE; timestruct = localtime(&tim1); |
Из структуры tim1 перепишем значения в наши структуры модуля RTC
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
timestruct = localtime(&tim1); user_sTimeStructure.Hours = timestruct->tm_hour; user_sTimeStructure.Minutes = timestruct->tm_min; user_sTimeStructure.Seconds = timestruct->tm_sec; if (HAL_RTC_SetTime(&hrtc, &user_sTimeStructure, RTC_FORMAT_BIN) != HAL_OK) { _Error_Handler(__FILE__, __LINE__); } user_sDateStructure.WeekDay = timestruct->tm_wday; user_sDateStructure.Month = timestruct->tm_mon + 1; user_sDateStructure.Date = timestruct->tm_mday; user_sDateStructure.Year = (uint8_t) timestruct->tm_year % 100; if (HAL_RTC_SetDate(&hrtc, &user_sDateStructure, RTC_FORMAT_BIN) != HAL_OK) { _Error_Handler(__FILE__, __LINE__); } |
А теперь опять сохраним значение системных квантов но уже в другую переменную, которая хранит новое значение системных квантов
1 2 3 |
_Error_Handler(__FILE__, __LINE__); } new_time_count = osKernelSysTick(); |
А теперь нам надо передать данное значение в функцию ntp_thread. И, конечно же, ничего больше не приходит в голову. как воспользоваться очередью, тем более она у нас пустует.
Перейдём в файл main.c и переименуем идентификатор нашей очереди
osMessageQId timeout_Queue;
Также переименуем данный идентификатор и в создании очереди в main()
osMessageQDef(timeout_Queue, QUEUE_SIZE, uint16_t);
timeout_Queue = osMessageCreate(osMessageQ(timeout_Queue), NULL);
В файле ntp.c подключим наш идентификатор
1 2 |
extern uint32_t time_count; extern osMessageQId timeout_Queue; |
Вернёмся в нашу функцию ntp_receive_callback и, если у нас превышен таймаут, то пошлём символическую единичку в очередь
1 2 3 4 5 |
new_time_count = osKernelSysTick(); if(new_time_count - old_time_count < 3000) { osMessagePut(timeout_Queue, 1, 100); } |
Примем нашу единичку в функции ntp_thread, в которой сначала добавим локальную переменную
1 2 3 |
void ntp_thread(void *arg) { osEvent event; |
Подождём немного, это будет первый таймаут
1 2 3 |
netbuf_delete(buf); } osDelay(20000); |
Вот это вот и будет наш первый таймаут, можно его в любой момент изменить, то есть сделать больше или меньше, это и будет период обращений клиента к серверу в случае неудачных попыток.
А теперь примем значение из очереди, если оно там, конечно есть
1 2 |
osDelay(20000); event = osMessageGet(timeout_Queue, 100); |
Добавим локальную переменную и обнулим её
1 2 |
struct netbuf *buf; volatile uint16_t timeout = 0; |
То есть сразу эта переменная будет в нуле, потому что у нас же в очередь пока ничего не придёт, если мы первый раз запустили задачу.
Примем значение из очереди и присвоим его нашей переменной
1 2 3 4 5 |
event = osMessageGet(timeout_Queue, 100); if (event.status == osEventMessage) { timeout = event.value.v; } |
И если у нас придёт единичка, то мы тогда будем считать, что у нас ответ нормально пришёл, вот тогда-то мы уже подождём побольше. Это и будет большой таймаут между обращениями к серверу уже с целью синхронизации часов, а не с целью повторного запроса вследствие неудачных. Если нам сервер вообще не ответит, то мы в очереди не получим ничего и там останется ноль.
Добавим наш второй таймаут, который мы применяем только в случае удачного флага, пришедшего из очереди
1 2 3 4 5 6 7 |
timeout = event.value.v; } if(timeout==1) { osDelay(40000); timeout = 0; } |
У нас получится тогда общая задержка — одна минута так как первую задержку мы всё равно ждём, а если неудачный случай, то только 20 секунд.
Потом мы второй таймаут увеличим. Сейчас мы его сделали небольшим, чтобы дождаться повторного запроса и ответа в удачном случае.
И ещё вот что. Мы обнуляем нашу переменную, чтобы опять сделать попытку отправки запроса на сервер и в неудачном случае чтобы у нас не осталась единичка, означающая удачный.
Вот как-то так.
Наконец-то соберём наш проект и проверим работу нашего кода на практике.
Сначала мы должны сразу получить ответ без всяких таймаутов. Ну это если, конечно, сервер существует и он свободен
Затем мы даже при удачном случае получим пакет примерно через 60 секунд, если конечно нам придёт единичка в очереди
И так, если всё нормально, то мы будем запрашивать и получать пакеты через 60 секунд
Мы проверили, что наш код работает правильно, все пакеты запрашиваются и получаются.
Значение нашего таймера на дисплее у нас уменьшено в 10 раз, так как мы обновляем значение времени раз в 10 милисекунд в функции netconn_thread.
Теперь давайте увеличим задержку между запросами, сделаем её хотя бы час, а то сервер может и разозлиться
1 2 3 |
if(timeout==1) { osDelay(3580000); |
Таким образом, в данном уроке мы научились работать с протоколом NTP, используя стек протоколов LWIP и интерфейс NETCONN, с помощью чего мы теперь можем получать точное мировое время и использовать его значение в своих нуждах. Мы его использовали в данном уроке для синхронизации времени модуля RTC.
Всем спасибо за внимание!
Предыдущая часть Программирование МК STM32 Следующий урок
Отладочную плату можно приобрести здесь 32F746G-DISCOVERY
Смотреть ВИДЕОУРОК (нажмите на картинку)
Добавить комментарий