八、DMA
8.1 DMA
8.1.1 DMA简介
DMA:直接存储器存取(访问)
DMA可以提供外设和存储器或者存储器和存储器之间的告诉数据传输,无须CPU干预,节省了CPU资源
DAM有12个独立可配置的通道:DMA1(7个通道),DMA2(5个通道)每个通道都支持软件触发和特定的硬件触发
STM32的资源:DMA1(7个通道)
8.1.2 存储器映像
ROM:易失性的只读存储器,掉电不丢失
RAM:易失性的随机读写存储器,掉电丢失
8.1.3 DMA结构
DMA框图:
左上角的Cortex—M3核心,包含了CPU和内核外设,其他的可以全部理解为存储器
Flash:主闪存(只读)
SRAM:运行内存(可以任意读写)
DMA转运数据可以简易地理解为,从一个地址读取数据,运送到另一个地址
总线矩阵:其左边是主动单元,可以主动读写存储器的内容,其右边是被动单元,只能被动地读写
DCode专门访问Flsah,系统总线访问其他区域
DMA中的仲裁器:根据优先级决定哪个通道优先访问
总线矩阵中也有仲裁器,如果DMA和CPU想要同时访问,那么DMA就会停止系统的访问,但是仍然会留给CPU一半的带宽,保证其可以正常使用。
AHB从设备:DMA的寄存器,连接在了总线右边的AHB总线上
所以DMA既是DMA上的主动单元,也是DMA上的被动单元
DMA基本结构
外设寄存器和存储器各有三个参数,用于控制传输数据的信息,其中起始地址可以进行修改,互换都可以,其只是一个名字而已;地址是否自增的函数,控制想要拿走或取来的数据的所在地址需不需要更换。
方向是一个单独的参数,可以控制数据运输的方向,但是不可以将任何一个地方的数据转运到Flash,因为Flash是只读存储器。
传输计数器:控制数据转运的次数,是一个自减计数器,比如给其5,不断减,到0就不再进行转运数据;同时,当其变为0之后,自增的地址会回到初始位置,以方便DMA下次转运
自动重装器:传输计数器减到0之后是否要回到最初的值
剩下的部分是DMA的触发部分,具体是硬件触发还是软件触发,由参数M2M决定,1由软件触发,2由硬件触发。
软件触发的逻辑是,以最快的速度使传输计数器清零,完成一轮的转换;并且软件触发不可以和自动重装器同时使用;其一般用于存储器到存储器的转运,不需要时机只需要快速完成任务。
硬件触发可以选择,ADC,串口,定时器等等;一般都是与外设有关的转运。
DMA运行有三个条件:1、开关控制使能。2、传输计数器大于0。3、有触发源
当传输计数器为0,且自动重装器不重装时,需要先将开关控制失能,然后再修改传输计数器的值,其为手册中的要求。
8.1.4 DMA请求映像
值得注意的是,每个通道支持硬件触发的硬件都各不相同。
选择每个通道的硬件触发源的方法为:打开该硬件的DAM通道,使用硬件的库函数即可完成;如果三个都开启了,三个硬件都可以进行触发。
固定硬件优先级为:通道号越小优先级越高,同时也可以自行配置优先级。
8.2 DMA实战
接线图:因为数据转运都是在STM32内部进行的,因此只需要一个OLED即可
知识补充:
STM32中const定义的变量,是存储在flash中的,只能读不能写,变为常量。
作用:当需要一堆变量不需要修改的时候,可以使用const进行定义,可以节省SRAM空间
MyDMA.c
#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_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_Cmd(DMA1_Channel1,DISABLE); } 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); } 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,4,(uint32_t)DataB,8); 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); 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_Transfer(); 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); } } 实现效果:DMA将存储器中的数据进行转运,显示在OLED中。