先排除lwip协议,使用最简单的链路发送接收就行,DMA该如何初始化,使用的芯片是DP83848
时间: 2025-08-18 20:19:35 浏览: 9
<think>我们正在配置STM32H750的以太网DMA,与DP83848 PHY通信,不使用LwIP,仅实现基本收发。
关键步骤:
1. 初始化ETH和PHY(DP83848)
2. 配置DMA描述符链表(用于接收和发送)
3. 编写数据收发函数
注意:由于不使用LwIP,我们需要自己管理缓冲区。
步骤详解:
1. 初始化ETH外设和PHY
- 使能ETH时钟
- 配置GPIO用于RMII或MII(根据硬件连接,DP83848通常使用RMII)
- 配置ETH基本参数(自动协商,双工模式等)
- 初始化PHY(DP83848)
2. 配置DMA描述符
- STM32H7的以太网DMA使用描述符链表来管理缓冲区。
- 需要初始化发送描述符链表和接收描述符链表。
3. 编写发送和接收函数
- 发送:将数据放入发送缓冲区,启动DMA传输。
- 接收:轮询或中断方式检查接收描述符,读取数据。
由于不使用协议栈,我们只处理原始以太网帧(需要自己构造MAC头等)。
具体配置代码示例(基于HAL库):
步骤1:初始化ETH和PHY
```c
#include "stm32h7xx_hal.h"
#include "stm32h7xx_hal_eth.h"
ETH_HandleTypeDef heth;
void ETH_Init(void)
{
// 使能ETH时钟
__HAL_RCC_ETH1MAC_CLK_ENABLE();
__HAL_RCC_ETH1TX_CLK_ENABLE();
__HAL_RCC_ETH1RX_CLK_ENABLE();
// 配置GPIO为RMII模式(根据你的硬件连接)
// 此处省略GPIO配置代码,请根据实际电路配置
heth.Instance = ETH;
heth.Init.MACAddr = (uint8_t *)MAC_ADDR; // 设置MAC地址
heth.Init.MediaInterface = ETH_MEDIA_INTERFACE_RMII;
heth.Init.RxDesc = DMA_RX_DESC_LIST; // 接收描述符链表
heth.Init.TxDesc = DMA_TX_DESC_LIST; // 发送描述符链表
heth.Init.RxBuffLen = ETH_RX_BUF_SIZE; // 接收缓冲区大小
if (HAL_ETH_Init(&heth) != HAL_OK)
{
// 初始化错误处理
}
// 配置DP83848 PHY
// 通过ETH MAC的SMI接口配置PHY
uint32_t phyreg;
HAL_ETH_ReadPHYRegister(&heth, DP83848_PHY_ADDR, DP83848_PHY_ID1, &phyreg);
// 检查PHY ID,确保PHY存在
// 设置PHY工作模式(例如:自动协商)
HAL_ETH_WritePHYRegister(&heth, DP83848_PHY_ADDR, DP83848_PHY_BMCR, DP83848_BMCR_AUTONEG_ENABLE);
// 等待自动协商完成
do {
HAL_Delay(100);
HAL_ETH_ReadPHYRegister(&heth, DP83848_PHY_ADDR, DP83848_PHY_BMSR, &phyreg);
} while (!(phyreg & DP83848_BMSR_AUTONEG_COMPLETE));
}
```
步骤2:配置DMA描述符链表
```c
// 定义描述符和缓冲区
#define ETH_RX_DESC_CNT 4 // 接收描述符数量
#define ETH_TX_DESC_CNT 4 // 发送描述符数量
#define ETH_RX_BUF_SIZE 1536 // 接收缓冲区大小(字节)
__ALIGNED(32) ETH_DMADescTypeDef DMARxDscrTab[ETH_RX_DESC_CNT]; // 接收描述符表(32字节对齐)
__ALIGNED(32) ETH_DMADescTypeDef DMATxDscrTab[ETH_TX_DESC_CNT]; // 发送描述符表
__ALIGNED(32) uint8_t Rx_Buff[ETH_RX_DESC_CNT][ETH_RX_BUF_SIZE]; // 接收缓冲区(32字节对齐,每个描述符对应一个缓冲区)
void ETH_DMA_DescInit(void)
{
ETH_DMADescTypeDef *DMARxDesc;
uint32_t i;
// 初始化接收描述符链表
DMARxDesc = DMARxDscrTab;
for (i=0; i<ETH_RX_DESC_CNT; i++)
{
// 设置Buffer1地址
DMARxDesc->Buffer1Addr = (uint32_t)Rx_Buff[i];
// 设置OWN位(由DMA控制),并设置接收结束中断(RER)和接收完成中断(RCH)
DMARxDesc->Status = ETH_DMARXDESC_OWN | ETH_DMARXDESC_RCH;
if (i == (ETH_RX_DESC_CNT-1))
{
// 最后一个描述符,设置环回(EOR)
DMARxDesc->Status |= ETH_DMARXDESC_EOR;
}
DMARxDesc->ControlBufferSize = ETH_RX_BUF_SIZE | ETH_DMARXDESC_RER;
if (i < (ETH_RX_DESC_CNT-1))
{
DMARxDesc->Buffer2NextDescAddr = (uint32_t)(DMARxDesc+1);
}
else
{
DMARxDesc->Buffer2NextDescAddr = (uint32_t)DMARxDscrTab; // 指向第一个描述符,形成环
}
DMARxDesc++;
}
// 初始化发送描述符链表(类似,但注意发送时不需要预先设置数据)
// ... 此处省略发送描述符初始化,与接收类似,但状态位不同
// 将描述符链表地址赋给ETH句柄
heth.RxDesc = DMARxDscrTab;
heth.TxDesc = DMATxDscrTab;
}
```
步骤3:启动ETH和DMA
```c
HAL_ETH_Start(&heth);
```
步骤4:数据发送函数
```c
void ETH_SendFrame(uint8_t *pData, uint16_t len)
{
ETH_DMADescTypeDef *DmaTxDesc;
uint32_t framelength = len;
uint32_t bufferoffset = 0;
uint32_t byteslefttocopy = 0;
uint32_t payloadoffset = 0;
// 获取当前发送描述符
DmaTxDesc = heth.TxDesc;
// 检查当前描述符是否由DMA占用(OWN位)
if ((DmaTxDesc->Status & ETH_DMATXDESC_OWN) != 0) {
// 描述符被DMA占用,发送失败(可以等待或返回错误)
return;
}
// 将数据复制到发送缓冲区(注意:这里我们假设一个描述符足够大,可以容纳整个帧)
// 如果帧长大于一个描述符的缓冲区,需要分片(这里简化处理)
memcpy((uint8_t *)DmaTxDesc->Buffer1Addr, pData, len);
// 设置发送描述符
DmaTxDesc->ControlBufferSize = len | ETH_DMATXDESC_LS | ETH_DMATXDESC_FS;
DmaTxDesc->Status = ETH_DMATXDESC_OWN; // 将描述符交给DMA
// 触发DMA发送
HAL_ETH_TransmitFrame(&heth, len);
}
```
步骤5:数据接收(轮询方式)
```c
uint16_t ETH_ReceiveFrame(uint8_t *pData)
{
ETH_DMADescTypeDef *DmaRxDesc;
uint32_t framelength = 0;
// 获取当前接收描述符
DmaRxDesc = heth.RxDesc;
// 检查是否有数据到达(OWN位被清除,表示DMA已经将数据放入缓冲区并交给CPU)
if ((DmaRxDesc->Status & ETH_DMARXDESC_OWN) == 0)
{
// 检查是否有错误(RER, RES等位)
if ((DmaRxDesc->Status & ETH_DMARXDESC_ES) == 0)
{
// 获取帧长度(从描述符中)
framelength = ((DmaRxDesc->Status & ETH_DMARXDESC_FL) >> ETH_DMARXDESC_FRAMELENGTHSHIFT) - 4; // 减去CRC长度(4字节)
// 复制数据到用户缓冲区
memcpy(pData, (uint8_t *)DmaRxDesc->Buffer1Addr, framelength);
// 将描述符归还给DMA(设置OWN位)
DmaRxDesc->Status = ETH_DMARXDESC_OWN;
heth.RxDesc = DmaRxDesc->Buffer2NextDescAddr; // 指向下一个描述符
return framelength;
}
else
{
// 错误处理:直接归还描述符
DmaRxDesc->Status = ETH_DMARXDESC_OWN;
heth.RxDesc = DmaRxDesc->Buffer2NextDescAddr;
return 0;
}
}
return 0;
}
```
注意:以上代码为简化示例,实际应用中需要考虑:
- 描述符链表的维护(环状结构)
- 错误处理
- 中断方式接收(推荐使用中断,轮询会占用CPU)
- 发送和接收缓冲区管理
对于接收,我们使用轮询方式,实际可以使用接收中断,在中断中处理数据。
配置中断:
```c
// 在初始化后使能中断
HAL_NVIC_SetPriority(ETH_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(ETH_IRQn);
// 在中断处理函数中
void ETH_IRQHandler(void)
{
HAL_ETH_IRQHandler(&heth);
}
// 在回调函数中处理接收事件
void HAL_ETH_RxAllocateCallback(uint8_t **pData)
{
// 这里可以分配新的缓冲区,但我们的描述符链表是静态的,所以不需要动态分配
// 我们使用静态缓冲区,因此这个回调可以不实现(或者简单返回一个缓冲区)
}
void HAL_ETH_RxCompleteCallback(void)
{
// 接收到一帧数据,可以在这里处理
// 注意:这个回调在中断上下文中,建议设置标志位,在任务中处理
}
```
但是,由于我们使用的是原始DMA描述符操作,HAL库的中断回调可能不直接适用。我们可以自己处理中断标志位。
更直接的方式是:在中断中检查接收中断标志,然后调用我们的接收函数。
总结:以上代码提供了一个不使用LwIP的基本以太网收发框架。实际测试时,需要构造正确的以太网帧(包括MAC头、类型/长度字段和数据)。同时,需要确保PHY(DP83848)与STM32H750的硬件连接正确(RMII或MII)。
注意:MAC地址需要设置,且发送的帧目的MAC地址要正确(例如广播或特定目标)。
相关问题:
1. 如何构造一个ARP请求以太网帧?
2. 如何解析接收到的以太网帧?
3. 如何优化STM32H750的以太网DMA传输性能?
4. 在无操作系统环境下,如何高效管理以太网接收中断?</think>以下是针对STM32H750与DP83848 PHY芯片的以太网DMA初始化配置指南,不使用LwIP协议栈,实现基本数据收发功能。配置基于STM32 HAL库,分为关键步骤和代码实现:
### 一、配置步骤
1. **硬件初始化**
- 启用ETH时钟和GPIO
- 配置RMII接口(DP83848常用)
- 设置PHY地址(DP83848默认0x01)
2. **DMA描述符配置**
- 创建发送/接收描述符链表(环形缓冲区)
- 设置缓冲区所有权(OWN位)和中断标志
3. **以太网参数设置**
- MAC地址配置
- 自动协商使能
- DMA传输模式设置
4. **数据收发实现**
- 发送:填充缓冲区→启动DMA
- 接收:轮询/中断检查OWN位
---
### 二、代码实现
#### 1. 初始化ETH和DMA(HAL库)
```c
// 定义描述符和缓冲区
#define ETH_RX_DESC_CNT 4 // 接收描述符数量
#define ETH_TX_DESC_CNT 4 // 发送描述符数量
#define ETH_RX_BUF_SIZE 1536 // 接收缓冲区大小
__ALIGNED(32) ETH_DMADescTypeDef DMARxDscrTab[ETH_RX_DESC_CNT]; // 接收描述符
__ALIGNED(32) ETH_DMADescTypeDef DMATxDscrTab[ETH_TX_DESC_CNT]; // 发送描述符
__ALIGNED(32) uint8_t Rx_Buff[ETH_RX_DESC_CNT][ETH_RX_BUF_SIZE]; // 接收缓冲区
ETH_HandleTypeDef heth;
void ETH_Init(void) {
// 1. 启用时钟
__HAL_RCC_ETH1MAC_CLK_ENABLE();
__HAL_RCC_ETH1TX_CLK_ENABLE();
__HAL_RCC_ETH1RX_CLK_ENABLE();
// 2. 配置GPIO为RMII模式(根据实际电路修改)
// RMII_REF_CLK, RMII_MDIO, RMII_MDC, RMII_TXD0, RMII_TXD1, RMII_TX_EN, RMII_RXD0, RMII_RXD1, RMII_CRS_DV
// 3. 初始化ETH句柄
heth.Instance = ETH;
heth.Init.MACAddr = (uint8_t *)MAC_ADDR; // 设置MAC地址
heth.Init.MediaInterface = ETH_MEDIA_INTERFACE_RMII;
heth.Init.RxDesc = DMARxDscrTab;
heth.Init.TxDesc = DMATxDscrTab;
heth.Init.RxBuffLen = ETH_RX_BUF_SIZE;
HAL_ETH_Init(&heth);
// 4. 初始化DP83848 PHY
uint32_t phyreg;
HAL_ETH_WritePHYRegister(&heth, DP83848_PHY_ADDR, DP83848_BMCR, DP83848_BMCR_AUTONEG_ENABLE);
// 等待自动协商完成
do {
HAL_Delay(100);
HAL_ETH_ReadPHYRegister(&heth, DP83848_PHY_ADDR, DP83848_BMSR, &phyreg);
} while (!(phyreg & DP83848_BMSR_AUTONEG_COMPLETE));
}
```
#### 2. DMA描述符初始化
```c
void ETH_DMA_DescInit(ETH_HandleTypeDef *heth) {
// 初始化接收描述符链表
for (int i = 0; i < ETH_RX_DESC_CNT; i++) {
DMARxDscrTab[i].Buffer1Addr = (uint32_t)Rx_Buff[i];
DMARxDscrTab[i].Status = ETH_DMARXDESC_OWN | ETH_DMARXDESC_RCH;
DMARxDscrTab[i].ControlBufferSize = ETH_RX_BUF_SIZE | ETH_DMARXDESC_RER;
if (i == (ETH_RX_DESC_CNT - 1)) {
DMARxDscrTab[i].Buffer2NextDescAddr = (uint32_t)&DMARxDscrTab[0]; // 环形链表
} else {
DMARxDscrTab[i].Buffer2NextDescAddr = (uint32_t)&DMARxDscrTab[i+1];
}
}
// 初始化发送描述符(类似结构)
// ...
// 启动DMA
HAL_ETH_Start(heth);
}
```
#### 3. 数据发送函数
```c
void ETH_SendFrame(uint8_t *pData, uint16_t len) {
ETH_DMADescTypeDef *dmatxdesc = heth.TxDesc;
// 检查描述符所有权
if ((dmatxdesc->Status & ETH_DMATXDESC_OWN) != 0) {
return; // 描述符被DMA占用
}
// 复制数据到缓冲区
memcpy((uint8_t *)dmatxdesc->Buffer1Addr, pData, len);
// 设置描述符
dmatxdesc->ControlBufferSize = len | ETH_DMATXDESC_LS | ETH_DMATXDESC_FS;
dmatxdesc->Status = ETH_DMATXDESC_OWN; // 移交所有权给DMA
// 触发传输
__HAL_ETH_DMA_TRANSMIT_ENABLE(&heth);
}
```
#### 4. 数据接收(轮询方式)
```c
uint16_t ETH_ReceiveFrame(uint8_t *pData) {
ETH_DMADescTypeDef *dmardesc = heth.RxDesc;
if ((dmardesc->Status & ETH_DMARXDESC_OWN) == 0) {
// 获取帧长度(去掉4字节CRC)
uint32_t framelen = ((dmardesc->Status & ETH_DMARXDESC_FL) >> ETH_DMARXDESC_FRAMELENGTHSHIFT) - 4;
// 复制数据到用户缓冲区
memcpy(pData, (uint8_t *)dmardesc->Buffer1Addr, framelen);
// 归还描述符给DMA
dmardesc->Status = ETH_DMARXDESC_OWN;
heth.RxDesc = (ETH_DMADescTypeDef*)dmardesc->Buffer2NextDescAddr;
return framelen;
}
return 0;
}
```
---
### 三、关键配置说明
1. **DMA方向管理**
- **发送(TX)**:内存 → 以太网FIFO (`MEM->PERIPH`)
- **接收(RX)**:以太网FIFO → 内存 (`PERIPH->MEM`)
- 方向由描述符的`OWN`位自动控制[^1]
2. **缓冲区对齐**
```c
__ALIGNED(32) // 32字节对齐(Cache一致性要求)
```
3. **中断配置(可选)**
```c
HAL_NVIC_SetPriority(ETH_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(ETH_IRQn);
```
---
### 四、调试建议
1. **PHY寄存器检查**
```c
uint32_t phyid;
HAL_ETH_ReadPHYRegister(&heth, DP83848_PHY_ADDR, DP83848_PHYID1, &phyid);
// 应返回0x2000
```
2. **数据包结构**
发送原始帧需包含:
```c
[目标MAC(6)][源MAC(6)][类型(2)][数据(46-1500)][CRC(4)]
```
3. **性能优化**
- 增大描述符数量(减少丢包)
- 使用D-Cache维护函数`SCB_CleanDCache_by_Addr`
---
### 常见问题解决
| 问题现象 | 检查点 |
|---------------------------|---------------------------------|
| 无法发送数据 | 1. OWN位是否释放<br>2. PHY链接状态 |
| 接收数据不全 | 1. 缓冲区大小<br>2. 对齐问题 |
| DMA传输卡死 | 1. 描述符链表闭环<br>2. Cache配置 |
>[!NOTE]
>完整工程参考:[STM32H7 Ethernet Baremetal Example](https://github.com/STMicroelectronics/STM32CubeH7/tree/master/Projects/NUCLEO-H743ZI/Applications/LwIP/LwIP_HTTP_Server_Netconn_RTOS)[^2]
---
**相关问题**
1. 如何验证DP83848与STM32H750的物理层连接?
2. 如何实现以太网接收中断代替轮询?
3. 如何构造合法的ARP响应帧?
4. 如何优化DMA描述符数量以减少丢包?
[^1]: STM32H7 Reference Manual (RM0433) - Ethernet DMA operation
[^2]: STM32CubeH7 GitHub Repository - Baremetal Ethernet Examples
阅读全文
相关推荐


















