STM Урок 83. LAN. ENC28J60. NTP. Узнаём точное время. Часть 2



 

Урок 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 секунд).

Также мы смещаемся вперёд по секундам на значение часового пояса, умноженного на количество секунд в часе.

Затем нам лишь остаётся всё отобразить в терминальной программе

Соберём ещё раз наш код, прошьём контроллер и посмотрим результат, убедившись, что он соответствует реальному времени

 

image01

 

Дни недели считаются в библиотеке от 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;

}

 

Соберём код. прошьём контроллер и посмотрим результат нашего труда

 

image02

 

Ну вот, сервер вернул время аж с 9 попытки, обычно где-то со 2 или 3-й. С 1-й очень редко.

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

Вы можете устанавливать свои величины времени до следующей попытки, также своё количество попыток.

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

Когда-нибудь, вооружившись знаниями из этого урока, мы, возможно напишем какие-нибудь часы с синхронизацией из интернет.

Спасибо за внимание!

 

 

Предыдущая часть Программирование МК STM32 Следующий урок

 

Исходный код

 

 

Отладочную плату STM32F103C8T6 можно приобрести здесь STM32F103C8T6

Программатор недорогой можно купить здесь ST-Link V2

Ethernet LAN Сетевой Модуль можно купить здесь ENC28J60 Ethernet LAN Сетевой Модуль.

Переходник USB to TTL можно приобрести здесь ftdi ft232rl

 

 

Смотреть ВИДЕОУРОК в RuTube (нажмите на картинку)

STM LAN. ENC28J60. NTP. Узнаём точное время

 

Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)

STM LAN. ENC28J60. NTP. Узнаём точное время

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

*