利用stm32串口空闲中断接收不定长数据
在做项目中,stm32的串口应用常用来作为单片机和模块之间的通信。比如单片机和gsm模块通信,单片机和gps模块的通信。那么和这些模块就用到了串口的协议解析。那么问题来了,现在基于stm32的Hal库把串口接收函数进行了封装,不管是非中断模式,中断模式,还是dma模式都是接收固定数量byte的数据。但是在协议处理的过程中单片机很多时候是不知道模块发过来的数据是有多少byte的。
我之前大部分做的时候是用中断来一个数据接收一个数据放在缓冲区,然后过一个时间片段进行分析接收到的数据。但是这种模式效率低下,还可能有丢数据的风险。最近在调试一个串口协议程序,看stm参考手册发现一个以前没有去关注过的空闲中断,觉得这个可以大大的派上了用场。随即在百度上进行了搜索,原来已经有很多小伙伴这样用过。但是在网上的资料往往都是千篇一律,一篇文章被转过来转过去。基本上都是空闲中断加DMA的方式来操作。
我后来自己实践了,不需要dma,只用空闲中段加接收完成中段就可以完成接收一帧不定长的数据。
我试验的平台是stm32F407,其他stm32型号都类似。基于hal库的封装还是很统一的。使用stm32cubemx进行配置。串口配置很简单,如下:
打开串口
配置串口参数:
打开串口中断:
其他配置的我就不啰嗦了,然后生成mdk工程。在mdk中我们就重点看下stm32Fxxx_it.c文件中关于串口中断这块代码
1 2 3 4 5 6 7 8 9 |
void USART1_IRQHandler(void) { /* USER CODE BEGIN USART1_IRQn 0 */ /* USER CODE END USART1_IRQn 0 */ HAL_UART_IRQHandler(&huart1); /* USER CODE BEGIN USART1_IRQn 1 */ /* USER CODE END USART1_IRQn 1 */ } |
在中断服务函数里面调用了HAL_UART_IRQHandler(&huart1),这个也是st hal库封装的基础。而我要做的就是不再用st对中断的封装而自己去处理。于是乎我进行了改装,如下:
1 2 3 4 5 6 7 8 9 |
void USART1_IRQHandler(void) { /* USER CODE BEGIN USART1_IRQn 0 */ user_Uart1Handler(); /* USER CODE END USART1_IRQn 0 */ /* USER CODE BEGIN USART1_IRQn 1 */ /* USER CODE END USART1_IRQn 1 */ } |
我把官方的中断函数换成了自己的user_Uart1Handler() 。之后串口的中断都有我这个函数来处理了。具体内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
void user_Uart1Handler() { if((__HAL_UART_GET_FLAG(&huart1,UART_FLAG_RXNE)!=RESET)) { if(uart1RxState==UART_RX_STATE_READY) //接收到一帧中的第一个字节 { uart1RxState=UART_RX_STATE_START; __HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE); //打开空闲中断 uart1RxCounter=0; //计数清零 uart1RxBuf[uart1RxCounter]=(uint8_t)(huart1.Instance->DR & (uint8_t)0x00FF); uart1RxCounter++; } else if(uart1RxState==UART_RX_STATE_START) { uart1RxBuf[uart1RxCounter]=(uint8_t)(huart1.Instance->DR & (uint8_t)0x00FF); uart1RxCounter++; } __HAL_UART_CLEAR_FLAG(&huart1,UART_FLAG_RXNE); } if((__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE)!=RESET)) //进入空闲中断 { __HAL_UART_DISABLE_IT(&huart1,UART_IT_IDLE); //关了空闲中断 __HAL_UART_DISABLE_IT(&huart1,UART_IT_RXNE); //关了接收完成中断 uart1RxState=UART_RX_STATE_DEAL; //状态表明一帧数据接收完成了,需要处理。处理完以后再把接收中断打开 } } |
除了这里的改造外还需要在void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)这个函数里面添加一句:打开串口接收中断的函数。
1 2 3 |
/* USER CODE BEGIN USART1_MspInit 1 */ __HAL_UART_ENABLE_IT(&huart1,UART_IT_RXNE); /* USER CODE END USART1_MspInit 1 */ |
好了,这样通过通过空闲中段接收一帧数据的完整工程就建好了。之后就要放大招解释整个的思路了:
首先我在初始化的时候调用__HAL_UART_ENABLE_IT(&huart1,UART_IT_RXNE);打开了串口的接收中断。注意这个时候我还没有打开空闲中断。而是在接收到了一个byte以后打开空闲中断。
就这样会一直接收数据一帧的其他数据,一帧接受完以后就会出现一个空闲的时间。这个时候空闲中断就会发生。这时候我就把空闲中断和接收中断都关了。存在buf区的数据就是完整的一帧数据。
处理完一帧数据以后我再把串口中断打开重复上面的流程,就可以完整的接收一帧一帧的数据。同时利用空闲中断也可以省去很多的的判断。
空闲中断到底是在什么时候发生?我刚开始还理解错了,以为一上电初始化的时候打开空闲中断,假如我没有收到数据就会进入空闲中断。实际上不是这样的,空闲中断是在收到数据之后再次出现空闲的时候就会触发。所以在利用空闲中断的时候不用担心一上电就会触发了他。不过我例程里面是接收到数据以后才打开,这种情况也不用考虑了。
在主函数中需要用UART_RECEIVE_IT()指定接收的数据长度么?
按照我这种思路操作的话就不需要调用那个函数了