STM32使用DMA控制器试验总结

偶尔善良 提交于 2020-02-11 14:32:57

在使用串口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通道,在需要数据传输时再打开。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!