LwIP — это стек протоколов TCP/IP с открытым исходным кодом. Первоначально LwIP был
разработан Адамом Дункельсом в Шведском институте компьютерных наук, сейчас разработка ведется силами сообщества.
LwIP получил очень широкое распространение во встраиваемых системах на базе микроконтроллеров благодаря
низкому потреблению оперативной памяти. Именно этот TCP/IP стек используется в фреймворке ARM mbed и генераторе кода инициализации STM32CubeMX.
LwIP распространяется под BSD лицензией.
В состав стека LwIP входят такие протоколы :
- IPv4 и IPv6 (Интернет протокол 4-ой и 6-ой версий)
- ICMP (протокол межсетевых управляющих сообщений)
- IGMP (протокол управления группами Интернета)
- UDP(протокол пользовательских датаграмм)
- TCP(протокол управления передачей)
- DNS(система доменных имен)
- SNMP(простой протокол сетевого управления)
- DHCP(протокол динамической настройки узла)
- PPP(двухточечный протокол канального уровня)
- ARP(протокол определения адреса)
Архитектура стека LwIP построена на модели, включающей четыре уровня абстракции. Список уровней от самого низкого до самого высокого :
- Уровень связи (link layer) содержит технологии коммуникации для одного сегмента локальной сети.
- Интернет уровень (IP) соединяет независимые сети, устанавливая межсетевое взаимодействие.
- Транспортный уровень обрабатывает соединения между хостами.
- Прикладной уровень содержит все протоколы для передачи данных между процессами.
В стеке LwIP доступно три интерфейса прикладного программирования (API) :
- RAW API – родной интерфейс LwIP. Он предусматривает использование обратных вызовов функций (callbacks) внутри стека.
Это означает, что вам перед началом работы со стеком необходимо присвоить указатели на функции-обработчики событий,
которые в процессе работы будут вызываться внутри LwIP. RAW API имеет наибольшую производительность и наименьший результирующий размер кода. - Netconn API – высокоуровневый последовательный интерфейс, построенный поверх RAW API. Этот интерфейс требует наличия RTOS и поддерживает мнопоточные операции.
- BSD Socket API – высокоуровневый интерфейс сокетов, разработанный поверх Netconn API. Этот интерфейс обеспечивает высокую переносимость ваших приложений, поскольку является стандартизированным API.
В состав пакета программ STM32CubeMX от компании STMicroelectronics входят примеры работы со стеком LwIP для отладочных плат STM32.
Именно этими примерами мы воспользуемся для первоначального ознакомления со стеком.
Я буду использовать отладочную плату NUCLEO_F429ZI на микроконтроллере STM32F429ZI, к которому через интерфейс RMII подключен физический уровень Ethernet на микросхеме LAN8742A.
После установки STM32CubeMX примеры программ можно найти в каталоге STM32Cube\Repository\STM32Cube_FW_F4_V1.21.0\Projects\STM32F429ZI-Nucleo\Applications.
Для демонстрации работы NUCLEO_F429ZI со стеком LwIP доступен только один пример, — HTTP сервер, использующий интерфейс Netconn API и работающий под управлением FreeRTOS (с интерфейсом CMSIS_RTOS первой версии).
Именно с этого примера мы и начнем наше знакомство со стеком Lightweight IP. Открываем проект в предпочитаемой среде разработки (IAR EWB, MDK-ARM, SW4STM32). Я буду это делать в бесплатной SW4STM32, построенной на базе Eclipse CDT.
Собираем и запускаем проект, а если ничего не получается, то читаем readme.txt 🙂 . В этом файле указано название руководства пользователя UM1713 “Интерфейс STM32Cube с LwIP и приложениями” для более детального изучения примера, а также предоставлено краткое описание основных файлов проекта.
Пример собирается в SW4STM32 без проблем, для загрузки прошивки в микроконтроллер достаточно подключить плату NUCLEO_F429ZI к компьютеру и скопировать бинарный файл на диск NODE_F429ZI.
По-умолчанию в примере используется динамическое назначение IP адреса с помощью DHCP . Узнать назначенный адрес можно с помощью консольной команды
1 |
arp -a |
Я установил для своей платы статический IP адрес 192.168.1.10 . Для этого необходимо закоментировать строку «#define USE_DHCP» и отредактировать IP адрес платы и шлюза в файле main.h.
После прошивки платы подключаем ее к компьютерной сети и набираем в строке браузера адрес web-сервера (в моем случае http://192.168.1.10).
Теперь настало время разобраться в работе web -сервера, изучая исходный код примера.
В главной функции main создается и запускается одна-единственная задача StartThread, которая выполняет начальную инициализацию lwip и запуск остальных задач.
Если вы ранее работали c FreeRTOS, но синтаксис функций вам кажется незнакомым (или не работали с RTOS вообще), ознакомьтесь с документацией CMSIS_RTOS API.
Первой вызывается функция tcpip_init , в которой производится начальная инициализация lwip и запуск задачи (программного потока), отвечающей за работу TCP/IP стека. Исходный код tcpip_init находится в файле tcpip.c .Второй вызываемой функцией является статическая функция Netif_Config , объявленная в файле main.c.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
static void Netif_Config(void) { ip_addr_t ipaddr; ip_addr_t netmask; ip_addr_t gw; #ifdef USE_DHCP ip_addr_set_zero_ip4(&ipaddr); ip_addr_set_zero_ip4(&netmask); ip_addr_set_zero_ip4(&gw); #else IP_ADDR4(&ipaddr,IP_ADDR0,IP_ADDR1,IP_ADDR2,IP_ADDR3); IP_ADDR4(&netmask,NETMASK_ADDR0,NETMASK_ADDR1,NETMASK_ADDR2,NETMASK_ADDR3); IP_ADDR4(&gw,GW_ADDR0,GW_ADDR1,GW_ADDR2,GW_ADDR3); #endif /* USE_DHCP */ netif_add(&gnetif, &ipaddr, &netmask, &gw, NULL, ðernetif_init, &tcpip_input); /* Registers the default network interface. */ netif_set_default(&gnetif); if (netif_is_link_up(&gnetif)) { /* When the netif is fully configured this function must be called.*/ netif_set_up(&gnetif); } else { /* When the netif link is down this function must be called */ netif_set_down(&gnetif); } } |
В этой функции назначается IP адрес платы (динамически или статически в зависимости от макроса USE_DHCP), маска сети и адрес шлюза .
Функция netif_add производит инициализацию структуры netif назначенными параметрами. Также в ней присваивается указатель на функцию инициализации Ethernet интерфейса ethernetif_init и функцию обратного вызова для обработки принятого пакета tcpip_input.
В функции netif_default_init регистрируется ранее инициализированная структура netif для использования в качестве сетевого интерфейса по-умолчанию.
Завершаем инициализацию проверкой флага NETIF_FLAG_LINK_UP и запуском интерфейса с помощью функции netif_set_up или отключением интерфейса с помощью функции netif_set_down в зависимости от значения флага.
Глубокого понимание инициализирующей последовательности на данном этапе от нас не требуется.
Дальше по коду производится инициализация web -сервера с помощью функции http_server_netconn_init, внутри которой создается новая задача (программный поток) для обработки запроса к web -серверу.
Web -сервер в этом примере очень простой, он обрабатывает только один запрос HTTP протокола. GET – запрос (команда GET) предназначен для получения указанного файла от web – сервера. Если это, например, html — файл , то он будет отображен в вашем браузере, архивный файл будет загружен на ваш компьютер и сохранен на жестком диске. Тип передаваемого контента указывается в параметрах GET – запроса.
Для получения подробной информации необходимо обратиться к спецификации протокола HTTP .
Исходный код обработки запроса к серверу можно просмотреть в функции http_server_netconn_thread из файла httpserver-netconn.c .
Вдаваться в подробности реализации web – сервера от компании STMicroelectronics мы не станем, потому что наша цель — научиться использовать стек LwIP.
Для нас на данном этапе наиболее важным моментом является понимание каким образом можно подключить
Ethernet драйвер к LwIP. Второе, чему мы должны научится , — это настраивать стек LwIP.
Интерфейс LwIP с драйвером Ethernet.
Для связывания LwIP стека с Ethernet интерфейсом микроконтроллеров stm32f4xx используется файл ethernetif.c.
Порт стека LwIP , предназначенный для работы с микроконтроллерами stm32f4xx, находится в каталоге lwip/system .
Вот перечень основных функций Ethernet драйвера :
- low_level_init – вызывает функции драйвера для инициализации Ethernet периферии stm32f4xx .
- low_level_output – вызывает функции драйвера для отправки Ethernet пакета.
- low_level_input — вызывает функции драйвера для приема Ethernet пакета.
- ethernetif_init – инициализирует структуру сетевого интерфейса (netif) и вызывает low_level_init.
- ethernetif_input – вызывает low_level_input, чтобы принять пакет и потом передать его в стек LwIP.
Итак , в нашем примере реализация Ethernet драйвера для использования RMII интерфейса на микросхеме LAN8742A находиться в файле ethernetif.c.
Драйвер с одной стороны обеспечивает интерфейс со стеком LwIP, а с другой стороны использует функции библиотеки HAL для работы с Ethernet интерфейсом микроконтроллера.
Связывание Ethernet драйвера со стеком LwIP происходит при вызове функции netif_add, в параметрах которой передается указатель на функцию ethernetif_init.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
err_t ethernetif_init(struct netif *netif) { LWIP_ASSERT("netif != NULL", (netif != NULL)); #if LWIP_NETIF_HOSTNAME /* Initialize interface hostname */ netif->hostname = "lwip"; #endif /* LWIP_NETIF_HOSTNAME */ netif->name[0] = IFNAME0; netif->name[1] = IFNAME1; netif->output = etharp_output; netif->linkoutput = low_level_output; /* initialize the hardware */ low_level_init(netif); return ERR_OK; } |
Внутри ethernetif_init производится инициализация указателя netif->linkoutput функцией обратного вызова low_level_output, предназначенной для отправки пакетов.
Для приема пакетов внутри low_level_init запускается еще одна задача (поток выполнения) ethernetif_input.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
void ethernetif_input( void const * argument ) { struct pbuf *p; struct netif *netif = (struct netif *) argument; for( ;; ) { if (osSemaphoreWait( s_xSemaphore, TIME_WAITING_FOR_INPUT)==osOK) { do { p = low_level_input( netif ); if (p != NULL) { if (netif->input( p, netif) != ERR_OK ) { pbuf_free(p); } } }while(p!=NULL); } } } |
Принятые данные копируются в связанный список pbuf и передаются в стек lwip через netif->input . При вызове netif_add в качестве параметра input мы передали адрес функции tcpip_input, именно эта функция и будет вызвана для обработки принятого пакета , точнее для отправки принятого пакета в очередь сообщений, из которой пакет может быть прочитан для дальнейшей обработки функциями Netconn API.
Настройка LwIP.
Для настройки стека LwIP используется заголовочный файл lwipopts.h. В этом файле можно настроить стек LwIP и все его модули. Вам не обязательно устанавливать все возможные параметры, если параметр не установлен, то используется его значение по-умолчанию из файла opt.h. Таким образом в файле lwipopts.h можно переопределить большую часть поведения стека.
В файле lwipopts.h можно выбрать только те модули, которые вы собираетесь использовать в своей программе и таким образом уменьшить размер кода прошивки.
1 2 |
/* ---------- DHCP options ---------- */ #define LWIP_DHCP 1 |
Стек LwIP предоставляет гибкий способ управления размерами и организацией пула памяти. Он резервирует область статической памяти фиксированного размера в сегменте данных .
Существуют различные пулы, которые стек использует для различных структур данных. Например , есть пул для структур struct tcp_pcb и struct udp_pcb. Каждый пул может быть сконфигурирован для хранения фиксированного количества структур данных. Это количество можно изменить в файле lwipopts.h . Например, MEMP_NUM_TCP_PCB и MEMP_NUM_UDP_PCB определяют максимальное количество структур
struct tcp_pcb и struct udp_pcb, которые могут быть активны в системе одновременно .
Далее перечислены основные опции памяти :
Макрос | Описание |
MEM_SIZE | размер кучи LwIP , которая используется при динамическом выделении памяти |
MEMP_NUM_PBUF | общее количество MEM_REF и MEM_ROM pbuf |
MEMP_NUM_UDP_PCB | общее количество структур UDP PCB |
MEMP_NUM_TCP_PCB | общее количество структур TCP PCB |
MEMP_NUM_TCP_PCB _LISTEN | общее количество прослушиваемых TCP PCB |
MEMP_NUM_TCP_SEG | максимальное количество одновременных сегментов TCP |
PBUF_POOL_SIZE | общее количество pbuf с типом PBUF_POOL |
PBUF_POOL_BUFSIZE | размер pbuf с типом PBUF_POOL |
TCP_MSS | максимальный размер сегмента TCP |
TCP_SND_BUF | пространство буфера отправки TCP для подключения |
TCP_SND_QUEUELEN | максимальное количество pbuf в очереди отправки TCP |
TCP_WND | регламентирует размер окна приема TCP |
Обратите также внимание на конфигурационный файл stm32f4xx_hal_conf.h библиотеки HAL , в нем помимо прочего присутствуют настройки Ethernet интерфейса.
Отладка LwIP.
Для вывода отладочной информации в файле lwipopt.h нужно дописать соответсвующие макросы.
Для общего разрешения вывода отладочной информации используется :
1 |
#define LWIP_DEBUG 1 |
Кроме того можно отдельно разрешать/запрещать вывод сообщений от различных модулей (уровней стека).
Например чтобы выводить отладочную информацию от сетевого интерфейса необходимо добавить в lwipopt.h макрос
1 |
#define NETIF_DEBUG LWIP_DBG_ON |
Для вывода сообщений от Socket API необходимо добавить макрос
1 |
#define SOCKETS_DEBUG LWIP_DBG_ON |
В конечном счете отладочная информация будет выведена через стандартную функцию printf().
Перенаправить вывод можно в UART или на символьный индикатор, или куда пожелаете.
Я буду выводить отладочную информацию в UART. Для среды разработки SW4STM32 необходимо реализовать функцию вывода символа в UART __io_putchar.
Перед использованием необходимо проинициализировать задействованный в выводе отладочной информации модуль UART .
Пускай читатели простят меня за сумбурность изложения и возможные неточности в описании, поскольку стек LwIP является достаточно сложным программным пакетом и с одного наскока разобраться в принципах его работы непросто.
В следующей статье я планирую разобрать пример работы со стеком LWIP через BSD Socket API.
Viewed 275092 times by 42289 viewers
Comments