LwIP入门实战 — 3 LwIP的网络接口管理
目录
3.1 以太网外设 (ETH)
3.1.1 以太网外设 (ETH)简介
3.1.2 SMI 接口
3.1.3 MII 和 RMII 接口
3.1.4 MAC 数据包发送与接收
3.1.5 MAC 数据包过滤
3.2 PHY:LAN8720A
3.3 硬件设计
3.4 软件设计
3.4.1 获取 STM32 的裸机工程模板
3.4.2 添加 bsp_eth.c 与 bsp_eth.h
3.4.3 修改 stm32f4xx_hal_conf.h 文件
3.1 以太网外设 (ETH)
3.1.1 以太网外设 (ETH)简介
STM32F42x 系列控制器内部集成了一个以太网外设,包括一个介质访问控制器和专用的 DMA 控制器,它的功能就是实现 MAC 层的任务。借助以太网外设,STM32F42x 可以通过 ETH 外设按照 IEEE 802.3-2002 标准发送和接收 MAC 数据包。ETH 支持介质独立接口 (MII) 和简化介质独立接口 (RMII) 用于与外部 PHY 芯片连接。MII 和 RMII 接口用于 MAC 数据包传输,ETH 还集成了站管理接口 (SMI) 接口专门用于与外部 PHY 通信,用于访问 PHY 芯片寄存器。
- ETH外设属于数据链路层,负责以太网帧的封装与解封装、MAC地址过滤、介质访问控制,并通过MII/RMII接口与外部PHY芯片通信。
- PHY芯片属于物理层,负责数字信号与模拟信号的相互转换、链路状态检测和自动协商,通过MII/RMII接口与MAC连接,完成10/100Mbps下物理介质的信号驱动与接收。常见水晶头网线 + 水晶头插座 +PHY 组合构成了物理层。
[CPU]
↓ (MII/RMII/GMII)
[ETH外设] ← [MAC 控制器] ← [DMA] 内置于 MCU(如 STM32Fxx/F7/H7 的 ETH 外设)
↓
[PHY 芯片] ← 如 LAN8720、KSZ8081、DP83848 等(物理层芯片)
↓
[RJ45 接口 + 网络]
ETH 有专用的 DMA 控制器,它通过 AHB 主从接口与内核和存储器相连,AHB 主接口用于控制数据传输,而 AHB 从接口用于访问“控制与状态寄存器”空间。在进行数据发送时,先将数据从存储器以 DMA 传输到发送 TX FIFO 进行缓冲,然后由 MAC 内核发送;接收数据时,RXFIFO 先接收以太网数据帧,再由 DMA 传输至存储器。ETH 系统功能框图见图。
AHB 从接口:用于与 STM32F42x 微控制器内部的高级高性能总线(AHB)相连,实现以太网外设与芯片内部其他模块的数据交互和控制信号传输,方便主处理器对以太网外设进行配置和数据读写。
外部 PHY(物理层)接口:通过 RMII(简化媒体独立接口)或 MII(媒体独立接口)与外部的物理层芯片相连。RMII 接口相对 MII 接口减少了引脚数量,主要负责实现数据的物理层传输,包括信号的发送和接收、电平转换等。MDC(管理数据时钟)和 MDIO(管理数据输入 / 输出)接口用于对外部 PHY 芯片进行配置和状态读取。
DMA 控制与状态寄存器:直接内存访问(DMA)用于在以太网数据传输过程中,实现数据在内存与外设之间的高速传输,无需 CPU 频繁参与,减轻 CPU 负担。这些寄存器用于配置 DMA 的工作模式、传输方向、数据长度等参数,同时记录 DMA 传输的状态信息,如传输完成标志、错误标志等。
工作模式寄存器:用于设置以太网外设的工作模式,例如全双工模式或半双工模式、传输速率(10Mbps 或 100Mbps 等)等。不同的工作模式可以根据实际的网络环境和应用需求进行灵活配置。
MAC 控制寄存器:媒体访问控制(MAC)层遵循 IEEE 802.3 标准,负责以太网数据帧的封装和解封装、寻址、错误检测等功能。MAC 控制寄存器用于配置 MAC 层的工作参数,比如设置发送和接收帧的过滤规则、启用或禁用 CRC(循环冗余校验)校验等。
RX FIFO(接收先进先出缓冲区):在接收以太网数据时,数据先进入 RX FIFO,起到缓冲作用,避免数据丢失。它可以暂存一定数量的数据帧,等待 DMA 或 CPU 进行读取和处理。
TX FIFO(发送先进先出缓冲区):在发送以太网数据时,待发送的数据先被写入 TX FIFO,然后由 MAC 层按照一定的规则将数据帧发送出去。TX FIFO 同样起到数据缓冲和协调发送节奏的作用。
校验和减荷:在数据发送和接收过程中,自动计算和验证数据帧的校验和,确保数据传输的准确性。校验和减荷功能可以减轻 CPU 的计算负担,提高数据处理效率。
PTP(IEEE1588):精确时间协议(PTP)模块用于实现高精度的网络时间同步,使得网络中的各个设备之间可以保持精确的时间同步,这对于一些对时间同步要求较高的应用场景,如工业自动化、电力系统等非常重要。
PMT(分组镜像和过滤):可以对特定的数据帧进行镜像或过滤操作。例如,在网络监控应用中,可以通过配置 PMT 将特定的网络流量镜像到指定的端口进行分析,或者过滤掉不符合特定规则的数据帧。
MMC(多播过滤):用于处理多播数据帧,根据配置的过滤规则,决定是否接收或丢弃多播数据,有效减少不必要的数据接收,节省系统资源。
3.1.2 SMI 接口
SMI(Serial Management Interface),也称为MDIO(Management Data Input/Output)接口,是以太网体系中定义的标准串行管理接口,用于MAC控制器与PHY芯片之间的配置和状态监控通信;该接口由两根信号线组成:MDC(时钟)和MDIO(数据线),支持多设备挂载,通过PHY地址区分不同设备;常用于设置工作模式、启用自动协商及读取链路状态。由于其低速、简单、可靠的特点,SMI/MDIO被广泛集成于各类MAC控制器中。
SMI 是通过数据帧方式与 PHY 通信的,帧格式如表 SMI 帧格式,数据位传输顺序从左到右。
报头 (32bit):读取和写入操作的报头都是32位 1,主要用于标识一个管理帧的开始,接收端通过检测报头来识别新的管理帧,以便进行后续的数据解析。
起始:值为 01,用于指示管理帧的起始阶段,辅助接收设备同步和区分不同的帧。
操作:读取操作值为 10,表示该管理帧用于从目标设备(如 PHY 芯片)读取数据;写入操作:值为 01,表示该管理帧用于向目标设备写入配置数据等信息。
PADDR(端口地址):由 “ppppp” 表示,用于指定要访问的 PHY 芯片内部端口地址。通过这个地址,主机可以准确地定位到需要操作的具体位置。
RADDR(寄存器地址):由 “rrrrr” 表示,在确定了端口后,进一步指定要访问的具体寄存器,不同的寄存器存储着不同的配置信息或状态数据。
TA(目标地址):在读取操作中值为 Z0,写入操作中值为 10。TA 用于标识目标设备,在一个系统中可能存在多个可管理的设备,TA 帮助主机明确操作是针对哪一个具体设备。
数据 (16bit) :在写入操作中,这部分携带要写入目标寄存器的具体数据;在读取操作中,设备将对应寄存器中的数据填充到这个字段返回给主机。
空闲:用 “Z” 表示,在帧传输过程中,空闲状态用于填充时间间隙,保持信号的连续性,同时也为设备提供处理和准备下一个帧的时间。
当以太网 MAC MII 地址寄存器 (ETH_MACMIIAR) 的写入位和繁忙位被置 1 时, SMI 将向指定的 PHY 芯片指定寄存器写入 ETH_MACMIIDR 中的数据。写操作时序见图SMI 写操作。
当以太网 MAC MII 地址寄存器 (ETH_MACMIIAR) 的写入位为 0 并且繁忙位被置 1 时, SMI 将从向指定的 PHY 芯片指定寄存器读取数据到 ETH_MACMIIDR 内。读操作时序见图SMI 读操作。
3.1.3 MII 和 RMII 接口
MII(Media Independent Interface)和RMII(Reduced Media Independent Interface)是以太网MAC控制器与PHY芯片之间用于传输高速数据的同步接口。
- MII由IEEE 802.3标准定义,工作在25 MHz时钟下,支持10/100 Mbps速率,采用16根信号线(包括发送、接收、控制和时钟等),数据宽度为4位,适用于百兆以太网通信;
- RMII是对MII的简化版本,将信号线减少至7根,统一使用50 MHz参考时钟,大幅降低引脚数量和硬件复杂度,更适合引脚资源有限的嵌入式系统,但仅支持10/100 Mbps,不适用于千兆及以上速率;
两者均保持MAC与PHY之间的“介质无关性”,即同一MAC可适配不同类型的PHY(如铜线或光纤),广泛应用于工业控制、物联网设备和消费类电子中。MII 接口与RMII 接口连接连接示意图如下。
图MII 接口连接示意
图RMII 接口连接示意
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。
常用PHY 芯片型号为 LAN8720A,该芯片只支持 RMII 接口,电路设计时参考图RMII接口连接。ETH 相关硬件在 STM32F42x 控制器分布参考表ETH 复用引脚。
3.1.4 MAC 数据包发送与接收
ETH 外设负责 MAC 数据包发送和接收。利用 DMA 从系统寄存器得到数据包数据内容, ETH 外设自动填充完成 MAC 数据包封装,然后通过 PHY 发送出去。在检测到有 MAC 数据包需要接收时, ETH 外设控制数据接收,并解封 MAC 数据包得到解封后数据通过 DMA 传输到系统寄存器内。
(1)MAC 数据包发送
MAC数据包的发送过程通常由CPU或DMA协同以太网MAC控制器完成:首先,应用程序或协议栈(如LwIP)将待发送的数据封装成以太网帧,并提交给MAC驱动;驱动程序将数据写入指向的内存缓冲区,并配置相关控制字段(如数据长度、中断使能等);随后,MAC控制器通过DMA从指定内存区域读取数据帧,将其转换为符合MII、RMII或GMII等物理接口时序的串行数据流,发送至PHY芯片;同时,MAC会添加前导码(Preamble)和帧起始定界符(SFD),并计算并附加CRC校验码;PHY接收到数据后,将其转换为模拟信号通过物理介质(如RJ45网线)发送出去;发送完成后,MAC控制器产生中断或更新状态寄存器,通知CPU发送完成,驱动程序释放缓冲区,完成整个发送流程。
当需要发送帧时,拉高MII_TX_EN使能信号,表示开始发送数据;MII_TX_CLK提供同步时钟;MII_TXD[3:0]在TX_EN有效期间逐位输出数据帧,包括前导码(PR)、目标地址(EAM)、源地址(BLE)和帧起始定界符(SFD)等字段;MII_CS信号在数据发送过程中保持高电平,表示芯片处于发送状态;MII_COL保持低电平,表示无冲突发生。
(2)MAC 数据包接收
MAC数据包通过PHY芯片将以太网信号转换,通过MII类接口将数据流传输至MAC控制器;MAC控制器在完成帧同步、CRC32校验、目的MAC地址匹配及帧长合法性检查后,通过DMA机制将有效以太网帧写入预分配内存缓冲区;随后,MAC控制器触发接收中断,通知处理器进入中断服务例程提交至上层协议栈进行后续处理,从而实现高效、可靠的链路层数据接收。
当 MAC 控制器在 MII 接口上检测到帧起始定界符(SFD)时,即启动接收操作。MAC 内核随即进入帧接收状态,去除前导码和 SFD 字段,获取数据内容。接收到的帧头(包括目的 MAC 地址、源 MAC 地址和 EtherType/Length 字段)被用于地址过滤和协议解析:若目标 MAC 地址未匹配且非广播或多播地址(在非混杂模式下),该帧将在 MAC 内核内部被丢弃。对于通过过滤的帧,MAC 持续接收直至帧结束,并利用帧尾的帧校验序列(FCS)字段执行 CRC-32 校验,以验证数据完整性 。MAC 数据包接收时序参考图 MAC 数据包接收时序。
3.1.5 MAC 数据包过滤
MAC数据包过滤是网络接口控制器(NIC)在硬件或驱动层面对接收到的数据帧进行筛选的过程,目的是仅将符合特定条件的帧传递给上层协议栈,从而减少CPU负载、提升系统效率并增强安全性
过滤类型 | 说明 |
单播过滤 | 只接收发给本设备的数据包 |
组播过滤 | 接收特定组播地址(如 ARP、IPv4 多播、PTP、LLDP) |
广播包过滤 | 可选择性接收或丢弃广播帧 |
EtherType 过滤 | 仅接收特定协议(如只处理 IPv4、ARP,忽略 IPv6、LLDP) |
VLAN 过滤 | 仅处理指定 VLAN ID 的帧 |
混杂模式 | 接收所有帧(调试用,一般关闭) |
3.2 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 有各个不同功能模块组成,最重要是接收控制器和发送控制器,其它的基本上都是与外部引脚挂钩,实现信号传输。
- Mode Control(模式控制):通过
MODE[0:2]
引脚输入的信号,配置芯片的工作模式,比如选择不同的以太网速率(10M/100M 等 )、接口模式(RMII 等 )等,让芯片按需适配网络环境和系统需求。 - Reset Control(复位控制):
nRST
引脚用于接收复位信号,当该信号有效(一般为低电平 )时,芯片会执行复位操作,将内部寄存器、状态等恢复到初始值,确保系统启动或异常恢复时芯片能稳定初始化。 - RMII Logic(RMII 逻辑):作为芯片与 MAC(介质访问控制 )层之间的接口逻辑,遵循 RMII(简化媒体独立接口 )标准,通过
TXD[0:1]
(发送数据 )、TXEN
(发送使能 )、RXD[0:1]
(接收数据 )、RXER
(接收错误 )、CRS_DV
(载波检测与数据有效 )等信号,实现 MAC 层与 PHY 层之间以太网数据帧的收发交互,简化硬件连接,降低系统成本和复杂度。 - Management Control(管理控制):借助
MDC
(管理数据时钟 )和MDIO
(管理数据输入输出 )引脚,实现对芯片的管理配置。遵循以太网管理接口标准,可读取芯片状态寄存器获取工作状态,也能写入控制寄存器调整芯片参数,比如配置自动协商、速率模式等。 - PHY Address Latches(PHY 地址锁存):
PHYAD0
引脚用于设置芯片在网络中的 PHY 地址,当系统存在多个 PHY 芯片时,通过该地址区分不同芯片,方便 MAC 层或管理模块针对性地访问和配置对应 PHY 芯片。 - Auto - Negotiation(自动协商):自动与连接的对端以太网设备协商工作参数,包括以太网速率(10Mbit/s 或 100Mbit/s )、双工模式(半双工或全双工 )等,无需人工干预,自动适配最佳通信参数,保证网络连接的兼容性和高效性。协商过程遵循 IEEE 802.3 标准,通过交换协商帧来确定双方共同支持的最优工作模式。
- Transmitter(发送器):分为
100M TX
和10M TX
路径,对应处理 100Mbps 和 10Mbps 速率的发送数据。
-
- 100M TX Logic/10M TX Logic(发送逻辑):对要发送的数据进行处理,包括编码、组帧等操作,将来自 MAC 层的并行数据转换为符合以太网物理层传输要求的格式,为进入发送器做准备。
- 100M Transmitter/10M Transmitter(发送单元):把经过逻辑处理的发送数据,进一步转换为适合在以太网传输介质(如双绞线 )上发送的电信号,通过后续
HP Auto - MDIX
等模块输出到网络链路。
- HP Auto - MDIX(自动交叉检测与匹配):具备自动检测和适配以太网接口线序的功能,不管连接的是直通线还是交叉线,都能自动调整收发信号的对应关系,保证数据正常收发。无需人工关注网线线序,提升了网络连接的便利性和兼容性,
TXP/TXN
(发送差分对 )、RXP/RXN
(接收差分对 )通过该模块连接到外部网络接口。 - MDIX Control(MDIX 控制):配合
HP Auto - MDIX
工作,实现对自动交叉功能的控制和状态管理,确保线序适配准确、稳定,保障数据发送和接收的物理连接正确。 - Receiver(接收器):分为
100M RX
和10M RX
路径,处理不同速率的接收数据。
-
- 100M RX Logic/10M RX Logic(接收逻辑):对从网络链路上接收到的电信号进行初步处理,包括解码、帧同步等,提取出有效的以太网数据帧,为后续处理做准备。
- Analog - to - Digital(模数转换):当接收到模拟信号形式的以太网数据时(比如通过双绞线传输的差分信号 ),将其转换为数字信号,方便后续数字电路进行处理。
- DSP System(数字信号处理系统):包含
Clock Data Recovery
(时钟数据恢复 )和Equalizer
(均衡器 ),时钟数据恢复用于从接收数据中提取时钟信号,保证数据采样的时序准确性;均衡器则对因传输线路衰减、干扰等导致失真的信号进行补偿和校正,提升接收数据的质量。 - 100M PLL/10M PLL(锁相环):为 100Mbps 和 10Mbps 接收路径提供稳定的时钟信号,通过锁定输入信号的频率和相位,产生与接收数据同步的时钟,确保数据正确接收和解码,
10M PLL
还会为10M RX
相关逻辑提供合适时钟 。 - Squelch & Filters(噪声抑制与滤波器):过滤接收信号中的噪声、干扰信号,抑制无用的微弱信号,只让符合以太网信号特征和强度要求的有效信号进入后续处理流程,提升接收数据的纯净度,减少误码。
- PLL(锁相环):除了上述接收路径中的
100M PLL
和10M PLL
,这里的 PLL 主要为芯片内部其他数字逻辑、控制模块等提供稳定的时钟信号,通过外部XTAL1/CLKIN
(时钟输入 )、XTAL2
(时钟输出,可用于驱动外部晶振相关电路 )引脚接入晶振或外部时钟源,产生芯片工作所需的各种时钟频率,保障芯片各模块协调、同步工作。 - Interrupt Generator(中断产生器):当芯片内部发生特定事件时,如接收错误、协商完成、链路状态变化等,会通过
nINT
引脚向外(一般连接到主控芯片 )发出中断信号,通知主控芯片及时处理这些事件,实现系统对 PHY 芯片工作状态的实时响应。 - LEDs(指示灯):
LED1
和LED2
可通过配置,用来指示芯片的工作状态,比如链路是否建立、数据收发状态、工作速率等,方便用户或系统通过指示灯直观了解以太网连接和工作情况。 - Central Bias(中心偏置):为芯片内部的模拟电路、射频电路等提供稳定的偏置电压,
RBIAS
引脚可用于调整或监测偏置电流 / 电压,保障这些电路在合适的工作点稳定运行,提升信号处理的准确性和可靠性。
3.3 硬件设计
在讲解移植步骤之前,了解一下硬件设计有助有我们把握整体,主要是 LAN8720A 通过RMII 和 SMI 接口与STM32F42x 控制器连接,见图(参考野火开发板)。
电路设计时,将 NINTSEL 引脚通过下拉电阻拉低,设置 NINT/FEFCLKO 为输出50MHz 时钟,当然前提是在 XTAL1 和 XTAL2 接入了 25MHz 的时钟源。另外也把REGOFF 引脚通过下拉电阻拉低,使能使用内部+1.2V 稳压器。
3.4 软件设计
3.4.1 获取 STM32 的裸机工程模板
我们选取比较简单的例程—“GPIO 输出—使用固件库点亮 LED”作为裸机工程模板。
3.4.2 添加 bsp_eth.c 与 bsp_eth.h
我们打开裸机工程之后, 就创建一个文件夹, 命名为 eth, 并且在该文件夹下创建两个文件, 分别为 bsp_eth.c 与 bsp_eth.h 文件,具体见图。
然后再将 bsp_eth.c 文件添加到工程分组中, 具体见图。
然后我们就可以在 bsp_eth.c 文件中进行初始化 eth 驱动了, 暂时加入以下代码,具体见代码清单。
/**
* @file main.c
* @author fire
* @version V1.0
* @date 2019-xx-xx
* @brief 以太网功能实现主文件,包含ETH初始化、中断处理等核心功能
*/
#include "./eth/bsp_eth.h"
#include "main.h" /* 全局以太网句柄,用于管理ETH外设的各种状态和配置 */
ETH_HandleTypeDef heth;// 根据不同编译器进行4字节对齐设置
#if defined ( __ICCARM__ )
#pragma data_alignment=4
#endif
// 以太网接收DMA描述符表,ETH_RXBUFNB表示接收缓冲区数量
__ALIGN_BEGIN ETH_DMADescTypeDef DMARxDscrTab[ETH_RXBUFNB] __ALIGN_END;/* Ethernet Rx DMA Descriptor */#if defined ( __ICCARM__ )
#pragma data_alignment=4
#endif
// 以太网发送DMA描述符表,ETH_TXBUFNB表示发送缓冲区数量
__ALIGN_BEGIN ETH_DMADescTypeDef DMATxDscrTab[ETH_TXBUFNB] __ALIGN_END;/* Ethernet Tx DMA Descriptor */#if defined ( __ICCARM__ )
#pragma data_alignment=4
#endif
// 以太网接收缓冲区数组,二维数组结构,每个缓冲区大小为ETH_RX_BUF_SIZE
__ALIGN_BEGIN uint8_t Rx_Buff[ETH_RXBUFNB][ETH_RX_BUF_SIZE] __ALIGN_END; /* Ethernet Receive Buffer */#if defined ( __ICCARM__ )
#pragma data_alignment=4
#endif
// 以太网发送缓冲区数组,二维数组结构,每个缓冲区大小为ETH_TX_BUF_SIZE
__ALIGN_BEGIN uint8_t Tx_Buff[ETH_TXBUFNB][ETH_TX_BUF_SIZE] __ALIGN_END; /* Ethernet Transmit Buffer *//*** @brief ETH外设的MSP初始化函数(底层硬件初始化)* @param ethHandle: 以太网句柄* @retval None* 说明:此函数由HAL库自动调用,用于初始化ETH相关的GPIO、时钟和中断*/
void HAL_ETH_MspInit(ETH_HandleTypeDef* ethHandle)
{GPIO_InitTypeDef GPIO_InitStruct; // GPIO初始化结构体if(ethHandle->Instance==ETH) // 确认操作的外设是ETH{/* USER CODE BEGIN ETH_MspInit 0 */// 用户可在此处添加自定义初始化代码/* USER CODE END ETH_MspInit 0 *//**ETH GPIO配置 - 配置以太网相关引脚PC1 ------> ETH_MDC(以太网管理数据时钟)PA1 ------> ETH_REF_CLK(以太网参考时钟)PA2 ------> ETH_MDIO(以太网管理数据输入输出)PA7 ------> ETH_CRS_DV(以太网载波侦听/数据有效)PC4 ------> ETH_RXD0(以太网接收数据0)PC5 ------> ETH_RXD1(以太网接收数据1)PB11 ------> ETH_TX_EN(以太网发送使能)PG13 ------> ETH_TXD0(以太网发送数据0)PG14 ------> ETH_TXD1(以太网发送数据1) */GPIO_InitStruct.Pin = ETH_MDC_Pin|ETH_RXD0_Pin|ETH_RXD1_Pin;GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; // 复用推挽输出GPIO_InitStruct.Pull = GPIO_NOPULL; // 无上下拉GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;// 高速模式GPIO_InitStruct.Alternate = GPIO_AF11_ETH; // 复用为ETH功能HAL_GPIO_Init(GPIOC, &GPIO_InitStruct); // 初始化GPIOCGPIO_InitStruct.Pin = ETH_REF_CLK_Pin|ETH_MDIO_Pin|ETH_CRS_DV_Pin;GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;GPIO_InitStruct.Pull = GPIO_NOPULL;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;GPIO_InitStruct.Alternate = GPIO_AF11_ETH;HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); // 初始化GPIOAGPIO_InitStruct.Pin = ETH_TX_EN_Pin;GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;GPIO_InitStruct.Pull = GPIO_NOPULL;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;GPIO_InitStruct.Alternate = GPIO_AF11_ETH;HAL_GPIO_Init(ETH_TX_EN_GPIO_Port, &GPIO_InitStruct); // 初始化TX_EN引脚所在端口GPIO_InitStruct.Pin = ETH_TXD0_Pin|ETH_TXD1_Pin;GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;GPIO_InitStruct.Pull = GPIO_NOPULL;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;GPIO_InitStruct.Alternate = GPIO_AF11_ETH;HAL_GPIO_Init(GPIOG, &GPIO_InitStruct); // 初始化GPIOG/* USER CODE BEGIN ETH_MspInit 1 *//* 使能以太网全局中断 */HAL_NVIC_SetPriority(ETH_IRQn, 6, 0); // 设置中断优先级,抢占优先级6,子优先级0HAL_NVIC_EnableIRQ(ETH_IRQn); // 使能ETH中断/* 使能ETHERNET时钟 */__HAL_RCC_ETH_CLK_ENABLE();/* USER CODE END ETH_MspInit 1 */}
}/*** @brief 以太网PHY芯片复位函数* @param None* @retval None* 说明:通过控制PI1引脚产生复位脉冲,复位LAN8720等PHY芯片*/
static void Eth_Reset(void)
{ /* PHY复位:使用PI1引脚 */GPIO_InitTypeDef GPIO_InitStructure;__HAL_RCC_GPIOI_CLK_ENABLE(); // 使能GPIOI时钟GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP; // 推挽输出模式GPIO_InitStructure.Pull = GPIO_PULLUP; // 上拉GPIO_InitStructure.Speed = GPIO_SPEED_FAST; // 快速模式GPIO_InitStructure.Pin = GPIO_PIN_1; // 配置PI1引脚HAL_GPIO_Init(GPIOI, &GPIO_InitStructure); // 初始化GPIOIHAL_GPIO_WritePin(GPIOI, GPIO_PIN_1, GPIO_PIN_RESET); // 拉低复位引脚HAL_Delay(5); // 延时5msHAL_GPIO_WritePin(GPIOI, GPIO_PIN_1, GPIO_PIN_SET); // 拉高复位引脚HAL_Delay(5); // 延时5ms,完成复位
}/*** @brief ETH外设的MSP去初始化函数* @param ethHandle: 以太网句柄* @retval None* 说明:用于释放ETH相关的硬件资源,如GPIO、时钟等*/
void HAL_ETH_MspDeInit(ETH_HandleTypeDef* ethHandle)
{if(ethHandle->Instance==ETH){/* USER CODE BEGIN ETH_MspDeInit 0 */// 用户可在此处添加自定义去初始化代码/* USER CODE END ETH_MspDeInit 0 *//* 禁用外设时钟 */__HAL_RCC_ETH_CLK_DISABLE();/**ETH GPIO配置复位PC1 ------> ETH_MDCPA1 ------> ETH_REF_CLKPA2 ------> ETH_MDIOPA7 ------> ETH_CRS_DVPC4 ------> ETH_RXD0PC5 ------> ETH_RXD1PB11 ------> ETH_TX_ENPG13 ------> ETH_TXD0PG14 ------> ETH_TXD1 */HAL_GPIO_DeInit(GPIOC, ETH_MDC_Pin|ETH_RXD0_Pin|ETH_RXD1_Pin);HAL_GPIO_DeInit(GPIOA, ETH_REF_CLK_Pin|ETH_MDIO_Pin|ETH_CRS_DV_Pin);HAL_GPIO_DeInit(ETH_TX_EN_GPIO_Port, ETH_TX_EN_Pin);HAL_GPIO_DeInit(GPIOG, ETH_TXD0_Pin|ETH_TXD1_Pin);/* USER CODE BEGIN ETH_MspDeInit 1 */// 用户可在此处添加自定义去初始化代码/* USER CODE END ETH_MspDeInit 1 */}
}/*** @brief 以太网初始化函数* @param None* @retval HAL_StatusTypeDef: 初始化状态(HAL_OK表示成功)* 说明:配置ETH的MAC地址、速率、双工模式等参数,并初始化DMA描述符*/
HAL_StatusTypeDef Bsp_Eth_Init(void)
{HAL_StatusTypeDef ret; // 函数返回状态uint8_t MACAddr[6] ; // MAC地址数组HAL_ETH_DeInit(&heth); // 先去初始化ETH,确保干净的状态Eth_Reset(); // 复位PHY芯片// 复位ETH DMA总线模式寄存器ETH->DMABMR |= ETH_DMABMR_SR;/* 初始化ETH参数 */// 设置MAC地址(02:00:00:00:00:00)MACAddr[0] = 0x02;MACAddr[1] = 0x00;MACAddr[2] = 0x00;MACAddr[3] = 0x00;MACAddr[4] = 0x00;MACAddr[5] = 0x00;heth.Instance = ETH; // 指向ETH外设heth.Init.AutoNegotiation = ETH_AUTONEGOTIATION_ENABLE; // 使能自动协商heth.Init.PhyAddress = LAN8720_PHY_ADDRESS; // 设置PHY地址(LAN8720的地址)heth.Init.MACAddr = &MACAddr[0]; // 指向MAC地址heth.Init.RxMode = ETH_RXINTERRUPT_MODE; // 接收模式:中断模式heth.Init.ChecksumMode = ETH_CHECKSUM_BY_HARDWARE; // 硬件校验和计算heth.Init.MediaInterface = ETH_MEDIA_INTERFACE_RMII; // 媒体接口:RMIIheth.Init.Speed = ETH_SPEED_100M; // 以太网速度:100Mbpsheth.Init.DuplexMode = ETH_MODE_FULLDUPLEX; // 双工模式:全双工/* 配置以太网外设(GPIOs, 时钟, MAC, DMA) */ret = HAL_ETH_Init(&heth);if (ret == HAL_OK)PRINT_DEBUG("eth hardware init sucess...\n"); elsePRINT_DEBUG("eth hardware init faild...\n"); /* 初始化发送DMA描述符列表(链式模式) */HAL_ETH_DMATxDescListInit(&heth, DMATxDscrTab, &Tx_Buff[0][0], ETH_TXBUFNB);/* 初始化接收DMA描述符列表(链式模式) */HAL_ETH_DMARxDescListInit(&heth, DMARxDscrTab, &Rx_Buff[0][0], ETH_RXBUFNB);/* 使能MAC和DMA的发送和接收功能 */ return ret;
}/*** @brief 以太网中断服务函数* @param None* @retval None* 说明:ETH中断发生时触发,调用HAL库的中断处理函数,并进行任务调度管理*/
void ETH_IRQHandler(void)
{uint32_t ulReturn;/* 进入临界段,保护中断处理过程不被其他中断干扰(支持嵌套) */ulReturn = taskENTER_CRITICAL_FROM_ISR();HAL_ETH_IRQHandler(&heth); // 调用HAL库的ETH中断处理函数/* 退出临界段 */taskEXIT_CRITICAL_FROM_ISR( ulReturn );
}/*** @brief 以太网接收完成回调函数* @param heth: ETH句柄* @retval None* 说明:当ETH接收数据完成后,HAL库自动调用此函数*/
extern xSemaphoreHandle s_xSemaphore; // 外部声明的信号量,用于同步接收事件
void HAL_ETH_RxCpltCallback(ETH_HandleTypeDef *heth)
{
// LED2_TOGGLE; // 注释掉的LED翻转代码,可用于调试接收状态portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE;// 从ISR中释放信号量,通知应用层有数据接收xSemaphoreGiveFromISR( s_xSemaphore, &xHigherPriorityTaskWoken );// 如果有更高优先级的任务被唤醒,则进行任务切换portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}/*** @brief 以太网发送完成回调函数* @param heth: ETH句柄* @retval None* 说明:当ETH发送数据完成后,HAL库自动调用此函数*/
void HAL_ETH_TxCpltCallback(ETH_HandleTypeDef *heth)
{; // 此处未实现具体功能,可根据需求添加发送完成后的处理逻辑
}/*** @brief 以太网错误回调函数* @param heth: ETH句柄* @retval None* 说明:当ETH发生错误时,HAL库自动调用此函数*/
void HAL_ETH_ErrorCallback(ETH_HandleTypeDef *heth)
{PRINT_ERR("eth err\n"); // 打印以太网错误信息
}
3.4.3 修改 stm32f4xx_hal_conf.h 文件
有人可能会问了, PHY 的初始化在哪呢?其实,调用 HAL_ETH_Init()函数的时候,HAL 库就已经对我们的 PHY 进行初始化了,当然,每个不一样的 PHY 肯定是不一样的配置,所以,这就需要我们自己对 PHY 参数进行配置,我们开发板使用的是 LAN8720A 芯片, LAN8720A 复位时需要一段延时时间,这里需要定义延时时间长度,大约 5~50ms 即可,驱动代码中需要获取 PHY 的速度和工作模式, LAN8720A 的 R31 是特殊控制/状态寄存器,包括指示以太网速度和工作模式的状态位,所以,我们需要在stm32f4xx_hal_conf.h中添加我们自己的 PHY 配置,具体见代码清单。
#ifndef __BSP_ETH_H__
#define __BSP_ETH_H__#include "stm32f4xx_hal.h"/* FreeRTOS头文件 */
#include "FreeRTOS.h"
#include "task.h"/**ETH GPIO Configuration
PC1 ------> ETH_MDC
PA1 ------> ETH_REF_CLK
PA2 ------> ETH_MDIO
PA7 ------> ETH_CRS_DV
PC4 ------> ETH_RXD0
PC5 ------> ETH_RXD1
PB11 ------> ETH_TX_EN
PG13 ------> ETH_TXD0
PG14 ------> ETH_TXD1
*//* Private defines --------------------------------------*/
#define ETH_MDC_Pin GPIO_PIN_1
#define ETH_MDC_GPIO_Port GPIOC
#define ETH_REF_CLK_Pin GPIO_PIN_1
#define ETH_REF_CLK_GPIO_Port GPIOA
#define ETH_MDIO_Pin GPIO_PIN_2
#define ETH_MDIO_GPIO_Port GPIOA
#define ETH_CRS_DV_Pin GPIO_PIN_7
#define ETH_CRS_DV_GPIO_Port GPIOA
#define ETH_RXD0_Pin GPIO_PIN_4
#define ETH_RXD0_GPIO_Port GPIOC
#define ETH_RXD1_Pin GPIO_PIN_5
#define ETH_RXD1_GPIO_Port GPIOC
#define ETH_TX_EN_Pin GPIO_PIN_11
#define ETH_TX_EN_GPIO_Port GPIOB
#define USART1_TX_Pin GPIO_PIN_9
#define USART1_TX_GPIO_Port GPIOA
#define USART1_TXA10_Pin GPIO_PIN_10
#define USART1_TXA10_GPIO_Port GPIOA
#define ETH_TXD0_Pin GPIO_PIN_13
#define ETH_TXD0_GPIO_Port GPIOG
#define ETH_TXD1_Pin GPIO_PIN_14
#define ETH_TXD1_GPIO_Port GPIOGHAL_StatusTypeDef Bsp_Eth_Init(void);#endif