DMA(Direct Memory Access)—直接存储器存取,是单片机的一个外设,它的主要功能
是用来搬数据,但是不需要占用 CPU,即在传输数据的时候,CPU 可以干其他的事情。
以STM32F103单片机为例
F03有DMA1和DMA2两组DMA,其中DMA1有7个通道,DMA2有5个通道。
外设需要使用DMA,则必须向DMA发送DMA请求,DMA收到请求信号之后,控制器会给外设一个应答信号,当外设应答后且 DMA 控制器收到应答信号之后,就会启动 DMA 的传输,直到传输完毕。
DMA 有 DMA1 和 DMA2 两个控制器,DMA1 有 7 个通道,DMA2 有 5 个通道,不同的 DMA 控制器的通道对应着不同的外设请求,这决定了我们在软件编程上该怎么设置,具体见 DMA 请求映像表。
DMA 具有 12 个独立可编程的通道,其中 DMA1 有 7 个通道,DMA2 有 5 个通道,每
个通道对应不同的外设的 DMA 请求。虽然每个通道可以接收多个外设的请求,但是同一
时间只能接收一个,不能同时接收多个。
当发生多个 DMA 通道请求时,就意味着有先后响应处理的顺序问题,这个就由仲裁器管理。仲裁器管理 DMA 通道请求分为两个阶段。第一阶段属于软件阶段,可以在DMA_CCRx 寄存器中设置,有 4 个等级:非常高、高、中和低四个优先级。
第二阶段属于硬件阶段,如果两个或以上的 DMA 通道请求设置的优先级一样,则他们优先级取决于通道编号,编号越低优先权越高,比如通道 0 高于通道 1。在大容量产品和互联型产品中,DMA1 控制器拥有高于 DMA2 控制器的优先级。
DMA 传输数据的方向有三个:从外设到存储器
,从存储器到外设
,从存储器到存储器
,。具体的方向 DMA_CCR配置,这里面涉及到的外设地址由 DMA_CPAR 配置,存储器地址由 DMA_CMAR 配置。
当我们使用从外设到存储器传输时,以 ADC 采集为例
。DMA 外设寄存器的地址对应的就是 ADC 数据寄存器DR的地址,DMA 存储器的地址就是我们自定义的变量(用来接收存储 AD 采集的数据)的地址。方向我们设置外设为源地址。
当我们使用从存储器到外设传输时,以串口向电脑端发送数据
为例。DMA 外设寄存器的地址对应的就是串口数据寄存器DR的地址,DMA 存储器的地址就是我们自定义的变量(相当于一个缓冲区,用来存储通过串口发送到电脑的数据)的地址。方向我们设置外设为目标地址。
当我们使用从存储器到存储器传输时,以内部 FLASH 向内部 SRAM 复制数据为例
。DMA 外设寄存器的地址对应的就是内部 FLASH(我们这里把内部 FALSH 当作一个外设来看)的地址,DMA 存储器的地址就是我们自定义的变量(相当于一个缓冲区,用来存储来自内部 FLASH 的数据)的地址。方向我们设置外设(即内部 FLASH)为源地址。跟上面两个不一样的是,这里需要把 DMA_CCR 位 14:MEM2MEM:存储器到存储器模式配置为 1,启动 M2M 模式。
以串口向电脑发送数据为例,源地址为片上flash,目标地址为USART->DR,可以一次性给电脑发送很多数据,具体多少由DMA_CNDTR 配置,这是一个 32 位的寄存器,一次最多只能传输 65535 个数据。
要想数据传输正确,源和目标地址存储的数据宽度还必须一致,串口数据寄存器是 8位的,所以我们定义的要发送的数据也必须是 8 位。外设的数据宽度由 DMA_CCRx 的PSIZE[1:0]配置,可以是8/16/32位,存储器的数据宽度由DMA_CCRx的MSIZE[1:0]配置,可以是 8/16/32 位。
在 DMA 控制器的控制下,数据要想有条不紊的从一个地方搬到另外一个地方,还必须正确设置两边数据指针的增量模式。
在 DMA 控制器的控制下,数据要想有条不紊的从一个地方搬到另外一个地方,还必须正确设置两边数据指针的增量模式。外设的地址指针由 DMA_CCRx 的 PINC 配置,存储器的地址指针由 MINC 配置。
以串口向电脑发送数据为例,要发送的数据很多,每发送完一个,那么存储器的地址指针就应该加 1,而串口数据寄存器只有一个,那么外设的地址指针就固定不变。具体的数据指针的增量模式由实际情况决定。
数据什么时候传输完成,我们可以通过查询标志位
或者通过中断的方式
来鉴别。每个DMA 通道在 DMA 传输过半
、传输完成
和传输错误
时都会有相应的标志位,如果使能了该类型的中断后,则会产生中断。有关各个标志位的详细描述请参考 DMA 中断状态寄存器DMA_ISR 的详细描述。
传输完成还分两种模式,是一次传输还是循环传输,一次传输很好理解,即是传输一次之后就停止,要想再传输的话,必须关断 DMA 使能后再重新配置后才能继续传输。循环传输则是一次传输完成之后又恢复第一次传输时的配置循环传输,不断的重复。具体的由 DMA_CCRx 寄存器的 CIRC 循环模式位控制。
链接: DMA固件库函数
#DMA配置
void DMA_Config(void)
{
DMA_InitTypeDef DMA_InitStructure;
// 开启 DMA 时钟
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
// 源数据地址 aSRC_Const_Buffer是使用const定义在内部flash上的一块数组
DMA_InitStructure.DMA_PeripheralBaseAddr =(uint32_t)aSRC_Const_Buffer;
// 目标地址,例:如果是通过串口发送数据那么目标地址可以设置为USART1->DR
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)aDST_Buffer;
// 方向:外设到存储器(这里的外设是内部的 FLASH),方向都是相对的,要看目标和源怎么设置了
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
// 传输大小
DMA_InitStructure.DMA_BufferSize = BUFFER_SIZE;
// 外设,前文设置的外设地址(内部的 FLASH)地址递增
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable;
// 内存地址递增
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
// 外设数据单位
DMA_InitStructure.DMA_PeripheralDataSize =DMA_PeripheralDataSize_Word;
// 内存数据单位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
// DMA 模式,一次或者循环模式
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
//DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
// 优先级:高
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
// 使能内存到内存的传输
DMA_InitStructure.DMA_M2M = DMA_M2M_Enable;
// 配置 DMA 通道
DMA_Init(DMA1_Channel6, &DMA_InitStructure);
// 使能 DMA
DMA_Cmd(DMA1_Channel6, ENABLE);
}