简介

DMA(Direct Memory Access,直接存储区访问)为实现数据高速在外设寄存器与存储器之间或者存储器与存储器之间传输提供了高效的方法。之所以称之为高效,是因为 DMA 传输实现高速数据移动过程无需任何 CPU 操作控制。从硬件层次上来说, DMA 控制器是独立于 Cortex-M4 内核的,有点类似 GPIO、 USART 外设一般,只是 DMA 的功能是可以快速移动内存数据。

STM32F4xx 系列的 DMA 功能齐全,工作模式众多,适合不同编程环境要求。STM32F4xx 系列的 DMA 支持外设到存储器传输、存储器到外设传输和存储器到存储器传输三种传输模式。这里的外设一般指外设的数据寄存器,比如 ADC、 SPI、 I2C、 DCMI 等等外设的数据寄存器,存储器一般是指片内 SRAM、外部存储器、片内 Flash 等等。

外设到存储器传输就是把外设数据寄存器内容转移到指定的内存空间。比如进行 ADC采集时我们可以利用 DMA 传输把 AD 转换数据转移到我们定义的存储区中,这样对于多通道采集、采样频率高、连续输出数据的 AD 采集是非常高效的处理方法。

存储区到外设传输就是把特定存储区内容转移至外设的数据寄存器中,这种多用于外设的发送通信。

存储器到存储器传输就是把一个指定的存储区内容拷贝到另一个存储区空间。功能类似于 C 语言内存拷贝函数 memcpy,利用 DMA 传输可以达到更高的传输效率,特别是DMA 传输是不占用 CPU 的,可以节省很多 CPU 资源。

DMA功能框图

STM32F4xx 系列的 DMA 可以实现外设寄存器与存储器之间或者存储器与存储器之间传输三种模式,这要得益于 DMA 控制器是采样 AHB 主总线的,可以控制 AHB 总线矩阵来启动 AHB 事务。

STM32-DMA直接存储器访问

1、外设通道选择

STM32F4xx 系列资源丰富,具有两个 DMA 控制器,同时外设繁多,为实现正常传输,DMA 需要通道选择控制。每个 DMA 控制器具有 8 个数据流,每个数据流对应 8 个外设请求。在实现 DMA 传输之前, DMA 控制器会通过 DMA 数据流 x 配置寄存器 DMA_SxCR(x为 0~7,对应 8 个 DMA 数据流)的 CHSEL[2:0]位选择对应的通道作为该数据流的目标外设。外设通道选择要解决的主要问题是决定哪一个外设作为该数据流的源地址或者目标地址。

DMA 请求映射情况参考表 21-1 和表 21-2。

DMA1请求映射

STM32-DMA直接存储器访问
DMA2请求映射

STM32-DMA直接存储器访问

每个外设请求都占用一个数据流通道,相同外设请求可以占用不同数据流通道。比如SPI3_RX 请求,即 SPI3 数据接收请求,占用 DMA1 的数据流 0 的通道 0,因此当我们使用该请求时,我们需要在把 DMA_S0CR 寄存器的 CHSEL[2:0]设置为“000”,此时相同数据流的其他通道不被选择,处于不可用状态,比如此时不能使用数据流 0 的通道 1 即I2C1_RX 请求等等。

2、仲裁器

一个 DMA 控制器对应 8 个数据流,数据流包含要传输数据的源地址、目标地址、数据等等信息。如果我们需要同时使用同一个 DMA 控制器(DMA1 或 DMA2)多个外设请求时,那必然需要同时使用多个数据流,那究竟哪一个数据流具有优先传输的权利呢?这就需要仲裁器来管理判断了。

仲裁器管理数据流方法分为两个阶段。第一阶段属于软件阶段,我们在配置数据流时可以通过寄存器设定它的优先级别,具体配置 DMA_SxCR 寄存器 PL[1:0]位,可以设置为非常高、高、中和低四个级别。第二阶段属于硬件阶段,如果两个或以上数据流软件设置优先级一样,则他们优先级取决于数据流编号,编号越低越具有优先权,比如数据流 2 优先级高于数据流 3。

3、FIFO

每个数据流都独立拥有四级 32 位 FIFO(先进先出存储器缓冲区)。 DMA 传输具有 FIFO模式和直接模式。

直接模式在每个外设请求都立即启动对存储器传输。在直接模式下,如果 DMA 配置为存储器到外设传输那 DMA 会将一个数据存放在 FIFO 内,如果外设启动 DMA 传输请求就可以马上将数据传输过去。

FIFO 用于在源数据传输到目标地址之前临时存放这些数据。可以通过 DMA 数据流xFIFO 控制寄存器 DMA_SxFCR 的 FTH[1:0]位来控制 FIFO 的阈值,分别为 1/4、 1/2、 3/4和满。如果数据存储量达到阈值级别时, FIFO 内容将传输到目标中。

FIFO 对于要求源地址和目标地址数据宽度不同时非常有用,比如源数据是源源不断的字节数据,而目标地址要求输出字宽度的数据,即在实现数据传输时同时把原来 4 个 8 位字节的数据拼凑成一个 32 位字数据。此时使用 FIFO 功能先把数据缓存起来,分别根据需要输出数据。

FIFO 另外一个作用使用于突发(burst)传输。

4、存储器端口、外设端口

DMA 控制器实现双 AHB 主接口,更好利用总线矩阵和并行传输。 DMA 控制器通过存储器端口和外设端口与存储器和外设进行数据传输,关系见图 21-2。 DMA 控制器的功能是快速转移内存数据,需要一个连接至源数据地址的端口和一个连接至目标地址的端口。

DMA2(DMA 控制器 2)的存储器端口和外设端口都是连接到 AHB 总线矩阵,可以使用AHB 总线矩阵功能。 DMA2 存储器和外设端口可以访问相关的内存地址,包括有内部Flash、内部 SRAM、 AHB1 外设、 AHB2 外设、 APB2 外设和外部存储器空间。

DMA1 的存储区端口相比 DMA2 的要减少 AHB2 外设的访问权,同时 DMA1 外设端口是没有连接至总线矩阵的,只有连接到 APB1 外设,所以 DMA1 不能实现存储器到存储器传输。

STM32-DMA直接存储器访问

5、编程端口

AHB 从器件编程端口是连接至 AHB2 外设的。 AHB2 外设在使用 DMA 传输时需要相关控制信号。

DMA数据配置

STM32-DMA直接存储器访问

1、DMA传输模式

DMA2 支持全部三种传输模式,而 DMA1 只有外设到存储器和存储器到外设两种模式。模式选择可以通过 DMA_SxCR 寄存器的 DIR[1:0]位控制,进而将 DMA_SxCR 寄存器的EN 位置 1 就可以使能 DMA 传输。

在 DMA_SxCR 寄存器的 PSIZE[1:0]和 MSIZE[1:0]位分别指定外设和存储器数据宽度大小,可以指定为字节(8 位)、半字(16 位)和字(32 位),我们可以根据实际情况设置。直接模式要求外设和存储器数据宽度大小一样,实际上在这种模式下 DMA 数据流直接使用PSIZE, MSIZE 不被使用。

2、源地址和目标地址

DMA 数据流 x 外设地址 DMA_SxPAR(x 为 0~7)寄存器用来指定外设地址,它是一个32 位数据有效寄存器。 DMA 数据流 x 存储器 0 地址 DMA_SxM0AR(x 为 0~7) 寄存器和DMA 数据流 x 存储器 1 地址 DMA_SxM1AR(x 为 0~7) 寄存器用来存放存储器地址,其中DMA_SxM1AR 只用于双缓冲模式, DMA_SxM0AR 和 DMA_SxM1AR 都是 32 位数据有效的。

当选择外设到存储器模式时,即设置 DMA_SxCR 寄存器的 DIR[1:0] 位为“00”,DMA_SxPAR 寄存器为外设地址,也是传输的源地址, DMA_SxM0AR 寄存器为存储器地址,也是传输的目标地址。对于存储器到存储器传输模式,即设置 DIR[1:0] 位为“10”时,采用与外设到存储器模式相同配置。而对于存储器到外设,即设置 DIR[1:0]位为“01”时,DMA_SxM0AR 寄存器作为为源地址, MA_SxPAR 寄存器作为目标地址。

3、流控制器

流控制器主要涉及到一个控制 DMA 传输停止问题。 DMA 传输在 DMA_SxCR 寄存器的 EN 位被置 1 后就进入准备传输状态,如果有外设请求 DMA 传输就可以进行数据传输。很多情况下,我们明确知道传输数据的数目,比如要传 1000 个或者 2000 个数据,这样我们就可以在传输之前设置DMA_SxNDTR 寄存器为要传输数目值, DMA 控制器在传输完这么多数目数据后就可以控制 DMA 停止传输。

DMA 数据流 x 数据项数 DMA_SxNDTR(x 为 0~7)寄存器用来记录当前仍需要传输数目,它是一个 16 位数据有效寄存器,即最大值为 65535,这个值在程序设计是非常有用也是需要注意的地方。我们在编程时一般都会明确指定一个传输数量,在完成一次数目传输后DMA_SxNDTR 计数值就会自减,当达到零时就说明传输完成。如果某些情况下在传输之前我们无法确定数据的数目,那 DMA 就无法自动控制传输停止了,此时需要外设通过硬件通信向 DMA 控制器发送停止传输信号。这里有一个大前提就是外设必须是可以发出这个停止传输信号,只有 SDIO 才有这个功能,其他外设不具备此功能。

4、循环模式

循环模式相对应于一次模式。一次模式就是传输一次就停止传输,下一次传输需要手动控制,而循环模式在传输一次后会自动按照相同配置重新传输,周而复始直至被控制停止或传输发生错误。

通过 DMA_SxCR 寄存器的 CIRC 位可以使能循环模式。

5、传输类型

DMA 传输类型有单次(Single)传输和突发(Burst)传输。突发传输就是用非常短时间结合非常高数据信号率传输数据,相对正常传输速度,突发传输就是在传输阶段把速度瞬间提高,实现高速传输,在数据传输完成后恢复正常速度,有点类似达到数据块“秒传”效果。为达到这个效果突发传输过程要占用 AHB 总线,保证要求每个数据项在传输过程不被分割,这样一次性把数据全部传输完才释放 AHB 总线;而单次传输时必须通过 AHB 的总线仲裁多次控制才传输完成。

单次和突发传输数据使用具体情况参考表 21-4。其中 PBURST[1:0]和 MBURST[1:0]位是位于 DMA_SxCR 寄存器中的,用于分别设置外设和存储器不同节拍数的突发传输,对应为单次传输、 4 个节拍增量传输、 8 个节拍增量传输和 16 个节拍增量传输。 PINC 位和MINC 位是寄存器 DMA_SxCR 寄存器的第 9 和第 10 位,如果位被置 1 则在每次数据传输后数据地址指针自动递增,其增量由 PSIZE 和 MSIZE 值决定,比如,设置 PSIZE 为半字大小,那么下一次传输地址将是前一次地址递增 2。

STM32-DMA直接存储器访问

突发传输与 FIFO 密切相关,突发传输需要结合 FIFO 使用,具体要求 FIFO 阈值一定要是内存突发传输数据量的整数倍。 FIFO 阈值选择和存储器突发大小必须配合使用,具体参考表 21-5。

STM32-DMA直接存储器访问

6、直接模式

默认情况下, DMA 工作在直接模式,不使能 FIFO 阈值级别。

直接模式在每个外设请求都立即启动对存储器传输的单次传输。直接模式要求源地址和目标地址的数据宽度必须一致,所以只有 PSIZE 控制,而 MSIZE 值被忽略。突发传输是基于 FIFO 的所以直接模式不被支持。另外直接模式不能用于存储器到存储器传输。

在直接模式下,如果 DMA 配置为存储器到外设传输那 DMA 会将一个数据存放在FIFO 内,如果外设启动 DMA 传输请求就可以马上将数据传输过去。

7、双缓冲模式

设置 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 双缓冲模式传输数据就可以非常好的解决这个问题,达到解码和输出音频数据到音频电路同步进行的效果。

8、DMA中断

每个 DMA 数据流可以在发送以下事件时产生中断:

  1. 达到半传输: DMA 数据传输达到一半时 HTIF 标志位被置 1,如果使能 HTIE 中断控制位将产生达到半传输中断;

  2. 传输完成: DMA 数据传输完成时 TCIF 标志位被置 1,如果使能 TCIE 中断控制位将产生传输完成中断;

  3. 传输错误: DMA 访问总线发生错误或者在双缓冲模式下试图访问“受限”存储器地址寄存器时 TEIF 标志位被置 1,如果使能 TEIE 中断控制位将产生传输错误中断;

  4. FIFO 错误:发生 FIFO 下溢或者上溢时 FEIF 标志位被置 1,如果使能 FEIE 中断控制位将产生 FIFO 错误中断;

  5. 直接模式错误:在外设到存储器的直接模式下,因为存储器总线没得到授权,使得先前数据没有完成被传输到存储器空间上, 此时 DMEIF 标志位被置 1,如果使能 DMEIE 中断控制位将产生直接模式错误中断。

DMA初始化结构体详解

DMA_ InitTypeDef 初始化结构体

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; //外设突发传输
} DMA_InitTypeDef;
  1. DMA_Channel: DMA 请求通道选择,可选通道 0 至通道 7,每个外设对应固定的通道,具体设置值需要查表 21-1 和表 21-2;它设 定 DMA_SxCR 寄存器 的CHSEL[2:0]位的值。例如,我们使用模拟数字转换器 ADC3 规则采集 4 个输入通道的电压数据,查表 21-2 可知使用通道 2。

  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 采集传输是直接模式,要求使用单次模式。

DMA存储器到存储器模式实验

存储器到存储器模式可以实现数据在两个内存的快速拷贝。我们先定义一个静态的源数据,然后使用 DMA 传输把源数据拷贝到目标地址上,最后对比源数据和目标地址的数据,看看是否传输准确。

参考引用:

  1. 野火---《零死角玩转STM32-F429挑战者》
  2. 《STM32F4xx中文参考手册》
  3. 《Cortex-M4内核编程手册》

相关文章: