【前言】
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服务器,则循环接收服务端是否发回了消息,如果有,通过串口进行打印。
我要赚赏金
