嵌入式——DMA(直接存储器存取)
作用:恢复缺省配置作用:初始化作用:结构体初始化作用:使能作用:中断输出使能作用:给传输计数器写数据作用:返回传输计数器的值作用:获取标志位状态作用:清除标志位作用:获取中断状态作用:清除中断挂起位初始化DMA步骤:①RCC开启DMA时钟②调用DMA_Init,初始化各个参数(外设站点三个参数)将函数参数1改为uint32_t ADDrA;//存储器站点三个参数// 起始地址将函数参数2改为uin
一、DMA
1.DMA简介
DMA(Direct Memory Access)直接存储器存取
DMA可以提供外设(外设寄存器,一般是外设数据寄存器DR,例如ADC的数据寄存器,串口的数据寄存器)和存储器(运行内存SARM和程序存储器FLASH,存储变量数组和程序代码的地方)或者存储器和存储器之间的高速数据传输,无须CPU干预,节省了CPU的资源
STM32具有12个独立可配置的通道(数据转运的路径): DMA1(7个通道), DMA2(5个通道)
每个通道都支持软件触发(存储器和存储器)和特定的硬件触发(外设和存储器)。
STM32F103C8T6 DMA资源:DMA1(7个通道)
2.存储器映像

ROM——只读存储器,非易失性,掉电不丢失的存储器
RAM——随机存储器,易失性,掉电丢失的存储器
3.DMA框图

左上角为Cortex-M3内核,里面包含了CPU和内核外设,剩下的全是存储器,Flash是主闪存,SARM是运行内存,各个外设都可以看成是寄存器,也是一种SARM存储器。
为了高效有条理地访问存储器,设计了一个总线矩阵,总线矩阵的左端,是主动单元,也就是拥有存储器的访问权。内核有DCode和系统总线,可以访问右边的存储器,其中DCode总线专门访问Flash,系统总线是访问其他东西的。此外由于DMA要转运数据,所以DMA也拥有存储器的访问权,由于DMA1有7个通道,但是DMA总线只有一个,所以当多个通道需要同时转运数据时,需要经过仲裁器,进行优先级分组,如果DMA和CPU都要访问同一个目标,那么DMA会暂停CPU的访问,以防止冲突。DMA自身的的寄存器——AHB从设备(配置DMA参数),DMA作为一个外设,自己也会有相应的配置寄存器,这里连接到了总线右边的AHB总线上,所以DMA,既是总线矩阵的主动单元,可以读写各种存储器,也是AHB总线上的被动单元,从CPU到AHB从设备的线路,就可以对DMA进行配置。DMA请求就是DMA的硬件触发源。CPU还是DMA对Flash都是可读不可写的状态。
右边是被动单元,它们的存储器只能被左边的主动单元读写。
4.DMA基本结构

外设寄存器和Flash以及SRAM为数据转运的两大站点,左边是外设寄储器站点,右边是存储器站点,包括Flash以及SRAM。外设和存储器都有三个参数,第一个参数:起始地址,有外设端的起始地址和存储器端的起始地址,决定数据从哪来到哪去,第二个参数:数据宽度,指定一次转运要按多大的数据宽度来进行,可以选择字节Byte(8位),半字节HalfWod(16位)和字节Word(32位)。第三个参数:地址是否自增,指定一次转运完成后,下一次转运,是不是要把地址移动到下一个位置去。
下面有传输计数器用来指定总共需要转运几次,是一个自减计数器。右边有自动重装器,也就是说当传输寄存器减到0后,是否要自动恢复到最初的值,决定转运的模式(单次模式或循环模式)。
最下面为DMA触发控制,触发源有硬件触发和软件触发,具体选择哪个由M2M参数决定,当M2M为1时,DMA选择软件触发(以最快的速度,连续不断的触发DMA,以最快速度把传输计数器清零,完成转换,不能与循环模式连续用,适合存储器到存储器的转运),当M2M为0时,DMA选择硬件触发。
最后为开关控制,也就是DMA_Cmd,当给DMA使能后,DMA准备就绪,可以进行转运。
5.数据宽度与对齐
将小的数据转到大的里面去,高位补0,将大的数据转到小的里面去,高位舍弃。
二、DMA库函数介绍
void DMA_DeInit(DMA_Channel_TypeDef* DMAy_Channelx);
作用:恢复缺省配置
void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx, DMA_InitTypeDef* DMA_InitStruct);
作用:初始化
void DMA_StructInit(DMA_InitTypeDef* DMA_InitStruct);
作用:结构体初始化
void DMA_Cmd(DMA_Channel_TypeDef* DMAy_Channelx, FunctionalState NewState);
作用:使能
void DMA_ITConfig(DMA_Channel_TypeDef* DMAy_Channelx, uint32_t DMA_IT, FunctionalState NewState);
作用:中断输出使能
void DMA_SetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx, uint16_t DataNumber);
作用:给传输计数器写数据
uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx);
作用:返回传输计数器的值
FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG);
作用:获取标志位状态
void DMA_ClearFlag(uint32_t DMAy_FLAG);
作用:清除标志位
ITStatus DMA_GetITStatus(uint32_t DMAy_IT);
作用:获取中断状态
void DMA_ClearITPendingBit(uint32_t DMAy_IT);
作用:清除中断挂起位
三、DMA代码介绍
1.DMA数据转运——使用DMA进行存储器到存储器的数据转运(将一个数组里的数据,复制到另一个数组里)
初始化DMA步骤:
①RCC开启DMA时钟
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
②调用DMA_Init,初始化各个参数
DMA_InitTypeDef DMA_InitStructure;
(外设站点三个参数)
DMA_InitStructure.DMA_PeripheralBaseAddr = ADDr;将函数参数1改为uint32_t ADDrA;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable;
//存储器站点三个参数
// 起始地址
DMA_InitStructure.DMA_MemoryBaseAddr = ADDrB;将函数参数2改为uint32_t ADDrB
//存储器站点数据宽度
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
// 存储器站点地址是否自增
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_BufferSize = Size; //参数:将函数参数3改为uint16_t Size;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
参数:外设站点时源端还是目的地
DMA_DIR_PeripheralDST——目的地
DMA_DIR_PeripheralSRC——源端
DMA_InitStructure.DMA_M2M = DMA_M2M_Enable;
参数:触发方式(硬件触发还是软件)
DMA_M2M_Enable——软件触发
DMA_M2M_Disable——硬件触发
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
参数:工作模式(是否采用自动重装)
DMA_Mode_Circular——循环模式
DMA_Mode_Normal——单次模式
DMA_InitStructure.DMA_Priority =DMA_Priority_Medium; //优先级
DMA_Init(DMA1_Channel1, &DMA_InitStructure);
③开关控制,DMA_Cmd
DMA_Cmd(DMA1_Channel1, ENABLE);
DMA转运需要三个基本的元素,(1)DMA_Cmd使能(2)传输计数器必须大于0 (3)触发方式选择——必须有触发源
这里有三个模块首先是MyDMA.c进行DMA初始化,以及专门的DMA传输函数。
#include "stm32f10x.h" // Device header
uint16_t MyDMA_Size;
void MyDMA_Init(uint32_t ADDrA, uint32_t ADDrB, uint16_t Size)
{
MyDMA_Size = Size;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
DMA_InitTypeDef DMA_InitStructure;
DMA_InitStructure.DMA_PeripheralBaseAddr = ADDrA; // 外设站点基地址
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //外设站点的传输宽度
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable; // 外设站点的地址自增
DMA_InitStructure.DMA_MemoryBaseAddr = 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_M2M = DMA_M2M_Enable; // 触发方式
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; // 自动重装
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
DMA_Init(DMA1_Channel1, &DMA_InitStructure);
DMA_Cmd(DMA1_Channel1, DISABLE);
}
void MyDMA_Transfor(void) // DMA传输函数,改变传输计数器的值
{
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);
}
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "MyDMA.h"
uint8_t DataA[] = {0x01, 0x02, 0x03, 0x04}; // 源端数组
uint8_t DataB[] = {0, 0, 0, 0}; // 目的数组
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);
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);
OLED_ShowHexNum(4, 4, DataB[1], 2);
OLED_ShowHexNum(4, 7, DataB[2], 2);
OLED_ShowHexNum(4, 10, DataB[3], 2);
Delay_ms(1000);
MyDMA_Transfor();
OLED_ShowHexNum(2, 1, DataA[0], 2);
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);
OLED_ShowHexNum(4, 4, DataB[1], 2);
OLED_ShowHexNum(4, 7, DataB[2], 2);
OLED_ShowHexNum(4, 10, DataB[3], 2);
Delay_ms(1000);
}
}
运行,下载后就可以在显示屏看到,第一行显示DataA 后面跟地址,第二行显示DataA的每一位每隔一秒+1,第三行显示DataB后面跟地址,第四行显示DataB的每一位收到DMA转运每隔一秒+1。
其实CMD的一个主要作用在CMD与AD多通道结合,这一部分代码在下一篇进行深刻学习。
四、总结
DMA(直接内存访问)是嵌入式系统中无需CPU干预,让外设与内存(或内存间)直接传输数据的技术,能显著减轻CPU负担、提升传输效率,适用于高频大数据场景(如ADC采样、通信传输等)。
更多推荐



所有评论(0)