4、串口通信(UART):掌握UART协议,用串口打印“Hello World”,实现电脑与单片机对话。
好,咱们进入第四课。这节课要聊的是串口通信,也就是UART。
说实话,UART是我在嵌入式开发中用得最多的通信接口,没有之一。调试、日志、数据采集、甚至固件升级,都离不开它。你想想看,一个单片机连上电脑,通过串口就能“说话”,是不是很酷?
4.1 什么是UART?
UART的全称是Universal Asynchronous Receiver/Transmitter,通用异步收发器。说白了,就是一种串行通信协议。
什么叫串行?就是数据一位一位地传。就像排队过安检,一次只能过一个人。与之对应的是并行通信,一次传8位、16位,但线多、成本高、抗干扰差。
UART的特点就是“异步”——没有时钟线。收发双方靠约定的波特率来同步。我刚开始学的时候,总觉得没有时钟线怎么对得上?后来才明白,只要两边速度一致,就能在正确的时间点采样数据。
核心要点:UART只需要两根线——TX(发送)和RX(接收)。交叉连接:单片机的TX接电脑的RX,单片机的RX接电脑的TX。地线也要连,GND对GND。
4.2 UART的数据帧格式
UART传输一帧数据,通常包含以下几个部分:
- 起始位:1位,逻辑0。告诉接收方“我要开始发了”。
- 数据位:5~9位,常用8位。就是你要传的字节。
- 校验位:可选,奇校验或偶校验。用于简单的错误检测。
- 停止位:1位或2位,逻辑1。表示这一帧结束。
举个例子,你发送字符'A'(ASCII码0x41,二进制01000001),格式为8N1(8数据位、无校验、1停止位),波形是这样的:
空闲(高电平)→ 起始位(低)→ 数据位(LSB先发)→ 停止位(高)→ 空闲
嗯,这里要注意:数据是从最低位(LSB)开始发的。我第一次用逻辑分析仪抓波形时,按MSB顺序读,结果数据全反了,折腾了半天。
4.3 波特率是什么?
波特率就是每秒传输的比特数,单位bps。常见的波特率有:9600、19200、38400、115200。
为什么是这些数字?因为晶振频率分频出来的。比如11.0592MHz晶振,分频后能得到精确的9600。如果用12MHz晶振,算出来的9600会有误差,时间长了就会丢数据。
我的经验:调试阶段用9600最稳,抗干扰强。量产时如果数据量大,可以上115200。但别用太冷门的波特率,比如14400,很多USB转串口工具不支持。
4.4 硬件连接
你需要准备:
- 一块单片机开发板(比如STM32或51单片机)
- 一个USB转TTL模块(CH340、CP2102都行)
- 几根杜邦线
连接方式:
单片机 TX → USB转TTL RX
单片机 RX → USB转TTL TX
单片机 GND → USB转TTL GND
千万别把TX和TX接一起,那是“两个人在同时说话,谁也听不清”。我曾经犯过这个错,焊好板子发现收不到数据,查了半天才发现线接反了。
4.5 代码实现:打印“Hello World”
咱们以STM32为例,用HAL库实现。先初始化UART:
// 初始化UART1,波特率115200,8N1
UART_HandleTypeDef huart1;
void MX_USART1_UART_Init(void)
{
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
HAL_UART_Init(&huart1);
}
然后发送数据:
#include <stdio.h>
// 重定向printf到串口
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 1000);
return ch;
}
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_USART1_UART_Init();
while (1)
{
printf("Hello World!\r\n");
HAL_Delay(1000); // 每秒打印一次
}
}
把程序下载到单片机,打开串口助手(比如SSCOM、Putty),设置波特率115200,就能看到“Hello World!”一行一行地跳出来。
注意:printf默认是阻塞的,如果发送缓冲区满了,程序会卡住。在正式项目中,建议用DMA或中断方式发送。不过咱们学习阶段,阻塞方式够用了。
4.6 实现电脑与单片机对话
光会打印还不够,咱们要让电脑和单片机“聊起来”。
思路很简单:单片机收到什么,就原样发回去。这就是“回声”程序。
uint8_t rx_buffer;
while (1)
{
// 接收一个字节,超时时间100ms
if (HAL_UART_Receive(&huart1, &rx_buffer, 1, 100) == HAL_OK)
{
// 收到数据,原样发回
HAL_UART_Transmit(&huart1, &rx_buffer, 1, 1000);
}
}
在串口助手里输入“A”,你会看到“A”被发回来。输入“Hello”,也会收到“Hello”。
为什么会这样?因为单片机把接收到的每个字节都立即转发回去了。这就是最简单的“对话”。
4.7 避坑指南
我这些年踩过的坑,列出来给你参考:
- 波特率不匹配:两边设置不一样,收到的是乱码。先检查这个。
- 忘记共地:GND没连,信号没有参考电平,数据肯定不对。
- TX/RX接反:这个最常见,交换一下试试。
- 串口助手设置错误:数据位、停止位、校验位要一致。8N1是默认配置。
- 电平不匹配:单片机是3.3V,USB转TTL模块也要选3.3V的。5V的模块可能会烧坏单片机引脚。
我的调试习惯:先用逻辑分析仪抓波形,看有没有数据出来。如果有波形但电脑收不到,问题在USB转TTL模块或驱动。如果没波形,问题在单片机程序。这样能快速定位。
4.8 进阶:中断方式接收
上面的轮询方式有个问题:单片机一直在等数据,干不了别的事。实际项目中,我们通常用中断接收。
// 开启中断接收
HAL_UART_Receive_IT(&huart1, &rx_buffer, 1);
// 中断回调函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART1)
{
// 处理收到的数据
// 比如:把数据存到环形缓冲区
// 然后重新开启中断接收
HAL_UART_Receive_IT(&huart1, &rx_buffer, 1);
}
}
这样,单片机在等待数据时可以去做其他任务,收到数据后自动进入中断处理。效率高得多。
4.9 小结
这节课我们学了:
- UART的基本原理:异步、串行、两根线
- 数据帧格式:起始位、数据位、校验位、停止位
- 波特率的概念和选择
- 硬件连接方法
- 用代码实现打印和回声对话
- 常见问题和调试技巧
串口通信是嵌入式开发的“基本功”,就像学编程要先学“Hello World”一样。把它练熟了,后面的传感器数据读取、WiFi模块通信都会轻松很多。
下一节课,咱们要聊ADC——模数转换器。到时候就能把气象站的传感器数据读进来了。做好准备!