Подключение микроконтроллера к локальной сети: UDP-клиент

В этой части мы продолжим писать наш стек протоколов. Добавим возможность отправлять UDP-пакеты на любой IP-адрес и научимся получать данные с удалённого сервера.

Краткое содержание:

  • Введение в роутинг
  • ARP-ресолвер
  • Отправка пакетов
  • Пример работы со стеком
  • Заключение

Маршрутизация в Internet


Немножко теории. Рассмотрим основы роутинга в Internet. Думаю, большинство читателей могут эту часть спокойно пропустить)

Итак, возьмём, для примера, мою домашнюю сеть.

Домашняя сеть

Допустим, комп знает IP-адрес девайса и хочет отправить ему пакет. При этом происходит следующее:

  • Комп убеждается, что девайс находится в той же локальной сети, что и он сам.
  • С помощью ARP, комп определяет MAC-адрес девайса.
  • Комп заворачивает IP-пакет в Ethernet-фрейм и отрпавляет на MAC-адрес девайса.
  • Девайс получает фрейм, видит в нём IP-пакет, в котором в качестве адреса получателя указан его собственный IP-адрес.
  • Полученный IP-пакет передаётся протоколу транспортного уровня (UDP, etc.) и, затем, приложению.

Как комп определяет, что девайс находится с ним в одной локальной сети? Любой IP-адрес состоит из адреса подсети и адреса хоста. Именно адрес подсети определяет принадлежность IP-адреса к конкретной локальной сети.

IP-адрес и маска подсети

Для выделения адреса подсети из IP-адреса служит маска подсети. Накладываем на IP-адрес маску и получаем адрес подсети.

В моей домашней сетке маска равна 255.255.255.0. Соответственно, адрес подсети равен 192.168.0.0, а допустимые IP-адреса — от 192.168.0.1 до 192.168.0.254 (первый и последний адреса зарезервированы). Видя, что адрес девайса относится к этому промежутку, комп и понимает — девайс находится с ним в одной локальной сети.

Но что, если мы хотим отправить пакет узлу, который находится не в нашей локальной сети, а вообще неизвестно где? Рассмотрим вот такую сеть (случай не то, чтобы очень жизненный, зато наглядный):

Пример сети

Запись вроде 192.168.3.0/24 означает подсеть с адресом 192.168.3.0 и маской 255.255.255.0 (24 бита установлено).

Допустим, 192.168.0.33 хочет отправить пакет узлу 192.168.3.3, находящемуся в сети 192.168.3.0. Происходит следующее:

  • 192.168.0.33 записывает в IP пакет адрес отправителя 192.168.0.33 (свой), а адрес получателя — 192.168.3.3. Ничего необычного.
  • Заворачивает пакет в Ethernet-фрейм и отправляет его на MAC-адрес узла 192.168.0.22.
  • 192.168.0.22 получает фрейм, видит в нём пакет, предназначеный 192.168.3.3. Поскольку он подключен к обоим локальным сетям, он просто пересылает пакет узлу 192.168.3.3.

Как 192.168.0.33 узнает кому пересылать пакет? Для этого у него есть таблица роутов. Выглядит она примерно так:

Таблица роутов

Когда узел хочет отправить пакет в другую сеть, он просматривает свою таблицу роутов. В ней он находит адрес узла (гейт), на который нужно переслать пакет, чтобы он попал в нужную сеть. Последняя запись — роут по-умолчанию (default route). Она определяет основной гейт (default gateway) — узел, на который пересылаются все пакеты, роут для которых не прописан в явном виде.

Вернёмся к нашей сети. Все пакеты, выходящие за пределы сети проходят через роутер (192.168.0.1), он-то и является основным гейтом. Дополнительные роуты нам прописывать ни к чему, для отправки пакетов наружу достаточно знать адрес основного гейта.

Собирая вместе всё вышесказанное, можно отметить: для полноценной работы наш девайс должен знать три вещи — свой IP-адрес, маску подсети и IP-адрес основного гейта. Маска подсети используется, чтобы определять, относится ли определённый IP-адрес к нашей локальной сети. Все пакеты, выходящие за пределы локальной сети мы пересылаем основному гейту.

ARP-ресолвер


В предыдущей части мы научились отвечать на ARP-запросы. Чтобы пересылать пакеты другим узлам, нам понадобится также написать ARP-ресолвер.

Алгоритм работы нашего ARP-ресолвера будет следующий:

  • Когда нам нужно определить MAC-адрес узла, ищем его в ARP-кэше по IP-адресу.
  • Если узел найден в кэше, просто возвращаем его MAC-адрес.
  • Если узел в кэше не найден, посылаем широковещательный ARP-запрос, чтобы найти нужный узел.
  • Получив ответ на наш запрос, добавляем узел в ARP-кэш.

Добавлять узлы в ARP-кэш будем по кругу (т.е при добавлении новой записи, самая старая будет затираться).

Проверять валидность записей кэша мы не будем — врядли MAC-адрес какого-либо узла внезапно изменится.

// Размер ARP-кэша
#define ARP_CACHE_SIZE      3

// ARP-кэш
typedef struct arp_cache_entry {
   uint32_t ip_addr;
   uint8_t mac_addr[6];
} arp_cache_entry_t;

uint8_t arp_cache_wr;
arp_cache_entry_t arp_cache[ARP_CACHE_SIZE];

// Поиск в ARP-кэше
uint8_t *arp_search_cache(uint32_t node_ip_addr)
{
    uint8_t i;
    for(i = 0; i < ARP_CACHE_SIZE; ++i)
    {
        if(arp_cache[i].ip_addr == node_ip_addr)
            return arp_cache[i].mac_addr;
    }
    return 0;
}

// ARP-ресолвер
// Если MAC-адрес узла известен, возвращает его
// Неизвестен - посылает запрос и возвращает 0
uint8_t *arp_resolve(uint32_t node_ip_addr)
{
    eth_frame_t *frame = (void*)net_buf;
    arp_message_t *msg = (void*)(frame->data);
    uint8_t *mac;

    // Ищем узел в кэше
    if((mac = arp_search_cache(node_ip_addr)))
        return mac;

    // Отправляем запрос
    memset(frame->to_addr, 0xff, 6);
    frame->type = ETH_TYPE_ARP;

    msg->hw_type = ARP_HW_TYPE_ETH;
    msg->proto_type = ARP_PROTO_TYPE_IP;
    msg->hw_addr_len = 6;
    msg->proto_addr_len = 4;
    msg->type = ARP_TYPE_REQUEST;
    memcpy(msg->mac_addr_from, mac_addr, 6);
    msg->ip_addr_from = ip_addr;
    memset(msg->mac_addr_to, 0x00, 6);
    msg->ip_addr_to = node_ip_addr;

    eth_send(frame, sizeof(arp_message_t));
    return 0;
}

// Обработчик ARP-пакетов
void arp_filter(eth_frame_t *frame, uint16_t len)
{
    arp_message_t *msg = (void*)(frame->data);

    // Проверяем длину пакета
    if(len >= sizeof(arp_message_t))
    {
        // Ethernet <> IP и наш IP-адрес
        if( (msg->hw_type == ARP_HW_TYPE_ETH) &&
            (msg->proto_type == ARP_PROTO_TYPE_IP) &&
            (msg->ip_addr_to == ip_addr) )
        {
            switch(msg->type)
            {
            // ARP-запрос, посылаем ответ
            case ARP_TYPE_REQUEST:
                msg->type = ARP_TYPE_RESPONSE;
                memcpy(msg->mac_addr_to, msg->mac_addr_from, 6);
                memcpy(msg->mac_addr_from, mac_addr, 6);
                msg->ip_addr_to = msg->ip_addr_from;
                msg->ip_addr_from = ip_addr;
                eth_reply(frame, sizeof(arp_message_t));
                break;
            
            // ARP-ответ, добавляем узел в кэш
            case ARP_TYPE_RESPONSE:
                if(!arp_search_cache(msg->ip_addr_from))
                {
                    arp_cache[arp_cache_wr].ip_addr = msg->ip_addr_from;
                    memcpy(arp_cache[arp_cache_wr].mac_addr, msg->mac_addr_from, 6);
                    arp_cache_wr++;
                    if(arp_cache_wr == ARP_CACHE_SIZE)
                        arp_cache_wr = 0;
                }
                break;
            }
        }
    }
}


Отправка пакетов


Теперь мы готовы отправлять IP-пакеты любым узлам, подключенным к интернету. Для начала, нам понадобится определить наш IP-адрес, маску подсети и основной гейт.

uint32_t ip_addr = inet_addr(192,168,0,222);
uint32_t ip_mask = inet_addr(255,255,255,0);
uint32_t ip_gateway = inet_addr(192,168,0,1);


Алгоритм отправки IP-пакета будет простой:

  • Определяем IP-адрес узла, на который будем отправлять Etheret-фрейм, содержащий пакет. Если пакет пересылается в пределах локальной сети, сразу посылаем его нужному узлу. Иначе будем отправлять фрейм основному гейту.
  • Ресолвим MAC-адрес узла.
  • Заворачиваем IP-пакет в Ethernet-фрейм и посылаем.

// Отправка Ethernet-фрейма
// Должны быть установлены следующие поля:
//    - frame.to_addr - MAC-адрес получателя
//    - frame.type - протокол
// len - длина поля данных фрейма
void eth_send(eth_frame_t *frame, uint16_t len)
{
    memcpy(frame->from_addr, mac_addr, 6);
    enc28j60_send_packet((void*)frame, len +
        sizeof(eth_frame_t));
}

// Отправка IP-пакета
// Следующие поля пакета должны быть установлены:
//    ip.to_addr - адрес получателя
//    ip.protocol - код протокола
// len - длина поля данных пакета
// Если MAC-адрес узла/гейта ещё не определён, функция возвращает 0 
uint8_t ip_send(eth_frame_t *frame, uint16_t len)
{
    ip_packet_t *ip = (void*)(frame->data);
    uint32_t route_ip;
    uint8_t *mac_addr_to;

    // Если узел в локалке, отправляем пакет ему
    //    если нет, то гейту
    if( ((ip->to_addr ^ ip_addr) & ip_mask) == 0 )
        route_ip = ip->to_addr;
    else
        route_ip = ip_gateway;

    // Ресолвим MAC-адрес
    if(!(mac_addr_to = arp_resolve(route_ip)))
        return 0;

    // Отправляем пакет
    len += sizeof(ip_packet_t);

    memcpy(frame->to_addr, mac_addr_to, 6);
    frame->type = ETH_TYPE_IP;

    ip->ver_head_len = 0x45;
    ip->tos = 0;
    ip->total_len = htons(len);
    ip->fragment_id = 0;
    ip->flags_framgent_offset = 0;
    ip->ttl = IP_PACKET_TTL;
    ip->cksum = 0;
    ip->from_addr = ip_addr;
    ip->cksum = ip_cksum(0, (void*)ip, sizeof(ip_packet_t));

    eth_send(frame, len);
    return 1;
}


Ну и отправка UDP-пакета.

// Отправляет UDP-пакет
// Должны быть установлены следующие поля:
//    ip.to_addr - адрес получателя
//    udp.from_port - порт отрпавителя
//    udp.to_port - порт получателя
// len - длина поля данных пакета
// Если MAC-адрес узла/гейта ещё не определён, функция возвращает 0
uint8_t udp_send(eth_frame_t *frame, uint16_t len)
{
    ip_packet_t *ip = (void*)(frame->data);
    udp_packet_t *udp = (void*)(ip->data);

    len += sizeof(udp_packet_t);

    ip->protocol = IP_PROTOCOL_UDP;
    ip->from_addr = ip_addr;

    udp->len = htons(len);
    udp->cksum = 0;
    udp->cksum = ip_cksum(len + IP_PROTOCOL_UDP, 
        (uint8_t*)udp-8, len+8);

    return ip_send(frame, len);
}


Пишем приложение


Чисто чтобы потестить наш улучшенный стек, попробуем получить какую-нибудь информацию из интернета. Например, точное время по NTP.

NTP реализуем самым тупым способом — отрпавляем серверу запрос, получаем ответ с точным временем. Для правильной работы NTP, локальный UDP-порт не должен равняться UDP-порту сервера (123). Также, нужно учитывать, что NTP возвращает неправильный timestamp — количество секунд, прошедших с 1 января 1900 года. Чтобы получить нормальный timestamp, считающийся с 1 января 1970, года, нужно отнять от NTP-timestamp'а ровно 2208988800 секунд.

// Порт NTP-сервера
#define NTP_SRV_PORT        htons(123)

// Локальный порт
#define NTP_LOCAL_PORT        htons(14444)

// Фомат времени в NTP - время в секундах с 1 января 1900 г.
//  В формате fixed point 32:32
typedef struct ntp_timestamp {
    uint32_t seconds; // целая часть
    uint32_t fraction; // дробная часть
} ntp_timestamp_t;

// Формат NTP-сообщения
typedef struct ntp_message {
    // информация о пакете
    uint8_t status;
    
    // информация об эталонных часах (тип, точность, etc.)
    uint8_t type;
    uint16_t precision;
    uint32_t est_error;
    uint32_t est_drift_rate;
    uint32_t ref_clock_id;
    
    // информация о времени
    ntp_timestamp_t ref_timestamp; // установки эталонных часов
    ntp_timestamp_t orig_timestamp; // отправки пакета клиентом
    ntp_timestamp_t recv_timestamp; // получения пакета сервером
    ntp_timestamp_t xmit_timestamp; // отправки пакета сервером
} ntp_message_t;

// Отправка запроса на NTP-сервер
uint8_t ntp_request(uint32_t srv_ip)
{
    eth_frame_t *frame = (void*)net_buf;
    ip_packet_t *ip = (void*)(frame->data);
    udp_packet_t *udp = (void*)(ip->data);
    ntp_message_t *ntp =(void*)(udp->data);

    ip->to_addr = srv_ip;
    udp->to_port = NTP_SRV_PORT;
    udp->from_port = NTP_LOCAL_PORT;

    // ntp.status = 8
    // остальные поля заполняем нулями
    memset(ntp, 0, sizeof(ntp_message_t));
    ntp->status = 0x08;

    return udp_send(frame, sizeof(ntp_message_t));
}

// Обработка ответа NTP-сервера
uint32_t ntp_parse_reply(void *data, uint16_t len)
{
    ntp_message_t *ntp = data;
    uint32_t temp;

    // Проверяем длину пакета
    if(len >= sizeof(ntp_message_t))
    {
        // Переводим в нормальный timestamp и возвращаем
        temp = ntp->xmit_timestamp.seconds;
        return (ntohl(temp) - 2208988800UL);
    }
    return 0;
}


С помощью NTP, будем запрашивать время каждые 12 часов.

// Цепляем библиотеку для HD44780
#include "hd44780.h"

// Адрес NTP-сервера
//  !!! Никогда не хардкодь адрес серевера в рабочий, не учебный девайс !!!
#define NTP_SERVER    inet_addr(62,117,76,142)

// Часовой пояс. Для простоты забьём его константой
#define TIMEZONE    7

// Счётчик времени с момента включения девайса
static volatile uint16_t ms_count;
static volatile uint32_t second_count;

// Время следующего NTP-запроса
static volatile uint32_t ntp_next_update;

// Точное время (относительно момента second_count = 0)
static volatile uint32_t time_offset;

// Таймер на частоту 1 кГц
ISR(TIMER0_COMP_vect)
{
    if(++ms_count == 1000)
    {
        ++second_count;
        ms_count = 0;
    }
}

// Обработчик получаемых UDP-пакетов
void udp_packet(eth_frame_t *frame, uint16_t len)
{
    ip_packet_t *ip = (void*)(frame->data);
    udp_packet_t *udp = (void*)(ip->data);
    uint32_t timestamp;

    // Получили ответ от NTP-сервера?
    if(udp->to_port == NTP_LOCAL_PORT)
    {
        if((timestamp = ntp_parse_reply(udp->data, len)))
        {
            // Запоминаем время
            time_offset = timestamp - second_count;
            
            // Следующее обновление через 12 часов
            ntp_next_update = second_count + 12UL*60*60;
        }
    }
}

int main()
{
    static uint8_t net_buf[512];
    eth_frame_t *frame = (void*)net_buf;
    uint32_t display_next_update = 0;
    uint32_t loctime;
    uint8_t s, m, h;
    char buf[5];

    // Инициализируем ENC28J60
    enc28j60_init(mac_addr);

    // Инициализируем LCD
    _delay_ms(30);
    hd44780_init();
    hd44780_mode(1,1,0);
    hd44780_clear();

    // Инициализируем Таймер 0 в режиме CTC, 1 кГц (при FCLK = 16 МГц)
    TCCR0 = (1<<WGM01)|(1<<CS01)|(1<<CS00);
    OCR0 = 250;
    TIMSK |= (1<<OCIE0);
    sei();

    while(1)
    {
        // Ловим пакеты
        if((len = enc28j60_recv_packet(net_buf, sizeof(net_buf))))
            eth_filter(frame, len);
        
        // Пора отправить NTP-запрос
        if(second_count >= ntp_next_update)
        {
            // Пробуем отправить запрос на NTP-сервер
            if(!ntp_request(NTP_SERVER))
            {
                // Пакет не отрпавлен - MAC-адрес гейта ещё не известен
                // попробуем снова через 2 секунды
                ntp_next_update = second_count+2;
            }
            else
            {
                // Пакет отправлен - если ответ не получим,
                // попробуем снова через 60 секунд
                ntp_next_update = second_count+60;
            }
        }

        // Пора обновить данные на экране (точное время известно)
        if((time_offset) && (second_count >= display_next_update))
        {
            // Вычисляем время
            loctime = time_offset+second_count + 60UL*60*TIMEZONE;
            s = loctime % 60;
            m = (loctime/60)%60;
            h = (loctime/3600)%24;

            // Рисуем время на экране
            hd44780_clear();
            itoa(h,buf,10);
            hd44780_puts(buf);
            hd44780_puts(":");
            itoa(m,buf,10);
            hd44780_puts(buf);
            hd44780_puts(":");
            itoa(s,buf,10);
            hd44780_puts(buf);

            // Следующее обновление через 1 секунду
            display_next_update = second_count+1;
        }
    }

    return 0;
}


Потестируем то, что получилось.



Заключение


Скачать проект можно тут.

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



Все статьи цикла

Комментарии (41)

RSS свернуть / развернуть
Великолепно. Изложено просто и доступно.
0
отличные статьи, спасибо!
0
Просто шикарно. Спасибо!
0
И как это понимать? Что всё в статье полностью понятно или наоборот, что не понятно ничего? ))
0
Как раз все понятно. И все раскладывается постепенно по полочкам. До этого у меня в этом вопросе был некоторый сумбур и беспорядок, в связи с тем, что вся инфа в интернете довольно фрагментирована. Там кусок, сям кусок и пока до всего дойдешь и соберешь воедино, что пропадает всякая охота заниматься этим вопросом. Несколько раз брался и бросал.
Так что огромное Вам мерси Мастер и дальнейших успехов в вашем творчестве! Ждем с нетерпением следующих статей.
0
И, кстати, ничего, что статья, включая примеры кода является продолжением предыдущей? Или это вовсе неочевидно?
0
Это же серия. так что нормально. Я потом это все перелинкую ссылками между собой. Когда все будет готово.
0
Thanks, как всегда доступно и понятно :)
0
Мёртвые маки из ARP таблицы лучше вычищать быстро и безжалостно. т.е. если по запросу к маку нет ответа — нахрен его из ARP таблицы, ибо бывает так, что выдернули один девайс, воткнули другой, IP один а коннекта нет.
0
Хм… ну мой стек много чего не делает. Не проверяет чексуммы пакетов, не выкидыват маки из табицы. Т.к. реально это всё понадобилось бы в 0,001% случаев, зато памяти МК (и тактов) отжирало очень прилично.
Хотя да, можно сделать 2 версии стека. И в полной версии всё проверять.
0
Очередная отличная статья! Ждем продолжения банкета :)
0
Вопрос. Как узел (в примере 192.168.0.33) получает/формирует таблицу маршрутов (роутов)?
0
Или ручками прописывается, или с использованием специальных протоколов (в больших интернетах).
0
Пардон. Сейчас дошло.
0
Вопрос. А с какой точностью можно получать время по NTP?
Что является точкой привязки к шкале времени(типа фронт импульса 1PPS привязан к шкале UTC)?
0
Ну не знаю. Конечно, трудов положено много, задумка неплохая, но. Много досадных непоняток. Вот например «Но что, если мы хотим отправить пакет узлу, который находится не в нашей локальной сети, а вообще неизвестно где?» и тут же следует пример, где не «неизвестно куда» шлет пакет 192.168.0.33, а оказывается у него есть таблица роутов и он знает куда слать. Неплавно как-то изложено, прыжками.
И к скачанному примеру к этой статье есть вопросы, почему переменная uint32_t second_count изменяется в прерывании и преспокойненько считывается в main безо всякин намёков на критическую секцию? volatile здесь не поможет.
Простите.
0
Вот например «Но что, если мы хотим отправить пакет узлу, который находится не в нашей локальной сети, а вообще неизвестно где?» и тут же следует пример, где не «неизвестно куда» шлет пакет 192.168.0.33, а оказывается у него есть таблица роутов и он знает куда слать.
Вполне нормально. Там показано как это вообще делается — таблица маршрутов содержит записи, определяющие через какой гейт слать пакет, чтобы он попал в такую-то сеть. Затем сказано, что для сабжа используется такая таблица из только одного адреса — главного гейта, т.е. все пакеты не в свою сеть будут отправлены ему.
И к скачанному примеру к этой статье есть вопросы, почему переменная uint32_t second_count изменяется в прерывании и преспокойненько считывается в main безо всякин намёков на критическую секцию? volatile здесь не поможет.
Синхронизация доступа нужна только при записи. При чтении пофиг. Разве что то, что она многобайтная — если изменится между чтением отдельных байт, то могут быть траблы.
0
1. Угу. Это называется «неизвестно где»?
2. uint32_t это по-Вашему сколько байт?
0
1. Угу. Это называется «неизвестно где»?
Да. В данном примере гейт непосредственно смотрит в эту сеть, но в принципе она может быть от него еще через несколько хопов. Ну и под «неизвестно где» видимо подразумевалось «не в локалке». В общем, на мой взгляд вполне понятно написано, хотя я в принципе с сетями немного знаком.
2. uint32_t это по-Вашему сколько байт?
Я и говорю, многобайтная. Но я не знаю, как компилятор читает многобайтные volatile переменные. Мож он там cli/sei вставляет.
0
Нет таких компиляторов, и не для этих целей служит volatile. Кроме того, там надо не cli/sei ставить, а по крайней мере save_interrupt-cli/ restore interrupt.
0
Что означают устрашающая фраза:
//!!! Никогда не хардкодь адрес серевера в рабочий, не учебный девайс!!!
???
В чем опасность?
А как делать правильно?
Спасибо.
0
Хардкод — зло. Тем более хардкод настроек — адрес сервера легко может поменяться — и тогда придется пересобирать прошивку (что может оказаться не так просто) и перепрошивать девайс.
0
Снимаю шапку перед Lifelover-ом за труды. Потихоньку начал понимать что к чему. Повторил проект с часами, адаприровав его к PIC24FJ256GB106 Только вот не могу понять почему он (проект) работает только с ntp1.vniiftri.ru (IP = 62.117.76.142) сервером указаном в проекте. Другие почему то не отвечают, например time.nist.gov (IP = 207.200.81.113) Хотя например на компе (интернет время) прекрасно синхронизируется с любым из них. В чем может быть причина? И еще, в статье написано локальный UDP-порт не должен равняться UDP-порту сервера (123). Но снифер показывает (при синхронизации времени с компа) оба порта равны 123
P.S. Просто разобраться хочется…
0
Lifelover, прошу помочь. Проблема с ARP резолвером. С моей железки резолвятся маки всех машин в локалке, НО роутер категорически отказывается отвечать на ARP пакеты. Посниферил ARP обмен между роутером, железкой и компом. Есть различия в запросах: запросы от железки на 18 байт длинее (заполнены нулями последние 18 байт) запросов от компа. Тем не менее комп на эти запросы выдает свой мак, а роутер «вертел их на своем х...» и молчит как партизан. причем пробовал дома (dir-100) и на работе (cisco), результат тот же.
0
решено тут
0
Хочу обратить внимание на IP адрес NTP сервера указанного в проекте — 62.117.76.142. Данный IP адрес уже не действителен -> www.vniiftri.ru/index.php/ru/news/publicnews/198-smena-ip-adresov-ntp-serverov < — для успешной синхронизации используйте NTP ntp1.vniiftri.ru — 89.109.251.21. Хотя, когда вы будете читать это сообщение — может и он уже устареет, так что проверяйте.
P.S. Данная проблема вроде и очевидна, но я потратил несколько дней перепахивая код (проект на STM32), и пытаясь понять почему нет ответа от NTP.
0
Ну вообще-то, там специально написано — «никогда не хардкодьте адрес сервера».
0
Вообще правильнее всего, если есть возможность резолвить DNS имена, использовать пулы типа 0.debian.pool.ntp.org, 1.debian.pool.ntp.org, 2.debian.pool.ntp.org.
www.pool.ntp.org/ru/
+1
Хотелось бы увидеть статью «Подключение микроконтроллера к локальной сети: ICMP-клиент». Т.е. что нужно сделать, чтобы получить только простейшую пинговалку.
0
Нашел на меня невероятный тупняк. Не могу понять как происходит преобразование. Смотрю переменную loctime в отладчике.
loctime = 1395593785
s(25) = loctime % 60;
m(56)= (loctime/60)%60;
h(16)= (loctime/3600)%24;
s = 1395593785 % 60 (% я так понимаю — остаток от деления)
1395593785 / 60 = [23259896,] 4166666666666666 ну ни как не получается 25.
0
Сам же и отвечаю:
1395593785 / 60 = 23259896,[4166666666666666] — отсекаем дробную часть.
23259896 * 60 = 1395593760 — восстанавливаем целую часть
1395593785 — 1395593760 = 25. разница между истинной и восстановленной
0
Доброго времени суток!
Выражаю огромную благодарность за труд автора, за хорошую подачу материала и простоту объяснения довольно сложных механизмов.
Однако у меня возник вопрос касательно описанных структур заголовков протоколов.
А конкретно — про выравнивание данных в оперативной памяти. Допустим, есть структура заголовка ethernet:
typedef struct
{
unsigned char to[6];
unsigned char from[6];
unsigned short int type;
}ethernet_head;
Ну и допустим, есть буффер, куда данные складываются: unsigned char net_buf[512].
По приходу данных делаем следующую процедуру:
ethernet_head *Eth = (ethernet_head *)netbuf;
if(type == 0x0800)

Но это будет работать только на МК с 8-разрядной архитектурой, т.к. процессоры таких ядер оперируют со словами в 1 байт. Гранулярность других вычислительных платформ (тот же STM32) варьируется, и в общем случае составляет 4 байта. Компилятор выровняет поля создаваемой структуры до целых слов, чтобы процессор быстрее выполнял операции над данными, состоящими из нескольких байт. А это значит, что при наложении структуры на массив данных, пришедших через Ethernet, все покорежится. Структуры можно упаковать (заставить компилятор не выравнивать поля) директивой #pragma pack()...pop(), но это не шибко правильно и возможно решается это по-другому. Прошу уточнения данного аспекта. Благодарю!
0
Такие структуры всегда пакуются. Ничего неправильного (по крайней мере на современном ARM'е — бывают архитектуры, не способные на невыровненный доступ) в этом нет — хотя это и не слишком оптимально.
+2
Hello my friend!
Старина well-man даже написал маленький дайджест на тему того, о чем ты спрашиваешь.
0
Благодарю! Было интересно капнуть глубже :)
0
Отличный цикл статей.Интересно всё описано.
А как бы так сделать посылку пакета на WEB сервер и дёргать там php файл.
В команду вложить значение температурного датчика, например DATCH=22.
Пробую сделать функцию, за основу взял ntp_request.
Ничего не получается, мало знаний ещё, во многом путаюсь.
Может кто поможет с кодом?
Очень был бы благодарен.
Спасибо.
0
Web работает через HTTP. Смотри соответствующие статьи (дальше в цикле).
0
Снова здравствуйте. Будьте так добры подсказать некоторые моменты.
На основе предыдущего примера удалось принять UDP сообщение на комп. А вот наоборот, отправить в комп UDP сообщение, не получается. Что я поменял
#define NTP_SERVER inet_addr(192,168,0,102) // 102 адрес моего компа

typedef struct ntp_message {
// информация о пакете
uint8_t udp_test_message;
} ntp_message_t;

uint8_t ntp_request(uint32_t srv_ip)
{
eth_frame_t *frame = (void*)net_buf;
ip_packet_t *ip = (void*)(frame->data);
udp_packet_t *udp = (void*)(ip->data);
ntp_message_t *ntp =(void*)(udp->data);

ip->to_addr = srv_ip;
udp->to_port = NTP_SRV_PORT;
udp->from_port = NTP_LOCAL_PORT;

// ntp.status = 8
// остальные поля заполняем нулями
ntp->udp_test_message = '3';

return udp_send(frame, sizeof(ntp_message_t));
}
Ну и соответственно вызываю полученную функцию. Подскажите, что я делаю не так? Как мне таки получить сообщение на комп. Комп видит что пакет приходит, но UDP клиент\сервер его не наблюдает. Заранее спасибо, ну видимо Vga. Чтоб я без вас делал)
0
А ты на компе на 123 порту слушаешь? Алсо, для своего сервера лучше выделить отдельный порт и не в диапазоне системных (0..1024) портов. Порт сервера задается в NTP_SRV_PORT.
0
Благодарю) Дело было именно в портах. Но вот еще непонятка. При закоменчивании функции lan_poll() отключается не только прием данных, но и передача, хотя функция передачи по прежнему вызывается. Почему так? Как они связаны?
0
Изучи код и узнаешь. Я не изучал.
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.