Продолжаем работу с сетью LAN, библиотекой стека протоколов LWIP, её интерфейсом NETCON.
Сегодня мы ещё раз поработаем с протоколом транспортного уровня, так сказать, закрепим практически лишний раз наши знания данного протокола, но только целью сегодняшнего занятия мы поставим себе задачу — получить точное время с одного из серверов точного времени по протоколу NTP (Network Time Protocol), который по модели OSI является уже протоколом прикладного уровня и несёт в себе уже осознанную полезную нагрузку. С протоколами такого уровня мы уже много раз встречались. Поэтому, думаю, будет не тяжело. Тем более с протоколом NTP мы работали и не раз, причём работали также с использованием контроллера STM32 в уроке 83, поэтому изучать нам его уже не придётся, мы будем уже пользоваться накопленными знаниями и использовать данный протокол в нашем проекте. Мы будем использовать внутренний часовой модуль RTC нашего контроллера STM32F746, и мы не будем устанавливать сами время для данного модуля, а произведём синхронизацию его показаний с мировым временем, которое мы и получим благодаря протоколу NTP. Тем более были многочисленные просьбы поработать с модулем RTC. Поэтому, думаю, урок будет для многих весьма полезным.
Что ж, приступим. Начнём сразу с проекта, который будет сделан из проекта прошлого занятия с именем LAN8742_WEBSOCKET_NETCONN и назван будет, соответственно, LAN8742_NTP_CLIENT_NETCONN.
После копирования папок Inc и Src в новый проект следует из них удалить все файлы, кроме тех, которые мы создавали сами, чтобы после того, когда мы отключим некоторые пункты в Cube MX, лишние файлы там не оставались, да и те, которые удалены нужные, создадутся по-новому, что даже лучше.
Теперь откроем наш проект в Cube MX и отключим сначала библиотеку mbedtls
Отключим также генератор случайных чисел
Включим модуль RTC
Перейдём в Configuration и в разделе LWIP отключим HTTPD
В разделе FREERTOS немного увеличим приоритет задачи вывода строк на дисплей
А в настройках RTC изменим формат данных
Прежде чем генерировать проект, немного подправим код в файлах библиотек DMA2D, чтобы две строки в main.c на нас не ругались.
Для этого откроем файл stm32f7xx_hal_dma2d.h по пути «C:\Users\Имя вашего пользователя\STM32Cube\Repository\STM32Cube_FW_F7_V1.12.0\Drivers\STM32F7xx_HAL_Driver\Inc» и закомментируем вот здесь четыре строки
И здесь тоже закомментируем четыре строки
Сохраним файл, сгенерируем проект для System Workbench и откроем его там. Установим уровень оптимизации в 1, уберём при наличии отладочные настройки и теперь неизвестных компилятору строк в файле main.c не будет. Сами строки будут, но они будут уже известными.
Для начала удалим вот эти строки
#include «mbedtls/sha1.h»
#include «mbedtls/base64.h»
Затем вот эти
char guid_str[] = {«258EAFA5-E914-47DA-95CA-C5AB0DC85B11»};
unsigned char hash[20] = {0};
osThreadId ThreadTCPHandle;
Удалим вот эти массивы, хотя в статье прошлого урока они вроде бы удалены, но в проекте, который я выложил, они почему-то остались
char str1[60];
char str_buf[1000]={'\0'};
В функции main() изменим шапку
TFT_DisplayString(0, 10, (uint8_t *)"NTP Client", CENTER_MODE);
Удалим вместе с телами функции StrSort, tcp_send и http_thread.
Функцию tcp_thread переименуем в netconn_thread и удалим из неё всё тело.
В функции задачи по умолчанию StartDefaultTask удалим инициализацию LWIP, иначе теперь он у нас инициализируется дважды
/* USER CODE BEGIN 5 */
MX_LWIP_Init();
Вместо этого мы установим цвет текста
1 2 |
/* USER CODE BEGIN 5 */ TFT_SetTextColor(LCD_COLOR_LIGHTBLUE); |
В следующей строке изменим имя задачи и её функции
sys_thread_new("netconn_thread", netconn_thread, NULL, DEFAULT_THREAD_STACKSIZE, osPriorityNormal);
В структуру вывода строки на печать добавим возможность задавать горизонтальную координату и размер шрифта
1 2 3 4 5 6 7 |
typedef struct struct_out_t { uint32_t tick_count; uint16_t x_pos; uint16_t y_pos; sFONT sfont; char str[60]; } struct_out; |
А в теле самой функции TaskStringOut установим заданный размер шрифта из очереди
1 2 |
qstruct = event.value.p; TFT_SetFont(&qstruct->sfont); |
А затем не забудем установить также из очереди и координату x
TFT_DisplayString(qstruct->x_pos, qstruct->y_pos, (uint8_t *)str_out, LEFT_MODE);
В функции netconn_thread добавим указатель на данную структуру
1 2 3 |
static void netconn_thread(void *arg) { struct_out *qstruct; |
Далее добавим переменные для даты и времени
1 2 3 |
struct_out *qstruct; RTC_TimeTypeDef sTimeStructure; RTC_DateTypeDef sDateStructure; |
Создадим два файла для библиотеки NTP — ntp.h и ntp.c в соответствующих папках Inc и Src проекта в дереве проектов, чтобы они сразу и подключились в наш проект.
Данные файлы первоначально будут иметь следующее содержание
ntp.h:
1 2 3 4 5 |
#ifndef NTP_H_ #define NTP_H_ //-------------------------------------------------- //-------------------------------------------------- #endif /* NTP_H_ */ |
ntp.c:
1 2 3 4 5 6 7 8 9 10 11 |
#include "ntp.h" #include "cmsis_os.h" #include "stdint.h" #include "lwip/opt.h" #include "lwip/opt.h" #include "lwip/arch.h" #include "lwip/api.h" #include "lwip/timeouts.h" #include "ltdc.h" #include <time.h> //--------------------------------------------------------------- |
Подключим нашу библиотеку также и в main.c
1 2 |
#include "fonts.h" #include "ntp.h" |
В файле ntp.c создадим функцию для задачи отправки пакетов NTP-серверу
1 2 3 4 5 6 7 8 9 10 |
#include <time.h> //--------------------------------------------------------------- void ntp_thread(void *arg) { for(;;) { osDelay(1); } } //--------------------------------------------------------------- |
Создадим на данную функцию прототип в заголовочном файле, перейдём в файл main.c и добавим глобальный счётчик
1 2 |
#define QUEUE_SIZE (uint32_t) 1 uint32_t time_count = 0; |
В функции netconn_thread создадим нашу задачу
1 2 |
RTC_DateTypeDef sDateStructure; sys_thread_new("ntp_thread", ntp_thread, NULL, DEFAULT_THREAD_STACKSIZE, osPriorityNormal ); |
Здесь же добавим бесконечный цикл с задержкой
1 2 3 4 5 |
sys_thread_new("ntp_thread", ntp_thread, NULL, DEFAULT_THREAD_STACKSIZE, osPriorityNormal ); for(;;) { osDelay(10); } |
Узнаем время, правда не мировое, а у нашего модуля RTC, занеся его в наши соответствующие структуры
1 2 3 |
osDelay(10); HAL_RTC_GetTime(&hrtc, &sTimeStructure, RTC_FORMAT_BIN); HAL_RTC_GetDate(&hrtc, &sDateStructure, RTC_FORMAT_BIN); |
Затем отобразим значение нашего счётчика, а также дату и время из структуры на дисплее, и по окончанию цикла увеличим счётчик на 1
1 2 3 4 5 6 7 8 9 10 11 |
HAL_RTC_GetDate(&hrtc, &sDateStructure, RTC_FORMAT_BIN); qstruct = osMailAlloc(strout_Queue, osWaitForever); qstruct->x_pos = 0; qstruct->y_pos = 50; qstruct->sfont = Font20; sprintf((char *)qstruct->str, "%10lu: %.d-%.2d-%.2d -%d- %.2d:%.2d:%.2d", time_count, sDateStructure.Date, sDateStructure.Month, sDateStructure.Year, sDateStructure.WeekDay, sTimeStructure.Hours, sTimeStructure.Minutes, sTimeStructure.Seconds); osMailPut(strout_Queue, qstruct); osMailFree(strout_Queue, qstruct); time_count++; |
Соберём код, прошьём контроллер и посмотрим результат на дисплее
Счётчик отлично инкрементируется, часы ходят, правда пока ещё не синхронизируются, но это только дело времени.
Перейдём теперь в файл ntp.c в функцию ntp_thread и добавим в неё некоторые переменные и структуры
1 2 3 4 5 6 |
void ntp_thread(void *arg) { struct netconn *conn; err_t err; ip_addr_t DestIPaddr; struct netbuf *buf; |
Создадим структуру для соединения по протоколу UDP с функцией обратного вызова
1 2 |
struct netbuf *buf; conn = netconn_new_with_callback(NETCONN_UDP,ntp_receive_callback); |
Теперь создадим прототип функции обратного вызова в этом же файле
1 2 3 4 |
#include <time.h> //--------------------------------------------------------------- void ntp_receive_callback(struct netconn* conn, enum netconn_evt evt, u16_t len); //--------------------------------------------------------------- |
И также в самом низу файла добавим данную функцию, в которой сразу добавим переменную, указатель и массив
1 2 3 4 5 6 7 8 |
//--------------------------------------------------------------- void ntp_receive_callback(struct netconn* conn, enum netconn_evt evt, u16_t len) { err_t recv_err; struct netbuf *buf; char str[60]; } //--------------------------------------------------------------- |
Вернёмся пока в функцию ntp_thread и уложим адрес сервера NTP в структуру
1 2 |
conn = netconn_new_with_callback(NETCONN_UDP,ntp_receive_callback); IP4_ADDR(&DestIPaddr, 85, 21, 78, 23); |
Как и где найти адрес сервера, я рассказывал в уроке 83, а также вы можете использовать и этот — у меня с ним проблем не было.
Далее, при успешно создании структуры, свяжем её с нашим локальным портом, выбрав любой пришедший в голову
1 2 3 4 5 |
IP4_ADDR(&DestIPaddr, 85, 21, 78, 23); if (conn!= NULL) { err = netconn_bind(conn, NULL, 1551); } |
При успешной связи с портом, мы пытаемся соединиться с сервером NTP, здесь уже используется порт 123, а если связь не с портом не удалась, то уничтожаем нашу структуру соединения
1 2 3 4 5 6 7 8 9 |
err = netconn_bind(conn, NULL, 1551); if (err == ERR_OK) { err = netconn_connect(conn, &DestIPaddr, 123); } else { netconn_delete(conn); } |
При успешном соединении с сервером создадим бесконечный цикл, в котором создадим структуру буфера и выделим под неё память 48 байт, сколько именно требуется для пакета NTP
1 2 3 4 5 6 7 8 9 |
err = netconn_connect(conn, &DestIPaddr, 123); if (err == ERR_OK) { for(;;) { buf = netbuf_new(); netbuf_alloc(buf, 48); } } |
Далее нам надо будет что-то отправить серверу, для этого создадим глобальную структуру
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
#include <time.h> //--------------------------------------------------------------- #ifdef PACK_STRUCT_USE_INCLUDES # include "arch/bpstruct.h" #endif PACK_STRUCT_BEGIN struct ntp_msg_struct { PACK_STRUCT_FLD_8(u8_t li_vn_mode); PACK_STRUCT_FLD_8(u8_t stratum); PACK_STRUCT_FLD_8(u8_t poll); PACK_STRUCT_FLD_8(u8_t precision); PACK_STRUCT_FIELD(u32_t root_delay); PACK_STRUCT_FIELD(u32_t root_dispersion); PACK_STRUCT_FIELD(u32_t reference_identifier); PACK_STRUCT_FIELD(u32_t reference_timestamp[2]); PACK_STRUCT_FIELD(u32_t originate_timestamp[2]); PACK_STRUCT_FIELD(u32_t receive_timestamp[2]); PACK_STRUCT_FIELD(u32_t transmit_timestamp[2]); } PACK_STRUCT_STRUCT; PACK_STRUCT_END #ifdef PACK_STRUCT_USE_INCLUDES # include "arch/bpstruct.h" #endif //-------------------------------------------------- |
Это стандартная структура, пакета NTP, также мы здесь применили выравнивание полей.
Возвращаемся в нашу функцию ntp_thread и при успешном создании структуры буфера добавим переменную-указатель на такую структуру, который приравняем к буферу, который будем отправлять на сервер
1 2 3 4 5 |
netbuf_alloc(buf, 48); if (buf != NULL) { struct ntp_msg_struct *ntpmsg = (struct ntp_msg_struct *)buf->p->payload; } |
Обнулим все поля структуры
1 2 |
struct ntp_msg_struct *ntpmsg = (struct ntp_msg_struct *)buf->p->payload; memset(ntpmsg, 0, 48); |
В первый байт занесём версию протокола и режим клиента
1 2 |
memset(ntpmsg, 0, 48); ntpmsg->li_vn_mode = 0x00 | (4<<3) | 0x03; //version: 4, mode: client |
Если вдруг нам за какое-то определённое время сервер не пришлёт ответ, то нам нужно будет послать запрос повторно, а если сервер ответит за это время, то таймаут будет уже другой. Его обычно рассчитывают по точности своих часов. Если они например сбиваются за сутки, то запрашиваем раз в сутки. Если сбиваются за час, то запрашиваем раз в час и т.д. Пока мы данные таймауты настроим поменьше, не ждать же нам часами. Только вот как же нам всё это организовать?
Есть на этот счёт специальная функция в LWIP, но она мне не понравилась. Сделал всё по-своему.
Добавим две глобальные переменные в ntp.c
1 2 3 4 |
# include "arch/bpstruct.h" #endif //--------------------------------------------------------------- uint32_t old_time_count = 0, new_time_count = 0; |
Одна из них будет хранить старое значение системного таймера, а вторая — новая, вот по разнице между их значениями мы и будем следить за таймаутами.
В следующей части занятия мы отправим пакет серверу, убедимся что ответ пришел вовремя, а также проверим наш код на практике.
Предыдущий урок Программирование МК STM32 Следующая часть
Отладочную плату можно приобрести здесь STM32F746G-DISCOVERY
Смотреть ВИДЕОУРОК (нажмите на картинку)
Добавить комментарий