之前已经介绍了LwIP源代码(lwip-1.4.1.zip)和ST官方LwIP测试平台资料(stsw-stm32070.zip)下载,我们移植步骤是基于这两份资料进行的。
无操作系统移植LwIP需要的文件参考图 ,图中只显示了*.c文件,还需要用到对应的*.h文件。

接下来,我们就根据图中文件结构详解移植过程。实验例程有需要用到系统滴答定时器systick、调试串口USART、独立按键KEY、LED灯功能, 对这些功能实现不做具体介绍,可以参考相关章节理解。
第一步:相关文件拷贝
首先,解压lwip-1.4.1.zip和stsw-stm32070.zip两个压缩包,把整个lwip-1.4.1文件夹拷贝到USER文件夹下,特别说明,在整个移植过程中, 不会对lwip-1.4.1.zip文件下的文件内容进行修改。然后,在stsw-stm32070文件夹找到port文件夹(路径:…UtilitiesThird_Partylwip-1.4.1port), 把整个port文件夹拷贝lwip-1.4.1文件夹中,在port文件夹下的STM32F4x7文件中把arch和Standalone两个文件夹直接剪切到port文件夹中, 即此时port文件夹有三个STM32F4x7、arch和Standalone文件夹,最后把STM32F4x7文件夹删除,最终的文件结构见图 , arch存放与开发平台相关头文件,Standalone文件夹是无操作系统移植时ETH外设与LwIP连接的底层驱动函数。

lwip-1.4.1文件夹下的doc文件夹存放LwIP版权、移植、使用等等说明文件,移植之前有必须认真浏览一遍;src文件夹存放LwIP的实现代码, 也是我们工程代码真正需要的文件;test文件夹存放LwIP部分功能测试例程;另外,还有一些无后缀名的文件,都是一些说明性文件,可用记事本直接打开浏览。 port文件夹存放LwIP与STM32平台连接的相关文件,正如上面所说contrib-1.4.1.zip包含了不同平台移植代码,不过遗憾地是没有STM32平台的, 所以我们需要从ST官方提供的测试平台找到这部分连接代码,也就是port文件夹的内容。
接下来,在Bsp文件下新建一个ETH文件夹,用于存放与ETH相关驱动文件,包括两个部分文件,其中一个是ETH外设驱动文件, 在stsw-stm32070文件夹中找到stm32f4x7_eth.h和stm32f4x7_eth.c两个文件(路径:…LibrariesSTM32F4x7_ETH_Driver), 将这两个文件拷贝到ETH文件夹中,这两个文件是ETH驱动文件,类似标准库中外设驱动代码实现文件,在移植过程中我们几乎不过文件的内容。 这部分函数由port文件夹相关代码调用。另外一部分是相关GPIO初始化、ETH外设初始化、PHY状态获取等等函数的实现, 在stsw-stm32070文件夹中找到stm32f4x7_eth_bsp.c、stm32f4x7_eth_bsp.h和stm32f4x7_eth_conf.h三个文件(路径:…ProjectStandalone cp_echo_client), 将这三个文件拷贝到ETH文件夹中。因为ST官方LwIP测试平台使用的PHY型号不是使用LAN8720A,所以这三个文件需要我们进行修改。
最后,是LwIP测试代码实现,为测试LwIP移植是否成功和检查LwIP功能,我们编写TCP通信实现代码,设置开发板为TCP从机,电脑端为TCP主机。 在stsw-stm32070文件夹中找到netconf.c、tcp_echoclient.c、lwipopts.h、netconf.h和tcp_echoclient.h五个文件(路径:…ProjectStandalone cp_echo_client), 直接拷贝到App文件夹(自己新建)中,netconf.c文件代码实现LwIP初始化函数、周期调用函数、DHCP功能函数等等,tcp_echoclient.c文件实现TCP通信参数代码, lwipopts.h包含LwIP功能选项。
第二部:为工程添加文件
第一步已经把相关的文件拷贝到对应的文件夹中,接下来就可以把需要用到的文件添加到工程中。 图 已经指示出来工程需要用到的*.c文件,所以最终工程文件结构见 , 图中api、ipv4和core都包含了对应文件夹下的所有*.c文件。

接下来,还需要在工程选择中添加相关头文件路径,参考图 。

第三步:文件修改
ethernetif.c文件是无操作系统时网络接口函数,该文件在移植是只需修改相关头文件名,函数实现部分无需修改。该文件主要有三个部分函数, 一个是low_level_init,用于初始化MAC相关工作环境、初始化DMA描述符链表,并使能MAC和DMA;一个是low_level_output, 它是最底层发送一帧数据函数;最后一个是low_level_input,它是最底层接收一帧数据函数。
stm32f4x7_eth.c和stm32f4x7_eth.h两个文件用于ETH驱动函数实现,它是通过直接操作寄存器方式实现,这两个文件我们无需修改。 stm32f4x7_eth_conf.h文件包含了一些功能选项的宏定义,我们对部分内容进行了修改。
通过宏定义USE_Delay可选是否使用自定义的延时函数,Delay_10ms函数是通过系统滴答定时器实现的延时函数,ETH_Delay函数是ETH驱动自带的简单循环延时函数, 延时函数实现方法不同,对形参要求不同。因为ST官方例程是基于DP83848型号的PHY,而开发板的PHY型号是LAN8720A。LAN8720A复位时需要一段延时时间, 这里需要定义延时时间长度,大约50ms。驱动代码中需要获取PHY的速度和工作模式,LAN8720A的R31是特殊控制/状态寄存器,包括指示以太网速度和工作模式的状态位。
stm32f4x7_phy.c和stm32f4x7_phy.h两个文件是ETH外设相关的底层配置,包括RMII接口GPIO初始化、SMI接口GPIO初始化、MAC控制器工作环境配置, 还有一些PHY的状态获取和控制修改函数。ST官方例程文件包含了中断引脚的相关配置,主要用于指示接收到以太网帧,我们这里不需要使用, 采用无限轮询方法检测接收状态。stm32f4x7_phy.h文件存放相关宏定义,包含RMII和SMI引脚信息等宏定义,其中要特别说明的有一个宏, 定义了PHY地址:ETHERNET_PHY_ADDRESS,这里根据硬件设计设置为0x00,这在SMI通信是非常重要的。
STM32f4xx控制器支持MII和RMII接口,通过程序控制使用RMII接口,同时需要使能SYSYCFG时钟,函数后部分就是接口GPIO初始化实现, 这里我们还连接了LAN8720A的复位引脚,通过拉低一段时间让芯片硬件复位。
首先是使能ETH时钟,复位ETH配置。ETH_StructInit函数用于初始化ETH_InitTypeDef结构体变量,会给每个成员赋予缺省值。 接下来就是根据需要配置ETH_InitTypeDef结构体变量,关于结构体各个成员意义已在“ETH初始化结构体详解”作了分析。 最后调用ETH_Init函数完成配置,ETH_Init函数有两个形参,一个是ETH_InitTypeDef结构体变量指针,第二个是PHY地址,函数还有一个返回值,用于指示初始化配置是否成功。
GET_PHY_LINK_STATUS()是定义获取PHY链路状态的宏,如果PHY连接正常那么整个宏定义为1,如果不正常则为0, 它是通过ETH_ReadPHYRegister函数读取PHY的基本状态寄存器(PHY_BSR)并检测其Link Status位得到的。
ETH_BSP_Config函数分别调用ETH_GPIO_Config和ETH_MACDMA_Config函数完成ETH初始化配置,最后调用GET_PHY_LINK_STATUS()来判断PHY状态, 并保存在EthStatus变量中。ETH_BSP_Config函数一般在main函数中优先LwIP_Init函数调用。
ETH_CheckLinkStatus函数用于获取PHY状态,实际上也是通过宏定义GET_PHY_LINK_STATUS()获取得到的,函数还根据PHY状态通知LwIP当前链路状态, gnetif是一个netif结构体类型变量,LwIP定义了netif结构体类型,用于指示某一网卡相关信息,LwIP是支持多个网卡设备, 使用时需要为每个网卡设备定义一个netif类型变量。无操作系统时ETH_CheckLinkStatus函数被无限循环调用。
ETH_link_callback函数被LwIP调用,当链路状态发送改变时该函数就被调用,用于状态改变后处理相关事务。首先调用netif_is_link_up函数判断新状态是否是链路启动状态, 如果是启动状态就进入if语句,接下来会判断ETH是否被设置为自适应模式,如果不是自适应模式需要使用ETH_WritePHYRegister函数使能PHY工作为自适应模式, 然后ETH_ReadPHYRegister函数读取PHY相关寄存器,获取PHY当前支持的以太网速度和工作模式,并保存到ETH_InitStructure结构体变量中。 ETH_Start函数用于使能ETH外设,之后就是配置ETH的IP地址、 子网掩码、网关,如果是定义了DHCP (动态主机配置协议)功能则启动DHCP。最后就是调用netif_set_up函数在LwIP层次配置启动ETH功能。
如果检测到是链路关闭状态,调用ETH_Stop函数关闭ETH,如果定义了DHCP功能则需关闭DHCP,最后调用netif_set_down函数在LwIP层次关闭ETH功能。
以上对文件修改部分更多涉及到ETH硬件底层驱动,一些是PHY芯片驱动函数、一些是ETH外设与LwIP连接函数。接下来要讲解的文件代码更多是与LwIP应用相关的。
netconf.c和netconf.h文件用于存放LwIP配置相关代码。netcon.h定义了相关宏。
USE_DHCP宏用于定义是否使用DHCP功能,如果不定义该宏,直接使用静态的IP地址,如果定义该宏,则使用DHCP功能,获取动态的IP地址, 这里有个需要注意的地方,电脑是没办法提供DHCP服务功能的,路由器才有DHCP服务功能,使用当开发板直连电脑时不能定义该宏。
SERIAL_DEBUG宏是定义是否使能串口定义相关调试信息功能,一般选择使能,所以在main函数中需要添加串口初始化函数。
接下来,定义了远端IP和端口、MAC地址、静态IP地址、子网掩码、网关相关宏,可以根据实际情况修改。
LAN8720A仅支持RMII接口,根据硬件设计这里定义使用RMII_MODE。
LwIP_Init函数用于初始化LwIP协议栈,一般在main函数中调用。首先是内存相关初始化,mem_init函数是动态内存堆初始化,memp_init函数是存储池初始化, LwIP是实现内存的高效利用,内部需要不同形式的内存管理模式。
接下来为ipaddr、netmask和gw结构体变量赋值,设置本地IP地址、子网掩码和网关,如果使用DHCP功能直接赋值为0即可。netif_add是以太网设备添加函数, 即向LwIP协议栈申请添加一个网卡设备,函数有7个形参,第一个为netif结构体类型变量指针,这里赋值为gnetif地址,该网卡设备属性就存放在gnetif变量中; 第二个为ip_addr结构体类型变量指针,用于设置网卡IP地址;第三个ip_addr结构体类型变量指针,用于设置子网掩码;第四个为ip_addr结构体类型变量指针, 用于设置网关;第五个为void变量,用户自定义字段,一般不用直接赋值NULL;第六个为netif_init_fn类型函数指针,用于指向网卡设备初始化函数, 这里赋值为指向ethernetif_init函数,该函数在ethernetif.c文件定义,初始化LwIP与ETH外设连接函数;最后一个参数为netif_input_fn类型函数指针, 用于指向以太网帧接收函数,这里赋值为指向ethernet_input函数,该函数定义在etharp.c文件中。
netif_set_default函数用于设置指定网卡为默认的网络通信设备。
在无硬件连接错误时,调用ETH_BSP_Config(优先LwIP_Init函数被调用)时会将EthStatus变量对应的ETH_LINK_FLAG位使能, 所以在LwIP_INIT函数中会执行if判断语句代码,置位网卡设备标志位以及运行netif_set_up函数启动网卡设备。否则执行netif_set_down函数停止网卡设备。
最后,根据需要调用netif_set_link_callback函数实在当链路状态发生改变时需要调用的回调函数配置。
LwIP_Pkt_Handle函数用于从以太网存储器读取一个以太网帧并将其发送给LwIP,它在接收到以太网帧时被调用, 它是直接调用ethernetif_input函数实现的,该函数定义在ethernetif.c文件中。
LwIP_Periodic_Handle函数是一个必须被无限循环调用的LwIP支持函数,一般在main函数的无限循环中调用,主要功能是为LwIP各个模块提供时间并查询链路状态, 该函数有一个形参,用于指示当前时间,单位为ms。
对于TCP功能,每250ms执行一次tcp_tmr函数;对于ARP(地址解析协议),每5s执行一次etharp_tmr函数;对于链路状态检测, 每1s执行一次ETH_CheckLinkStatus函数;对于DHCP功能,每500ms执行一次dhcp_fine_tmr函数, 如果DHCP处于DHCP_START或DHCP_WAIT_ADDRESS状态就执行LwIP_DHCP_Process_Handle函数,对于DHCP功能,还有每60s执行一次dhcp_coarse_tmr函数。
LwIP_DHCP_Process_Handle函数用于执行DHCP功能,当DHCP状态为DHCP_START时,执行dhcp_start函数启动DHCP功能,LwIP会向DHCP服务器申请分配IP请求, 并进入等待分配状态。当DHCP状态为DHCP_WAIT_ADDRESS时,先判断IP地址是否为0,如果不为0说明已经有IP地址,DHCP功能已经完成可以停止它; 如果IP地址总是为0,就需要判断是否超过最大等待时间,并提示出错。
lwipopts.h文件存放一些宏定义,用于剪切LwIP功能,比如有无操作系统、内存空间分配、存储池分配、TCP功能、DHCP功能、UDP功能选择等等。这里使用与ST官方例程相同配置即可。
LwIP为使用者提供了两种应用程序接口(API函数)来实现TCP/IP协议栈,一种是低水平、基于回调函数的API,称为RAW API,另外一种是高水平、连续的API, 称为sequential API,sequentialAPI又有两种函数结构,一种是Netconn,一种是Socket,它与在电脑端使用的BSD标准的Socket API结构和原理是非常相似的。
接下来内容我们使用RAW API实现一个简单的TCP通信测试,ST官方有提供相关的例程,我们对其内容稍作调整。代码内容存放在tcp_echoclient.c文件中。 TCP在各个层次处理过程见图 。

网络接口层的netif->output和netif->input是在ethernetif.c文件中实现的,网络层和传输层有LwIP协议栈实现,应用层代码就是用户使用LwIP函数实现网络功能。
tcp_echoclient_connect函数用于创建TCP从设备并启动与TCP服务器连接。tcp_new函数创建一个新TCP协议控制块,主要是必要的内存申请, 返回一个未初始化的TCP协议控制块指针。如果返回值不了0就可以使用tcp_connect函数连接到TCP服务器,tcp_connect函数用于TCP从设备连接至指定IP地址和端口的TCP服务器, 它有四个形参,第一个为TCP协议控制块指针,第二个为服务器IP地址,第三个为服务器端口,第四个为函数指针,当连接正常建立时或连接错误时函数被调用, 这里赋值tcp_echoclient_connected函数名。如果tcp_new返回值为0说明创建TCP协议控制块失败,调用memp_free函数释放相关内容。
echoclient是自定义的一个结构体类型,包含了TCP从设备的状态、TCP协议控制块指针和发送数据指针。tcp_echoclient_disconnect函数用于断开TCP连接, 通过调用tcp_echoclient_connection_close函数实现,它有两个形参,一个是TCP协议控制块,一个是echoclient类型指针。
tcp_echoclient_connected函数作为tcp_connect函数设置的回调函数,在TCP建立连接时被调用,这里实现的功能是向TCP服务器发送一段数据。 使用mem_malloc函数申请内存空间存放echoclient结构体类型数据,并赋值给es指针变量。如果内存申请失败调用tcp_echoclient_connection_close函数关闭TCP连接; 确保内存申请成功后为es成员赋值,p_tx成员是发送数据指针,这里使用pbuf_alloc函数向内存池申请存放发送数据的存储空间,即数据发送缓冲区。 确保发送数据存储空间申请成功后使用pbuf_take函数将待发送数据data拷贝到数据发送存储器。tcp_arg函数用于设置用户自定义参数, 使得该参数可在相关回调函数被重新使用。tcp_recv、tcp_sent和tcp_poll函数分别设置TCP协议控制块对应的接收、发送和轮询回调函数。最后调用tcp_echoclient_send函数发送数据。
tcp_echoclient_recv函数是TCP接收回调函数,TCP从设备接收到数据时该函数就被运行一次,我们可以提取数据帧内容。函数先检测是否为空帧, 如果为空帧则关闭TCP连接,然后检测是否发生传输错误,如果发送错误执行pbuf_free函数释放内存。检查无错误就可以调用tcp_recved函数接收数据, 这样就可以提取接收到信息。最后调用pbuf_free函数释放相关内存。
tcp_echoclient_send函数用于TCP数据发送,它有两个形参,一个是TCP协议控制块结构体指针,一个是echoclient结构体指针。 在判断待发送数据存在并不超过最大可用发送队列数据数后,执行tcp_write函数将待发送数据写入发送队列,由协议内核决定发送时机。
tcp_echoclient_poll函数是由tcp_poll函数指定的回调函数,它每500ms执行一次,函数检测是否有待发送数据,如果有就执行tcp_echoclient_send函数发送数据。
tcp_echoclient_sent函数是有tcp_sent函数指定的回调函数,当接收到远端设备发送应答信号时被调用,它实际是通过调用tcp_echoclient_send函数发送数据实现的。
tcp_echoclient_connection_close函数用于关闭TCP连接,将相关的回调函数解除,释放es变量内存,最后调用tcp_close函数关闭TCP连接,释放TCP协议控制块内存。
LwIP_Periodic_Handle函数执行LwIP需要周期性执行函数,该所以我们需要为该函数提高一个时间基准,这里使用TIM3产生这个基准, 初始化配置TIM3每10ms中断一次,在其中断服务函数中递增LocalTime变量值。
首先是初始化LED指示灯、按键、调试串口、系统滴答定时器,TIM3_Config函数配置10ms定时并启动定时器,ETH_BSP_Config函数初始化ETH相关GPIO、 配置MAC和DMA并获取PHY状态,LwIP_Init函数初始化LwIP协议栈。进入无限循环函数,不断检测按键状态,如果KEY1被按下则调用tcp_echoclient_connect函数启动TCP连接, 如果KEY2被按下则调用tcp_echoclient_disconnect关闭TCP连接。ETH_CheckFrameReceived函数用于检测是否接收到数据帧, 如果接收到数据帧则调用LwIP_Pkt_Handle函数将数据帧从缓冲区传入LwIP。LwIP_Periodic_Handle函执行必须被周期调用的函数。
下载验证
保证开发板相关硬件连接正确,用USB线连接开发板“USB TO UART”接口跟电脑,在电脑端打开串口调试助手并配置好相关参数; 使用网线连接开发板网口跟路由器,这里要求电脑连接在同一个路由器上,之所以使用路由器是这样连接方便,电脑端无需更多操作步骤, 并且路由器可以提供DHCP服务器功能,而电脑是不行的,最后在电脑端打开网络调试助手软件,并设置相关参数, 见图 ,调试助手的设置与netconf.h文件中相关宏定义是对应的, 不同电脑设置情况可能不同。把编译好的程序下载到开发板。

在系统硬件初始化时串口调试助手会打印相关提示信息,等待初始化完成后可打开电脑端CMD窗口,输入ping命令测试开发板链路, 图 为链路正常情况,如果出现ping不同情况,检查网线连接。

ping状态正常后,可按下开发板KEY1按键,使能开发板连接电脑端的TCP服务器,之后就可以进行数据传输, 需要接收传输时可以按下开发板KEY2按键,实际操作调试助手界面见图 。

版权声明:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权、违法违规、事实不符,请将相关资料发送至xkadmin@xkablog.com进行投诉反馈,一经查实,立即处理!
转载请注明出处,原文链接:https://www.xkablog.com/bcyy/15793.html