一、介绍
串口的传输方式包括:轮询、中断DMA,在此要介绍的是关于HAL库底层串口接收中断流程的讲解,包括串口错误的处理,中断回调函数以及错误中断回调函数的执行。
二、配置流程
首先使用STM32CubeMX配置串口参数和工作方式。如下图:
配置好基础参数波特率和数据长度,校验位,停止位后, 选择NVIC Settings点击Enabled使能全局中断。
这样整个串口配置就完成了。
三、开启接收中断
在代码初始化调用HAL_UART_Receive_IT(&huart2, &gUart2.Temp, RECLEN)函数即可开启接收中断,这边gUart2结构体是自己定义用来存放串口接收的内容,RECLEN则指一次性接收几个字节后触发中断回调函数。这边先介绍一下串口底层数据的存放。串口收到一个字节数据时最先存放到移位寄存器内,然后移到RDR寄存器中。当RDR寄存器有值时则RXNE标志置1(指示接收非空),这时将RXNEIE标志置1则会触发中断。执行HAL_UART_Receive_IT函数的作用就是指定接收数据长度,存放的地址以及开启接收非空中断,还有接收函数的映射。
四、中断流程
当接收到一个字节的数据时进入了中断处理函数,由于这边使用的是USART2,所以进入的是USART2_IRQHandler:
void USART2_IRQHandler(void)
{
/* USER CODE BEGIN USART2_IRQn 0 */
/* USER CODE END USART2_IRQn 0 */
HAL_UART_IRQHandler(&huart2);
/* USER CODE BEGIN USART2_IRQn 1 */
/* USER CODE END USART2_IRQn 1 */
}
紧接着进入HAL_UART_IRQHandler里面执行,点进去可以发现里面在做的是一些状态位读取以及判断,还有中断回调函数接收函数的入口。如下读取ISR状态寄存器以及CR1控制寄存器还有CR3控制寄存器的内容分别存放到isrflags、cr1its、cr3its。然后让状态寄存器去与上以下四种错误PE奇偶校验错误、FE帧错误、ORE溢出错误、NE噪声错误。查看状态位是否有以下四种错误。
uint32_t isrflags = READ_REG(huart->Instance->ISR);
uint32_t cr1its = READ_REG(huart->Instance->CR1);
uint32_t cr3its = READ_REG(huart->Instance->CR3);
uint32_t errorflags;
uint32_t errorcode;
/* If no error occurs */
errorflags = (isrflags & (uint32_t)(USART_ISR_PE | USART_ISR_FE | USART_ISR_ORE | USART_ISR_NE));
接着根据无错误和有错误分成两种情况执行。
1)无错误时,判断RXNE是否置1(RDR寄存器收到值后硬件置1),中断开启标志RXNEIE是否打开,满足的话再去判断函数指针RxISR是否非空这函数指针在执行HAL_UART_Receive_IT时将huart->RxISR 函数指针映射到了UART_RxISR_8BIT(数据位为8位)。这时直接进入UART_RxISR_8BIT函数做接收处理。
if (errorflags == 0U)
{
/* UART in mode Receiver ---------------------------------------------------*/
if (((isrflags & USART_ISR_RXNE) != 0U)
&& ((cr1its & USART_CR1_RXNEIE) != 0U))
{
if (huart->RxISR != NULL)
{
huart->RxISR(huart);
}
return;
}
}
if ((huart->Init.WordLength == UART_WORDLENGTH_9B) && (huart->Init.Parity ==
UART_PARITY_NONE))
{
huart->RxISR = UART_RxISR_16BIT;
}
else
{
huart->RxISR = UART_RxISR_8BIT;
}
一进来之后首先去读RDR寄存器的值存放在临时变量里然后让这临时变量与上事先配置的掩码则会得到真实的数据,这边的机制博主暂时也还没弄懂,反正结果是这样操作之后能得到真实的数据。然后让存放数据的指针指向下一块地址作好下一个数据的存放并对自己事先设定的接收长度减1。当接收长度不为0时,一直反复执行如上过程进中断->进中断接收函数存放数据。当接收长度为0时,执行关闭接收中断等操作并且调用HAL_UART_RxCpltCallback即中断回调函数(这边没有自己定义USE_HAL_REGISTER_CALLBACKS,所以会执行此函数)。
if (huart->RxState == HAL_UART_STATE_BUSY_RX)
{
uhdata = (uint16_t) READ_REG(huart->Instance->RDR);
*huart->pRxBuffPtr = (uint8_t)(uhdata & (uint8_t)uhMask);
huart->pRxBuffPtr++;
huart->RxXferCount--;
if (huart->RxXferCount == 0U)
{
/* Disable the UART Parity Error Interrupt and RXNE interrupts */
CLEAR_BIT(huart->Instance->CR1, (USART_CR1_RXNEIE | USART_CR1_PEIE));
/* Disable the UART Error Interrupt: (Frame error, noise error, overrun error) */
CLEAR_BIT(huart->Instance->CR3, USART_CR3_EIE);
/* Rx process is completed, restore huart->RxState to Ready */
huart->RxState = HAL_UART_STATE_READY;
/* Clear RxISR function pointer */
huart->RxISR = NULL;
#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
/*Call registered Rx complete callback*/
huart->RxCpltCallback(huart);
#else
/*Call legacy weak Rx complete callback*/
HAL_UART_RxCpltCallback(huart);
#endif /* USE_HAL_UART_REGISTER_CALLBACKS */
}
}
HAL_UART_RxCpltCallback(huart)函数在系统内部是个虚函数即可以重写,一般都是用户自己重新定义实现处理接收内容。如要处理一整帧数据,对帧数据头判断校验判断等,注意要在里面开启接收中断,因为在执行完接收长度后中断关闭了,这边需要打开才能保证下次数据的接收。
2)有错误时,先判断错误中断有没有开启或者接收非空中断还有奇偶校验中断有没有开启,有的话则进入错误处理。
if ((errorflags != 0U)
&& (((cr3its & USART_CR3_EIE) != 0U)
|| ((cr1its & (USART_CR1_RXNEIE | USART_CR1_PEIE)) != 0U)))
这边查询出错的标志,以及对应错误的中断是否开启,如果开启则去清除错误标志,并将错误状态保存到ErrorCode变量中。
/* UART parity error interrupt occurred -------------------------------------*/
if (((isrflags & USART_ISR_PE) != 0U) && ((cr1its & USART_CR1_PEIE) != 0U))
{
__HAL_UART_CLEAR_FLAG(huart, UART_CLEAR_PEF);
huart->ErrorCode |= HAL_UART_ERROR_PE;
}
/* UART frame error interrupt occurred --------------------------------------*/
if (((isrflags & USART_ISR_FE) != 0U) && ((cr3its & USART_CR3_EIE) != 0U))
{
__HAL_UART_CLEAR_FLAG(huart, UART_CLEAR_FEF);
huart->ErrorCode |= HAL_UART_ERROR_FE;
}
/* UART noise error interrupt occurred --------------------------------------*/
if (((isrflags & USART_ISR_NE) != 0U) && ((cr3its & USART_CR3_EIE) != 0U))
{
__HAL_UART_CLEAR_FLAG(huart, UART_CLEAR_NEF);
huart->ErrorCode |= HAL_UART_ERROR_NE;
}
/* UART Over-Run interrupt occurred -----------------------------------------*/
if (((isrflags & USART_ISR_ORE) != 0U)
&& (((cr1its & USART_CR1_RXNEIE) != 0U) ||
((cr3its & USART_CR3_EIE) != 0U)))
{
__HAL_UART_CLEAR_FLAG(huart, UART_CLEAR_OREF);
huart->ErrorCode |= HAL_UART_ERROR_ORE;
}
然后判断ErrorCode的状态如果不为HAL_UART_ERROR_NONE,即有错误的话,会先去读取出错时RDR寄存器中的值,然后读取错误存于errorcode变量中根据错误种类分情况处理。
if (((isrflags & USART_ISR_RXNE) != 0U)
&& ((cr1its & USART_CR1_RXNEIE) != 0U))
{
if (huart->RxISR != NULL)
{
huart->RxISR(huart);
}
}
/* If Overrun error occurs, or if any error occurs in DMA mode reception,
consider error as blocking */
errorcode = huart->ErrorCode;
这里DMA的一个情况与ORE错误放在同一个地方处理,有ORE错误时首先执行UART_EndRxTransfer这函数里面会做中断标志的清除,也就是说进入ORE错误如果没有处理接收中断就会给关闭。然后这边进入的是else分支直接执行HAL_UART_ErrorCallback,一般在里面开启接收中断保证出错时串口还能再接收。
if ((HAL_IS_BIT_SET(huart->Instance->CR3, USART_CR3_DMAR)) ||
((errorcode & HAL_UART_ERROR_ORE) != 0U))
{
/* Blocking error : transfer is aborted
Set the UART state ready to be able to start again the process,
Disable Rx Interrupts, and disable Rx DMA request, if ongoing */
UART_EndRxTransfer(huart);
/* Disable the UART DMA Rx request if enabled */
if (HAL_IS_BIT_SET(huart->Instance->CR3, USART_CR3_DMAR))
{
CLEAR_BIT(huart->Instance->CR3, USART_CR3_DMAR);
/* Abort the UART DMA Rx channel */
if (huart->hdmarx != NULL)
{
/* Set the UART DMA Abort callback :
will lead to call HAL_UART_ErrorCallback() at end of DMA abort procedure */
huart->hdmarx->XferAbortCallback = UART_DMAAbortOnError;
/* Abort DMA RX */
if (HAL_DMA_Abort_IT(huart->hdmarx) != HAL_OK)
{
/* Call Directly huart->hdmarx->XferAbortCallback function in case of error */
huart->hdmarx->XferAbortCallback(huart->hdmarx);
}
}
else
{
/* Call user error callback */
#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
/*Call registered error callback*/
huart->ErrorCallback(huart);
#else
/*Call legacy weak error callback*/
HAL_UART_ErrorCallback(huart);
#endif /* USE_HAL_UART_REGISTER_CALLBACKS */
}
}
else
{
/* Call user error callback */
#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
/*Call registered error callback*/
huart->ErrorCallback(huart);
#else
/*Call legacy weak error callback*/
HAL_UART_ErrorCallback(huart);
#endif /* USE_HAL_UART_REGISTER_CALLBACKS */
}
}
如果不是ORE错误则只执行HAL_UART_ErrorCallback,和清除ErrorCode状态,也就是除ORE错误以外的PE、FE、NE错误是不会影响接收中断的。
else
{
/* Non Blocking error : transfer could go on.
Error is notified to user through user error callback */
#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
/*Call registered error callback*/
huart->ErrorCallback(huart);
#else
/*Call legacy weak error callback*/
HAL_UART_ErrorCallback(huart);
#endif /* USE_HAL_UART_REGISTER_CALLBACKS */
huart->ErrorCode = HAL_UART_ERROR_NONE;
}
五、总结
以上总结了HAL库关于串口接收中断的处理流程,可以帮助读者更好的理解串口底层的动作,如有异议,还请提出指教!