串口通讯是每一位学习STM32开发的朋友必定绕不开的知识点,它不仅是单片机与电脑、传感器、蓝牙模块等外设通信的最基础桥梁,更是我们调试程序、打印日志、观察变量、排查bug的核心工具 —— 可以说,没有串口,我们就像失去了和单片机 “对话” 的眼睛,绝大多数嵌入式开发工作都无法顺利开展。
对于STM32开发者而言,串口的学习路径清晰且层层递进:从最基础的GPIO模拟串口通信,到使用芯片内置的硬件USART/UART外设,再到配置中断、DMA实现高效收发,最后对接蓝牙、GPS、WiFi等串口外设做实际项目,每一步都是嵌入式开发的核心基本功。
在STM32 中,串口数据的读取与处理方式通常分为轮询接收、中断接收以及DMA接收三种。实际开发中,所有串口接收的数据处理逻辑,均基于这三种方式展开。
本期我们来详细介绍这三种不同的接收方案实现以及它们的优劣。
1、轮询接收
HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
轮询接收的串口函数为上述HAL_UART_Receive,需要接收四个参数:串口句柄、接收缓存区、接收长度、等待时间。
轮询接收的方式会让MCU一直等待接收缓存区收到指定长度数据或者达到等待时间后才会退出等待。
HAL_UART_Receive(&huart1,buf,1,HAL_MAX_DELAY);HAL_GPIO_WritePin(GPIOC,GPIO_PIN_All,1);//LED灯组全灭HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,1);//打开锁存器HAL_GPIO_WritePin(GPIOC,buf[0]<<8,0);//写入LEDHAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,0);//关闭锁存器buf[0]=0;//清空缓存区
例如图中所示代码,当我们使用轮询函数接收串口信息,等待时间采用HAL_MAX_Delay时,系统将一直等待用户输入,才会执行下方程序。
2、中断接收
轮询方式需要CPU停下操作等待接收完成或者超出等待时间,这会极大的耽误其他任务的使用,因此我们需要了解中断接收的方式:
当我们开启串口的中断后,我们就可以开启中断接收,串口中断接收的原型函数如下:
HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
中断接收函数需要三个参数:串口句柄、串口接收缓存区还有接收长度,当串口接收到指定长度的数据后将会触发中断进入对应中断回调函数:
voidHAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
我们可以在串口接收中断完成数据处理、重启串口接收的操作:
voidHAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){if(huart->Instance == USART1){HAL_GPIO_WritePin(GPIOC,GPIO_PIN_All,1);HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,1);HAL_GPIO_WritePin(GPIOC,buf[0]<<8,0);HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,0);/*需要重新开启中断接收*/HAL_UART_Receive_IT(&huart1,buf,1);}}
在初始化中需要第一次开启接收中断,之后每接收到指定个数的字符后(文中是1个)都会进入中断函数,进入中断函数后,我们可以进行字符处理,之后别忘记在中断函数中再次开启中断接收。
3、DMA接收
中断接收的方式虽然可以解决CPU等待串口消息的缺点,可是当大量串口信息需要被接收的时候,会导致系统频繁进入中断来处理所得数据。因此使用串口配合DMA在批量数据处理中显得格外有效。
在STM32中的DMA中可以设置为串口接收和DMA通道相绑定,DMA模式选择循环模式则在每次传输到缓存区尽头的时候从头开始传输。
HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
串口DMA接收的函数有三个参数传入,分别是串口句柄,接收缓存区,接收缓存区长度。
在DMA的Normal模式下,接收数据长度到达预定长度后将会停止DMA传输。
在DMA的Circule模式下,接收数据长度到达预定长度后,DMA会回到缓存区的开头,从头开始覆盖接收,这涉及到了DMA环形存储区的设计。
HAL_UART_Receive_DMA(&huart1,buf,1);/* USER CODE END 2 *//* Infinite loop *//* USER CODE BEGIN WHILE */while (1){HAL_GPIO_WritePin(GPIOC,GPIO_PIN_All,1);HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,1);HAL_GPIO_WritePin(GPIOC,buf[0]<<8,0);HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,0);HAL_UART_Receive_IT(&huart1,buf,1);/* USER CODE END WHILE *//* USER CODE BEGIN 3 */}
在这段演示代码中,我们只需要第一次开启DMA的环形接收,演示代码中仅有存储区长度为1,这样子我们就可以无需二次开启接收,一直接收数据来覆盖buf数组。
但是还是需要提及的是,DMA真正的优势是大批量数据传输中极大的解放CPU占用和中断次数,在小规模应用中优势并不明显。
4、总结
当然在STM32中,串口的接收只是其中一个步骤,如何处理接收到的信息才是重中之重,无论是轮询还是中断都有一个缺点:只能接收定长的数据,对于不定长的数据帧处理起来极难,需要自己去定义每次数据的结尾去判断。
DMA可以高效的处理大量数据传输,但是环形存储区会导致数据覆盖等问题出现,如何及时安排数据处理,解决环形存储区覆盖问题这些都是串口通讯中会碰到的问题,因此真正的掌握串口通信的使用仍然任重道远,需要大家不断的学习。
546