3. UART驱动实战:STM32 HAL库轮询模式收发数据
UART,说白了就是串口。做嵌入式开发的人,没人离得开它。我最早接触单片机时,第一个调通的外设就是UART——看着串口助手打印出"Hello World",那种感觉,嗯,至今还记得。
这一章咱们就动手实战。用STM32的HAL库,把UART轮询模式跑起来。你跟着我一步步来,很快就能看到效果。
3.1 轮询模式是什么?
轮询模式,就是CPU一直盯着某个标志位,等它变化。比如发数据时,等发送完成标志位置位;收数据时,等接收完成标志位置位。
说白了,就是"死等"。CPU在这期间啥也不干,就干等。
轮询模式的特点:
- 代码简单,容易理解
- 适合短数据、低频通信
- CPU占用率高,不能做其他事
- 实时性差,不适合高速或大数据量场景
我在项目中遇到过一个小白同事,用轮询模式跑115200波特率,一次发几百字节,结果整个系统卡死了。后来改成中断模式才解决。所以你要记住:轮询模式适合小数据量,别贪多。
3.2 STM32CubeMX配置UART
咱们用STM32F103C8T6做演示。先打开CubeMX,配置UART1。
配置步骤:
- 选择芯片型号:STM32F103C8Tx
- 在Pinout & Configuration页面,找到USART1
- Mode选择Asynchronous(异步模式)
- Parameter Settings里设置:
- Baud Rate:115200 Bits/s
- Word Length:8 Bits
- Parity:None
- Stop Bits:1
- NVIC Settings里,暂时不使能中断(轮询模式不需要)
- 生成代码
小提示:波特率115200是常用值。如果通信不稳定,可以降到9600试试。我调试时经常先设9600,通了再往上提。
3.3 HAL库UART轮询API
HAL库给我们封装好了三个轮询函数。你记住它们就行:
| 函数名 | 功能 | 超时参数 |
|---|---|---|
HAL_UART_Transmit |
发送数据 | 超时时间(ms) |
HAL_UART_Receive |
接收数据 | 超时时间(ms) |
HAL_UART_Transmit_IT |
中断发送(本章不用) | — |
函数原型长这样:
// 发送:pData是数据指针,Size是字节数,Timeout是超时时间
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart,
uint8_t *pData,
uint16_t Size,
uint32_t Timeout);
// 接收:同样参数结构
HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart,
uint8_t *pData,
uint16_t Size,
uint32_t Timeout);
返回值是HAL_OK表示成功,HAL_TIMEOUT表示超时,HAL_ERROR表示出错。
3.4 实战:发送"Hello UART"
好,咱们写代码。打开main.c,在main函数里加上发送代码。
#include "main.h"
#include "usart.h"
#include "gpio.h"
uint8_t txBuffer[] = "Hello UART!\r\n";
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
while (1)
{
// 发送数据,超时时间1000ms
HAL_UART_Transmit(&huart1, txBuffer, sizeof(txBuffer) - 1, 1000);
// 延时1秒
HAL_Delay(1000);
}
}
编译下载,打开串口助手。你应该能看到每隔1秒打印一次"Hello UART!"。
注意:sizeof(txBuffer) - 1是为了去掉字符串结尾的'\0'。如果你不减1,会多发一个空字符,串口助手可能显示乱码。我刚开始学时就踩过这个坑。
3.5 实战:接收并回显
光发不收,那是单工。咱们来个回显程序:收到什么,就发回什么。
uint8_t rxByte;
while (1)
{
// 接收1个字节,超时时间1000ms
if (HAL_UART_Receive(&huart1, &rxByte, 1, 1000) == HAL_OK)
{
// 收到数据,原样发回
HAL_UART_Transmit(&huart1, &rxByte, 1, 1000);
}
}
你往串口助手发一个字符,它就会回显一个字符。这就是最简单的"串口回环"。
为什么会这样?因为HAL_UART_Receive在收到数据前会一直等(轮询),直到超时。收到后立即发回,然后继续等下一个字节。
3.6 轮询模式的坑
我用轮询模式踩过的坑,给你列出来:
- 超时时间设太短:如果对方回复慢,你的程序会反复超时退出。我建议设1000ms起步。
- 接收卡死:如果对方不发数据,HAL_UART_Receive会一直等。整个程序就卡在那了。所以轮询模式不适合做"等待不确定时长"的接收。
- 数据粘包:轮询模式一次收一个字节,如果对方连续发多个字节,你可能只收到第一个,后面的丢了。解决办法是用中断或DMA。
我的建议:轮询模式只用在"发"的场景,或者"收固定长度数据"的场景。不确定长度的接收,别用轮询。
3.7 知识结构图
下面这张图,帮你理清UART轮询模式的核心逻辑:
3.8 完整示例代码
最后,给你一个完整的收发示例。这个程序会先发提示信息,然后等待用户输入,收到后回显并加上换行。
/* main.c - UART轮询模式收发示例 */
#include "main.h"
#include "usart.h"
#include "gpio.h"
uint8_t prompt[] = "Please input a character:\r\n";
uint8_t rxByte;
uint8_t txPrefix[] = "You sent: ";
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
// 发送提示信息
HAL_UART_Transmit(&huart1, prompt, sizeof(prompt) - 1, 1000);
while (1)
{
// 接收1个字节
if (HAL_UART_Receive(&huart1, &rxByte, 1, 5000) == HAL_OK)
{
// 发送前缀
HAL_UART_Transmit(&huart1, txPrefix, sizeof(txPrefix) - 1, 1000);
// 发送收到的字符
HAL_UART_Transmit(&huart1, &rxByte, 1, 1000);
// 发送换行
uint8_t newline[] = "\r\n";
HAL_UART_Transmit(&huart1, newline, 2, 1000);
}
else
{
// 超时处理
uint8_t timeoutMsg[] = "Timeout! No data received.\r\n";
HAL_UART_Transmit(&huart1, timeoutMsg, sizeof(timeoutMsg) - 1, 1000);
}
}
}
调试技巧:如果串口助手收不到数据,先检查这几项:波特率是否一致?串口线是否接反(TX接RX,RX接TX)?地线是否共地?我调试时至少有一半的问题出在这些地方。