当前位置: 首页 > news >正文

别再让CPU干杂活了!手把手教你用STM32的DMA给串口发送数据提速

别再让CPU干杂活了!手把手教你用STM32的DMA给串口发送数据提速

在嵌入式开发中,串口通信是最基础也最常用的功能之一。但当我们需要发送大量数据时,传统的轮询或中断方式往往会成为系统性能的瓶颈。想象一下这样的场景:你的设备需要每秒发送数百KB的传感器数据,同时还要处理复杂的算法和用户交互——这时如果CPU被串口发送数据这种"杂活"拖累,整个系统的实时性就会大打折扣。

这就是DMA技术大显身手的时候。DMA(直接内存访问)就像是一个专门负责数据搬运的"小助手",它可以在不占用CPU资源的情况下,高效地完成内存与外设之间的数据传输。本文将带你深入理解DMA的工作原理,并通过一个完整的STM32 USART DMA发送实例,展示如何将串口发送速度提升数倍,同时大幅降低CPU占用率。

1. 为什么你的串口发送需要DMA?

在开始技术细节之前,让我们先看看三种常见的串口数据发送方式及其性能对比:

发送方式CPU占用率最大吞吐量实时性影响代码复杂度
轮询发送100%中等严重
中断发送30-70%较低中等中等
DMA发送<5%最高极小较高

表:三种串口发送方式性能对比

传统轮询方式就像是你亲自一趟趟搬运货物——CPU必须等待每个字节发送完成才能处理下一个,效率低下且完全阻塞了其他任务。中断方式稍好一些,像是雇佣了一个临时工,但每次搬运少量货物就要通知你一次,频繁的上下文切换仍然消耗大量资源。

而DMA方式则像是雇佣了一个专业的物流团队,你只需要告诉他们货物的位置和目的地,剩下的工作完全由他们自主完成,期间你可以专心处理其他重要事务。这种方式的优势在以下场景尤为明显:

  • 高频传感器数据采集与传输
  • 大容量日志记录
  • 实时音频/视频流传输
  • 需要低延迟响应的控制系统

2. DMA核心机制深度解析

要充分利用DMA,我们需要理解它的几个关键特性:

2.1 DMA通道与仲裁机制

STM32的DMA控制器采用多通道设计,以F1系列为例:

  • DMA1:7个通道,所有型号都有
  • DMA2:5个通道,仅大容量产品具备

每个通道可以独立配置,服务于特定的外设。当多个通道同时请求时,仲裁器会根据以下优先级决定处理顺序:

  1. 软件优先级(通过DMA_CCRx寄存器配置):

    • 很高
    • 中等
  2. 硬件优先级(当软件优先级相同时):

    • 通道编号越小优先级越高

2.2 数据传输模式

DMA支持多种灵活的数据传输方式:

typedef enum { DMA_Mode_Normal = 0, // 普通模式,传输完成后停止 DMA_Mode_Circular = 1 // 循环模式,自动重装计数器 } DMA_Mode_TypeDef;

循环模式特别适合以下场景:

  • ADC连续采样
  • 环形缓冲区管理
  • 实时数据流处理

2.3 地址增量与数据宽度

DMA传输中的地址管理非常灵活:

DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 外设地址固定 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; // 内存地址递增 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; // 8位 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; // 8位

这种配置组合可以实现:

  • 固定外设寄存器地址(如USART->DR)
  • 连续的内存区域访问
  • 不同位宽的数据自动打包/解包

3. 实战:USART DMA发送完整配置

让我们以STM32F103的USART1为例,详细讲解DMA发送配置步骤。

3.1 硬件连接与时钟配置

首先确保硬件连接正确:

  • USART1_TX → PA9
  • 串口终端设备(如USB转TTL)正确连接

时钟配置代码:

// 使能DMA1时钟 RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); // 使能USART1时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); // GPIO时钟使能 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

3.2 DMA通道初始化

USART1_TX对应DMA1通道4,配置代码如下:

void USART1_DMA_Config(void) { DMA_InitTypeDef DMA_InitStructure; // 复位DMA通道 DMA_DeInit(DMA1_Channel4); // 配置DMA参数 DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR; DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)SendBuffer; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; // 内存到外设 DMA_InitStructure.DMA_BufferSize = BUFFER_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_High; DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; // 非内存到内存 DMA_Init(DMA1_Channel4, &DMA_InitStructure); // 使能USART1 DMA发送请求 USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE); }

3.3 数据传输控制

启动DMA传输的典型流程:

// 准备发送数据 void PrepareData(uint8_t *data, uint16_t length) { memcpy(SendBuffer, data, length); DMA_SetCurrDataCounter(DMA1_Channel4, length); // 设置传输数量 } // 开始DMA传输 void StartDMATransfer(void) { DMA_Cmd(DMA1_Channel4, DISABLE); // 先禁用通道 DMA_SetCurrDataCounter(DMA1_Channel4, BUFFER_SIZE); // 重新设置计数器 DMA_Cmd(DMA1_Channel4, ENABLE); // 启用通道 } // 检查传输状态 uint8_t IsTransferComplete(void) { return DMA_GetFlagStatus(DMA1_FLAG_TC4) == SET; }

4. 高级技巧与性能优化

掌握了基本配置后,我们来看几个提升DMA使用效率的高级技巧。

4.1 双缓冲技术

对于连续数据流,可以采用双缓冲机制:

#define BUF_SIZE 512 uint8_t BufferA[BUF_SIZE]; uint8_t BufferB[BUF_SIZE]; volatile uint8_t ActiveBuffer = 0; // 0:BufferA, 1:BufferB void DMA1_Channel4_IRQHandler(void) { if(DMA_GetITStatus(DMA1_IT_TC4)) { DMA_ClearITPendingBit(DMA1_IT_TC4); // 切换缓冲区 if(ActiveBuffer == 0) { DMA_SetCurrDataCounter(DMA1_Channel4, BUF_SIZE); DMA_SetMemoryAddress(DMA1_Channel4, (uint32_t)BufferB); ActiveBuffer = 1; } else { DMA_SetCurrDataCounter(DMA1_Channel4, BUF_SIZE); DMA_SetMemoryAddress(DMA1_Channel4, (uint32_t)BufferA); ActiveBuffer = 0; } // 处理新数据填充... } }

4.2 内存到内存传输

DMA不仅可以用于外设通信,还能加速内存间数据传输:

void MemoryCopy_DMA(uint32_t *src, uint32_t *dst, uint16_t count) { DMA_InitTypeDef DMA_InitStructure; DMA_DeInit(DMA1_Channel1); DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)src; DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)dst; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; DMA_InitStructure.DMA_BufferSize = count; DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word; DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word; DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; DMA_InitStructure.DMA_Priority = DMA_Priority_High; DMA_InitStructure.DMA_M2M = DMA_M2M_Enable; DMA_Init(DMA1_Channel1, &DMA_InitStructure); DMA_Cmd(DMA1_Channel1, ENABLE); while(DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET); DMA_ClearFlag(DMA1_FLAG_TC1); }

4.3 动态调整传输速率

在某些应用中,我们需要根据系统负载动态调整DMA传输速率:

void AdjustDMARate(uint16_t new_rate) { // 先停止当前传输 USART_DMACmd(USART1, USART_DMAReq_Tx, DISABLE); DMA_Cmd(DMA1_Channel4, DISABLE); // 调整USART波特率 USART_InitTypeDef USART_InitStructure; USART_InitStructure.USART_BaudRate = new_rate; 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_Tx; USART_Init(USART1, &USART_InitStructure); // 重新配置DMA DMA_SetCurrDataCounter(DMA1_Channel4, BUFFER_SIZE); USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE); DMA_Cmd(DMA1_Channel4, ENABLE); }

5. 常见问题与调试技巧

即使正确配置了DMA,实际开发中仍可能遇到各种问题。以下是几个常见问题及解决方法:

5.1 DMA传输不启动

检查清单:

  1. 确认所有相关时钟已使能(DMA、USART、GPIO)
  2. 验证DMA通道与外设的对应关系是否正确
  3. 检查DMA和外设的使能顺序:
    • 先配置DMA参数
    • 然后使能外设DMA请求
    • 最后使能DMA通道

5.2 数据传输不完整

可能原因:

  • 缓冲区大小设置错误
  • 内存地址未正确递增
  • 传输过程中被高优先级任务打断

调试方法:

// 在传输过程中监控剩余数据量 uint16_t remaining = DMA_GetCurrDataCounter(DMA1_Channel4); printf("Remaining data: %d\n", remaining);

5.3 性能优化检查点

要获得最佳性能,注意以下几点:

  1. 将DMA缓冲区放在高速内存区域(如CCM RAM)
  2. 合理设置DMA通道优先级
  3. 对于大数据传输,使用循环缓冲避免频繁重配置
  4. 对齐内存地址到4字节边界(32位系统)

提示:使用DMA时,合理的内存布局能显著提升性能。考虑使用__attribute__((aligned(4)))确保缓冲区对齐。

在实际项目中,我遇到过一个典型的性能问题:设备在发送大量数据时,其他任务的响应变得迟缓。通过将串口发送改为DMA方式,CPU占用率从70%降到了不到5%,同时系统吞吐量提升了3倍。关键是要确保DMA缓冲区足够大,避免频繁启动小数据量传输带来的开销。

http://www.jsqmd.com/news/946370/

相关文章:

  • 如何用Paperless-ngx打造你的数字文档管理中枢:从零开始构建智能归档系统
  • AIOps落地失败率高达73%?揭秘头部企业私有化整合框架(2024最新Gartner认证实践)
  • 告别CLI手忙脚乱:用Docker+OpenConfig+gRPC,5分钟搞定网络设备数据采集
  • redis-数据安全性
  • AutoJs Pro 7.0.4-1 避坑指南:一机一号稳定运行快手极速版,告别封号风险
  • 别再混淆了!深入对比SO_REUSEADDR和SO_REUSEPORT:在Linux下实现UDP/TCP多进程监听同一端口
  • Thumbfast:mpv播放器高性能实时缩略图生成终极指南
  • 2000-2024年上市公司动态能力数据+stata代码
  • AI驱动秒杀系统性能飙升300%:揭秘LLM调度引擎+实时库存预测的工业级整合路径
  • ai开发新范式,快马生成基于ollama本地的智能测试用例生成器
  • PX4飞控系统架构解析:模块化无人机自主飞行实现原理
  • 第二次web设计作业
  • 量子性质估计与AiDE-Q框架:解决量子测量资源挑战
  • 阿里 CodeTop 代码随想录 123.买卖股票的最佳时机Ⅲ
  • BiCoR-Seg框架:高分辨率遥感图像语义分割新突破
  • 2026年评价高的广东双排配电箱/家用配电箱/广东明装配电箱优质公司推荐 - 行业平台推荐
  • MODTRAN观测几何参数(CARD3)详解:卫星遥感与地面观测场景下的参数设置实战
  • 终极指南:Rhino Compute REST几何计算服务器深度解析与实战应用
  • CSDN AI 数字营销工具试用体验
  • 混合架构安全获取原生权限实战
  • 2026年靠谱的压力平流喷雾干燥机/离心造粒喷雾干燥机/常州喷雾干燥机/常州气流喷雾干燥机批量采购厂家推荐 - 行业平台推荐
  • 操作系统OS
  • 从Flask到Django:用Click给你的Python项目加个“专业”命令行界面
  • n8n Webhook 能直接公网暴露吗?鉴权和密钥保护建议
  • 避开这些坑!STM32F407 MAC地址配置与网络调试的完整流程
  • 告别阻塞延时!STM32+ADS1115多通道轮询采样的高效定时器方案详解
  • XAutoDaily:5步实现QQ自动化签到,彻底解放你的双手
  • 告别CH340!用STM32F103C8T6的USB虚拟串口搞定Arduino数据上传(附完整代码)
  • 告别单调表格!用QStyledItemDelegate为你的Qt应用打造个性化数据视图
  • 新手必看:用AT89C51和DS18B20做个温度计,LCD1602显示,代码逐行讲解