之前参加比赛使用过USART+DMA的串口通信,最近找工作准备在stm32f103上复习下,结果调了一下午都没成功,最后终于成功通信并通过分析HAL源代码找到问题所在。整个过程主要遇到一下问题:
1、在串口中断中调用HAL_Delay()函数会卡死。
原因:是因为HAL_Delay()函数是基于sys_clock中断实现,而这个中断优先级不够导致程序在调用出卡死。解决方法可以修改系统时钟的优先级。
2、在usart1调用DMA发送后必须调用HAL_UART_DMAStop(),不然程序后续无法继续发送。
原因:首先串口配置正常配置,DMA选择普通模式而不是循环模式。此函数源代码如下:
HAL_StatusTypeDef HAL_UART_DMAStop(UART_HandleTypeDef *huart)
{
uint32_t dmarequest = 0x00U;
/* The Lock is not implemented on this API to allow the user application
to call the HAL UART API under callbacks HAL_UART_TxCpltCallback() / HAL_UART_RxCpltCallback():
when calling HAL_DMA_Abort() API the DMA TX/RX Transfer complete interrupt is generated
and the correspond call back is executed HAL_UART_TxCpltCallback() / HAL_UART_RxCpltCallback()
*/
/* Stop UART DMA Tx request if ongoing */
dmarequest = HAL_IS_BIT_SET(huart->Instance->CR3, USART_CR3_DMAT);
if ((huart->gState == HAL_UART_STATE_BUSY_TX) && dmarequest)
{
CLEAR_BIT(huart->Instance->CR3, USART_CR3_DMAT);
/* Abort the UART DMA Tx channel */
if (huart->hdmatx != NULL)
{
HAL_DMA_Abort(huart->hdmatx);
}
UART_EndTxTransfer(huart);
}
/* Stop UART DMA Rx request if ongoing */
dmarequest = HAL_IS_BIT_SET(huart->Instance->CR3, USART_CR3_DMAR);
if ((huart->RxState == HAL_UART_STATE_BUSY_RX) && dmarequest)
{
CLEAR_BIT(huart->Instance->CR3, USART_CR3_DMAR);
/* Abort the UART DMA Rx channel */
if (huart->hdmarx != NULL)
{
HAL_DMA_Abort(huart->hdmarx);
}
UART_EndRxTransfer(huart);
}
return HAL_OK;
}
我们发现此函数会两次判断,首先判断是否发送,然后中断发送,并设置一些标志位,然后判断是否还在接受,后续操作同上。其中非常关键一点是UART_EndRxTransfer()和UART_EndTxTransfer()函数,以UART_EndTxTransfer()函数代码为例(如下),其会在最后面设置huart->RxState = HAL_UART_STATE_READY。没有这句话即使一次数据发生完,其状态并不会改变,而在后续调用的HAL_UART_Receive_DMA()函数首先就会判断huart->RxState状态,如果不是HAL_UART_STATE_READY则直接返回错误(具体可以取看HAL_UART_Transmit_DMA()代码)。所以每次发送完后必须调用HAL_UART_DMAStop()函数,不然只会成功发送第一次数据。
同理,在normal模式下,每调用一次HAL_UART_Receive_DMA()函数也必须调用一次HAL_UART_DMAStop(),不然后续无法成功接受数据。
static void UART_EndRxTransfer(UART_HandleTypeDef *huart)
{
/* Disable RXNE, PE and ERR (Frame error, noise error, overrun error) interrupts */
CLEAR_BIT(huart->Instance->CR1, (USART_CR1_RXNEIE | USART_CR1_PEIE));
CLEAR_BIT(huart->Instance->CR3, USART_CR3_EIE);
/* In case of reception waiting for IDLE event, disable also the IDLE IE interrupt source */
if (huart->ReceptionType == HAL_UART_RECEPTION_TOIDLE)
{
CLEAR_BIT(huart->Instance->CR1, USART_CR1_IDLEIE);
}
/* At end of Rx process, restore huart->RxState to Ready */
huart->RxState = HAL_UART_STATE_READY;
huart->ReceptionType = HAL_UART_RECEPTION_STANDARD;
}
3、第三个问题:使用HAL_UART_Receive_DMA()接受不到数据(DMA还是normal模式),我代码逻辑是初始化调用DMA读取数据,然后在串口UART_FLAG_IDLE中断中读取DMA传输数量再打印到上位机上,结果能正常工作,但是每次DMA数据传输量都为0。
原因:在我的程序逻辑中是一个TIM2每1s产生一次中断,然后触发标志位发送一个句到上位机;同时串口一旦触发UART_FLAG_IDLE也触发标志位将接受内容发送到上位机。
根据问题2我每次发送数据后调用HAL_UART_DMAStop(),这个函数作用会同时关闭huart中Tx和Rx两条DMA通道,所以就当我在等待的时候如果TIM2触发发送就会导致我等待数据过程中Rx的DMA通道关闭,即使触发UART_FLAG_IDLE中断数据进来但是DMA并不会工作,接受缓冲区也就没有数据。解决方法我通过设置一个is_stop标志位,只要一调用HAL_UART_DMAStop()触发标志,然后主程序while循环中再次调用HAL_UART_Receive_DMA()函数开启接受DMA通道。
所以这个问题在于HAL_UART_DMAStop()的调用会关闭usart相关联的DMA,这点很奇怪。
最后还有一点疑问:
我在串口1中断函数中调用HAL_UART_Receive_DMA()函数和在main()函数中调用HAL_UART_Receive_DMA()效果不一样,前者还是无法正常工作,后者可以正常进行下一次接收,忘大佬解答。
最终效果图如下:
我的代码如下:
(7条消息) 基于HAL库的stm32f103单片机实现DMA+串口的接收、发送数据功能代码-单片机文档类资源-CSDN文库