Ethernet поверх USB на STM32F4

[embedded content]Недавно возникла идея заставить плату на базе МК STM32F4 работать по сети. Поскольку на борту отсутствовал Ethernet PHY контроллер, то единственным вариантом было использовать USB FullSpeed интерфейс для эмуляции Ethernet устройства. Распространённый стандарт USB-класса, реализующий данную функцию, называется RNDIS.К своему огорчению, поиск RNDIS драйвера для STM32 не увенчался успехом. Впрочем, это не удивило, т.к. открытые примеры использования USB порта у STM32 ограничиваются только теми, что предоставил нам производитель.Захотелось исправить сию несправедливость. А заодно и поиметь нужные исходники, благо в будущем они пригодятся.Сейчас, когда демонстрационная версия библиотеки готова, выкладываю её в свет на правах MIT-лицензии. Поэтому, все кому библиотека интересна — пользуйтесь «на здоровье». Библиотека имеет название LRNDIS, первая буква которого означает использование сетевого стека для встраиваемых систем «lwip».Помимо стека lwip на борту заведены DHCP и DNS сервер (ipv4), а также простой HTTP-ответчик.Итак, обо всём этом подробнее…1. Драйвер RNDISНа этапе написания решалось две задачи: подписать наше устройство и поддержать стандарт RNDIS.Подпись устройства сводится к составлению верных USB-дескрипторов. Значение VID выбрано 0×0483 (STMicroelectronics), значение PID — 0×0123 (произвольное). Само собой, в коммерческом применении так делать не следует.Просмотреть дескрипторы Device Descriptor Offset Field Size Value Description 0 bLength 1 12h 1 bDescriptorType 1 01h Device 2 bcdUSB 2 0200h USB Spec 2.0 4 bDeviceClass 1 02h CDC Control 5 bDeviceSubClass 1 00h 6 bDeviceProtocol 1 00h 7 bMaxPacketSize0 1 40h 64 bytes 8 idVendor 2 0483h SGS Thomson Microelectronics 10 idProduct 2 0123h 12 bcdDevice 2 0001h 0.01 14 iManufacturer 1 01h «Fetisov Sergey» 15 iProduct 1 02h «STM32F4 RNDIS» 16 iSerialNumber 1 03h »00000000123C» 17 bNumConfigurations 1 01h Configuration Descriptor 1 Offset Field Size Value Description 0 bLength 1 09h 1 bDescriptorType 1 02h Configuration 2 wTotalLength 2 0043h 4 bNumInterfaces 1 02h 5 bConfigurationValue 1 01h 6 iConfiguration 1 00h 7 bmAttributes 1 40h Self Powered 8 bMaxPower 1 01h 2 mA Interface Descriptor 0/0 CDC Control, 1 Endpoint Offset Field Size Value Description 0 bLength 1 09h 1 bDescriptorType 1 04h Interface 2 bInterfaceNumber 1 00h 3 bAlternateSetting 1 00h 4 bNumEndpoints 1 01h 5 bInterfaceClass 1 02h CDC Control 6 bInterfaceSubClass 1 02h Abstract Control Model 7 bInterfaceProtocol 1 FFh Vendor-Specific 8 iInterface 1 00h Header Functional Descriptor Offset Field Size Value Description 0 bFunctionLength 1 05h 1 bDescriptorType 1 24h CS Interface 2 bDescriptorSubtype 1 00h Header 3 bcdCDC 2 0110h 1.10 Call Management Functional Descriptor Offset Field Size Value Description 0 bFunctionLength 1 05h 1 bDescriptorType 1 24h CS Interface 2 bDescriptorSubtype 1 01h Call Management 3 bmCapabilities 1 00h 4 bDataInterface 1 01h Abstract Control Management Functional Descriptor Offset Field Size Value Description 0 bFunctionLength 1 04h 1 bDescriptorType 1 24h CS Interface 2 bDescriptorSubtype 1 02h Abstract Control Management 3 bmCapabilities 1 00h Requests/notifications not supported Union Functional Descriptor Offset Field Size Value Description 0 bFunctionLength 1 05h 1 bDescriptorType 1 24h CS Interface 2 bDescriptorSubtype 1 06h Union 3 bControlInterface 1 00h 4 bSubordinateInterface0 1 01h CDC Data Endpoint Descriptor 81 1 In, Interrupt, 80 ms Offset Field Size Value Description 0 bLength 1 07h 1 bDescriptorType 1 05h Endpoint 2 bEndpointAddress 1 81h 1 In 3 bmAttributes 1 03h Interrupt 4 wMaxPacketSize 2 0008h 8 bytes 6 bInterval 1 50h 80 ms Interface Descriptor 1/0 CDC Data, 2 Endpoints Offset Field Size Value Description 0 bLength 1 09h 1 bDescriptorType 1 04h Interface 2 bInterfaceNumber 1 01h 3 bAlternateSetting 1 00h 4 bNumEndpoints 1 02h 5 bInterfaceClass 1 0Ah CDC Data 6 bInterfaceSubClass 1 00h 7 bInterfaceProtocol 1 00h 8 iInterface 1 00h Endpoint Descriptor 82 2 In, Bulk, 64 bytes Offset Field Size Value Description 0 bLength 1 07h 1 bDescriptorType 1 05h Endpoint 2 bEndpointAddress 1 82h 2 In 3 bmAttributes 1 02h Bulk 4 wMaxPacketSize 2 0040h 64 bytes 6 bInterval 1 00h Endpoint Descriptor 03 3 Out, Bulk, 64 bytes Offset Field Size Value Description 0 bLength 1 07h 1 bDescriptorType 1 05h Endpoint 2 bEndpointAddress 1 03h 3 Out 3 bmAttributes 1 02h Bulk 4 wMaxPacketSize 2 0040h 64 bytes 6 bInterval 1 00h Поддержка стандарта осуществлена в соответствии с документацией.Также в сети присутствует множество RNDIS-драйверов для других платформ (1, 2, 3), что существенно упростило разработку.В части обмена драйвер повторяет особенность CDC-класса за исключением того, что передаваемые пакеты оборачиваются служебной информацией. Также существует специальный интерфейс для передачи запросов от usb-хоста с целью настройки устройства или получения его статуса. Детально код драйвера можно изучить в модуле usbd_rndis_core.c. Минимальная настройка драйвера при встраивании заключается в изменении определений в файле usbd_rndis_core.h.— MAC-адрес устройства (PERMANENT_HWADDR, STATION_HWADDR)— Идентификатор производителя (RNDIS_VENDOR)— Значение MTU (RNDIS_MTU) #define ETH_MTU 1500 // MTU value #define ETH_LINK_SPEED 250000 // bits per sec #define RNDIS_VENDOR «fetisov» // NIC vendor name #define STATION_HWADDR 0×20,0×89,0×84,0×6A,0×96,0xAA // station MAC #define PERMANENT_HWADDR 0×20,0×89,0×84,0×6A,0×96,0xAA // permanent MAC Также в файле usbd_desc.h следует изменить название продукта (USBD_PRODUCT_STRING) и производителя (USBD_MANUFACTURER_STRING).Процесс установки драйвера в ОС Windows: 5dcbc149db9342afb344db2a801a0251.png1. Пункт меню «обновить драйвер»2. Выполнить поиск драйвера3. Выбрать драйвер из списка4. В списке классов устройств выбрать «Сетевые адаптеры»5. В списке производителе выбрать «Microsoft Corporation»6. И продукт «Remote NDIS based Internet Sharing Device«Более подробно процесс установки стандартного RNDIS устройства описан по ссылке.2. Прикручиваем lwip2c29d64bc93af68e723341f5e4eade50.jpgДрайвер RNDIS обеспечивает лишь транспортную функцию для кадров стандарта 802.3 (Ethernet). Очевидно, что поддержку всего многообразия пакетов и стандартов требуется возложить на сетевой стек. В его роли было принято решение использовать популярный стек для встраиваемых систем lwip последней (на текущий момент) версии 1.4.1. Надо отдать должное авторам стека, тот получился весьма просто интегрируемым. При всём при этом, код стека богат полезными комментариями и инструкциями.

lwIP is a small independent implementation of the TCP/IP protocol suite that has been initially developed by Adam Dunkels and is now continued here.The focus of the lwIP TCP/IP implementation is to reduce resource usage while still having a full scale TCP. This makes lwIP suitable for use in embedded systems with tens of kilobytes of free RAM and room for around 40 kilobytes of code ROM.Main features include: — Protocols: IP, ICMP, UDP, TCP, IGMP, ARP, PPPoS, PPPoE— DHCP client, DNS client, AutoIP/APIPA (Zeroconf), SNMP agent (private MIB support)— APIs: specialized APIs for enhanced performance, optional Berkeley-alike socket API— Extended features: IP forwarding over multiple network interfaces, TCP congestion control, RTT estimation and fast recovery/fast retransmit— Addon applications: HTTP server, SNTP client, SMTP client, ping, NetBIOS nameserver

Запуск стека под stm32 ограничилось включением в процесс сборки конкретного набора исходных файлов и вводом определений в файле lwipopts.h: #define NO_SYS 1 #define LWIP_RAW 1 #define LWIP_NETCONN 0 #define LWIP_SOCKET 0 #define LWIP_DHCP 0 #define LWIP_ICMP 1 #define LWIP_UDP 1 #define LWIP_TCP 1 #define ETH_PAD_SIZE 0 #define LWIP_IP_ACCEPT_UDP_PORT (p) ((p) == PP_NTOHS (67))

#define MEM_SIZE 10000 #define TCP_MSS (1500 /*mtu*/ — 14 /*ethhdr*/ — 20 /*iphdr*/ — 20 /*tcphhr*/)

#define ETHARP_SUPPORT_STATIC_ENTRIES 1 Нужно отметить, работа по портированию стека на STM32F4 производилась и раньше фирмой STMicroelectronics (приложение STSW-STM32070).Запускаем DHCP-серверbbafc127c9d800eda02f8c5ced0030d2.pngДобавление DHCP-сервера в состав библиотеки связано с необходимостью инициализировать сетевой интерфейс на стороне host-а. По умолчанию, создаваемый при подключении устройства интерфейс настроен на автоматическое получение ip-адреса. Библиотека также успешно работает и при статической адресации, но это является не совсем удобным.К сожалению, среди поставляемых lwip средств, DHCP-сервер отсутствует.Однако, это не является существенной проблемой, т.к. в своей минимальной реализации DHCP-сервер весьма минималистичен.В сети присутствует, пожалуй, единственный пример DHCP-сервера, работающего со стеком lwip. Данный источник оказался весьма полезным для изучения, хоть и не пригодным для встраивания по принципу «as-is» по причине отсутствия возможности конфигурирования и использования socket-api.Поэтому было принято решение написать DHCP сервер.И вот его скромные возможности: — выдача адресов на произвольное время— резервирование адресов по MAC адресу— настройка DNS-сервераПодключение сервера в тестовом проекте:

#define NUM_DHCP_ENTRY 3

static dhcp_entry_t entries[NUM_DHCP_ENTRY] = { // mac ip address subnet mask lease time { {0}, {192, 168, 7, 2}, {255, 255, 255, 0}, 24×60 * 60 }, { {0}, {192, 168, 7, 3}, {255, 255, 255, 0}, 24×60 * 60 }, { {0}, {192, 168, 7, 4}, {255, 255, 255, 0}, 24×60 * 60 } };

static dhcp_config_t dhcp_config = { {192, 168, 7, 1}, 67, // server address, port {192, 168, 7, 1}, // dns server «stm», // dns suffix NUM_DHCP_ENTRY, // num entry entries // entries };

int main (void) { … while (dhserv_init (&dhcp_config) != ERR_OK) ; … } Запускаем DNS-сервер9f2047d0ea975f81c0992747c3155a89.pngЖелаемый нами результат работы — отображение web-страницы при вводе в браузере некоторого имени ресурса. Однако, это возможно только при наличии DNS-сервера, который будет знать о нашем хосте. Конечно, этот результат доступен, если в адресной строке напрямую ввести ip-адрес: 192.168.7.1. Такой адрес имеет по умолчанию наше устройство. Однако, будем более искусны и запустим DNS-сервер.В отличии от DHCP, текущая реализация DNS-сервера ещё «тоньше». На данный момент она позволяет обработать только стандартные DNS-запросы на одну запись.Запуск сервера в проекте:

bool dns_query_proc (const char *name, ip_addr_t *addr) { if (strcmp (name, «run.stm») == 0 || strcmp (name, «www.run.stm») == 0) { addr→addr = *(uint32_t *)ipaddr; return true; } return false; }

int main (void) { … while (dnserv_init (PADDR (ipaddr), 53, dns_query_proc) != ERR_OK) ; … } Запускаем HTTP-серверДолго и безрезультатно пришлось бороться с запуском известного сервера из пакета lwip «contrib-1.4.1». До сих пор для меня остаётся загадкой таинственный HardFault, возникающий на, казалось бы, ровном месте. Были проверены все настройки, адреса чтения и записи, глубина стека… Но, увы.Позже я начал писать рабочий http-сервер и HardFault повторился тогда, когда я обращался к области памяти, выделенной функцией mem_malloc. При этом адреса были валидны и относились к внутренней RAM. В общем, с радостью попрощался с динамической аллокацией и принялся использовать статику в сервере. Однако, вопрос о причине HardFault остался открытым, и поэтому нужно иметь в виду, что пользоваться lwip функциями mem_* в текущей версии библиотеки небезопасно.Итак, результат был всё таки получен.Код включения HTTP-сервера:

static const my_page_t my_pages[] = { { »/», 200, MIME_TEXT_HTML, page1_html, page1_html_size }, { »/page2.htm», 200, MIME_TEXT_HTML, page2_html, page2_html_size }, { »/page3.htm», 200, MIME_TEXT_HTML, page3_html, page3_html_size }, { »/check.gif», 200, MIME_IMAGE_GIF, check_png, check_png_size }, { NULL, 404, MIME_TEXT_HTML, page_not_found, page_not_found_size } };

bool on_http_req (const http_req_t *req, http_resp_t *resp, void **arg) { const my_page_t *page; for (page = my_pages; page→uri!= NULL; page++) if (strcmp (page→uri, req→uri) == 0) break; resp→code = page→code; resp→cont_len = page→size; resp→mime = page→mime; resp→conn_type = CT_CLOSE; *arg = (void *)page; return true; }

void http_write_data () { for (int i = 0; i < HTTP_SERVER_MAX_CON; i++) { int n; const htcon_t *con; my_page_t *page; con = htcon(i); if (con == NULL) continue; page = (my_page_t *)con->arg; if (con→state == CON_CLOSED) { htcon_free (i); continue; } if (con→state!= CON_ACTIVE) continue; n = page→size — con→writed; htcon_write (i, (char *)page→data + con→writed, n); } }

int main (void) { …

htserv_on_req = on_http_req; while (htserv_init (80) != ERR_OK) ;

while (1) { stmr (); // call software timers usb_polling (); // usb device polling http_write_data (); // writes http response } } Нерешённые вопросы1. Ньюансы релицензирования стека lwip, который может иметь свои условия включения в состав другого ПО.2. Проблема с работой под ОС Linux.3. Добавление обработки POST-запросов4. Доработка DNS-сервера для обработки «многовопросных» пакетов. 5. Проблема с mem_malloc

© Habrahabr.ru