1. UART通信基础:从协议到实战

各位同学好,我是你们的老朋友。今天咱们正式开始《单片机UART硬件驱动开发与寄存器编程实战》的第一章。

UART这个东西,说白了就是让两个设备之间能“说上话”。我做了十几年嵌入式,接触过的通信协议少说也有几十种,但UART始终是用的最多的那个。为什么?因为它简单、可靠、几乎每个单片机都自带。

这一章,咱们先把UART通信的底子打牢。你只有搞懂了数据是怎么在线上跑的,后面写驱动、调寄存器才不会发懵。

1.1 什么是UART协议?

UART的全称是Universal Asynchronous Receiver/Transmitter,通用异步收发器。它定义了一套规则,让两个设备通过一根发送线(TX)和一根接收线(RX)进行串行通信。

注意,UART本身是硬件模块,它负责把并行数据转成串行比特流发出去,再把收到的串行比特流转回并行数据。而协议,就是约定好这些比特怎么排列、什么时候开始、什么时候结束。

核心特点:

  • 异步:没有独立的时钟线,靠双方约定的波特率同步
  • 全双工:发送和接收可以同时进行
  • 点对点:通常只连接两个设备
  • 串行:数据逐位传输,节省引脚

我刚开始做项目时,总觉得异步通信不靠谱——没有时钟线,两边怎么对齐?后来调通了第一个UART驱动,看到示波器上波形严丝合缝,才明白这个设计有多巧妙。

1.2 异步串行通信原理

异步通信的核心思想是:双方各自用自己的时钟,但约定好相同的速率。

发送方在TX线上逐位发送数据,接收方在RX线上采样。没有时钟线,那接收方怎么知道什么时候该采样?答案是:靠起始位和波特率。

我画了一张图,帮你理解这个过程:

UART异步串行通信原理 发送方 TX 接收方 RX TX → RX 本地时钟 9600Hz 本地时钟 9600Hz 数据帧格式(9600波特率,8N1) 起始位(1bit) + 数据位(8bits) + 停止位(1bit) = 10bits/帧 每比特时间 = 1 / 9600 ≈ 104μs 每帧时间 = 10 × 104μs ≈ 1.04ms

你看,发送方和接收方各自有独立的时钟,但都配置成9600波特率。发送方按这个速率逐位发送,接收方也按这个速率采样。只要两边时钟误差在允许范围内(通常±2%以内),通信就能正常进行。

我的经验:实际项目中,晶振精度和温度漂移都会影响时钟。我曾经在一个户外项目中,白天40度、晚上零下10度,UART通信时好时坏。后来发现是晶振温漂导致波特率偏差过大。从那以后,我只要做宽温产品,都会选用±25ppm以内的晶振。

1.3 数据帧格式详解

UART的数据帧,就是一次完整传输的基本单位。它由几个固定部分组成:

字段 长度 电平状态 作用
空闲位 不定 高电平(1) 线路空闲时的默认状态
起始位 1 bit 低电平(0) 通知接收方“准备接收数据”
数据位 5~9 bits 高/低电平 实际传输的数据,通常LSB先发
校验位 0/1 bit 奇/偶校验 可选,用于检错
停止位 1/1.5/2 bits 高电平(1) 表示一帧结束,给接收方处理时间

咱们逐个拆开看:

起始位

线路空闲时是高电平。发送方把电平拉低,这就是起始位。接收方检测到这个下降沿,就知道“数据要来了”。

我习惯在示波器上看起始位——那个下降沿特别明显。如果波形上找不到下降沿,说明两边根本没对上。

数据位

起始位之后,就是真正的数据。通常是5到9位,最常用的是8位(正好一个字节)。数据按低位在前(LSB First)的顺序发送。

举个例子,你要发送0x55(二进制01010101),实际线上发送的顺序是:1→0→1→0→1→0→1→0。为什么?因为LSB是第0位,先发。

注意:有些老设备或特殊协议会使用MSB First,但绝大多数UART都是LSB First。如果你发现通信数据全是反的,先检查一下位顺序配置。

校验位

校验位是可选的,用于检测传输错误。有两种模式:

  • 奇校验(Odd):数据位中1的个数 + 校验位 = 奇数
  • 偶校验(Even):数据位中1的个数 + 校验位 = 偶数

比如数据是0x55(二进制01010101,有4个1),如果选偶校验,校验位就是0(4+0=4,偶数);如果选奇校验,校验位就是1(4+1=5,奇数)。

说实话,现在大多数应用都不用校验位了。因为UART本身误码率很低,而且上层协议通常有更可靠的校验机制(比如CRC)。我一般只在长距离传输或噪声环境恶劣时才启用校验位。

停止位

数据位(和校验位)发完后,发送方把电平拉高并保持至少1个比特时间。这就是停止位。它给接收方留出时间处理刚收到的数据,准备接收下一帧。

停止位可以是1位、1.5位或2位。1.5位比较少见,我几乎没用过。2位停止位常用于低速设备或机械式设备,给它们多一点处理时间。

1.4 波特率概念

波特率(Baud Rate)就是每秒传输的符号数。在UART中,一个符号就是一个比特,所以波特率就等于比特率(bps)。

常见的波特率有:

波特率(bps) 每比特时间 典型应用场景
2400 416.7 μs 低速设备、老旧设备
9600 104.2 μs 最常用,兼容性好
115200 8.68 μs 高速通信,调试输出
921600 1.09 μs 高速数据传输,短距离

为什么是这些数字?它们都是从系统时钟分频得来的。比如一个16MHz的单片机,要得到9600波特率,需要分频系数 = 16,000,000 / (16 × 9600) ≈ 104.17。取整后就是104,实际波特率 = 16,000,000 / (16 × 104) ≈ 9615,误差只有0.16%,完全可用。

波特率误差公式:

实际波特率 = 系统时钟频率 / (16 × 分频系数)
误差 = |实际波特率 - 目标波特率| / 目标波特率 × 100%

一般要求误差 < 2%,否则可能丢数据。

我建议新手从9600开始。这个速率足够慢,示波器上波形清晰,调试方便。等调通了再逐步提高速率。我曾经见过有人一上来就设921600,结果死活调不通,最后发现是线太长信号衰减了。

1.5 常见配置组合

UART的配置通常写成“波特率-数据位-校验位-停止位”的格式,比如:

  • 9600-8-N-1:9600波特率,8位数据,无校验,1位停止位(最常用)
  • 115200-8-E-1:115200波特率,8位数据,偶校验,1位停止位
  • 4800-7-O-2:4800波特率,7位数据,奇校验,2位停止位

我90%的项目都用9600-8-N-1。简单、可靠、兼容性好。除非有特殊需求,否则别搞花里胡哨的配置。

避坑指南:我曾经在一个项目里,设备A用9600-8-N-1,设备B用9600-8-E-1。两边波特率一样,但校验位不匹配。结果数据能收到,但全是乱码。我查了整整两天才找到原因。所以,通信前一定要确认两边配置完全一致。

1.6 本章小结

这一章咱们把UART通信的基础过了一遍。你知道了:

  • UART是异步串行通信,没有时钟线
  • 数据帧由起始位、数据位、校验位、停止位组成
  • 波特率决定了传输速度,两边必须一致
  • 最常用的配置是9600-8-N-1

这些概念看起来简单,但它们是后面所有内容的基础。下一章,咱们会深入UART硬件模块的内部结构,看看那些寄存器到底是怎么工作的。

记住,搞嵌入式,基础不牢,地动山摇。把这些概念吃透了,后面写驱动就是水到渠成的事。