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

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

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

 

 

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

 

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

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

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

*