1715535584,2024年最新物联网嵌入式开发语言基础教程培训

108 阅读57分钟

10 uint32_t ETH_Speed; // 以太网速度

11 uint32_t ETH_ReceiveOwn; // 接收自身

12 uint32_t ETH_LoopbackMode; // 回送模式

13 uint32_t ETH_Mode; // 模式

14 uint32_t ETH_ChecksumOffload; // 校验和减荷

15 uint32_t ETH_RetryTransmission; // 传输重试

16 uint32_t ETH_AutomaticPadCRCStrip; // 自动去除PAD和FCS字段

17 uint32_t ETH_BackOffLimit; // 后退限制

18 uint32_t ETH_DeferralCheck; // 检查延迟

19 uint32_t ETH_ReceiveAll; // 接收所有MAC帧

20 uint32_t ETH_SourceAddrFilter; // 源地址过滤

21 uint32_t ETH_PassControlFrames; // 传送控制帧

22 uint32_t ETH_BroadcastFramesReception; // 广播帧接收

23 uint32_t ETH_DestinationAddrFilter; // 目标地址过滤

24 uint32_t ETH_PromiscuousMode; // 混合模式

25 uint32_t ETH_MulticastFramesFilter; // 多播源地址过滤

26 uint32_t ETH_UnicastFramesFilter; // 单播源地址过滤

27 uint32_t ETH_HashTableHigh; // 散列表高位

28 uint32_t ETH_HashTableLow; // 散列表低位

29 uint32_t ETH_PauseTime; // 暂停时间

30 uint32_t ETH_ZeroQuantaPause; // 零时间片暂停

31 uint32_t ETH_PauseLowThreshold; // 暂停阈值下限

32 uint32_t ETH_UnicastPauseFrameDetect; // 单播暂停帧检测

33 uint32_t ETH_ReceiveFlowControl; // 接收流控制

34 uint32_t ETH_TransmitFlowControl; // 发送流控制

35 uint32_t ETH_VLANTagComparison; // VLAN标记比较

36 uint32_t ETH_VLANTagIdentifier; // VLAN标记标识符

37 /**

38 * @brief / * DMA

39 */

40 uint32_t ETH_DropTCPIPChecksumErrorFrame; // 丢弃TCP/IP校验错误帧

41 uint32_t ETH_ReceiveStoreForward; // 接收存储并转发

42 uint32_t ETH_FlushReceivedFrame; // 刷新接收帧

43 uint32_t ETH_TransmitStoreForward; // 发送存储并并转发

44 uint32_t ETH_TransmitThresholdControl; // 发送阈值控制

45 uint32_t ETH_ForwardErrorFrames; // 转发错误帧

46 uint32_t ETH_ForwardUndersizedGoodFrames; // 转发过小的好帧

47 uint32_t ETH_ReceiveThresholdControl; // 接收阈值控制

48 uint32_t ETH_SecondFrameOperate; // 处理第二个帧

49 uint32_t ETH_AddressAlignedBeats; // 地址对齐节拍

50 uint32_t ETH_FixedBurst; // 固定突发

51 uint32_t ETH_RxDMABurstLength; // DMA突发接收长度

52 uint32_t ETH_TxDMABurstLength; // DMA突发发送长度

53 uint32_t ETH_DescriptorSkipLength; // 描述符跳过长度

54 uint32_t ETH_DMAArbitration; // DMA仲裁

55 } ETH_InitTypeDef;

    ETH_AutoNegotiation:自适应功能选择,可选使能或禁止,一般选择使能自适应功能,系统会自动寻找最优工作方式,包括选择10Mbps或者100Mbps的以太网速度以及全双工模式或半双工模式。

    ETH_Watchdog:以太网看门狗功能选择,可选使能或禁止,它设定以太网MAC配置寄存器(ETH_MACCR)的WD位的值。如果设置为1,使能看门狗,在接收MAC帧超过2048字节时自动切断后面数据,一般选择使能看门狗。如果设置为0,禁用看门狗,最长可接收16384字节的帧。

    ETH_Jabber:jabber定时器功能选择,可选使能或禁止,与看门狗功能类似,只是看门狗用于接收MAC帧,jabber定时器用于发送MAC帧,它设定ETH_MACCR寄存器的JD位的值。如果设置为1,使能jabber定时器,在发送MAC帧超过2048字节时自动切断后面数据,一般选择使能jabber定时器。

    ETH_InterFrameGap:控制发送帧间的最小间隙,可选96bit时间、88bit时间、…、40bit时间,他设定ETH_MACCR寄存器的IFG[2:0]位的值,一般设置96bit时间。

    ETH_CarrierSense:载波侦听功能选择,可选使能或禁止,它设定ETH_MACCR寄存器的CSD位的值。当被设置为低电平时,MAC发送器会生成载波侦听错误,一般使能载波侦听功能。

    ETH_Speed:以太网速度选择,可选10Mbps或100Mbit/s,它设定ETH_MACCR寄存器的FES位的值,一般设置100Mbit/s,但在使能自适应功能之后该位设置无效。

    ETH_ReceiveOwn:接收自身帧功能选择,可选使能或禁止,它设定ETH_MACCR寄存器的ROD位的值,当设置为0时,MAC接收发送时PHY提供的所有MAC包,如果设置为1,MAC禁止在半双工模式下接收帧。一般使能接收。

    ETH_LoopbackMode:回送模式选择,可选使能或禁止,它设定ETH_MACCR寄存器的LM位的值,当设置为1时,使能MAC在MII回送模式下工作。

    ETH_Mode:以太网工作模式选择,可选全双工模式或半双工模式,它设定ETH_MACCR寄存器DM位的值。一般选择全双工模式,在使能了自适应功能后该成员设置无效。

    ETH_ChecksumOffload:IPv4校验和减荷功能选择,可选使能或禁止,它设定ETH_MACCR寄存器IPCO位的值,当该位被置1时使能接收的帧有效载荷的TCP/UDP/ICMP标头的IPv4校验和检查。一般选择禁用,此时PCE和IP HCE状态位总是为0。

    ETH_RetryTransmission:传输重试功能,可选使能或禁止,它设定ETH_MACCR寄存器RD位的值,当被设置为1时,MAC仅尝试发送一次,设置为0时,MAC会尝试根据BL的设置进行重试。一般选择使能重试。

    ETH_AutomaticPadCRCStrip:自动去除PAD和FCS字段功能,可选使能或禁用,它设定ETH_MACCR寄存器APCS位的值。当设置为1时,MAC在长度字段值小于或等于1500自己是去除传入帧上的PAD和FCS字段。一般禁止自动去除PAD和FCS字段功能。

    ETH_BackOffLimit:后退限制,在发送冲突后重新安排发送的延迟时间,可选10、8、4、1,它设定ETH_MACCR寄存器BL位的值。一般设置为10。

    ETH_DeferralCheck:检查延迟,可选使能或禁止,它设定ETH_MACCR寄存器DC位的值,当设置为0时,禁止延迟检查功能,MAC发送延迟,直到CRS信号变成无效信号。

    ETH_ReceiveAll:接收所有MAC帧,可选使能或禁用,它设定以太网MAC帧过滤寄存器(ETH_MACFFR)RA位的值。当设置为1时,MAC接收器将所有接收的帧传送到应用程序,不过滤地址。当设置为0是,MAC接收会自动过滤不与SA/DA匹配的帧。一般选择不接收所有。

    ETH_SourceAddrFilter:源地址过滤,可选源地址过滤、源地址反向过滤或禁用源地址过滤,它设定ETH_MACFFR寄存器SAF位和SAIF位的值。一般选择禁用源地址过滤。

    ETH_PassControlFrames:传送控制帧,控制所有控制帧的转发,可选阻止所有控制帧到达应用程序、转发所有控制帧、转发通过地址过滤的控制帧,它设定ETH_MACFFR寄存器PCF位的值。一般选择禁止转发控制帧。

    ETH_BroadcastFramesReception:广播帧接收,可选使能或禁止,它设定ETH_MACFFR寄存器BFD位的值。当设置为0时,使能广播帧接收,一般设置接收广播帧。

    ETH_DestinationAddrFilter:目标地址过滤功能选择,可选正常过滤或目标地址反向过滤,它设定ETH_MACFFR寄存器DAIF位的值。一般设置为正常过滤。

    ETH_PromiscuousMode:混合模式,可选使能或禁用,它设定ETH_MACFFR寄存器PM位的值。当设置为1时,不论目标或源地址,地址过滤器都传送所有传入的帧。一般禁用混合模式。

    ETH_MulticastFramesFilter:多播源地址过滤,可选完美散列表过滤、散列表过滤、完美过滤或禁用过滤,它设定ETH_MACFFR寄存器HPF位、PAM位和HM位的值。一般选择完美过滤。

    ETH_UnicastFramesFilter:单播源地址过滤,可选完美散列表过滤、散列表过滤或完美过滤,它设定ETH_MACFFR寄存器HPF位和HU位的值。一般选择完美过滤。

    ETH_HashTableHigh:散列表高位,和ETH_HashTableLow组成64位散列表用于组地址过滤,它设定以太网MAC散列表高位寄存器(ETH_MACHTHR)的值。

    ETH_HashTableLow:散列表低位,和ETH_ HashTableHigh组成64位散列表用于组地址过滤,它设定以太网MAC散列表低位寄存器(ETH_MACHTLR)的值。

    ETH_PauseTime:暂停时间,保留发送控制帧中暂停时间字段要使用的值,可设置0至65535,它设定以太网MAC流控制寄存器(ETH_MACFCR)PT位的值。

    ETH_ZeroQuantaPause:零时间片暂停,可选使用或禁止,它设定ETH_MACFCR寄存器ZQPD位的值。当设置为1时,当来自FIFO层的流控制信号去断言后,此位会禁止自动生成零时间片暂停控制帧。一般选择禁止。

    ETH_PauseLowThreshold:暂停阈值下限,配置暂停定时器的阈值,达到该值值时,会自动程序传输暂停帧,可选暂停时间减去4个间隙、28个间隙、144个间隙或256个间隙,它设定ETH_MACFCR寄存器PLT位的值。一般选择暂停时间减去4个间隙。

    ETH_UnicastPauseFrameDetect:单播暂停帧检测,可选使能或禁止,它设定ETH_MACFCR寄存器UPFD位的值。当设置为1时,MAC除了检测具有唯一多播地址的暂停帧外,还会检测具有ETH_MACA0HR和ETH_MACA0LR寄存器所指定的站单播地址的暂停帧。一般设置为禁止。

    ETH_ReceiveFlowControl:接收流控制,可选使能或禁止,它设定ETH_MACFCR寄存器RFCE位的值。当设定为1时,MAC对接收到的暂停帧进行解码,并禁止其在指定时间(暂停时间)内发送;当设置为0时,将禁止暂停帧的解码功能,一般设置为禁止。

    ETH_TransmitFlowControl:发送流控制,可选使能或禁止,它设定ETH_MACFCR寄存器TFCE位的值。在全双工模式下,当设置为1时,MAC将使能流控制操作来发送暂停帧;为0时,将禁止MAC中的流控制操作,MAC不会传送任何暂停帧。在半双工模式下,当设置为1时,MAC将使能背压操作;为0时,将禁止背压功能。

    ETH_VLANTagComparison:VLAN标记比较,可选12位或16位,它设定以太网MAC VLAN标记寄存器(ETH_MACVLANTR)VLANTC位的值。当设置为1时,使用12位VLAN标识符而不是完整的16位VLAN标记进行比较和过滤;为0时,使用全部16位进行比较,一般选择16位。

    ETH_VLANTagIdentifier:VLAN标记标识符,包含用于标识VLAN帧的802.1Q VLAN标记,并与正在接收的VLAN帧的第十五和第十六字节进行比较。位[15:13]是用户优先级,位[12]是标准格式指示符(CFI),位[11:0]是VLAN标记的VLAN标识符(VID)字段。VLANTC位置1时,仅使用VID(位[11:0])进行比较。

    ETH_DropTCPIPChecksumErrorFrame:丢弃TCP/IP校验错误帧,可选使能或禁止,它设定以太网DMA工作模式寄存器(ETH_DMAOMR)DTCEFD位的值,当设置为 1时,如果帧中仅存在由接收校验和减荷引擎检测出来的错误,则内核不会丢弃它;为0时,如果FEF为进行了复位,则会丢弃所有错误帧。

    ETH_ReceiveStoreForward:接收存储并转发,可选使能或禁止,它设定以太网DMA工作模式寄存器(ETH_DMAOMR)RSF位的值,当设置为1时,向RX FIFO写入完整帧后可以从中读取一帧,同时忽略接收阈值控制(RTC)位;当设置为0时,RX FIFO在直通模式下工作,取决于RTC位的阈值。一般选择使能。

    ETH_FlushReceivedFrame:刷新接收帧,可选使能或禁止,它设定ETH_DMAOMR寄存器FTF位的值,当设置为1时,发送FIFO控制器逻辑会恢复到缺省值,TX FIFO中的所有数据均会丢失/刷新,刷新结束后改为自动清零。

    ETH_TransmitStoreForward:发送存储并并转发,可选使能或禁止,它设定ETH_DMAOMR寄存器TSF位的值,当设置为1时,如果TX FIFO有一个完整的帧则发送会启动,会忽略TTC值;为0时,TTC值才会有效。一般选择使能。

    ETH_TransmitThresholdControl:发送阈值控制,有多个阈值可选,它设定ETH_DMAOMR寄存器TTC位的值,当TX FIFO中帧大小大于该阈值时发送会自动,对于小于阈值的全帧也会发送。

    ETH_ForwardErrorFrames:转发错误帧,可选使能或禁止,它设定ETH_DMAOMR寄存器FEF位的值,当设置为1时,除了段错误帧之外所有帧都会转发到DMA;为0时,RX FIFO会丢弃滴啊有错误状态的帧。一般选择禁止。

    ETH_ForwardUndersizedGoodFrames:转发过小的好帧,可选使能或禁止,它设定ETH_DMAOMR寄存器FUGF位的值,当设置为1时,RX FIFO会转发包括PAD和FCS字段的过小帧;为0时,会丢弃小于64字节的帧,除非接收阈值被设置为更低。

    ETH_ReceiveThresholdControl:接收阈值控制,当RX FIFO中的帧大小大于阈值时启动DMA传输请求,可选64字节、32字节、96字节或128字节,它设定ETH_DMAOMR寄存器RTC位的值。

    ETH_SecondFrameOperate:处理第二个帧,可选使能或禁止,它设定ETH_DMAOMR寄存器OSF位的值,当设置为1时会命令DMA处理第二个发送数据帧。

    ETH_AddressAlignedBeats:地址对齐节拍,可选使能或禁止,它设定以太网DMA总线模式寄存器(ETH_DMABMR)AAB位的值,当设置为1并且固定突发位(FB)也为1时,AHB接口会生成与起始地址LS位对齐的所有突发;如果FB位为0,则第一个突发不对齐,但后续的突发与地址对齐。一般选择使能。

    ETH_FixedBurst:固定突发,控制AHB主接口是否执行固定突发传输,可选使能或禁止,它设定ETH_DMABMR寄存器FB位的值,当设置为1时,AHB在正常突发传输开始期间使用SINGLE、INCR4、INCR8或INCR16;为0时,AHB使用SINGLE和INCR突发传输操作。

    ETH_RxDMABurstLength:DMA突发接收长度,有多个值可选,一般选择32Beat,可实现32*32bits突发长度,它设定ETH_DMABMR寄存器FPM位和RDP位的值。

    ETH_TxDMABurstLength:DMA突发发送长度,有多个值可选,一般选择32Beat,可实现32*32bits突发长度,它设定ETH_DMABMR寄存器FPM位和PBL位的值。

    ETH_DescriptorSkipLength:描述符跳过长度,指定两个未链接描述符之间跳过的字数,地址从当前描述符结束处开始跳到下一个描述符起始处,可选0~7,它设定ETH_DMABMR寄存器DSL位的值。

    ETH_DMAArbitration:DMA仲裁,控制RX和TX优先级,可选RX TX优先级比为1:1、2:1、3:1、4:1或者RX优先于TX,它设定ETH_DMABMR寄存器PM位和DA位的值,当设置为1时,RX优先于TX;为0时,循环调度,RX TX优先级比由PM位给出。

39.8 以太网通信实验:无操作系统LwIP移植

LwIP可以在带操作系统上运行,亦可在无操作系统上运行,这一实验我们讲解在无操作系统的移植步骤,并实现简单的传输代码,后续章节会讲解在带操作系统移植过程,一般都是在无操作系统基础上修改而来的。

39.8.1 硬件设计

在讲解移植步骤之前,有必须先介绍我们的实验硬件设计,主要是LAN8720A通过RMII和SMI接口与STM32F42x控制器连接,见图 3914。

图 3914 PHY硬件设计

电路设计时,将NINTSEL引脚通过下拉电阻拉低,设置NINT/FEFCLKO为输出50MHz时钟,当然前提是在XTAL1和XTAL2接入了25MHz的时钟源。另外也把REGOFF引脚通过下拉电阻拉低,使能使用内部+1.2V稳压器。

39.8.2 移植步骤

之前已经介绍了LwIP源代码(lwip-1.4.1.zip)和ST官方LwIP测试平台资料(stsw-stm32070.zip)下载,我们移植步骤是基于这两份资料进行的。

无操作系统移植LwIP需要的文件参考图 3915,图中只显示了*.c文件,还需要用到对应的*.h文件。

图 3915 LwIP移植实验文件结构

接下来,我们就根据图中文件结构详解移植过程。实验例程有需要用到系统滴答定时器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文件夹(路径:… \Utilities\Third_Party\lwip-1.4.1\port),把整个port文件夹拷贝lwip-1.4.1文件夹中,在port文件夹下的STM32F4x7文件中把arch和Standalone两个文件夹直接剪切到port文件夹中,即此时port文件夹有三个STM32F4x7、arch和Standalone文件夹,最后把STM32F4x7文件夹删除,最终的文件结构见图 3916,arch存放与开发平台相关头文件,Standalone文件夹是无操作系统移植时ETH外设与LwIP连接的底层驱动函数。

图 3916 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两个文件(路径:…\Libraries\STM32F4x7_ETH_Driver),将这两个文件拷贝到ETH文件夹中,对应改名为stm32f429_eth.h和stm32f429_eth.c,这两个文件是ETH驱动文件,类似标准库中外设驱动代码实现文件,在移植过程中我们几乎不过文件的内容。这部分函数由port文件夹相关代码调用。另外一部分是相关GPIO初始化、ETH外设初始化、PHY状态获取等等函数的实现,在stsw-stm32070文件夹中找到stm32f4x7_eth_bsp.c、stm32f4x7_eth_bsp.h和stm32f4x7_eth_conf.h三个文件(路径:…\Project\Standalone\tcp_echo_client),将这三个文件拷贝到ETH文件夹中,对应改名为stm32f429_phy.c、stm32f429_phy.h和stm32f429_eth_conf.h。因为,ST官方LwIP测试平台使用的PHY型号不是使用LAN8720A,所以这三个文件需要我们进行修改。

最后,是LwIP测试代码实现,为测试LwIP移植是否成功和检查LwIP功能,我们编写TCP通信实现代码,设置开发板为TCP从机,电脑端为TCP主机。在stsw-stm32070文件夹中找到netconf.c、tcp_echoclient.c、lwipopts.h、netconf.h和tcp_echoclient.h五个文件(路径:…\Project\Standalone\tcp_echo_client),直接拷贝到App文件夹(自己新建)中,netconf.c文件代码实现LwIP初始化函数、周期调用函数、DHCP功能函数等等,tcp_echoclient.c文件实现TCP通信参数代码,lwipopts.h包含LwIP功能选项。

第二部:为工程添加文件

第一步已经把相关的文件拷贝到对应的文件夹中,接下来就可以把需要用到的文件添加到工程中。图 3915已经指示出来工程需要用到的*.c文件,所以最终工程文件结构见图 3917,图中api、ipv4和core都包含了对应文件夹下的所有*.c文件。

图 3917 工程文件结构

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

图 3918 添加相关头文件路径

第三步:文件修改

ethernetif.c文件是无操作系统时网络接口函数,该文件在移植是只需修改相关头文件名,函数实现部分无需修改。该文件主要有三个部分函数,一个是low_level_init,用于初始化MAC相关工作环境、初始化DMA描述符链表,并使能MAC和DMA;一个是low_level_output,它是最底层发送一帧数据函数;最后一个是low_level_input,它是最底层接收一帧数据函数。

stm32f429_eth.c和stm32f429_eth.h两个文件用于ETH驱动函数实现,它是通过直接操作寄存器方式实现,这两个文件我们无需修改。stm32f429_eth_conf.h文件包含了一些功能选项的宏定义,我们对部分内容进行了修改。

代码清单 392 stm32f429_eth_conf.h文件宏定义

1 #ifdef USE_Delay

2 #include "Bsp/systick/bsp_SysTick.h"

3 #define _eth_delay_ Delay_10ms

4 #else

5 #define _eth_delay_ ETH_Delay

6 #endif

7

8 #ifdef USE_Delay

9 /* LAN8742A Reset delay */

10 #define LAN8742A_RESET_DELAY ((uint32_t)0x00000005)

11 #else

12 /* LAN8742A Reset delay */

13 #define LAN8742A_RESET_DELAY ((uint32_t)0x00FFFFFF)

14 #endif

15

16 /* The LAN8742A PHY status register */

17 /* PHY status register Offset */

18 #define PHY_SR ((uint16_t)0x001F)

19 /* PHY Speed mask 1:10Mb/s 0:100Mb/s*/

20 #define PHY_SPEED_STATUS ((uint16_t)0x0004)

21 /* PHY Duplex mask 1:Full duplex 0:Half duplex*/

22 #define PHY_DUPLEX_STATUS ((uint16_t)0x0010)

通过宏定义USE_Delay可选是否使用自定义的延时函数,Delay_10ms函数是通过系统滴答定时器实现的延时函数,ETH_Delay函数是ETH驱动自带的简单循环延时函数,延时函数实现方法不同,对形参要求不同。因为ST官方例程是基于DP83848型号的PHY,而开发板的PHY型号是LAN8720A。LAN8720A复位时需要一段延时时间,这里需要定义延时时间长度,大约50ms。驱动代码中需要获取PHY的速度和工作模式,LAN8720A的R31是特殊控制/状态寄存器,包括指示以太网速度和工作模式的状态位。

stm32f42x_phy.c和stm32f42x_phy.h两个文件是ETH外设相关的底层配置,包括RMII接口GPIO初始化、SMI接口GPIO初始化、MAC控制器工作环境配置,还有一些PHY的状态获取和控制修改函数。ST官方例程文件包含了中断引脚的相关配置,主要用于指示接收到以太网帧,我们这里不需要使用,采用无限轮询方法检测接收状态。stm32f42x_phy.h文件存放相关宏定义,包含RMII和SMI引脚信息等宏定义,其中要特别说明的有一个宏,定义了PHY地址:ETHERNET_PHY_ADDRESS,这里根据硬件设计设置为0x00,这在SMI通信是非常重要的。

代码清单 393 ETH_GPIO_Config函数

1 void ETH_GPIO_Config(void)

2 {

3 GPIO_InitTypeDef GPIO_InitStructure;

4

5 /* Enable GPIOs clocks */

6 RCC_AHB1PeriphClockCmd(ETH_MDIO_GPIO_CLK | ETH_MDC_GPIO_CLK |

7 ETH_RMII_REF_CLK_GPIO_CLK|ETH_RMII_CRS_DV_GPIO_CLK|

8 ETH_RMII_RXD0_GPIO_CLK | ETH_RMII_RXD1_GPIO_CLK |

9 ETH_RMII_TX_EN_GPIO_CLK | ETH_RMII_TXD0_GPIO_CLK |

10 ETH_RMII_TXD1_GPIO_CLK | ETH_NRST_GPIO_CLK, ENABLE);

11

12 /* Enable SYSCFG clock */

13 RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);

14

15 /* MII/RMII Media interface selection ------------------------------*/

16 #ifdef MII_MODE /* Mode MII with STM324xx-EVAL */

17 #ifdef PHY_CLOCK_MCO

18 /* Output HSE clock (25MHz) on MCO pin (PA8) to clock the PHY */

19 RCC_MCO1Config(RCC_MCO1Source_HSE, RCC_MCO1Div_1);

20 #endif /* PHY_CLOCK_MCO */

21

22 SYSCFG_ETH_MediaInterfaceConfig(SYSCFG_ETH_MediaInterface_MII);

23 #elif defined RMII_MODE /* Mode RMII with STM324xx-EVAL */

24

25 SYSCFG_ETH_MediaInterfaceConfig(SYSCFG_ETH_MediaInterface_RMII);

26 #endif

27

28 /* Ethernet pins configuration *************************************/

29 /*

30 ETH_MDIO -------------------------> PA2

31 ETH_MDC --------------------------> PC1

32 ETH_MII_RX_CLK/ETH_RMII_REF_CLK---> PA1

33 ETH_MII_RX_DV/ETH_RMII_CRS_DV ----> PA7

34 ETH_MII_RXD0/ETH_RMII_RXD0 -------> PC4

35 ETH_MII_RXD1/ETH_RMII_RXD1 -------> PC5

36 ETH_MII_TX_EN/ETH_RMII_TX_EN -----> PB11

37 ETH_MII_TXD0/ETH_RMII_TXD0 -------> PG13

38 ETH_MII_TXD1/ETH_RMII_TXD1 -------> PG14

39 ETH_NRST -------------------------> PI1

40 */

41 GPIO_InitStructure.GPIO_Pin = ETH_NRST_PIN;

42 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;

43 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;

44 GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;

45 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;

46 GPIO_Init(ETH_NRST_PORT, &GPIO_InitStructure);

47

48 ETH_NRST_PIN_LOW();

49 _eth_delay_(LAN8742A_RESET_DELAY);

50 ETH_NRST_PIN_HIGH();

51 _eth_delay_(LAN8742A_RESET_DELAY);

52

53 /* Configure ETH_MDIO */

54 GPIO_InitStructure.GPIO_Pin = ETH_MDIO_PIN;

55 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;

56 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;

57 GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;

58 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;

59 GPIO_Init(ETH_MDIO_PORT, &GPIO_InitStructure);

60 GPIO_PinAFConfig(ETH_MDIO_PORT, ETH_MDIO_SOURCE, ETH_MDIO_AF);

61

62 /* Configure ETH_MDC */

63 GPIO_InitStructure.GPIO_Pin = ETH_MDC_PIN;

64 GPIO_Init(ETH_MDC_PORT, &GPIO_InitStructure);

65 GPIO_PinAFConfig(ETH_MDC_PORT, ETH_MDC_SOURCE, ETH_MDC_AF);

66

67 /**************************************/

68 /** 省略部分引脚初始化 ***/

69 /**************************************/

70

71 /* Configure ETH_RMII_TXD1 */

72 GPIO_InitStructure.GPIO_Pin = ETH_RMII_TXD1_PIN;

73 GPIO_Init(ETH_RMII_TXD1_PORT, &GPIO_InitStructure);

74 GPIO_PinAFConfig(ETH_RMII_TXD1_PORT, ETH_RMII_TXD1_SOURCE,

75 ETH_RMII_TXD1_AF);

76 }

STM32f42x控制器支持MII和RMII接口,通过程序控制使用RMII接口,同时需要使能SYSYCFG时钟,函数后部分就是接口GPIO初始化实现,这里我们还连接了LAN8720A的复位引脚,通过拉低一段时间让芯片硬件复位。

代码清单 394 ETH_MACDMA_Config函数

1 static void ETH_MACDMA_Config(void)

2 {

3 /* Enable ETHERNET clock */

4 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_ETH_MAC |

5 RCC_AHB1Periph_ETH_MAC_Tx|RCC_AHB1Periph_ETH_MAC_Rx,ENABLE);

6

7 /* Reset ETHERNET on AHB Bus */

8 ETH_DeInit();

9 /* Software reset */

10 ETH_SoftwareReset();

11 /* Wait for software reset */

12 while (ETH_GetSoftwareResetStatus() == SET);

13

14 /* ETHERNET Configuration ------------------------------*/

15 /* 缺省配置ETH_InitStructure */

16 ETH_StructInit(&ETH_InitStructure);

17

18 /* Fill ETH_InitStructure parametrs */

19 /*-------------------- MAC ----------------------------*/

20 /* 开启网络自适应功能,速度和工作模式无需配置 */

21 ETH_InitStructure.ETH_AutoNegotiation = ETH_AutoNegotiation_Enable;

22 // ETH_InitStructure.ETH_AutoNegotiation = ETH_AutoNegotiation_Disable;

23 // ETH_InitStructure.ETH_Speed = ETH_Speed_10M;

24 // ETH_InitStructure.ETH_Mode = ETH_Mode_FullDuplex;

25 /* 关闭反馈 */

26 ETH_InitStructure.ETH_LoopbackMode = ETH_LoopbackMode_Disable;

27 /* 关闭重传功能 */

28 ETH_InitStructure.ETH_RetryTransmission=ETH_RetryTransmission_Disable;

29 /* 关闭自动去除PDA/CRC功能 */

30 ETH_InitStructure.ETH_AutomaticPadCRCStrip =

31 ETH_AutomaticPadCRCStrip_Disable;

32 /* 关闭接收所有的帧 */

33 ETH_InitStructure.ETH_ReceiveAll = ETH_ReceiveAll_Disable;

34 /* 允许接收所有广播帧 */

35 ETH_InitStructure.ETH_BroadcastFramesReception =

36 ETH_BroadcastFramesReception_Enable;

37 /* 关闭混合模式的地址过滤 */

38 ETH_InitStructure.ETH_PromiscuousMode = ETH_PromiscuousMode_Disable;

39 /* 对于组播地址使用完美地址过滤 */

40 ETH_InitStructure.ETH_MulticastFramesFilter =

41 ETH_MulticastFramesFilter_Perfect;

42 /* 对单播地址使用完美地址过滤 */

43 ETH_InitStructure.ETH_UnicastFramesFilter =

44 ETH_UnicastFramesFilter_Perfect;

45 #ifdef CHECKSUM_BY_HARDWARE

46 /* 开启ipv4和TCP/UDP/ICMP的帧校验和卸载 */

47 ETH_InitStructure.ETH_ChecksumOffload = ETH_ChecksumOffload_Enable;

48 #endif

49

50 /*------------------------ DMA -------------------------------*/

51 /*当我们使用帧校验和卸载功能的时候,一定要使能存储转发模式,存储

52 转发模式中要保证整个帧存储在FIFO中, 这样MAC能插入/识别出帧校验

53 值,当真校验正确的时候DMA就可以处理帧,否则就丢弃掉该帧*/

54 /* 开启丢弃TCP/IP错误帧 */

55 ETH_InitStructure.ETH_DropTCPIPChecksumErrorFrame =

56 ETH_DropTCPIPChecksumErrorFrame_Enable;

57 /* 开启接收数据的存储转发模式 */

58 ETH_InitStructure.ETH_ReceiveStoreForward =

59 ETH_ReceiveStoreForward_Enable;

60 /* 开启发送数据的存储转发模式 */

61 ETH_InitStructure.ETH_TransmitStoreForward =

62 ETH_TransmitStoreForward_Enable;

63

64 /* 禁止转发错误帧 */

65 ETH_InitStructure.ETH_ForwardErrorFrames =

66 ETH_ForwardErrorFrames_Disable;

67 /* 不转发过小的好帧 */

68 ETH_InitStructure.ETH_ForwardUndersizedGoodFrames =

69 ETH_ForwardUndersizedGoodFrames_Disable;

70 /* 打开处理第二帧功能 */

71 ETH_InitStructure.ETH_SecondFrameOperate =

72 ETH_SecondFrameOperate_Enable;

73 /* 开启DMA传输的地址对齐功能 */

74 ETH_InitStructure.ETH_AddressAlignedBeats =

75 ETH_AddressAlignedBeats_Enable;

76 /* 开启固定突发功能 */

77 ETH_InitStructure.ETH_FixedBurst = ETH_FixedBurst_Enable;

78 /* DMA发送的最大突发长度为32个节拍 */

79 ETH_InitStructure.ETH_RxDMABurstLength = ETH_RxDMABurstLength_32Beat;

80 /*DMA接收的最大突发长度为32个节拍 */

81 ETH_InitStructure.ETH_TxDMABurstLength = ETH_TxDMABurstLength_32Beat;

82 ETH_InitStructure.ETH_DMAArbitration =

83 ETH_DMAArbitration_RoundRobin_RxTx_2_1;

84

85 /* 配置ETH */

86 EthStatus = ETH_Init(&ETH_InitStructure, ETHERNET_PHY_ADDRESS);

87 }

首先是使能ETH时钟,复位ETH配置。ETH_StructInit函数用于初始化ETH_InitTypeDef结构体变量,会给每个成员赋予缺省值。接下来就是根据需要配置ETH_InitTypeDef结构体变量,关于结构体各个成员意义已在"ETH初始化结构体详解"作了分析。最后调用ETH_Init函数完成配置,ETH_Init函数有两个形参,一个是ETH_InitTypeDef结构体变量指针,第二个是PHY地址,函数还有一个返回值,用于指示初始化配置是否成功。

代码清单 395 ETH_BSP_Config函数

1 #define GET_PHY_LINK_STATUS()

2 (ETH_ReadPHYRegister(ETHERNET_PHY_ADDRESS,PHY_BSR)&0x00000004)

3

4 void ETH_BSP_Config(void)

5 {

6 /* Configure the GPIO ports for ethernet pins */

7 ETH_GPIO_Config();

8

9 /* Configure the Ethernet MAC/DMA */

10 ETH_MACDMA_Config();

11

12 /* Get Ethernet link status*/

13 if (GET_PHY_LINK_STATUS()) {

14 EthStatus |= ETH_LINK_FLAG;

15 }

16 }

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函数调用。

代码清单 396 ETH_CheckLinkStatus函数

1 void ETH_CheckLinkStatus(uint16_t PHYAddress)

2 {

3 static uint8_t status = 0;

4 uint32_t t = GET_PHY_LINK_STATUS();

5

6 /* If we have link and previous check was not yet */

7 if (t && !status) {

8 /* Set link up */

9 netif_set_link_up(&gnetif);

10

11 status = 1;

12 }

13 /* If we don't have link and it was on previous check */

14 if (!t && status) {

15 EthLinkStatus = 1;

16 /* Set link down */

17 netif_set_link_down(&gnetif);

18

19 status = 0;

20 }

21 }

ETH_CheckLinkStatus函数用于获取PHY状态,实际上也是通过宏定义GET_PHY_LINK_STATUS()获取得到的,函数还根据PHY状态通知LwIP当前链路状态,gnetif是一个netif结构体类型变量,LwIP定义了netif结构体类型,用于指示某一网卡相关信息,LwIP是支持多个网卡设备,使用时需要为每个网卡设备定义一个netif类型变量。无操作系统时ETH_CheckLinkStatus函数被无限循环调用。

代码清单 397 ETH_link_callback函数

1 void ETH_link_callback(struct netif *netif)

2 {

3 __IO uint32_t timeout = 0;

4 uint32_t tmpreg;

5 uint16_t RegValue;

6 struct ip_addr ipaddr;

7 struct ip_addr netmask;

8 struct ip_addr gw;

9

10 if (netif_is_link_up(netif)) {

11 /* Restart the auto-negotiation */

12 if (ETH_InitStructure.ETH_AutoNegotiation !=

13 ETH_AutoNegotiation_Disable) {

14 /* Reset Timeout counter */

15 timeout = 0;

16 /* Enable auto-negotiation */

17 ETH_WritePHYRegister(ETHERNET_PHY_ADDRESS, PHY_BCR,

18 PHY_AutoNegotiation);

19 /* Wait until the auto-negotiation will be completed */

20 do {

21 timeout++;

22 } while (!(ETH_ReadPHYRegister(ETHERNET_PHY_ADDRESS, PHY_BSR)

23 &PHY_AutoNego_Complete)&&(timeout<(uint32_t)PHY_READ_TO));

24

25 /* Reset Timeout counter */

26 timeout = 0;

27 /* Read the result of the auto-negotiation */

28 RegValue = ETH_ReadPHYRegister(ETHERNET_PHY_ADDRESS, PHY_SR);

29

30 if ((RegValue & PHY_DUPLEX_STATUS) != (uint16_t)RESET) {

31 ETH_InitStructure.ETH_Mode = ETH_Mode_FullDuplex;

32 } else {

33 ETH_InitStructure.ETH_Mode = ETH_Mode_HalfDuplex;

34 }

35 if (RegValue & PHY_SPEED_STATUS) {

36 /* Set Ethernet speed to 10M following the auto-negotiation */

37 ETH_InitStructure.ETH_Speed = ETH_Speed_10M;

38 } else {

39 /* Set Ethernet speed to 100M following the auto-negotiation */

40 ETH_InitStructure.ETH_Speed = ETH_Speed_100M;

41 }

42

43 /*------------ ETHERNET MACCR Re-Configuration -------------*/

44 /* Get the ETHERNET MACCR value */

45 tmpreg = ETH->MACCR;

46

47 /* Set the FES bit according to ETH_Speed value */

48 /* Set the DM bit according to ETH_Mode value */

49 tmpreg |= (uint32_t)(ETH_InitStructure.ETH_Speed |

50 ETH_InitStructure.ETH_Mode);

51

52 /* Write to ETHERNET MACCR */

53 ETH->MACCR = (uint32_t)tmpreg;

54

55 _eth_delay_(ETH_REG_WRITE_DELAY);

56 tmpreg = ETH->MACCR;

57 ETH->MACCR = tmpreg;

58 }

59

60 /* Restart MAC interface */

61 ETH_Start();

62

63 #ifdef USE_DHCP

64 ipaddr.addr = 0;

65 netmask.addr = 0;

66 gw.addr = 0;

67 DHCP_state = DHCP_START;

68 #else

69 IP4_ADDR(&ipaddr, IP_ADDR0, IP_ADDR1, IP_ADDR2, IP_ADDR3);

70 IP4_ADDR(&netmask, NETMASK_ADDR0, NETMASK_ADDR1 ,

71 NETMASK_ADDR2, NETMASK_ADDR3);

72 IP4_ADDR(&gw, GW_ADDR0, GW_ADDR1, GW_ADDR2, GW_ADDR3);

73 #endif /* USE_DHCP */

74

75 netif_set_addr(&gnetif, &ipaddr , &netmask, &gw);

76

77 /* When the netif is fully configured this function must be called.*/

78 netif_set_up(&gnetif);

79

80 EthLinkStatus = 0;

81 } else {

82 ETH_Stop();

83 #ifdef USE_DHCP

84 DHCP_state = DHCP_LINK_DOWN;

85 dhcp_stop(netif);

86 #endif /* USE_DHCP */

87

88 /* When the netif link is down this function must be called.*/

89 netif_set_down(&gnetif);

90 }

91 }

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定义了相关宏。

代码清单 398 LwIP配置相关宏定义

1 /* DHCP状态 */

2 #define DHCP_START 1

3 #define DHCP_WAIT_ADDRESS 2

4 #define DHCP_ADDRESS_ASSIGNED 3

5 #define DHCP_TIMEOUT 4

6 #define DHCP_LINK_DOWN 5

7

8 //#define USE_DHCP /* enable DHCP, if disabled static address is used */

9

10 /* 调试信息输出 */

11 #define SERIAL_DEBUG

12 /* 远端IP地址和端口 */

13 #define DEST_IP_ADDR0 192

14 #define DEST_IP_ADDR1 168

15 #define DEST_IP_ADDR2 1

16 #define DEST_IP_ADDR3 105

17 #define DEST_PORT 6000

18

19 /* MAC地址:网卡地址 */

20 #define MAC_ADDR0 2

21 #define MAC_ADDR1 0

22 #define MAC_ADDR2 0

23 #define MAC_ADDR3 0

24 #define MAC_ADDR4 0

25 #define MAC_ADDR5 0

26

27 /*静态IP地址 */

28 #define IP_ADDR0 192

29 #define IP_ADDR1 168

30 #define IP_ADDR2 1

31 #define IP_ADDR3 122

32

33 /* 子网掩码 */

34 #define NETMASK_ADDR0 255

35 #define NETMASK_ADDR1 255

36 #define NETMASK_ADDR2 255

37 #define NETMASK_ADDR3 0

38

39 /* 网关 */

40 #define GW_ADDR0 192

41 #define GW_ADDR1 168

42 #define GW_ADDR2 1

43 #define GW_ADDR3 1

44

45 /* 检测PHY链路状态的实际间隔(单位:ms) */

46 #ifndef LINK_TIMER_INTERVAL

47 #define LINK_TIMER_INTERVAL 1000

48 #endif

49

50 /* MII and RMII mode selection ***********/

51 #define RMII_MODE

52 //#define MII_MODE

53

54 /* 在MII模式时,使能MCO引脚输出25MHz脉冲 */

55 #ifdef MII_MODE

56 #define PHY_CLOCK_MCO

57 #endif

USE_DHCP宏用于定义是否使用DHCP功能,如果不定义该宏,直接使用静态的IP地址,如果定义该宏,则使用DHCP功能,获取动态的IP地址,这里有个需要注意的地方,电脑是没办法提供DHCP服务功能的,路由器才有DHCP服务功能,使用当开发板直连电脑时不能定义该宏。

SERIAL_DEBUG宏是定义是否使能串口定义相关调试信息功能,一般选择使能,所以在main函数中需要添加串口初始化函数。

接下来,定义了远端IP和端口、MAC地址、静态IP地址、子网掩码、网关相关宏,可以根据实际情况修改。

LAN8720A仅支持RMII接口,根据硬件设计这里定义使用RMII_MODE。

代码清单 399 LwIP_Init函数

1 void LwIP_Init(void)

2 {

3 struct ip_addr ipaddr;

4 struct ip_addr netmask;

5 struct ip_addr gw;

6

7 /* Initializes the dynamic memory heap defined by MEM_SIZE.*/

8 mem_init();

9 /* Initializes the memory pools defined by MEMP_NUM_x.*/

10 memp_init();

11

12 #ifdef USE_DHCP

13 ipaddr.addr = 0;

14 netmask.addr = 0;

15 gw.addr = 0;

16 #else

17 IP4_ADDR(&ipaddr, IP_ADDR0, IP_ADDR1, IP_ADDR2, IP_ADDR3);

18 IP4_ADDR(&netmask, NETMASK_ADDR0, NETMASK_ADDR1 ,

19 NETMASK_ADDR2,NETMASK_ADDR3);

20 IP4_ADDR(&gw, GW_ADDR0, GW_ADDR1, GW_ADDR2, GW_ADDR3);

21 #endif

22 /* 添加以太网设备 */

23 netif_add(&gnetif, &ipaddr, &netmask, &gw, NULL,

24 &ethernetif_init, &ethernet_input);

25

26 /* 设置以太网设备为默认网卡 */

27 netif_set_default(&gnetif);

28

29 if (EthStatus == (ETH_INIT_FLAG | ETH_LINK_FLAG)) {

30 gnetif.flags |= NETIF_FLAG_LINK_UP;

31 /* 配置完成网卡后启动网卡*/

32 netif_set_up(&gnetif);

33 #ifdef USE_DHCP

34 DHCP_state = DHCP_START;

35 #else

36 #ifdef SERIAL_DEBUG

37 printf("\n Static IP address \n");

38 printf("IP: %d.%d.%d.%d\n",IP_ADDR0,IP_ADDR1,IP_ADDR2,IP_ADDR3);

39 printf("NETMASK: %d.%d.%d.%d\n",NETMASK_ADDR0,NETMASK_ADDR1,

40 NETMASK_ADDR2,NETMASK_ADDR3);

41 printf("Gateway:%d.%d.%d.%d\n",GW_ADDR0,GW_ADDR1,GW_ADDR2,GW_ADDR3);

42 #endif /* SERIAL_DEBUG */

43 #endif /* USE_DHCP */

44 } else {

45 /* 当网络链路关闭时关闭网卡设备 */

46 netif_set_down(&gnetif);

47 #ifdef USE_DHCP

48 DHCP_state = DHCP_LINK_DOWN;

49 #endif /* USE_DHCP */

50 #ifdef SERIAL_DEBUG

51 printf("\n Network Cable is \n");

52 printf(" not connected \n");

53 #endif /* SERIAL_DEBUG */

54 }

55 /* 设置链路回调函数,用于获取链路状态 */

56 netif_set_link_callback(&gnetif, ETH_link_callback);

57 }

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函数实在当链路状态发生改变时需要调用的回调函数配置。

代码清单 3910 LwIP_Pkt_Handle函数

1 void LwIP_Pkt_Handle(void)

2 {

3 /* 从以太网存储器读取一个以太网帧并将其发送给LwIP */

4 ethernetif_input(&gnetif);

5 }

LwIP_Pkt_Handle函数用于从以太网存储器读取一个以太网帧并将其发送给LwIP,它在接收到以太网帧时被调用,它是直接调用ethernetif_input函数实现的,该函数定义在ethernetif.c文件中。

代码清单 3911 LwIP_Periodic_Handle函数

1 void LwIP_Periodic_Handle(__IO uint32_t localtime)

2 {

3 #if LWIP_TCP

4 /* TCP periodic process every 250 ms */

5 if (localtime - TCPTimer >= TCP_TMR_INTERVAL) {

6 TCPTimer = localtime;

7 tcp_tmr();

8 }

9 #endif

10

11 /* ARP periodic process every 5s */

12 if ((localtime - ARPTimer) >= ARP_TMR_INTERVAL) {

13 ARPTimer = localtime;

14 etharp_tmr();

15 }

16

17 /* Check link status periodically */

18 if ((localtime - LinkTimer) >= LINK_TIMER_INTERVAL) {

19 ETH_CheckLinkStatus(ETHERNET_PHY_ADDRESS);

20 LinkTimer=localtime;

21 }

22

23 #ifdef USE_DHCP

24 /* Fine DHCP periodic process every 500ms */

25 if (localtime - DHCPfineTimer >= DHCP_FINE_TIMER_MSECS) {

26 DHCPfineTimer = localtime;

27 dhcp_fine_tmr();

28 if ((DHCP_state != DHCP_ADDRESS_ASSIGNED) &&

29 (DHCP_state != DHCP_TIMEOUT) &&

30 (DHCP_state != DHCP_LINK_DOWN)) {

31 #ifdef SERIAL_DEBUG

32 LED1_TOGGLE;

33 printf("\nFine DHCP periodic process every 500ms\n");

34 #endif /* SERIAL_DEBUG */

35

36 /* process DHCP state machine */

37 LwIP_DHCP_Process_Handle();

38 }

39 }

40

41 /* DHCP Coarse periodic process every 60s */

42 if (localtime - DHCPcoarseTimer >= DHCP_COARSE_TIMER_MSECS) {

43 DHCPcoarseTimer = localtime;

44 dhcp_coarse_tmr();

45 }

46

47 #endif

48 }

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函数。

代码清单 3912 LwIP_DHCP_Process_Handle函数

1 void LwIP_DHCP_Process_Handle(void)

2 {

3 struct ip_addr ipaddr;

4 struct ip_addr netmask;

5 struct ip_addr gw;

6

7 switch (DHCP_state) {

8 case DHCP_START: {

9 DHCP_state = DHCP_WAIT_ADDRESS;

10 dhcp_start(&gnetif);

11 /* IP address should be set to 0

12 every time we want to assign a new DHCP address */

13 IPaddress = 0;

14 #ifdef SERIAL_DEBUG

15 printf("\n Looking for \n");

16 printf(" DHCP server \n");

17 printf(" please wait... \n");

18 #endif /* SERIAL_DEBUG */

19 }

20 break;

21

22 case DHCP_WAIT_ADDRESS: {

23 /* Read the new IP address */

24 IPaddress = gnetif.ip_addr.addr;

25

26 if (IPaddress!=0) {

27 DHCP_state = DHCP_ADDRESS_ASSIGNED;

28 /* Stop DHCP */

29 dhcp_stop(&gnetif);

30 #ifdef SERIAL_DEBUG

31 printf("\n IP address assigned \n");

32 printf(" by a DHCP server \n");

33 printf("IP: %d.%d.%d.%d\n",(uint8_t)(IPaddress),

34 (uint8_t)(IPaddress >> 8),(uint8_t)(IPaddress >> 16),

35 (uint8_t)(IPaddress >> 24));

36 printf("NETMASK: %d.%d.%d.%d\n",NETMASK_ADDR0,NETMASK_ADDR1,

37 NETMASK_ADDR2,NETMASK_ADDR3);

38 printf("Gateway: %d.%d.%d.%d\n",GW_ADDR0,GW_ADDR1,

39 GW_ADDR2,GW_ADDR3);

40 LED1_ON;

41 #endif /* SERIAL_DEBUG */

42 } else {

43 /* DHCP timeout */

44 if (gnetif.dhcp->tries > MAX_DHCP_TRIES) {

45 DHCP_state = DHCP_TIMEOUT;

46 /* Stop DHCP */

47 dhcp_stop(&gnetif);

48 /* Static address used */

49 IP4_ADDR(&ipaddr, IP_ADDR0 ,IP_ADDR1 , IP_ADDR2 , IP_ADDR3 );

50 IP4_ADDR(&netmask, NETMASK_ADDR0, NETMASK_ADDR1,

51 NETMASK_ADDR2, NETMASK_ADDR3);

52 IP4_ADDR(&gw, GW_ADDR0, GW_ADDR1, GW_ADDR2, GW_ADDR3);

53 netif_set_addr(&gnetif, &ipaddr , &netmask, &gw);

54 #ifdef SERIAL_DEBUG

55 printf("\n DHCP timeout \n");

56 printf(" Static IP address \n");

57 printf("IP: %d.%d.%d.%d\n",IP_ADDR0,IP_ADDR1,

58 IP_ADDR2,IP_ADDR3);

59 printf("NETMASK: %d.%d.%d.%d\n",NETMASK_ADDR0,NETMASK_ADDR1,

60 NETMASK_ADDR2,NETMASK_ADDR3);

61 printf("Gateway: %d.%d.%d.%d\n",GW_ADDR0,GW_ADDR1,

62 GW_ADDR2,GW_ADDR3);

63 LED1_ON;

64 #endif /* SERIAL_DEBUG */

65 }

66 }

67 }

68 break;

69 default:

70 break;

71 }

72 }

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,sequential API又有两种函数结构,一种是Netconn,一种是Socket,它与在电脑端使用的BSD标准的Socket API结构和原理是非常相似的。

接下来内容我们使用RAW API实现一个简单的TCP通信测试,ST官方有提供相关的例程,我们对其内容稍作调整。代码内容存放在tcp_echoclient.c文件中。TCP在各个层次处理过程见图 3919。

图 3919 TCP处理过程

网络接口层的netif->output和netif->input是在ethernetif.c文件中实现的,网络层和传输层有LwIP协议栈实现,应用层代码就是用户使用LwIP函数实现网络功能。

代码清单 3913 tcp_echoclient_connect函数

1 void tcp_echoclient_connect(void)

2 {

3 struct ip_addr DestIPaddr;

4

5 /* create new tcp pcb */

6 echoclient_pcb = tcp_new();

7

8 if (echoclient_pcb != NULL) {

9 IP4_ADDR( &DestIPaddr, DEST_IP_ADDR0, DEST_IP_ADDR1,

10 DEST_IP_ADDR2, DEST_IP_ADDR3 );

11

12 /* connect to destination address/port */

13 tcp_connect(echoclient_pcb,&DestIPaddr,

14 DEST_PORT,tcp_echoclient_connected);

15 } else {

16 /* deallocate the pcb */

17 memp_free(MEMP_TCP_PCB, echoclient_pcb);

18 #ifdef SERIAL_DEBUG

19 printf("\n\r can not create tcp pcb");

20 #endif

21 }

22 }

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函数释放相关内容。

代码清单 3914 tcp_echoclient_disconnect函数

1 struct echoclient {

2 enum echoclient_states state; /* connection status */

3 struct tcp_pcb *pcb; /* pointer on the current tcp_pcb */

4 struct pbuf *p_tx; /* pointer on pbuf to be transmitted */

5 };

6

7 void tcp_echoclient_disconnect(void)

8 {

9 /* close connection */

10 tcp_echoclient_connection_close(echoclient_pcb,echoclient_es);

11 #ifdef SERIAL_DEBUG

12 printf("\n\r close TCP connection");

13 #endif

14 }

echoclient是自定义的一个结构体类型,包含了TCP从设备的状态、TCP协议控制块指针和发送数据指针。tcp_echoclient_disconnect函数用于断开TCP连接,通过调用tcp_echoclient_connection_close函数实现,它有两个形参,一个是TCP协议控制块,一个是echoclient类型指针。

代码清单 3915 tcp_echoclient_connected函数

1 static err_t tcp_echoclient_connected(void *arg, struct tcp_pcb *tpcb,

2 err_t err)

3 {

4 struct echoclient *es = NULL;

5

6 if (err == ERR_OK) {

7 /* allocate structure es to maintain tcp connection informations */

8 es = (struct echoclient *)mem_malloc(sizeof(struct echoclient));

9 echoclient_es=es;

10 if (es != NULL) {

11 es->state = ES_CONNECTED;

12 es->pcb = tpcb;

13 sprintf((char*)data, "sending tcp client message %d",

14 message_count);

15 /* allocate pbuf */

16 es->p_tx = pbuf_alloc(PBUF_TRANSPORT, strlen((char*)data),

17 PBUF_POOL);

18 if (es->p_tx) {

19 /* copy data to pbuf */

20 pbuf_take(es->p_tx, (char*)data, strlen((char*)data));

21 /* pass newly allocated es structure as argument to tpcb */

22 tcp_arg(tpcb, es);

23 /* initialize LwIP tcp_recv callback function */

24 tcp_recv(tpcb, tcp_echoclient_recv);

25 /* initialize LwIP tcp_sent callback function */

26 tcp_sent(tpcb, tcp_echoclient_sent);

27 /* initialize LwIP tcp_poll callback function */

28 tcp_poll(tpcb, tcp_echoclient_poll, 1);

29 /* send data */

30 tcp_echoclient_send(tpcb,es);

31 return ERR_OK;

32 }

33 } else {

34 /* close connection */

35 tcp_echoclient_connection_close(tpcb, es);

36 /* return memory allocation error */

37 return ERR_MEM;

38 }

39 } else {

40 /* close connection */

41 tcp_echoclient_connection_close(tpcb, es);

42 }

43 return err;

44 }

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函数发送数据。

代码清单 3916 tcp_echoclient_recv函数

1 static err_t tcp_echoclient_recv(void *arg, struct tcp_pcb *tpcb,

2 struct pbuf *p, err_t err)

3 {

4 char *recdata=0;

5 struct echoclient *es;

6 err_t ret_err;

7

8 LWIP_ASSERT("arg != NULL",arg != NULL);

9 es = (struct echoclient *)arg;

10 /* if we receive an empty tcp frame from server => close connection */

11 if (p == NULL) {

12 /* remote host closed connection */

13 es->state = ES_CLOSING;

14 if (es->p_tx == NULL) {

15 /* we're done sending, close connection */

16 tcp_echoclient_connection_close(tpcb, es);

17 } else {

18 /* send remaining data*/

19 tcp_echoclient_send(tpcb, es);

20 }

21 ret_err = ERR_OK;

22 }

23 /* else : a non empty frame was received from echo server

24 but for some reason err != ERR_OK */

25 else if (err != ERR_OK) {

26 /* free received pbuf*/

27 pbuf_free(p);

28 ret_err = err;

29 } else if (es->state == ES_CONNECTED) {

30 /* increment message count */

31 message_count++;

32 /* Acknowledge data reception */

33 tcp_recved(tpcb, p->tot_len);

34 #ifdef SERIAL_DEBUG

35 recdata=(char *)malloc(p->len*sizeof(char));

36 if (recdata!=NULL) {

37 memcpy(recdata,p->payload,p->len);

38 printf("upd_rec<<%s",recdata);

39 }

40 free(recdata);

41 #endif

42 /* free received pbuf*/

43 pbuf_free(p);

44 ret_err = ERR_OK;

45 }

46 /* data received when connection already closed */

47 else {

48 /* Acknowledge data reception */

49 tcp_recved(tpcb, p->tot_len);

50

51 /* free pbuf and do nothing */

52 pbuf_free(p);

53 ret_err = ERR_OK;

54 }

55 return ret_err;

56 }

tcp_echoclient_recv函数是TCP接收回调函数,TCP从设备接收到数据时该函数就被运行一次,我们可以提取数据帧内容。函数先检测是否为空帧,如果为空帧则关闭TCP连接,然后检测是否发生传输错误,如果发送错误执行pbuf_free函数释放内存。检查无错误就可以调用tcp_recved函数接收数据,这样就可以提取接收到信息。最后调用pbuf_free函数释放相关内存。

代码清单 3917 tcp_echoclient_send函数

1 static void tcp_echoclient_send(struct tcp_pcb *tpcb, struct echoclient * es)

2 {

3 struct pbuf *ptr;

4 err_t wr_err = ERR_OK;

5

6 while ((wr_err == ERR_OK) &&

7 (es->p_tx != NULL) &&

8 (es->p_tx->len <= tcp_sndbuf(tpcb))) {

9

10 /* get pointer on pbuf from es structure */

11 ptr = es->p_tx;

12

13 /* enqueue data for transmission */

14 wr_err = tcp_write(tpcb, ptr->payload, ptr->len, 1);

15

16 if (wr_err == ERR_OK) {

17 /* continue with next pbuf in chain (if any) */

18 es->p_tx = ptr->next;

19

20 if (es->p_tx != NULL) {

21 /* increment reference count for es->p */

22 pbuf_ref(es->p_tx);

23 }

24

25 /* free pbuf: will free pbufs up to es->p

26 (because es->p has a reference count > 0) */

27 pbuf_free(ptr);

28 } else if (wr_err == ERR_MEM) {

29 /* we are low on memory, try later, defer to poll */

30 es->p_tx = ptr;

31 } else {

32 /* other problem ?? */

33 }

34 }

35 }

tcp_echoclient_send函数用于TCP数据发送,它有两个形参,一个是TCP协议控制块结构体指针,一个是echoclient结构体指针。在判断待发送数据存在并不超过最大可用发送队列数据数后,执行tcp_write函数将待发送数据写入发送队列,由协议内核决定发送时机。

代码清单 3918 tcp_echoclient_poll函数

1 static err_t tcp_echoclient_poll(void *arg, struct tcp_pcb *tpcb)

2 {

3 err_t ret_err;

4 struct echoclient *es;

5

6 es = (struct echoclient*)arg;

7 if (es != NULL) {

8 if (es->p_tx != NULL) {

9 /* there is a remaining pbuf (chain) , try to send data */

10 tcp_echoclient_send(tpcb, es);

11 } else {

12 /* no remaining pbuf (chain) */

13 if (es->state == ES_CLOSING) {

14 /* close tcp connection */

15 tcp_echoclient_connection_close(tpcb, es);

16 }

17 }

18 ret_err = ERR_OK;

19 } else {

20 /* nothing to be done */

21 tcp_abort(tpcb);

22 ret_err = ERR_ABRT;

23 }

24 return ret_err;

25 }

tcp_echoclient_poll函数是由tcp_poll函数指定的回调函数,它每500ms执行一次,函数检测是否有待发送数据,如果有就执行tcp_echoclient_send函数发送数据。

代码清单 3919 tcp_echoclient_sent函数

1 static err_t tcp_echoclient_sent(void *arg, struct tcp_pcb *tpcb, u16_t len)

2 {

3 struct echoclient *es;

4

5 LWIP_UNUSED_ARG(len);

6

7 es = (struct echoclient *)arg;

8

9 if (es->p_tx != NULL) {

10 /* still got pbufs to send */

11 tcp_echoclient_send(tpcb, es);

12 }

13

14 return ERR_OK;

15 }

tcp_echoclient_sent函数是有tcp_sent函数指定的回调函数,当接收到远端设备发送应答信号时被调用,它实际是通过调用tcp_echoclient_send函数发送数据实现的。

代码清单 3920 tcp_echoclient_connection_close函数

1 static void tcp_echoclient_connection_close(struct tcp_pcb *tpcb,

2 struct echoclient * es )

3 {

4 /* remove callbacks */

5 tcp_recv(tpcb, NULL);

6 tcp_sent(tpcb, NULL);

7 tcp_poll(tpcb, NULL,0);

8

9 if (es != NULL) {

10 mem_free(es);

11 }

12 /* close tcp connection */

13 tcp_close(tpcb);

14 }

tcp_echoclient_connection_close函数用于关闭TCP连接,将相关的回调函数解除,释放es变量内存,最后调用tcp_close函数关闭TCP连接,释放TCP协议控制块内存。

代码清单 3921 定时器初始化配置及中断服务函数

1 /* 初始化配置TIM3,使能每10ms发生一次中断 */

2 static void TIM3_Config(uint16_t period,uint16_t prescaler)

3 {

4 TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;

5 NVIC_InitTypeDef NVIC_InitStructure;

6

7 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE); ///使能TIM3时钟

8

9 TIM_TimeBaseInitStructure.TIM_Prescaler=prescaler; //定时器分频

10 TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;

11 TIM_TimeBaseInitStructure.TIM_Period=period; //自动重装载值

12 TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;

13

14 TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);

15

16 TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE); //允许定时器3更新中断

17 TIM_Cmd(TIM3,ENABLE); //使能定时器3

18

19 NVIC_InitStructure.NVIC_IRQChannel=TIM3_IRQn; //定时器3中断

20 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x01;

21 NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x03;

22 NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;

23 NVIC_Init(&NVIC_InitStructure);

24 }

25

26 /* TIM3中断服务函数 */

27 void TIM3_IRQHandler(void)

28 {

29 if (TIM_GetITStatus(TIM3,TIM_IT_Update)==SET) { //溢出中断

30 LocalTime+=10;//10ms增量

31 }

32 TIM_ClearITPendingBit(TIM3,TIM_IT_Update); //清除中断标志位

33 }

LwIP_Periodic_Handle函数执行LwIP需要周期性执行函数,该所以我们需要为该函数提高一个时间基准,这里使用TIM3产生这个基准,初始化配置TIM3每10ms中断一次,在其中断服务函数中递增LocalTime变量值。

代码清单 3922 main函数

1 int main(void)

2 {

3 uint8_t flag=0;

4 /* 初始化LED */

5 LED_GPIO_Config();

6

7 /* 初始化按键 */

8 Key_GPIO_Config();

9

10 /* 初始化调试串口,一般为串口1 */

11 Debug_USART_Config();

12

13 /* 初始化系统滴答定时器 */

14 SysTick_Init();

15

16 TIM3_Config(999,899);//10ms定时器

17 printf("以太网通信实现例程\n");

18

19 /* Configure ethernet (GPIOs, clocks, MAC, DMA) */

20 ETH_BSP_Config();

21 printf("PHY初始化结束\n");

22

23 /* Initilaize the LwIP stack */

24 LwIP_Init();

25

26 printf(" KEY1: 启动TCP连接\n");

27 printf(" KEY2: 断开TCP连接\n");

28

29 /* IP地址和端口可在netconf.h文件修改,或者使用DHCP服务自动获取IP

30 (需要路由器支持)*/

31 printf("本地IP和端口: %d.%d.%d.%d\n",IP_ADDR0,IP_ADDR1,

32 IP_ADDR2,IP_ADDR3);

33 printf("远端IP和端口: %d.%d.%d.%d:%d\n",DEST_IP_ADDR0, DEST_IP_ADDR1,

34 DEST_IP_ADDR2, DEST_IP_ADDR3,DEST_PORT);

35

36 while (1) {

37 if ((Key_Scan(KEY1_GPIO_PORT,KEY1_PIN)==KEY_ON) && (flag==0)) {

38 LED2_ON;

39 if (EthLinkStatus == 0) {

40 printf("connect to tcp server\n");

41 /*connect to tcp server */

42 tcp_echoclient_connect();

43 flag=1;

44 }

45 }

46 if ((Key_Scan(KEY2_GPIO_PORT,KEY2_PIN)==KEY_ON) && flag) {

47 LED2_OFF;

48 tcp_echoclient_disconnect();

49 flag=0;

50 }

51 /* check if any packet received */

52 if (ETH_CheckFrameReceived()) {

53 /* process received ethernet packet */

54 LwIP_Pkt_Handle();

55 }

56 /* handle periodic timers for LwIP */

57 LwIP_Periodic_Handle(LocalTime);

58 }

59 }

首先是初始化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服务器功能,而电脑不行的,最后在电脑端打开网络调试助手软件,并设置相关参数,见图 3920,调试助手的设置与netconf.h文件中相关宏定义是对应的,不同电脑设置情况可能不同。把编译好的程序下载到开发板。

图 3920 调试助手设置界面

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

图 3921 ping窗口

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

图 3922 调试助手接发通信效果

39.9 基于uCOS-III移植LwIP实验

上面的实验是无操作系统移植LwIP,LwIP也确实是支持无操作系统移植运行,这对于芯片资源紧张、不合适运行操作系统环境还是有很大用处的。不过在很多应用中会采用操作系统上运行LwIP,这有利于提高整体性能。这个实验我们主要讲解移植操作步骤,过程中直接使用上个实验LwIP底层驱动,除非有需要修改地方才指出,同时这里假设已有移植好的uCOS-III工程可参考使用,关于uCOS-III移植部分可参考我们相关文档,这里主要介绍LwIP使用uCOS-III信号量、消息队列、定时器函数等等函数接口。

这个实验最终实现在uCOS-III操作系统基础上移植LwIP,使能DHCP功能,在动态获取IP之后即可ping通。运行uCOS-III操作系统之后一般会使用Netconn或Socket方法使用LwIP,关于这两个的应用方法限于篇幅问题这里不做深入探究。

UCOS-III和LwIP都是属于软件编程层次,所以硬件设计部分并不需要做更改,直接使用上个实验的硬件设计即可。

接下来开始介绍移植步骤,为简化移植步骤,我们的思路是直接使用uCOS-III例程,在其基础上移植LwIP部分。

第一步:文件拷贝

拷贝整个uCOS-III工程,修改文件夹名称为"ETH—基于uCOS-III的LwIP移植",作为我们这个实验工程基础,我们在此基础上添加功能。拷贝上个实验工程中的lwip-1.4.1整个文件夹到USER文件夹(路径:…\ETH—基于uCOS-III的LwIP移植\USER)中。

LwIP源码部分,即src文件夹,内容是不用修改的,只有port文件夹内容需要修改。在stsw-stm32070文件夹找到FreeRTOS文件夹(路径:… \Utilities\Third_Party\lwip-1.4.1\port \STM32F4x7\FreeRTOS),该文件夹内容是LwIP与FreeRTOS操作系统连接的相关接口函数,虽然我们选择使用uCOS-III操作系统,当还是有很多可以借鉴的地方,移植过程我们采用修改这些文件方法实现而不是完全自己新建文件,把FreeRTOS整个文件夹拷贝到port文件夹(路径:…\ETH—基于uCOS-III的LwIP移植\USER\lwip-1.4.1\port)内,并改名为UCOS305,此时port文件夹内有三个文件夹,分别为:arch、Standalone、UCOS305,其中Standalone在本实验是不被使用的。

把上个实验工程中的App文件夹拷贝到本实验相同位置,其中tcp_echoclient.c和tcp_echoclient.h文件不是本实验需要的,将其删除。netconf.c、netconf.h和lwipopts.h三个文件是必需的,但因为如果在本实验直接使用lwipopts.h文件需要修改较多地方,我们先将该文件删除,然后在stsw-stm32070文件夹找到httpserver_socket文件夹。(路径:… \Utilities\Third_Party\lwip-1.4.1\port \STM32F4x7\FreeRTOS\httpserver_socket),在该文件夹下inc文件夹中的lwipopts.h文件是更方便我们移植的文件,我们拷贝它到App文件夹中。

最后,把上个实验工程中的ETH文件夹拷贝到本实验相同位置,这个文件夹内容都是必需的,但我们不用进行修改。

第二步:为工程添加文件

与上个工程相比,LwIP部分文件只有port文件夹文件有所修改,其他使用与上个实验相同文件结构皆可,最终工程文件结构参考图 3923。

图 3923 工程文件结构

添加完源文件后,还需要在工程选项中设置添加头文件路径,参考图 3924。

图 3924 添加头文件路径

第三步:文件修改

ETH文件夹内文件,stm32f429_eth.c、stm32f429_eth.h、stm32f429_phy.c和stm32f429_phy.h四个文件是ETH外部和PHY相关驱动,本实验并无需修改硬件,所以这四个文件内容不用修改,stm32f429_eth_conf.h文件是与ETH外设相关硬件宏定义,因为本实验使用操作系统,对延时函数定义与上个实验工程有所不同,需要稍作修改。

代码清单 3923 延时函数定义

1 #ifdef USE_Delay

2 #include "Bsp/bsp.h"

3 #define _eth_delay_ Delay_10ms

4 #else

5 #define _eth_delay_

6 #endif

这里使用在bsp.h文件中定义的Delay_10ms延时函数。

sys_arch.h和sys_arch.c两个文件是LwIP与uCOS-III连接的实现代码。sys_arch.h存放相关宏定义和类型定义。

代码清单 3924 宏定义

1 #define LWIP_STK_SIZE 512

2 #define LWIP_TASK_MAX 8

3

4 #define LWIP_TSK_PRIO 3

5 #define LWIP_TASK_START_PRIO LWIP_TSK_PRIO

6 #define LWIP_TASK_END_PRIO LWIP_TSK_PRIO +LWIP_TASK_MAX

7

8 #define MAX_QUEUES 10 // 消息邮箱的数量

9 #define MAX_QUEUE_ENTRIES 20 // 每个邮箱的大小

10

11 #define SYS_MBOX_NULL (void *)0

12 #define SYS_SEM_NULL (void *)0

13

14 #define sys_arch_mbox_tryfetch(mbox,msg) sys_arch_mbox_fetch(mbox,msg,1)

宏LWIP_STK_SIZE定义LwIP任务栈空间大小,实际空间是4*LWIP_STK_SIZE个字节。宏LWIP_TASK_MAX定义预留给LwIP使用的最大任务数量。LWIP_TSK_PRIO、LWIP_TASK_START_PRIO和LWIP_TASK_END_PRIO三个宏指定LwIP任务的优先级范围。宏MAX_QUEUES定义LwIP可以使用的最大邮箱数量,宏MAX_QUEUE_ENTRIES定义每个邮箱的大小。宏SYS_MBOX_NULL和SYS_SEM_NULL分别定义邮箱和信号量NULL对于的值。sys_arch_mbox_tryfetch函数是尝试获取邮箱内容,这里直接调用sys_arch_mbox_fetch函数实现。

代码清单 3925 类型定义

1 typedef OS_SEM sys_sem_t; // type of semiphores

2 typedef OS_MUTEX sys_mutex_t; // type of mutex

3 typedef OS_Q sys_mbox_t; // type of mailboxes

4 typedef CPU_INT08U sys_thread_t; // type of id of the new thread

5

6 typedef CPU_INT08U sys_prot_t;

不同操作系统有不同名称定义信号量、复合信号、邮箱、任务ID等等,这里使用uCOS-III操作系统需要使用对应的名称。

实际上,除了需要定于与操作系统对应的名称之外,还需要定于与编译器相关的名称,在sys_arch.h文件中有引用了cc.h头文件,因为我们使用Windows操作系统的Keil开发工具,需要对cc.h文件进行必须修改。

代码清单 3926 编译器相关类型定于和宏定义

1 typedef u32_t mem_ptr_t;

2 //typedef int sys_prot_t;

3

4

5 //#define U16_F "hu"

6 //#define S16_F "d"

7 //#define X16_F "hx"

8 //#define U32_F "u"

9 //#define S32_F "d"

10 //#define X32_F "x"

11 //#define SZT_F "uz"

12

13 #define U16_F "4d"

14 #define S16_F "4d"

15 #define X16_F "4x"

16 #define U32_F "8ld"

17 #define S32_F "8ld"

18 #define X32_F "8lx"

sys_prot_t类型已在sys_arch.h文件中定于,在cc.h文件必须注释掉不被使用。U16_F、S16_F、X16_F等等一系列名称用于LwIP的调试函数,这一系列宏定于用于调试信息输出格式化。

代码清单 3927 调试信息输出定于

1 #define LWIP_PLATFORM_DIAG(x) {printf x;}

2

3 #define LWIP_PLATFORM_ASSERT(x) do { printf("Assertion "%s" failed at \

4 line %d in %s\n",x, __LINE__, __FILE__);} while(0)

5

6 #define LWIP_ERROR(message, expression, handler) do { if (!(expression)) { \

7 printf("Assertion "%s" failed at line %d in %s\n", message, \

8 __LINE__, __FILE__); fflush(NULL);handler;} } while(0)

LwIP实现代码已经添加了调试信息功能,我们只需要定于信息输出途径即可,这里直接使用printf函数,将调试信息打印到串口调试助手。

sys_arch.c文件存放uCOS-III与LwIP连接函数,LwIP为实现在操作系统上运行,预留了相关接口函数,不同操作系统使用不同方法实现要求的功能。该文件存放在UCOS305文件夹内。

代码清单 3928 sys_now函数

1 u32_t sys_now()

2 {

3 OS_TICK os_tick_ctr;

4 CPU_SR_ALLOC();

5

6 CPU_CRITICAL_ENTER();

7 os_tick_ctr = OSTickCtr;

8 CPU_CRITICAL_EXIT();

9

10 return os_tick_ctr;

11 }

sys_now函数用于为LwIP提供系统时钟,这里直接的读取OSTickCtr变量值。CPU_CRITICAL_ENTER和CPU_CRITICAL_EXIT分别是关闭总中断和开启总中断。

LwIP的邮箱用于缓存和传递数据包。

代码清单 3929 邮箱创建与删除

1 err_t sys_mbox_new(sys_mbox_t *mbox, int size)

2 {

3 OS_ERR ucErr;

4

5 OSQCreate(mbox,"LWIP quiue", size, &ucErr);

6 LWIP_ASSERT( "OSQCreate ", ucErr == OS_ERR_NONE );

7

8 if ( ucErr == OS_ERR_NONE) {

9 return 0;

10 }

11 return -1;

12 }

13

14 void sys_mbox_free(sys_mbox_t *mbox)

15 {

16 OS_ERR ucErr;

17 LWIP_ASSERT( "sys_mbox_free ", mbox != SYS_MBOX_NULL );

18

19 OSQFlush(mbox,& ucErr);

20

21 OSQDel(mbox, OS_OPT_DEL_ALWAYS, &ucErr);

22 LWIP_ASSERT( "OSQDel ", ucErr == OS_ERR_NONE );

23 }

sys_mbox_new函数要求实现的功能是创建一个邮箱,这里使用OSQCreate函数创建一个队列。sys_mbox_free函数要求实现的功能是释放一个邮箱,如果邮箱存在内容,会发生错误,这里先使用OSQFlush函数清除队列内容,然后再使用OSQDel函数删除队列。LWIP_ASSERT函数是由LwIP定义的断言,用于调试错误。

代码清单 3930 邮箱发送和获取

1 void sys_mbox_post(sys_mbox_t *mbox, void *data)

2 {

3 OS_ERR ucErr;

4 CPU_INT08U i=0;

5 if ( data == NULL ) data = (void*)&pvNullPointer;

6 /* try 10 times */

7 while (i<10) {

8 OSQPost(mbox, data,0,OS_OPT_POST_ALL,&ucErr);

9 if (ucErr == OS_ERR_NONE)

10 break;

11 i++;

12 OSTimeDly(5,OS_OPT_TIME_DLY,&ucErr);

13 }

14 LWIP_ASSERT( "sys_mbox_post error!\n", i !=10 );

15 }

16

17 err_t sys_mbox_trypost(sys_mbox_t *mbox, void *msg)

18 {

19 OS_ERR ucErr;

20 if (msg == NULL ) msg = (void*)&pvNullPointer;

21 OSQPost(mbox, msg,0,OS_OPT_POST_ALL,&ucErr);

22 if (ucErr != OS_ERR_NONE) {

23 return ERR_MEM;

24 }

25 return ERR_OK;

26 }

27

28 u32_t sys_arch_mbox_fetch(sys_mbox_t *mbox, void **msg, u32_t timeout)

29 {

30 OS_ERR ucErr;

31 OS_MSG_SIZE msg_size;

32 CPU_TS ucos_timeout;

33 CPU_TS in_timeout = timeout/LWIP_ARCH_TICK_PER_MS;

34 if (timeout && in_timeout == 0)

35 in_timeout = 1;

36 *msg = OSQPend (mbox,in_timeout,OS_OPT_PEND_BLOCKING,&msg_size,

37 &ucos_timeout,&ucErr);

38

39 if ( ucErr == OS_ERR_TIMEOUT )

40 ucos_timeout = SYS_ARCH_TIMEOUT;

41 return ucos_timeout;

42 }

sys_mbox_post函数要求实现的功能是发送一个邮箱,这里主要调用OSQPost函数实现队列发送,为保证发送成功,最多尝试10次队列发送。sys_mbox_trypost函数是尝试发送一个邮箱,这里我们直接使用OSQPost函数发送一次信号量,而不像sys_mbox_post函数在发送失败时可能尝试发送多次。sys_arch_mbox_fetch函数用于获取邮箱内容,并指定等待超时时间,这里主要通过调用OSQPend函数实现队列获取。

代码清单 3931 邮箱可用性检查和不可用设置

1 int sys_mbox_valid(sys_mbox_t *mbox)

2 {

3 if (mbox->NamePtr)

4 return (strcmp(mbox->NamePtr,"?Q"))? 1:0;

5 else

6 return 0;

7 }

8

9 void sys_mbox_set_invalid(sys_mbox_t *mbox)

10 {

11 if (sys_mbox_valid(mbox))

12 sys_mbox_free(mbox);

13 }

sys_mbox_valid函数要求实现的功能是检查指定的邮箱是否可用,对于uCOSIII,直接调用strcmp函数检查队列名称是否存在"Q"字段,如果存在说明该邮箱可用,否则不可用。sys_mbox_set_invalid函数要求实现的功能是将指定的邮箱设置为不可用(无效),这里先调用sys_mbox_valid函数判断邮箱是可用的,如果本身不可用就无需操作,确定邮箱可用后调用sys_mbox_free函数删除邮箱。

LwIP的信号量用于进程间的通信。

代码清单 3932 新建信号量

1 err_t sys_sem_new(sys_sem_t *sem, u8_t count)

2 {

3 OS_ERR ucErr;

4 OSSemCreate (sem,"LWIP Sem",count,&ucErr);

5 if (ucErr != OS_ERR_NONE ) {

6 LWIP_ASSERT("OSSemCreate ",ucErr == OS_ERR_NONE );

7 return -1;

8 }

9 return 0;

10 }

sys_sem_new函数要求实现的功能是新建一个信号量,这里直接调用OSSemCreate函数新建一个信号量,count参数用于指定信号量初始值。

代码清单 3933 信号量相关函数

1 u32_t sys_arch_sem_wait(sys_sem_t *sem, u32_t timeout)

img img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上物联网嵌入式知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、电子书籍、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取