STM32实现DMA存储器到外设串口传输详解

作者:菠萝爱吃肉2026.11.06 21:19浏览量:0

简介:本文以DMA存储器到外设传输模式为核心,完整说明如何将指定内存缓冲区数据自动搬运到USART数据寄存器并发送至上位机。读者可掌握DMA与串口配合的配置流程、常见编译错误排查方法,以及释放CPU负载实现并行任务的设计思路,适合嵌入式开发与通信调试场景。

一、DMA存储器到外设传输的应用背景

在嵌入式系统中,当需要将一段连续内存中的数据发送到串口、SPI、I2C等外设时,传统做法是由CPU逐字节读取存储器并写入外设数据寄存器。这种方式虽然直观,但在数据量较大或发送频率较高时,会占用大量CPU时间,导致系统无法及时响应其他任务。直接存储器访问控制器为此提供了一种更高效的传输机制:在CPU完成初始化配置后,DMA可以独立地将数据从存储器搬运到外设,无需CPU介入每一个字节的移动过程。

在串口通信场景中,开发板常需要向上位机、另一块控制板或功能模块发送数据。使用DMA存储器到外设模式,可以将待发送的缓冲区首地址、长度以及外设数据寄存器地址告知DMA,随后由DMA在硬件层面完成搬运。CPU在发起传输后即可转向其他工作,例如控制LED闪烁、执行传感器采集或处理通信协议,从而显著提升系统的并发能力与实时性。

二、硬件设计说明

本示例使用微控制器的USART1外设作为数据发送端。硬件上只需要保证USART1的发送引脚与外部接收设备正确连接。如果使用开发板与PC通信,通常需借助USB转串口模块或板载调试器自带的虚拟串口,将USART1的TX引脚连接到接收端RX。对于仅进行发送测试的场景,不要求必须连接RX,但一般建议保留完整串口连接以便观察回显或进行双向调试。

由于DMA属于芯片内部总线控制模块,无需为DMA本身增加额外硬件电路。重点在于确认USART1的时钟、引脚复用以及DMA请求映射关系。不同微控制器系列在DMA通道分配上可能存在差异,配置前应查阅对应参考手册,确认USART1_TX对应的DMA控制器编号、通道号以及请求编号,避免因映射错误导致传输无法启动。

三、软件架构与初始化流程

软件设计遵循外设初始化、DMA初始化、数据填充、传输使能、主循环并行的基本流程。首先,配置USART1的工作参数,包括波特率、数据位、停止位、校验位以及发送引脚复用功能。其次,配置DMA通道,将外设地址设置为USART1数据寄存器地址,存储器地址设置为待发送缓冲区的首地址,并设置传输方向为存储器到外设。

在传输方向为存储器到外设的模式下,DMA从存储器读取一个字节或半字,然后写入外设数据寄存器。每完成一次搬运,存储器地址自动递增,外设地址可以保持不变。缓冲区长度决定本次传输的数据总量。传输完成或发生错误时,DMA可产生中断,便于上层程序进行状态管理。

3.1 USART与DMA联合配置示例

  1. #include "stm32f4xx.h"
  2. #include "bsp_debug_usart.h"
  3. #include "bsp_dma.h"
  4. #include "bsp_led.h"
  5. #include "Delay.h"
  6. #define SENDBUFF_SIZE 256
  7. uint8_t SendBuff[SENDBUFF_SIZE];
  8. static void USART_DMA_Config(void)
  9. {
  10. DMA_InitTypeDef DMA_InitStructure;
  11. RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);
  12. DMA_DeInit(DMA2_Stream7);
  13. DMA_InitStructure.DMA_Channel = DMA_Channel_4;
  14. DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&USART1->DR);
  15. DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)SendBuff;
  16. DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;
  17. DMA_InitStructure.DMA_BufferSize = SENDBUFF_SIZE;
  18. DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
  19. DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
  20. DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
  21. DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
  22. DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
  23. DMA_InitStructure.DMA_Priority = DMA_Priority_High;
  24. DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
  25. DMA_Init(DMA2_Stream7, &DMA_InitStructure);
  26. USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);
  27. }

上述代码仅为通用配置思路展示,不同芯片的库函数名称和寄存器定义可能略有差异。实际开发时,应以所用微控制器对应的标准外设库或硬件抽象层接口为准,重点核对DMA控制器编号、流编号、通道编号以及USART数据寄存器地址。

3.2 数据填充与传输启动

在主函数中,首先完成USART和DMA初始化,再对待发送缓冲区进行填充。填充数据可以是固定字符、协议帧、传感器采样值或格式化字符串。示例中填充字符‘A’用于串口助手显示验证,便于直接观察接收结果是否连续且无丢字节。

  1. int main(void)
  2. {
  3. uint16_t i;
  4. Debug_USART_Config();
  5. USART_DMA_Config();
  6. LED_Config();
  7. printf("\r\n USART1 DMA TX test start \r\n");
  8. for (i = 0; i < SENDBUFF_SIZE; i++)
  9. {
  10. SendBuff[i] = 'A';
  11. }
  12. USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);
  13. while (1)
  14. {
  15. LED1_TOGGLE;
  16. Delay_ms(1000);
  17. }
  18. }

在传输启动后,CPU不再需要逐字节搬移数据,而是进入主循环执行LED闪烁任务。这种并行处理方式是DMA最直接的收益体现。若采用传统阻塞式发送,CPU会在整个发送周期内反复等待发送数据寄存器空标志,主循环中的LED控制将被显著延迟。

四、常见编译错误与排查方法

在工程开发中,DMA配置代码经常出现两类典型错误。第一类为函数隐式声明错误,例如编译器提示function "USART_DMA_Config" declared implicitly。该错误通常表示调用函数之前未包含对应头文件,或者函数实现位于调用之后且缺少前置声明。解决方法是在调用前添加函数原型声明,或将函数定义移至调用之前,确保编译器能够识别函数签名。

第二类为标识符未定义错误,例如identifier "SENDBUFF_SIZE" is undefined。此类错误常见于宏定义缺失、头文件包含顺序错误或条件编译未启用。应在统一配置头文件中定义缓冲区长度,并确保该头文件在引用该宏的源文件及头文件中均可见。对于跨文件使用的全局缓冲区,还需要在头文件中使用extern声明,在源文件中进行实际定义,避免重复定义或作用域不足。

其他常见问题包括:DMA通道请求与USART外设不匹配导致传输不启动;缓冲区长度设置为零导致DMA立即结束;外设地址未取数据寄存器地址而是取基地址;未使能DMA控制器时钟;传输模式配置为循环模式导致数据重复发送;存储器地址未按数据宽度对齐引发总线错误。针对这些问题,应逐项检查DMA初始化结构体各字段,并结合调试器观察DMA状态寄存器中的传输完成标志和错误标志。

五、传输模式选择与性能优化建议

DMA传输模式分为普通模式和循环模式。普通模式下,DMA在完成指定数量数据传输后自动停止,适合发送长度固定的一帧数据。循环模式下,DMA在传输结束后自动重新装载初值并继续传输,适合周期性刷新数据或连续音频流等场景。对于串口发送,若数据内容不变且需要反复广播,可选用循环模式;若每次发送内容动态变化,通常使用普通模式,并在下一帧发送前重新配置缓冲区地址和数据长度。

性能优化方面,可注意以下几点:合理选择DMA数据宽度,使存储器访问与外设数据宽度匹配;尽量将缓冲区放置在可被DMA高效访问的内存区域;减少传输过程中的中断次数,可采用传输完成中断或半传输中断进行双缓冲管理;避免频繁重配置DMA参数,可通过多缓冲区轮换提高连续发送效率。对于高波特率串口通信,DMA配合硬件流控可以在不丢数据的前提下持续发送大规模数据。

六、总结

DMA存储器到外设传输模式为串口发送提供了一种低CPU占用的高效方案。通过正确配置DMA通道、外设地址、存储器地址、传输方向和数据长度,可以构建稳定可靠的自动数据搬运链路。开发过程中需重点关注通道映射关系、外设数据寄存器地址、缓冲区声明与宏定义可见性等细节。掌握该模式后,开发者可将相同思路扩展到SPI发送、I2S音频输出以及并行接口数据传输等更多外设场景,为复杂嵌入式系统提供更优的数据吞吐能力与任务并行性。