UART基础:异步串行通信原理

UART,全称是Universal Asynchronous Receiver/Transmitter,通用异步收发器。说白了,它就是让两个设备之间,只用一根数据线就能互相传数据的接口。我刚开始接触单片机时,觉得这东西挺神奇的——一根线怎么能既发又收?后来才明白,它其实是靠约定好的“节奏”来工作的。

UART通信的核心,就是“异步”两个字。没有时钟线,全靠双方事先约定好速率。你想想看,两个人隔空喊话,得先定好语速,不然一个说得快一个听得慢,肯定乱套。UART也是这个道理。

核心要点:UART是异步、全双工、串行通信协议。只需要TX(发送)、RX(接收)两根线,外加共地。

一帧数据的结构

UART传输的基本单位叫“帧”。一帧数据,就像一列火车,由好几节车厢组成。我习惯把这几个部分记成:起始位、数据位、校验位、停止位。

UART 一帧数据格式 空闲位 起始位 数据位(5~8位) 校验位 停止位 说明: • 空闲位:总线为高电平,表示没有数据传输 • 起始位:拉低电平,告诉接收方“准备接收数据” • 数据位:LSB在前,MSB在后,逐位传输 • 停止位:拉高电平,表示一帧传输结束

起始位:通信的“发令枪”

起始位是UART通信的起点。平时总线处于空闲状态,电平是高的。当发送方要发数据时,先把电平拉低一个位的时间。这个下降沿,就是告诉接收方:“注意,我要开始发了!”

我在项目中遇到过一个问题:两个设备通信时,偶尔会出现第一个字节丢失的情况。查了半天,发现是接收方在初始化时,RX引脚电平不稳定,误把噪声当成了起始位。后来我加了一个去抖延时,问题就解决了。

我的经验:起始位的下降沿非常关键。如果信号线上有毛刺,接收方可能误触发。建议在硬件上加一个RC低通滤波,或者在软件里做多次采样确认。

数据位:真正要传的内容

起始位之后,就是数据位了。数据位可以是5到8位,最常见的是8位。为什么是8位?因为一个字节就是8位,传ASCII字符刚刚好。

数据位的传输顺序是LSB(最低有效位)在前,MSB(最高有效位)在后。举个例子,你要发送0x55(二进制01010101),先发最低位1,再发0,再发1……最后发最高位0。

// 发送一个字节的示例(伪代码)
void uart_send_byte(uint8_t data) {
    // 发送起始位:拉低TX
    TX_LOW();
    delay_bit_time();
    
    // 发送8位数据,LSB first
    for (int i = 0; i < 8; i++) {
        if (data & 0x01) {
            TX_HIGH();
        } else {
            TX_LOW();
        }
        delay_bit_time();
        data >>= 1;  // 右移,准备下一位
    }
    
    // 发送停止位:拉高TX
    TX_HIGH();
    delay_bit_time();
}

注意:数据位的位数必须收发双方一致。你设8位,对方设7位,那收到的数据全是错的。我曾经在调试一个GPS模块时犯过这个错,折腾了两天才发现是数据位不匹配。

校验位:数据完整性的“小保镖”

校验位是可选的,用来检测数据传输过程中有没有出错。常用的校验方式有三种:

校验方式 原理 特点
无校验(None) 不发送校验位 效率最高,但无检错能力
奇校验(Odd) 数据位+校验位中1的个数为奇数 能检测奇数个位错误
偶校验(Even) 数据位+校验位中1的个数为偶数 能检测奇数个位错误

举个例子:你要发送0x55(二进制01010101),里面有4个1。如果用偶校验,校验位就是0,因为4已经是偶数了。如果用奇校验,校验位就是1,凑成5个1。

说实话,在短距离通信中,校验位的作用有限。我做过一个项目,两个板子距离不到10厘米,用无校验跑了半年,一次错都没出过。但如果是长线通信或者工业环境,校验位还是很有必要的。

停止位:一帧的“句号”

数据位(和校验位)发完之后,发送方把电平拉高,持续1位、1.5位或2位的时间。这个高电平就是停止位。它有两个作用:

  • 告诉接收方:这一帧结束了,你可以处理数据了
  • 给下一帧做准备:总线回到空闲状态,等待下一个起始位

停止位的长度可以配置。1位停止位最常见,2位停止位用在慢速设备或噪声环境中。1.5位比较少见,我做了这么多年嵌入式,只用过一次——跟一个老旧的工业仪表通信时遇到的。

关键参数:UART通信必须确保收发双方的波特率、数据位、校验位、停止位完全一致。这四个参数,少一个都不行。

波特率:通信的“语速”

波特率就是每秒传输的位数,单位是bps(bits per second)。常见的波特率有9600、19200、38400、115200等。

为什么是这些奇怪的数字?因为它们都是从1.8432MHz晶振分频得来的。我刚开始学的时候也觉得奇怪,后来做产品选型时才发现,这些标准波特率几乎所有的MCU都支持,兼容性最好。

波特率越高,传输越快,但误码率也越高。我个人的经验是:

  • 9600 bps:稳定可靠,适合长线(10米以上)
  • 115200 bps:常用调试波特率,适合短距离
  • 1M bps以上:需要精心设计PCB,线长尽量控制在10厘米以内

避坑指南:我曾经在一个项目里用了921600 bps的波特率,结果在高温测试时频繁出错。后来降到115200,问题就消失了。高温下晶振频率会漂移,高速通信对时钟精度要求更高。

UART通信的完整流程

把上面这些串起来,一次完整的UART传输是这样的:

  1. 总线空闲,电平为高
  2. 发送方拉低电平,产生起始位
  3. 逐位发送数据位(LSB first)
  4. 如果启用了校验位,发送校验位
  5. 发送方拉高电平,产生停止位
  6. 接收方检测到停止位,将收到的数据存入寄存器
  7. 如果还有数据要发,重复步骤2-6

接收方是怎么知道什么时候采样数据的?它内部有一个波特率发生器,在每一位的中间时刻采样。比如9600 bps,每一位大约是104微秒,接收方在52微秒时采样,这样最稳定。

UART 接收采样时序 TX: 采样 采样 采样 采样 采样 采样 采样 起始位 数据位(8位) 停止位 采样策略: • 接收方在每个数据位的中间时刻采样,避开电平跳变区域 • 常用方法:16倍过采样,取中间3个采样点做多数表决

UART的硬件连接

UART的硬件连接很简单:

  • TX:发送引脚,接对方的RX
  • RX:接收引脚,接对方的TX
  • GND:共地,必须连接

注意,TX和RX要交叉连接。我见过不少新手把TX接TX、RX接RX,结果怎么都通不了。嗯,这个坑我也踩过。

电平匹配:MCU的UART通常是3.3V或5V的TTL电平。如果跟RS232设备通信,需要加电平转换芯片(如MAX3232)。直接连接可能会烧坏IO口,我有个同事就干过这事。

总结

UART的原理其实不复杂,就是按约定的节奏,一根线发、一根线收。关键是把起始位、数据位、校验位、停止位和波特率这五个参数配置对。只要收发双方参数一致,通信就能跑起来。

在实际项目中,我建议你先把波特率设低一点(比如9600),确认通信正常后再慢慢提高。这样排查问题会容易很多。