STM32用CubeMx生成的HAL库效率很低,利用HAL自带的串口中断在接收大量数据时很容易出现数据丢失。一般来说,串口所接受的数据长度可能是不固定的。然而在一串数据的连续接收中,串口接收中断内实现的程序应尽可能短小,避免因打断接收过程而产生数据丢失。
本文介绍自己写一个轻量的串口接收中断,使用空闲中断实现效率的大幅提升,并且避免数据丢失。
原理
STM32 的串口中断标记中有
- UART_IT_RXNE:串口非空标记,在接收缓存寄存器中有数据时置位;
- UART_IT_IDLE:串口空闲标记,在一串数据接收完成后置位;
当每接收到一个字符时都会置位UART_IT_RXNE,此时我们将字符存到自定义的数组Buffer中,然后将UART_IT_RXNE清零。当一串字符接收结束后串口进入空闲状态,UART_IT_IDLE则会置位,此时我们将接收到的所有数据进行处理,并将UART_IT_IDLE清零。
发送端每次都发送一串数据,发送这一串数据的各字符之间没有空闲时间,而每串数据结束到接收到下一串数据是有一定空闲时间的。因此,接收端只有在接收完一串数据之后才能检测到空闲,从而触发空闲中断。该方法直接利用数据串之间的空闲来识别数据串的尾部,而非使用如’n’等符号表示数据结尾,也将提高处理的效率。
实现过程
这里是基于CubeMx生成的HAL库代码而改写。
在串口初始化中使能上述中断标记位
__HAL_UART_ENABLE_IT(uartHandle,UART_IT_IDLE);
__HAL_UART_ENABLE_IT(uartHandle,UART_IT_RXNE);
具体代码:
void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
{
GPIO_InitTypeDef GPIO_InitStruct;
if(uartHandle->Instance==USART1)
{
/* USER CODE BEGIN USART1_MspInit 0 */
/* USER CODE END USART1_MspInit 0 */
/* USART1 clock enable */
__HAL_RCC_USART1_CLK_ENABLE();
/**USART1 GPIO Configuration
PA9 ------> USART1_TX
PA10 ------> USART1_RX
*/
GPIO_InitStruct.Pin = GPIO_PIN_9|GPIO_PIN_10;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF7_USART1;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* USART1 interrupt Init */
HAL_NVIC_SetPriority(USART1_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(USART1_IRQn);
/* USER CODE BEGIN USART1_MspInit 1 */
__HAL_UART_ENABLE_IT(uartHandle,UART_IT_IDLE);
__HAL_UART_ENABLE_IT(uartHandle,UART_IT_RXNE);
/* USER CODE END USART1_MspInit 1 */
}
}
改写串口中断函数
在stm32f4xx_it.c里的中断函数USART1_IRQHandler()原本是调用HAL库的HAL_UART_IRQHandler() 函数,这里把这个调用注释掉。
写检查UART_FLAG_RXNE 并存储接收到的字符的代码:
if(__HAL_UART_GET_FLAG(&hirda3, UART_FLAG_RXNE) != RESET)
{
uint8_t ch=(uint16_t) READ_REG(huart1.Instance->DR);
*(pBuff++)=ch;
ctn++;
__HAL_UART_CLEAR_FLAG(&huart1,UART_FLAG_RXNE);
}
写检查UART_FLAG_IDLE 并触发回调函数的代码:
if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE) != RESET)
{
UART1_IDLECallBack(UART1_RxBuff,ctn);
ctn=0;
pBuff=UART1_RxBuff;
__HAL_UART_CLEAR_IDLEFLAG(&huart1);
}
完整代码如下:
extern void UART1_IDLECallBack(uint8_t *buff,uint8_t size);
uint8_t UART1_RxBuff[50];
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
static uint8_t* pBuff=UART1_RxBuff;
static uint8_t ctn=0;
if(__HAL_UART_GET_FLAG(&hirda3, UART_FLAG_RXNE) != RESET)
{
uint8_t ch=(uint16_t) READ_REG(huart1.Instance->DR);
*(pBuff++)=ch;
ctn++;
__HAL_UART_CLEAR_FLAG(&huart1,UART_FLAG_RXNE);
}
if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE) != RESET)
{
UART1_IDLECallBack(UART1_RxBuff,ctn);
ctn=0;
pBuff=UART1_RxBuff;
__HAL_UART_CLEAR_IDLEFLAG(&huart1);
}
/* USER CODE END USART1_IRQn 0 */
//HAL_IRDA_IRQHandler(&huart1);
/* USER CODE BEGIN USART1_IRQn 1 */
/* USER CODE END USART1_IRQn 1 */
}
写回调函数
最后只要在main.c中重定义UART1_IDLECallBack()函数即可。
void IRDA3_IDLECallBack(uint8_t *buff,uint8_t size)
{
HAL_UART_Transmit(&huart1, buff, size, 0xFFFF);
}
上述回调函数内容根据实际情况编写,这里提供的例程是听过串口将受到的一整串数据全部返回。
结束语
简化单个字符接收后的处理过程,即只将字符存入缓存数组,只当整串数据接受完才调用回调函数执行对该串数据的处理。该方法大大提高了串口接收的效率,避免了数据丢失。
STM32用CubeMx生成的HAL库效率很低,利用HAL自带的串口中断在接收大量数据时很容易出现数据丢失。一般来说,串口所接受的数据长度可能是不固定的。然而在一串数据的连续接收中,串口接收中断内实现的程序应尽可能短小,避免因打断接收过程而产生数据丢失。
本文介绍自己写一个轻量的串口接收中断,使用空闲中断实现效率的大幅提升,并且避免数据丢失。
原理
STM32 的串口中断标记中有
- UART_IT_RXNE:串口非空标记,在接收缓存寄存器中有数据时置位;
- UART_IT_IDLE:串口空闲标记,在一串数据接收完成后置位;
当每接收到一个字符时都会置位UART_IT_RXNE,此时我们将字符存到自定义的数组Buffer中,然后将UART_IT_RXNE清零。当一串字符接收结束后串口进入空闲状态,UART_IT_IDLE则会置位,此时我们将接收到的所有数据进行处理,并将UART_IT_IDLE清零。
发送端每次都发送一串数据,发送这一串数据的各字符之间没有空闲时间,而每串数据结束到接收到下一串数据是有一定空闲时间的。因此,接收端只有在接收完一串数据之后才能检测到空闲,从而触发空闲中断。该方法直接利用数据串之间的空闲来识别数据串的尾部,而非使用如’n’等符号表示数据结尾,也将提高处理的效率。
实现过程
这里是基于CubeMx生成的HAL库代码而改写。
在串口初始化中使能上述中断标记位
__HAL_UART_ENABLE_IT(uartHandle,UART_IT_IDLE);
__HAL_UART_ENABLE_IT(uartHandle,UART_IT_RXNE);
具体代码:
void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
{
GPIO_InitTypeDef GPIO_InitStruct;
if(uartHandle->Instance==USART1)
{
/* USER CODE BEGIN USART1_MspInit 0 */
/* USER CODE END USART1_MspInit 0 */
/* USART1 clock enable */
__HAL_RCC_USART1_CLK_ENABLE();
/**USART1 GPIO Configuration
PA9 ------> USART1_TX
PA10 ------> USART1_RX
*/
GPIO_InitStruct.Pin = GPIO_PIN_9|GPIO_PIN_10;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF7_USART1;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* USART1 interrupt Init */
HAL_NVIC_SetPriority(USART1_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(USART1_IRQn);
/* USER CODE BEGIN USART1_MspInit 1 */
__HAL_UART_ENABLE_IT(uartHandle,UART_IT_IDLE);
__HAL_UART_ENABLE_IT(uartHandle,UART_IT_RXNE);
/* USER CODE END USART1_MspInit 1 */
}
}
改写串口中断函数
在stm32f4xx_it.c里的中断函数USART1_IRQHandler()原本是调用HAL库的HAL_UART_IRQHandler() 函数,这里把这个调用注释掉。
写检查UART_FLAG_RXNE 并存储接收到的字符的代码:
if(__HAL_UART_GET_FLAG(&hirda3, UART_FLAG_RXNE) != RESET)
{
uint8_t ch=(uint16_t) READ_REG(huart1.Instance->DR);
*(pBuff++)=ch;
ctn++;
__HAL_UART_CLEAR_FLAG(&huart1,UART_FLAG_RXNE);
}
写检查UART_FLAG_IDLE 并触发回调函数的代码:
if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE) != RESET)
{
UART1_IDLECallBack(UART1_RxBuff,ctn);
ctn=0;
pBuff=UART1_RxBuff;
__HAL_UART_CLEAR_IDLEFLAG(&huart1);
}
完整代码如下:
extern void UART1_IDLECallBack(uint8_t *buff,uint8_t size);
uint8_t UART1_RxBuff[50];
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
static uint8_t* pBuff=UART1_RxBuff;
static uint8_t ctn=0;
if(__HAL_UART_GET_FLAG(&hirda3, UART_FLAG_RXNE) != RESET)
{
uint8_t ch=(uint16_t) READ_REG(huart1.Instance->DR);
*(pBuff++)=ch;
ctn++;
__HAL_UART_CLEAR_FLAG(&huart1,UART_FLAG_RXNE);
}
if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE) != RESET)
{
UART1_IDLECallBack(UART1_RxBuff,ctn);
ctn=0;
pBuff=UART1_RxBuff;
__HAL_UART_CLEAR_IDLEFLAG(&huart1);
}
/* USER CODE END USART1_IRQn 0 */
//HAL_IRDA_IRQHandler(&huart1);
/* USER CODE BEGIN USART1_IRQn 1 */
/* USER CODE END USART1_IRQn 1 */
}
写回调函数
最后只要在main.c中重定义UART1_IDLECallBack()函数即可。
void IRDA3_IDLECallBack(uint8_t *buff,uint8_t size)
{
HAL_UART_Transmit(&huart1, buff, size, 0xFFFF);
}
上述回调函数内容根据实际情况编写,这里提供的例程是听过串口将受到的一整串数据全部返回。
结束语
简化单个字符接收后的处理过程,即只将字符存入缓存数组,只当整串数据接受完才调用回调函数执行对该串数据的处理。该方法大大提高了串口接收的效率,避免了数据丢失。
举报