STM32网络通讯之LWIP下载移植项目设计(十六)
STM32F407 系列文章 - ETH-LWIP-Transplant(十六)
目录
前言
一、软件设计
二、下载移植实现
1.lwip下载
1.下载方式一
2.下载方式二
2.lwip文件介绍
3.lwip移植
4.添加PHY驱动代码
5.修改ethernetif文件
6.添加用户代码
7.main文件实现
8.效果演示
总结
前言
一般对于许多嵌入式系统或单片机,在其资源受限的环境下,要想实现网络通讯,并保证资源的高效利用和稳定的网络通信,我们一般采用一种轻量级的网络协议lwIP。TI公司的STM32芯片一般都会自带一路以太网口,用于网络通讯,但因其内存资源受限,所以都用采用一种小型化、轻量级的lwIP网络协议,只需十几KB的RAM和大约40K的ROM即可运行,既可以在无操作系统环境下工作,也可以与各种操作系统配合使用,使其成为资源受限的嵌入式系统的理想选择。一般市场上所卖的板子都带这一功能的,需准备STM32F407开发板一块和网线一根。
一、软件设计
前面博文STM32之LWIP网络通讯设计介绍(十四)-CSDN博客讲述了对STM32实现LWIP网络通讯的前提性要求介绍,包含使用到的网络协议、MAC内核、PHY驱动芯片、通讯连接示意图、以及硬件电路原理设计图,为网络通讯软件开发提供了设计指导。STM32网络通讯的软件设计,在上一篇文章STM32网络通讯之CubeMX实现LWIP项目设计(十五)-CSDN博客,采用的是通过可视化工具STM32CubeMX完成对lwIP通讯的配置,一键化生成工程代码。今天将采用另一种实现方法,即先下载lwIP,然后完成lwIP移植到STM32工程项目中,在完成其通讯配置,这种实现方式较前面的有一定的困难,对熟悉lwip通讯有一定的要求,如果对lwip不是足够了解,博主推荐采用前篇文章实现。
二、下载移植实现
通过下载、移植lwIP到STM32工程项目中,实现网络通讯。
1.lwip下载
点击打开LWIP官网lwIP - 轻量级 TCP/IP 堆栈,显示界面如下所示。在上面既可以下载所需版本lwip,也可以获得相应技术支持。
1.下载方式一
在上面找到下载区,点击进入后,选择我们需要下载的版本,如下所示。这里为了与前面的CubeMX工具上lwip版本保持一致,也选择lwip 2.12版本。
点击上面的版本进行下载时,有时无法响应,这时我们可以采取git方式下,即方式二下载。
2.下载方式二
选择上面的git存储区,点击“lwIP-轻量级 TCPIP堆栈”(lwip.git - lwIP - 轻量级 TCPIP 堆栈),选择对应的版本进行下载。在该选项下面还有一个“lwIP Contrib - 为轻量级 TCP/IP 堆栈提供的代码”选项(contrib.git - lwIP Contrib - 用户例程代码)即官方Contrib提供的demo例程代码,这里选择lwip-contrib-STABLE-2_1_0_RELEASE.tar.gz进行下载(后面需要用到里面的文件)。
也可以直接点击下载网址进行下载,提供对应版本下载地址如下:
https://git.savannah.nongnu.org/cgit/lwip.git/snapshot/lwip-STABLE-2_1_2_RELEASE.tar.gzhttps://git.savannah.nongnu.org/cgit/lwip.git/snapshot/lwip-STABLE-2_1_2_RELEASE.tar.gz下载完后,可以看到如下大小的压缩包。
2.lwip文件介绍
对上面下载的压缩包进行解压,得到如下画面。
上图中的lwip2.1.2文件夹包含了许多文件和子文件夹,关于里面的文件我们不需要关心,主要是记录lwIP源码更新、开源软件license、描述lwIP的特点、介绍lwIP源码包的文件目录信息等等,无关紧要。另外还有三个文件夹doc、src、test,其中doc文件夹里面是关于LwIP的一些文档,可以看成是应用和移植LwIP的指南;test文件夹里面是测试LwIP内核性能的源码,将它们和LwIP源码加入到工程中一起编译,调用它们提供的函数,可以获得许多与LwIP内核性能有关的指标;src文件夹是lwIP源码包中最重要的,它是lwIP的内核文件,也是我们移植到工程中的重要文件。
打开src文件夹,如下所示,主要讲解下面5个文件夹。
api文件夹里面装的是NETCONN API和Socket API相关的源文件,只有在操作系统的环境中,才 能被编译。apps文件夹里面装的是应用程序的源文件,包括常见的应用程序,如httpd、mqtt、tftp、sntp、snmp等。core文件夹里面是LwIP的内核源文件,后续会详细讲解。include文件夹里面是LwIP所有模块对应的头文件。netif文件夹里面是与网卡移植有关的文件,这些文件为我们移植网卡提供了模板,我们可以直接 使用。
LwIP内核是由一系列模块组合而成的,这些模块包括:TCP/IP协议栈的各种协议、内存管理模 块、数据包管理模块、网卡管理模块、网卡接口模块、基础功能类模块、API模块。每个模块是由相关的几个源文件和头文件组成的,通过头文件对外声明一些函数、宏、数据类型,使得其它模块可以方便地调用此模块的功能。而构成每个模块的头文件都被组织在了include目录中,而源文件则根据类型被分散地组织在 api、apps、core、netif目录中。下面的子级文件在不作介绍,具体可以查看LwIP的官方说明文档。
3.lwip移植
打开我们stm32项目工程文件,如下。如果没有Middlewares文件夹,则创建它,然后在 Middlewares文件夹下创建了一个名为“lwip”的子文件夹。在“lwip”文件夹下,我们又创建了两个子文件夹:arch和lwip_app。arch文件夹用于存放lwIP系统的配置文件;而lwip_app文件夹则用于存放用户自定义的文件,例如应用程序的源代码等。最后将上述的lwip-STABLE-2_1_2_RELEASE文件夹里面的src文件夹拷贝到lwip文件夹里面。
打开stm32工程,并添加 Middlewares/lwip/src、Middlewares/lwip/lwip_app 和 Middlewares/lwip/arch这3个分组,如下所示。
在Middlewares/lwip/src分组添加src/api 路径下、和src/core路径下的除ipv6文件夹的全部.c文件,另添加src/netif路径下的ethernet.c文件,如下图所示。
而arch文件夹主要存放lwipopts.h、cc.h、ethernetif.c/h这四个文件都可以在“lwip-contrib-STABLE-2_1_0_RELEASE”文件包下获取。然后再Middlewares/lwip/arch分组添加arch文件夹下的.c文件,显示如下。
添加/移植完后,在keil上显示如下文件。
4.添加PHY驱动代码
这里添加的PHY驱动文件代码,就相当于上一篇博文CubeMX生成项目工程下的lwip.c/h和ethernetif.c/h文件的代码,因为CubeMX界面上配置好的PHY驱动参数,生成为lwip.c/h和ethernetif.c/h文件,lwip官方代码是不带驱动代码的,因其不知道你用哪款驱动芯片。添加PHY驱动代码实现步骤如下:
在工程项目路径Drivers\BSP文件夹中,创建“ETHERNET”文件夹,如下所示。
然后在“ETHERNET”文件夹下新建ethernet.c和ethernet.h这两个文件,并将这两个文件添加到项目工程中Drivers/BSP分组下,如下所示。
在这两个文件需要完成以太网驱动初始化和MAC的驱动程序,源代码如下示例。
#include "./BSP/ETHERNET/EthDriver.h"
ETH_HandleTypeDef g_eth_handler; /* 以太网句柄 */
LWIP_DEV_INFO g_lwipdev; /* lwip控制结构体 */
struct netif g_netif; /* 定义一个全局的网络接口 */
void lwip_dhcp_process(struct netif *netif);
/**
* @breif lwip 设备信息默认设置
* @param lwipx : lwip控制结构体指针
* @retval 无
*/
uint8_t lwip_dev_info_set(LWIP_DEV_INFO *lwipx)
{
/* 默认远端IP为:192.168.1.113 */
lwipx->remoteip[0] = 192;
lwipx->remoteip[1] = 168;
lwipx->remoteip[2] = 1;
lwipx->remoteip[3] = 113;
/* MAC地址设置 */
lwipx->mac[0] = 0xB8;
lwipx->mac[1] = 0xAE;
lwipx->mac[2] = 0x1D;
lwipx->mac[3] = 0x00;
lwipx->mac[4] = 0x01;
lwipx->mac[5] = 0x00;
/* 默认本地IP为:192.168.1.201 */
lwipx->localip[0] = 192;
lwipx->localip[1] = 168;
lwipx->localip[2] = 1;
lwipx->localip[3] = 201;
/* 默认子网掩码:255.255.255.0 */
lwipx->netmask[0] = 255;
lwipx->netmask[1] = 255;
lwipx->netmask[2] = 255;
lwipx->netmask[3] = 0;
/* 默认网关:192.168.1.1 */
lwipx->gateway[0] = 192;
lwipx->gateway[1] = 168;
lwipx->gateway[2] = 1;
lwipx->gateway[3] = 1;
/* 默认远端主机端口:8888 */
lwipx->remoteport = UDP_PORT;
/* 默认本机主机端口:8888 */
lwipx->localport = UDP_PORT;
lwipx->dhcpstatus = 0; /* 没有DHCP */
return 0;
}
/**
* @brief 以太网芯片初始化
* @param 无
* @retval 0,成功
* 1,失败
*/
uint8_t ethernet_init(void)
{
uint8_t macaddress[6];
macaddress[0] = g_lwipdev.mac[0];
macaddress[1] = g_lwipdev.mac[1];
macaddress[2] = g_lwipdev.mac[2];
macaddress[3] = g_lwipdev.mac[3];
macaddress[4] = g_lwipdev.mac[4];
macaddress[5] = g_lwipdev.mac[5];
g_eth_handler.Instance = ETH;
g_eth_handler.Init.AutoNegotiation = ETH_AUTONEGOTIATION_ENABLE; /* 使能自协商模式 */
g_eth_handler.Init.Speed = ETH_SPEED_100M; /* 速度100M,如果开启了自协商模式,此配置就无效 */
g_eth_handler.Init.DuplexMode = ETH_MODE_FULLDUPLEX; /* 全双工模式,如果开启了自协商模式,此配置就无效 */
g_eth_handler.Init.PhyAddress = ETHERNET_PHY_ADDRESS; /* 以太网芯片的地址 */
g_eth_handler.Init.MACAddr = macaddress; /* MAC地址 */
g_eth_handler.Init.RxMode = ETH_RXINTERRUPT_MODE; /* 中断接收模式 */
g_eth_handler.Init.ChecksumMode = ETH_CHECKSUM_BY_HARDWARE; /* 硬件帧校验 */
g_eth_handler.Init.MediaInterface = ETH_MEDIA_INTERFACE_RMII; /* RMII接口 */
if (HAL_ETH_Init(&g_eth_handler) == HAL_OK)
return 0; /* 成功 */
else
return 1; /* 失败 */
}
/**
* @brief ETH底层驱动,时钟使能,引脚配置
* @note 此函数为虚函数被重写 此函数会被HAL_ETH_Init()调用
* @param heth:以太网句柄
* @retval 无
*/
void HAL_ETH_MspInit(ETH_HandleTypeDef *heth)
{
GPIO_InitTypeDef gpio_init_struct;
__HAL_RCC_ETH_CLK_ENABLE(); /* 开启ETH时钟 */
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOG_CLK_ENABLE();
/* 网络引脚设置 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 -------------------> PG11
* ETH_RMII_TXD0 --------------------> PG13
* ETH_RMII_TXD1 --------------------> PG14
* ETH_RESET-------------------------> PD3
*/
/* PA1,2,7 */
gpio_init_struct.Pin = GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_7;
gpio_init_struct.Mode = GPIO_MODE_AF_PP; /* 推挽复用 */
gpio_init_struct.Pull = GPIO_NOPULL; /* 不带上下拉 */
gpio_init_struct.Speed = GPIO_SPEED_HIGH; /* 高速 */
gpio_init_struct.Alternate = GPIO_AF11_ETH; /* 复用为ETH功能 */
HAL_GPIO_Init(GPIOA, &gpio_init_struct); /* ETH_CLK,ETH_MDIO,ETH_CRS引脚模式设置 */
/* PC1,4,5 */
gpio_init_struct.Pin = GPIO_PIN_1|GPIO_PIN_4|GPIO_PIN_5;
HAL_GPIO_Init(GPIOC, &gpio_init_struct); /* ETH_MDC,ETH_RXD0,ETH_RXD1初始化 */
/* PG11,13,14 */
gpio_init_struct.Pin = GPIO_PIN_11|GPIO_PIN_13|GPIO_PIN_14;
HAL_GPIO_Init(GPIOG, &gpio_init_struct); /* ETH_TX_EN,ETH_TXD0,ETH_TXD1初始化 */
/* PD3复位引脚 */
gpio_init_struct.Pin = GPIO_PIN_3; /* ETH_RESET初始化 */
gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP; /* 推挽输出 */
HAL_GPIO_Init(GPIOD, &gpio_init_struct);
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_3, GPIO_PIN_RESET); /* 硬件复位 */
delay_ms(50);
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_3, GPIO_PIN_SET); /* 复位结束 */
HAL_NVIC_SetPriority(ETH_IRQn, 1, 0); /* 网络中断优先级应该高一点 */
HAL_NVIC_EnableIRQ(ETH_IRQn);
}
/**
* @breif LWIP初始化(LWIP启动的时候使用)
* @param 无
* @retval 0,成功
* 1,设备IP信息错误
* 2,以太网芯片初始化失败
* 3,网卡添加失败.
*/
uint8_t MX_LWIP_Init(void)
{
ip4_addr_t ipaddr; /* ip地址 */
ip4_addr_t netmask; /* 子网掩码 */
ip4_addr_t gw; /* 默认网关 */
if (lwip_dev_info_set(&g_lwipdev)) /* 设置默认IP等信息 0:ok 1:fail */
return 1; /* 设置IP信息失败 */
if (ethernet_init()) /* 初始化以太网芯片 0:ok 1:fail */
return 3; /* 以太网芯片初始化失败 */
lwip_init(); /* 初始化LWIP内核 Initilialize the LwIP stack without RTOS */
#if LWIP_DHCP /* 使用动态IP */
ip_addr_set_zero_ip4(&ipaddr); /* 对IP地址、子网掩码及网关清零 */
ip_addr_set_zero_ip4(&netmask);
ip_addr_set_zero_ip4(&gw);
#else /* 使用静态IP */
IP4_ADDR(&ipaddr, g_lwipdev.localip[0], g_lwipdev.localip[1], g_lwipdev.localip[2], g_lwipdev.localip[3]);
IP4_ADDR(&netmask, g_lwipdev.netmask[0], g_lwipdev.netmask[1], g_lwipdev.netmask[2], g_lwipdev.netmask[3]);
IP4_ADDR(&gw, g_lwipdev.gateway[0], g_lwipdev.gateway[1], g_lwipdev.gateway[2], g_lwipdev.gateway[3]);
g_lwipdev.dhcpstatus = 0XFF; /* 获取失败 */
#endif
/* 向网卡列表中添加一个网口add the network interface (IPv4/IPv6) without RTOS */
if (netif_add(&g_netif, &ipaddr, &netmask, &gw, NULL, ðernetif_init, ðernet_input) == NULL) /* 调用netif_add()返回值,用于判断网络初始化是否成功 */
return 2; /* 网卡添加失败 */
netif_set_default(&g_netif); /* 网口添加成功,设置netif为默认网口,并且打开netif网口 */
if (netif_is_link_up(&g_netif))
netif_set_up(&g_netif); /* 当netif完全配置后,调用此函数打开netif网口 */
else
netif_set_down(&g_netif); /* 当netif链接关闭时,调用此函数关闭netif网口 */
lwip_link_status_updated(&g_netif); /* DHCP链接状态更新函数 */
netif_set_link_callback(&g_netif, lwip_link_status_updated); /*设置链接回调函数,此函数在链接状态更改时调用 */
#if LWIP_DHCP /* 如果使用DHCP的话 */
g_lwipdev.dhcpstatus = 0; /* DHCP标记为0 */
#endif
return 0; /* 操作OK. */
}
/**
* @breif 读取以太网芯片寄存器值
* @param reg:读取的寄存器地址
* @retval 无
*/
uint32_t ethernet_read_phy(uint16_t reg)
{
uint32_t regval;
HAL_ETH_ReadPHYRegister(&g_eth_handler, reg, ®val);
return regval;
}
/**
* @breif 向以太网芯片指定地址写入寄存器值
* @param reg : 要写入的寄存器
* @param value : 要写入的寄存器
* @retval 无
*/
void ethernet_write_phy(uint16_t reg, uint16_t value)
{
uint32_t temp = value;
HAL_ETH_WritePHYRegister(&g_eth_handler, reg, temp);
}
/**
* @breif 获得网络芯片的速度模式
* @param 无
* @retval 1:获取100M成功
0:失败
*/
uint8_t ethernet_chip_get_speed(void)
{
uint8_t speed;
#if(PHY_TYPE == LAN8720)
speed = ~((ethernet_read_phy(PHY_SR) & PHY_SPEED_STATUS)); /* 从LAN8720的31号寄存器中读取网络速度和双工模式 */
#elif(PHY_TYPE == YT8512C)
speed = ((ethernet_read_phy(PHY_SR) & PHY_SPEED_STATUS) >> 14); /* 从YT8512C的17号寄存器中读取网络速度和双工模式 */
#endif
return speed;
}
/**
* @breif 获取接收到的帧长度
* @param dma_rx_desc : 接收DMA描述符
* @retval frameLength : 接收到的帧长度
*/
uint32_t ethernet_get_eth_rx_size(ETH_DMADescTypeDef *dma_rx_desc)
{
uint32_t frameLength = 0;
if (((dma_rx_desc->Status & ETH_DMARXDESC_OWN) == (uint32_t)RESET) &&
((dma_rx_desc->Status & ETH_DMARXDESC_ES) == (uint32_t)RESET) &&
((dma_rx_desc->Status & ETH_DMARXDESC_LS) != (uint32_t)RESET))
{
frameLength = ((dma_rx_desc->Status & ETH_DMARXDESC_FL) >> ETH_DMARXDESC_FRAME_LENGTHSHIFT);
}
return frameLength;
}
/**
* @breif 中断服务函数
* @param 无
* @retval 无
*/
void ETH_IRQHandler(void)
{
if (ethernet_get_eth_rx_size(g_eth_handler.RxDesc)) {
ethernetif_input(&g_netif); /* 从网络缓冲区中读取接收到的数据包并将其发送给LWIP处理 */
}
__HAL_ETH_DMA_CLEAR_IT(&g_eth_handler, ETH_DMA_IT_NIS); /* 清除DMA中断标志位 */
__HAL_ETH_DMA_CLEAR_IT(&g_eth_handler, ETH_DMA_IT_R); /* 清除DMA接收中断标志位 */
}
/**
* @breif LWIP轮询任务
* @param 无
* @retval 无
*/
void lwip_periodic_handle(void)
{
sys_check_timeouts();
#if LWIP_DHCP /* 如果使用DHCP */
/* Fine DHCP periodic process every 500ms */
if (HAL_GetTick() - g_dhcp_fine_timer >= DHCP_FINE_TIMER_MSECS)
{
g_dhcp_fine_timer = HAL_GetTick();
/* process DHCP state machine */
lwip_dhcp_process(&g_netif);
}
#endif
}
#if LWIP_DHCP /* 如果使用DHCP */
/**
* @brief lwIP的DHCP分配进程
* @param netif:网卡控制块
* @retval 无
*/
void lwip_dhcp_process(struct netif *netif)
{
uint32_t ip = 0;
uint32_t netmask = 0;
uint32_t gw = 0;
struct dhcp *dhcp;
uint8_t iptxt[20]; /* 存储已分配的IP地址 */
g_lwipdev.dhcpstatus = 1; /* DHCP标记为1 */
/* 根据DHCP状态进入执行相应的动作 */
switch (g_lwip_dhcp_state)
{
case LWIP_DHCP_START:
{
/* 对IP地址、网关地址及子网页码清零操作 */
ip_addr_set_zero_ip4(&netif->ip_addr);
ip_addr_set_zero_ip4(&netif->netmask);
ip_addr_set_zero_ip4(&netif->gw);
/* 设置DHCP的状态为等待分配IP地址 */
g_lwip_dhcp_state = LWIP_DHCP_WAIT_ADDRESS;
/* 开启DHCP */
dhcp_start(netif);
}
break;
case LWIP_DHCP_WAIT_ADDRESS:
{
ip = g_netif.ip_addr.addr; /* 读取新IP地址 */
netmask = g_netif.netmask.addr; /* 读取子网掩码 */
gw = g_netif.gw.addr; /* 读取默认网关 */
printf ("等待DHCP分配网络参数......\r\n");
if (dhcp_supplied_address(netif))
{
printf ("DHCP分配成功......\r\n");
g_lwip_dhcp_state = LWIP_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\r\n", iptxt);
if (ip != 0)
{
g_lwipdev.dhcpstatus = 2; /* DHCP成功 */
printf("网卡en的MAC地址为:................%d.%d.%d.%d.%d.%d\r\n", g_lwipdev.mac[0], g_lwipdev.mac[1], g_lwipdev.mac[2], g_lwipdev.mac[3], g_lwipdev.mac[4], g_lwipdev.mac[5]);
/* 解析出通过DHCP获取到的IP地址 */
g_lwipdev.localip[3] = (uint8_t)(ip >> 24);
g_lwipdev.localip[2] = (uint8_t)(ip >> 16);
g_lwipdev.localip[1] = (uint8_t)(ip >> 8);
g_lwipdev.localip[0] = (uint8_t)(ip);
printf("通过DHCP获取到IP地址..............%d.%d.%d.%d\r\n", g_lwipdev.localip[0], g_lwipdev.localip[1], g_lwipdev.localip[2], g_lwipdev.localip[3]);
/* 解析通过DHCP获取到的子网掩码地址 */
g_lwipdev.netmask[3] = (uint8_t)(netmask >> 24);
g_lwipdev.netmask[2] = (uint8_t)(netmask >> 16);
g_lwipdev.netmask[1] = (uint8_t)(netmask >> 8);
g_lwipdev.netmask[0] = (uint8_t)(netmask);
printf("通过DHCP获取到子网掩码............%d.%d.%d.%d\r\n", g_lwipdev.netmask[0], g_lwipdev.netmask[1], g_lwipdev.netmask[2], g_lwipdev.netmask[3]);
/* 解析出通过DHCP获取到的默认网关 */
g_lwipdev.gateway[3] = (uint8_t)(gw >> 24);
g_lwipdev.gateway[2] = (uint8_t)(gw >> 16);
g_lwipdev.gateway[1] = (uint8_t)(gw >> 8);
g_lwipdev.gateway[0] = (uint8_t)(gw);
printf("通过DHCP获取到的默认网关..........%d.%d.%d.%d\r\n", g_lwipdev.gateway[0], g_lwipdev.gateway[1], g_lwipdev.gateway[2], g_lwipdev.gateway[3]);
}
}
else
{
dhcp = (struct dhcp *)netif_get_client_data(netif, LWIP_NETIF_CLIENT_DATA_INDEX_DHCP);
/* DHCP超时 */
if (dhcp->tries > LWIP_MAX_DHCP_TRIES)
{
printf ("DHCP分配失败,进入静态分配......\r\n");
g_lwip_dhcp_state = LWIP_DHCP_TIMEOUT;
/* 停止DHCP */
dhcp_stop(netif);
g_lwipdev.dhcpstatus = 0XFF;
/* 使用静态IP地址 */
IP4_ADDR(&(g_netif.ip_addr), g_lwipdev.localip[0], g_lwipdev.localip[1], g_lwipdev.localip[2], g_lwipdev.localip[3]);
IP4_ADDR(&(g_netif.netmask), g_lwipdev.netmask[0], g_lwipdev.netmask[1], g_lwipdev.netmask[2], g_lwipdev.netmask[3]);
IP4_ADDR(&(g_netif.gw), g_lwipdev.gateway[0], g_lwipdev.gateway[1], g_lwipdev.gateway[2], g_lwipdev.gateway[3]);
netif_set_addr(netif, &g_netif.ip_addr, &g_netif.netmask, &g_netif.gw);
sprintf((char *)iptxt, "%s", ip4addr_ntoa((const ip4_addr_t *)&netif->ip_addr));
printf ("Static IP address: %s\r\n", iptxt);
}
}
}
break;
case LWIP_DHCP_LINK_DOWN:
{
/* 停止DHCP */
dhcp_stop(netif);
g_lwip_dhcp_state = LWIP_DHCP_OFF;
}
break;
default: break;
}
}
#endif
在上述源代码中,主要实现LWIP初始化功能,由MX_LWIP_Init()函数实现,该函数在LWIP启动的时候使用,被main调用。其中MX_LWIP_Init函数会调用lwip_dev_info_set、ethernet_init、lwip_init、netif_add、netif_is_link_up、netif_set_link_callback这五个函数,分别实现设置默认IP等信息、初始化以太网芯片、初始化LWIP内核、向网卡列表中添加一个网口、并打开该网口、设置链接回调函数在链接状态更改时调用等功能,具体实现方法请参考文中代码。
需要注意一点是的,ethernet_init()函数中将g_eth_handler以太网句柄变量中的接收模式设置为ETH_RXINTERRUPT_MODE中断接收模式,这个实现方式与CubeMX实现是有区别的,请注意,这一点直接影响后面在main上的使用方式,当然你也可以改成跟CubeMX实现同样的接收模式ETH_RXPOLLING_MODE。
以太网驱动初始化和MAC的驱动程序头文件代码如下示例。
#ifndef __ETHERNET_H
#define __ETHERNET_H
#include "ethernetif.h"
#define UDP_PORT 8888 /* 网络端口 */
typedef struct
{
uint8_t mac[6]; /* MAC地址 */
uint8_t remoteip[4]; /* 远端主机IP地址 */
uint8_t localip[4]; /* 本机IP地址 */
uint8_t netmask[4]; /* 子网掩码 */
uint8_t gateway[4]; /* 默认网关的IP地址 */
uint16_t remoteport; /* 远端主机端口 */
uint16_t localport; /* 本机主机端口 */
uint8_t dhcpstatus; /* dhcp状态 */
/* 0, 未获取DHCP地址;*/
/* 1, 进入DHCP获取状态*/
/* 2, 成功获取DHCP地址*/
/* 0XFF,获取失败 */
}LWIP_DEV_INFO; /* lwip控制结构体 */
extern LWIP_DEV_INFO g_lwipdev; /* lwip控制结构体 */
extern ETH_HandleTypeDef g_eth_handler; /* 以太网句柄 */
void ETH_IRQHandler(void);
uint32_t ethernet_read_phy(uint16_t reg); /* 读取以太网芯片寄存器值 */
void ethernet_write_phy(uint16_t reg, uint16_t value); /* 向以太网芯片指定地址写入寄存器值 */
uint8_t ethernet_chip_get_speed(void); /* 获得以太网芯片的速度模式 */
void lwip_periodic_handle(void); /* lwip_periodic_handle */
uint8_t MX_LWIP_Init(void); /* LWIP初始化(LWIP启动的时候使用) */
void lwip_dhcp_process_handle(void); /* DHCP处理任务 */
#endif
5.修改ethernetif文件
从“lwip-contrib-STABLE-2_1_0_RELEASE”文件包上获取的ethernetif.c/h这两个文件,需进行如下修改,ethernetif.h头文件代码示例。
#ifndef __ETHERNETIF_H__
#define __ETHERNETIF_H__
#include <string.h>
#include "lwip/udp.h"
#include "lwip/init.h"
#include "lwip/snmp.h"
#include "netif/etharp.h"
#include "lwip/dhcp.h"
#include "lwip/timeouts.h"
#include "./SYSTEM/delay/delay.h"
/* DHCP进程状态 */
#if LWIP_DHCP
#define LWIP_DHCP_OFF (uint8_t) 0 /* DHCP服务器关闭状态 */
#define LWIP_DHCP_START (uint8_t) 1 /* DHCP服务器启动状态 */
#define LWIP_DHCP_WAIT_ADDRESS (uint8_t) 2 /* DHCP服务器等待分配IP状态 */
#define LWIP_DHCP_ADDRESS_ASSIGNED (uint8_t) 3 /* DHCP服务器地址已分配状态 */
#define LWIP_DHCP_TIMEOUT (uint8_t) 4 /* DHCP服务器超时状态 */
#define LWIP_DHCP_LINK_DOWN (uint8_t) 5 /* DHCP服务器链接失败状态 */
#define LWIP_MAX_DHCP_TRIES 4 /* DHCP服务器最大重试次数 */
extern uint32_t g_dhcp_fine_timer; /* DHCP精细处理计时器 */
extern volatile uint8_t g_lwip_dhcp_state; /* DHCP状态初始化 */
#endif
err_t ethernetif_init(struct netif *netif); /* 网卡初始化函数 */
void lwip_link_status_updated(struct netif *netif); /* DHCP链接状态更新函数 */
void ethernetif_input(struct netif *netif); /* 数据包输入函数 */
u32_t sys_now(void);
#endif
ethernetif.c源文件代码示例,具体函数解释见文中代码处,或者参考前面一篇博文STM32之LWIP网络通讯设计介绍(十四)-CSDN博客提供lwip参考书籍,野火的lwIP开发指南【免费】嵌入式开发:基于野火STM32的LwIP应用开发指南、或原子的lwIP开发指南。
#include "./BSP/ETHERNET/EthDriver.h"
/* 定义这些以更好地描述您的网络接口 Network interface name */
#define IFNAME0 'e'
#define IFNAME1 'n'
/* Private variables ---------------------------------------------------------*/
__ALIGN_BEGIN ETH_DMADescTypeDef DMARxDscrTab[ETH_RXBUFNB] __ALIGN_END; /* 以太网DMA接收描述符数据结构体 */
__ALIGN_BEGIN ETH_DMADescTypeDef DMATxDscrTab[ETH_TXBUFNB] __ALIGN_END; /* 以太网DMA发送描述符数据结构体 */
__ALIGN_BEGIN uint8_t Rx_Buff[ETH_RXBUFNB][ETH_RX_BUF_SIZE] __ALIGN_END; /* 以太网底层驱动接收buffer */
__ALIGN_BEGIN uint8_t Tx_Buff[ETH_TXBUFNB][ETH_TX_BUF_SIZE] __ALIGN_END; /* 以太网底层驱动发送buffer */
#if LWIP_DHCP
uint32_t g_dhcp_fine_timer = 0; /* DHCP精细处理计时器 */
__IO uint8_t g_lwip_dhcp_state = LWIP_DHCP_OFF; /* DHCP状态初始化 */
#endif
struct ethernetif {
struct eth_addr *ethaddr;
/* Add whatever per-interface state that is needed here. */
};
/* Forward declarations. */
void ethernetif_input(struct netif *netif);
/**
* 在此功能中 应初始化硬件
* 被ethernetif_init()调用
*/
static void
low_level_init(struct netif *netif)
{
netif->hwaddr_len = ETHARP_HWADDR_LEN; /* 设置MAC地址长度,为6个字节 */
/* 初始化MAC地址,设置什么地址由用户自己设置,但是不能与网络中其他设备MAC地址重复 */
netif->hwaddr[0] = g_lwipdev.mac[0];
netif->hwaddr[1] = g_lwipdev.mac[1];
netif->hwaddr[2] = g_lwipdev.mac[2];
netif->hwaddr[3] = g_lwipdev.mac[3];
netif->hwaddr[4] = g_lwipdev.mac[4];
netif->hwaddr[5] = g_lwipdev.mac[5];
netif->mtu = 1500; /* 最大允许传输单元,允许该网卡广播和ARP功能 */
/* 网卡状态信息标志位,是很重要的控制字段,它包括网卡功能使能、广播 */
/* 使能、 ARP 使能等等重要控制位 */
netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_LINK_UP; /* 广播 ARP协议 链接检测 */
HAL_ETH_DMATxDescListInit(&g_eth_handler,DMATxDscrTab,&Tx_Buff[0][0],ETH_TXBUFNB); /* 初始化发送描述符 */
HAL_ETH_DMARxDescListInit(&g_eth_handler,DMARxDscrTab,&Rx_Buff[0][0],ETH_RXBUFNB); /* 初始化接收描述符 */
HAL_ETH_Start(&g_eth_handler); /* 开启ETH */
}
/**
* @brief 通知用户网络接口配置状态
* @param netif:网卡控制块
* @retval 无
*/
void lwip_link_status_updated(struct netif *netif)
{
if (netif_is_up(netif))
{
#if LWIP_DHCP
/* Update DHCP state machine */
g_lwip_dhcp_state = LWIP_DHCP_START;
#endif /* LWIP_DHCP */
}
else
{
#if LWIP_DHCP
/* Update DHCP state machine */
g_lwip_dhcp_state = LWIP_DHCP_LINK_DOWN;
#endif /* LWIP_DHCP */
}
}
static err_t
low_level_output(struct netif *netif, struct pbuf *p)
{
err_t errval;
struct pbuf *q;
uint8_t *buffer = (uint8_t *)(g_eth_handler.TxDesc->Buffer1Addr);
__IO ETH_DMADescTypeDef *DmaTxDesc;
uint32_t framelength = 0;
uint32_t bufferoffset = 0;
uint32_t byteslefttocopy = 0;
uint32_t payloadoffset = 0;
DmaTxDesc = g_eth_handler.TxDesc;
bufferoffset = 0;
#if ETH_PAD_SIZE
pbuf_remove_header(p, ETH_PAD_SIZE); /* drop the padding word */
#endif
/* 从pbuf中拷贝要发送的数据 */
for (q = p;q != NULL;q = q->next)
{
/* 判断此发送描述符是否有效,即判断此发送描述符是否归以太网DMA所有 */
if ((DmaTxDesc->Status & ETH_DMATXDESC_OWN) != (uint32_t)RESET)
{
errval = ERR_USE;
goto error; /* 发送描述符无效,不可用 */
}
byteslefttocopy = q->len; /* 要发送的数据长度 */
payloadoffset = 0;
/* 将pbuf中要发送的数据写入到以太网发送描述符中,有时候我们要发送的数据可能大于一个以太网
描述符的Tx Buffer,因此我们需要分多次将数据拷贝到多个发送描述符中 */
while ((byteslefttocopy + bufferoffset) > ETH_TX_BUF_SIZE )
{
/* 将数据拷贝到以太网发送描述符的Tx Buffer中 */
memcpy((uint8_t*)((uint8_t*)buffer + bufferoffset),(uint8_t*)((uint8_t*)q->payload + payloadoffset),(ETH_TX_BUF_SIZE - bufferoffset));
/* DmaTxDsc指向下一个发送描述符 */
DmaTxDesc = (ETH_DMADescTypeDef *)(DmaTxDesc->Buffer2NextDescAddr);
/* 检查新的发送描述符是否有效 */
if ((DmaTxDesc->Status & ETH_DMATXDESC_OWN) != (uint32_t)RESET)
{
errval = ERR_USE;
goto error; /* 发送描述符无效,不可用 */
}
buffer = (uint8_t *)(DmaTxDesc->Buffer1Addr); /* 更新buffer地址,指向新的发送描述符的Tx Buffer */
byteslefttocopy = byteslefttocopy - (ETH_TX_BUF_SIZE - bufferoffset);
payloadoffset = payloadoffset + (ETH_TX_BUF_SIZE - bufferoffset);
framelength = framelength + (ETH_TX_BUF_SIZE - bufferoffset);
bufferoffset = 0;
}
/* 拷贝剩余的数据 */
memcpy( (uint8_t*)((uint8_t*)buffer + bufferoffset),(uint8_t*)((uint8_t*)q->payload+payloadoffset),byteslefttocopy );
bufferoffset = bufferoffset + byteslefttocopy;
framelength = framelength + byteslefttocopy;
}
/* 当所有要发送的数据都放进发送描述符的Tx Buffer以后就可发送此帧了 */
HAL_ETH_TransmitFrame(&g_eth_handler,framelength);
errval = ERR_OK;
error:
/* 发送缓冲区发生下溢,一旦发送缓冲区发生下溢TxDMA会进入挂起状态 */
if ((g_eth_handler.Instance->DMASR & ETH_DMASR_TUS) != (uint32_t)RESET)
{
/* 清除下溢标志 */
g_eth_handler.Instance->DMASR = ETH_DMASR_TUS;
/* 当发送帧中出现下溢错误的时候TxDMA会挂起,这时候需要向DMATPDR寄存器 */
/* 随便写入一个值来将其唤醒,此处我们写0 */
g_eth_handler.Instance->DMATPDR = 0;
}
#if ETH_PAD_SIZE
pbuf_add_header(p, ETH_PAD_SIZE); /* reclaim the padding word */
#endif
return errval;
}
/**
* Should allocate a pbuf and transfer the bytes of the incoming
* packet from the interface into the pbuf.
*
* @param netif the lwip network interface structure for this ethernetif
* @return a pbuf filled with the received packet (including MAC header)
* NULL on memory error
*/
static struct pbuf *
low_level_input(struct netif *netif)
{
struct pbuf *p, *q;
u16_t len;
uint8_t *buffer;
__IO ETH_DMADescTypeDef *dmarxdesc;
uint32_t bufferoffset = 0;
uint32_t payloadoffset = 0;
uint32_t byteslefttocopy = 0;
uint32_t i = 0;
if (HAL_ETH_GetReceivedFrame(&g_eth_handler) != HAL_OK) /* 判断是否接收到数据 */
return NULL;
len = g_eth_handler.RxFrameInfos.length; /* 获取接收到的以太网帧长度 */
#if ETH_PAD_SIZE
len += ETH_PAD_SIZE; /* allow room for Ethernet padding */
#endif
buffer = (uint8_t *)g_eth_handler.RxFrameInfos.buffer; /* 获取接收到的以太网帧的数据buffer */
p = pbuf_alloc(PBUF_RAW,len,PBUF_POOL); /* 申请pbuf */
if (p != NULL) /* pbuf申请成功 */
{
dmarxdesc = g_eth_handler.RxFrameInfos.FSRxDesc; /* 获取接收描述符链表中的第一个描述符 */
bufferoffset = 0;
for (q = p;q != NULL;q = q->next)
{
byteslefttocopy = q->len;
payloadoffset = 0;
/* 将接收描述符中Rx Buffer的数据拷贝到pbuf中 */
while ((byteslefttocopy + bufferoffset) > ETH_RX_BUF_SIZE )
{
/* 将数据拷贝到pbuf中 */
memcpy((uint8_t*)((uint8_t*)q->payload+payloadoffset),(uint8_t*)((uint8_t*)buffer + bufferoffset),(ETH_RX_BUF_SIZE - bufferoffset));
/* dmarxdesc向下一个接收描述符 */
dmarxdesc = (ETH_DMADescTypeDef *)(dmarxdesc->Buffer2NextDescAddr);
/* 更新buffer地址,指向新的接收描述符的Rx Buffer */
buffer = (uint8_t *)(dmarxdesc->Buffer1Addr);
byteslefttocopy = byteslefttocopy - (ETH_RX_BUF_SIZE - bufferoffset);
payloadoffset = payloadoffset + (ETH_RX_BUF_SIZE - bufferoffset);
bufferoffset = 0;
}
/* 拷贝剩余的数据 */
memcpy((uint8_t*)((uint8_t*)q->payload + payloadoffset),(uint8_t*)((uint8_t*)buffer + bufferoffset),byteslefttocopy);
bufferoffset = bufferoffset + byteslefttocopy;
}
}
else
{
/* drop packet(); 丢包函数自行编写 */
LINK_STATS_INC(link.memerr);
LINK_STATS_INC(link.drop);
MIB2_STATS_NETIF_INC(netif, ifindiscards);
}
/* 释放DMA描述符 */
dmarxdesc = g_eth_handler.RxFrameInfos.FSRxDesc;
for (i = 0;i < g_eth_handler.RxFrameInfos.SegCount; i ++)
{
dmarxdesc->Status |= ETH_DMARXDESC_OWN; /* 标记描述符归DMA所有 */
dmarxdesc = (ETH_DMADescTypeDef *)(dmarxdesc->Buffer2NextDescAddr);
}
g_eth_handler.RxFrameInfos.SegCount = 0; /* 清除段计数器 */
if ((g_eth_handler.Instance->DMASR & ETH_DMASR_RBUS) != (uint32_t)RESET) /* 接收缓冲区不可用 */
{
/* 清除接收缓冲区不可用标志 */
g_eth_handler.Instance->DMASR = ETH_DMASR_RBUS;
/* 当接收缓冲区不可用的时候RxDMA会进去挂起状态,通过向DMARPDR写入任意一个值来唤醒Rx DMA */
g_eth_handler.Instance->DMARPDR = 0;
}
return p;
}
/**
* This function should be called when a packet is ready to be read
* from the interface. It uses the function low_level_input() that
* should handle the actual reception of bytes from the network
* interface. Then the type of the received packet is determined and
* the appropriate input function is called.
*
* @param netif the lwip network interface structure for this ethernetif
*/
void
ethernetif_input(struct netif *netif)
{
struct pbuf *p;
/* move received packet into a new pbuf */
p = low_level_input(netif);
/* if no packet could be read, silently ignore this */
if (p != NULL)
{
/* pass all packets to ethernet_input, which decides what packets it supports */
if (netif->input(p, netif) != ERR_OK)
{
LWIP_DEBUGF(NETIF_DEBUG, ("ethernetif_input: IP input error\n"));
pbuf_free(p);
p = NULL;
}
}
}
/**
* Should be called at the beginning of the program to set up the
* network interface. It calls the function low_level_init() to do the
* actual setup of the hardware.
*
* This function should be passed as a parameter to netif_add().
*
* @param netif the lwip network interface structure for this ethernetif
* @return ERR_OK if the loopif is initialized
* ERR_MEM if private data couldn't be allocated
* any other err_t on error
*/
err_t
ethernetif_init(struct netif *netif)
{
struct ethernetif *ethernetif;
LWIP_ASSERT("netif != NULL", (netif != NULL));
ethernetif = mem_malloc(sizeof(struct ethernetif));
if (ethernetif == NULL)
{
LWIP_DEBUGF(NETIF_DEBUG, ("ethernetif_init: out of memory\n"));
return ERR_MEM;
}
#if LWIP_NETIF_HOSTNAME
/* Initialize interface hostname */
netif->hostname = "lwip";
#endif /* LWIP_NETIF_HOSTNAME */
/*
* Initialize the snmp variables and counters inside the struct netif.
* The last argument should be replaced with your link speed, in units
* of bits per second.
*/
MIB2_INIT_NETIF(netif, snmp_ifType_ethernet_csmacd, LINK_SPEED_OF_YOUR_NETIF_IN_BPS);
netif->state = ethernetif;
netif->name[0] = IFNAME0;
netif->name[1] = IFNAME1;
/* We directly use etharp_output() here to save a function call.
* You can instead declare your own function an call etharp_output()
* from it if you have to do some checks before sending (e.g. if link
* is available...) */
#if LWIP_IPV4
netif->output = etharp_output;
#endif /* LWIP_IPV4 */
#if LWIP_IPV6
netif->output_ip6 = ethip6_output;
#endif /* LWIP_IPV6 */
netif->linkoutput = low_level_output;
ethernetif->ethaddr = (struct eth_addr *) & (netif->hwaddr[0]);
/* initialize the hardware */
low_level_init(netif);
return ERR_OK;
}
u32_t sys_now(void)
{
return HAL_GetTick();
}
6.添加用户代码
将CubeMX实现的用户层代码添加进来,代码跟上面提供的一样,这里不在给出。添加到工程的 Middlewares\lwip\lwip_app\ 路径下,需添加的文件如下所示,包含源文件和头文件。
7.main文件实现
main()函数主要完成相关GPIO引脚、LWIP、系统时钟初始化配置;然后初始化用户UDP网络设置、和用户参数;最后进入主循环,处理网络上的数据,由UDP_Data_Process()函数完成。根据前面PHY驱动代码(ethernet.c文件中),将g_eth_handler以太网句柄变量中的接收模式设置为ETH_RXINTERRUPT_MODE中断接收模式(这个方式一实现是有区别的)。当ETH_IRQHandler中断服务函数接收到网络上数据后,调用ethernetif_input任务函数,它调用low_level_input函数获取描述符管理缓冲区的数据,并使用netif->input函数将缓冲区数据交给IP层,传递给pbuf数据包,由主循环上注册的接收回调函数UDP_Receive_Callback解析处理。main函数代码示例如下。
#include "eth_user.h"
int main(void)
{
uint8_t retry = 0;
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(336, 8, 2, 7); /* 设置时钟,168Mhz */
delay_init(168); /* 延时初始化 */
while (MX_LWIP_Init()) { /* 初始化lwip,如果失败的话就重试3次 */
retry++;
if (retry > 3)
break; /* lwip初始化失败 */
}
//if (!ethernet_read_phy(PHY_SR)) /* 检查CPU与PHY芯片是否通信成功 */
// printf("CPU与PHY驱动芯片通信失败\r\n");
//uint8_t speed = ethernet_chip_get_speed();
// printf("网络速度:%d\r\n", speed);
#if LWIP_DHCP
while ((g_lwipdev.dhcpstatus != 2) && (g_lwipdev.dhcpstatus != 0XFF)) /* 等待DHCP获取成功/超时溢出 */
{
lwip_periodic_handle(); /* LWIP轮询任务 */
delay_ms(1000);
}
#endif
User_UDP_Init();
while (1)
{
UDP_Data_Process();
}
}
8.效果演示
上述移植、修改、编写完代码后,进行编译运行,连接仿真器在线调式、或者直接烧写到板子中,打开网络监控助手,效果如下。
总结
下面提供的代码,基于STM32F407ZGT芯片编写,可直接在原子开发板上运行,也可运行在各工程项目上,但需要注意各接口以及相应的引脚应和原子开发板上保持一致。
相应的代码链接:单片机STM32F407-Case程序代码例程-CSDN文库