DMA简介:
直接存储器访问 (DMA) 用于在外设与存储器之间以及存储器与存储器之间提供高速数据传输。可以在无需任何 CPU 操作的情况下通过 DMA 快速移动数据。这样节省的 CPU 资源可供其他操作使用。
DMA 控制器基于复杂的总线矩阵架构,将功能强大的双 AHB 主总线架构与独立的 FIFO 结合在一起,优化了系统带宽。
两个 DMA 控制器(DMA1 和 DMA2)总共有 16 个数据流(每个控制器 8 个),每一个 DMA 控制器都用于管理一个或多个外设的存储器访问请求。每个数据流总共可以有多达 16 个通 道(或称请求)。每个 DMA 控制器都有一个仲裁器,用于处理 DMA 请求间的优先级。一个完整的DMA传输过程必须经过DMA请求、DMA响应、DMA传输、DMA结束4个步骤。
DMA主要特性:
-
双 AHB 主总线架构,一个用于存储器访问,另一个用于外设访问
-
每个数据流有四个字深的 32 位FIFO,可用于 FIFO 模式或直接模式
-
DMA支持下列传输:
外设到存储器的传输
存储器到外设的传输
存储器到存储器的传输 -
独立的源和目标传输宽度(字节、半字、字),外设和存储器指针在每次传输后可以自动向后递增或保持常量
-
5 个事件标志(DMA 半传输、DMA 传输完成、DMA 传输错误、DMA FIFO错误、直接模式错误),进行逻辑或运算,从而产生每个数据流的单个中断请求
-
循环模式可用于处理循环缓冲区和连续数据流(例如 ADC 扫描模式)。可以使用DMA_SxCR寄存器中的 CIRC 位使能此特性。当激活循环模式时,要传输的数据项的数目在数据流配置阶段自动用设置的初始值进行加载,并继续响应 DMA 请求。
-
单次传输和突发传输:DMA 控制器可以产生单次传输或 4 个、8 个和 16 个节拍的增量突发传输。突发大小指示突发中的节拍数,而不是传输的字节数。
串口DMA单次发送实例:
#include "dma.h"
#include "module.h"
u8 DMA_Busy_Flag = 0; //DMA状态标志
//DMAx的各通道配置
//从存储器->外设模式/8位数据宽度/存储器增量模式
//DMA_Streamx:DMA数据流,DMA1_Stream0~7/DMA2_Stream0~7
//chx:DMA通道选择,@ref DMA_channel DMA_Channel_0~DMA_Channel_7
//par:外设地址
//mar:存储器地址
//ndtr:数据传输量
void MyDMA_Config(DMA_Stream_TypeDef *DMA_Streamx,u32 chx,u32 par,u32 mar,u16 ndtr)
{
DMA_InitTypeDef DMA_InitStructure;
if((u32)DMA_Streamx>(u32)DMA2)//得到当前stream是属于DMA2还是DMA1
{
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE);//DMA2时钟使能
}else
{
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1,ENABLE);//DMA1时钟使能
}
DMA_DeInit(DMA_Streamx);
while (DMA_GetCmdStatus(DMA_Streamx) != DISABLE);//等待DMA可配置
/* 配置 DMA Stream */
DMA_InitStructure.DMA_Channel = chx; //通道选择
DMA_InitStructure.DMA_PeripheralBaseAddr = par;//DMA外设地址
DMA_InitStructure.DMA_Memory0BaseAddr = mar;//DMA 存储器0地址
DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;//存储器到外设模式
DMA_InitStructure.DMA_BufferSize = ndtr;//数据传输量
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设非增量模式
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//存储器增量模式
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//外设数据长度:8位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;//存储器数据长度:8位
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;// 使用普通模式
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;//中等优先级
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;//存储器突发单次传输
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;//外设突发单次传输
DMA_Init(DMA_Streamx, &DMA_InitStructure);//初始化DMA Stream
}
//开启一次DMA传输
//DMA_Streamx:DMA数据流,DMA1_Stream0~7/DMA2_Stream0~7
//ndtr:数据传输量
void MyDMA_Start(USART_TypeDef *USARTx,DMA_Stream_TypeDef *DMA_Streamx,u16 ndtr)
{
DMA_Busy_Flag = 0; //DMA忙
USART_DMACmd(USARTx,USART_DMAReq_Tx,ENABLE); //使能串口DMA发送
DMA_Cmd(DMA_Streamx, DISABLE); //关闭DMA传输
while (DMA_GetCmdStatus(DMA_Streamx) != DISABLE){} //确保DMA可以被设置
DMA_SetCurrDataCounter(DMA_Streamx,ndtr); //数据传输量
DMA_Cmd(DMA_Streamx, ENABLE); //开启DMA传输
}
//初始化DMA和开启传输完成中断
void InitDMA(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
#ifdef __CUSTOM_UART__
//DMA2,STEAM7,CH5,外设为串口6
MyDMA_Config(DMA2_Stream7,DMA_Channel_5,(u32)&USART6->DR,(u32)data2host_dma,sizeof(data2host_dma));
DMA_ITConfig(DMA2_Stream7,DMA_IT_TC,ENABLE);
NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream7_IRQn;
#else
//DMA2,STEAM0,CH1,外设为串口9
MyDMA_Config(DMA2_Stream0,DMA_Channel_1,(u32)&UART9->DR,(u32)data2host_dma,sizeof(data2host_dma));
DMA_ITConfig(DMA2_Stream0,DMA_IT_TC,ENABLE);
NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream0_IRQn;
#endif
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2 ;//抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器
#ifdef __CUSTOM_UART__
MyDMA_Start(USART6,DMA2_Stream7,strlen(data2host_dma));//客户主控串口数据start发送
#else
MyDMA_Start(UART9,DMA2_Stream0,strlen(data2host_dma));//主机透传蓝牙数据start发送
#endif
}
//相对应的中断函数
#ifdef __CUSTOM_UART__
void DMA2_Stream7_IRQHandler(void)
{
if(DMA_GetFlagStatus(DMA2_Stream7,DMA_FLAG_TCIF7) == SET)
{
DMA_Busy_Flag = 1; //DMA空闲
memset(data2host_dma,0,strlen(data2host_dma));//清buf
DMA_ClearFlag(DMA2_Stream7,DMA_FLAG_TCIF7);//清除DMA2_Steam7传输完成标志
}
}
#else
void DMA2_Stream0_IRQHandler(void)
{
if(DMA_GetFlagStatus(DMA2_Stream0,DMA_FLAG_TCIF0) == SET)
{
DMA_Busy_Flag = 1; //DMA空闲
memset(data2host_dma,0,strlen(data2host_dma));//清buf
DMA_ClearFlag(DMA2_Stream0,DMA_FLAG_TCIF0);//清除DMA2_Steam0传输完成标志
}
}
#endif
串口DMA传输小结:
- 使用串口DMA发送数据时只需初始化DMA和配置通道等,使能串口DMA发送和设置数据传输量就能开启传输
- 当DMA单次传输完成后事件标志TCIF会置位,在下次开始DMA传输时需要清除事件标志
- 由于DMA和内核是使用不同的总线的并行传输,开启下一次传输时需要注意上次传输是否完成
- 使用串口接收不定长的数据时可以使能DMA传输数据并开启空闲中断(USART_IT_IDLE)实现数据的接收,需要特别注意DMA缓冲区的大小和接收数据的长度
本文深入介绍了直接存储器访问(DMA)的工作原理和特性,包括双AHB主总线架构、FIFO优化及不同传输模式。通过示例展示了如何配置DMA进行串口数据传输,强调了DMA在传输过程中的中断管理和状态检查,以及在处理循环缓冲区和连续数据流中的作用。此外,还探讨了串口DMA单次传输的实现和中断处理。
6614





