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 标志

相关推荐
新晨单片机设计2 小时前
S008A-基于STM32单片机八路抢答器(数码管)【Proteus仿真+Keil程序+原理图】
stm32·单片机·proteus
米饭不加菜2 小时前
STM32——数码管显示
stm32·单片机·嵌入式硬件
梵亿2 小时前
基于 STM32F103C8T6 的无人机低功耗设计与实现
stm32·嵌入式硬件·无人机·低功耗
hrw_embedded3 小时前
Keil开发与STM32Cube开发的差异点总结(未完待续)
stm32·单片机·嵌入式硬件
Brilliantwxx3 小时前
【STM32】RCC与时钟树 深度详解
stm32·单片机·嵌入式硬件·架构
Rsingstarzengjx3 小时前
第二十六章 Linux 蜂鸣器实验笔记
linux·驱动开发·笔记·stm32·嵌入式硬件·stm32mp157
一路往蓝-Anbo3 小时前
第七篇:以太网设计 —— 从 RMII 时序到变压器隔离的深度拆解
stm32·单片机·嵌入式硬件·嵌入式·stm32硬件设计
jianqiang.xue4 小时前
ESP-IDF保姆级入门22|WiFi联网与网络编程全解:STA/AP双模式/TCP-UDP Socket/HTTP服务端/自动重连,掌握工业级可靠网络通信
人工智能·stm32·单片机·mcu·物联网·51单片机·iot
会不再投降21911 小时前
初识嵌入式历史发展与生态
stm32