一直都是在CSDN上学习,也没发过什么文章,最近做项目时遇到了串口接收的问题,一直没找到解决方案,后来自己做了一种解决方案,索性发出来共大家参考,共同学习。
背景概述
之前用串口通信的接收都是用的DMA+空闲中断,以触发空闲中断视为接收完一帧数据,同时获取接收长度,这样接收数据很方便,尤其是接收不定长的数据。这里就不说太多了,大家想必都清楚,而且搜索“DMA 空闲中断 接收不定长数据”会有很多文章讲解。
但是最近遇到了特殊一个特殊情况,一个串口透传的无线模块,它无线接收到数据后,通过串口传给单片机时,本该完整的一帧数据它竟然是断开传的,例如它发出的数据是:
0xAA 0xBB 0xCC 0xDD 0xEE 0xFF
它实际是先发送0xAA,然后延时了30ms左右,再发的0xBB 0xCC 0xDD 0xEE 0xFF,这就导致了它发完0xAA后出发了单片机的一次空闲中断,然后后面的数据再进来之后又触发一次空闲中断,所以每次一帧接收的数据都不完整,判断帧头和校验都没法处理。
我很不喜欢用HAL_UART_Receive_IT函数来接收数据,每来一个字节都要进一次中断,太麻烦,就喜欢接收完完整的一帧数据后统一处理。
在网上搜了很久,没找到想要的解决方案,基本都是用HAL_UART_Receive_IT接收的,由于硬件工程师的强迫症精神,必须要接收完完整的一帧数据后统一处理,自己做了一个方案,还是用DMA+空闲中断接收,完成接收不连续的一帧数据。
仅供参考,不完善的地方希望大家多多指点。
方案概述
其实很简单,以往的DMA+空闲中断接收是这样实现的,在串口中断函数中写入以下代码:
void USART2_IRQHandler(void)
{
/* USER CODE BEGIN USART2_IRQn 0 */
uint32_t RX_Recv_Len_temp=0;
if(__HAL_UART_GET_FLAG(&huart2,UART_FLAG_IDLE) != RESET)
{
HAL_UART_DMAStop(&huart2);
__HAL_UART_CLEAR_IDLEFLAG(&huart2);
RX_Recv_Len_temp = __HAL_DMA_GET_COUNTER(&hdma_usart2_rx);
USART2_Recv_Len = USART2_Recv_Len_MAX - RX_Recv_Len_temp;
USART2_Recv_Finish_Flag = 1;
}
/* USER CODE END USART2_IRQn 0 */
HAL_UART_IRQHandler(&huart2);
/* USER CODE BEGIN USART2_IRQn 1 */
/* USER CODE END USART2_IRQn 1 */
}
以上代码实现了接收一帧数据后,获取接收长度,并置位接收完成标志。
如果接收的数据不连续,在这个中断里就不处理数据了,而是这样:
void USART2_IRQHandler(void)
{
/* USER CODE BEGIN USART2_IRQn 0 */
if(__HAL_UART_GET_FLAG(&huart2,UART_FLAG_IDLE) != RESET)
{
__HAL_UART_CLEAR_IDLEFLAG(&huart2);
TIM14->CNT = 0;
HAL_TIM_Base_Start_IT(&htim14);
}
/* USER CODE END USART2_IRQn 0 */
HAL_UART_IRQHandler(&huart2);
/* USER CODE BEGIN USART2_IRQn 1 */
/* USER CODE END USART2_IRQn 1 */
}
是的,清除空闲中断后打开一个定时器中断,定时器定时100ms,如果100ms内再没有接收到数据且触发空闲中断,才视为接收完成,然后在定时器的回调函数内处理接收的数据:
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
uint32_t RX_Recv_Len_temp=0;
if(htim == &htim14)
{
HAL_UART_DMAStop(&huart2);
HAL_TIM_Base_Stop_IT(&htim14);
RX_Recv_Len_temp = __HAL_DMA_GET_COUNTER(&hdma_usart2_rx);
USART2_Recv_Len = USART2_Recv_Len_MAX - RX_Recv_Len_temp;
USART2_Recv_Finish_Flag = 1;
}
}
在定时器中断内获取接收长度,并置位接收完成标志。
这样就避免了数据的不连续问题,同样还是用DMA接收,不会产生太多的中断。
在主函数内通过判断接收完成标志来执行进一步的操作:
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
if(USART2_Recv_Finish_Flag != 0)
{
USART2_Recv_Finish_Flag = 0;
if(USART2_Recv_Len > 0)
{
/* 这里增加串口数据处理或相关的执行代码 */
/**********************************/
// memset(USART2_RX_Data, 0, sizeof USART2_RX_Data); //数组清零
USART2_Recv_Len = 0;
HAL_UART_Receive_DMA(&huart2, USART2_RX_Data, USART2_Recv_Len_MAX); //重新打开DMA接收
}
}
/* USER CODE END 3 */
}
定时器的时间设置为比不连续的两个字节间的间隔时间稍长一些即可,根据实际情况决定。
方案讲解完毕。