前言:在嵌入式系统中,DMA(Direct Memory Access)可以把数据在外设与内存之间(或内存与内存之间)搬运,而无需 CPU 每次拷贝,极大降低 CPU 负担、提高实时性。下一章节将结合ADC。
目录
一、接线图

二、DMA-API介绍
1.void DMA_DeInit(DMA_Channel_TypeDef* DMAy_Channelx);
作用:把指定 DMA 通道的寄存器恢复到复位默认值。
2.void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx, DMA_InitTypeDef* DMA_InitStruct);
作用 :把 DMA_InitTypeDef 结构体中描述的配置写入指定通道寄存器(源地址、目的地址、传输方向、数据宽度、递增模式、传输计数、模式、优先级等)。
3.void DMA_StructInit(DMA_InitTypeDef* DMA_InitStruct);
作用 :把 DMA_InitTypeDef 结构体填充为库定义的默认值。
4.void DMA_Cmd(DMA_Channel_TypeDef* DMAy_Channelx, FunctionalState NewState);
作用:使能或失能指定 DMA 通道(开启或停止 DMA 工作)。
5.void DMA_ITConfig(DMA_Channel_TypeDef* DMAy_Channelx, uint32_t DMA_IT, FunctionalState NewState);
作用:配置并使能/禁用 DMA 通道的中断(如传输完成 TC、中途半传 HT、传输错误 TE)。
6.void DMA_SetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx, uint16_t DataNumber);
作用:设置当前传输计数器(要搬运的数据单元数量)。此计数在 DMA 工作时会递减。
7.uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx);
作用:读取当前剩余的传输计数(可以用于监测传输进展或实现超时)。
8.FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG);
作用 :读取 DMA 的全局/通道标志(例如传输完成 DMA1_FLAG_TC1、传输错误等)。
9.void DMA_ClearFlag(uint32_t DMAy_FLAG);
作用:清除对应的 DMA 标志位(常在处理中或中断服务中使用)。
10.ITStatus DMA_GetITStatus(uint32_t DMAy_IT);
作用:检查某中断源(TC/HT/TE)是否挂起。常在中断向量中判断具体中断原因。
11.void DMA_ClearITPendingBit(uint32_t DMAy_IT);
作用:清除 DMA 中断挂起位,结束中断处理。
三、代码实现
1.DMA初始化
void MyDMA_Init(uint32_t AddrA, uint32_t AddrB, uint16_t Size)
{
MyDMA_Size = Size; //将Size写入到全局变量,记住参数Size
/*开启时钟*/
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //开启DMA的时钟
/*DMA初始化*/
DMA_InitTypeDef DMA_InitStructure; //定义结构体变量
DMA_InitStructure.DMA_PeripheralBaseAddr = AddrA; //外设基地址,给定形参AddrA
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //外设数据宽度,选择字节
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable; //外设地址自增,选择使能
DMA_InitStructure.DMA_MemoryBaseAddr = AddrB; //存储器基地址,给定形参AddrB
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //存储器数据宽度,选择字节
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //存储器地址自增,选择使能
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //数据传输方向,选择由外设到存储器
DMA_InitStructure.DMA_BufferSize = Size; //转运的数据大小(转运次数)
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //模式,选择正常模式
DMA_InitStructure.DMA_M2M = DMA_M2M_Enable; //存储器到存储器,选择使能
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //优先级,选择中等
DMA_Init(DMA1_Channel1, &DMA_InitStructure); //将结构体变量交给DMA_Init,配置DMA1的通道1
/*DMA使能*/
DMA_Cmd(DMA1_Channel1, DISABLE);
}
2.转运接口
void MyDMA_Transfer(void)
{
DMA_Cmd(DMA1_Channel1, DISABLE);
DMA_SetCurrDataCounter(DMA1_Channel1, MyDMA_Size);
DMA_Cmd(DMA1_Channel1, ENABLE);
while (DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);
DMA_ClearFlag(DMA1_FLAG_TC1);
}
3.主函数
int main(void)
{
OLED_Init();
MyDMA_Init((uint32_t)DataA, (uint32_t)DataB, 4);
/*显示静态字符串*/
OLED_ShowString(1, 1, "DataA");
OLED_ShowString(3, 1, "DataB");
/*显示数组的首地址*/
OLED_ShowHexNum(1, 8, (uint32_t)DataA, 8);
OLED_ShowHexNum(3, 8, (uint32_t)DataB, 8);
while (1)
{
DataA[0] ++; //变换测试数据
DataA[1] ++;
DataA[2] ++;
DataA[3] ++;
OLED_ShowHexNum(2, 1, DataA[0], 2); //显示数组DataA
OLED_ShowHexNum(2, 4, DataA[1], 2);
OLED_ShowHexNum(2, 7, DataA[2], 2);
OLED_ShowHexNum(2, 10, DataA[3], 2);
OLED_ShowHexNum(4, 1, DataB[0], 2); //显示数组DataB
OLED_ShowHexNum(4, 4, DataB[1], 2);
OLED_ShowHexNum(4, 7, DataB[2], 2);
OLED_ShowHexNum(4, 10, DataB[3], 2);
Delay_ms(1000); //延时1s,观察转运前的现象
MyDMA_Transfer(); //使用DMA转运数组,从DataA转运到DataB
OLED_ShowHexNum(2, 1, DataA[0], 2); //显示数组DataA
OLED_ShowHexNum(2, 4, DataA[1], 2);
OLED_ShowHexNum(2, 7, DataA[2], 2);
OLED_ShowHexNum(2, 10, DataA[3], 2);
OLED_ShowHexNum(4, 1, DataB[0], 2); //显示数组DataB
OLED_ShowHexNum(4, 4, DataB[1], 2);
OLED_ShowHexNum(4, 7, DataB[2], 2);
OLED_ShowHexNum(4, 10, DataB[3], 2);
Delay_ms(1000); //延时1s,观察转运后的现象
}
}
四、程序现象
-
初始化 OLED 与 DMA,用
MyDMA_Init((uint32_t)DataA, (uint32_t)DataB, 4)把两个数组地址传入,并显示两者的首地址与数组内容。 -
主循环先修改
DataA[](自增),显示 DataA 与 DataB。 -
调用
MyDMA_Transfer()将 DataA 的内容拷贝到 DataB(通过 DMA)。 -
通过 OLED 在调用前后对比两组值,可以直观观察到 DMA 完成后
DataB被更新为DataA的当前值。
五、DMA+ADC思路
-
准备缓冲区
-
配置 ADC
-
配置 DMA(外设→内存)
-
启动 DMA。
-
处理数据