Data Memory Access 直接存储器访问
DMA在接收到CPU的指令后,他可以搬运数据,而不再需要CPU来工作,此时CPU可以做其他的事情,无需CPU直接控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过硬件IO设备和RAM开辟一条直接传输数据的通道
作用:为CPU减负
DMA1 : P->M ,M->P 可以把外设的数据搬到SRAM,也可以把SRAM搬到外设
DMA2除了可以实现上述功能外,还能实现M->M (flash->SRAM),这是因为DMA2通过总线矩阵连接了FLASH 和SRAM的,而DMA1是直接连在了外设上。从下图中可以看出,DMA1的存储区端口比DMA2减少了AHB2外设的访问权,同时外设端口没有连接总线矩阵,所以DMA1是不能将存储器之一作为外设来实现存储器到存储器的传输的。
常见的外设有ADC, SPI, I^2C ,DCMI 等外设的数据寄存器,存储器一般指的是片内的SRAM, 外部存储器, 片内FLASH等。
传输的方向有以下几种:
(1)外设到存储器传输,把外设数据寄存器中的内容转移到指定的内存空间,e.g. 利用DMA把AD转换数据转移到定义的存储器中,对于多通道采集,采样频率高,连续输出的AD采集是非常高效的处理方法。
(2) 存储器到外设传输,把特定存储区的内容转移到外设的数据寄存器中, 多用于外设的发送通信;
(3) 存储器到存储器的传输,就是把数据从一个存储区复制到另一个存储区,功能类似于memcpy的操作。使用DMA传输可获得更高的传输效率,因为他不占用CPU,可以节省CPU资源。
通道和流
每个DMA有8个数据流,每个数据流又有8个通道(有的只有6个或7个),每个通道都有一个仲裁器,用于处理DMA的优先级。
DMA控制器 通过DMA数据流x配置寄存器DMAA_SxCR(x为0-7 对应的是8个DMA数据流)的CHSEL[2:0]位来选择对应通道作为数据流的目标外设,下面是DMA1 DMA2对应通道的映射关系
DMA1 的通道映射
DMA2的通道映射
每个外设都占用一个数据通道,相同外设可以请求占用不同的数据通道,比如SPI3_RX既可以用stream 0的channel 0 也可以用stream 2 的channel 0. 当然要注意的是,如果选择了channel 0 的stream 0 ,那么就不能再选择 channel 0 的stream 1 了,所以这样一对多的映射关系是为了尽可能的保证不会出现诸多外设均需要使用DMA时无法协调的情况。
仲裁器
简单来说,就是用来管理stream的。
比如说,使用同一个DMA控制器(DMA1/DMA2) 来应对多个外设请求时,必然要使用多个stream,那么如果外设都发送了DMA请求时,哪个stream具有优先传输的权利就由仲裁器 来进行管理。
仲裁分为两个阶段:
(1)软件阶段 配置寄存器的DMA_SxCR寄存器的PL[1:0]位,可以设为从非常高到低的四挡;
(2)硬件阶段 软件配置相同时,优先级取决于硬件流的编号,编号越低越具有优先权。
FIFO : 源和目标的数据中转站, 大小为4个字(1word = 4 byte = 4*8=32bit, for stm32), MA_SxFCR可以禁止直接模式或者使用直接模式,即选择是使用直接模式还是FIFO模式。
FIFO的作用
FIFO 对于要求源地址和目标地址数据宽度不同时非常有用,比如源数据是源源不断的字节数据,而目标地址要求输出字宽度的数据,即在实现数据传输时同时把原来 4 个 8 位字节的数据拼凑成一个 32 位字数据。此时使用 FIFO 功能先把数据缓存起来,分别根据需要输出数据。
FIFO 另外一个作用使用于突发 (burst) 传输。
选用直接模式时,A放到FIFO中后,直接就传个B ,不在FIFO中做任何停留。
如果选用FIFO模式,则可以配置DMA_SxFCR中的 FTH(FIFO Threshod)位来选择FIFO的阈值级别,可以选择阈值级别为1/4 ,1/2, 3/4,或者满,意思是,A的数据放入FIFO,放到1/4(其他值同理)FIFO,也就是1个字(4个字节)时,FIFO把数据再给B。
这4个字节又怎么传呢? 一个一个传还是打包成一个字来传? 这需要配置MBURST/PBURST。突发传输与 FIFO 密切相关,突发传输需要结合 FIFO 使用,具体要求 FIFO 阈值一定要是内存发传输数据量的整数倍。FIFO 阈值选择和存储器突发大小必须配合使用,具体参考下表。
一个节拍就当成MSIZE就行了,比如对于MSIZE为字节时,一个节拍就是一个字节,对于半字而言,一个节拍就是一个半字(2个字节)
DMA的传输模式
支持3种传输模式,DMA1支持两种, 通过控制DMA_SxCR的 DIR[1:0]来控制,进而将DMA_SxCR的EN位置置为1即可使能DMA传输
DMA_SxCR寄存器的PSIZE[1:0]和MSIZE[1:0]位即可分别制定外设和存储器的数据宽度大小,可以指定为字节(byte)半字(16位)和字(32位),要根据实际情况来进行配置。对于直接模式而言,其要求外设和存储器宽度大小一样,因此实际上在这种情况下,DMA数据流会以PSIZE为准,而MSIZE不被使用
源地址和目标地址
DMA_SxPAR来指定外设地址,DMA_SxM0AR 和DMA_SxM1AR存放存储器的地址
其中,DMA_SxM1AR只用于双缓冲模式。
如果方向为外设到存储器模式时,那么外设地址就是源地址,存储器的地址就是目标地址;
当方向为存储器到外设模式时,那么外设地址就是目标地址,存储器的地址就是源地址。
流控制器
流控制器主要涉及的是DMA传输停止的问题。DMA传输在DMA_SxCR寄存器的EN位被置为1之后就进入到准备传输的状态,如果有外设请求DMA传输就可以进行数据传输。
如果明确的知道传输收据的数目,比如我们知道要传送1000个数据,那么就可以在传输之前设置DMA_SxNDTR寄存器位要传输的数目值,在DMA完成传输这么多的数据后就会控制DMA停止传输。
DMA_SxNDTR寄存器是一个16位有效寄存器,最大值为65535,完成一次传送后,该寄存器的技术值就会自减,达到0则说明传输完成。
如果某些情况下在传输之前我们无法确定数据的数目,那 DMA 就无法自动控制传输停止了,此时需要外设通过硬件通信向 DMA 控制器发送停止传输信号。这里有一个大前提就是外设必须是可以发出这个停止传输信号,只有 SDIO 才有这个功能,其他外设不具备此功能
循环模式
如果是单次模式就是传输一次后停止传输,而循环模式则是每传输一次后按照相同的配置重新传输,周而复始直到被控制停止或者传输时发生错误。通过配置DMA_SxCR的CRC位可以使能循环模式。存储器到存储器传输时只能配置成一次模式
传输类型
传输类型分为单次传输(single) 和突发传输(burst)
突发传输就是用非常短时间结合非常高数据信号率传输数据,相对正常传输速度,突发传输就是在传输阶段把速度瞬间提高,实现高速传输,在数据传输完成后恢复正常速度,有点类似达到数据块“秒传”效果。为达到这个效果突发传输过程要占用 AHB 总线,保证要求每个数据项在传输过程不被分割,这样一次性把数据全部传输完才释放 AHB 总线,此时不可被打断;而单次传输时必须通过 AHB 的总线仲裁多次控制才传输完成。
单次和突发传输数据使用具体情况参考表 22‑4。其中 PBURST[1:0] 和 MBURST[1:0] 位是位于DMA_SxCR 寄存器中的,用于分别设置外设和存储器不同节拍数的突发传输,对应为单次传输、4 个节拍增量传输、8 个节拍增量传输和 16 个节拍增量传输。PINC 位和 MINC 位是寄存器DMA_SxCR 寄存器的第 9 和第 10 位,如果位被置 1 则在每次数据传输后数据地址指针自动递增,其增量由 PSIZE 和 MSIZE 值决定,比如,设置 PSIZE 为半字大小,那么下一次传输地址将是前一次地址递增 2。
双缓冲模式
设置 DMA_SxCR 寄存器的 DBM 位为 1 可启动双缓冲传输模式,并自动激活循环模式。双缓冲不应用与存储器到存储器的传输。双缓冲模式下,两个存储器地址指针都有效,即 DMA_SxM1AR寄存器将被激活使用。开始传输使用 DMA_SxM0AR 寄存器的地址指针所对应的存储区,当这个存储区数据传输完 DMA 控制器会自动切换至 DMA_SxM1AR 寄存器的地址指针所对应的另一块存储区,如果这一块也传输完成就再切换至 DMA_SxM0AR 寄存器的地址指针所对应的存储区,这样循环调用。
当其中一个存储区传输完成时都会把传输完成中断标志 TCIF 位置 1,如果我们使能了DMA_SxCR 寄存器的传输完成中断,则可以产生中断信号,这个对我们编程非常有用。另外一个非常有用的信息是 DMA_SxCR 寄存器的 CT 位,当 DMA 控制器是在访问使用 DMA_SxM0AR时 CT=0,此时 CPU 不能访问 DMA_SxM0AR,但可以向 DMA_SxM1AR 填充或者读取数据;当DMA 控制器是在访问使用 DMA_SxM1AR 时 CT=1,此时 CPU 不能访问 DMA_SxM1AR,但可以向 DMA_SxM0AR 填充或者读取数据。另外在未使能 DMA 数据流传输时,可以直接写 CT 位,改变开始传输的目标存储区。
双缓冲模式应用在需要解码程序的地方是非常有效的。比如 MP3 格式音频解码播放,MP3 是被压缩的文件格式,我们需要特定的解码库程序来解码文件才能得到可以播放的 PCM 信号,解码需要一定的实际,按照常规方法是读取一段原始数据到缓冲区,然后对缓冲区内容进行解码,解码后才输出到音频播放电路,这种流程对 CPU 运算速度要求高,很容易出现播放不流畅现象。如果我们使用 DMA 双缓冲模式传输数据就可以非常好的解决这个问题,达到解码和输出音频数据到音频电路同步进行的效果。
typedef struct {
uint32_t DMA_Channel; //通道选择,可以选择通道0-7,设置CHSEL[2:0]
uint32_t DMA_PeripheralBaseAddr; //外设地址,设置SxPAR,一般设置为外设的数据寄存器地址
uint32_t DMA_Memory0BaseAddr; //存储器 0 地址。设置SxM0AR,一般设定为我们自定义的存储区首地址
uint32_t DMA_DIR; //传输方向,设定外设到存储器/存储器到外设/存储器到存储器
uint32_t DMA_BufferSize; //数据数目,设定SxNDTR寄存器的值
uint32_t DMA_PeripheralInc; //外设递增,配置PINC位,一般外设只有一个数据寄存器,不使能
uint32_t DMA_MemoryInc; //存储器递增,配置MINC位,一般存储区是存放多个地址的,所以使能存储器自动递增功能
uint32_t DMA_PeripheralDataSize; //外设数据宽度,可选字节(8位)半字和字,配置的是PSIZE
uint32_t DMA_MemoryDataSize; //存储器数据宽度,同上,配置的是MSIZE
uint32_t DMA_Mode; //模式选择,配置一次传输或者循环传输
uint32_t DMA_Priority; //优先级,设定软件优先级
uint32_t DMA_FIFOMode; //FIFO 模式,是否使能FIFO模式,在存储器到存储器模式时FIFO自动开启,软件禁止不了
uint32_t DMA_FIFOThreshold; //FIFO 阈值,只有使能了FIFO模式才有用
uint32_t DMA_MemoryBurst; //存储器突发传输,存储器突发模式选择,配置的是MBURST可选单次模式/4节拍增量/8节拍/16节拍
uint32_t DMA_PeripheralBurst; //外设突发传输,配置的是PBURST
} DMA_InitTypeDef;
void DMA_DeInit(DMA_Stream_TypeDef* DMAy_Streamx);//初始化DMA的寄存器到复位的状态,这是为了防止开始的时候FIFO里边还存在有上一次的数据,养成习惯先复位一次
void DMA_Init(DMA_Stream_TypeDef* DMAy_Streamx, DMA_InitTypeDef* DMA_InitStruct);//初始化函数
void DMA_Cmd(DMA_Stream_TypeDef* DMAy_Streamx, FunctionalState NewState);//DMA使能函数
M TO M编程要点
1.在FLASH中定义好要传输的数据,在SRAM中定义好要放的位置,定义好用来接收Flash数据的变量即可,在c语言中采用const关键字修饰后可以把数据放在FLASH里面,所以先定义了一个常亮的数组,作为我们要传输的数据。
确定使用DMA的那个数据流,哪个通道?定义成宏,方便修改。在m to m模式下,只能用DMA2,通道,流任选。
初始化DMA,配置DMA初始化结构体