打印
[APM32F4]

APM32的DMA配置和应用

[复制链接]
814|0
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
#申请原创#@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[1:0] 位的值,每个 DMA 请求产生一次字节、半字或字的数据传输。

> 单次传输时必须通过AHB 的总线仲裁多次控制才传输完成

#### 突发
突发传输就是用非常短时间结合非常高数据信号率传输数据,相对正常传输速度,突发传输就是在传输阶段把速度瞬间提高,实现高速传输,在数据传输完成后恢复正常速度。

DMA 控制器可以产生单次传输或 4 个、8 个和 16 个节拍的增量突发传输。当 AHB 外设端口被配置为突发传输时,根据 DMA_SxCR 寄存器 PBURST[1:0] 和PSIZE[1:0] 位的值,每个 DMA 请求相应地生成 4 个、8 个或 16 个节拍的字节、半字或字的传输。

突发模式下,当DMA请求总线成功后会连续传送数据,而不给CPU使用总线的机会,直到数据传送完毕。比如设置了4个节拍的突发传输,而传输宽度位为8 bit,则一个DMA请求会连续传送4个字节,是单次传输的4倍,大提高了传输的速度。

突发传输需要结合FIFO 使用,具体配置可看FIFO阈值与突发配置章节。

> 1.在直接模式下,数据流只能生成单次传输,而 MBURST[1:0] 和 PBURST[1:0] 位由硬件强制配置
>
> 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_T  dmaConfigStruct;

    /* 配置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_CONV_CH_SIZE];

/** 定义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[TX_BUFFER_SIZE];


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);
}




使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

19

主题

31

帖子

3

粉丝