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



 

Урок 47

 

Часть 2

 

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

 

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

 

Теперь перед нами стоит задача — время в секундах преобразовать в удобочитаемый формат.

Сначала в файле ntp.h добавим макрос для часового пояса. У нас часовой пояс третий, надеюсь, вы свой знаете

 

#define LOCAL_PORT_FOR_NTP 14444

#define TIMEZONE 3

 

Теперь вернёмся в нашу функцию и напишем код преобразования, благо для этого у нас есть библиотека time.h

 

USART_TX((uint8_t*)str1,strlen(str1));

time_t rawtime = (time_t)(be32todword(ntp_pkt->tx_ts.sec)-NTP_OFFSET+60UL*60*TIMEZONE);

timestruct = localtime(&rawtime);

 

Здесь мы уже используем для временного хранения ещё одну структуру — time_t для установки указателя в соответствующее место пакета NTP. Также сделаем смещение на формат NTP, так как он предусматртивает отсчёт времени от другой даты, нежели стандартный таймштамп, а также смещаемся на наш часовой пояс, умножив его соответственно на кол-во секунд в часе. Затем мы с помощью функции localtime структурируем сырое значение времени в удобочитаемое.

Ну и останется нам всё это показать в терминальной программе

 

  timestruct = localtime(&rawtime);

  sprintf(str1,"%02d.%02d.%04u %02d:%02d:%02d -%d-\r\n", 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);

  USART_TX((uint8_t*)str1,strlen(str1));

  return res;

}

 

К году прибавляется 1900, так как именно с этого момента они считаются с учетом смещения, это указано и в самой библиотеке time.h.

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

 

 image08

 

День недели у нас нулевой, так как сегодня воскресенье, а дни недели считаются в библиотеке от 0 до 6

 

int8_t tm_wday; /**< days since Sunday - [ 0 to 6 ] */

 

При желании можно написать условие и открорректировать это, но таких задач перед нами не стоит.

У нас более серьёзная задача — получить время во что бы то не стало, а оно сразу не приходит, поэтому автоматизируем наши запросы ну или попытки. Это оказалось не так-то легко.

Создадим ради этого ещё одну структуру для статуса нашего клиента 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];

  uint32_t ip_dst;

  uint16_t port_dst;//порт получателя

} ntp_prop_ptr;

//----------------------------------------------

 

В принципе назначение каждого поля я осветил в комментариях.

 

 

Так как попытки будут осуществляться из другого места, не всегда из net_cmd, то решение было принято адреса все хранить в структуре.

В файле ntp.c создадим переменную типа нашей структуры

 

extern uint8_t macaddr[6];

ntp_prop_ptr ntpprop;

 

В файле net.c её мы также подключим

 

extern USART_prop_ptr usartprop;

extern ntp_prop_ptr ntpprop;

 

В функции инициализации инициализируем основные поля нашей структуры на всякий случай

 

enc28j60_ini();

ntpprop.set=0;

ntpprop.ntp_cnt=0;

ntpprop.ntp_timer=0;

 

В одном из условий в функции net_cmd занесём адрес ip в соответствующее поле нашей структуры

 

else if(usartprop.is_ip==6)//статус попытки отправить NTP-пакет

{

  ip=ip_extract((char*)usartprop.usart_buf,usartprop.usart_cnt);

  ntpprop.ip_dst = ip;

 

Также в этой же функции мы добавим указатель на наш пакет

 

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 секунд между попытками и взвели флаг запроса времени, который мы при удачном ответе сервера будем сбрасывать.

Теперь идём в обработчик таймера ISR (TIMER0_COMPA_vect) и добавим там следующее условие, которое определит, установлен ли вообще флаг запроса 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: %d\r\n",ntpprop.ntp_cnt);

  USART_TX((uint8_t*)str1,strlen(str1));

  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 мы также сбросим флаги нашей структуры на случай, если пакет придёт не с последней попытки, чтобы нам больше запросы в случае удачного ответа больше не посылать

 

  USART_TX((uint8_t*)str1,strlen(str1));

  //сбросим все флаги и счетчики

  ntpprop.set=0;

  ntpprop.ntp_cnt=0;

  ntpprop.ntp_timer=0;

  return res;

}

 

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

 

image09

 

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

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

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

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

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

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

 

 

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

 

Исходный код

 

 

Приобрести плату Arduino UNO R3 можно здесь.

Программатор (продавец надёжный) USBASP USBISP 2.0

Ethernet LAN Сетевой Модуль можно купить здесь (модуль SD SPI в подарок) ENC28J60 Ethernet LAN Сетевой Модуль.

 

 

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

 

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

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

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

*