目录
前人经验(DMA初始化在串口初始化之前,这里我只用了串口1的DMA发送)
前提回顾
通过CUBEMX配置串口以DMA方式发送。
为什么使用DMA,DMA可以为外设和内存提供一条数据通道,使得数据的复制不需要CPU去参与,减低CPU的负担,在实时性的工作时显得格外重要。
通过一般的串口发送数据函数:
HAL_UART_Transmit(&huart5,(uint8_t*)Rx5sBuf,length,10);
可以看到有一个等待时间,这里表示CPU参与数据搬运最多等待10ms,所以CPU是在等待串口的。
而配置成DMA发送,基于STM32F103ZET6只有usart1 ,usart2,usart3的发送和接受有DMA模式。串口4,和串口5都只能异步发送(一般我们都使用异步发送数据,只有在某些时序中需要使用到同步)。
再回到串口以DMA的方式发送数据
HAL_UART_Transmit_DMA(&huart1, (uint8_t *)RX1sBuf, sizeof(RX1sBuf));
此时没有等待时间,这里表示CPU不需要等待串口,在程序后台DMA通道将数据搬运到串口再发送。这里需要注意的是,虽然cpu无须干预,可以继续执行后面的代码,但DMA将数据从内存(定义数组的位置)搬运到外设(串口)是需要时间的,如果是连续使用DMA串口发送(连续调用上面代码),必然是不合理的,这时需要判断此串口发送所在的DMA通道是否是空闲再继续发送。
在CUBEMX配置时,默认是把DMA的中断打开的,无论是adc的dma还是串口,它们的完成中断都是打开的,为了避免不必要的进入中断,一般我把ADC的DMA中断关闭,把串口的以DMA接受数据的中断也关闭.
如下图中的强制DMA中断
配置串口的DMA发送数据和空闲中断+DMA接受数据
串口一配置DMA发送和DMA接受(这里以DMA加空闲中断的方式)
串口接受数据模式normal ,发送数据也为normal。(接受数据若设置为循环模式则不知道接受的数组缓存是否错位,除非在
在这里,DMA接受数据的DMA中断关闭,发送数据的DMA中断使能 。(这里的DMA中断是发送数据和接受完数据后会进入中断)
串口全局中断使能。
关于串口的DMA发送只发送一次的问题解决
前人经验(DMA初始化在串口初始化之前,这里我只用了串口1的DMA发送)
我遇到的坑点
而我则是未打开串口以DMA发送的中断。
这里串口发送用到的是DMA1通道4
在stm32f1xx_it.c中看到其通道4的中断函数
再进入其回调函数 HAL_DMA_IRQHandler
(在传输一半完成中断管理,传输完成中断管理,传输错误中断管理里中做标记处理)
通过点灯判断出了串口DMA发送进入了传输完成中断管理,清除了该dma通道的中断标记位和
设置dma通道为就绪状态以及该dma通道作为资源的释放。
/**
* @brief Handles DMA interrupt request.
* @param hdma: pointer to a DMA_HandleTypeDef structure that contains
* the configuration information for the specified DMA Channel.
* @retval None
*/
void HAL_DMA_IRQHandler(DMA_HandleTypeDef *hdma)
{
uint32_t flag_it = hdma->DmaBaseAddress->ISR;
uint32_t source_it = hdma->Instance->CCR;
/* Half Transfer Complete Interrupt management ******************************/
if (((flag_it & (DMA_FLAG_HT1 << hdma->ChannelIndex)) != RESET) && ((source_it & DMA_IT_HT) != RESET))
{
/* Disable the half transfer interrupt if the DMA mode is not CIRCULAR */
if((hdma->Instance->CCR & DMA_CCR_CIRC) == 0U)
{
/* Disable the half transfer interrupt */
__HAL_DMA_DISABLE_IT(hdma, DMA_IT_HT);
}
/* Clear the half transfer complete flag */
__HAL_DMA_CLEAR_FLAG(hdma, __HAL_DMA_GET_HT_FLAG_INDEX(hdma));
/* DMA peripheral state is not updated in Half Transfer */
/* but in Transfer Complete case */
if(hdma->XferHalfCpltCallback != NULL)
{
/* Half transfer callback */
hdma->XferHalfCpltCallback(hdma);
}
}
/* Transfer Complete Interrupt management ***********************************/
else if (((flag_it & (DMA_FLAG_TC1 << hdma->ChannelIndex)) != RESET) && ((source_it & DMA_IT_TC) != RESET))
{
if((hdma->Instance->CCR & DMA_CCR_CIRC) == 0U)
{
/* Disable the transfer complete and error interrupt */
__HAL_DMA_DISABLE_IT(hdma, DMA_IT_TE | DMA_IT_TC);
/* Change the DMA state */
hdma->State = HAL_DMA_STATE_READY;
}
/* Clear the transfer complete flag */
__HAL_DMA_CLEAR_FLAG(hdma, __HAL_DMA_GET_TC_FLAG_INDEX(hdma));
/* Process Unlocked */
__HAL_UNLOCK(hdma);
if(hdma->XferCpltCallback != NULL)
{
/* Transfer complete callback */
hdma->XferCpltCallback(hdma);
}
}
/* Transfer Error Interrupt management **************************************/
else if (( RESET != (flag_it & (DMA_FLAG_TE1 << hdma->ChannelIndex))) && (RESET != (source_it & DMA_IT_TE)))
{
/* When a DMA transfer error occurs */
/* A hardware clear of its EN bits is performed */
/* Disable ALL DMA IT */
__HAL_DMA_DISABLE_IT(hdma, (DMA_IT_TC | DMA_IT_HT | DMA_IT_TE));
/* Clear all flags */
hdma->DmaBaseAddress->IFCR = (DMA_ISR_GIF1 << hdma->ChannelIndex);
/* Update error code */
hdma->ErrorCode = HAL_DMA_ERROR_TE;
/* Change the DMA state */
hdma->State = HAL_DMA_STATE_READY;
/* Process Unlocked */
__HAL_UNLOCK(hdma);
if (hdma->XferErrorCallback != NULL)
{
/* Transfer error callback */
hdma->XferErrorCallback(hdma);
}
}
return;
}
最后通过调用
HAL_UART_Transmit_DMA(&huart1, (uint8_t *)RX1sBuf, sizeof(RX1sBuf));
完成DMA的数据发送。
一般使用DMA发送数据我还是采用定时发送。
DMA+空闲中断
这个极大地提高了效率,而且简洁,hal库不愧是不断完善的。
相比于之前我用串口接受中断和空闲中断接受数据会接受一个字节就进入接受数据中断。而DMA+空闲中断则是接受完一个数据帧再进入空闲中断。
总共就2个函数解决问题。
HAL_UARTEx_ReceiveToIdle_DMA(&huart1,Rx1Buf,102); //串口1开启DMA接受
while之前开启数据接受。
再重写其回调函数
// DMA加串口空闲中断
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
if(huart->Instance == USART1)
{
//这里的DMA为Normal模式
//HAL_UART_DMAStop(huart)//如果DMA为Circular模式,回调函数加上stop函数
//USER_FNC();//用户自定义函数
printf("[RX1:] %s\r\n",Rx1Buf);//串口1的dma接受缓存数组为Rx1Buf
memset(Rx1Buf,0x00,sizeof(Rx1Buf));
HAL_UARTEx_ReceiveToIdle_DMA(&huart1,Rx1Buf,sizeof(Rx1Buf));//因为是normal模式再次开启DMA的空闲中断接受
}
}
效果
参考博客