Наконец дошли руки и до Ethernet на stm32f4 discovery.
Прикупил себе модулек с физическим интерфейсом LAN8720. Он подключается к stm32 по интерфейсу RMII.
ETH_MDIO --------------> PA2 ETH_MDC ---------------> PC1 ETH_RMII_REF_CLK-------> PA1 ETH_RMII_CRS_DV -------> PA7 ETH_RMII_RXD0 -------> PC4 ETH_RMII_RXD1 -------> PC5 ETH_RMII_TX_EN -------> PB11 ETH_RMII_TXD0 -------> PB12 ETH_RMII_TXD1 -------> PB13 ETH_RST_PIN -------> PE2
В сети ходит пример работы с данной микросхемой. Но стек lwip там старый, да и freertos не самый свежий.
За сим предлагаю вам пример на freertos 8.2 и lwip 1.4.1.
В примере с изображения выше все довольно сильно наворочено. Там вам и картинки в Flash памяти контроллера и dhcp, мы пойдем слегка более легким путем.
Нам понадобятся свежие исходники freertos и lwip.
Как добавить в проект freertos.
Из исходников lwip нам понодобятся папки api, core/ipv4, netif.
Также нужен порт.
Портируется lwip довольно просто. Порт включает в себя функции оболочки для создания и управления семафорами и задачами операционной системы, а также низкоуровневые функции работы с периферией. Это два файла sys_arch.c и ethernetif.c. Первый можно добыть в недрах freertos (или написать свой), а второй — часть примеров ST.
Структура проекта в итоге получится вот такая:
При старте нужно инициализировать MAC модуль в STM32. Это делается при помощи функции ETH_BSP_Config() из библиотеки STM32F4x7_ETH_Driver. Она стандартна и доступна на сайте ST. В ней настраиваются GPIO, NVIC и MAC модули.
Сразу оговорка. Нужно аккуратно настраивать NVIC с freertos. Иначе можно улететь в HardFault.
Когда периферия контроллера настроена и готова к работе нужно инициализировать lwip.
Нужно ему сообщить ip адрес, маску и шлюз.
Я настроил на компьютере ip 192.168.1.1, маску 255.255.255.0 и шлюз 192.168.1.1.
В main.h указал такие настройки.
/*Static IP ADDRESS*/ #define IP_ADDR0 192 #define IP_ADDR1 168 #define IP_ADDR2 1 #define IP_ADDR3 2 /*NETMASK*/ #define NETMASK_ADDR0 255 #define NETMASK_ADDR1 255 #define NETMASK_ADDR2 255 #define NETMASK_ADDR3 0 /*Gateway Address*/ #define GW_ADDR0 192 #define GW_ADDR1 168 #define GW_ADDR2 1 #define GW_ADDR3 1
Эти настройки сугубо личные. Можно настроить как угодно, но у меня интернет через WifI и стандартные 192.168.0.1 заняты под интернет, чтобы не переключать постоянно настройки, сделал так.
Вызываем LwIP_Init.
void LwIP_Init(void) { ip_addr_t ipaddr; ip_addr_t netmask; ip_addr_t gw; /* Create tcp_ip stack thread */ tcpip_init( NULL, NULL ); /* IP address setting & display on STM32_evalboard LCD*/ #ifdef USE_DHCP ipaddr.addr = 0; netmask.addr = 0; gw.addr = 0; #else IP4_ADDR(&ipaddr, IP_ADDR0, IP_ADDR1, IP_ADDR2, IP_ADDR3); IP4_ADDR(&netmask, NETMASK_ADDR0, NETMASK_ADDR1 , NETMASK_ADDR2, NETMASK_ADDR3); IP4_ADDR(&gw, GW_ADDR0, GW_ADDR1, GW_ADDR2, GW_ADDR3); #endif /* - netif_add(struct netif *netif, struct ip_addr *ipaddr, struct ip_addr *netmask, struct ip_addr *gw, void *state, err_t (* init)(struct netif *netif), err_t (* input)(struct pbuf *p, struct netif *netif)) Adds your network interface to the netif_list. Allocate a struct netif and pass a pointer to this structure as the first argument. Give pointers to cleared ip_addr structures when using DHCP, or fill them with sane numbers otherwise. The state pointer may be NULL. The init function pointer must point to a initialization function for your ethernet netif interface. The following code illustrates it's use.*/ netif_add(&xnetif, &ipaddr, &netmask, &gw, NULL, ðernetif_init, &tcpip_input); /* Registers the default network interface. */ netif_set_default(&xnetif); /* When the netif is fully configured this function must be called.*/ netif_set_up(&xnetif); }
В данном примере DHCP не используется. Оставил из старого примера.
Теперь создадим задачу обслуживания соединения. Сделаем простой http сервер.
xTaskCreate(http_server_netconn_thread, "HTTP", configMINIMAL_STACK_SIZE, NULL, 2, ( TaskHandle_t * ) NULL);
В этой задаче мы создаем TCP сокет на 80 порту и ждем соединения.
void http_server_netconn_thread() { struct netconn *conn, *newconn; err_t err; /* Create a new TCP connection handle */ conn = netconn_new(NETCONN_TCP); if (conn!= NULL) { /* Bind to port 80 (HTTP) with default IP address */ err = netconn_bind(conn, NULL, 80); if (err == ERR_OK) { /* Put the connection into LISTEN state */ netconn_listen(conn); while(1) { /* accept any icoming connection */ err = netconn_accept(conn, &newconn); /* serve connection */ http_server_serve(newconn); /* delete connection */ netconn_delete(newconn); } } else { printf("can not bind netconn"); } } else { printf("can not create netconn"); } }
При появлении запроса вызываем функцию http_server_serve.
void http_server_serve(struct netconn *conn) { struct netbuf *inbuf; err_t res; char* buf; u16_t buflen; size_t len; static u16_t call_times = 0; res = netconn_recv(conn, &inbuf); if (res == ERR_OK) { netbuf_data(inbuf, (void**)&buf, &buflen); if ((buflen >=5) && (strncmp(buf, "GET /", 5) == 0)) { sprintf(data, "Hello %d times!", call_times++); len = strlen(data); netconn_write(conn, (const unsigned char*)(data), (size_t)len, NETCONN_NOCOPY); } } /* Close the connection (server closes in HTTP) */ netconn_close(conn); /* Delete the buffer (netconn_recv gives us ownership, so we have to make sure to deallocate the buffer) */ netbuf_delete(inbuf); }
Я сделал 1 страничку «Hello world».
При каждом обновлении цифра увеличивается на единицу.
Eсли в общем, то главное отличие порта lwip 1.4 от 1.3 в функциях работы с семаформи. В новом lwip работа с ними происходит по указателю, в старом же — функции возвращали семафоры.
Код на GitHub.