STM32以太网外设(ETH)介绍和使用、LAN8720A、LwIP

ETH—Lwip以太网通信

TCP/IP五层混合模型

在这里插入图片描述
数据链路层又被分为LLC层(逻辑链路层)和MAC层(媒体介质访问层)。
对于接入网络终端的设备, LLC层和MAC层是软、硬件的分界线。如PC的网卡主要负责实现参考模型中的MAC子层和物理层,在PC的软件系统中则有一套庞大程序实现了LLC层及以上的所有网络层次的协议。

由硬件实现的物理层和MAC子层在不同的网络形式有很大的区别,如以太网和Wi-Fi,这是由物理传输方式决定的。但由软件实现的其它网络层次通常不会有太大区别。

以太网外设(ETH)

STM32F4xx系列控制器内部集成了一个以太网外设,它的功能就是实现MAC层的任务。借助以太网外设,STM32F4xx控制器可以通过ETH外设按照IEEE 802.3-2002标准发送和接收MAC数据包。ETH内部自带专用的DMA控制器用于MAC,ETH支持MII或RMII与PHY芯片传输数据包。ETH还集成了站管理接口(SMI)与外部PHY通信,用于访问PHY芯片寄存器。

物理层定义了以太网使用的传输介质、传输速度、数据编码方式和冲突检测机制,PHY芯片是物理层功能实现的实体,生活中常用水晶头网线+水晶头插座+PHY组合构成了物理层。

在这里插入图片描述
ETH有专用的DMA控制器,它通过AHB与内核/存储器相连,AHB主接口用于控制数据传输,而AHB从接口用于访问“控制与状态寄存器”(CSR)空间。
在进行数据发送时,先将数据由存储器以DMA传输到发送TX FIFO进行缓冲,然后由MAC内核发送;接收数据时,RX FIFO先接收以太网数据帧,再由DMA传输至存储器。

SMI接口

SMI是MAC内核访问PHY寄存器标志接口,它由两根线组成,数据线MDIO和时钟线MDC。
SMI支持访问32个PHY,这在设备需要多个网口时非常有用,不过一般设备都只使用一个PHY。PHY芯片内部一般都有32个16位的寄存器,用于配置PHY芯片属性、工作环境、状态指示等等,当然很多PHY芯片并没有使用到所有寄存器位。
MAC内核就是通过SMI向PHY的寄存器写入数据或从PHY寄存器读取PHY状态,一次只能对一个PHY的其中一个寄存器进行访问。
SMI最大通信频率为2.5MHz,通过控制以太网MAC MII地址寄存器 (ETH_MACMIIAR)的CR位可选择时钟频率。

SMI帧格式

SMI是通过数据帧方式与PHY通信的,帧格式如下,数据位传输顺序从左到右。
在这里插入图片描述
PADDR用于指定PHY地址,每个PHY都有一个地址,一般由PHY硬件设计决定,所以是固定不变的。RADDR用于指定PHY寄存器地址。
TA为状态转换域,若为读操作,MAC输出两个位高阻态,而PHY芯片则在第一位时输出高阻态,第二位时输出“0”。
若为写操作,MAC输出“10”,PHY芯片则输出高阻态。
数据段有16位,对应PHY寄存器每个位,先发送或接收到的位对应以太网 MAC MII 数据寄存器(ETH_MACMIIDR)寄存器的位15。

SMI读写操作

当以太网MAC MII地址寄存器 (ETH_MACMIIAR)的写入位和繁忙位被置1时,SMI将向指定的PHY芯片的指定寄存器写入ETH_MACMIIDR中的数据。
在这里插入图片描述
当以太网MAC MII地址寄存器 (ETH_MACMIIAR)的写入位为0并且繁忙位被置1时,SMI将从向指定的PHY芯片指定寄存器读取数据到ETH_MACMIIDR内。
在这里插入图片描述

MII和RMII接口

MII用于连接MAC控制器和PHY芯片,提供数据传输路径。 RMII接口是MII接口的简化版本,MII需要16根通信线,RMII只需7根通信, 在功能上是相同的。
在这里插入图片描述

  • TX_CLK:数据发送时钟线。标称速率为10Mbit/s时为2.5MHz;速率为100Mbit/s时为25MHz。RMII接口没有该线。

  • RX_CLK:数据接收时钟线。标称速率为10Mbit/s时为2.5MHz;速率为100Mbit/s时为25MHz。RMII接口没有该线。

  • TX_EN:数据发送使能。在整个数据发送过程保持有效电平。

  • TXD[3:0]或TXD[1:0]:数据发送数据线。对于MII有4位,RMII只有2位。只有在TX_EN处于有效电平数据线才有效。

  • CRS:载波侦听信号,由PHY芯片负责驱动,当发送或接收介质处于非空闲状态时使能该信号。在全双工模式该信号线无效。

  • COL:冲突检测信号,由PHY芯片负责驱动,检测到介质上存在冲突后该线被使能,并且保持至冲突解除。在全双工模式该信号线无效。

  • RXD[3:0]或RXD[1:0]:数据接收数据线,由PHY芯片负责驱动。对于MII有4位, RMII只有2位。在MII模式,当RX_DV禁止、RX_ER使能时,特定的RXD[3:0]值用于传输来自PHY的特定信息。

  • RX_DV:接收数据有效信号,功能类似TX_EN,只不过用于数据接收,由PHY芯片负责驱动。 对于RMII接口,是把CRS和RX_DV整合成CRS_DV信号线,当介质处于不同状态时会自切换该信号状态。

  • RX_ER:接收错误信号线,由PHY驱动,向MAC控制器报告在帧某处检测到错误。

  • REF_CLK:仅用于RMII接口,由外部时钟源提供50MHz参考时钟。

因为要达到100Mbit/s传输速度,MII和RMII数据线数量不同,使用MII和RMII在时钟线的设计是完全不同的。对于MII接口,一般是外部为PHY提供25MHz时钟源,再由PHY提供TX_CLK和RX_CLK时钟。对于RMII接口,一般需要外部直接提供50MHz时钟源,同时接入MAC和PHY。

ETH相关硬件在STM32F4xx控制器分布参考表
在这里插入图片描述

PPS_OUT是IEEE 1588定义的一个时钟同步机制。

MAC数据包发送和接收

ETH外设负责MAC数据包发送和接收。
利用DMA从系统寄存器得到数据包数据内容,ETH外设自动填充完成MAC数据包封装,然后通过PHY发送出去。
在检测到有MAC数据包需要接收时,ETH外设控制数据接收,并解封MAC数据包得到解封后数据通过DMA传输到系统寄存器内。

MAC数据包发送

MAC数据帧发送全部由DMA控制,从系统存储器读取的以太网帧由DMA推入FIFO,然后将帧弹出并传输到MAC内核。帧传输结束后,从MAC内核获取发送状态并传回DMA。
在检测到SOF(Start Of Frame)时,MAC接收数据并开始MII发送。在EOF(End Of Frame)传输到MAC内核后,内核将完成正常的发送,然后将发送状态返回给DMA。如果在发送过程中发送常规冲突,MAC内核将使发送状态有效,然后接受并丢弃所有后续数据,直至收到下一SOF。检测到来自MAC的重试请求时,应从SOF重新发送同一帧。如果发送期间未连续提供数据,MAC将发出下溢状态。在帧的正常传输期间,如果MAC在未获得前一帧的EOF的情况下接收到SOF,则将忽略该SOF并将新的帧视为前一帧的延续。

MAC控制MAC数据包的发送操作,它会自动生成前导字段和SFD以及发送帧状态返回给DMA,在半双工模式下自动生成阻塞信号,控制jabber(MAC看门狗)定时器用于在传输字节超过2048字节时切断数据包发送。在半双工模式下,MAC使用延迟机制进行流量控制,程序通过将ETH_MACFCR寄存器的BPA位置1来请求流量控制。MAC包含符合IEEE 1588的时间戳快照逻辑。

MAC数据包发送时序(无冲突):
在这里插入图片描述

MAC数据包接收

MAC接收到的数据包填充RX FIFO,达到FIFO设定阈值后请求DMA传输。在默认直通模式下,当FIFO接收到64个字节(使用ETH_DMAOMR寄存器中的RTC位配置)或完整的数据包时,数据将弹出,其可用性将通知给DMA。DMA向AHB接口发起传输后,数据传输将从FIFO持续进行,直到传输完整个数据包。完成EOF帧的传输后,状态字将弹出并发送到DMA控制器。在Rx FIFO存储转发模式(通过ETH_DMAOMR寄存器中的RSF位配置)下,仅在帧完全写入Rx FIFO后才可读出帧。

当MAC在MII上检测到SFD时,将启动接收操作。MAC内核将去除报头和SFD,然后再继续处理帧。检查报头字段以进行过滤,FCS字段用于验证帧的CRC如果帧未通过地址滤波器,则在内核中丢弃该帧。

MAC数据包接收时序(无错误):
在这里插入图片描述

MAC过滤

MAC过滤功能可以选择性的过滤设定目标地址或源地址的MAC帧。它将检查所有接收到的数据帧的目标地址和源地址,根据过滤选择设定情况,检测后报告过滤状态。针对目标地址过滤可以有三种,分别是单播、多播和广播目标地址过滤;针对源地址过滤就只有单播源地址过滤。

单播目标地址过滤是将接收的相应DA字段与预设的以太网MAC地址寄存器内容比较,最高可预设4个过滤MAC地址。多播目标地址过滤是根据帧过滤寄存器中的HM位执行对多播地址的过滤,是对MAC地址寄存器进行比较来实现的。单播和多播目标地址过滤都还支持Hash过滤模式。广播目标地址过滤通过将帧过滤寄存器的BFD位置1使能,这使得MAC丢弃所有广播帧。

单播源地址过滤是将接收的SA字段与SA寄存器内容进行比较过滤。

MAC过滤还具备反向过滤操作功能,即让过滤结构求补集。

PHY:LAN8720A

LAN8720A是SMSC公司(已被Microchip公司收购)设计的一个体积小、功耗低、全能型10/100Mbps的以太网物理层收发器。它是针对消费类电子和企业应用而设计的。LAN8720A总共只有24Pin,仅支持RMII接口。
在这里插入图片描述
LAN8720A通过RMII与MAC连接。RJ45是网络插座,在与LAN8720A连接之间还需要一个变压器,所以一般使用带电压转换和LED指示灯的HY911105A型号的插座。一般来说,必须为使用RMII接口的PHY提供50MHz的时钟源输入到REF_CLK引脚,不过LAN8720A内部集成PLL,可以将25MHz的时钟源倍频到50MHz并在指定引脚输出该时钟,所以我们可以直接使其与REF_CLK连接达到提供50MHz时钟的效果。
在这里插入图片描述
LAN8720A由各个不同功能模块组成,最重要的是接收控制器、发送控制器,其它的基本上都是与外部引脚挂钩,实现信号传输。部分引脚是具有双重功能的,比如PHYAD0与RXER引脚是共用的,在系统上电后LAN8720A会马上读取这部分共用引脚的电平,以确定系统的状态并保存在相关寄存器内,之后则自动转入作为另一功能引脚。

PHYAD[0]引脚用于配置SMI通信的LAN8720A地址,在芯片内部该引脚已经自带下拉电阻,默认认为0(即使外部悬空不接),在系统上电时会检测该引脚获取得到LAN8720A的地址为0或者1,并保存在特殊模式寄存器(R18)的PHYAD位中,该寄存器的PHYAD有5个位,在需要超过2个LAN8720A时可以通过软件设置不同SMI通信地址。PHYAD[0]是与RXER引脚共用。

MODE[2:0]引脚用于选择LAN8720A网络通信速率和工作模式,可选10Mbps或100Mbps通信速度,半双工或全双工工作模式,另外LAN8720A支持HP Auto-MDIX自动翻转功能,即可自动识别直连或交叉网线并自适应。一般将MODE引脚都设置为1,可以让LAN8720A启动自适应功能,它会自动寻找最优工作方式。MODE[0]与RXD0引脚共用、MODE[1]与RXD1引脚共用、MODE[2]与CRS_DV引脚共用。

nINT/REFCLKO引脚用于RMII接口中REF_CLK信号线,当nINTSEL引脚为低电平时,它也可以被设置成50MHz时钟输出,这样可以直接与STM32F4xx的REF_CLK引脚连接为其提供50MHz时钟源,这种模式要求为XTAL1与XTAL2之间或为XTAL1/CLKIN提供25MHz时钟,由LAN8720A内部PLL电路倍频得到50MHz时钟,此时nIN/REFCLKO引脚的中断功能不可用,用于50MHz时钟输出。当nINTSEL引脚为高电平时,LAN8720A被设置为时钟输入,即外部时钟源直接提供50MHz时钟接入STM32F4xx的REF_CLK引脚和LAN8720A的XTAL1/CLKIN引脚,此时nINT/REFCLKO可用于中断功能。nINTSEL与LED2引脚共用,一般使用下拉。

REGOFF引脚用于配置内部+1.2V电压源,LAN8720A内部需要+1.2V电压,可以通过VDDCR引脚输入+1.2V电压提供,也可以直接利用LAN8720A内部+1.2V稳压器提供。当REGOFF引脚为低电平时选择内部+1.2V稳压器。REGOFF与LED1引脚共用。

SMI支持寻址32个寄存器,LAN8720A只用到其中14个。

序号寄存器名称分组
0Basic Control RegisterBasic
1Basic Status RegisterBasic
2PHY Identifier 1Extended
3PHY Identifier 2Extended
4Auto-Negotiation Advertisement RegisterExtended
5Auto-Negotiation Link Partner Ability RegisterExtended
6Auto-Negotiation Expansion RegisterExtended
17Mode Control/Status RegisterVendor-specific
18Special ModesVendor-specific
26Symbol Error Counter RegisterVendor-specific
27Control / Status Indication RegisterVendor-specific
29Interrupt Source RegisterVendor-specific
30Interrupt Mask RegisterVendor-specific
31PHY Special Control/Status RegisterVendor-specific

序号与SMI数据帧中的RADDR是对应的,这在编写驱动时非常重要,本文将它们标记为R0~R31。
寄存器可规划为三个组:Basic、Extended和Vendor-specific。Basic是IEEE 802.3要求的,R0是基本控制寄存器,其位15为Soft Reset位,向该位写1启动LAN8720A软件复位,还包括速度、自适应、低功耗等等功能设置。R1是基本状态寄存器。Extended是扩展寄存器,包括LAN8720A的ID号、制造商、版本号等等信息。Vendor-specific是供应商自定义寄存器,R31是特殊控制/状态寄存器,指示速度类型和自适应功能。

LwIP:轻型TCP/IP协议栈

LwIP是Light Weight Internet Protocol 的缩写,是由瑞士计算机科学院Adam Dunkels等开发的适用于嵌入式领域的开源轻量级TCP/IP协议栈。它可以移植到含有操作系统的平台中,也可以在无操作系统的平台下运行。由于它开源、占用的RAM和ROM比较少、支持较为完整的TCP/IP协议、且十分便于裁剪、调试,被广泛应用在中低端的32位控制器平台。可以访问网站:http://savannah.nongnu.org/projects/lwip/ 获取更多LwIP信息。

目前,LwIP最新更新到1.4.1版本,我们在上述网站可找到相应的LwIP源码下载通道。我们下载两个压缩包:lwip-1.4.1.zip和contrib-1.4.1.zip,lwip-1.4.1.zip包括了LwIP的实现代码,contrib-1.4.1.zip包含了不同平台移植LwIP的驱动代码和使用LwIP实现的一些应用实例测试。

但是,遗憾的是contrib-1.4.1.zip并没有为STM32平台提供实例,这对于初学者想要移植LwIP来说难度还是非常大的。ST公司也是认识到LwIP在嵌入式领域的重要性,所以他们针对LwIP应用开发了测试平台,其中有一个是在STM32F4x7系列控制器运行的(文件编号为:STSW-STM32070)。为减少移植工作量,我们选择使用ST官方例程相关文件,特别是ETH底层驱动部分函数,这样我们也可以花更多精力在理解代码实现方法上。

ETH初始化结构体详解

STM32 HAL库为ETH外设提供了专用的初始化结构体,通过配置结构体成员,由HAL库函数自动设置ETH相关寄存器,简化外设配置流程。相关结构体定义在stm32f4xx_hal_eth.hstm32f4xx_hal_eth.c中。

ETH_InitTypeDef(ETH核心初始化结构体)


typedef struct{
    uint32_t AutoNegotiation;    // 自适应功能
    uint32_t Speed;              // 以太网速度
    uint32_t DuplexMode;         // 以太网工作模式选择
    uint16_t PhyAddress;         // 以太网PHY地址
    uint8_t * MACAddr;           // MAC地址指针
    uint32_t RxMode;             // 以太网接收模式
    uint32_t ChecksumMode;       // 检查校验和模式
    uint32_t MediaInterface;     // 以太网介质接口
}ETH_InitTypeDef;
  • AutoNegotiation:使能/禁止自适应,一般使能,自动匹配10/100Mbps速率和半/全双工模式;

  • Speed:以太网速度选择,可选10Mbps或100Mbit/s,一般设置100Mbit/s。配置ETH_MACCR寄存器FES位。自适应使能后该配置无效。

  • DuplexMode:以太网工作模式选择,可选全双工模式或半双工模式,一般选择全双工模式。配置ETH_MACCR寄存器DM位。自适应使能后该配置无效。

  • PhyAddress:以太网PHY地址,取值范围为0~32。该字段指示正在访问 32 个可能的 PHY 器件中的哪一个。LAN8720A默认0。

  • MACAddr:6字节MAC地址数组的指针,需自定义唯一地址;

  • RxMode:以太网接收接收模式,可以是轮询模式或者中断模式。

  • ChecksumMode:检查校验和模式,可以是硬件校验或者软件校验。

  • MediaInterface:MII/RMII接口,LAN8720A选择RMII。

ETH_MACInitTypeDef(MAC层初始化结构体)


typedef struct{
    uint32_t Watchdog;                // 以太网看门狗
    uint32_t Jabber;                  // jabber定时器功能
    uint32_t InterFrameGap;           // 发送帧间间隙
    uint32_t CarrierSense;            // 载波侦听
    uint32_t ReceiveOwn;              // 接收自身
    uint32_t LoopbackMode;            // 回送模式
    uint32_t ChecksumOffload;         // 校验和减荷
    uint32_t RetryTransmission;       // 传输重试
    uint32_t AutomaticPadCRCStrip;    // 自动去除PAD和FCS字段
    uint32_t BackOffLimit;            // 后退限制
    uint32_t DeferralCheck;           // 检查延迟
    uint32_t ReceiveAll;              // 接收所有MAC帧
    uint32_t SourceAddrFilter;        // 源地址过滤
    uint32_t PassControlFrames;       // 传送控制帧
    uint32_t BroadcastFramesReception;// 广播帧接收
    uint32_t DestinationAddrFilter;   // 目标地址过滤
    uint32_t PromiscuousMode;         // 混合模式
    uint32_t MulticastFramesFilter;   // 多播源地址过滤
    uint32_t UnicastFramesFilter;     // 单播源地址过滤
    uint32_t HashTableHigh;           // 散列表高位
    uint32_t HashTableLow;            // 散列表低位
    uint32_t PauseTime;               // 暂停时间
    uint32_t ZeroQuantaPause;         // 零时间片暂停
    uint32_t PauseLowThreshold;       // 暂停阈值下限
    uint32_t UnicastPauseFrameDetect; // 单播暂停帧检测
    uint32_t ReceiveFlowControl;      // 接收流控制
    uint32_t TransmitFlowControl;     // 发送流控制
    uint32_t VLANTagComparison;       // VLAN标记比较
    uint32_t VLANTagIdentifier;       // VLAN标记标识符
}ETH_MACInitTypeDef;
  • Watchdog:以太网看门狗功能选择,可选使能或禁止,它设定以太网MAC配置寄存器(ETH_MACCR)的WD位的值。 如果设置为1,使能看门狗,在接收MAC帧超过2048字节时自动切断后面数据,一般选择使能看门狗。 如果设置为0,禁用看门狗,最长可接收16384字节的帧。
  • Jabber:jabber定时器功能选择,可选使能或禁止,与看门狗功能类似,只是看门狗用于接收MAC帧, jabber定时器用于发送MAC帧,它设定ETH_MACCR寄存器的JD位的值。如果设置为1,使能jabber定时器, 在发送MAC帧超过2048字节时自动切断后面数据,一般选择使能jabber定时器。
  • InterFrameGap:控制发送帧间的最小间隙,可选96bit时间、88bit时间、…、40bit时间, 他设定ETH_MACCR寄存器的IFG[2:0]位的值,一般设置96bit时间。
  • CarrierSense:载波侦听功能选择,可选使能或禁止,它设定ETH_MACCR寄存器的CSD位的值。 当被设置为低电平时,MAC发送器会生成载波侦听错误,一般使能载波侦听功能。
  • ReceiveOwn:接收自身帧功能选择,可选使能或禁止,它设定ETH_MACCR寄存器的ROD位的值, 当设置为0时,MAC接收发送时PHY提供的所有MAC包,如果设置为1,MAC禁止在半双工模式下接收帧。一般使能接收。
  • LoopbackMode:回送模式选择,可选使能或禁止,它设定ETH_MACCR寄存器的LM位的值,当设置为1时, 使能MAC在MII回送模式下工作。
  • ChecksumOffload:IPv4校验和减荷功能选择,可选使能或禁止,它设定ETH_MACCR寄存器IPCO位的值, 当该位被置1时使能接收的帧有效载荷的TCP/UDP/ICMP标头的IPv4校验和检查。一般选择禁用,此时PCE和IPHCE状态位总是为0。
  • RetryTransmission:传输重试功能,可选使能或禁止,它设定ETH_MACCR寄存器RD位的值, 当被设置为1时,MAC仅尝试发送一次,设置为0时,MAC会尝试根据BL的设置进行重试。一般选择使能重试。
  • AutomaticPadCRCStrip:自动去除PAD和FCS字段功能,可选使能或禁用,它设定ETH_MACCR寄存器APCS位的值。 当设置为1时,MAC在长度字段值小于或等于1500自己是去除传入帧上的PAD和FCS字段。一般禁止自动去除PAD和FCS字段功能。
  • BackOffLimit:后退限制,在发送冲突后重新安排发送的延迟时间,可选10、8、4、1, 它设定ETH_MACCR寄存器BL位的值。一般设置为10。
  • DeferralCheck:检查延迟,可选使能或禁止,它设定ETH_MACCR寄存器DC位的值,当设置为0时, 禁止延迟检查功能,MAC发送延迟,直到CRS信号变成无效信号。
  • ReceiveAll:接收所有MAC帧,可选使能或禁用,它设定以太网MAC帧过滤寄存器(ETH_MACFFR)RA位的值。 当设置为1时,MAC接收器将所有接收的帧传送到应用程序,不过滤地址。当设置为0是,MAC接收会自动过滤不与SA/DA匹配的帧。一般选择不接收所有。
  • SourceAddrFilter:源地址过滤,可选源地址过滤、源地址反向过滤或禁用源地址过滤, 它设定ETH_MACFFR寄存器SAF位和SAIF位的值。一般选择禁用源地址过滤。
  • PassControlFrames:传送控制帧,控制所有控制帧的转发,可选阻止所有控制帧到达应用程序、 转发所有控制帧、转发通过地址过滤的控制帧,它设定ETH_MACFFR寄存器PCF位的值。一般选择禁止转发控制帧。
  • BroadcastFramesReception:广播帧接收,可选使能或禁止,它设定ETH_MACFFR寄存器BFD位的值。 当设置为0时,使能广播帧接收,一般设置接收广播帧。
  • DestinationAddrFilter:目标地址过滤功能选择,可选正常过滤或目标地址反向过滤, 它设定ETH_MACFFR寄存器DAIF位的值。一般设置为正常过滤。
  • PromiscuousMode:混合模式,可选使能或禁用,它设定ETH_MACFFR寄存器PM位的值。 当设置为1时,不论目标或源地址,地址过滤器都传送所有传入的帧。一般禁用混合模式。
  • MulticastFramesFilter:多播源地址过滤,可选完美散列表过滤、散列表过滤、完美过滤或禁用过滤, 它设定ETH_MACFFR寄存器HPF位、PAM位和HM位的值。一般选择完美过滤。
  • UnicastFramesFilter:单播源地址过滤,可选完美散列表过滤、散列表过滤或完美过滤, 它设定ETH_MACFFR寄存器HPF位和HU位的值。一般选择完美过滤。
  • HashTableHigh:散列表高位,和HashTableLow组成64位散列表用于组地址过滤, 它设定以太网MAC散列表高位寄存器(ETH_MACHTHR)的值。
  • HashTableLow:散列表低位,和HashTableHigh组成64位散列表用于组地址过滤, 它设定以太网MAC散列表低位寄存器(ETH_MACHTLR)的值。
  • PauseTime:暂停时间,保留发送控制帧中暂停时间字段要使用的值,可设置0至65535, 它设定以太网MAC流控制寄存器(ETH_MACFCR)PT位的值。
  • ZeroQuantaPause:零时间片暂停,可选使用或禁止,它设定ETH_MACFCR寄存器ZQPD位的值。 当设置为1时,当来自FIFO层的流控制信号去断言后,此位会禁止自动生成零时间片暂停控制帧。一般选择禁止。
  • PauseLowThreshold:暂停阈值下限,配置暂停定时器的阈值,达到该值值时, 会自动程序传输暂停帧,可选暂停时间减去4个间隙、28个间隙、144个间隙或256个间隙, 它设定ETH_MACFCR寄存器PLT位的值。一般选择暂停时间减去4个间隙。
  • UnicastPauseFrameDetect:单播暂停帧检测,可选使能或禁止,它设定ETH_MACFCR寄存器UPFD位的值。 当设置为1时,MAC除了检测具有唯一多播地址的暂停帧外,还会检测具有ETH_MACA0HR和ETH_MACA0LR寄存器所指定的站单播地址的暂停帧。一般设置为禁止。
  • ReceiveFlowControl:接收流控制,可选使能或禁止,它设定ETH_MACFCR寄存器RFCE位的值。 当设定为1时,MAC对接收到的暂停帧进行解码,并禁止其在指定时间(暂停时间)内发送;当设置为0时, 将禁止暂停帧的解码功能,一般设置为禁止。
  • TransmitFlowControl:发送流控制,可选使能或禁止,它设定ETH_MACFCR寄存器TFCE位的值。 在全双工模式下,当设置为1时,MAC将使能流控制操作来发送暂停帧;为0时,将禁止MAC中的流控制操作, MAC不会传送任何暂停帧。在半双工模式下,当设置为1时,MAC将使能背压操作;为0时,将禁止背压功能。
  • VLANTagComparison:VLAN标记比较,可选12位或16位,它设定以太网MAC VLAN标记寄存器(ETH_MACVLANTR)VLANTC位的值。当设置为1时,使用12位VLAN标识符而不是完整的16位VLAN标记进行比较和过滤;为0时,使用全部16位进行比较,一般选择16位。
  • VLANTagIdentifier:VLAN标记标识符,包含用于标识VLAN帧的802.1Q VLAN标记,并与正在接收的VLAN帧的第十五和第十六字节进行比较。位[15:13]是用户优先级,位[12]是标准格式指示符(CFI),位[11:0]是VLAN标记的VLAN标识符(VID)字段。VLANTC位置1时,仅使用VID(位[11:0])进行比较。

ETH_DMAInitTypeDef(DMA层初始化结构体)


typedef struct{
    uint32_t DropTCPIPChecksumErrorFrame; //丢弃TCP/IP校验错误帧
    uint32_t ReceiveStoreForward;         // 接收存储并转发
    uint32_t FlushReceivedFrame;          // 刷新接收帧
    uint32_t TransmitStoreForward;        // 发送存储并转发
    uint32_t TransmitThresholdControl;    // 发送阈值控制
    uint32_t ForwardErrorFrames;          // 转发错误帧
    uint32_t ForwardUndersizedGoodFrames; // 转发过小的好帧
    uint32_t ReceiveThresholdControl;     // 接收阈值控制
    uint32_t SecondFrameOperate;          // 处理第二个帧
    uint32_t AddressAlignedBeats;         // 地址对齐节拍
    uint32_t FixedBurst;                  // 固定突发
    uint32_t RxDMABurstLength;            // DMA突发接收长度
    uint32_t TxDMABurstLength;            // DMA突发发送长度
    uint32_t EnhancedDescriptorFormat;    // 增强描述符格式
    uint32_t DescriptorSkipLength;        // 描述符跳过长度
    uint32_t DMAArbitration;              // DMA仲裁
} ETH_DMAInitTypeDef;
  • DropTCPIPChecksumErrorFrame:丢弃TCP/IP校验错误帧,可选使能或禁止, 它设定以太网DMA工作模式寄存器(ETH_DMAOMR)DTCEFD位的值,当设置为 1时,如果帧中仅存在由接收校验和减荷引擎检测出来的错误,则内核不会丢弃它;为0时,如果FEF为进行了复位,则会丢弃所有错误帧。
  • ReceiveStoreForward:接收存储并转发,可选使能或禁止, 它设定以太网DMA工作模式寄存器(ETH_DMAOMR)RSF位的值,当设置为1时,向RX FIFO写入完整帧后可以从中读取一帧,同时忽略接收阈值控制(RTC)位;当设置为0时,RX FIFO在直通模式下工作,取决于RTC位的阈值。一般选择使能。
  • FlushReceivedFrame:刷新接收帧,可选使能或禁止,它设定ETH_DMAOMR寄存器FTF位的值, 当设置为1时,发送FIFO控制器逻辑会恢复到缺省值,TX FIFO中的所有数据均会丢失/刷新,刷新结束后改为自动清零。
  • TransmitStoreForward:发送存储并并转发,可选使能或禁止, 它设定ETH_DMAOMR寄存器TSF位的值,当设置为1时,如果TX FIFO有一个完整的帧则发送会启动,会忽略TTC值;为0时,TTC值才会有效。一般选择使能。
  • TransmitThresholdControl:发送阈值控制,有多个阈值可选,它设定ETH_DMAOMR寄存器TTC位的值,当TX FIFO中帧大小大于该阈值时发送会自动,对于小于阈值的全帧也会发送。
  • ForwardErrorFrames:转发错误帧,可选使能或禁止,它设定ETH_DMAOMR寄存器FEF位的值, 当设置为1时,除了段错误帧之外所有帧都会转发到DMA;为0时,RX FIFO会丢弃滴啊有错误状态的帧。一般选择禁止。
  • ForwardUndersizedGoodFrames:转发过小的好帧,可选使能或禁止, 它设定ETH_DMAOMR寄存器FUGF位的值,当设置为1时,RX FIFO会转发包括PAD和FCS字段的过小帧;为0时,会丢弃小于64字节的帧,除非接收阈值被设置为更低。
  • ReceiveThresholdControl:接收阈值控制,当RX FIFO中的帧大小大于阈值时启动DMA传输请求,可选64字节、32字节、96字节或128字节, 它设定ETH_DMAOMR寄存器RTC位的值。
  • SecondFrameOperate:处理第二个帧,可选使能或禁止,它设定ETH_DMAOMR寄存器OSF位的值, 当设置为1时会命令DMA处理第二个发送数据帧。
  • AddressAlignedBeats:地址对齐节拍,可选使能或禁止,它设定以太网DMA总线模式寄存器(ETH_DMABMR)AAB位的值, 当设置为1并且固定突发位(FB)也为1时,AHB接口会生成与起始地址LS位对齐的所有突发; 如果FB位为0,则第一个突发不对齐,但后续的突发与地址对齐。一般选择使能。
  • FixedBurst:固定突发,控制AHB主接口是否执行固定突发传输,可选使能或禁止, 它设定ETH_DMABMR寄存器FB位的值,当设置为1时,AHB在正常突发传输开始期间使用SINGLE、 INCR4、INCR8或INCR16;为0时,AHB使用SINGLE和INCR突发传输操作。
  • RxDMABurstLength:DMA突发接收长度,有多个值可选,一般选择32Beat, 可实现32*32bits突发长度,它设定ETH_DMABMR寄存器FPM位和RDP位的值。
  • TxDMABurstLength:DMA突发发送长度,有多个值可选,一般选择32Beat, 可实现32*32bits突发长度,它设定ETH_DMABMR寄存器FPM位和PBL位的值。
  • EnhancedDescriptorFormat:增强描述符格式,可以使能或者禁止。该位置 1 时,使能增强描述符格式,并将描述符大小增加至 32 字节(8 个 DWORD)。如果已激活时间戳功能(ETH_PTPTSCR 位 0 TSE=1)或 IPv4 校验和减荷(ETH_MACCR 位10 IPCO=1),则必须使用此增强描述符。
  • DescriptorSkipLength:描述符跳过长度,指定两个未链接描述符之间跳过的字数, 地址从当前描述符结束处开始跳到下一个描述符起始处,可选0~7,它设定ETH_DMABMR寄存器DSL位的值。
  • 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位给出。

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

硬件设计

在这里插入图片描述

  1. nINTSEL引脚下拉:配置nINT/REFCLKO为50MHz时钟输出,为STM32提供REF_CLK;

  2. REGOFF引脚下拉:启用LAN8720A内部+1.2V稳压器,无需外部供电;

  3. XTAL1/XTAL2接入25MHz晶振:经LAN8720A内部PLL倍频为50MHz,供RMII接口使用;

  4. RJ45使用HY911105A:带变压器和LED指示灯,指示链路和速度状态;

  5. ETH引脚配置:严格遵循STM32F4xx的RMII复用引脚定义(PA1/PA2/PA7/PC1/PC4/PC5/PG11/PG13/PG14)。

移植步骤

移植基于lwip-1.4.1.zipSTSW-STM32070.zip两份资料,核心是拷贝源码、添加工程文件、修改硬件适配代码,实现LwIP与STM32 ETH外设的对接。
在这里插入图片描述

第一步:相关文件拷贝

解压lwip-1.4.1.zip和stsw-stm32070.zip两个压缩包,把整个lwip-1.4.1文件夹拷贝到USER文件夹下
在stsw-stm32070文件夹找到port文件夹(路径:… \UtilitiesThird_Partylwip-1.4.1port),把整个port文件夹拷贝lwip-1.4.1文件夹中,在port文件夹下的STM32F4x7文件中把arch和Standalone两个文件夹直接剪切到port文件夹中,即此时port文件夹有三个STM32F4x7、arch和Standalone文件夹,最后把STM32F4x7文件夹删除,最终的文件结构见 图36_1_16,arch存放与开发平台相关头文件,Standalone文件夹是无操作系统移植时ETH外设与LwIP连接的底层驱动函数。
在这里插入图片描述
lwip-1.4.1文件夹下的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驱动文件,类似HAL库中外设驱动代码实现文件,在移植过程中我们几乎不过文件的内容。这部分函数由port文件夹相关代码调用。另外一部分是相关GPIO初始化、ETH外设初始化、PHY状态获取等等函数的实现,在stsw-stm32070文件夹中找到stm32f4x7_eth_bsp.c、stm32f4x7_eth_bsp.h和stm32f4x7_eth_conf.h三个文件(路径:…ProjectStandalonetcp_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五个文件(路径:…ProjectStandalonetcp_echo_client),直接拷贝到App文件夹(自己新建)中,netconf.c文件代码实现LwIP初始化函数、周期调用函数、DHCP功能函数等等,tcp_echoclient.c文件实现TCP通信参数代码,lwipopts.h包含LwIP功能选项。

第二步:为工程添加文件

第一步已经把相关的文件拷贝到对应的文件夹中,接下来就可以把需要用到的文件添加到工程中。 图36_1_15 已经指示出来工程需要用到的*.c文件,所以最终工程文件结构见 图36_1_17,图中api、ipv4和core都包含了对应文件夹下的所有*.c文件。
在这里插入图片描述
接下来,还需要在工程选择中添加相关头文件路径
在这里插入图片描述

第三步:文件修改

ethernetif.c文件是无操作系统时网络接口函数,该文件在移植时需要根据实际硬件初始化网络相关IO口,以及需要指定的SRAM空间作为缓存。该文件主要有三个部分函数,HAL_ETH_MspInit函数用于初始化系统硬件接口; low_level_init函数用于初始化MAC相关工作环境、初始化DMA描述符链表,并使能MAC和DMA; low_level_output函数是最底层发送一帧数据函数; low_level_input函数是最底层接收一帧数据函数。sys_now函数获取当前时间的一个函数;ethernetif_init函数初始化网络接口结构 (netif)并调用 low_level_init 以初始化以太网外设;ethernet_input函数调用 low_level_input 接收包,然后将其提供给 LwIP 栈。

app_ethernet.c文件主要是实际的网络初始化应用程序,这里包含两个函数,Netif_Config函数是创建一个网络接口;User_notification函数是指示当前网络连接的状态。

LAN8720A.h和LAN8720A.c两个文件是ETH外设相关的底层配置,主要是 GPIO初始化即相关时钟使能。

void ETH_GPIO_Config(void)
{
   GPIO_InitTypeDef GPIO_InitStructure;
   /* 使能端口时钟 */
   ETH_MDIO_GPIO_CLK_ENABLE();
   ETH_MDC_GPIO_CLK_ENABLE();
   ETH_RMII_REF_CLK_GPIO_CLK_ENABLE();
   ETH_RMII_CRS_DV_GPIO_CLK_ENABLE();
   ETH_RMII_RXD0_GPIO_CLK_ENABLE();
   ETH_RMII_RXD1_GPIO_CLK_ENABLE();
   ETH_RMII_TX_EN_GPIO_CLK_ENABLE();
   ETH_RMII_TXD0_GPIO_CLK_ENABLE();
   ETH_RMII_TXD1_GPIO_CLK_ENABLE();

   /* 配置以太网引脚*/
   /*
   ETH_MDIO -------------------------> PA2
   ETH_MDC --------------------------> PC1
   ETH_MII_RX_CLK/ETH_RMII_REF_CLK---> PA1
   ETH_MII_RX_DV/ETH_RMII_CRS_DV ----> PA7
   ETH_MII_RXD0/ETH_RMII_RXD0 -------> PC4
   ETH_MII_RXD1/ETH_RMII_RXD1 -------> PC5
   ETH_MII_TX_EN/ETH_RMII_TX_EN -----> PB11
   ETH_MII_TXD0/ETH_RMII_TXD0 -------> PG13
   ETH_MII_TXD1/ETH_RMII_TXD1 -------> PG14
   */

   /* 配置ETH_MDIO引脚 */
   GPIO_InitStructure.Pin = ETH_MDIO_PIN;
   GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
   GPIO_InitStructure.Mode = GPIO_MODE_AF_PP;
   GPIO_InitStructure.Pull = GPIO_NOPULL;
   GPIO_InitStructure.Alternate = ETH_MDIO_AF;
   HAL_GPIO_Init(ETH_MDIO_PORT, &GPIO_InitStructure);

   /* 配置ETH_MDC引脚 */
   GPIO_InitStructure.Pin = ETH_MDC_PIN;
   GPIO_InitStructure.Alternate = ETH_MDC_AF;
   HAL_GPIO_Init(ETH_MDC_PORT, &GPIO_InitStructure);

   /* 配置ETH_RMII_REF_CLK引脚 */
   GPIO_InitStructure.Pin = ETH_RMII_REF_CLK_PIN;
   GPIO_InitStructure.Alternate = ETH_RMII_REF_CLK_AF;
   HAL_GPIO_Init(ETH_RMII_REF_CLK_PORT, &GPIO_InitStructure);

   /* 配置ETH_RMII_CRS_DV引脚 */
   GPIO_InitStructure.Pin = ETH_RMII_CRS_DV_PIN;
   GPIO_InitStructure.Alternate = ETH_RMII_CRS_DV_AF;
   HAL_GPIO_Init(ETH_RMII_CRS_DV_PORT, &GPIO_InitStructure);

   /* 配置ETH_RMII_RXD0引脚 */
   GPIO_InitStructure.Pin = ETH_RMII_RXD0_PIN;
   GPIO_InitStructure.Alternate = ETH_RMII_RXD0_AF;
   HAL_GPIO_Init(ETH_RMII_RXD0_PORT, &GPIO_InitStructure);

   /* 配置ETH_RMII_RXD1引脚 */
   GPIO_InitStructure.Pin = ETH_RMII_RXD1_PIN;
   GPIO_InitStructure.Alternate = ETH_RMII_RXD1_AF;
   HAL_GPIO_Init(ETH_RMII_RXD1_PORT, &GPIO_InitStructure);

   /* 配置ETH_RMII_TX_EN引脚 */
   GPIO_InitStructure.Pin = ETH_RMII_TX_EN_PIN;
   GPIO_InitStructure.Alternate = ETH_RMII_TX_EN_AF;
   HAL_GPIO_Init(ETH_RMII_TX_EN_PORT, &GPIO_InitStructure);

   /* 配置ETH_RMII_TXD0引脚 */
   GPIO_InitStructure.Pin = ETH_RMII_TXD0_PIN;
   GPIO_InitStructure.Alternate = ETH_RMII_TXD0_AF;
   HAL_GPIO_Init(ETH_RMII_TXD0_PORT, &GPIO_InitStructure);

   /* 配置ETH_RMII_TXD1引脚 */
   GPIO_InitStructure.Pin = ETH_RMII_TXD1_PIN;
   GPIO_InitStructure.Alternate = ETH_RMII_TXD1_AF;
   HAL_GPIO_Init(ETH_RMII_TXD1_PORT, &GPIO_InitStructure);
}

HAL_ETH_MspInit函数调用ETH_GPIO_Config进行硬件初始化,并使能以太网时钟。

/**
   * @brief  以太网硬件底层驱动
   * @param  heth: 以太网句柄
   * @retval None
   */
void HAL_ETH_MspInit(ETH_HandleTypeDef *heth)
{
   ETH_GPIO_Config();
   /* 使能以太网时钟  */
   __HAL_RCC_ETH_CLK_ENABLE();
}

low_level_init主要是初始化硬件外设,最终被 ethernetif_init函数调用。

/**
* @brief 在这个函数中初始化硬件
*      最终被ethernetif_init函数调用
*
* @param netif已经初始化了这个以太网的lwip网络接口结构
*/
static void low_level_init(struct netif *netif)
{
uint8_t macaddress[6]= { MAC_ADDR0, MAC_ADDR1, MAC_ADDR2,
      MAC_ADDR3, MAC_ADDR4, MAC_ADDR5 };

   EthHandle.Instance = ETH;
   EthHandle.Init.MACAddr = macaddress;
   EthHandle.Init.AutoNegotiation = ETH_AUTONEGOTIATION_ENABLE;//使能自协商模式
   EthHandle.Init.Speed = ETH_SPEED_100M;//网络速率100M
   EthHandle.Init.DuplexMode = ETH_MODE_FULLDUPLEX;//全双工模式
   EthHandle.Init.MediaInterface = ETH_MEDIA_INTERFACE_RMII;//RMII接口
   EthHandle.Init.RxMode = ETH_RXPOLLING_MODE;//轮询接收模式
   EthHandle.Init.ChecksumMode = ETH_CHECKSUM_BY_HARDWARE;//硬件帧校验
   EthHandle.Init.PhyAddress = LAN8720A_PHY_ADDRESS;//PHY地址

   /* 配置以太网外设 (GPIOs, clocks, MAC, DMA) */
   if (HAL_ETH_Init(&EthHandle) == HAL_OK) {
      /* 设置netif链接标志 */
      netif->flags |= NETIF_FLAG_LINK_UP;
   }

   /* 初始化 Tx 描述符列表:链接模式 */
   HAL_ETH_DMATxDescListInit(&EthHandle, DMATxDscrTab, &Tx_Buff[0][0], ETH_TXBUFNB);

   /* 初始化 Rx 描述符列表:链接模式 */
   HAL_ETH_DMARxDescListInit(&EthHandle, DMARxDscrTab, &Rx_Buff[0][0], ETH_RXBUFNB);

   /* 设置netif MAC 硬件地址长度 */
   netif->hwaddr_len = ETHARP_HWADDR_LEN;

   /* 设置netif MAC 硬件地址 */
   netif->hwaddr[0] =  MAC_ADDR0;
   netif->hwaddr[1] =  MAC_ADDR1;
   netif->hwaddr[2] =  MAC_ADDR2;
   netif->hwaddr[3] =  MAC_ADDR3;
   netif->hwaddr[4] =  MAC_ADDR4;
   netif->hwaddr[5] =  MAC_ADDR5;

   /* 设置netif最大传输单位 */
   netif->mtu = 1500;

   /* 接收广播地址和ARP流量 */
   netif->flags |= NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP;

   /* 使能 MAC 和 DMA 发送和接收 */
   HAL_ETH_Start(&EthHandle);
}

首先是ETH_HandleTypeDef结构体填充,关于结构体各个成员意义已在“ETH初始化结构体详解”作了分析。然后调用系统函数HAL_ETH_Init初始化以太网外设。初始化相关描述符的列表,设置MAC地址,使能MAC和DMA发送和接收。

Netif_Config函数一般在main函数中在LwIP_Init函数初始化完成后调用。

#define DEST_IP_ADDR0   (uint8_t)192
#define DEST_IP_ADDR1   (uint8_t)168
#define DEST_IP_ADDR2   (uint8_t)31
#define DEST_IP_ADDR3   (uint8_t)198

#define DEST_PORT       (uint8_t)7

/*Static IP ADDRESS: IP_ADDR0.IP_ADDR1.IP_ADDR2.IP_ADDR3 */
#define IP_ADDR0   (uint8_t) 192
#define IP_ADDR1   (uint8_t) 168
#define IP_ADDR2   (uint8_t) 31
#define IP_ADDR3   (uint8_t) 122

/*NETMASK*/
#define NETMASK_ADDR0   (uint8_t) 255
#define NETMASK_ADDR1   (uint8_t) 255
#define NETMASK_ADDR2   (uint8_t) 255
#define NETMASK_ADDR3   (uint8_t) 0

/*Gateway Address*/
#define GW_ADDR0   (uint8_t) 192
#define GW_ADDR1   (uint8_t) 168
#define GW_ADDR2   (uint8_t) 31
#define GW_ADDR3   (uint8_t) 1
/**
* @brief  建立网络接口
* @param  None
* @retval None
*/
void Netif_Config(void)
{
   ip_addr_t ipaddr;
   ip_addr_t netmask;
   ip_addr_t gw;

   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_add(&gnetif, &ipaddr, &netmask, &gw, NULL, &ethernetif_init, &ethernet_input);

   /* 注册默认网络接口 */
   netif_set_default(&gnetif);

   if (netif_is_link_up(&gnetif)) {
      /* 当netif完全配置时,必须调用此函数 */
      netif_set_up(&gnetif);
   } else {
      /* 当netif链接断开时,必须调用此函数 */
      netif_set_down(&gnetif);
   }
}

通过宏定义了远端IP和端口、MAC地址、静态IP地址、子网掩码、网关相关宏,可以根据实际情况修改。netif_add函数添加网络接口;netif_set_default注册默认网络接口。

/**
* @brief  通知用户有关网络接口配置状态
* @param  netif: 网络接口
* @retval None
*/
void User_notification(struct netif *netif)
{
   if (netif_is_up(netif)) {
      printf("Static IP: %d.%d.%d.%d\n",
IP_ADDR0,IP_ADDR1,IP_ADDR2,IP_ADDR3);
      printf("NETMASK  : %d.%d.%d.%d\n",
NETMASK_ADDR0,NETMASK_ADDR1,NETMASK_ADDR2,NETMASK_ADDR3);
      printf("Gateway  : %d.%d.%d.%d\n",
GW_ADDR0,GW_ADDR1,GW_ADDR2,GW_ADDR3);
      LED_GREEN;
   } else {
      printf ("The network cable is not connected \n");
      LED_RED;
   }
}

User_notification函数在网络接口配置完成后调用,通知用户有关网络接口配置状态。打印接口连接状态,LED指示连接状态。

/**
* @ingroup lwip_nosys
* 初始化所有模块.
* 在NO_SYS模式下使用,否则使用tcpip_init()。
*/
void
lwip_init(void)
{
#ifndef LWIP_SKIP_CONST_CHECK
   int a = 0;
   LWIP_UNUSED_ARG(a);
   LWIP_ASSERT("LWIP_CONST_CAST not implemented correctly.
Check your lwIP port.", LWIP_CONST_CAST(void*, &a) == &a);
#endif
#ifndef LWIP_SKIP_PACKING_CHECK
   LWIP_ASSERT("Struct packing not implemented correctly.
Check your lwIP port.", sizeof(struct packed_struct_test) ==
      PACKED_STRUCT_TEST_EXPECTED_SIZE);
#endif

   /* 模块初始化 */
   stats_init();
#if !NO_SYS
   sys_init();
#endif /* !NO_SYS */
   mem_init();
   memp_init();
   pbuf_init();
   netif_init();
#if LWIP_IPV4
   ip_init();
#if LWIP_ARP
   etharp_init();
#endif /* LWIP_ARP */
#endif /* LWIP_IPV4 */
#if LWIP_RAW
   raw_init();
#endif /* LWIP_RAW */
#if LWIP_UDP
   udp_init();
#endif /* LWIP_UDP */
#if LWIP_TCP
   tcp_init();
#endif /* LWIP_TCP */
#if LWIP_IGMP
   igmp_init();
#endif /* LWIP_IGMP */
#if LWIP_DNS
   dns_init();
#endif /* LWIP_DNS */
#if PPP_SUPPORT
   ppp_init();
#endif

#if LWIP_TIMERS
   sys_timeouts_init();
#endif /* LWIP_TIMERS */
}

lwip_Init函数用于初始化LwIP协议栈,一般在main函数中调用。首先是内存相关初始化,mem_init函数是动态内存堆初始化,memp_init函数是存储池初始化,LwIP是实现内存的高效利用,内部需要不同形式的内存管理模式。

pbuf 函数为预留的函数,目前是一个空操作。netif_init函数多播的时候用到,本例没有用到。后面的功能都是通过lwipopts.h进行裁剪。

/**
* @brief 当数据包准备好从接口读取时,应该调用此函数。
*它使用应该处理来自网络接口的字节的实际接收的函数low_level_input。
*然后确定接收到的分组的类型,并调用适当的输入功能。
*
* @param netif 以太网的lwip网络接口结构
*/
void ethernetif_input(struct netif *netif)
{
   err_t err;
   struct pbuf *p;

   /* 将接收到的数据包移动到新的pbuf中 */
   p = low_level_input(netif);

   /* 没有数据包可以读取,直接返回 */
   if (p == NULL) return;

   /* 到LwIP堆栈入口 */
   err = netif->input(p, netif);

   if (err != ERR_OK) {
      LWIP_DEBUGF(NETIF_DEBUG, ("ethernetif_input: IP input error\n"));
      pbuf_free(p);
      p = NULL;
   }
}

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

/**
* @ingroup lwip_nosys
* 处理NO_SYS==1超时 (即不使用tcpip_thread/sys_timeouts_mbox_fetch())
* 使用sys_now()函数,当超时到期时调用超时处理函数。
* 必须定期从主循环中调用。
*/
#if !NO_SYS && !defined __DOXYGEN__
static
#endif /* !NO_SYS */
void
sys_check_timeouts(void)
{
   if (next_timeout) {
         struct sys_timeo *tmptimeout;
         u32_t diff;
         sys_timeout_handler handler;
         void *arg;
         u8_t had_one;
         u32_t now;

         now = sys_now();
         /* this cares for wraparounds */
         diff = now - timeouts_last_time;
         do {
            PBUF_CHECK_FREE_OOSEQ();
            had_one = 0;
            tmptimeout = next_timeout;
            if (tmptimeout && (tmptimeout->time <= diff)) {
               /* timeout has expired */
               had_one = 1;
               timeouts_last_time += tmptimeout->time;
               diff -= tmptimeout->time;
               next_timeout = tmptimeout->next;
               handler = tmptimeout->h;
               arg = tmptimeout->arg;
#if LWIP_DEBUG_TIMERNAMES
               if (handler != NULL) {
               LWIP_DEBUGF(TIMERS_DEBUG, ("sct calling h=%s arg=%p\n",
                                                tmptimeout->handler_name, arg));
               }
#endif /* LWIP_DEBUG_TIMERNAMES */
               memp_free(MEMP_SYS_TIMEOUT, tmptimeout);
               if (handler != NULL) {
#if !NO_SYS
         /* For LWIP_TCPIP_CORE_LOCKING, lock the core before calling the
                        timeout handler function. */
                     LOCK_TCPIP_CORE();
#endif /* !NO_SYS */
                     handler(arg);
#if !NO_SYS
                     UNLOCK_TCPIP_CORE();
#endif /* !NO_SYS */
               }
               LWIP_TCPIP_THREAD_ALIVE();
            }
            /* repeat until all expired timers have been called */
         } while (had_one);
   }
}

sys_check_timeouts函数是一个必须被无限循环调用的LwIP支持函数,一般在main函数的无限循环中调用,使用sys_now()函数,当超时到期时调用超时处理函数。

void LwIP_DHCP_Process_Handle(void)
{
   struct ip_addr ipaddr;
   struct ip_addr netmask;
   struct ip_addr gw;

   switch (DHCP_state) {
   case DHCP_START: {
      DHCP_state = DHCP_WAIT_ADDRESS;
      dhcp_start(&gnetif);
      /* IP address should be set to 0
         every time we want to assign a new DHCP address */
      IPaddress = 0;
#ifdef SERIAL_DEBUG
      printf("\n     Looking for    \n");
      printf("     DHCP server    \n");
      printf("     please wait... \n");
#endif /* SERIAL_DEBUG */
   }
   break;

   case DHCP_WAIT_ADDRESS: {
      /* Read the new IP address */
      IPaddress = gnetif.ip_addr.addr;

      if (IPaddress!=0) {
            DHCP_state = DHCP_ADDRESS_ASSIGNED;
            /* Stop DHCP */
            dhcp_stop(&gnetif);
#ifdef SERIAL_DEBUG
            printf("\n  IP address assigned \n");
            printf("    by a DHCP server   \n");
            printf("IP: %d.%d.%d.%d\n",(uint8_t)(IPaddress),
                           (uint8_t)(IPaddress >> 8),(uint8_t)(IPaddress >> 16),
                           (uint8_t)(IPaddress >> 24));
            printf("NETMASK: %d.%d.%d.%d\n",NETMASK_ADDR0,NETMASK_ADDR1,
                                                NETMASK_ADDR2,NETMASK_ADDR3);
            printf("Gateway: %d.%d.%d.%d\n",GW_ADDR0,GW_ADDR1,
                                                   GW_ADDR2,GW_ADDR3);
            LED1_ON;
#endif /* SERIAL_DEBUG */
      } else {
            /* DHCP timeout */
            if (gnetif.dhcp->tries > MAX_DHCP_TRIES) {
               DHCP_state = DHCP_TIMEOUT;
               /* Stop DHCP */
               dhcp_stop(&gnetif);
               /* Static address used */
               IP4_ADDR(&ipaddr, IP_ADDR0 ,IP_ADDR1 , IP_ADDR2 , IP_ADDR3 );
               IP4_ADDR(&netmask, NETMASK_ADDR0, NETMASK_ADDR1,
                                       NETMASK_ADDR2, NETMASK_ADDR3);
               IP4_ADDR(&gw, GW_ADDR0, GW_ADDR1, GW_ADDR2, GW_ADDR3);
               netif_set_addr(&gnetif, &ipaddr , &netmask, &gw);
#ifdef SERIAL_DEBUG
               printf("\n    DHCP timeout    \n");
               printf("  Static IP address   \n");
               printf("IP: %d.%d.%d.%d\n",IP_ADDR0,IP_ADDR1,
                                                IP_ADDR2,IP_ADDR3);
               printf("NETMASK: %d.%d.%d.%d\n",NETMASK_ADDR0,NETMASK_ADDR1,
                                                      NETMASK_ADDR2,NETMASK_ADDR3);
               printf("Gateway: %d.%d.%d.%d\n",GW_ADDR0,GW_ADDR1,
                                                      GW_ADDR2,GW_ADDR3);
               LED1_ON;
#endif /* SERIAL_DEBUG */
            }
      }
   }
   break;
   default:
      break;
   }
}

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官方例程相同配置即可。

/**
* @brief  主函数
* @param  无
* @retval 无
*/
int main(void)
{

   /* 配置系统时钟为168 MHz */
   SystemClock_Config();

   /* 初始化RGB彩灯 */
   LED_GPIO_Config();

   /* 初始化USART1 配置模式为 115200 8-N-1 */
   UARTx_Config();

   /* 初始化LwIP协议栈*/
   lwip_init();

   printf("LAN8720A Ethernet Demo\n");
   printf("LwIP版本:%s\n",LWIP_VERSION_STRING);

   printf("ping实验例程\n");

   printf("使用同一个局域网中的电脑ping开发板的地址,可进行测试\n");

   //IP地址和端口可在main.h文件修改
   printf("本地IP和端口: %d.%d.%d.%d\n",
IP_ADDR0,IP_ADDR1,IP_ADDR2,IP_ADDR3);
   /* 网络接口配置 */
   Netif_Config();
   /* 报告用户网络连接状态 */
   User_notification(&gnetif);

   while (1) {
      /* 从以太网缓冲区中读取数据包,交给LwIP 处理 */
      ethernetif_input(&gnetif);
      /* 处理 LwIP 超时 */
      sys_check_timeouts();
   }
}

首先是使能指令缓存、数据缓存,初始化系统时钟、LED指示灯、按键、调试串口,lwip_init 函数初始化LwIP协议栈。通过Netif_Config函数配置网络接口;通过User_notification函数报告用户网络连接状态。进入无限循环函数,调用ethernetif_input 函数从以太网缓存中读取数据包并交给LwIP处理;调用sys_check_timeouts 函数处理LwIP超时。这两个函数必须在大循环中调用。

下载验证

保证开发板相关硬件连接正确,用USB线连接开发板“USB TO UART”接口跟电脑,在电脑端打开串口调试助手并配置好相关参数;使用网线连接开发板网口跟路由器,这里要求电脑连接在同一个路由器上,之所以使用路由器是这样连接方便,电脑端无需更多操作步骤,并且路由器可以提供DHCP服务器功能,而电脑是不行的,最后在电脑端打开网络调试助手软件,并设置相关参数,见 图36_1_19,调试助手的设置与netconf.h文件中相关宏定义是对应的, 不同电脑设置情况可能不同。把编译好的程序下载到开发板。
在这里插入图片描述
在系统硬件初始化时串口调试助手会打印相关提示信息,等待初始化完成后可打开电脑端CMD窗口,输入ping命令测试开发板链路, 图36_1_20 为链路正常情况,如果出现ping不同情况,检查网线连接。
在这里插入图片描述
ping状态正常后,可按下开发板KEY1按键,使能开发板连接电脑端的TCP服务器, 之后就可以进行数据传输,需要接收传输时可以按下开发板KEY2按键
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qlexcel

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值