Подключение микроконтроллера к локальной сети: тесты производительности и краткое описание API стека

В этой части будет просто формальное описание TCP/IP стека, который мы тут соорудили, по ходу дела.

Краткое содержание:
  • Бенчмарки (немножко)
  • Системные требования
  • Параметры конфигурации
  • Описание API
  • Заключение


Описание стека начну, пожалуй с тестов производительности.

Производительность


В каждом тесте замеряно количество тактов процессора, потраченных на работу стека за определённый промежуток времени. Если поделить это число на время измерения и на частоту процессора, получим что-то вроде «загрузки процессора» на разных тестах. Все тесты проводились с кварцем на 16 мегагерц.

Бенчмарки

Первый тест проводился без всякой сетевой активности. Ну прилетит раз в 10 секунд какой-то широковещательный пакет, девайс его примет и отбросит. В таком режиме стек сожрал 0,0006% процессорного времени.

Во втором тесте я просто пинговал девайс обычной пинговалкой из cygwin'а на дефолтных настройках. Отвечая на пинги, стек сожрал уже 0,03% процессорного времени. Но всё равно маленько.

В третьем тесте мега отдавала mp3 файлик по HTTP на скорости 170 КБ/с по локальной сети. Загрузка составила 95%, т.е., можно сказать, скорость отдачи упёрлась в тактовую частоту проца. 55,2% тактов захавал сам стек, а остальное — работа с SD-карточкой и файловой системой.

В общем, тесты ничего полезного не показали. Всё вроде и так понятно)

Системные требования


Требования стека к оперативной памяти зависят от его конфигурации, а именно, от размера буфера пакета, размера ARP-таблицы и максимального количество параллельных TCP-соединений.

Обозначения

В скобочках указаны наиболее юзабельные значения, но ничего не мешает при необходимости выходить за эти границы)

Больше всего память хавает буфер пакетов. Правда буфер пакетов реюзабелен. Когда прога выполняется за пределами стека, буфер пакетов можно юзать для любых своих целей.

Системные требования

  • ENC28J60 — собственно, драйвер микрухи.
  • Счётчик — либа counter, в которой определены функции rtime() и gettc(). Либа нужна только для DHCP и TCP. Если эти компоненты не используются, либу можно выкинуть. Но, разумеется, либу можно использовать и в основной прошивке, как отдельный компонент)
  • ARP+IP — ядро стека, которое используется всеми остальными компонентами.
  • ICMP, DHCP, UDP и TCP — опциональные компоненты. DHCP требует UDP. TCP можно собрать с поддержкой ретрансмиссий или без неё.

Вот несколько примеров конфигураций стека.

Примеры конфигураций

Самая простая конфигурация, только UDP хавает меньше 3 КБ флешки и 278 байт памяти. Может использоваться, скажем, для NTP-часиков. Влезет в Мегу48 или в навороченную тиню (скажем, 88), впрочем, мега дешевле)).

Минимальная конфигурация для TCP хавает меньше 5 КБ флешки и 311 байт памяти. Влезет в Мегу8.

Макисмально навороченная конфигурация хавает чуть меньше 8 КБ флешки и 845 байт памяти. Требует не меньше Меги16.

Конфигурация


Стек построен так, что ненужные фичи можно выкинуть, чтобы понизить системные требования. Набор фич настраивается директивками:

#define WITH_ICMP // ICMP
#define WITH_DHCP // DHCP (требуется UDP и таймер)
#define WITH_UDP  // UDP
#define WITH_TCP  // TCP (требуется таймер)
#define WITH_TCP_REXMIT // поддержка TCP-ретрансмиссий


Ненужные строчки просто комментим.

Больше всего памяти тратится на буфер пакетов. От размера буфера зависит какого размера пакет сможет захавать или отправить наш стек. Максимальное значение — 1500 байт, по максимальному размеру Ethernet-фрейма. Но если, скажем, нам нафик не нужны пакеты больше 64 байт — зачем ставить больше? Многие протоколы рассчитаны на минимальный MTU для IP — 576 байт. 576 байт и заголовок Ethernet-фрейма дадут примерно 600 байт, это и будет оптимальным размером буфера. При использовании DHCP желательно выбирать размер буфера пакетов не меньше 600 байт.

Тоже самое значение используется и для ограничения размера фреймов, принимаемых и отправляемых ENC28J60.

#define ENC28J60_MAXFRAME    600


Каждая запись ARP-таблицы хавает 10 байт памяти (IP и MAC-адреса).

#define ARP_CACHE_SIZE            3


Часто достаточно и одной записи в таблице. ARP-таблица используется при работе с UDP в роли клиента или с TCP (с поддержкой ретрансмиссий).

Максимальное количество параллельных TCP-соединений. Если делаем веб-сервер, отдающий странички с картинками и прочим подобным контентом, то чем больше, тем лучше. Но каждое соединение хавает по 21 байт памяти, а с поддержкой ретрансмиссий — по 27.

#define TCP_MAX_CONNECTIONS        5


Ниже идут дополнительные настройки, которые можно и не трогать)

TCP-таймаут. Указывает через сколько мс соединение будет обрубаться при отсутствии активности со стороны удалённого узла (если не используюстся ретрансмиты).

#define TCP_CONN_TIMEOUT        2500


Если ретрансмиты используются, указывается через сколько мс будет производиться ретрансмит и через сколько ретрансмитов соединение будет обрубаться.

#define TCP_REXMIT_TIMEOUT    1000
#define TCP_REXMIT_LIMIT        5


Максимальный размер TCP-пакета. Передаётся удалённому узлу при открытии соединения. Выбираем такой, чтобы пакет (+заголовки IP и Ethernet) точно влезли в буфер пакета. И чтобы избежать фрагментирования IP-пакета. Оптимальное значение — 512 байт или чуть больше.

#define TCP_SYN_MSS                512


Фейковый размер окна, который будет отправляться удалённому узлу. Окна у нас всё равно нету. Так что будем отправлять что попало) Слишком мало ставить не нужно (если только мы не хотим принимать много данных, иначе может переполниться буфер в ENC28J60 и будет теряться много пакетов), т.к. удалённый узел может затупить.

#define TCP_WINDOW_SIZE            65535


API


API стека будет, пожалуй, посложнее чем API беркли-сокетов))
Ну, это плата за простоту стека, малое потребление ресурсов и мои кривые руки, написавшие всё это)
Чтобы разобраться в стеке, очень рекомендуется раскуривать примеры, которые были в предыдущих статьях. А тут просто очень кратенький списочек функций стека.

В комплекте со стеком идёт библиотека counter, в которой определена парочка фуннкций (на самом деле макросов), удобных для отсчёта интервалов времени.

GetTickCount — возвращает количество мс с момента включения питания. Естественно, значение может время от времени переполняться.

uint32_t gettc()


Relative Time — возвращает количество секунд с момента включения питания. Это значение едва ли переполнится))

uint32_t rtime();


Функция для инициализации библиотеки (запускает таймер).

void counter_init();


Вспомогательные макросы.

Для перевода чисел из нормального формата в богомерзкий big-endian и обратно.

uint16_t htons(uint16_t val); // из LE в BE, 16 бит
uint16_t ntohs(uint16_t val); // из BE в LE, 16 бит
uint32_t htonl(uint32_t val); // из LE в BE, 32 бита
uint32_t ntohl(uint32_t val); // из BE в LE, 32 бита


Оч много данных в протоколах TCP/IP стека и не только, закодированы в big-endian'е. Перед использованием их нужно сконвертировать в человеческий формат.

Макрос для IP-адреса. Помогает записать IP-адрес в читабельном виде. Возвращает адрес с big-endian'е.

uint32_t inet_addr(a,b,c,d);


Основные функции.

Инициализация стека.

void lan_init();


Периодическое событие. Ловит пакеты, отвечает на пинги, обновляет адрес по DHCP, производит TCP-ретрансмиссии и делает всё такое. Нужно вызывать с некоторым интервалом (ну, скажем, хотя-бы раз в секунду, но лучше, почаще).

void lan_poll();


Функция проверки готовности стека. Можно вызывать из главного цикла и включать светодиодик при готовности сети. Также желательно проверять готовность стека перед тем, как пытаться отправлять какие-то пакеты или открывать соединения.

uint8_t lan_up();


Пример:

int main()
{
    // инициализация
    lan_led_init(); //светодиодик
    lan_init();     //стек
    counter_init(); //таймер
    sei();

    while(1)
    {
        lan_poll();

        //если сеть готова, включаем светодиодик
        if(lan_up()) lan_led_on();
        else lan_led_off();
    }

    return 0;
}


Функции для работы с UDP.

Коллбэк, вызываемый при получении UDP-пакета. frame — буфер пакетов. len — длина полученных UDP-данных (только полезные UDP-данные, без заголовков).

Узнать адрес, порты и другую информацию о пакете можно прям из заголовка пакета.

void udp_packet(eth_frame_t *frame, uint16_t len);


Пример (из проекта NTP-часиков):

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

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


Функция для отправки UDP-пакета. frame — буфер пакета. len — длина полезных данных. Перед вызовом функции, в буфер пакета нужно записать: IP-адрес получателя, порт получателя, порт отправителя, полезные данные. Если MAC-адрес удалённого узла неизвестен, функция возвращает 0 и пакет не отправляется, зато отправляется ARP-запрос. Вызов функции нужно будет повторить позднее.

uint8_t udp_send(eth_frame_t *frame, uint16_t len);


Пример (из проекта 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; //локальный порт

    // записываем полезные данные
    memset(ntp, 0, sizeof(ntp_message_t));
    ntp->status = 0x08;

    // отправляем пакет
    return udp_send(frame, sizeof(ntp_message_t));
}


Функция для отправки UDP-пакета. Перед отправкой пакета в буфер нужно записать только полезные данные. Функция может вызываться только из коллбэка udp_packet (пока что, только один раз).

void udp_reply(eth_frame_t *frame, uint16_t len);


Пример (из проекта переходничка Ethernet в RS-232):

void udp_packet(eth_frame_t *frame, uint16_t len)
{
    //берём заголовки пакетов IP и UDP
    ip_packet_t *ip = (void*)(frame->data);
    udp_packet_t *udp = (void*)(ip->data);
    uint8_t *data = udp->data;
    uint8_t i, count;

    //работаем с полученными данными (data)
    for(i = 0; i < len; ++i)
        uart_write(data[i]);

    //если у нас есть данные для отправки
    count = uart_rx_count();
    if(count)
    {
        //записываем данные в буфер пакета
        for(i = 0; i < count; ++i)
            data[i] = uart_read();
        //отправляем
        udp_reply(frame, count);
    }
}


Функции для работы с TCP.

TCP поддерживает работу с несколькими параллельными соединениями. Каждое активное соединение имеет индекс. Например, если стек собран с поддержкой 5 соединений, допустимые значения индекса — от 0 до 4. Иднекс передаётся в каждую функцию, связанную с TCP.

Коллбэк, вызываемый при получении запроса на соединение. Должен возвращать ненулевое значение, если приложение подтверждает соединение. id — индекс соединения (которое будет открыто, если приложение подтвердит установку соединения), frame — указатель на буфер пакета, содержащий полученный пакет.

uint8_t tcp_listen(uint8_t id, eth_frame_t *frame);


Пример:

uint8_t tcp_listen(uint8_t id, eth_frame_t *frame)
{
    //берём заголовки IP и TCP
    ip_packet_t *ip = (void*)(frame->data);
    tcp_packet_t *tcp = (void*)(ip->data);

    //принимаем подключения только на порт 80
    return (tcp->to_port == htons(80));
}


Коллбэк, вызываемый при закрытии соединения. id — индекс закрытого соединения, hard — параметр равен нулю, если соединение закрыто корректно (graceful close).

void tcp_closed(uint8_t id, uint8_t hard);


Коллбэк, вызываемый при получении данных по TCP. id — индекс соединения, frame — указатель на буфер пакета, len — количество полученных полезных данных.

void tcp_write(uint8_t id, eth_frame_t *frame, uint16_t len);


Заголовок TCP-пакета может содержать опции, поэтому для доступа к полученным данным, приложение должно использовать макрос tcp_get_data.

Коллбэк, вызываемый, когда приложение может отправлять данные по TCP. Как минимум, при открытии соединения, при получении данных (после вызова tcp_write), при успешной доставке предыдущих отправленных данных. Также, если стек собран с поддержкой ретрансмиссий, функция вызывается, если приложение должно в точности регенерировать предыдущие отправленные данные. При этом установлен параметр re.

void tcp_read(uint8_t id, eth_frame_t *frame, uint8_t re);


Функция для открытия TCP-соединения. addr — IP-адрес удалённого узла, port — порт, к которому подключаемся (big-endian), local_port — локальный порт (big-endian). Функция возвращает индекс соединения, которое будет открыто, если удалённый узел подтвердит соединение, либо 0xff при ошибке. Если MAC-адрес удалённого узла неизвестен, функция возвращает 0xff и отправляется ARP-запрос. В таком случае, вызов нужно повторить позднее.

uint8_t tcp_open(uint32_t addr, uint16_t port, uint16_t local_port);


Функция для отправки данных по TCP и закрытия соединения. Может вызываться только из коллбэка tcp_read. id — индекс соединения, должен быть равен значению, переданному в коллбэк tcp_read. frame — указатель на буфер пакета, должен быть равен значению, переданному в коллбэк tcp_read. len — длина отправляемых полезных данных. options — опции, могут содержать следующие флаги:

  • TCP_OPTION_PUSH — отправить пакет с флагом PSH.
  • TCP_OPTION_CLOSE — отправить данные и начать закрытие соединения. После вызова фуннкции с этим флагом, до закрытия соединения может вызываться коллбэк tcp_write, если у удалённого узла остались данные в буфере, либо tcp_read, только если нужно произвести ретрансмиссию.

void tcp_send(uint8_t id, eth_frame_t *frame, uint16_t len, uint8_t options);


Заключение


Последнюю версию стека можно взять тут: tcpip.zip

И вот, наконец-то забрезжил свет в конце тоннеля))

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

Upd. В прикреплённом архиве нужно поправить строчку 1256 в lan.c. Должно быть не uint8_t len, а uint16_t len, как во всех остальных примерах (видимо, изменил по ошибке).



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

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

RSS свернуть / развернуть
Хорошая, годная статья )
0
Хех, так бы и сказали, что хрень))
0
Отличная хрень, спасибо за доступную информацию! Разгорелся большой интерес к сетке, так и хочется уже самому поэкспериментировать :)
0
Хошь верь, хошь не верь. Но в Элементе стали последнее время спрашивать ENC, разьемы с трансформаторами и кварцы на 25мгц. С чего бы это?
0
Может, стоит выпускать эту штуку по лицензии автора?)))
0
Но ведь дорого, блджад! Какие-нибудь rtl8139 в разы дешевле и доступнее. Но шина и доки всё портят.
0
надо волну ловить. запускать в производство макетки
0
да офигенная статья.

было бы отлично иметь законченное устройство, которое может с веб-морды изменять состояние своих выходов, показывать состояние входов, и отдавать данные по уарт.
да, черный ящик. да, узкость в применении. зато простота использования(весь этот код надо еще осмыслить, и собрать плату с микросхемой, мегой и разьемом), и возможность подключить к любому своему устройству без серьезных знаний. это бы уже и продавать можно было бы.
0
Присоеденяюсь :) Хотелось бы поиметь модульс ENC и для примера MEGA32 который будет иметь на борти DHCP и TCPIP сервер с настройками. Ну и конфигурить просто например два порта для приёма и передачи данных… Всё это как модуль который можно подключить к основному МК…
0
Забавненько. Soft-USB напоминает, по API и требованиям)
port — локальный порт (big-endian), local_port — локальный порт (big-endian)
Ошипко

Ты этот стек параллельно с описанием делал или до цикла статей?
С soft-eth скрестить не пробовал?)
0
  • avatar
  • Vga
  • 03 мая 2011, 02:37
Параллельно)
Я про софтовый ethernet недавно узнал. Надо будет тоже покурить.
0
А где взять «perf.h»?
0
  • avatar
  • RSG
  • 04 мая 2011, 18:19
Блиин))
Это осталось от тестов. Выкинул и перезалил)
0
Спасибо. Цикл статей очень нравиться. Давно ковырял что-то подобное с tuxgraphics.org, но здесь в разы доступней и изящней. Правда небольшие шероховатости есть, например DHCP срабатывает не всегда, но это не страшно главное идея и доступность изложения.
0
Обмен данными с этим девайсом осуществлялся через Switch или напрямую?
0
  • avatar
  • Zov
  • 05 мая 2011, 15:36
Взял я версию по ссылке lifelover.dyndns.org/data/tcpip.zip
И заменил этими файлами файлы в проекте eth_232. При компиляции куча ошибок на отсутствие tcp_write, tcp_read и т.п.
Неплохо было бы реализовать примеры именно на этой версии и обеспечить совместимость с новыми версиями, чтобы просто заменять файлы.
0
хе, WITH_TCP выключи просто
0
надо сначала вкурить почему DHCP неработает(
мой девайс нормально получает адрес с сервака под виндой, под линуховой виртуалкой и с роутера. но уже 2 человека жалуются что dhcp неработает как надо. хз в чём может быть проблема((
0
Значит так))
DHCP сервер по логам выдает адрес, но в стеке
ip_addr=0
ip_mask=0
ip_gateway=0

Проснифить не могу, все через Wi-Fi кроме МК
0
Инфу выводил через уарт.
Ты бы написал прогу программку для МК которая выдает в уарт диагностические данные, а мы бы гуртом тебе выслали результаты.
0
0
ой, точнее вотъ
0
ссыль не работат))
0
66 06 F8 06 78 06 9E 60 66 E6 86 9E 7E 9E 00 18 06 33 30 66 80 06 98 06 60 CC CC 86 9E 00 18 78 06 C0 CC F8 66 78 1E 9E F8 86 E6 80 98 80 60 9E 80 9E 1E 9E 00 06 60 0C CF 66 78 06 9E 1E 66 78 00 98 86 86 9E 60 9E 06 86 E6 80 98 80 F8 9E 66 9E 60 E6 7E FE 9E 18 E6 E6 06 60 0C C3 E6 9E 7E 86 9E E6 80 98 80 1E 66 78 F8 9E 60 9E 86 9E F8 9E 7E 9E 00 18 86 06 E6 E6 E0 06 F8 06 60 0C C3 66 E6 E6 F8 E6 E0 E6 9E 78 66 78 18 66 80 98 80 60 9E 80 9E 1E 9E 00 06 60 0C CF 66 78 06 9E 1E 66 78 00 98 86 86 9E 60 9E 06 86 E6 80 98 80 F8 9E 66 9E 60 E6 7E FE 9E 18 E6 E6 06 60 0C C3 E6 9E 7E 86 9E E6 80 98 80 1E 66 78 F8 9E 60 9E 86 9E F8 9E 7E 9E 00 18 86 06 E6 E6 E0 06 F8 06 60 0C C3 66 E6 E6 F8 E6 E0 E6 9E 78 66 78 18 66 80 98 80
0
Стоп, не то! Только что увидел что скорость 9600, а я на 19200 подключал. То то смотрю каша какая то.
0
Если у тебя все гуд, то это видимо у нас это из-за свитчей и роутеров. У меня связка Dir-320 с родной прошей. МК воткнут напрямую в DIR, ноут через вайфай.
0
Проша выдала:
updating DHCP in 2 sec.

И всё!
0
А кто нить пробовал/встречал реализацию протокола SNMP на подобном железе? было бы интересно
0
Не думаю что будет сложно реализовать. Он же на основе UDP.
0
=====
updating DHCP in 2 sec.
dhcp lease end!
network down
sending dhcp discover
======

Дело в том что сигнао rst enc28j60 у меня не подключен ни куда. Я совместно с ресетом МК кинул проводок rst на массу. Только после этого он выдал инфу которая сверху.
0
Всмысле reset накинут на питание. Может стоит назначить пин для резета и в lan.h прописывать его, потом дергать из lan_init?

Но так или иначе DHCP не заработал.
0
хмм… а трафик поснифаешь?
0
Попробую, но не обещаю что получится.
0
бьюсь уже пару дней, но так и не могу допетрить… udp победил и даже статью про DNS написал. В коде по TCP не понимаю. Взял последнюю версию. Подцепил к своему проекту.
1. Собрал без поддержки ретрансмиссий.
Пытаюсь тупо подцепиться к smtp.mail.ru чтобы получить от него ответ. Соединение закрывается «жестко». т.е. срабатывает tcp_closed с параметром reset=1.
В снифере вижу, что отправлен запрос на сервер с флагом SYN, сервер мне отвечает пакетом с флагами SYN & ACK — т.е. пытается установить соединение. Я ему получается обратно не отправляю ничего… вставил заглушку в tcp_listen, но эта функция не вызывается даже…

2. С поддержкой ретрансмиссий все веселее — отправляется пара пакетов с запросом ACK, мне приходит пара SYN & ACK. Затем все повторяется, но уже по одному пакету…

мозги не выдерживают. Подскажите куда копать?

TTL пробовал увеличить и кол-во тиков таймера тоже…
0
что интересно — втыкаю заглушки в tcp_filter — они не выскакивают даже (((
0
Вообщем, несколько ночей ковыряния привели к тому что я таки соединился с сервером.

В пакете есть поля ack и seq. Они при обмене пакетами меняются местами (ес-но с инкрементом где надо). Почему-то последняя версия стэка этого не делает… поправил своими кривыми руками. И думаю правильно ли направил…
0
Сравнил эту версию с имеющимися. Длина пакета в lan_poll() обрезается до 8 бит почему-то. Не знаю как это получилось, в других примерах этого нет, нужно заменить uint8_t len на uint16_t len :/ Других принципиальных отличий нет.
0
Я не стану говорить о том, что «эти постоянные обновления из-за криворукости говнокодеров на этих ООП», как бы это ваша прераготива :)
+2


Elm Chan: А ты кто такой?
0
это бы мешало, если бы длина пакета была более 255 байт. Но проблем из-за этого не было. Вопрос остается открытым… что будет если просто подцепиться к smtp.mail.ru на Вашем железе и Вашем коде?
0
Все у меня заработали кусочки. Вопрос с заменой ack и seq для меня остался открытым…
У меня 2 мысли крутяться — я что-то не понял в описании и надо в коллбэках втыкать дополнительно обмен ацк и сек либо действительно в сырцах ошибочка…
Что интересно — в секвенции FIN все работает без правок…
Прошу прощения за назойливость, но прояснить хотса ситуацию.
0
Спасибо за проделанную работу!
Разобрался с TCP сервером. Но вот с клиентом пока проблемы.
Если создать соединение (открываю по флагу в lan_poll()) функцией tcp_open(), то его получается не возможно закрыть, кроме как оборвав соединение со стороны сервера?
То есть его может разорвать только сервер?
Да и к к серверу пока не хочет коннектится…

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

Спасибо!
0
Добавлю свои пять копеек. Использовал данный стек с NXP LPC1114,
ззернетом ENC28J60 и протоколом MQTT. Первое с чем столкнулся —
выравнивание структур. Следующая проблема возникла на этапе авторизации
TCP соединения. Вся загвоздка в значениях st->syn_num и st->ack_num.
Дело в том что syn_num (идентификатор потока) не ноль вначале соединения,
а какоето случайное число, для идентификации соединения (както),
можно задавать это значение равным нулю, но ноль в syn_num это на самом
деле смещение относительно этого начального случайного числа.
Для начала стек проверяет соответствие этих номеров указанным в пакете
в КАЖДОМ пакете, даже когда мы только получаем sun_num от сервера:
st->ack_num=0 (ожидаемое значение, вначале соединения еще не установленное)
tcp->syn_num=4536...43 (значение передаваемое сервером для синхронизации)
Этот кусок кода проверяет КАЖДЫЙ пакет и соответственно выдаст ошибку
(lan.c функция tcp_filter() строка приблизительно 602):
if( (ntohl(tcp->seq_num) != st->ack_num) ||
    (ntohl(tcp->ack_num) != st->seq_num) ||
    (!(tcpflags & TCP_FLAG_ACK)) )
	return;

решил добавлением условия перед ним:
if(st->status == TCP_ESTABLISHED)

Но это не все, если даже мы и пропустим эту проверку, функция все равно
не сохранит правильное значение, полученное от сервера, поэтому я
сохранил его сам (тотже файл, строка приблизительно 638):
case TCP_SYN_SENT:

	// received packet must be SYN/ACK
	if(tcpflags != (TCP_FLAG_SYN|TCP_FLAG_ACK))
	{
		uart_printf("SYN_SENT->CLOSED\r\n");
		st->status = TCP_CLOSED;
		break;
	}

	//manitou: set target syn number (not allways 0)
	st->ack_num = htonl(tcp->seq_num)+1;
	//end manitou

	// send ACK (active open, step 3)
	tcp->flags = TCP_FLAG_ACK;
	tcp_xmit(st, frame, 0);

	uart_printf("SYN_SENT->ESTABLISHED\r\n");
	// connection is now established
	st->status = TCP_ESTABLISHED;

	// app can send some data
	tcp_read(id, frame, 0);

	break;

После этого проблема неправильных номеров пропала.

Стек работает как положено, но в силу своей легковесности может отправлять данные
только по запросу (в коллбеках) используя полученный пакет как шаблон.
MQTT протокол не опрашивает устройство, оно само должно отправлять данные.
Так как в LPC будет побольше памяти чем в AVRках, я осмелился скопировать шапку
эзернет фрейма и использовать ее как шаблон при отправке пакетов. Добавил опцию
которая будет означать отправку нового пакета и чуть подправил tcp_send() для
установки необходимых флагов перед вызовом tcp_xmit(). Выглядит отправка примерно так:
изменения в lan.h строка приблизительно 212

//дополнительная опция для tcp_send()
#define TCP_OPTION_SEND			0x04

изменения в lan.c функция tcp_send() строка приблизительно 494

if(options & TCP_OPTION_SEND){
	tcp_send_mode = TCP_SENDING_SEND;
}

что-то такое должно быть в main.c

//копия эзернет фрейма и ссылка на настоящий фрейм (ее нужно както получить, например 
//в коллбеке tcp_read()) 
eth_frame_t send_frame, *orig_frame;

//копируем шаблон на его законное место
memcpy(orig_frame, &send_frame, sizeof(eth_frame_t));

//копируем данные в пакет
memcpy(tcp->data, buf, len);

//вызываем отправку с флагами
tcp_send(tcp_id, orig_frame, len, TCP_OPTION_PUSH|TCP_OPTION_SEND);    

Примерно так. Грязно, зато быстро.

Использовал этот стек потомучто он достаточно легковесный и обладает отличной русскоязычной
документацией. Буду читать комментарии еще несколько месяцев, так что если будут вопросы
или желание взглянуть на испорченный мной исходник — пишите сюда.
0
Здравствуйте! очень помогла ваша статья начать работать с этой сетевой картой, спасибо!
есть однако проблемка, которую не могу решить — максимальная скорость отдачи данных, которой мне удалось достичь 1.7 мбит/с (это больше чем в представленных тестах, но и контроллер взят более скоростной, по расчетам я должен был упереться в потолок самой карточки, т.е. 10 мбит/с), однако все еще хуже с загрузкой, хотя сам процесс идет ровно, не удется выскочить за 100кб./с. Не могли бы вы сказать что-то по этому вопросу?
0
Скажите, пожалуйста, у кого-либо удалось подключиться к серверу в режиме клиента используя исходники с данной статьи?
Сначала пробовал это сделать с помощью исходников со статьи «HTTP и CGI», подключалось замечательно, а вот в этих ни в какую не хочет. При попытке подключения функция tcp_open возвращает не 0xFF, значит, якобы подключено, но сам сервер не видит подключения и функция tcp_opened не вызывается. Подключаюсь вот так:
if (tcp_open(inet_addr(192,168,1,100), htons(8881), htons(81)) != 0xFF) LED_ON;

Вот что показывает Wireshark:
0
Я цеплялся — там проблема с нумерацией ACK — толи Little|big endian наврали то ли еще что-то… посмотрите — wireshark пишет номера в процессе соединения. У Вас там черти что…
0
Сорри, что придираюсь, но не могли бы Вы поделиться рабочими исходниками (или их частью)?
0
Попробую, ближе к вечеру.
0
Пасибки! Будет типа подарок на НГ))))))
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.