APM32的DMA配置和应用
#申请原创#@21小跑堂APM32的DMA配置和应用
## 01 DMA简介DMA(Direct Memory Access),直接内存存取,是一种AMBA 先进高性能总线(AHB)模块,是独立于CPU的一种数据高速传输的方式。
### DMA的功能DMA 可以让数据的传输工作在后台进行,能够在没有CPU干预的情况下快速实现数据的转移。但并非不需要占用系统总线,只是可以在不显著影响系统性能的情况下进行大量数据的传输。
> DMA 主要用于实现不同外设模块的集中数据缓冲和存储
### DMA的工作原理DMA从本质上看,是从“地址”到“地址”的方式来实现数据传输的。当设定好"源地址"、"目标地址"和"需要传输的数据量"后,DMA控制器就会启动传输,直至剩余传输数据量到0为止(非循环模式下)。DMA主要有四种数据传输类型:
1.外设到存储器
2.存储器到外设
3.存储器到存储器
4.外设到外设
> 最后一个“外设到外设”的传输类型,是指从外设的数据存储器到外设的数据存储器,外设的数据存储器实质上也是一种数据存储单元。后面章节会具体讲述其中的原理
## 02 APM32中的DMA以下内容以F4xx系列为例。
### DMA总线架构由下面的总线架构图中可以看到,一个DMA控制器通过一个专用的AHB主端口连接到AHB总线矩阵。而总线矩阵则采用主/从结构。
整个架构中只有CPU和DMA充当主机,其他所有连接的部件都只作为从机访问。
在总线矩阵内,只要两个独立的AHB主机针对不同的AHB总线矩阵从机端口提交两个并发AHB传输,就不存在总线矩阵仲裁。例如,当CPU从闪存读取指令,而DMA从另一个存储器读取数据时,它们不会以任何方式相互限制。换句话说,就是仲裁只在两个主机需要访问相同的从机存储器或外设时发生。
从以上的描述能得出两个结论:
1.DMA和CPU如果同时访问同一外设或存储器时,会发生总线仲裁
2.DMA传输数据时会占用总线带宽
> 总线带宽:定义为总线可以在固定时间内传输的数据量,通常以每秒传输多少数据量来表示。它由时钟速度、总线宽度和总线管理开销决定
注意
> 1.图中有一个64KB的CCM data RAM只能由D-Bus总线访问,而DMA是无权访问的,后续应用时需要留意。
> 2.DMA1 控制器 AHB 外设端口与 DMA2 控制器的情况不同,不连接到总线矩阵,因此,仅 DMA2 数据流能够执行存储器到存储器的传输
### DMA的结构
DMA的结构如下,并有几个大特点。
1.双 AHB 主总线架构,一个用于存储器访问,另一个用于外设访问
2.每个 DMA 控制器有 8 个数据流,每个数据流有多达 8 个通道。每次数据流可选择的通道数多达8个,可由软件配置,允许几个外设启动 DMA请求
3.每个数据流有单独的四级 32 位先进先出存储器缓冲区 (FIFO),可用于 FIFO 模式或直接模式
4.DMA 数据流请求之间的优先级可用软件编程
5.5 个事件标志(DMA 半传输、DMA 传输完成、DMA 传输错误、DMA FIFO 错误、直接模式错误),进行逻辑或运算,从而产生每个数据流的单个中断请求
要传输的数据项的数目可以由 DMA 控制器或外设管理:
> 1.DMA 流控制器:要传输的数据项的数目是 1 到 65535,可用软件编程
> 2.外设流控制器:要传输的数据项的数目未知并由源或目标外设控制,这些外设通过硬件发出传输结束的信号
### DMA支持的内存访问区域
源传输和目标传输支持在整个 4 GB 区域(地址在 0x0000 0000 和 0xFFFF FFFF 之间)内寻址外设和存储器。
### DMA事务
DMA 事务由给定数目的数据传输序列组成。要传输的数据项的数目及其宽度(8 位、16 位 或 32 位)可用软件编程。
每个 DMA 传输包含三项操作:
1.通过 DMA_SxPAR 或 DMA_SxM0AR 寄存器寻址,从外设数据寄存器或存储器单元中加载数据
2.通过 DMA_SxPAR 或 DMA_SxM0AR 寄存器寻址,将加载的数据存储到外设数据寄存器或存储器单元
3.DMA_SxNDTR 计数器在数据存储结束后递减,该计数器中包含仍需执行的事务数
### DMA进行数据传输的必要条件
1.剩余传输数据量大于0
2.DMA通道传输使能
3.通道上DMA数据传输有事件请求
> 存储器对存储器的置位,就相当于相应通道的事件有效。对应通道的事件有效和存储器对存储器的置位,就是传输的触发位。每次传输的事件置位一次,完成一次传输。如果是由外设引发的DMA传输,则传输完成后,相应传输事件会置为无效,而存储器对存储器的传输,则一次传输完成后,相应事件一直有效,直至完成设定的传输量
### 外设通道选择
每个数据流都与一个 DMA 请求相关联,这个请求可以从 8 个可能的通道请求中选出。
而来自外设的 8 个请求独立连接到每个通道,具体连接方式每个系列都不一样,我们需要选择将某个外设作为DMA数据流的源地址或目标地址。下表是F4xx系列的通道请求映射。
> 每个外设请求都占用一个数据流通道,如数据流0的通道0占用了数据流通道,那么其他通道(1-7)则处于不可用状态。
> 相同外设请求可以占用不同数据流通道,比如SDIO可以同时占用DMA2的数据流3和数据流6(一个作TX,一个作RX)
#### DMA1请求映射
#### DMA2请求映射
> 注意:只有DMA2可以使用“M2M”模式
### 仲裁器
一个DMA 控制器对应8 个数据流,数据流包含要传输数据的源地址、目标地址、数据等信息。如果同一个DMA同时有多个外设请求时,则需要分配优先级。
优先级管理分为两个阶段:
1.软件 每个数据流优先级都可以在 DMA_SxCR 寄存器中配置。分为四个级别:
- 非常高优先级
- 高优先级
- 中优先级
- 低优先级
2.硬件 如果两个请求具有相同的软件优先级,则编号低的数据流优先于编号高的数据流。例如,数据流 2 的优先级高于数据流 4
### DMA的控制和传输模式
根据控制和传输模式的不同,DMA存储多种组合的配置,F4xx的DMA工作配置组合如下所示。
#### 控制模式
##### FIFO模式
1.FIFO特点
每个数据流都有一个独立的 4 字 FIFO,阈值级别可由软件配置为 1/4、1/2、3/4 或Full。
2.FIFO的结构
FIFO 的结构随源与目标数据宽度的不同而不同。
3.FIFO的数据传输
FIFO 用于在源数据传输到目标地址之前临时存放这些数据。可以设置阈值,如果数据存储量达到阈值级别时,FIFO 内容将传输到目标中。
> 1.FIFO模式对于源地址和目标地址数据宽度不同的情况非常有用,比如源数据是字节数据,而目标地址要求输出字宽度的数据,即在实现数据传输时同时把原来4 个8 位字节的数据拼凑成一个32 位字数据。此时可使用FIFO功能先把数据缓存起来,分别根据需要输出数据
> 2.FIFO模式也常使用于突发(burst) 传输
> 3.当在直接模式(禁止 FIFO)下将 DMA 配置为以存储器到外设模式传输数据时,DMA 会将一 个数据从存储器预加载到内部 FIFO,从而确保一旦外设触发 DMA 请求时则立即传输数据。
4.FIFO 阈值与突发配置
使用 FIFO 阈值和存储器突发大小配置时需要注意,FIFO 阈值所指向的内容必须与整数个存储器突发传输完全匹配。否则,当使能数据流时会报 FIFO 错误,然后DMA将自动禁止数据流传输。
以下是FIFO阈值配置表。
##### 循环模式
循环模式可用于处理循环缓冲区或连续数据流,比如ADC的多通道连续扫描。当Enable循环模式时,要传输的数据项的数目,在数据流配置阶段自动用设置的初始值进行加载,并持续响应 DMA 的请求。
注意在循环模式下,如果同时为存储器配置了突发模式,那么必须遵循下列规则:
DMA_SxNDTR = ((Mburst 节拍 ) × (Msize)/(Psize)) 的倍数
其中,
- DMA_SxNDTR = AHB 外设端口上要传输的数据项的数目
- (Mburst 节拍 ) = 4、8 或 16(取决于 DMA_SxCR 寄存器中的 MBURST 位)
- ((Msize)/(Psize)) = 1、2、4、1/2 或 1/4(Msize 和 Psize 表示 DMA_SxCR 寄存器中的 MSIZE 和 PSIZE 位。它们与字节相关)
例如:
Mburst 节拍 = 8 (INCR8),MSIZE =“00”(字节)和 PSIZE =“01”(半字),则可以计数得到:DMA_SxNDTR 必须是 (8 × 1/2 = 4) 的倍数,否则 DMA 行为和数据完整性得不到保证。
> NDTR 还必须是外设突发大小与外设数据大小乘积的倍数,否则会导致错误的 DMA 行为
##### 直接模式
直接模式在每个外设请求时,都会立即启动对存储器传输的单次传输。同时要求源地址和目标地址的数据宽度必须一致,所以只有PSIZE 控制,而MSIZE 值会被忽略。默认情况下,DMA 工作在直接模式,不使能FIFO 阈值级别。
> 直接模式不能用于存储器到存储器传输。
##### 双缓冲模式
在此模式下,每次DMA事务结束时,DMA 控制器都从当前目标存储器转换到另一个目标存储器。 这样,当软件在处理当前存储器区域的同时,DMA 传输还可以填充或使用第二个存储器区域。以加快传输速度。
> 1.使能双缓冲区模式时,自动使能循环模式,所以也不适用于M2M模式
> 2.双缓冲模式在I2S解码或传输PDM信号时经常使用,使用该模式去播放音频时可以减少不流畅现象
#### 传输模式
##### 外设到存储器
1.FIFO模式
每次产生外设请求,数据流都会启动数据源到 FIFO 的传输。当数据量达到 FIFO 所设定的阈值级别时,FIFO 的内容将移出并存储到目标中。
2.直接模式
每完成一次从外设到 FIFO 的数据传输后,相应的数据立即就会移出并存储到目标中。
> 不使用 FIFO 的阈值级别控制功能
两种模式下,如果 DMA_SxNDTR 寄存器计数到零,或外设请求传输终止(在使用外设流控制器的情况下)或 DMA_SxCR 寄存器中的 EN 位由软件清零,传输都会立即停止。
##### 存储器到外设
1.FIFO模式
数据流会立即启动传输,从存储器源完全填充到FIFO中。每次发生外设请求,FIFO 的内容都会移出并存储到目标中。
> 当 FIFO 的级别小于或等于预定 义的阈值级别时,将使用存储器中的数据完全重载 FIFO
2.直接模式
该模式下,一旦使能了数据流,DMA便会预装载第一个数据,将其传输到内部 FIFO。这时当发生外设请求数据传输,DMA便会将预装载的值传输到配置的目标。紧接着,DMA会使用要传输的下一个数据再次重载内部空 FIFO。预装载的数据大小为 DMA_SxCR 寄存器中 PSIZE 位字段的值。
两种模式下,如果 DMA_SxNDTR 寄存器计数到零,或外设请求传输终止(在使用外设流控制器的情况下)或 DMA_SxCR 寄存器中的 EN 位由软件清零,传输都会立即停止。
##### 存储器到存储器
默认使用FIFO模式,当使能数据流时,数据流会立即开始填充 FIFO,直至达到阈值级别。达到阈值级**,FIFO 的内容便会移出,并存储到目标中。
如果 DMA_SxNDTR 寄存器计数到零,或外设请求传输终止(在使用外设流控制器的情况下)或 DMA_SxCR 寄存器中的 EN 位由软件清零,传输都会立即停止。
> 1.该模式下,不允许使用循环模式和直接模式,所以也不能使用双缓冲区模式(自动启用循环模式)
> 2.只有 DMA2 控制器能够执行存储器到存储器的传输
##### “外设到外设”
参考手册中没有提到这个模式,但DMA的本质是“地址到地址”。 如果源地址是外设,目标地址是内存,那么传输模式就是外设到内存; 如果源地址是外设,目标地址也是外设,那传输模式就是外设到外设。
例如用DMA把ADC数据寄存器的值直接传送到SPI数据寄存器,即属于“外设”到“外设”的传输模式。
### 传输类型
#### 单次
当 AHB 外设端口被配置为单次传输时,根据 DMA_SxCR 寄存器 PSIZE 位的值,每个 DMA 请求产生一次字节、半字或字的数据传输。
> 单次传输时必须通过AHB 的总线仲裁多次控制才传输完成
#### 突发
突发传输就是用非常短时间结合非常高数据信号率传输数据,相对正常传输速度,突发传输就是在传输阶段把速度瞬间提高,实现高速传输,在数据传输完成后恢复正常速度。
DMA 控制器可以产生单次传输或 4 个、8 个和 16 个节拍的增量突发传输。当 AHB 外设端口被配置为突发传输时,根据 DMA_SxCR 寄存器 PBURST 和PSIZE 位的值,每个 DMA 请求相应地生成 4 个、8 个或 16 个节拍的字节、半字或字的传输。
突发模式下,当DMA请求总线成功后会连续传送数据,而不给CPU使用总线的机会,直到数据传送完毕。比如设置了4个节拍的突发传输,而传输宽度位为8 bit,则一个DMA请求会连续传送4个字节,是单次传输的4倍,大提高了传输的速度。
突发传输需要结合FIFO 使用,具体配置可看FIFO阈值与突发配置章节。
> 1.在直接模式下,数据流只能生成单次传输,而 MBURST 和 PBURST 位由硬件强制配置
>
> 2.突发传输过程会一直占用AHB 总线,保证每个数据项在传输过程不被分割
### 中断类型
对于每个 DMA 数据流,可在发生以下事件时产生中断:
- 达到半传输
DMA 数据传输达到一半时,HTIF 标志位会被置位。
- 传输完成
DMA 数据传输完成时,TCIF 标志位会被置位。
- 传输错误
DMA 访问总线发生错误或者在双缓冲模式下试图访问“受限”存储器地址寄存器时,TEIF 标志位被置位。
- FIFO 错误(上溢、下溢或 FIFO 级别错误)
发生FIFO 下溢或者上溢时FEIF 标志位被置位。
- 直接模式错误
在外设到存储器的直接模式下,因为存储器总线没得到授权,使得先前数据没有完成被传输到存储器空间上,此时DMEIF 标志位被置1。
> 在将使能控制位置‘1’前,应将相应的事件标志清零,否则会立即产生中断
## 03 DMA的配置和应用
### DCMI + LCD DMA传输应用
这里应用了“外设”到“存储器”的方式,把DCMI->DR寄存器的数据直接传送到挂在FSMC总线的LCD数据地址上。开启循环模式和FIFO,并将外设设为单次传输,而存储器设为8个节拍的突发传输。在字节宽度的设置上,DCMI的数据寄存器宽度为32bit,而LCD端的RGB565,宽度为16bit。使用了如下图红框所示的配置。
整个传输过程是这样的,当DCMI的数据寄存器收到32bit数据才会触发一次DMA请求,然后存入DMA的FIFO中,当存满到4字节的FIFO阈值时,突发传输1次8个节拍的半字数据到LCD中显示。
#define DEBUG_DCMI_DR_BASE (uint32_t)&DCI->DATA
#define DEBUG_DCMI_DMA_CLK RCM_AHB1_PERIPH_DMA2
#define DEBUG_DCMI_DMA_CHANNEL DMA_CHANNEL_1
#define DEBUG_DCMI_DMA_STREAM DMA2_Stream1
#define FSMC_LCD_DATA_SIZE 1
#define FSMC_LCD_ADDRESS ((uint32_t) 0x68000002)
void OV2640_DMAConfig(void)
{
DMA_Config_TdmaConfigStruct;
/* 配置DMA从DCMI中获取数据*/
/* 使能DMA*/
RCM_EnableAHB1PeriphClock(DEBUG_DCMI_DMA_CLK);
DMA_Reset(DEBUG_DCMI_DMA_STREAM);
while (DMA_ReadCmdStatus(DEBUG_DCMI_DMA_STREAM) != DISABLE)
{
}
dmaConfigStruct.channel = DEBUG_DCMI_DMA_CHANNEL;
dmaConfigStruct.peripheralBaseAddr = DEBUG_DCMI_DR_BASE;
dmaConfigStruct.memoryBaseAddr = FSMC_LCD_ADDRESS;
dmaConfigStruct.dir = DMA_DIR_PERIPHERALTOMEMORY;
dmaConfigStruct.bufferSize = FSMC_LCD_DATA_SIZE;
dmaConfigStruct.peripheralInc = DMA_PERIPHERAL_INC_DISABLE;
dmaConfigStruct.memoryInc = DMA_MEMORY_INC_DISABLE;
dmaConfigStruct.peripheralDataSize = DMA_PERIPHERAL_DATA_SIZE_WORD;
dmaConfigStruct.memoryDataSize = DMA_MEMORY_DATA_SIZE_HALFWORD;
dmaConfigStruct.loopMode = DMA_MODE_CIRCULAR;
dmaConfigStruct.priority = DMA_PRIORITY_HIGH;
dmaConfigStruct.fifoMode = DMA_FIFOMODE_ENABLE;
dmaConfigStruct.fifoThreshold = DMA_FIFOTHRESHOLD_FULL;
dmaConfigStruct.memoryBurst = DMA_MEMORYBURST_INC8;
dmaConfigStruct.peripheralBurst = DMA_PERIPHERALBURST_SINGLE;
/* DMA初始化 */
DMA_Config(DEBUG_DCMI_DMA_STREAM, &dmaConfigStruct);
}
### ADC DMA传输应用这里应用了“外设”到“存储器”的方式,因为ADC数据寄存器地址不会随通道不同而变化,所以使能了存储器增量模式,禁止外设增量。而控制模式使用“循环模式” + “单次传输”的方式。
/** 定义ADC 相关信息*/
#define DEBUG_ADC_BASE ADC1_BASE
#define DEBUG_ADC_DR_ADDR ((uint32_t)DEBUG_ADC_BASE + 0x4C)
/** 定义数据存储数组*/
#define ADC_CONV_CH_SIZE 3
uint16_t adcConvertedValue;
/** 定义ADC DMA相关信息*/
#define DEBUG_ADC_DMA_CLK RCM_AHB1_PERIPH_DMA2
#define DEBUG_ADC_DMA_CHANNEL DMA_CHANNEL_0
#define DEBUG_ADC_DMA_STREAM DMA2_Stream0
/** 初始化ADC相应的DMA*/
void ADC_DMAConfig(void)
{
DMA_Config_T dmaConfigStruct;
RCM_EnableAHB1PeriphClock(DEBUG_ADC_DMA_CLK);
/** ADC 数据寄存器地址*/
dmaConfigStruct.peripheralBaseAddr = DEBUG_ADC_DR_ADDR;
dmaConfigStruct.memoryBaseAddr = (uint32_t)&adcConvertedValue;
dmaConfigStruct.dir = DMA_DIR_PERIPHERALTOMEMORY;
/** buffer size 和需扫描的ADC通道数一致*/
dmaConfigStruct.bufferSize = ADC_CONV_CH_SIZE;
dmaConfigStruct.peripheralInc = DMA_PERIPHERAL_INC_DISABLE;
/** 存储器地址递增*/
dmaConfigStruct.memoryInc = DMA_MEMORY_INC_ENABLE;
/** ADC DR数据大小为半字,即两个字节*/
dmaConfigStruct.peripheralDataSize = DMA_PERIPHERAL_DATA_SIZE_HALFWORD;
/** 存储器数据大小为半字*/
dmaConfigStruct.memoryDataSize = DMA_MEMORY_DATA_SIZE_HALFWORD;
dmaConfigStruct.loopMode = DMA_MODE_CIRCULAR;
dmaConfigStruct.priority = DMA_PRIORITY_HIGH;
dmaConfigStruct.fifoMode = DMA_FIFOMODE_DISABLE;
dmaConfigStruct.fifoThreshold = DMA_FIFOTHRESHOLD_HALFFULL;
dmaConfigStruct.memoryBurst = DMA_MEMORYBURST_SINGLE;
dmaConfigStruct.peripheralBurst = DMA_PERIPHERALBURST_SINGLE;
dmaConfigStruct.channel = DEBUG_ADC_DMA_CHANNEL;
DMA_Config(DEBUG_ADC_DMA_STREAM, &dmaConfigStruct);
}
### 串口DMA传输应用这里应用了“存储器”到“外设”的方式,因为USART数据寄存器地址是固定的,所以使能了存储器增量模式,禁止外设增量。控制模式使用“循环模式” + “单次传输”的方式。#define TX_BUFFER_SIZE 100
#define DEBUG_USART_DR_BASE (USART1_BASE + 0x04)
#define DEBUG_USART_DMA_CLK RCM_AHB1_PERIPH_DMA2
#define DEBUG_USART_DMA_CHANNEL DMA_CHANNEL_4
#define DEBUG_USART_DMA_STREAM DMA2_Stream7
uint8_t txBuffer;
void USART_DMAConfig(void)
{
DMA_Config_T dmaConfigStruct;
/* 开启DMA时钟*/
RCM_EnableAHB1PeriphClock(DEBUG_USART_DMA_CLK, ENABLE);
/* 复位初始化DMA数据流 */
DMA_Reset(DEBUG_DCMI_DMA_STREAM);
/* 确保DMA数据流复位完成 */
while (DMA_ReadCmdStatus(DEBUG_DCMI_DMA_STREAM) != DISABLE)
{
}
/* usart1 tx对应DMA2,通道4,数据流7 */
dmaConfigStruct.channel = DEBUG_USART_DMA_CHANNEL;
/* 设置DMA源:串口数据寄存器地址*/
dmaConfigStruct.peripheralBaseAddr = DEBUG_USART_DR_BASE;
/* 存储器地址*/
dmaConfigStruct.memoryBaseAddr = (uint32_t)txBuffer;
/* 从内存到外设*/
dmaConfigStruct.dir = DMA_DIR_MEMORYTOPERIPHERAL;
/* 传输大小*/
dmaConfigStruct.bufferSize = TX_BUFFER_SIZE;
dmaConfigStruct.peripheralInc = DMA_PERIPHERAL_INC_DISABLE;
/* 内存地址自增*/
dmaConfigStruct.memoryInc = DMA_MEMORY_INC_ENABLE;
/* 外设数据单位*/
dmaConfigStruct.peripheralDataSize = DMA_PERIPHERAL_DATA_SIZE_BYTE;
/* 内存数据单位 8bit*/
dmaConfigStruct.memoryDataSize = DMA_MEMORY_DATA_SIZE_BYTE;
/* 开启循环循环*/
dmaConfigStruct.loopMode = DMA_MODE_CIRCULAR;
dmaConfigStruct.priority = DMA_PRIORITY_HIGH;
/* 禁用FIFO*/
dmaConfigStruct.fifoMode = DMA_FIFOMODE_DISABLE;
dmaConfigStruct.fifoThreshold = DMA_FIFOTHRESHOLD_FULL;
/* 存储器单次传输*/
dmaConfigStruct.memoryBurst = DMA_MEMORYBURST_SINGLE;
/* 外设单次传输*/
dmaConfigStruct.peripheralBurst = DMA_PERIPHERALBURST_SINGLE;
DMA_Config(DEBUG_USART_DMA_STREAM, &dmaConfigStruct);
}
页:
[1]