目录
为什么要使用串口不定长接收
普通的阻塞接收函数
HAL_UART_Receive(UART_HandleTypeDef * huart, uint8_t * pData, uint16_t Size,uint32_t Timeout)
huart:句柄
pData:盛放接收数据的变量
Size:接收数据的大小
中断接收函数
HAL_UART_Receive_IT(UART_HandleTypeDef * huart, uint8_t * pData, uint16_t Size)
huart:句柄
pData:盛放接收数据的变量
Size:接收数据的大小
不论是阻塞接收还是中断接收都有一个共性,就是定长接收,参数Size就是期望接收数据的长度,当串口发送长度不固定的数据时,会出现吞字符的现象,使得接收信息不完整(使用定长接收方式接收不定长数据的效果如下图所示)。我们为了保证下位机接收到准确的指令,常需要串口可以自由接收不定长度的数据。一般需要用串口空闲中断来实现功能。
什么是串口空闲中断
串口空闲中断是指当串口接收缓冲区中没有数据时,串口控制器产生的一种中断信号。在串口通信中,当接收到一个完整的数据帧后,通常会有一个停止位,表示数据的传输结束。当接收缓冲区中没有数据时,串口控制器会检测到停止位的连续空闲状态,并产生空闲中断信号。使用串口空闲中断可以消除轮训接收缓冲区的需要,提高系统的性能和效率。
注意:
(1)空闲中断的允许需要手动解除中断屏蔽
(2)空闲中断必须手动清零,不然中断会一直置位,系统会不断进入中断处理函数
通过串口空闲中断实现不定长接收
每次接收一个字符触发中断,接收中断保存当前接收到的字符到一个接收缓冲区,当下一个字符来临,继续将字符保存到数组的下一个元素。
当接收完所有数据时触发空闲中断,在空闲中断中那个保存数组就是接收到的数据。然后就可以对这个数组进行判断发送等操作了。
步骤:
1、main.c 内添加全局变量
uint8_t buf[1]={0}; // 用于存放单次接收的字符
2、主函数中使能串口空闲中断并开启接收中断
__HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE); //使能串口空闲中断
HAL_UART_Receive_IT(&huart1,buf,1); //开启接收中断,每次只接收一个字符
3、stm32xxxx_it.c 内添加全局变量
uint8_t len=0; // 接收计数器
uint8_t buf_recv[256]; // 接收缓冲区,用于存储完整数据帧
extern uint8_t buf[1]; // 用于存放接收的单个字符
4、在串口中断服务函数(USART1_IRQHandler)中添加如下代码:
当有数据到来时,将字节依次存入 buf_recv
当数据停止传输(产生空闲中断),处理已接收的完整数据帧并重置接收状态
记得添加头文件 #include<string.h>
/* USER CODE BEGIN USART1_IRQn 0 */
if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE)) // 检查是否发生空闲中断
{
__HAL_UART_CLEAR_FLAG(&huart1,UART_CLEAR_IDLEF); // 清除空闲中断标志
/**** 在此区域处理接收到的完整数据
我这里把接收到的数据回传只是为了功能测试,一般实际项目打印不会出现在中断函数(打印相对耗时)
HAL_UART_Transmit(&huart1,buf_recv,sizeof(buf_recv),1000);
****/
len=0; // 重置接收计数器
memset(buf_recv,0,sizeof(buf_recv)); // 清空接收缓冲区
}else // 普通接收中断处理
{
buf_recv[len]=USART1->RDR; // 将接收到的单个字节存入缓冲区
len++; // 递增接收计数器
HAL_UART_Receive_IT(&huart1,buff,1); // 重新开启接收中断,准备接收下一个字节
}
/* USER CODE END USART1_IRQn 0 */
串口空闲中断+DMA方式(推荐)
传统中断方式的缺点:
每接收一个字节都会触发中断,CPU需要频繁响应,导致效率低下。在高波特率(如115200、1Mbps)下,CPU可能被大量中断占用,无法处理其他任务。
DMA方式的优势:
数据传输完全由DMA控制器管理,CPU仅在数据接收完成(或缓冲区满)时被通知,可以专注于其他任务(如算法处理、协议解析等)。适用于高波特率、大数据量的串口通信(如GPS模块、无线通信、高速数据采集)。
步骤:
1.配置STM32CubeMX
2、在main.c定义全局变量
uint8_t buf_ctl[256]; // 接收数据缓冲区,大小按需求自定义即可
3、主函数中开启串口空闲中断,使能串口DMA接收
__HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE); //开启串口空闲中断
HAL_UART_Receive_DMA(&huart1,buf_ctl,256); //接收DMA使能
4、修改stm32xxxx_it.c里的串口中断服务函数
添加全局变量
extern uint8_t buf_ctl[256]; // 接收数据缓冲区
uint8_t len=0; // 记录实际接收长度
在串口中断服务函数(USART1_IRQHandler)处理串口空闲中断,停止DMA传输,计算接收长度处理完整数据,重启DMA准备接收下一帧数据。
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE)) // 获得串口空闲中断标志
{
__HAL_UART_CLEAR_FLAG(&huart1,UART_CLEAR_IDLEF); // 清除空闲中断标志
HAL_UART_DMAStop(&huart1); // 停止DMA传输,防止数据覆盖
/**** 在此区域处理接收到的完整数据,我这里把接收到的数据回传只是为了功能测试,一般实际项目打印不会出现在中断函数(打印相对耗时)
len = 256 - hdma_usart1_rx.Instance->CNDTR; // 计算实际接收的数据长度
buf_ctl[len]='\0'; // 在接收数据的末尾添加字符串结束符(适用于ASCII数据)
HAL_UART_Transmit(&huart1,buf_ctl,len+1,500); // 将接收数据回传,ASCII文本发送长度len+1,二进制数据发送长度len
****/
memset(buf_ctl,0,sizeof(buf_ctl)); // 清空接收缓冲区
HAL_UART_Receive_DMA(&huart1, buf_ctl, 256); // 重新开启DMA,准备接收下一帧数据。
}
/* USER CODE END USART1_IRQn 0 */
HAL_UART_IRQHandler(&huart1);
/* USER CODE BEGIN USART1_IRQn 1 */
/* USER CODE END USART1_IRQn 1 */
}
效果展示
其他备注:
建议在实际开发中参考具体型号的参考手册和HAL库头文件,以确认正确的标志位操作方式。