文章目录
- 一、DMA介绍
- 1.1 DMA简介
- 1.2 DMA结构
- 二、DMA相关寄存器(了解)
- 三、DMA的工作过程(掌握)
- 四、DMA应用实例
- 4.1 DMA常用库函数
- 4.2 实例程序
一、DMA介绍
1.1 DMA简介
DMA用来提供外设与外设之间、外设与存储器之间、存储器与存储器之间的高速数据传输,无需CPU干预,数据可以通过DMA快速传输,节省CPU的资源。
在实现DMA传输时,由DMA控制器直接掌管总线,因此,存在着一个总线控制权转移的问题,即DMA传输前,CPU把总线控制权交给DMA控制器,在结束DMA传输后,DMA控制器立即把总线控制权交回CPU。
一个完整的DMA传输过程包括DMA请求、DMA响应、DMA传输、DMA结束等4个步骤。
(1)DMA请求:CPU对DMA控制器初始化,并向IO接口发出操作命令,IO接口提出DMA请求。
(2)DMA响应:DMA控制器对DMA请求判别优先级及屏蔽,向总线裁决逻辑提出总线请求。当CPU执行完当前总线周期即可释放总线控制权。此时,总线裁决逻辑输出总线应答,表示DMA已经响应,通过DMA控制器通知IO接口开始DMA传输。
(3)DMA传输:DMA控制器获得总线控制权后,CPU即刻挂起或只执行内部操作,由DMA控制器输出读写命令,直接控制RAM与IO接口进行DMA传输。在DMA控制器的控制下,CPU只要提供传送数据的起始位置和数据长度,然后存储器和外设之间直接进行数据传输,CPU不参与传送过程。
(4)DMA结束:当完成规定的成批数据传送后,DMA控制器释放总线控制权,并向IO接口发出结束信号。当IO接口收到结束信号后,一方面停止IO设备的工作,另一方面向CPU提出中断请求,CPU响应中断,执行一段检查本次DMA传输操作正确性的代码。最后,带着本次操作结果及状态继续执行原来程序。
1.2 DMA结构
STM32最多拥有两个DMA控制器,DMA1控制器拥有7个独立的可配置通道,DMA2有5个独立的可配置通道,每个通道用来管理来自一个或多个外设对存储器访问的请求。
DMA是AHB总线上的设备,它有2个AHB端口:一个是从端口,用于配置DMA;一个是主端口,用于设备间数据传输。两个AHB/APB桥在AHB和两个APB总线间提供同步连接。APB1操作速度限于36MHz,APB2操作速度限于72MHz。STM32F103的DMA结构图如下所示:
STM32的Cortex-M3核心和DMA控制器通过总线矩阵连接到Flash控制总线、SRAM总线和AHB总线,进而通过AHB总线连接到APB总线服务外设。
总线矩阵有两个主要特征:循环优先调度、多层结构和总线挪用。
- 循环优先调度
NVIC和Cortex-M3处理器实现了高性能低延时中断调度。所有的Cortex-M3指令即可以在单周期内执行,也可以在总线周期上被中断。循环优先级调度能够确保CPU在必要时每两个总线周期就去访问其他总线。 - 多层结构和总线挪用
多层结构允许两个主设备并行执行数据传输。使用总线挪用存取机制,CPU访问和DMA通过APB总线存取外设可以并行工作。DMA总线挪用机制使得总线利用效率更高。
DMA功能的是通过操作两类寄存器实现的:
一类是具有DMA功能的相关外设寄存器,主要来自外设开启DMA功能,例如在通用定时器中的DMA/中断使能寄存器、DMA控制寄存器和连续式的DMA地址寄存器。
另一类是DMA相关寄存器,用来设置DMA的具体工作方式,DMA相关寄存器包括DMA中断状态寄存器(DMA_ISR)、DMA中断标志清除寄存器(DMA_IFCR)等。
二、DMA相关寄存器(了解)
-
DMA中断状态寄存器——DMA_ISR
该寄存器保存着DMA中断的状态。
-
DMA中断标志清除寄存器——DMA_IFCR
该寄存器用来清除DMA中断标志。
-
DMA通道x配置寄存器——DMA_CCRx
该寄存器用来配置DMA工作模式。
-
DMA通道x传输数量寄存器——DMA_CNDTRx
该寄存器用来配置DMA传输数据量。
-
DMA通道x外设地址寄存器——DMA_CPARx
该寄存器用来配置DMA通道外设地址。
-
DMA通道x存储器地址寄存器——DMA_CMARx
该寄存器用来配置DMA通道存储器地址。
三、DMA的工作过程(掌握)
1. DMA传送
当发生一个事件后,外设发送一个时间请求信号到DMA控制器,DMA控制器根据通道的优先权来处理请求。当DMA控制器开始访问外设时,DMA控制器首先向外设发送一个应答信号;外设得到应答信号后立即撤销请求;外设撤销请求后,DMA控制器同时撤销应答信号。如果载磁发生请求,外设则可以启动下次传送。每个DMA传送由以下3个操作组成;
(1) 从外设数据寄存器或由DMA_CMARx寄存器指定地址的存储单元执行加载操作。(取数据)
(2)存储数据到外设数据寄存器或存数据到DMA_CMARx寄存器指定地址的存储单元。(存储)
(3)执行依次DMA_CNDTRx寄存器的递减操作,该寄存器内容为完成的传输内容数量。
2. 仲裁器
仲裁器根据通道请求的优先级来启动外设/存储器的访问。优先权管理分2个阶段:
(1)软件:每个通道的优先权可以在DMA_CCRx寄存器中设置,有4个等级: 最高优先级、高优先级 、中等优先级 、低优先级。
(2)硬件:如果2个请求有相同的软件优先级,则较低编号的通道比较高编号的通道有较高的优先权。举个例子,通道2优先于通道4。
3. DMA通道
每个通道都可以在有固定地址的外设寄存器和存储器地址之间执行DMA传输。DMA传输的数据量是可编程的,最大达到65535。包含要传输的数据项数量的寄存器,在每次传输后递减。
(1)传输的数据量
外设和存储器的传输数据量可以通过DMA_CCRx寄存器中的PSIZE和MSIZE位编程。
(2)指针自增
通过设置DMA_CCRx寄存器中的PINC和MINC标志位,外设和存储器的指针在每次传输后可以有选择地完成自动增量。当设置为增量模式时,下一个要传输的地址将是前一个地址加上增量值,增量值取决与所选的数据宽度为1、2或4。第一个传输的地址是存放在DMA_CPARx /DMA_CMARx寄存器中地址。
(3)通道配置过程
下面是配置DMA通道x的过程(x代表通道号):
①在DMA_CPARx寄存器中设置外设寄存器的地址。发生外设数据传输请求时,这个地址将是数据传输的源或目标。
② 在DMA_CMARx寄存器中设置数据存储器的地址。发生外设数据传输请求时,传输的数据将从这个地址读出或写入这个地址。
③ 在DMA_CNDTRx寄存器中设置要传输的数据量。在每个数据传输后,这个数值递减。
④ 在DMA_CCRx寄存器的PL[1:0]位中设置通道的优先级。
⑤ 在DMA_CCRx寄存器中设置数据传输的方向、循环模式、外设和存储器的增量模式、外设和存储器的数据宽度、传输一半产生中断或传输完成产生中断。
⑥ 设置DMA_CCRx寄存器的ENABLE位,启动该通道。
⑦一旦启动了DMA通道,它既可响应连到该通道上的外设的DMA请求。
⑧当传输一半的数据后,半传输标志(HTIF)被置1,当设置了允许半传输中断位(HTIE)时,将产生一个中断请求。在数据传输结束后,传输完成标志(TCIF)被置1,当设置了允许传输完成中断位(TCIE)时,将产生一个中断请求。
(4)循环模式
循环模式用于处理循环缓冲区和连续的数据传输(如ADC的扫描模式)。在DMA_CCRx寄存器中的CIRC位用于开启这一功能。当启动了循环模式,数据传输的数目变为0时,将会自动地被恢复成配置通道时设置的初值,DMA操作将会继续进行。
(5)存储器到存储器模式
DMA通道的操作可以在没有外设请求的情况下进行,这种操作就是存储器到存储器模式。 当设置了DMA_CCRx寄存器中的MEM2MEM位之后,在软件设置了DMA_CCRx寄存器中的EN位启动DMA通道时,DMA传输将马上开始。当DMA_CNDTRx寄存器变为0时,DMA传输结束。存储器到存储器模式不能与循环模式同时使用。
4. 错误管理
读写一个保留的地址区域,将会产生DMA传输错误。当在DMA读写操作时发生DMA传输错误时,硬件会自动地清除发生错误的通道所对应的通道配置寄存器(DMA_CCRx)的EN位,该通道操作被停止。此时,在DMA_IFR寄存器中对应该通道的传输错误中断标志位(TEIF)将被置位,如果在DMA_CCRx寄存器中设置了传输错误中断允许位,则将产生中断。
5. DMA请求映射
从外设(TIMx[x=1、2、3、4]、ADC1、SPI1、SPI/I2S2、I2Cx[x=1、2]和USARTx[x=1、2、3])产生的7个请求,通过逻辑或输入到DMA1控制器,这意味着同时只能有一个请求有效。
四、DMA应用实例
4.1 DMA常用库函数
函数名称 | 功能 |
---|---|
DMA_DeInit | 将DMA的通道x寄存器重设为缺省值 |
DMA_Init | 根据DMA_InitStruct中指定的参数初始化DMA通道x寄存器 |
DMA_StructInit | 把DMA_InitStruct中的每一个参数按缺省值填入 |
DMA_Cmd | 使能或使能指定的通道x |
DMA_ITConfig | 使能或是能指定的通道x中断 |
DMA_GetCurrDataCounte | 返回当前DMA通道x剩余的待传输数据数目 |
DMA_GetFlagStatus | 检查指定的DMA通道x标志位设置与否 |
DMA_ClearFlag | 清除DMA通道x待处理标志位 |
DMA_GetITStatus | 检查指定的DMA通道x中断发生与否 |
DMA_ClearITPendingBit | 清除DMA通道x中断待处理标志位 |
4.2 实例程序
- 实例介绍: 利用DMA将Flash中的32位数据缓冲区的内容传送到RAM中所定义的缓冲区内。传输完成后产生传输完成中断,最后将源缓冲区中数据与目的缓冲区中数据对比来检测所有数据是否传输正确。
- 实例
(1)DMA初始化
void DMADriver_Init(void)
{
DMA_InitTypeDef DMA_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
// 外设基地址, 源地址
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)SRC_Const_Buffer;
// RAM基地址, 目的地址
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)DST_Buffer;
// 传输方向,外设为源地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
// 缓冲区大小, 0~65536
DMA_InitStructure.DMA_BufferSize = BufferSize;
// 外设地址自增1
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable;
// RAM地址自增1
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
// 外设数据宽度
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word; // 字,4个字节
// RAM数据宽度
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
// 传输模式 一次传送
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
// 优先级
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
//内存到内存传输
DMA_InitStructure.DMA_M2M = DMA_M2M_Enable;
DMA_Init(DMA1_Channel1,&DMA_InitStructure);
// 传输完成中断
DMA_ITConfig(DMA1_Channel1,DMA_IT_TC,ENABLE);
// 开始传输前数据量
CurrDataCounterBegin = DMA_GetCurrDataCounter(DMA1_Channel1);
// 开启DMA
DMA_Cmd(DMA1_Channel1,ENABLE);
}
(2)中断处理函数
void DMA1_Channel1_IRQHandler(void)
{
// 检测DMA1通道1传输完成中断
if(DMA_GetITStatus(DMA1_IT_TC1))
{
// 传输完成后得到当前计数
CurrDataCounterEnd = DMA_GetCurrDataCounter(DMA1_Channel1);
// 清除标志位
DMA_ClearITPendingBit(DMA1_IT_TC1);
}
}
(3)串口初始化在前几章有写过,这里就不介绍了
// 定义存储大小
#define BufferSize 32
uint32_t CurrDataCounterBegin = 0;
uint32_t CurrDataCounterEnd = 0x01;
const uint32_t SRC_Const_Buffer[BufferSize] = {
0x01022231,0x19234012,0x02348192,0x19384910,
0x01022231,0x19234012,0x02348192,0x19384910,
0x01022231,0x19234012,0x02348192,0x19384910,
0x01022231,0x19234012,0x02348192,0x19384910,
0x01022231,0x19234012,0x02348192,0x19384910,
0x01022231,0x19234012,0x02348192,0x19384910,
0x01022231,0x19234012,0x02348192,0x19384910,
0x01022231,0x19234012,0x02348192,0x19384910};
uint32_t DST_Buffer[BufferSize];
void main()
{
UsartDriver_Init();
DMADriver_Init();
Delay_ms(10);
while(CurrDataCounterEnd != 0);
printf("Complete transfer!\r\n")
}