Урок 83
Часть 2
LAN. ENC28J60. NTP. Узнаём точное время
В предыдущей части нашего урока мы узнали всё необходимое о протоколе NTP и написали код, который отправляет запрос, дождидается ответа от сервера и отображает в терминальной программе мировое время в виде таймштампа в секундах.
Теперь же перед нами стоит задача — время в секундах преобразовать в удобочитаемый формат.
Сначала в файле ntp.h добавим макрос для часового пояса. У нас часовой пояс третий, надеюсь, вы свой знаете
#define LOCAL_PORT_FOR_NTP 14444
#define TIMEZONE 3
Теперь вернёмся в нашу функцию и напишем код преобразования, благо для этого у нас есть библиотека time.h
HAL_UART_Transmit(&huart1,(uint8_t*)str1,strlen(str1),0x1000);
time_t rawtime = (time_t)(be32todword(ntp_pkt->tx_ts.sec)-2208988800+60UL*60*TIMEZONE);
timestruct = localtime(&rawtime);
sprintf(str1,"%02d.%02d.%04u %02d:%02d:%02d -%d-rn", timestruct->tm_mday,timestruct->tm_mon+1, //месяцы считаются от 0, а не от 1
timestruct->tm_year+1900,timestruct->tm_hour,timestruct->tm_min,timestruct->tm_sec,timestruct->tm_wday);
HAL_UART_Transmit(&huart1,(uint8_t*)str1,strlen(str1),0x1000);
К году прибавляется 1900, так как именно с этого момента они считаются с учетом смещения, это указано и в самой библиотеке time.h.
Смещение мы используем из-за того, что на NTP-серверах время отсчитывается с другого года (разница 20 лет или 2208988800 секунд).
Также мы смещаемся вперёд по секундам на значение часового пояса, умноженного на количество секунд в часе.
Затем нам лишь остаётся всё отобразить в терминальной программе
Соберём ещё раз наш код, прошьём контроллер и посмотрим результат, убедившись, что он соответствует реальному времени
Дни недели считаются в библиотеке от 0 до 6, так что воскресенье будет 0
int tm_wday; /* days since Sunday, 0 to 6 */
При желании можно написать условие, чтобы день 0 превращался в 7, но таких задач перед нами не стоит.
У нас более серьёзная задача — получить время во что бы то не стало, а оно сразу не приходит, поэтому автоматизируем наши запросы ну или попытки. Это оказалось не так-то легко.
Создадим ради этого ещё одну структуру для статуса нашего клиента NTP в файле ntp.h
} ntp_pkt_ptr;
//--------------------------------------------------
typedef struct ntp_prop{
uint8_t ntp_cnt; //количество оставшихся попыток получить время
int32_t ntp_timer; //таймер для следующей попытки
uint8_t set; //флаг получения времени
uint8_t macaddr_dst[6];
uint8_t ip_dst[4];
uint16_t port_dst;//порт получателя
} ntp_prop_ptr;
//----------------------------------------------
В принципе назначение практически каждого поля я осветил в комментариях.
Так как попытки будут осуществляться из другого места, не всегда из net_cmd, то решение было принято адреса все хранить в структуре.
В файле ntp.c создадим переменную типа нашей структуры
extern uint8_t ipaddr[4];
ntp_prop_ptr ntpprop;
В файле net.c её мы также подключим
USART_prop_ptr usartprop;
extern ntp_prop_ptr ntpprop;
В функции инициализации net_ini инициализируем основные поля нашей структуры на всякий случай
enc28j60_ini();
ntpprop.set=0;
ntpprop.ntp_cnt=0;
ntpprop.ntp_timer=0;
В одном из условий в функции net_cmd занесём адрес ip в соответствующее поле нашей структуры
else if(usartprop.is_ip==6)//статус попытки отправить NTP-пакет
{
ip_extract((char*)usartprop.usart_buf,usartprop.usart_cnt,ip);
memcpy(ntpprop.ip_dst,ip,4);
Также в этой же функции мы добавим указатель на наш пакет
static uint16_t port=0;
enc28j60_frame_ptr *frame=(void*)net_buf;
В следующем условии также занесём значения в соответствующие поля структуры и воспользуемся на всякий случай уже ими в вызове функции отправки запроса NTP
else if(usartprop.is_ip==7)//статус отправки NTP-пакета
{
port=port_extract((char*)usartprop.usart_buf,usartprop.usart_cnt);
ntpprop.port_dst = port;
ntpprop.ntp_cnt = 10; //10 попыток
ntpprop.ntp_timer = 5;//5 секунд до следующей попытки
ntpprop.set=1;//флаг запроса времени взведен
memcpy(ntpprop.macaddr_dst,frame->addr_dest,6);
ntp_request(ntpprop.ip_dst,ntpprop.port_dst);
Вообщем здесь мы назначили сделать 10 попыток для запроса NTP, также временной интервал в 5 секунд между попытками и взвели флаг запроса времени, который мы при удачном ответе сервера будем сбрасывать.
Теперь идём в обработчик таймера TIM_PeriodElapsedCallback и добавим там следующее условие, которое определит, установлен ли вообще флаг запроса NTP
clock_cnt++;
if (ntpprop.set)
{
}
Затем в теле добавленного условия мы убавим секунды до следующей попытки
if (ntpprop.set)
{
ntpprop.ntp_timer--;
}
Затем добавим в этом условии ещё одно условие, подтверждающее то, что у нас таймер дошел до нуля, а количество попыток у нас ещё не закончилось
ntpprop.ntp_timer--;
if ((ntpprop.ntp_timer<0)&&(ntpprop.ntp_cnt>0))
{
}
А в теле этого условия мы опять установим время до следующей попытки, убавим количество попыток и отправим запрос NTP серверу, перед этим отобразив в терминальной программе количество оставшихся попыток
if ((ntpprop.ntp_timer<0)&&(ntpprop.ntp_cnt>0))
{
ntpprop.ntp_timer = 5;
ntpprop.ntp_cnt--;
sprintf(str1,"ntp_cnt: %drn",ntpprop.ntp_cnt);
HAL_UART_Transmit(&huart1,(uint8_t*)str1,strlen(str1),0x1000);
ntp_request(ntpprop.ip_dst,ntpprop.port_dst);
}
Выйдем только из этого условия и добавим следующее, в котором сбросим все флаги
ntp_request(ntpprop.ip_dst,ntpprop.port_dst);
}
else if (ntpprop.ntp_cnt<=0)
{
//сбросим все флаги и счетчики
ntpprop.set=0;
ntpprop.ntp_cnt=0;
ntpprop.ntp_timer=0;
}
Перейдём в функцию отправки запроса NTP ntp_request в файл ntp.c и занесём в соответствующее поле MAC-адрес приёмника, так как он вполне может исказиться, если между попытками произойдёт приём или отправка других пакетов, соответственно наш буфер может быть перезаполнен, а в данной функции мы этот адрес нигде явно не заносим
memcpy(frame->addr_src,macaddr,6);
memcpy(frame->addr_dest,ntpprop.macaddr_dst,6);
frame->type=ETH_IP;
Ну и также в функции приёма NTP-пакета от сервера ntp_read мы также сбросим флаги нашей структуры на случай, если пакет придёт не с последней попытки, чтобы нам больше запросы в случае удачного ответа больше не посылать
HAL_UART_Transmit(&huart1,(uint8_t*)str1,strlen(str1),0x1000);
//сбросим все флаги и счетчики
ntpprop.set=0;
ntpprop.ntp_cnt=0;
ntpprop.ntp_timer=0;
return res;
}
Соберём код. прошьём контроллер и посмотрим результат нашего труда
Ну вот, сервер вернул время аж с 9 попытки, обычно где-то со 2 или 3-й. С 1-й очень редко.
Также я пробовал запустить бесконечные пинги на наш модуль с компьютера и в это время запросить также точное время. Сервер его возвращал. Поэтому в таком случае также всё у нас работает.
Вы можете устанавливать свои величины времени до следующей попытки, также своё количество попыток.
Самое главное, чтобы вы поняли сам смысл работы протоколов и рассчётов в них, что, я надеюсь вам дал. Конечно, не обязательно, всё делать именно таким образом. Вы можете придумать свой способ запроса времени. Тем не менее, надеюсь, что мой урок вам пойдёт на пользу и свою мысль или своё видение данного вопроса мне передать вам удалось.
Когда-нибудь, вооружившись знаниями из этого урока, мы, возможно напишем какие-нибудь часы с синхронизацией из интернет.
Спасибо за внимание!
Предыдущая часть Программирование МК STM32 Следующий урок
Отладочную плату STM32F103C8T6 можно приобрести здесь STM32F103C8T6
Программатор недорогой можно купить здесь ST-Link V2
Ethernet LAN Сетевой Модуль можно купить здесь ENC28J60 Ethernet LAN Сетевой Модуль.
Переходник USB to TTL можно приобрести здесь ftdi ft232rl
Смотреть ВИДЕОУРОК в RuTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)
Добавить комментарий