本帖最后由 DKENNY 于 2025-5-26 16:15 编辑
#申请原创# #技术资源# @21小跑堂
前言
今天我们继续讲讲ETH这玩意儿,我们知道ETH在使用的时候,会有个专门的DMA使用,文档里说以太网通信需要DMA,代码里要配置DMA,但DMA到底是干啥的?为啥以太网非得用它?它跟其他外设(比如串口USART)的DMA有啥不一样?不用DMA行不行?
1、引言:DMA是个啥?为啥以太网离不开它?
如果我们在用APM32F407开发一个联网设备,比如一个能跟远程服务器聊天的智能传感器,我们的程序要发送一大堆数据(比如传感器读数)到服务器,或者从服务器接收一堆数据,这时候,APM32F407的以太网模块就像一个“快递公司”,负责把数据打包、发送,或者接收、拆包。但问题来了:这些数据量可能很大,频繁地搬来搬去会让CPU忙得喘不过气。怎么办?这就是 DMA 登场的时候!
DMA(Direct Memory Access,直接内存访问)就像快递公司里的“自动搬运机器人”。它可以在CPU“偷懒”的时候,自动把数据从一个地方(比如内存)搬到另一个地方(比如以太网模块),或者反过来搬回来。有了DMA,CPU就不用亲自操心数据的搬运,可以专心干别的活儿(比如处理传感器数据)。
在以太网通信中,DMA的角色尤其重要,因为以太网数据量大、速度快,手动搬运根本忙不过来。这篇文章将带你搞清楚:
1. DMA在以太网模块中具体干了啥?
2. 它传输的是什么数据?
3. 跟普通外设(像USART)的DMA有啥区别?
4. 它怎么帮单片机跟服务器通信?
5. 不用DMA会发生什么?
2、DMA的基础知识:从“搬运工”到“超级助手”
2.1 DMA是什么?
DMA是“直接内存访问”的缩写,顾名思义,它能直接访问内存,把数据从一个地方搬到另一个地方,而不需要CPU亲自参与。正常情况下,CPU就像个“勤劳的搬运工”,每次都要亲自把数据从内存读出来,写到外设(比如以太网模块)里,或者反过来。但如果数据量很大,CPU就会累得不行,效率也低。
DMA就像一个聪明的“机器人”,可以接管搬运任务。它的工作方式是:
- CPU通知DMA:“你把这块内存的数据搬到以太网模块去!”
- DMA就自动开始搬运,搬完后通知CPU:“活儿干完了!”
- CPU在这期间可以干别的,比如计算、控制LED,或者啥也不干(省电!)。
2.2 为什么以太网需要DMA?
以太网通信跟其他外设(比如串口)比起来,有几个“硬核”特点:
- 数据量大:一个以太网数据包(帧)可能有几十到上千字节,远超串口的几十字节。
- 速度快:以太网支持10Mbps或100Mbps,数据来得又快又猛,CPU手动搬运根本跟不上。
- 实时性强:网络通信讲究“低延迟”,如果CPU忙着搬数据,可能会错过关键时机,导致丢包。
DMA的出现完美解决了这些问题。这玩意儿就是一个“超级搬运工”,能快速、批量地把数据搬来搬去,让以太网通信又快又稳。
图1:DMA在以太网通信中的角色
3、DMA在APM32F407以太网模块中的作用:它具体干了啥?
APM32F407的以太网模块内置了MAC(Media Access Control,介质访问控制),负责处理以太网帧的逻辑(打包、拆包、校验等)。但MAC只是个“打包员”,它需要有人把数据送到它手里,或者把收到的数据搬走。这就是DMA的舞台!
3.1 DMA传输的内容:搬运的是啥?
在以太网通信中,DMA主要搬运的是以太网数据帧(Ethernet Frame)。一个以太网帧包含:
- 前导码:用来同步信号。
- 目标地址和源地址:标明数据发给谁、从哪儿来。
- 类型字段:说明数据是什么类型(例如IP包)。
- 数据载荷:真正的“货物”,例如你的传感器数据或服务器的响应。
- 校验码(CRC):用来检查数据有没有出错。
DMA的任务是:
- 发送时:把内存里准备好的以太网帧(包括头、数据和校验码)搬到MAC的发送缓冲区(TX Buffer)。
- 接收时:把MAC接收缓冲区(RX Buffer)里的以太网帧搬到内存,供程序处理。
3.2 以太网DMA的工作流程
APM32F407的以太网模块有专门的DMA控制器,分为 发送DMA(TX DMA)和 接收DMA(RX DMA)。以下是它们的工作流程:
发送流程
1. 程序准备数据:
- 你的程序(比如用LwIP协议栈)生成一个数据包,比如一个UDP包(“Hello, Server!”)。
- 数据包被封装成以太网帧,存储在内存的某个区域(比如一个数组)。
2. 配置DMA:
- CPU告诉TX DMA:“把内存地址0x20001000开始的1500字节数据搬到MAC的TX Buffer!”
- 配置包括源地址(内存)、目标地址(TX Buffer)、数据长度等。
3. DMA搬运:
- TX DMA自动从内存读取数据,搬到MAC的发送缓冲区。
- 搬运过程中,CPU可以干别的(比如处理传感器数据)。
4. MAC发送:
- MAC把缓冲区的数据通过PHY(物理层芯片)发送到网线。
- 发送完成后,DMA通知CPU:“数据发完了!”
接收流程
1. MAC接收数据:
- PHY从网线接收到数据帧,交给MAC。
- MAC把数据帧存到接收缓冲区(RX Buffer)。
2. 配置DMA:
- CPU告诉RX DMA:“把MAC的RX Buffer数据搬到内存地址0x20002000!”
- 配置包括源地址(RX Buffer)、目标地址(内存)、最大长度等。
3. DMA搬运:
- RX DMA自动从RX Buffer读取数据,搬到内存。
- 搬完后,DMA通知CPU:“新数据到啦!”
4. 程序处理:
- 程序从内存读取数据,交给协议栈处理(比如解析服务器的响应)。
图2:以太网DMA工作流程
4、与普通外设的DMA对比:以太网DMA有啥特别?
你可能听说过其他外设(比如USART、SPI)也用DMA,比如串口用DMA传输一串字符。乍一看,以太网的DMA跟这些好像差不多,都是“搬数据”。但实际上,以太网DMA有几个独特之处,让它跟普通外设的DMA大不相同。
4.1 数据量和速度
普通外设(USART):
- 数据量小:串口一次传输几十字节,比如一个字符串“Hello”。
- 速度慢:USART的波特率通常是115200bps(约11.5KB/s),远低于以太网的100Mbps(12.5MB/s)。
- DMA任务简单:一次搬运几十字节,CPU手动搬运也能勉强应付。
以太网:
- 数据量大:一个以太网帧可能有1500字节,连续传输可能有成千上万个帧。
- 速度快:100Mbps的速率意味着每秒要处理12.5MB数据,CPU手动搬运根本忙不过来。
- DMA任务复杂:需要高效、批量地搬运大数据块,还要保证实时性。
串口DMA就像搬运一篮子苹果,慢悠悠搬就行;而以太网DMA像搬运一卡车货物,必须用高速传送带。
4.2 数据结构
普通外设(USART):
- 数据是简单的字节流,比如一串字符,没有复杂的结构。
- DMA直接把字节从内存搬到串口寄存器,或者反过来。
以太网:
- 数据是以太网帧,有复杂的结构(前导码、地址、数据、校验码)。
- DMA需要按照帧的格式搬运,还要配合MAC的缓冲区管理(TX/RX Descriptor)。
串口DMA就像搬运一堆散装零件,简单直接;而以太网DMA像搬运一箱箱精心打包的快递,得多留心格式。
4.3 缓冲区管理
普通外设(USART):
- 通常只有一个简单的缓冲区(比如串口发送寄存器)。
- DMA直接把数据搬进/搬出这个寄存器,管理简单。
以太网:
- 以太网模块有专门的发送和接收缓冲区(TX Buffer和RX Buffer),通过描述符(Descriptor)管理。
- 描述符是一个数据结构,告诉DMA每个数据帧的地址、长度、状态等信息。
- DMA需要根据描述符一个接一个地搬运帧,管理复杂得多。
串口DMA就像把东西扔进一个小篮子;而以太网DMA像管理一个大仓库,货物按清单(描述符)整齐摆放。
4.4 中断和实时性
普通外设(USART):
- 传输完成后,DMA触发一次中断,通知CPU“搬完了”。
- 实时性要求低,晚点处理也没啥大不了。
以太网:
- 数据帧来得快,DMA可能频繁触发中断(每次帧搬完都可能通知)。
- 实时性要求高,稍有延迟就可能丢包,影响网络通信。
串口DMA就像慢悠悠送封信,晚点没事;而以太网DMA像实时送快递,晚了客户就得投诉。
图3:以太网DMA与USART DMA对比
5、DMA如何辅助与远程服务器通信?
以太网通信的终极目标是让APM32F407跟远程服务器“聊天”,比如发送传感器数据或接收控制命令。DMA在其中扮演了“高速搬运工”的角色,确保数据顺畅流动。让我们以一个实际场景为例,来看看DMA是怎么干活的。
5.1 场景:发送传感器数据到服务器
假设你用APM32F407开发一个温湿度传感器,通过以太网把数据发送到远程服务器。流程如下:
1. 程序生成数据
- 传感器每秒采集一次温湿度,生成一个JSON格式的数据包,比如`{"temp":25.5, "humidity":60}`。
- LwIP协议栈把数据封装成UDP包,加上IP头、UDP头,变成以太网帧,存储在内存(比如地址0x20001000)。
2. DMA搬运数据
- CPU配置TX DMA,告诉它:“把0x20001000的1500字节数据搬到MAC的TX Buffer!”
- TX DMA自动从内存读取数据,搬到发送缓冲区。
- 搬运过程中,CPU可以继续采集下一次传感器数据。
3. MAC和PHY发送
- MAC把缓冲区的数据通过PHY(比如LAN8720)发送到网线。
- 数据通过路由器、交换机,最终到达服务器。
4. DMA通知完成
- TX DMA搬完数据,触发中断,通知CPU:“发送缓冲区已准备好,可以发下一个帧了!”
DMA就像个快递员,把你打包好的传感器数据(快递包裹)从仓库(内存)送到卡车(MAC),然后卡车开往服务器。
5.2 场景:接收服务器的响应
现在服务器回复了一条命令,比如`{"command":"turn_on_led"}`。接收流程如下:
1. PHY和MAC接收
- PHY从网线接收到数据帧,交给MAC。
- MAC把数据存到接收缓冲区(RX Buffer)。
2. DMA搬运数据
- CPU配置RX DMA,告诉它:“把RX Buffer的数据搬到内存0x20002000!”
- RX DMA自动从缓冲区读取数据,搬到内存。
3. 程序处理
- RX DMA搬完数据,触发中断,通知CPU:“新数据到啦!”
- 程序从内存读取数据,解析命令,点亮LED。
这时,DMA像个收货员,把服务器送来的包裹(数据帧)从卡车(MAC)搬到仓库(内存),让你轻松处理。
5.3 DMA的贡献
DMA在与服务器通信中的作用可以总结为:
- 高效搬运:快速搬运大数据帧,适应以太网的高速需求。
- 解放CPU:让CPU专注逻辑处理(比如解析JSON、控制硬件),而不是忙于搬数据。
- 保证实时性:及时搬运数据,减少丢包,确保通信顺畅。
图4:DMA在服务器通信中的作用
6、如果不用DMA会怎么样?
为了突出DMA的重要性,我们来设想一下:如果以太网通信不用DMA,全靠CPU手动搬运,会发生什么?
6.1 CPU忙到“崩溃”
- 问题:以太网帧动辄上千字节,100Mbps速率下每秒有12.5MB数据。CPU需要不停地从内存读取数据,写入MAC的寄存器,或者反过来。
- 后果:CPU几乎全部时间都花在搬运数据上,没空干别的(比如处理传感器、更新显示屏)。系统会变得超级卡顿。
贴切的说,没有DMA的时候,CPU就像一个快递员,既要打包、搬货,还要开车送货,累得晕头转向。
6.2 数据丢失
- 问题:以太网数据来得快,CPU手动搬运可能跟不上节奏。如果MAC的接收缓冲区满了,新数据就会被丢弃。
- 后果:丢包严重,通信不稳定,可能连不上服务器。
贴切的说,没有DMA的时候,快递卡车送货太快,仓库来不及收,包裹就被扔了。
6.3 实时性差
- 问题:网络通信讲究低延迟,CPU忙于搬运会导致响应变慢。
- 后果:服务器发来的命令可能延迟处理,用户体验变差(比如LED晚点亮)。
贴切的说,没有DMA的时候,客户等着收快递,但快递员忙着搬货,送货慢得让人抓狂。
6.4 总结:不用DMA几乎不可能
以太网通信的高速和大流量特性决定了DMA的不可或缺。没有DMA,CPU会被数据搬运任务拖垮,通信效率和稳定性都会大幅下降。在实际开发中,APM32F407的以太网模块几乎总是搭配DMA使用。
7、DMA的实现:代码和配置
为了更直观地理解DMA,我们来看看在APM32F407上如何配置以太网DMA,以及实际的一些代码实现。
7.1 DMA配置的关键点
APM32F407的以太网DMA通过以下寄存器配置:
- ETH_DMATxDTR:发送描述符表寄存器,指向TX描述符列表。
- ETH_DMARxDTR:接收描述符表寄存器,指向RX描述符列表。
- ETH_DMACTLR:控制DMA的启动、停止和模式。
- ETH_DMAISR:中断状态寄存器,检查DMA传输状态。
描述符(Descriptor)是DMA的“任务清单”,每个描述符包含:
- 数据缓冲区的地址。
- 数据长度。
- 状态标志(比如传输完成、错误)。
7.2 代码示例:以太网DMA初始化
以下是初始化以太网DMA的代码,基于LAN8720(RMII接口):
#include "apm32f4xx_eth.h"
#include "apm32f4xx_gpio.h"
#include "apm32f4xx_rcm.h"
#define TX_DESC_NUM 4
#define RX_DESC_NUM 4
ETH_DMADesc_T txDescTable[TX_DESC_NUM];
ETH_DMADesc_T rxDescTable[RX_DESC_NUM];
uint8_t txBuffer[TX_DESC_NUM][1536]; // 每个缓冲区1536字节
uint8_t rxBuffer[RX_DESC_NUM][1536];
void ETH_DMA_Init(void)
{
// 使能以太网时钟
RCM_EnableAHB1PeriphClock(RCM_AHB1_PERIPH_ETHMAC | RCM_AHB1_PERIPH_ETHMACTX | RCM_AHB1_PERIPH_ETHMACRX);
// 初始化描述符表
ETH_DMADescConfig_T descConfig;
descConfig.buffer1Addr = (uint32_t)txBuffer;
descConfig.buffer2Addr = 0;
descConfig.length = 1536;
descConfig.descNum = TX_DESC_NUM;
ETH_DMATxDescConfig(txDescTable, &descConfig);
descConfig.buffer1Addr = (uint32_t)rxBuffer;
descConfig.descNum = RX_DESC_NUM;
ETH_DMARxDescConfig(rxDescTable, &descConfig);
// 配置DMA
ETH_DMAConfig_T dmaConfig;
dmaConfig.txDescTable = txDescTable;
dmaConfig.rxDescTable = rxDescTable;
dmaConfig.dropFrame = ENABLE;
ETH_DMAConfig(&dmaConfig);
// 启动DMA
ETH_DMAStart();
}
7.3 数据发送和接收
以下是使用DMA发送和接收数据的代码:
void ETH_SendPacket(uint8_t* data, uint32_t len)
{
// 填充发送缓冲区
memcpy(txBuffer[0], data, len);
// 配置描述符
txDescTable[0].length = len;
txDescTable[0].status = ETH_DMATXDESC_OWN;
// 触发发送
ETH_DMATxStart();
}
void ETH_ReceivePacket(uint8_t* buffer, uint32_t* len)
{
if (!(rxDescTable[0].status & ETH_DMARXDESC_OWN))
{
*len = rxDescTable[0].length;
memcpy(buffer, rxBuffer[0], *len);
rxDescTable[0].status = ETH_DMARXDESC_OWN; // 归还描述符
}
}
说明:
- 发送时,程序把数据拷贝到txBuffer,DMA根据描述符搬运到MAC。
- 接收时,DMA把RX Buffer的数据搬到rxBuffer,程序从中读取。
8、常见问题
在开发以太网应用时,DMA配置可能会遇到问题。以下是常见的坑和解决办法:
1. 问题:DMA不工作
- 检查DMA时钟是否使能(RCM_AHB1_PERIPH_ETHMAC)。
- 确认描述符表的地址和长度是否正确。
2. 问题:丢包严重
- 检查描述符数量是否足够(TX_DESC_NUM和RX_DESC_NUM太少会导致溢出)。
- 确保内存分配正确,避免缓冲区覆盖。
3. 问题:中断频繁
- 检查DMA中断配置,禁用不必要的中断(比如每次帧都触发)。
- 优化协议栈处理速度,减少DMA等待时间。
9、总结:DMA是不可或缺的“搬运英雄”
本文中,我们从DMA的定义、作用,到在APM32F407以太网模块中的具体实现,彻底揭开了它的神秘面纱。DMA是高速以太网通信的“搬运工”,负责把以太网帧在内存和MAC之间快速搬运,解放CPU,保证通信的效率和稳定性。
与普通外设(像USART)的DMA相比,以太网DMA处理的数据量更大、结构更复杂、实时性要求更高。没有DMA,CPU会被搬运任务拖垮,通信几乎无法进行。在与远程服务器的通信中,DMA确保数据顺畅流动,让你的传感器数据或控制命令快速到达目的地。
|
以太网通信中DMA的使用方法,作者从DMA的介绍到以太网中DMA起到的作用入手,以实际操作演示DMA如何提升以太网的通讯效率。整体流程完整,源码和解读较佳。