《STM32从零开始学习历程》@EnzoReventon
本文主要介绍STM32F4 DMA直接存储区的理论知识部分,本文主要参考手册为:
[野火EmbedFire]《STM32库开发实战指南——基于野火霸天虎开发板》
[正点原子]STM32F4开发指南-库函数版本_V1.2
[ST]《STM32F4xx中文参考手册》
在学习野火教程第22章的基础上进行理解、解读与拓展,争取以一种比较好理解的简述方式向大家介绍这一内容。
DMA(Direct Memory Access, 直接存储区访问)是一种独立于CPU的控制器。它的主要功能是实现数据在“外设寄存器与存储器之间”、“存储器与存储器之间”独立于CPU的高速传输。
在这里,外设一般指外设的数据寄存器(如ADC,SPI,I2C,DCMI等外设的数据寄存器);存储器一般是指片内SRAM、外部存储器、片内FLASH等。
① 外设寄存器到存储器传输:就是把外设数据寄存器内容转移到指定的内存空间中。
② 存储器到外设寄存器传输:就是把特定存储区域的内容转移到外设寄存器中。
③ 存储器到存储器传输:就是把一个指定存储区内容拷贝到另一个指定的存储区中。
注意: DMA1 仅支持① ② ,不支持③。,DMA2支持① ② ③。
=============================================================================================
解释一下为什么DMA1不支持存储器到存储器的传输:
如上图所示,DMA2控制器ANB接口都与中间的总线矩阵相连接了,存储器RAM与总线矩阵相连接,因此DMA2可以实现存储器与存储器之间的数据传输。
而DMA1,其中一个AHB接口没有与总线矩阵相连接,固然也就无法实现存储器与存储器之间的数据传输了。
作为一个固定的知识点记住就好。
STM32F4其中一个的DMA控制器框图如上图所示,下面我们来详细讲解一下这个框图。(STM32一共有两个DMA控制器)
=============================================================================================
在上图中,左边的叫做通道,右边的叫做流。
好了,那么问题来了,什么是“通道”?什么是“流”?
首先,“流”是数据传输的一条链路,“通道”:emmm不知道怎么解释,只可意会不可言传,不同的通道对印着不同的DMA请求。
紧接着,从上图看到,对于一个DMA控制器共有8路“流”,每一“流”,又有8个“通道”!也就是说,对于一个DMA控制共有8 x 8 = 64 路通道可以用来独立的传输数据。
好了又有问题来了,那么这么多通道怎么用?是不是可以随便用?
答案是否定的!
每一个通道有着不同的传输请求映射!如下表所示:
DMA1:
外设请求 | 数据流0 | 数据流1 | 数据流2 | 数据流3 | 数据流4 | 数据流5 | 数据流6 | 数据流7 |
---|---|---|---|---|---|---|---|---|
通道 0 | SPI3_RX | SPI3_RX | SPI2_RX | SPI2_TX | SPI3_TX | SPI3_TX | ||
通道 1 | I2C1_RX | TIM7_UP | TIM7_UP | I2C1_RX | I2C1_TX | I2C1_TX | ||
通道 2 | TIM4_CH1 | I2S3_EXT_RX | TIM4_CH2 | CH2 I2S2_EXT_TX | I2S3_EXT_TX | TIM4_UP | TIM4_CH3 | |
通道 3 | I2S3_EXT_RX | TIM2_UP TIM2_CH3 | I2C3_RX | I2S2_EXT_RX | I2C3_TX | TIM2_CH1 | TIM2_CH2 TIM2_CH4 | TIM2_UP TIM2_CH4 |
通道 4 | UART5_RX | USART3_RX | UART4_RX | USART3_TX | UART4_TX | USART2_RX | USART2_TX | UART5_TX |
通道 5 | UART8_TX | UART7_TX | TIM3_CH4 TIM3_UP | UART7_RX | TIM3_CH1 TIM3_TRIG | TIM3_CH2 | UART8_RX | TIM3_CH3 |
通道 6 | TIM5_CH3 TIM5_UP | TIM5_CH4 TIM5_TRIG | TIM5_CH1 | TIM5_CH4 TIM5_TRIG | TIM5_CH2 | TIM5_UP | ||
通道 7 | TIM6_UP | I2C2_RX | I2C2_RX | USART3_TX | DAC1 | DAC2 | I2C2_TX |
DMA2:
外设请求 | 数据流0 | 数据流1 | 数据流2 | 数据流3 | 数据流4 | 数据流5 | 数据流6 | 数据流7 |
---|---|---|---|---|---|---|---|---|
通道 0 | ADC1 | TIM8_CH1 TIM8_CH2 TIM8_CH3 | ADC1 | TIM1_CH1 TIM1_CH2 TIM1_CH3 | ||||
通道 1 | DCMI | ADC2 | ADC2 | SPI6_TX | SPI6_RX | DCMI | ||
通道 2 | ADC3 | ADC3 | SPI5_RX | SPI5_TX | CRYP_OUT | CRYP_IN | HASH_IN | |
通道 3 | SPI1_RX | SPI1_RX | SPI1_TX | SPI1_TX | ||||
通道 4 | SPI4_RX | SPI4_TX | USART1_RX | SDIO | SART1_RX | SDIO | USART1_TX | |
通道 5 | USART6_RX | USART6_RX | SPI4_RX | SPI4_TX | USART6_TX | USART6_TX | ||
通道 6 | TIM1_TRIG | TIM1_CH1 | TIM1_CH2 | TIM1_CH1 | TIM1_CH4 TIM1_TRIG TIM1_COM | TIM1_UP | TIM1_CH3 | |
通道 7 | TIM8_UP | TIM8_CH1 | TIM8_CH2 | TIM8_CH3 | SPI5_RX | SPI5_TX | TIM8_CH4 TIM8_TRIG TIM8_COM |
每个外设请求都会占用一个数据流通道,相同外设请求可以占用不同的数据流通道。
例如:DMA1的数据流0,我选择使用通道2 I2C1_RX,那么其他7路通道就不可以使用了。
再例如:DMA1的数据流2,通道1,TIM7_UP与DMA1的数据流4,通道1,TIM7_UP是可以同时使用的。也就是说只要不是同一个数据流就可以了。
好了,通道 和 流 的基本信息就已经介绍完了,总结一下:
① STM32F4共有2个DMA控制器
② DMA1不支持存储器到存储器传输
③ 每一个DMA有8路数据流
④ 每一路流有8个通道
⑤ 每一个通道有特定的功能映射,需要查阅上文的表格进行选择使用
⑥ 每一路流的只能有一个通道使用,一路流有一个以上通道使用是不允许的
⑦ 相同功能不同流的通道可以同时使用
=============================================================================================
顾名思义,仲裁器是用来仲裁、评判的,一个DMA控制器有8个数据流,如果在某一时刻,我们使用同一个DMA控制器中的多个数据流进行传输数据,那么必然会导致有多个数据流,如果没有仲裁器,就像是十字路口没有红绿灯,会造成拥堵甚至事故,对于数据也是如此,因此需要一个仲裁器来设定数据流的优先传输的权力。
通过仲裁器来设定流的优先级时分为两个阶段,第一阶段为软件阶段,第二阶段为硬件阶段。
软件阶段: 用户可以通过配置优先级寄存器,将数据流优先级设定为:非常高,高,中和低四个级别。
硬件阶段: 如果两个或以上数据流的优先级一样,则仲裁器优先级设定转为硬件层面,他们的优先级取决于数据流的编号,编号越低则优先级越高,例如,在软件层面优先级相同的情况下,数据流1的优先级高于数据流5的优先级。
=============================================================================================
我们要重点的来讲一下什么是FIFO,First In First Out。
首先,FIFO叫做先进先出存储缓冲区,它介于源与目标之间,是一个数据的中转站。
每一个数据流都有4字大小的FIFO。1字 = 4字节 = 32位,4字 = 16字节。
对于这个缓冲区,存在有两种数据传输模式:第一种为直接模式,第二种为FIFO模式。
模式的选择由 DMA_SxFCR寄存器的DMDIS位控制,0为使能直接模式,1为禁止直接模式、开启FIFO模式。
直接模式: 数据经过FIFO,不在FIFO中停留,直接将数据传送到目标地址中。
FIFO模式: 数据经过FIFO,在FIFO中停留,根据设置的阈值,等到数据量达到了设定的阈值然后再发送到目标地址中。阈值可以设定为1/4,1/2,3/4,1。例如,阈值设定为1/4时,FIFO中的数据量达到16*(1/4)= 4 字节时将数据打包发送到目标地址中。
对于在FIFO中的数据,是以什么方式进行传输的呢?例如,阈值设定为1/4时,FIFO中也存满了4字节的数据,这4字节的数据怎么打包发送呢?它是4字节全部打包发送还是4字节分四次打包发送呢?这就需要另外一个寄存器进行选择配置了:DMA_SxCR中的MBURST(存储器突发传输配置)以及PBURST(外设突发传输配置),突发模式!
下一章节将详细的讲解何为突发模式!
1. 突发&节拍的配置
位 24:23 MBURST:存储器突发传输配置 (Memory burst transfer configuration)
这些位将由软件置 1 和清零。
00:单次传输
01:INCR4(4 个节拍的增量突发传输)
10:INCR8(8 个节拍的增量突发传输)
11:INCR16(16 个节拍的增量突发传输)
这些位受到保护,只有 EN 为“0”时才可以写入 在直接模式中,当位EN=“1”时,这些位由硬件强制置为 0x0。
位 22:21 PBURST[1:0]:外设突发传输配置 (Peripheral burst transfer configuration)
这些位将由软件置 1 和清零。
00:单次传输
01:INCR4(4 个节拍的增量突发传输)
10:INCR8(8 个节拍的增量突发传输)
11:INCR16(16 个节拍的增量突发传输)
这些位受到保护,只有 EN为“0”时才可以写入 在直接模式下,这些位由硬件强制置为 0x0。
这里又来了一个“节拍”的概念,我们来详细的讲一下这是什么。
MSIZE | FIFO级别 | MBURST=INCR4 | MBURST=INCR8 | MBURST=INCR16 |
---|---|---|---|---|
字节 | 1/4 | 4个节拍1次突发 | – | – |
字节 | 1/2 | 4个节拍2次突发 | 8个节拍1次突发 | – |
字节 | 3/4 | 4个节拍3次突发 | – | – |
字节 | 1 | 4个节拍4次突发 | 8个节拍2次突发 | 16个节拍1次突发 |
半字 | 1/4 | – | – | – |
半字 | 1/2 | 4个节拍1次突发 | – | – |
半字 | 3/4 | – | – | – |
半字 | 1 | 4个节拍2次突发 | 8个节拍1次突发 | – |
字 | 1/4 | – | – | – |
字 | 1/2 | – | – | – |
字 | 3/4 | – | – | – |
字 | 1 | 4个节拍1次突发 | – | – |
首先,FIFO大小为4个字,FIFO阈值 = FIFO级别 x FIFO大小。
例如:
FIFO级别为1/4时,FIFO阈值大小为 4 x(1/4) = 1字 = 4字节。
FIFO级别为3/4时,FIFO阈值大小为 4 x(3/4) = 3字 = 12字节。
=============================================================================================
当目标地址存储单元为字节时:
FIFO级别为1/4时:
当FIFO中存满4 x(1/4) = 1字 = 4字节时,完成1次突发,4字节数据一次性打包发送给目标地址,每次发送4个字节。
FIFO级别为1/2时:
当FIFO中存满4 x(1/2) = 2字 = 8字节时,完成2次突发,8字节数据分两次发送给目标地址,每次发送4个字节;
或者8字节数据一次性发送给目标地址,每次发送8个字节。
FIFO级别为3/4时:
当FIFO中存满4 x(3/4) = 3字 = 12字节时,完成3次突发,12字节数据分三次发送给目标地址,每次发送4个字节。
FIFO级别为1时:
当FIFO中存满4 x 1 = 4字 = 16字节时,可以以4次突发,16字节数据分四次发送给目标地址,每次发送4个字节;
可以以2次突发,16字节数据分两次发送给目标地址,每次发送8个字节;
也可以以1次突发,16字节数据一次性传输给目标地址,每次发送16个字节。
=============================================================================================
为什么FIFO级别为1/4时不能以8个节拍进行发送呢?
因为,FIFO的级别为1/4,其阈值为1个字,4个字节,此时FIFO存满也就4个字节的数据,固然无法实现8个节拍进行发送。
为什么MSIZE为半字时,无法实现FIFO的级别为1/4以4个字节发送?
因为,FIFO的级别为1/4,其阈值为1个字,4个字节;如果是4个节拍发送的话,就是4个半字=8个字节发送,此时FIFO最大也就只有4个字节的数据,也便无法实现发送。
为什么MSIZE为字时,无法实现FIFO的级别为3/4以3个字节发送?
因为,FIFO的级别为3/4,其阈值为3个字,12个字节;如果实现4个节拍发送的画,就是4个字=16个字节发送,此时FIFO最大也就12个字节的数据,无法实现发送。
下面再来解释下为什么为什么MSIZE为半字时,可以实现FIFO的级别为1/2以8个字节发送?
因为,FIFO的级别为1/2,其阈值为2个字,8个字节;以4个节拍也就是4个半字=8个字节发送,正好等于FIFO阈值的数据大小(8个字节),所以可以成功发送。
以此类推!
=============================================================================================
PS: 在这里为了更好的理解FIFO阈值配置表格,可以将节拍理解为MSIZE,例如:MSIZE为字节时,FIFO级别为1/4(1个字,4个字节),4个节拍1次突发可以理解为4个字节一次突发;MSIZE为半字时,8个节拍一次突发可以理解为8个半字(4个字,16个字节),FIFO级别为1时(4个字,16个字节),可以实现一次突发。
也就是说:
节拍数(MSIZE数)x 突发数 = FIFO级别 x FIFO大小 (FIFO大小固定为4字)
例如:MSIZE = 半字;FIFO级别为 = 1/2;MBURST = 4个节拍1次突发
节拍数(MISIZE数)= 半字
突发数 = 4节拍 x 1次突发 = 4 半字 = 2 字 = 8字节
那么:节拍数(MSIZE数)x 突发数 = 8 字节
FIFO阈值 = 1/2 x 4 字 = 2 字 = 8字节
由此可见 FIFO阈值 = 节拍数(MSIZE数)x 突发数。
2. 循环模式
循环模式相对应于一次模式。一次模式就是传输一次就停止传输,下一次传输需要手动控制,而循环模式在传输一次后会自动按照相同配置重新传输,周而复始直至被控制停止或传输发生错误。
可以通过DMA_SxCR 寄存器的CIRC 位可以使能循环模式。
3. 双缓冲模式
设置DMA_SxCR 寄存器的DBM 位为1 可启动双缓冲传输模式,并自动激活循环模式。
双缓冲不应用与存储器到存储器的传输。双缓冲模式下,两个存储器地址指针都有效,即DMA_SxM1AR寄存器将被激活使用。开始传输使用DMA_SxM0AR 寄存器的地址指针所对应的存储区,当这个存储区数据传输完DMA 控制器会自动切换至DMA_SxM1AR 寄存器的地址指针所对应的另一块存储区,如果这一块也传输完成就再切换至DMA_SxM0AR 寄存器的地址指针所对应的存储区,这样循环调用。
4. DMA中断
每个DMA 数据流可以在发送以下事件时产生中断:
typedef struct {
uint32_t DMA_Channel; //通道选择
uint32_t DMA_PeripheralBaseAddr; //外设地址
uint32_t DMA_Memory0BaseAddr; //存储器0 地址
uint32_t DMA_DIR; //传输方向
uint32_t DMA_BufferSize; //数据数目
uint32_t DMA_PeripheralInc; //外设递增
uint32_t DMA_MemoryInc; //存储器递增
uint32_t DMA_PeripheralDataSize; //外设数据宽度
uint32_t DMA_MemoryDataSize; //存储器数据宽度
uint32_t DMA_Mode; //模式选择
uint32_t DMA_Priority; //优先级
uint32_t DMA_FIFOMode; //FIFO 模式
uint32_t DMA_FIFOThreshold; //FIFO 阈值
uint32_t DMA_MemoryBurst; //存储器突发传输
uint32_t DMA_PeripheralBurst; //外设突发传输
}
1) DMA_Channel:
DMA 请求通道选择,可选通道0 至通道7,每个外设对应固定的通道,具体设置值需要查表DMA1 各个通道的请求映像和表DMA2 各个通道的请求映像。
2) DMA_PeripheralBaseAddr:
外设地址,设定DMA_SxPAR 寄存器的值;一般设置为外设的数据寄存器地址,如果是存储器到存储器模式则设置为其中一个存储区地址。
ADC3 的数据寄存器ADC_DR 地址为((uint32_t)ADC3+0x4C)。
3) DMA_Memory0BaseAddr:
存储器0 地址,设定DMA_SxM0AR 寄存器值;一般设置为我们自义存储区的首地址。我们程序先自定义一个16 位无符号整形数组ADC_ConvertedValue[4]用来存放每个通道的ADC 值, 所以把数组首地址(直接使用数组名即可) 赋值给DMA_Memory0BaseAddr。
4) DMA_DIR:
传输方向选择,可选外设到存储器、存储器到外设以及存储器到存储器。它设定DMA_SxCR 寄存器的DIR[1:0] 位的值。ADC 采集显然使用外设到存储器模式。
5) DMA_BufferSize:
设定待传输数据数目,初始化设定DMA_SxNDTR 寄存器的值。这里ADC是采集4 个通道数据,所以待传输数目也就是4。
6) DMA_PeripheralInc:
如果配置为DMA_PeripheralInc_Enable,使能外设地址自动递增功能,它设定DMA_SxCR 寄存器的PINC 位的值;一般外设都是只有一个数据寄存器,所以一般不会使能该位。ADC3 的数据寄存器地址是固定并且只有一个所以不使能外设地址递增。
7) DMA_MemoryInc:
如果配置为DMA_MemoryInc_Enable,使能存储器地址自动递增功能,它设定DMA_SxCR 寄存器的MINC 位的值;我们自定义的存储区一般都是存放多个数据的,所以使能存储器地址自动递增功能。我们之前已经定义了一个包含4 个元素的数字用来存放数据,使能存储区地址递增功能,自动把每个通道数据存放到对应数组元素内。
8) DMA_PeripheralDataSize:
外设数据宽度,可选字节(8 位)、半字(16 位) 和字(32 位),它设定DMA_SxCR 寄存器的PSIZE[1:0] 位的值。ADC 数据寄存器只有低16 位数据有效,使用半字数据宽度。
9) DMA_MemoryDataSize:
存储器数据宽度,可选字节(8 位)、半字(16 位) 和字(32 位),它设定DMA_SxCR 寄存器的MSIZE[1:0] 位的值。保存ADC 转换数据也要使用半字数据宽度,这跟我们定义的数组是相对应的。
10) DMA_Mode:
DMA 传输模式选择,可选一次传输或者循环传输,它设定DMA_SxCR 寄存器的CIRC 位的值。我们希望ADC 采集是持续循环进行的,所以使用循环传输模式。
11) DMA_Priority:
软件设置数据流的优先级,有4 个可选优先级分别为非常高、高、中和低,它设定DMA_SxCR 寄存器的PL[1:0] 位的值。DMA 优先级只有在多个DMA 数据流同时使用时才有意义,这里我们设置为非常高优先级就可以了。
12) DMA_FIFOMode:
FIFO 模式使能,如果设置为DMA_FIFOMode_Enable 表示使能FIFO 模式功能;它设定DMA_SxFCR 寄存器的DMDIS 位。ADC 采集传输使用直接传输模式即可,不需要使用FIFO 模式。
13) DMA_FIFOThreshold:
FIFO 阈值选择,可选4 种状态分别为FIFO 容量的1/4、1/2、3/4 和满;它设定DMA_SxFCR 寄存器的FTH[1:0] 位;DMA_FIFOMode 设置为DMA_FIFOMode_Disable,那DMA_FIFOThreshold 值无效。ADC 采集传输不使用FIFO 模式,设置改值无效。
14) DMA_MemoryBurst:
存储器突发模式选择,可选单次模式、4 节拍的增量突发模式、8 节拍的增量突发模式或16 节拍的增量突发模式,它设定DMA_SxCR 寄存器的MBURST[1:0] 位的值。ADC 采集传输是直接模式,要求使用单次模式。
15) DMA_PeripheralBurst:
外设突发模式选择,可选单次模式、4 节拍的增量突发模式、8 节拍的增量突发模式或16 节拍的增量突发模式,它设定DMA_SxCR 寄存器的PBURST[1:0] 位的值。
ADC 采集传输是直接模式,要求使用单次模式。