5 DMA简介
DMA全称direct memory access,即直接存储器访问。
其主要作用是不占用CPU大量资源,在APB、AHB总线上的设备之间以硬件方式高速有效地传输数据。
注意:在ing20系列使能 FLASH_PROGRAM_WITH_DMA 宏时,DMA的通道0会被FLASH写入时占用,当调用flash写入相关接口时,会修改ch0的配置用于flash写入
5.1 功能描述
5.1.2 搬运方式
单次数据块搬运:DMA使用单个通道,一次使能将数据从SRC到DST位置搬运一次
成串多数据块搬运:DMA使用单个通道,一次使能按照DMA链表信息依次将数据从SRC到DST位置搬运多次或循环搬运。
其根本区别是有无注册有效的DMA链表。
5.1.4 中断类型
IntErr:错误中断表示DMA传输发生了错误而触发中断,主要包括总线错误、地址没对齐和传输数据宽度没对齐等。
IntAbt:终止传输中断会在终止DMA通道传输时产生。
IntTC:TC中断会在没有产生IntErr和IntAbt的情况下完成一次传输时产生。
注意: 当DMA传输链表有效时,TC中断只会在传输完成时产生,不会在传输过程中产生。
IntEachDesc:链转换结束中断。当DMA传输链表有效时,每当一个描述符传输完成时产生。
注意: 中断触发时会停止DMA传输,需要在中断重新使能通道才会继续进行链式DMA传输。
5.1.5 数据地址类型
Increment address
Decrement address
Fixed address
如果Increment则DMA从地址由小到大搬运数据,相反的Decrement则由大到小搬运。fixed地址适用于外设FIFO的寄存器搬运数据。
5.1.6 数据方式
normal mode
handshake mode
DMA搬运前需要对数据源和数据目的地址的数据方式进行配置。
数据方式的选择有如下建议:
1. 从内存搬运数据选择normal mode;
2. 从外设FIFO搬运数据选择handshake mode,同时要和外设协商好BurstSize,支持2^n(n = 0-7)大小的BurstSize。
5.2 使用方法
5.2.1 方法概述
首先确认数据搬运需求是单次搬运还是成串搬运,以及搬运类型,即memory和peripheral的关系。
5.2.2 注意点
定义DMA_Descriptor变量需要8字节对齐,否则DMA搬运不成功
成串搬运如果配置多个DMA中断则需要在每个中断里使能DMA,直到最后一次搬运完成
对于从外设搬运需要确认外设是否支持DMA
建议从外设搬运选择握手方式,并与外设正确协商burstSize
burstSize尽量取较大值,有利于减少DMA中断次数提高单次中断处理效率。但burstSize太大可能最后一次不能搬运丢弃较多数据
建议设置从外设搬运总数据量为burstSize的整数倍或采用乒乓搬运的方式
在DMA从外设搬运的情况下,正确的操作顺序是先配置并使能好DMA,再使能外设开始产生数据
5.2.3 握手请求
握手请求:DMA请求握手信号,请求对方传输数据。当进行外设到内存数据传输,内存到外设数据传输以及外设到外设数据传输时,都需要进行握手请求。
握手请求的触发条件:
DMA用作到外设到FIFO搬运数据时,当FIFO内数据计数增加waterlevel时,外设会发出数据请求,DMA开始将数据从外设FIFO搬运到内存。
DMA用作FIFO到外设数据传输时,当FIFO内数据计数减小到waterlevel时,DMA会发出数据请求,DMA开始将数据从内存搬运到外设FIFO。
例如 :
使用DMA将RX接收数据搬运到内存: UART设置RX FIFO waterlevel为4,当接收到4个字节数据时,UART会发出数据请求,DMA开始将数据从外设FIFO搬运到内存。
使用DMA将内存搬运到外设触发TX发送:UART设置TX FIFO waterlevel为4,当fifo内的数据计数到达4时,UART会发出数据请求,DMA开始将数据从内存搬运到外设FIFO。
握手请求的配置: 通常情况无需关心,使用默认配置。
当需要使能的DMA搬运配置为pdm,adc,pwm_channel0,pwm_channel2,qdec_req2外设时,需要手动重新配置握手请求表,再使用对应接口初始化DMA. 详情请参照SYSCTRL DMA规划章节。
5.3 编程指南
5.3.1 驱动接口
DMA_PrepareMem2Mem:memory到memory搬运标准DMA寄存器配置接口
DMA_PreparePeripheral2Mem:Peripheral到memory搬运标准DMA寄存器配置接口
DMA_PrepareMem2Peripheral:memory到Peripheral搬运标准DMA寄存器配置接口
DMA_PreparePeripheral2Peripheral:Peripheral到Peripheral搬运标准DMA寄存器配置接口
DMA_Reset:DMA复位接口
DMA_GetChannelIntState:DMA通道中断状态获取接口
DMA_ClearChannelIntState:DMA通道清中断接口
DMA_EnableChannel:DMA通道使能接口
DMA_AbortChannel:DMA通道终止接口
DMA_ConfigSrcBurstSize: DMA配置通道burstSize接口
5.3.2 代码示例
5.3.2.1 单次搬运
下面以memory到memory单次搬运展示DMA的基本用法:
#define CHANNEL_ID 0
char src[] = "hello world!";
char dst[20];
DMA_Descriptor test __attribute__((aligned (8)));
static uint32_t DMA_cb_isr(void *user_data)
{
uint32_t state = DMA_GetChannelIntState(CHANNEL_ID);
DMA_ClearChannelIntState(CHANNEL_ID, state);
printf("dst = %s\n", dst);
return 0;
}
void DMA_Test(void)
{
DMA_Reset();
platform_set_irq_callback(PLATFORM_CB_IRQ_DMA, DMA_cb_isr, 0);
DMA_PrepareMem2Mem(&test[0],
dst,
src, strlen(src),
DMA_ADDRESS_INC, DMA_ADDRESS_INC, 0);
DMA_EnableChannel(CHANNEL_ID, &test);
}最终会在DMA中断程序里面将搬运到dst中的“hello world!”字符串打印出来。
5.3.2.2 成串搬运
下面以memory到memory两块数据搬运拼接字符串展示DMA成串搬运的基本用法:
#define CHANNEL_ID 0
char src[] = "hello world!";
char src1[] = "I am ING20.";
char dst[100];
DMA_Descriptor test[2] __attribute__((aligned (8)));
static uint32_t DMA_cb_isr(void *user_data)
{
uint32_t state = DMA_GetChannelIntState(CHANNEL_ID);
DMA_ClearChannelIntState(CHANNEL_ID, state);
printf("dst = %s\n", dst);
return 0;
}
void DMA_Test(void)
{
DMA_Reset();
platform_set_irq_callback(PLATFORM_CB_IRQ_DMA, DMA_cb_isr, 0);
test[0].Next = &test[1]; // make a DMA link chain
test[1].Next = NULL;
DMA_PrepareMem2Mem(&test[0],
dst,
src, strlen(src),
DMA_ADDRESS_INC, DMA_ADDRESS_INC, 0);
DMA_PrepareMem2Mem(&test[1],
dst + strlen(src),
src1, sizeof(src1),
DMA_ADDRESS_INC, DMA_ADDRESS_INC, 0);
DMA_EnableChannel(CHANNEL_ID, &test[0]);
}最终将会打印出“hello world!I am ING20.”字符串。
5.3.2.3 DMA乒乓搬运
DMA乒乓搬运是一种DMA搬运的特殊用法,其主要应用场景是将外设FIFO中数据循环搬运到memory中并处理。
可实现“搬运”和“数据处理”分离,从而大大提高程序处理数据的效率。
对于大量且连续的数据搬运,如音频,我们推荐选用DMA乒乓搬运的方式。
5.3.2.3.1 DMA乒乓搬运接口
在最新SDK中我们已将DMA乒乓搬运封装成标准接口,方便开发者调用,提高开发效率。
使用时请添加 pingpong.c文件,并包含 pingpong.h文件。
DMA_PingPongSetup:DMA乒乓搬运建立接口
DMA_PingPongIntProc:DMA乒乓搬运标准中断处理接口
DMA_PingPongGetTransSize:获取DMA乒乓搬运数据量接口
DMA_PingPongEnable:DMA乒乓搬运使能接口
DMA_PingPongDisable:DMA乒乓搬运去使能接口
更多程序开发者可以参考voice_remote_ctrl例程。
5.3.2.3.2 DMA乒乓搬运示例
下面将以最常见的DMA乒乓搬运I2s数据为例展示DMA乒乓搬运的用法。
I2s的相关配置不在本文的介绍范围内,默认I2s已经配置好,DMA和I2s协商burstSize=8。
#include "pingpong.h"
#define CHANNEL_ID 0
DMA_PingPong_t PingPong;
static uint32_t DMA_cb_isr(void *user_data)
{
uint32_t state = DMA_GetChannelIntState(CHANNEL_ID);
DMA_ClearChannelIntState(CHANNEL_ID, state);
// call 'DMA_PingPongIntProc' to get the pointer of data-buff.
uint32_t *rr = DMA_PingPongIntProc(&PingPong, CHANNEL_ID);
uint32_t i = 0;
// call 'DMA_PingPongGetTransSize' to kwon how much data in data-buff.
uint32_t transSize = DMA_PingPongGetTransSize(&PingPong);
while (i < transSize) {
// do something with data 'rr[i]'
i++;
}
return 0;
}
void DMA_Test(void)
{
// call 'DMA_PingPongSetup' to setup ping-pong DMA.
DMA_PingPongSetup(&PingPong, SYSCTRL_DMA_I2S_RX, 100, 8);
platform_set_irq_callback(PLATFORM_CB_IRQ_DMA, DMA_cb_isr, 0);
// call 'DMA_PingPongEnable' to start ping-pong DMA transmission.
DMA_PingPongEnable(&PingPong, CHANNEL_ID);
I2S_ClearRxFIFO(APB_I2S);
I2S_DMAEnable(APB_I2S, 1, 1);
I2S_Enable(APB_I2S, 0, 1); // Enable I2s finally
}停止DMA乒乓搬运可以调用以下接口:
void Stop(void)
{
// call 'DMA_PingPongEnable' to disable ping-pong DMA transmission.
DMA_PingPongDisable(&PingPong, CHANNEL_ID);
I2S_Enable(APB_I2S, 0, 0);
I2S_DMAEnable(APB_I2S, 0, 0);
}