1. 项目概述
在嵌入式系统开发中,DMA(直接存储器访问)技术是提升系统性能的关键手段之一。本实验基于STM32平台,实现存储器到外设模式的DMA传输,具体是将内存中的数据通过DMA自动搬运到USART数据寄存器,再由USART发送至PC端串口调试助手显示。相比传统的CPU轮询或中断方式,DMA传输可以显著降低CPU负载,提高系统整体效率。
这个实验的核心价值在于:
- 掌握STM32中DMA控制器的基本配置方法
- 理解存储器到外设模式的数据传输机制
- 实现USART与DMA的协同工作
- 体验DMA传输带来的CPU资源释放效果
2. 硬件设计解析
2.1 硬件连接方案
实验采用STM32F4系列开发板,通过CH340G USB转串口芯片实现与PC的通信连接。关键硬件连接如下:
-
USART1引脚配置:
- TX引脚:PA9(USART1_TX)
- RX引脚:PA10(USART1_RX)
-
DMA通道选择:
- USART1_TX对应DMA2 Stream7通道4
- 这是STM32F4系列芯片的固定映射关系
-
时钟配置:
- USART1挂载在APB2总线
- DMA2控制器使用AHB1总线时钟
注意:不同STM32系列的DMA流和通道映射可能不同,务必查阅对应芯片的参考手册确认。
2.2 硬件初始化流程
硬件初始化遵循以下顺序:
- 使能GPIO端口时钟
- 配置GPIO为复用功能模式
- 使能USART外设时钟
- 配置USART工作参数
- 使能DMA控制器时钟
这种顺序确保了各外设的正确初始化,避免了潜在的硬件冲突。
3. 软件设计实现
3.1 关键宏定义
c复制// USART1相关定义
#define DEBUG_USART USART1
#define DEBUG_USART_CLK RCC_APB2Periph_USART1
#define DEBUG_USART_BAUDRATE 115200
// DMA相关定义
#define DEBUG_USART_DR_BASE (USART1_BASE+0x04)
#define SENDBUFF_SIZE 5000
#define DEBUG_USART_DMA_CLK RCC_AHB1Periph_DMA2
#define DEBUG_USART_DMA_CHANNEL DMA_Channel_4
#define DEBUG_USART_DMA_STREAM DMA2_Stream7
宏定义的价值在于:
- 集中管理硬件配置参数
- 提高代码可读性和可维护性
- 方便后续修改和移植
3.2 USART配置详解
USART配置分为以下几个步骤:
- GPIO初始化:
c复制GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = DEBUG_USART_TX_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStructure);
- USART参数配置:
c复制USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate = DEBUG_USART_BAUDRATE;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(DEBUG_USART, &USART_InitStructure);
USART_Cmd(DEBUG_USART, ENABLE);
波特率计算是USART配置的关键点。STM32使用分数波特率发生器,计算公式为:
code复制波特率 = fCK / (16 * USARTDIV)
其中USARTDIV是一个包含整数和小数部分的数值,分别对应BRR寄存器的高12位和低4位。
3.3 DMA配置实现
DMA配置是本次实验的核心,主要步骤如下:
- DMA初始化结构体配置:
c复制DMA_InitTypeDef DMA_InitStructure;
DMA_InitStructure.DMA_Channel = DEBUG_USART_DMA_CHANNEL;
DMA_InitStructure.DMA_PeripheralBaseAddr = DEBUG_USART_DR_BASE;
DMA_InitStructure.DMA_Memory0BaseAddr = (u32)SendBuff;
DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;
DMA_InitStructure.DMA_BufferSize = SENDBUFF_SIZE;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
DMA_Init(DEBUG_USART_DMA_STREAM, &DMA_InitStructure);
- 关键参数解析:
DMA_DIR_MemoryToPeripheral:设置传输方向为内存到外设DMA_Mode_Normal:单次传输模式,传输完成后需要重新使能DMA_Priority_Medium:设置DMA通道优先级为中DMA_FIFOMode_Disable:禁用FIFO,直接传输模式
- DMA使能:
c复制DMA_Cmd(DEBUG_USART_DMA_STREAM, ENABLE);
while(DMA_GetCmdStatus(DEBUG_USART_DMA_STREAM) != ENABLE);
实际项目中,建议在DMA传输完成中断中处理后续操作,而不是使用while循环等待。
3.4 USART与DMA联动机制
USART与DMA的协同工作通过以下寄存器位控制:
-
USART_CR3中的DMAT位:
- 使能后,当TXE=1时,USART会向DMA控制器发送传输请求
- 对应代码:
USART_DMACmd(DEBUG_USART, USART_DMAReq_Tx, ENABLE)
-
数据传输流程:
- USART检测到TXE=1且DMAT=1
- USART向DMA控制器发送请求
- DMA控制器从内存读取数据并写入USART_DR
- USART发送数据,完成后TXE再次置1
- 重复2-4过程,直到DMA计数器归零
4. 主函数实现与测试
4.1 主函数代码分析
c复制int main(void)
{
uint16_t i;
// 外设初始化
Debug_USART_Config();
USART_DMA_Config();
LED_GPIO_Config();
// 填充发送缓冲区
for(i=0; i<SENDBUFF_SIZE; i++) {
SendBuff[i] = 'A';
}
// 启动DMA传输
USART_DMACmd(DEBUG_USART, USART_DMAReq_Tx, ENABLE);
// 主循环中CPU可执行其他任务
while(1) {
LED1_TOGGLE;
Delay(0xFFFFF);
}
}
4.2 测试注意事项
-
串口调试助手设置:
- 波特率设置为115200
- 建议启用"自动清空接收区"功能
- 避免长时间运行大量数据传输,可能导致PC端软件卡顿
-
DMA模式选择:
- 正常模式(DMA_Mode_Normal):单次传输,适合小批量数据
- 循环模式(DMA_Mode_Circular):持续传输,适合流式数据
-
性能观察:
- LED闪烁证明CPU未被数据传输阻塞
- 可通过逻辑分析仪观察USART引脚波形
5. 常见问题与解决方案
5.1 DMA传输不启动
可能原因及解决方法:
-
时钟未使能:
- 检查DMA和USART时钟是否已正确开启
- 确认GPIO端口时钟已使能
-
DMA流配置错误:
- 确认DMA流和通道选择正确
- 检查外设和内存地址是否有效
-
USART未正确初始化:
- 确认USART已使能
- 检查波特率等参数设置是否正确
5.2 数据传输不完整
可能原因及解决方法:
-
缓冲区大小不匹配:
- 确保DMA_BufferSize与实际数据量一致
- 检查内存地址自增设置
-
优先级冲突:
- 调整DMA通道优先级
- 检查是否有更高优先级中断抢占
-
时序问题:
- 在DMA传输前确保USART已准备好
- 适当增加传输间的延迟
5.3 系统稳定性问题
优化建议:
- 启用DMA传输完成中断,而非轮询状态
- 添加错误检测和重传机制
- 对于大量数据传输,考虑使用双缓冲技术
- 定期检查DMA和USART状态寄存器
6. 进阶应用与扩展
6.1 双缓冲技术实现
双缓冲可以有效提高数据传输效率:
c复制uint8_t SendBuff1[SENDBUFF_SIZE];
uint8_t SendBuff2[SENDBUFF_SIZE];
volatile uint8_t currentBuffer = 0;
void DMA_IRQHandler(void)
{
if(DMA_GetITStatus(DMA2_Stream7, DMA_IT_TCIF7)) {
DMA_ClearITPendingBit(DMA2_Stream7, DMA_IT_TCIF7);
// 切换缓冲区
if(currentBuffer == 0) {
DMA_MemoryTargetConfig(DMA2_Stream7, (uint32_t)SendBuff2, DMA_Memory_0);
currentBuffer = 1;
} else {
DMA_MemoryTargetConfig(DMA2_Stream7, (uint32_t)SendBuff1, DMA_Memory_0);
currentBuffer = 0;
}
// 重新启动DMA传输
DMA_SetCurrDataCounter(DMA2_Stream7, SENDBUFF_SIZE);
DMA_Cmd(DMA2_Stream7, ENABLE);
}
}
6.2 与RTOS集成
在RTOS环境中使用DMA的注意事项:
- 确保DMA相关操作在临界区内完成
- 使用信号量或消息队列同步DMA状态
- 合理设置任务优先级,避免DMA被长时间阻塞
6.3 性能优化技巧
- 内存对齐:确保缓冲区地址按4字节对齐,提高传输效率
- 数据打包:将多个小数据打包传输,减少DMA启动开销
- 缓存控制:在Cortex-M7等有缓存的核心上,注意维护缓存一致性
通过本实验,我们不仅掌握了STM32 DMA的基本使用方法,更理解了如何通过硬件加速提升系统性能。在实际项目中,合理运用DMA技术可以显著提高系统效率,特别是在需要高频、大数据量传输的应用场景中。
