1. UART基础与协议概述

UART,全称是Universal Asynchronous Receiver/Transmitter,通用异步收发器。说实话,这可能是嵌入式工程师打交道最多的外设之一了。我刚开始做嵌入式那会儿,调试信息全靠它打印,跟传感器通信也靠它,甚至两个单片机之间聊天还是靠它。你想想看,一个没有UART的板子,就像一个人没了嘴巴和耳朵,憋屈得很。

1.1 UART的历史:从电传到串口

UART的历史其实挺有意思的。它的老祖宗可以追溯到电报时代的电传打字机,那时候就已经在用串行方式传输数据了。后来计算机出现了,需要一种简单可靠的方式连接外设,比如调制解调器、打印机,于是RS-232标准在1960年代诞生了。

我记得第一次看到RS-232的DB9接口时,心里还在嘀咕:这玩意儿怎么这么大?但你别看它个头大,它可是定义了UART通信的电气基础。后来随着微控制器的发展,UART被集成到了芯片内部,变成了我们今天用的这种形式。

核心要点:UART从独立芯片演变为MCU内置外设,但通信的基本原理——异步串行——几乎没有变过。这就像汽车的发动机,技术迭代了无数次,但烧油做功的原理还是那个原理。

1.2 异步串行通信原理

什么叫异步?说白了,就是收发双方没有独立的时钟线。你想想看,两个人打电话,如果一方说话另一方必须同时听,那就需要同步。但UART不是这样,它只靠一根发送线(TX)和一根接收线(RX)就能干活。

那问题来了:没有时钟线,接收方怎么知道什么时候该采样数据?

答案是:双方事先约定好速率。这个速率就是波特率。接收方用自己的时钟去采样RX线上的电平变化,只要双方时钟偏差在容忍范围内,就能正确通信。

我在项目中遇到过一个问题:两块板子用UART通信,一块用16MHz晶振,另一块用8MHz晶振,波特率都配成了115200。结果数据总是偶尔出错。后来一查,是晶振精度不够,导致波特率偏差超过了2%。从那以后,我养成了一个习惯:高速通信时一定用带小数分频的MCU,或者干脆用外部高精度晶振。

1.3 波特率:通信的节拍器

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

波特率 (bps) 每比特时间 (μs) 典型应用场景
9600 104.17 调试终端、低速传感器
38400 26.04 GPS模块、蓝牙模块
115200 8.68 常用调试、WiFi模块
921600 1.09 高速数据传输、固件升级

我的经验:调试阶段用115200最舒服,速度够快,稳定性也还行。量产产品如果通信距离超过1米,我建议降到38400或更低,抗干扰能力会好很多。

1.4 数据帧格式:一帧数据的结构

UART的数据是一帧一帧传输的。每一帧包含以下几个部分:

  • 起始位(Start Bit):1位,逻辑0。告诉接收方“我要开始发数据了”。
  • 数据位(Data Bits):5~9位,常用8位。就是你要传的实际数据。
  • 校验位(Parity Bit):可选,0或1位。用于简单的错误检测。
  • 停止位(Stop Bit):1、1.5或2位,逻辑1。表示这一帧结束了。

我画了一张图,帮你直观理解一帧数据的结构:

UART数据帧结构图 UART 数据帧结构(8N1格式) 起始位 0 (低电平) D0 LSB D1 D2 D3 D4 D5 D6 D7 MSB 校验 (可选) 停止位 1 (高电平) 传输方向:从左到右,先发LSB(最低有效位),后发MSB(最高有效位) 空闲状态为高电平,起始位拉低,停止位恢复高电平 时序波形示意 空闲 起始位(0) 8位数据位(高低变化) 停止位(1)

1.5 常见数据帧格式

实际项目中,最常用的数据帧格式是8N1,也就是8位数据位、无校验、1位停止位。为什么是8N1?因为8位刚好是一个字节,无校验省去了计算开销,1位停止位效率最高。

当然,也有其他格式:

格式名称 数据位 校验位 停止位 典型场景
8N1 8 无 1 通用调试、传感器
8E1 8 偶校验 1 工业通信(Modbus)
8O1 8 奇校验 1 老式设备兼容
9N1 9 无 1 特殊协议(如DMX512)
7E2 7 偶校验 2 老式电传打字机

注意:我曾经在一个项目中,设备手册写的是8N1,结果死活通信不上。折腾了半天才发现,手册写错了,实际是8E1。所以拿到一个新设备,第一件事就是确认它的真实帧格式,别信手册,信逻辑分析仪。

1.6 校验位:简单的错误检测

校验位分三种:无校验(N)、奇校验(O)、偶校验(E)。

  • 无校验(N):不发送校验位,帧长度最短,效率最高。
  • 奇校验(O):保证数据位+校验位中1的个数为奇数。
  • 偶校验(E):保证数据位+校验位中1的个数为偶数。

举个例子:你要发送0x55(二进制01010101),里面有4个1。

  • 如果选偶校验,校验位就是0,因为4已经是偶数了。
  • 如果选奇校验,校验位就是1,让1的总数变成5(奇数)。

接收方收到数据后,会重新计算1的个数,跟校验位对比。如果不一致,就说明传输过程中有比特翻转了。

我的建议:校验位只能检测奇数个比特错误,检测不了偶数个错误。如果你对数据可靠性要求高,别指望校验位,上CRC或者更高级的协议层校验才是正道。

1.7 停止位:帧与帧之间的间隔

停止位的作用是给接收方一个缓冲时间,让它处理完当前帧,准备好接收下一帧。停止位可以是1位、1.5位或2位。

什么时候用2位停止位?

  • 接收方的时钟精度比较差的时候。
  • 通信距离比较长,信号有反射的时候。
  • 老式设备,处理速度慢的时候。

我一般默认用1位停止位。只有在遇到通信不稳定、偶尔丢帧的情况下,才会尝试改成2位。嗯,这里要注意:改成2位停止位会降低有效数据传输速率,因为每帧多花了1个比特的时间。

1.8 本章小结

UART的基础知识就这些了。说白了,它就是一套约定:双方说好波特率、数据位、校验位、停止位,然后一根线发、一根线收,没有时钟线也能干活。这个设计从1960年代用到现在,足以证明它的经典和实用。