简介
存储器映像
ROM:只读存储器,是一种非易失性、掉电不丢失的存储器
RAM:随机存储器,是一种易失性、掉电丢失的存储器
DMA框图
使用DMA进行数据转运可以归为一类问题—— 从某个地址取内容,然后再放到另一个地址去
为了高效有条理地访问存储器,设计了一个总线矩阵,其左端是主动单元,拥有存储器的访问权,右边是被动单元,它们的存储器只能被左边的主动单元读写。
主动单元中内核有DCode和系统总线,可以访问右边的存储器。DCode专门访问Flash。
DMA需要转运数据,所以DMA也有访问的主动权,DMA1、DMA2和以太网都有各自的DMA总线。DMA1有七个通道,各个通道可以分别设置它们转运数据的源地址和目的地址,这样它们就可以各自独立地工作了。
虽然多个通道可以独立转运数据,但是因为DMA的总线只有一条,所以的通道都只能分时复用这一条DMA总线,如果产生冲突,那就会由仲裁器,根据通道的优先级决定先转运哪一条通道的数据。(另外总线矩阵中也有一个仲裁器,如果DMA和CPU都要访问同一个目标,那么DMA就会暂停CPU的访问,以防止冲突)。
因为DMA作为一个外设,它也有相对应的配置寄存器——AHB从设备。所以DMA即是总线矩阵的主动单元,可以读写各自存储器,也是AHB总线上的被动单元。
DMA请求:即触发,这条线路右边的触发源是各个外设,所以这个DMA请求就是DMA的硬件触发源(比如ADC转换完成,需要触发DMA转运数据时,就会通过这条线路,向DMA发出硬件触发信号)
基本结构
起始地址:有外设端的起始地址和存储器端的起始地址,决定里的数据从哪里来到哪里去的
数据宽度:指定一次转运要按多大的数据宽度来进行,可以选择字节Byte(8位,uint8_t)、半字HalfWord(16位,uint16_t)和字Word(32位,uint32_t)
地址是否自增:指定一次转运完成后,下一次转运是不是要把地址移动到下一个位置去,相当于指针p++(比如ADC扫描模式,用DMA进行数据转运,外设地址是ADC_DR寄存器,寄存器这边显然地址是不用自增的,如果自增,下一次转运就跑到别的寄存器那里去了;存储器这边地址就需要自增了,每转运一次都需要往后挪个位置,不然就会导致下次再转把上次的数据覆盖掉)
传出计数器:是个自减的计数器,输入一个数字,每传输一次数据,计数就自减,当计数为0时,停止转换;
自动重装器:(即转换完一轮是否需要再来一轮)当传输计数器计数为0时,自动重装器就可以决定是否从计数的初始置重新开始计数,不重装,就是正常的单次模式,如果重装,则是循环模式。如果是ADC扫描模式+连续转换,为了配合ADC,则DMA也需要使用循环模式;
触发控制:分为硬件触发和软件触发,有M2M这个参数控制选择哪一种。
软件触发:以最快的速度连续不断地触发DMA,争取最快速把传输计数器清零,完成这一轮转换,不可以和自动重装器(循环模式)搭配使用,因为这样下去就是无限循环触发DMA,运用在存储器到存储器转运的情况
硬件触发:可以选择ADC、串口、定时器等,一般与外设有关的转运才会使用硬件触发,因为外设的转运(ADC转换完成,串口接收到信号等等)都有一个时机,当达到这个时机时触发DMA的转运。
DMA转运的条件
1、开关控制,DMA_Cmd必须使能;
2、就是传输计数器必须大于0;
3、必须有触发信号
(当传输计数器为0时,无论怎么样都不能再触发DMA了,想要再次触发,就必须关闭开关控制,即给DMA_Cmd一个DISABLE信号,关闭DMA,再给传输计数器写入一个大于0的数,再打开开关控制,才能再次进行DMA转运,需要注意的是当传输计数器为0时,且开关控制在ENABLE状态下,是不能给传输计数器写入值的,需要关闭开关才可以写入)
DMA请求
硬件触发必须使用对应的通道,软件触发则随意
那么一个通道如何判断是哪个外设发出的请求信号呢,会有相关函数初始化相关外设的通道
数据宽度与对齐
总结:大数据转到小的里去,舍弃高位,小数据转到大的里去,高位补0
举个栗子
数据转运+DMA
需要把DataA的数据转运到DataB上去
应该如何配置DMA的数据
首先外设地址填DataA,存储器地址填DataB;数据宽度皆为uint8_t;地址是否自增:是;
传输计数器给7;不需要自动重装,软件触发(因为是存储器到存储器的转运,不需要等待时机)
ADC扫描模式+DMA
扫描模式下,ADC会自动转换从序列1到最后的序列的数据,不需要地址自增,而DMA储存器需要地址自增;因为ADC转换完一个通道后既不会置标志位,也不会触发中断,按理说是无法体现“时机”的,但是据研究表明ADC转换完一个序列的数据后,会自动触发DMA的数据转运,所以这里选择硬件触发。ADC的扫描模式不使用DMA,功能会受到很大的限制。
拓展
利用结构体访问寄存器的地址(原理详见江科大P247min)
比如要访问ADC1的DR寄存器
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
int main(void)
{
OLED_Init();
//要输入地址信息就需要强制转换为该类型
OLED_ShowHexNum(1, 1, (uint32_t)&ADC1->DR, 8);
while(1)
{
}
}
OLED上显示地址为:4001244C,这个地址是固定的,可以通过数据手册查出来的。
先打开数据手册的2.3存储器映像,可查到ADC1的地址起始位为0x40012400
然后再打开11.12.15ADC寄存器地址映像,可查到ADC_DR的偏移量为4C
综合可得到ADC1_DR的地址为0x4001244C。
如果想查某个寄存器的地址,可以先通过查存储器映像,查出其起始地址,然后再在外设的寄存器总表中查其偏移量,即可计算得出其地址了。
代码实操
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);
DMA设置当前数据寄存器(即给传输计数器写数据的)
void DMA_SetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx, uint16_t DataNumber);
DMA获取当前数据寄存器(返回传输计数器的值)
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);
按照下图编写代码
先建立一个数组作为传输的数据,建立另一个数组用于接收数据
再建立一个在System中建立模块(因为DMA不涉及外围硬件电路)
1、RCC开启DMA时钟
//开启时钟,DMA是AHB总线的设备
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
2、调用DMA_Init初始化各个参数
//DMA初始化
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;
//传输方向
//SRC-从外设站点传输到存储器站点,DST-从存储器传输到外设站点
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
//缓冲器大小(传输计数器)
DMA_InitStructure.DMA_BufferSize = BufferSize;
//传输模式(是否启用自动重装)(不自动重装)
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
//是否是存储器到存储器(硬件触发还是软件触发)
//Enable-软件触发,Disable-硬件触发
DMA_InitStructure.DMA_M2M = DMA_M2M_Enable;
//优先级(一个随便选
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
//y决定是哪个DMA,x决定是DMA的哪个通道
DMA_Init(DMA1_Channel1, &DMA_InitStructure);
3、开关控制文章来源:https://uudwc.com/A/Oq4B5
DMA_Cmd(DMA1_Channel1, ENABLE);
(还可以调用中断函数)文章来源地址https://uudwc.com/A/Oq4B5