【前言】
STM32F769板载了网络接口,这就可以实现联网。由于没有基于stm32cubeMX的示例,我通过配置ETH以及LWIP但是没有成功的获取IP,所以手工进行移植。
【前期准备】
1、在官方的示例中,有基于MDK的联网示例,我是基于vscode的工程,所以只能做为学习的示例。
2、在github上面找到了一个基于stm32cubIDE的联网工程,能联上网,但是他是基于mqtt还有http的,他的工程是基于LCD的,所以我也只能拿过来学习。
3、然后我还使用stm32cubeMX配置了FreeRTOS。
在这样的前提下,我进行如下的方法,成功的实现了LWIP联网。
【源码的移植】
1、我将STM32CubeIDE的工程中的lwip源码,复制到我工程目录ThirdPart下面
2、然后根据Filielists.mk编把工程中的文件夹以及.c添加进CMakeLists.txt中。
最后添加好后文件如下:
target_include_directories(ThirdPart INTERFACE ./nr_micro_shell/inc ./lwip/src/include ./lwip/src/include/lwip ./lwip/src/include/lwip/apps ./lwip/src/include/lwip/priv ./lwip/src/include/lwip/prot ./lwip/src/include/netif ./lwip/src/include/posix ./lwip/src/include/posix/sys ./lwip/system ./lwip/LwIP/system ./lwip/src/include/netif/ppp ) target_sources(ThirdPart INTERFACE ./nr_micro_shell/src/ansi_port.c ./nr_micro_shell/src/nr_micro_shell.c ./nr_micro_shell/src/ansi.c ./lwip/src/core/init.c ./lwip/src/core/def.c ./lwip/src/core/dns.c ./lwip/src/core/inet_chksum.c ./lwip/src/core/ip.c ./lwip/src/core/mem.c ./lwip/src/core/memp.c ./lwip/src/core/netif.c ./lwip/src/core/pbuf.c ./lwip/src/core/raw.c ./lwip/src/core/stats.c ./lwip/src/core/sys.c ./lwip/src/core/tcp.c ./lwip/src/core/tcp_in.c ./lwip/src/core/tcp_out.c ./lwip/src/core/timeouts.c ./lwip/src/core/udp.c ./lwip/src/core/ipv4/autoip.c ./lwip/src/core/ipv4/dhcp.c ./lwip/src/core/ipv4/etharp.c ./lwip/src/core/ipv4/icmp.c ./lwip/src/core/ipv4/igmp.c ./lwip/src/core/ipv4/ip4_frag.c ./lwip/src/core/ipv4/ip4.c ./lwip/src/core/ipv4/ip4_addr.c ./lwip/src/api/api_lib.c ./lwip/src/api/api_msg.c ./lwip/src/api/err.c ./lwip/src/api/netbuf.c ./lwip/src/api/netdb.c ./lwip/src/api/netifapi.c ./lwip/src/api/sockets.c ./lwip/src/api/tcpip.c ./lwip/src/netif/ethernet.c ./lwip/src/netif/slipif.c ./lwip/src/apps/snmp/snmp_asn1.c ./lwip/src/apps/snmp/snmp_core.c ./lwip/src/apps/snmp/snmp_mib2.c ./lwip/src/apps/snmp/snmp_mib2_icmp.c ./lwip/src/apps/snmp/snmp_mib2_interfaces.c ./lwip/src/apps/snmp/snmp_mib2_ip.c ./lwip/src/apps/snmp/snmp_mib2_snmp.c ./lwip/src/apps/snmp/snmp_mib2_system.c ./lwip/src/apps/snmp/snmp_mib2_tcp.c ./lwip/src/apps/snmp/snmp_mib2_udp.c ./lwip/src/apps/snmp/snmp_msg.c ./lwip/src/apps/snmp/snmpv3.c ./lwip/src/apps/snmp/snmp_netconn.c ./lwip/src/apps/snmp/snmp_pbuf_stream.c ./lwip/src/apps/snmp/snmp_raw.c ./lwip/src/apps/snmp/snmp_scalar.c ./lwip/src/apps/snmp/snmp_table.c ./lwip/src/apps/snmp/snmp_threadsync.c ./lwip/src/apps/snmp/snmp_traps.c ./lwip/src/apps/snmp/snmpv3_mbedtls.c ./lwip/src/apps/snmp/snmpv3_dummy.c # ./lwip/src/apps/httpd/fs.c # ./lwip/src/apps/httpd/httpd.c ./lwip/src/apps/sntp/sntp.c ./lwip/src/apps/netbiosns/netbiosns.c ./lwip/src/apps/tftp/tftp_server.c ./lwip/src/apps/mqtt/mqtt.c ./lwip/system/OS/sys_arch.c )
3、接下来根据示例,编写网卡驱动ethernetif.c
根据原理图:
核对示例中的程序是否与原理图相同,编写驱动如下:
void HAL_ETH_MspInit(ETH_HandleTypeDef *heth) { GPIO_InitTypeDef GPIO_InitStructure; /* Enable GPIOs clocks */ __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_GPIOC_CLK_ENABLE(); __HAL_RCC_GPIOG_CLK_ENABLE(); /* Ethernet pins configuration ************************************************/ /* RMII_REF_CLK ----------------------> PA1 RMII_MDIO -------------------------> PA2 RMII_MDC --------------------------> PC1 RMII_MII_CRS_DV -------------------> PA7 RMII_MII_RXD0 ---------------------> PC4 RMII_MII_RXD1 ---------------------> PC5 RMII_MII_RXER ---------------------> PG2 RMII_MII_TX_EN --------------------> PG11 RMII_MII_TXD0 ---------------------> PG13 RMII_MII_TXD1 ---------------------> PG14 */ /* Configure PA1, PA2 and PA7 */ GPIO_InitStructure.Speed = GPIO_SPEED_HIGH; GPIO_InitStructure.Mode = GPIO_MODE_AF_PP; GPIO_InitStructure.Pull = GPIO_NOPULL; GPIO_InitStructure.Alternate = GPIO_AF11_ETH; GPIO_InitStructure.Pin = GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_7; HAL_GPIO_Init(GPIOA, &GPIO_InitStructure); /* Configure PC1, PC4 and PC5 */ GPIO_InitStructure.Pin = GPIO_PIN_1 | GPIO_PIN_4 | GPIO_PIN_5; HAL_GPIO_Init(GPIOC, &GPIO_InitStructure); /* Configure PG2, PG11, PG13 and PG14 */ GPIO_InitStructure.Pin = GPIO_PIN_2 | GPIO_PIN_11 | GPIO_PIN_13 | GPIO_PIN_14; HAL_GPIO_Init(GPIOG, &GPIO_InitStructure); /* Enable the Ethernet global Interrupt */ HAL_NVIC_SetPriority(ETH_IRQn, 0x7, 0); HAL_NVIC_EnableIRQ(ETH_IRQn); /* Enable ETHERNET clock */ __HAL_RCC_ETH_CLK_ENABLE(); }
此函数主要核对示例中的官脚与开发板原理图是相同的,不用修改,直接放入core/src目录下面,同时把头文件也添加进工程中。
为是实现中断,需要在stm32f7xx_it.c中添加中断函数:
/* USER CODE BEGIN 1 */ void ETH_IRQHandler(void) { HAL_ETH_IRQHandler(&EthHandle); } /* USER CODE END 1 */
同时也要extern 一下extern ETH_HandleTypeDef EthHandle;
4、编写用户app_ethernet.c,这里主要实现网络接口状态,如果网络接口已连接,则根据是否需要进行DHCP进行相应的操作
/* Private function prototypes -----------------------------------------------*/ /* Private functions ---------------------------------------------------------*/ /** * @brief 通知用户网络接口配置状态 * @param netif: the network interface * @retval None */ void User_notification(struct netif *netif) { if (netif_is_up(netif)) { #ifdef USE_DHCP /* Update DHCP state machine */ DHCP_state = DHCP_START; #else uint8_t iptxt[20]; sprintf((char *)iptxt, "%s", ip4addr_ntoa((const ip4_addr_t *)&netif->ip_addr)); printf ("Static IP address: %s\n", iptxt); #endif /* USE_DHCP */ } else { #ifdef USE_DHCP /* Update DHCP state machine */ DHCP_state = DHCP_LINK_DOWN; #endif /* USE_DHCP */ printf ("The network cable is not connected \n"); } }
5、接下来实现一个DHCP的任务:
#ifdef USE_DHCP /** * @brief DHCP Process * @param argument: network interface * @retval None */ void DHCP_thread(void const * argument) { struct netif *netif = (struct netif *) argument; ip_addr_t ipaddr; ip_addr_t netmask; ip_addr_t gw; struct dhcp *dhcp; uint8_t iptxt[20]; for (;;) { switch (DHCP_state) { case DHCP_START: { ip_addr_set_zero_ip4(&netif->ip_addr); ip_addr_set_zero_ip4(&netif->netmask); ip_addr_set_zero_ip4(&netif->gw); dhcp_start(netif); DHCP_state = DHCP_WAIT_ADDRESS; printf (" State: Looking for DHCP server ...\n"); } break; case DHCP_WAIT_ADDRESS: { if (dhcp_supplied_address(netif)) { DHCP_state = DHCP_ADDRESS_ASSIGNED; sprintf((char *)iptxt, "%s", ip4addr_ntoa((const ip4_addr_t *)&netif->ip_addr)); printf("IP address assigned by a DHCP server: %s\n", iptxt); } else { dhcp = (struct dhcp *)netif_get_client_data(netif, LWIP_NETIF_CLIENT_DATA_INDEX_DHCP); /* DHCP timeout */ if (dhcp->tries > MAX_DHCP_TRIES) { DHCP_state = DHCP_TIMEOUT; /* Stop DHCP */ dhcp_stop(netif); /* Static address used */ 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); netif_set_addr(netif, ip_2_ip4(&ipaddr), ip_2_ip4(&netmask), ip_2_ip4(&gw)); sprintf((char *)iptxt, "%s", ip4addr_ntoa((const ip4_addr_t *)&netif->ip_addr)); printf("DHCP Timeout !! \n"); printf("Static IP address: %s\n", iptxt); } } } break; case DHCP_LINK_DOWN: { /* Stop DHCP */ dhcp_stop(netif); DHCP_state = DHCP_OFF; } break; default: break; } /* wait 250 ms */ osDelay(250); } }
在这个函数中,如果有配置函数,定义的DHCP,则进行DHCP的任务进程,如果是DHCP开始,测清空网卡中的addr、netmask、gw,同时开启dhcp_start任务。
接下来就判断是成功获取到IP地址,如果没有,则按照配置文件的中IP地址、网卡、DNS进行配置。
相关的配置我放到main.h中进行了定义:
6、需要用到网卡驱动,我们需要到stm32f7xx_hal_conf.h中打开#define HAL_ETH_MODULE_ENABLED配置。
同时还需要添加网络的配置信息,配置信息如下:
/* Section 1 : Ethernet peripheral configuration */ /* MAC ADDRESS: MAC_ADDR0:MAC_ADDR1:MAC_ADDR2:MAC_ADDR3:MAC_ADDR4:MAC_ADDR5 */ #define MAC_ADDR0 2U #define MAC_ADDR1 0U #define MAC_ADDR2 0U #define MAC_ADDR3 0U #define MAC_ADDR4 0U #define MAC_ADDR5 0U /* Definition of the Ethernet driver buffers size and count */ #define ETH_RX_BUF_SIZE ETH_MAX_PACKET_SIZE /* buffer size for receive */ #define ETH_TX_BUF_SIZE ETH_MAX_PACKET_SIZE /* buffer size for transmit */ #define ETH_RXBUFNB ((uint32_t)4U) /* 4 Rx buffers of size ETH_RX_BUF_SIZE */ #define ETH_TXBUFNB ((uint32_t)4U) /* 4 Tx buffers of size ETH_TX_BUF_SIZE */ /* Section 2: PHY configuration section */ /* LAN8742A PHY Address*/ #define LAN8742A_PHY_ADDRESS 0x00U /* DP83848_PHY_ADDRESS Address*/ #define DP83848_PHY_ADDRESS /* PHY Reset delay these values are based on a 1 ms Systick interrupt*/ #define PHY_RESET_DELAY ((uint32_t)0x000000FFU) /* PHY Configuration delay */ #define PHY_CONFIG_DELAY ((uint32_t)0x00000FFFU) #define PHY_READ_TO ((uint32_t)0x0000FFFFU) #define PHY_WRITE_TO ((uint32_t)0x0000FFFFU) /* Section 3: Common PHY Registers */ #define PHY_BCR ((uint16_t)0x0000U) /*!< Transceiver Basic Control Register */ #define PHY_BSR ((uint16_t)0x0001U) /*!< Transceiver Basic Status Register */ #define PHY_RESET ((uint16_t)0x8000U) /*!< PHY Reset */ #define PHY_LOOPBACK ((uint16_t)0x4000U) /*!< Select loop-back mode */ #define PHY_FULLDUPLEX_100M ((uint16_t)0x2100U) /*!< Set the full-duplex mode at 100 Mb/s */ #define PHY_HALFDUPLEX_100M ((uint16_t)0x2000U) /*!< Set the half-duplex mode at 100 Mb/s */ #define PHY_FULLDUPLEX_10M ((uint16_t)0x0100U) /*!< Set the full-duplex mode at 10 Mb/s */ #define PHY_HALFDUPLEX_10M ((uint16_t)0x0000U) /*!< Set the half-duplex mode at 10 Mb/s */ #define PHY_AUTONEGOTIATION ((uint16_t)0x1000U) /*!< Enable auto-negotiation function */ #define PHY_RESTART_AUTONEGOTIATION ((uint16_t)0x0200U) /*!< Restart auto-negotiation function */ #define PHY_POWERDOWN ((uint16_t)0x0800U) /*!< Select the power down mode */ #define PHY_ISOLATE ((uint16_t)0x0400U) /*!< Isolate PHY from MII */ #define PHY_AUTONEGO_COMPLETE ((uint16_t)0x0020U) /*!< Auto-Negotiation process completed */ #define PHY_LINKED_STATUS ((uint16_t)0x0004U) /*!< Valid link established */ #define PHY_JABBER_DETECTION ((uint16_t)0x0002U) /*!< Jabber condition detected */ /* Section 4: Extended PHY Registers */ #define PHY_SR ((uint16_t)0x1FU) /*!< PHY special control/ status register Offset */ #define PHY_SPEED_STATUS ((uint16_t)0x0004U) /*!< PHY Speed mask */ #define PHY_DUPLEX_STATUS ((uint16_t)0x0010U) /*!< PHY Duplex mask */ #define PHY_ISFR ((uint16_t)0x1DU) /*!< PHY Interrupt Source Flag register Offset */ #define PHY_ISFR_INT4 ((uint16_t)0x0010U) /*!< PHY Link down inturrupt */
7、接下来在freertos.c中的任务,添加配置初始化与网络开始的代码:
/* USER CODE BEGIN Variables */ 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); } } /* USER CODE END Variables */
这里我们选初始化网络配置,根据是否DHCP进行一些基本配置,同时根据网络是否接入,开启基本配置。
接下来在任务中调用tcpip_init。
然后创建一个dhcp的任务。
接下来等待网络接入,DHCP是否就绪,如果就绪则打印出IP地址。
void StartDefaultTask(void const * argument) { /* USER CODE BEGIN StartDefaultTask */ /* Infinite loop */ tcpip_init(NULL, NULL); Netif_Config(); User_notification(&gnetif); #ifdef USE_DHCP /* Start DHCPClient */ osThreadDef(DHCP, DHCP_thread, osPriorityBelowNormal, 0, configMINIMAL_STACK_SIZE * 5); osThreadCreate(osThread(DHCP), &gnetif); #endif // 等待网络接口启用 while (!netif_is_up(&gnetif)) { shell_printf("Waiting for network interface to be up...\n"); osDelay(1000); } // 等待获取IP地址 while (ip_addr_isany(netif_ip4_addr(&gnetif))) { shell_printf("Waiting for IP address...\n"); osDelay(1000); } shell_printf("IP address obtained: %s\n", ipaddr_ntoa(netif_ip4_addr(&gnetif))); // 启动TCP客户端 tcp_client_init(); for(;;) { shell_usart_loop(); osDelay(100); } /* USER CODE END StartDefaultTask */ }
8、tcp_client测试。
我创建了一个tcp_client的测试程序,在程序中创建tcp客户端去连接我在电脑上创建的一个TCP服务器:
示例代码如下:
#include "lwip/tcp.h" #include "lwip/netif.h" #include "lwip/init.h" #include "lwip/sys.h" #include "lwip/ip_addr.h" #include "shell_uart.h" #include "string.h" static err_t tcp_client_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err); static err_t tcp_client_send(struct tcp_pcb *tpcb, const char *data, u16_t len); static err_t tcp_client_connected(void *arg, struct tcp_pcb *tpcb, err_t err) { if (err != ERR_OK) { tcp_close(tpcb); return err; } shell_printf("tcp_client connected\r\n"); // Set the receive callback function tcp_recv(tpcb, tcp_client_recv); // Send data to the server const char *data = "Hello, Server!\r\n"; tcp_client_send(tpcb, data, strlen(data)); // Send data or perform other actions upon successful connection return ERR_OK; } static err_t tcp_client_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err) { if (p == NULL) { tcp_close(tpcb); return ERR_OK; } // Process received data shell_printf("Received data: %.*s\n", p->tot_len, (char *)p->payload); // Indicate that the data has been received tcp_recved(tpcb, p->tot_len); // Free the received pbuf pbuf_free(p); // Continue to receive data tcp_recv(tpcb, tcp_client_recv); return ERR_OK; } static void tcp_client_error(void *arg, err_t err) { // Handle error shell_printf("TCP error: %d\n", err); } static err_t tcp_client_poll(void *arg, struct tcp_pcb *tpcb) { // Handle periodic actions return ERR_OK; } static err_t tcp_client_send(struct tcp_pcb *tpcb, const char *data, u16_t len) { struct pbuf *p = pbuf_alloc(PBUF_TRANSPORT, len, PBUF_POOL); if (p == NULL) { shell_printf("Failed to allocate pbuf\n"); return ERR_MEM; } memcpy(p->payload, data, len); err_t err = tcp_write(tpcb, p->payload, len, TCP_WRITE_FLAG_COPY); if (err != ERR_OK) { shell_printf("Failed to send data: %d\n", err); pbuf_free(p); return err; } err = tcp_output(tpcb); if (err != ERR_OK) { shell_printf("Failed to output data: %d\n", err); pbuf_free(p); return err; } pbuf_free(p); return ERR_OK; } void tcp_client_init(void) { struct tcp_pcb *tpcb; ip_addr_t server_ip; IP4_ADDR(&server_ip, 192, 168, 3, 231); tpcb = tcp_new(); if (tpcb != NULL) { tcp_arg(tpcb, NULL); tcp_err(tpcb, tcp_client_error); tcp_poll(tpcb, tcp_client_poll, 1); err_t err = tcp_connect(tpcb, &server_ip, 8000, tcp_client_connected); if (err != ERR_OK) { shell_printf("tcp_client err: %d\r\n", err); tcp_close(tpcb); } else { shell_printf("tcp_client OK\r\n"); } } else { shell_printf("Failed to create TCP PCB\n"); } }
在此示例中,如果成功的连接到了TCP服务器,则循环接收服务端是否发回了消息,如果有,通过串口进行打印。