在使用串口DMA试验过程中,遇到了一些问题,通过试验找到了问题所在,也对DMA的应用有了新的认识,仅以此分享给大家,不足之处请多多指教。
DMA初始化
// 串口对应的DMA请求通道
#define USART_TX_DMA_CHANNEL DMA1_Channel4
#define USART_TX_DMA_IRQ DMA1_Channel4_IRQn
#define USART_TX_DMA_IRQHandler DMA1_Channel4_IRQHandler
// 外设寄存器地址
#define USART_DR_ADDRESS (USART1_BASE+0x04)
// 一次发送的数据量
#define SENDBUFF_SIZE 250
/**
* @brief USARTx TX DMA 配置,内存到外设(USART1->DR)
* @param 无
* @retval 无
*/
void USARTx_DMA_Config(void)
{
DMA_InitTypeDef DMA_InitStructure;
DMA_DeInit(USART_TX_DMA_CHANNEL);
// 开启DMA时钟
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
// 设置DMA源地址:串口数据寄存器地址*/
DMA_InitStructure.DMA_PeripheralBaseAddr = USART_DR_ADDRESS;
// 内存地址(要传输的变量的指针)
DMA_InitStructure.DMA_MemoryBaseAddr = (u32)SendBuff;
// 方向:从内存到外设
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
// 传输大小
DMA_InitStructure.DMA_BufferSize = SENDBUFF_SIZE;
// 外设地址不增
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
// 内存地址自增
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
// 外设数据单位
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
// 内存数据单位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
// DMA模式,一次或者循环模式
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal ; //单次模式,需要手动加载数据传输数量
// DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; //自动重载数据传输数量
// 优先级:中
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
// 禁止内存到内存的传输
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
// 配置DMA通道
DMA_Init(USART_TX_DMA_CHANNEL, &DMA_InitStructure);
NVIC_DMA_Configuration();
// 使能DMA
DMA_Cmd(USART_TX_DMA_CHANNEL,DISABLE);
//使能DMA传送完成中断
DMA_ITConfig(USART_TX_DMA_CHANNEL,DMA_IT_TC,ENABLE);
}
在本次试验中,主要遇到的问题是:DMA模式配置为单次模式(DMA_Mode_Normal),这样在完成一次数据发送后通道传输数量寄存器CNDTR数值为0,根据手册介绍:当CNDTR为0 时,即使通道开启,都不会发生任何数据传输。所以在下一次数据传输时,需要重新配置CNDTR寄存器。
我这里是自己写了一个函数配置CNDTR寄存器。需要注意的是:CNDTR寄存器只有在通道不工作(DMA_CCRx的EN=0)时才可以写入数据。
void USART_DMA_Enable(void)
{
USART_DMACmd(DEBUG_USARTx, USART_DMAReq_Tx, ENABLE);
DMA_Cmd(USART_TX_DMA_CHANNEL, DISABLE ); //关闭 USART1 TX DMA1 所指示的通道
DMA_SetCurrDataCounter(USART_TX_DMA_CHANNEL,SENDBUFF_SIZE);//设置 DMA 缓存的大小
DMA_Cmd(USART_TX_DMA_CHANNEL, ENABLE); //使能 USART1 TX DMA1 所指示的通道
}
DMA的循环模式
针对上面的问题,还可以使用DMA的循环模式进行解决。
在DMA循环模式下,CNDTR寄存器内容变为0时,将自动重载为之前配置的值。不需要手动重载。
当然这种模式下需要注意的是,当通道开启时,会一直进行数据传输。所以需要在一次数据传输完成后,关闭DMA通道。
我这里是使用的DMA中断,在中断中关闭DMA通道。
void USART_TX_DMA_IRQHandler(void)
{
// if(DMA_GetITStatus(DMA1_IT_TC4) != RESET)
// {
// DMA_ClearITPendingBit(DMA1_IT_TC4);
DMA_Cmd (USART_TX_DMA_CHANNEL,DISABLE);
DMA_ClearFlag(DMA1_FLAG_TC4|DMA1_FLAG_GL4|DMA1_FLAG_HT4);//清除通道 4 传输完成标志
// 使能串口发送中断
USART_ITConfig(DEBUG_USARTx, USART_IT_TC, ENABLE);
// }
}
循环模式 下,发送数据只要使能DMA通道即可,不需要重新设置CNDTR寄存器
void USART_DMA_Enable(void)
{
USART_DMACmd(DEBUG_USARTx, USART_DMAReq_Tx, ENABLE);
// DMA_Cmd(USART_TX_DMA_CHANNEL, DISABLE ); //关闭 USART1 TX DMA1 所指示的通道
// DMA_SetCurrDataCounter(USART_TX_DMA_CHANNEL,SENDBUFF_SIZE);//设置 DMA 缓存的大小
DMA_Cmd(USART_TX_DMA_CHANNEL, ENABLE); //使能 USART1 TX DMA1 所指示的通道
}
综上所述,建议无论DMA模式是单次还是循环,在DMA初始化时先关闭DMA通道,在需要数据传输时再打开。
来源:CSDN
作者:basic_yxw
链接:https://blog.csdn.net/basic_yxw/article/details/104261574