一 查询模式
待定,日后补充
二 中断模式
1 中断接收
1.1中断接收流程
先在启动文件中找到中断向量表(此处以USART1为例子)
然后找到USART1_IRQHandler的函数定义
在这里就会发现又跳转到了 HAL_UART_IRQHandler(&huart1);,追进去为下图所示:
在图中标记的前三步是读取寄存器SR、CR1和CR3中的值。
第四步就是提取出SR(状态寄存器)中的各个错误标志位,然后赋给errorflags,如果errorflags为0 ,就会执行下列程序
/* UART in mode Receiver -------------------------------------------------*/
if (((isrflags & USART_SR_RXNE) != RESET) && ((cr1its & USART_CR1_RXNEIE) != RESET))
{
UART_Receive_IT(huart);
return;
}
其中判断句就是说:此时USART_SR_RXNE已经置1,表示接收缓冲区已有数据,USART_SR_RXNE置1是靠硬件置1,而清零是只要读取DR寄存器,该位就会清零;USART_CR1_RXNEIE已置1,表示当USART_SR_RXNE置1的时候,且该标志位也提前置1时,那么当接收缓冲区有数据的时候就会进入接收中断。我们就可以在中断函数中对接收缓冲区中的数据进行读操作。USART_SR_RXNE的置1和清零都需要依靠软件进行实施。因此如果想要使用中断法接收数据,就需要在初始化时同时将CR1->RXNEIE置为高电平。
之后就会执行发送函数 UART_Receive_IT(huart),并且返回空值;反之,就是进行下面的错误分析,并将相应的错误信息进行返回。详细代码见如下所示:
/**
* @brief This function handles UART interrupt request.
* @param huart Pointer to a UART_HandleTypeDef structure that contains
* the configuration information for the specified UART module.
* @retval None
*/
void HAL_UART_IRQHandler(UART_HandleTypeDef *huart)
{
uint32_t isrflags = READ_REG(huart->Instance->SR);
uint32_t cr1its = READ_REG(huart->Instance->CR1);
uint32_t cr3its = READ_REG(huart->Instance->CR3);
uint32_t errorflags = 0x00U;
uint32_t dmarequest = 0x00U;
/* If no error occurs */
errorflags = (isrflags & (uint32_t)(USART_SR_PE | USART_SR_FE | USART_SR_ORE | USART_SR_NE));
if (errorflags == RESET)
{
/* UART in mode Receiver -------------------------------------------------*/
if (((isrflags & USART_SR_RXNE) != RESET) && ((cr1its & USART_CR1_RXNEIE) != RESET))
{
UART_Receive_IT(huart);
return;
}
}
/* If some errors occur */
if ((errorflags != RESET) && (((cr3its & USART_CR3_EIE) != RESET) || ((cr1its & (USART_CR1_RXNEIE | USART_CR1_PEIE)) != RESET)))
{
/* UART parity error interrupt occurred ----------------------------------*/
if (((isrflags & USART_SR_PE) != RESET) && ((cr1its & USART_CR1_PEIE) != RESET))
{
huart->ErrorCode |= HAL_UART_ERROR_PE;
}
/* UART noise error interrupt occurred -----------------------------------*/
if (((isrflags & USART_SR_NE) != RESET) && ((cr3its & USART_CR3_EIE) != RESET))
{
huart->ErrorCode |= HAL_UART_ERROR_NE;
}
/* UART frame error interrupt occurred -----------------------------------*/
if (((isrflags & USART_SR_FE) != RESET) && ((cr3its & USART_CR3_EIE) != RESET))
{
huart->ErrorCode |= HAL_UART_ERROR_FE;
}
/* UART Over-Run interrupt occurred --------------------------------------*/
if (((isrflags & USART_SR_ORE) != RESET) && ((cr3its & USART_CR3_EIE) != RESET))
{
huart->ErrorCode |= HAL_UART_ERROR_ORE;
}
/* Call UART Error Call back function if need be --------------------------*/
if (huart->ErrorCode != HAL_UART_ERROR_NONE)
{
/* UART in mode Receiver -----------------------------------------------*/
if (((isrflags & USART_SR_RXNE) != RESET) && ((cr1its & USART_CR1_RXNEIE) != RESET))
{
UART_Receive_IT(huart);
}
/* If Overrun error occurs, or if any error occurs in DMA mode reception,
consider error as blocking */
dmarequest = HAL_IS_BIT_SET(huart->Instance->CR3, USART_CR3_DMAR);
if (((huart->ErrorCode & HAL_UART_ERROR_ORE) != RESET) || dmarequest)
{
/* 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;
if (HAL_DMA_Abort_IT(huart->hdmarx) != HAL_OK)
{
/* Call Directly 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 */
}
}
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;
}
}
return;
} /* End if some error occurs */
/* UART in mode Transmitter ------------------------------------------------*/
if (((isrflags & USART_SR_TXE) != RESET) && ((cr1its & USART_CR1_TXEIE) != RESET))
{
UART_Transmit_IT(huart);
return;
}
/* UART in mode Transmitter end --------------------------------------------*/
if (((isrflags & USART_SR_TC) != RESET) && ((cr1its & USART_CR1_TCIE) != RESET))
{
UART_EndTransmit_IT(huart);
return;
}
}
到此为止,如果没有接收异常发生,接收中断回调函数就会执行 UART_Receive_IT(huart),发送中断回调函数就会执行 UART_Transmit_IT(huart)和
UART_EndTransmit_IT(huart),对于此处的讲解详见中断发送章节。
在接收数据的时候,校验位是不需要接收的,所以相比于无校验位而言就会少接收一位。因此对于九字节宽的数据,无校验位就依旧是接收九位数据,因此每次就选用占用两个字节,而对于有校验位就只需要占用一个字节;但我们通常使用的是八位宽无校验位的,所以每次接收一个字节,而对于八位宽有校验位而言,每次实际上接收的是七位宽。
到此处我们就可以看出,HAL库实际上是接收一个字节 ,当RxXferCount递减为0时,才会调用最终我们寻找的接收中断回调函数HAL_UART_RxCpltCallback(huart),其中RxXferCount和pRxBuffPtr的赋值均是在
HAL_UART_Receive_IT中给定的。而最值的我们关注的一点是,在调用该中断回调函数之前先关闭了串口接收中断,也就是说,在一次串口中断接收过程的最后,即串口接收完一组数据之后会关闭串口接收中断。(这个后面还会再讲,先记住。完整代码如下所示:
static HAL_StatusTypeDef UART_Receive_IT(UART_HandleTypeDef *huart)
{
uint16_t *tmp;
/* Check that a Rx process is ongoing */
if (huart->RxState == HAL_UART_STATE_BUSY_RX)
{
if (huart->Init.WordLength == UART_WORDLENGTH_9B)
{
tmp = (uint16_t *) huart->pRxBuffPtr;
if (huart->Init.Parity == UART_PARITY_NONE)
{
*tmp = (uint16_t)(huart->Instance->DR & (uint16_t)0x01FF);
huart->pRxBuffPtr += 2U;
}
else
{
*tmp = (uint16_t)(huart->Instance->DR & (uint16_t)0x00FF);
huart->pRxBuffPtr += 1U;
}
}
else
{
if (huart->Init.Parity == UART_PARITY_NONE)
{
*huart->pRxBuffPtr++ = (uint8_t)(huart->Instance->DR & (uint8_t)0x00FF);
}
else
{
*huart->pRxBuffPtr++ = (uint8_t)(huart->Instance->DR & (uint8_t)0x007F);
}
}
if (--huart->RxXferCount == 0U)
{
/* Disable the UART Data Register not empty Interrupt */
__HAL_UART_DISABLE_IT(huart, UART_IT_RXNE);
/* Disable the UART Parity Error Interrupt */
__HAL_UART_DISABLE_IT(huart, UART_IT_PE);
/* Disable the UART Error Interrupt: (Frame error, noise error, overrun error) */
__HAL_UART_DISABLE_IT(huart, UART_IT_ERR);
/* Rx process is completed, restore huart->RxState to Ready */
huart->RxState = HAL_UART_STATE_READY;
#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 */
return HAL_OK;
}
return HAL_OK;
}
else
{
return HAL_BUSY;
}
}
总结一下串口中断接收流程:
USART1_IRQHandler(void) -> HAL_UART_IRQHandler(UART_HandleTypeDef *huart) -> UART_Receive_IT(UART_HandleTypeDef *huart) -> HAL_UART_RxCpltCallback(huart);
其中HAL_UART_RxCpltCallback(huart)才是用户需要在主函数中进行重写的回调函数,也就是说,进入该函数之前,数据已经接收完,并且关闭了串口接收中断。
1.2使用接收中断方式
不论啥设备,只要用到中断,就一定记得先使能NVIC,否则中断无法使用。
在cube中配置完了之后并没有使能串口中断(有一个串口初始化函数,但是在这个函数中并未使能串口中断)需要用户手动使能。使能代码如下:
HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
我的使用方式,在初始化中调用此函数进行打开中断,如下:
uint8_t rev_data ;
static void MX_USART1_UART_Init(void)
{
/* USER CODE BEGIN USART1_Init 0 */
/* USER CODE END USART1_Init 0 */
/* USER CODE BEGIN USART1_Init 1 */
/* USER CODE END USART1_Init 1 */
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart1) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN USART1_Init 2 */
HAL_UART_Receive_IT(&huart1, &rev_data, 1);
/* USER CODE END USART1_Init 2 */
}
其中在 HAL_UART_Receive_IT()中有这三句代码就是打开接收中断相关的标志位,三句代码如下所示:
/* Enable the UART Parity Error Interrupt */
__HAL_UART_ENABLE_IT(huart, UART_IT_PE);
/* Enable the UART Error Interrupt: (Frame error, noise error, overrun error) */
__HAL_UART_ENABLE_IT(huart, UART_IT_ERR);
/* Enable the UART Data Register not empty Interrupt */
__HAL_UART_ENABLE_IT(huart, UART_IT_RXNE);
其中
#define UART_IT_RXNE ((uint32_t)(UART_CR1_REG_INDEX << 28U | USART_CR1_RXNEIE))//使能RXNEIE
实现的功能就是PC机给串口发送数据,串口会先接收到,然后再通过串口中断服务函数返回刚刚发送的数据,在回调函数中的代码示意,如下所示:
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART1)
{
HAL_UART_Transmit(&huart1,&rev_data, 1, 1000);//
HAL_UART_Receive_IT(&huart1, &rev_data, 1); // 需要重新注册,因为在进入中断回调函数之前已将中断标志位清零
}
}
其中,如果想每发送两个数据或n个数据才进一次中断,那就需要首先将re_data变为数据uint8_t re_data[n-1] ;然后回调函数中的代码示意为:
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART1)
{
HAL_UART_Transmit(&huart1,rev_data, n, 1000);//次处n为常数,表示每接收n个数据才进一次中断,然后将这n个数据再发送出去
HAL_UART_Receive_IT(&huart1, rev_data, n); // 需要重新注册,因为在进入中断回调函数之前已将中断标志位清零
}
}
2中断发送
中断发送的意思,非常类似于中断接收,但其中有一些不同。如果在初始化函数中调用了如下所示的函数:
HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
在该函数中会执行如下代码:
/* Enable the UART Transmit data register empty Interrupt */
__HAL_UART_ENABLE_IT(huart, UART_IT_TXE);
#define UART_IT_TXE ((uint32_t)(UART_CR1_REG_INDEX << 28U | USART_CR1_TXEIE)) //开启TXEIE,靠软件进行置位和清零
那么当调用HAL_UART_Transmit_IT发送数据的时候,就会进入发送中断函数,见下图:
先满足①的条件,所以先执行①,在UART_Transmit_IT中,将数据写入DR寄存器,并且使能TC标志位,同时会关闭UART_IT_TXE。之后因为在UART_Transmit_IT中开启了UART_IT_TC,代码如下:
__HAL_UART_ENABLE_IT(huart, UART_IT_TC);
#define UART_IT_TC ((uint32_t)(UART_CR1_REG_INDEX << 28U | USART_CR1_TCIE)) //也就是使能了TCIE
USART_SR_TC是靠硬件置位,也就是当发送完数据后硬件会自定将该位置1(通过读取SR寄存器或向DR寄存器写值就可以清零)<此处感觉有误,待修正>,所以接下来就是进入②中,在②中就有最终需要我们用户调用的,发送中断回调函数 HAL_UART_TxCpltCallback(huart)。
由此可以看出,在进入发送中断回调函数前,数据已经发送出去,而且在进入发送中断回调函数前已经将TCIE标志位清零。详细代码如下所示:
static HAL_StatusTypeDef UART_EndTransmit_IT(UART_HandleTypeDef *huart)
{
/* Disable the UART Transmit Complete Interrupt */
__HAL_UART_DISABLE_IT(huart, UART_IT_TC);//TCIE标志位清零
/* Tx process is ended, restore huart->gState to Ready */
huart->gState = HAL_UART_STATE_READY;
#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
/*Call registered Tx complete callback*/
huart->TxCpltCallback(huart);
#else
/*Call legacy weak Tx complete callback*/
HAL_UART_TxCpltCallback(huart);
#endif /* USE_HAL_UART_REGISTER_CALLBACKS */
return HAL_OK;
}
发送中断回调函数非常类似于中断接收使能的函数。接收中断使能函数的作用是绑定接收缓存区并使能接收中断,但是对于发送;发送中断使能函数的作用是发送指定长度的指定数据并使能发送中断,可判断是否发送完成。
比如有一个unsigned char 数组a[10],HAL_UART_Transmit_IT(&huart1, a, 10),这一句的意思是用usart2(串口2)发送a数组中的10个数据,然后使能发送中断。
当发送完成之后(或者发送一半,发送一半也有个中断)就会执行回调函数。
总结一下发送中断:
使用HAL_UART_Transmit_IT函数发送指定长度的数据,并使能发送中断,发送到一半和发送结束会触发中断(相关的回调函数是HAL_UART_TxHalfCpltCallback()和HAL_UART_TxCpltCallback())中断触发后发送中断使能会被清除,然后调用回调函数,回调函数执行完成之后结束本次发送。
三 补充
中断事件 | 事件标志 | 使能控制位 |
---|---|---|
发送数据寄存器为空 | TXE | TXEIE |
CTS标志 | CTS | CTSIE |
发送完成 | TC | TCIE |
准备好读取接收到的数据 | RXNE | RXNEIE |
检测到上溢错误 | ORE | RXNEIE |
检测到空闲线路 | IDLE | IDLEIE |
奇偶校验错误 | PE | PEIE |
断路标志 | LBD | LBDIE |
多缓冲通信中的噪声标志、上溢错误和帧错误 | NF/ORE/FE | EIE |