LWIP--以太网
一:硬件说明
1、MAC 简介
STM32F407 自带有 10/100Mbit/s 的以太网 MAC 内核,这个以太网 MAC 内核有如下特性:
1、支持外部 PHY 接口实现 10/100Mbit/s 数据传输速率。
2、通过符合 IEEE802.3 的 MII 接口与外接快速以太网 PHY 进行通信
3、支持全双工和半双工操作
4、报头和帧起始数据 (SFD) 在发送路径中插入、在接收路径中删除
5、可逐帧控制 CRC 和 pad 自动生成。
6、可编程帧长度,支持高达 16KB 的巨型帧。
7、可编程帧间隔
8、支持通过 MDIO 接(标注说明:这个意思就是 MDIO 接口)

1.2、SMI 接口
F407 的 MAC 有 3 种接口:SMI、MII 和 RMII。SMI 称为站管理接口,程序中可以通过这个接口来访问 PHY 寄存器,SMI 接口有两条线:数据线 MDIO 和时钟线 MDC,该接口支持访问多达 32 个 PHY。
- MDC:周期性时钟,提供以最大频率 2.5 MHz 传输数据时的参考时序,在空闲状态下,SMI 管理接口将 MDC 时钟信号驱动为低电平。
- MDIO:数据输入 / 输出比特流,用于通过 MDC 时钟信号向 / 从 PHY 设备同步传输状态信息。
A.MII 接口
MII 接口:介质独立接口 (MII) 定义了 10 Mbit/s 和 100 Mbit/s 的数据传输速率下 MAC 子层与 PHY 之间的互连。

时钟说明
TX_CLK 和 RX_CLK 为发送和接收连续时钟,当速率为 10Mbit/s 时为 2.5MHZ,速率为 100Mbit/s 时为 25MHZ(标注说明:这个一乘就出来了TX和RX都是4个)。
- 时钟需求:为生成 MII 接口的 TX_CLK 和 RX_CLK 时钟,需向外部 PHY 提供 25MHz 时钟。
- 供给方式
- 方式一:使用 25M 晶振直接提供。
- 方式二:利用 STM32F4xx 的MCO 引脚(微控制器时钟输出引脚)配置输出 25MHz 时钟。
- 功能意义:25MHz 时钟是 MII 接口在 100Mbit/s 速率下(或分频到 10Mbit/s)实现 MAC 与 PHY 时序同步的关键,两种供给方式为硬件设计提供了灵活性,开发者可根据电路复杂度、时钟精度需求选择方案。

B.精简介质独立接口(RMII)
- 接口作用:降低 10/100 Mbit/s 下微控制器以太网外设与外部 PHY 间的引脚数。
- 引脚数对比:根据 IEEE 802.3u 标准,MII 包含 16 个数据和控制信号引脚,RMII 将引脚数减少为 7 个,引脚数减少 62.5%。
- 时钟要求:RMII 接口的参考时钟必须为 50MHz。
- 接口信号(STM32 与外部 PHY 之间):TXD [1:0]、TX_EN、RXD [1:0]、CRS_DV、MDC、MDIO、REF_CLK(由时钟源提供)。

RMII 时钟源
- 驱动方式:通常采用 50MHz 时钟直接驱动 PHY,或借助嵌入式 PLL 将 25MHz 时钟倍频生成 50MHz 频率来驱动 PHY。
- 功能意义:为 RMII 接口提供 50MHz 参考时钟,确保 10/100 Mbit/s 速率下 STM32 与外部 PHY 的时序同步,是 RMII 接口稳定工作的关键时钟保障,两种方案为硬件时钟源设计提供了灵活选择。

我们使用的是探索者,使他外部的25MHZ晶振提供给PHY芯片作为时钟。支持2中接口

2、YT8512C 简介
- 芯片类型:低功耗单端口 10/100Mbps 以太网 PHY 芯片。
- 物理层功能:通过两条标准双绞线电缆收发器,实现发送和接收数据所需的所有物理层功能。
- 接口类型:通过标准 MII 和 RMII 接口与 MAC 层进行连接。

- PHY 地址设置:通过 LED0 和 LED1 引脚的电平进行配置。
- 时钟来源:PHY 内部时钟由 XTAL 和 Clock 引脚提供。
- 数据收发接口:TXP、TXN、RXP 和 RXN 引脚连接至 RJ45 网口,实现数据的发送与接收功能。
| Pin24(LED0/PHYAD[0]) | Pin25(LED1/PHYAD[1]) | PHY 地址 |
|---|---|---|
| 0 | 0 | 00000 |
| 0 | 1 | 00010 |
| 1 | 0 | 00001 |
| 1 | 1 | 00011 |

二:软件
1、以太网 DMA 描述符
4.1、以太网 DMA 描述符
- DMA 特性:F407 有一个以太网专用的 DMA,可在 CPU 完全不干预的情况下,通过描述符有效将数据从源传送到目标,接收缓冲区和发送缓冲区的数据均可通过以太网 DMA 传送。
- 描述符数量与结构:
- 共有两个描述符,一个用于接收,一个用于发送。
- 描述符是一种链表,最后一个描述符会指回第一个描述符构成环形结构。
- 存储位置:描述符列表位于主机的物理存储空间,两个列表的基址分别写入 ETH_DMARDLAR 寄存器和 ETH_DMATDLAR 寄存器中。
- 缓冲区指向:每个描述符最多可指向两个缓冲区。
- 结构类型:描述符共有两种结构,分别是环形结构和链接结构。

在 ST 提供的以太网驱动库 stm32f4x7_eth.c 中使用的是链接结构,链接结构如下:

- 描述符包含 Control/Status、Buffer Count、Buffer Address、Next Descriptor
- 1、一个以太网数据包可以跨越一个或多个 DMA 描述符
- 2、一个 DMA 描述符只能用于一个以太网数据包
- 3、DMA 描述符列表中的最后一个描述符指向第一个,形成链式结构
描述符不是实际存在的物理结构,而是在程序中通过软件实现的,其实就是一个结构体!在 ST 的以太网驱动库 stm32f4x7_eth.h 中有个结构体 ETH_DMADESCTypeDef,这个结构体就是 DMA 描述符!
typedef struct {__IO uint32_t Status; //状态uint32_t ControlBufferSize; //控制和buffer1,buffer2的长度uint32_t Buffer1Addr; //buffer1地址uint32_t Buffer2NextDescAddr; //buffer2地址或下一个描述符地址
//一下只有增强的以太网DMA描述符含有
#ifdef USE_ENHANCED_DMA_DESCRIPTORSuint32_t ExtendedStatus; //增强描述符状态uint32_t Reserved1; //保留uint32_t TimeStampLow; //时间戳低位uint32_t TimeStampHigh; //时间戳高位
#endif
} ETH_DMADESCTypeDef;
DMA 描述符类型说明
- 分类:分为增强描述符和常规描述符,本次仅讲解常规描述符(因网络例程仅使用常规描述符)。
- 结构差异:常规描述符与增强描述符的结构体成员变量不同,常规描述符仅使用前 4 个成员变量。
- 细分类型:常规描述符和增强描述符又分为发送描述符和接收描述符两种。
- 常规发送描述符:

2、ST 以太网驱动库简介
- 驱动库获取:ST 提供的以太网驱动库可从 ST 官网下载,也已放置在 F4 开发板光盘中。
- 文件组成:
文件名 描述 stm32f4x7_eth.h 以太网驱动头文件 stm32f4x7_eth.c 以太网驱动库 stm32f4x7_eth_conf_template.h 以太网驱动配置文件 - 使用说明:使用时需将
stm32f4x7_eth_conf_template.h重命名为stm32f4x7_eth_conf.h。
1.1、pbuf 结构
- 功能背景:LWIP 是 TCP/IP 协议栈的一种具体实现,本质是对数据包的处理,在 LWIP 中使用
pbuf结构管理数据包,相关源码文件为pbuf.c和pbuf.h。 - pbuf 结构体定义及成员说明:
struct pbuf
{struct pbuf *next; //构成链表时指向下一个pbufvoid *payload; //指向数据缓冲区u16_t tot_len; //pbuf链表中所有pbuf的数据长度u16_t len; //当前pbuf中的数据长度u8_t type; //pbuf类型u8_t flags; //状态u16_t ref; //记录当前pbuf被引用的次数
};
1.2、pbuf 类型
- 类型标识:pbuf 结构的
type字段用于描述当前 pbuf 类型,在pbuf.h文件中通过枚举类型pbuf_type定义。 - 枚举类型定义及说明:
c
运行
typedef enum
{PBUF_RAM, //pbuf数据紧跟着pbuf的结构存储,数据存储在ram中PBUF_ROM, //pbuf数据存储在rom中PBUF_REF, //pbuf数据存储在ram中,但是与pbuf结构的位置无关PBUF_POOL //pbuf结构和其数据存储在同一个内存池中
} pbuf_type;///////////////////////////////////////////////////////////////////////////
/*** pbuf数据存储在RAM中,主要用于发送(TX)。struct pbuf及其有效载荷在一块连续的内存中分配(因此可通过struct pbuf计算出第一个有效载荷字节的位置)。* pbuf_alloc() 会将PBUF_RAM类型的pbuf分配为无链式结构的pbuf(不过未来版本可能会变更)。* 所有出站数据包(发送用)都应使用该类型。*/
PBUF_RAM = (PBUF_ALLOC_FLAG_DATA_CONTIGUOUS | PBUF_TYPE_FLAG_STRUCT_DATA_CONTIGUOUS | PBUF_TYPE_ALLOC_SRC_MASK_STD_HEAP),/*** pbuf数据存储在ROM中,即struct pbuf与其有效载荷位于完全不同的内存区域。由于其指向ROM,在排队发送时无需复制有效载荷。*/
PBUF_ROM = PBUF_TYPE_ALLOC_SRC_MASK_STD_MEMP_PBUF,/*** pbuf来自pbuf池。与PBUF_ROM类似,但有效载荷可能会改变,因此在排队发送前需根据对其的“引用(ref)”情况进行复制。*/
PBUF_REF = (PBUF_TYPE_FLAG_DATA_VOLATILE | PBUF_TYPE_ALLOC_SRC_MASK_STD_MEMP_PBUF),/*** pbuf有效载荷指向RAM。该类型来自内存池,应用于接收(RX)。有效载荷可链式存储(分散-聚集式接收),且与PBUF_RAM类似,struct pbuf及其有效载荷在一块连续的内存中分配(因此可通过struct pbuf计算出第一个有效载荷字节的位置)。* 不要将其用于发送(TX),若因TCP排队导致内存池耗尽,将无法接收TCP确认包!*/
PBUF_POOL = (PBUF_ALLOC_FLAG_RX | PBUF_TYPE_FLAG_STRUCT_DATA_CONTIGUOUS | PBUF_TYPE_ALLOC_SRC_MASK_STD_MEMP_PBUF_POOL)
PBUF_RAM 类型
- 内存申请来源:PBUF_RAM 类型的 pbuf 空间从 LWIP 的内存堆中申请。
- 应用场景:协议栈和应用程序中待发送数据采用该类型。
- 申请方式:在
pbuf_alloc()中进行申请,代码如下:
case PBUF_RAM:p = (struct pbuf*)mem_malloc(LWIP_MEM_ALIGN_SIZE\(SIZEOF_STRUCT_PBUF + offset) + LWIP_MEM_ALIGN_SIZE(length));if (p == NULL){return NULL;}p->payload = LWIP_MEM_ALIGN((void *)((u8_t *)p + SIZEOF_STRUCT_PBUF + offset));p->len = p->tot_len = length;p->next = NULL;p->type = type;break;
PBUF_POOL 类型
- 内存申请来源:PBUF_POOL 类型的 pbuf 空间从 LWIP 的内存池中申请。
- 特性:因从内存池申请,分配时间极短。
- 应用场景:网卡接收数据包时使用该类型。
- 申请代码:
c
运行
case PBUF_POOL:p = (struct pbuf *)memp_malloc(MEMP_PBUF_POOL);if (p == NULL){PBUF_POOL_IS_EMPTY();return NULL;}p->type = type;p->next = NULL;p->payload = LWIP_MEM_ALIGN((void *)((u8_t *)p + (SIZEOF_STRUCT_PBUF + offset)));break;
PBUF_ROM 和 PBUF_REF 类型
- 内存申请来源:PBUF_ROM 和 PBUF_REF 类型的 pbuf 空间从 LWIP 的内存池中申请,使用内存池
MEMP_PBUF。 - 内存分配特点:仅申请 pbuf 结构体的内存空间,不包含数据空间。
- 分配代码:
c
运行
case PBUF_ROM:
case PBUF_REF:p = (struct pbuf *)memp_malloc(MEMP_PBUF);if (p == NULL){return NULL;}p->payload = NULL;p->len = p->tot_len = length;p->next = NULL;p->type = type;break;
PBUF_ROM 和 PBUF_REF 类型数据空间说明
- 数据空间特点:PBUF_ROM 和 PBUF_REF 类型未给数据空间申请内存,数据空间来自其他地方的内存。
- 数据空间差异:
- PBUF_ROM 的数据空间在 ROM 中。
- PBUF_REF 的数据空间在 RAM 中。

pbuf 的申请、释放与分层
- 操作函数:
- 申请:
pbuf_alloc(pbuf_layer layer, u16_t length, pbuf_type type) - 释放:
pbuf_free(struct pbuf *p)
- 申请:
pbuf_alloc参数说明:layer:决定协议栈的申请层级,影响 pbuf 的数据区首部预留空间(offset)。type:决定申请的 pbuf 类型。
pbuf_layer枚举(定义于pbuf.h):
c
运行
typedef enum {PBUF_TRANSPORT, //传输层PBUF_IP, //IP层PBUF_LINK, //链路层PBUF_RAW //原始层
} pbuf_layer;
| 函数分组 | API 函数 | 函数功能描述 |
|---|---|---|
| TCP 连接建立 | tcp_new() | 创建一个 TCP 的 PCB 控制块 |
| tcp_bind() | 为 TCP 的 PCB 控制块绑定一个本地 IP 地址和端口号 | |
| tcp_listen() | 开始 TCP 的 PCB 监听 | |
| tcp_accept() | 控制块 accept 字段注册的回调函数,侦听到连接时被调用 | |
| tcp_accented() | 通知 LWIP 协议栈一个 TCP 连接被接受了(注:原表格可能存在笔误,应为 tcp_accepted ()) | |
| tcp_connect() | 连接远端主机 | |
| 发送 TCP 数据 | tcp_write() | 构造一个报文并放到控制块的发送缓冲队列中 |
| tcp_sent() | 控制块 sent 字段注册的回调函数,数据发送成功后被回调 | |
| tcp_output() | 将发送缓冲队列中的数据发送出去 | |
| 接收 TCP 数据 | tcp_recv() | 控制块 recv 字段注册的回调函数,当接收到新数据时被调用 |
| tcp_recved() | 当程序处理完数据后一定要调用这个函数,通知内核更新接收窗口 | |
| 轮询函数 | tcp_poll() | 控制块 poll 字段注册的回调函数,该函数周期性调用 |
| 关闭和中止连接 | tcp_close() | 关闭一个 TCP 连接 |
| tcp_err() | 控制块 err 字段注册的回调函数,遇到错误时被调用 | |
| tcp_abort() | 中断 TCP 连接 |
| 函数 | 描述 |
|---|---|
| tcp_client_set_remoteip() | 设置远端 IP 地址,即服务器地址 |
| tcp_client_test() | TCP 客户端测试程序 |
| tcp_client_connected() | TCP 连接建立后调用的回调函数 |
| tcp_client_recv() | 接收到数据时的回调函数 |
| tcp_client_error() | 接收错误时的回调函数 |
| tcp_client_pool() | 轮询函数 |
| tcp_client_sent() | 发送回调函数 |
| tcp_client_senddata() | 发送数据 |
| tcp_client_connection_close() | 关闭 TCP 连接 |
具体的请参考正点原子的PDF
