DMA 学习复盘笔记

STM32F1 DMA 学习复盘笔记

实验场景:存储器到存储器 DMA 搬运(内存→内存),MYDMA 封装库实验 核心痛点:DMA 传输计数器不是字节数,是搬运次数;数据宽度决定搬运字节与地址步长,数组类型必须和 DMA 数据宽度匹配

一、DMA 关键寄存器参数(F1)

  1. 传输计数器 DMA_SetCurrDataCounter()
  • 含义:DMA 搬运多少次,不是多少字节!
  • 总搬运字节 = 搬运次数 × 单次搬运字节宽度
  1. PeripheralDataSize 外设数据宽度
  2. MemoryDataSize 存储器数据宽度

两个宽度同时决定两件事:①单次搬运多少字节;②搬运完成后源 / 目标地址自增步长

表格

配置宏 单次搬运大小 地址自增步长 适配数组类型
DMA_MemoryDataSize_Byte 8bit (1 字节) +1 uint8_t
DMA_MemoryDataSize_HalfWord 16bit (2 字节) +2 uint16_t
DMA_MemoryDataSize_Word 32bit (4 字节) +4 uint32_t

⚠️重点:只有数组类型和 DMA 数据宽度匹配时,传入的计数器数值才等于数组元素个数。不匹配时不能直接填数组元素数量。

二、实验案例分析

实验:把源数组 4 个元素 DMA 拷贝到目标数组,函数调用:MyDMA_Init(源地址,目标地址,4);

案例 1:uint32_t DataA 4、uint32_t DataB 4

情况 A:DMA 配置 Word(32bit)✅正确
复制代码
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
MyDMA_Init((uint32_t)DataA,(uint32_t)DataB,4);
  • 每次搬运 4 字节,地址 + 4;搬运 4 次
  • 效果:DataA[0]→DataB[0]、DataA[1]→DataB[1]、DataA[2]→DataB[2]、DataA[3]→DataB[3],4 个元素完整拷贝。
情况 B:DMA 配置 Byte(8bit)❌错误,无法实现预期
复制代码
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
MyDMA_Init((uint32_t)DataA,(uint32_t)DataB,4);
  • 每次搬运 1 字节,地址 + 1;搬运 4 次,总共搬运 4 字节。
  • STM32 小端模式:只会读取DataA 0 这个 32 位变量内部的 4 个字节,依次写入 DataB 0~DataB 3。
  • 现象:DataB 数组存的是 DataA 0 拆分的字节;DataA 1、DataA 2、DataA 3 完全没有搬运。
  • 修改方案:需要拷贝 4 个 uint32_t 一共 16 字节,计数器改为16。

案例 2:uint8_t DataA 4、uint8_t DataB 4

情况 A:DMA 配置 Byte(8bit)✅正确
复制代码
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
MyDMA_Init((uint32_t)DataA,(uint32_t)DataB,4);

每次搬运 1 字节,地址 + 1,搬运 4 次;4 个 uint8_t 元素一一拷贝。

情况 B:DMA 配置 Word(32bit)❌错误
复制代码
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
MyDMA_Init((uint32_t)DataA,(uint32_t)DataB,4);

每次搬运 4 字节,地址 + 4,搬运 4 次,总计搬运 16 字节。

  • 第一次读取 DataA 0~DataA 3 拼成 32 位;后续三次地址直接越出数组边界,读取内存垃圾。
  • 现象:数据错乱,内存越界,偶发程序跑飞、OLED 乱码。

三、DMA 等待传输完成代码坑(分号陷阱)

错误版本

复制代码
while(DMA_GetFlagStatus(DMA1_FLAG_TC1)==RESET)
DMA_ClearFlag(DMA1_FLAG_TC1);

等价于while(条件){DMA_ClearFlag();}

问题:DMA 传输完成跳出循环后,清除标志的代码属于循环体,不会执行。TC1 完成标志一直置 1;第二次调用 DMA 时直接跳过等待,数据出错。

正确版本

复制代码
while(DMA_GetFlagStatus(DMA1_FLAG_TC1)==RESET);  //空循环阻塞等待传输完成
DMA_ClearFlag(DMA1_FLAG_TC1);

规范写法,防止漏写分号,建议用大括号空语句:

复制代码
while(DMA_GetFlagStatus(DMA1_FLAG_TC1)==RESET)
{
    ;
}
DMA_ClearFlag(DMA1_FLAG_TC1);

补充知识点:DMA_Cmd(DISABLE) 不会自动清除 TC 传输完成标志,必须软件手动清除。

四、避坑总结

  1. DMA 计数器 = 搬运次数,不是字节数。总字节 = 次数 × 单次搬运字节。
  2. MemoryDataSize 同时控制:单次搬运字节 + 内存地址自增步长。
  3. 数组类型必须和 DMA 数据宽度匹配,计数器才等于数组元素个数。不匹配要手动计算总字节数填写计数器。
  4. 等待 DMA 传输完成,警惕 while 后分号;TC 标志硬件不会自动清零,软件手动 ClearFlag。
  5. 内存到内存 DMA 越界访问会破坏其他全局变量,现象:数据乱码、程序偶发死机。

五、做题 / 调试快速判断流程

  1. 看数组类型:uint8 /uint16 /uint32_t
  2. 看 DMA 配置的 MemoryDataSize
  3. 看传入的计数器值
  4. 计算总字节 = 计数器 × 单次搬运字节,对比数组实际总字节,判断是否匹配。

口诀:看类型,对宽度,算总字节,盯 while 分号,记得清 TC 标志。

相关推荐
Zw-awa2 小时前
先让程序开口说话:为什么把人机交互放在最前面
stm32
Zw-awa2 小时前
把按键和 LED 状态显示出来:第一次看见程序到底在干什么
stm32
Zw-awa2 小时前
面包板接线为什么这样接:从模块需求到芯片资源分配
stm32
小程序设计3 小时前
基于STM32的办公室智能空气净化系统设计
stm32·单片机
Zw-awa3 小时前
用 OLED 给 STM32 做实时调试面板
stm32
Zw-awa3 小时前
按键为什么会乱跳:一次按下为什么变成很多次
stm32
XiHongShi20166 天前
STM32F407 RTC定时器例程,建议保存
stm32·单片机·学习
优信电子6 天前
AIT1001 非接触式红外测温模块 STM32 驱动开发实战:从引脚到代码全解析
stm32·嵌入式·温度传感器·红外测温·非接触式测温·ait1001
深圳老胡6 天前
STM32F407 控制 L6470 步进电机驱动 —— 控制过程简介
笔记·stm32·单片机·嵌入式硬件·代码规范
wuyk5556 天前
《WiFi 嵌入式物联网开发全套实战》| 第 16 章 ESP32 AP+STA 双模共存原理与工程坑点
网络·stm32·物联网