Урок 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.
Соберём ещё раз наш код, прошьём контроллер и посмотрим результат, убедившись, что он соответствует реальному времени
День недели у нас нулевой, так как сегодня воскресенье, а дни недели считаются в библиотеке от 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;
}
Соберём код. прошьём контроллер и посмотрим результат нашего труда
Ну вот, сервер вернул время с 5 попытки, обычно где-то со 2 или 3-й. С 1-й очень редко.
Также я пробовал запустить бесконечные пинги на наш модуль с компьютера и в это время запросить также точное время. Сервер его возвращал. Поэтому в таком случае также всё у нас работает.
Вы можете устанавливать свои величины времени до следующей попытки, также своё количество попыток.
Самое главное, чтобы вы поняли сам смысл работы протоколов и расчётов в них, что, я надеюсь вам дал. Конечно, не обязательно, всё делать именно таким образом. Вы можете придумать свой способ запроса времени. Тем не менее, надеюсь, что мой урок вам пойдёт на пользу и свою мысль или своё видение данного вопроса мне передать вам удалось.
Когда-нибудь, вооружившись знаниями из этого урока, мы, возможно напишем какие-нибудь часы с синхронизацией из интернет.
Спасибо за внимание!
Предыдущая часть Программирование МК AVR Следующий урок
Приобрести плату Arduino UNO R3 можно здесь.
Приобрести программатор USBASP USBISP с адаптером можно здесь USBASP USBISP 3.3 с адаптером
Ethernet LAN Сетевой Модуль можно купить здесь ENC28J60 Ethernet LAN Сетевой Модуль.
Смотреть ВИДЕОУРОК в RuTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)
Добавить комментарий