1. 课程导入
大家好,我是你们这趟串口与 I2C 性能调优之旅的向导。咱们第一节课,先把串口的老底儿翻出来晒晒。别觉得基础就简单,很多坑其实都埋在这些“基础”里。
1.1 UART 协议帧格式:数据是怎么“坐车”的?
UART,全称 Universal Asynchronous Receiver/Transmitter。说白了,就是让两个设备能“异步”聊天。什么叫异步?就是没有专门的时钟线,全靠双方约定好一个“语速”(波特率),然后在一根数据线上你一句我一句。
那每一句话(数据帧)长什么样呢?我习惯把它想象成一列火车:
- 起始位(Start Bit): 火车头。平时数据线是“高电平”(空闲状态),一旦拉低,就告诉对方:“注意,我要开始说话了!” 固定为 1 位。
- 数据位(Data Bits): 车厢。这是真正的货物,通常是 5 到 8 位。最常见的是 8 位,刚好一个字节。传输时一般是低位在前(LSB first)。
- 校验位(Parity Bit): 可选的车厢。用来做简单的错误检查。可以选无校验(None)、奇校验(Odd)或偶校验(Even)。
- 停止位(Stop Bit): 火车尾。必须拉高电平,表示“我说完了”。长度可以是 1、1.5 或 2 位。
核心配置: 我们常说的“9600 8N1”,意思就是:波特率 9600,8 位数据位,无校验(None),1 位停止位。这是嵌入式世界里最通用的配置。
为什么会需要停止位?其实是为了给接收端一点时间来处理刚收到的字节。我在一个老项目里遇到过,把停止位从 1 改成 2,一个棘手的丢数据问题就解决了。你想想看,有时候就是这么微妙。
1.2 波特率计算:语速到底有多快?
波特率(Baud Rate),就是每秒传输的符号数。在 UART 里,一个符号就是一个 bit,所以波特率就等于比特率(bps)。
那怎么算传输一个字节需要多久?公式很简单:
传输时间 = (1 / 波特率) * (1 + 数据位数 + 校验位数 + 停止位数)
举个例子,9600 8N1:
- 传输 1 位的时间:1/9600 ≈ 104.17 微秒
- 传输 1 帧(1 起始 + 8 数据 + 1 停止)的时间:104.17 * 10 ≈ 1.04 毫秒
- 理论最大吞吐量:1 / 1.04ms ≈ 961 字节/秒
我的经验: 实际吞吐量通常只有理论值的 70%-80%。因为还有中断处理、系统调度、协议开销(比如 Modbus 的帧间隔)等损耗。别把理论值当上限去设计系统。
波特率是怎么产生的?通常由 MCU 的主时钟经过分频得到。比如一个 16MHz 的晶振,要得到 9600 波特率,分频系数就是 16,000,000 / (16 * 9600) ≈ 104.17。这个“16”是过采样率,大部分 UART 都用 16 倍过采样来保证采样精度。
避坑指南: 我曾经在一个 115200 波特率的项目里,因为晶振精度不够(用了 1% 的陶瓷谐振器),导致长时间通信后出现帧错误。后来换成 50ppm 的晶振,问题消失。记住,波特率误差必须小于 2%,最好控制在 1% 以内。
1.3 RS232 / RS485 电平转换:TTL 电平的“翻译官”
MCU 的 UART 引脚输出的是 TTL 电平(0V 为逻辑 0,3.3V 或 5V 为逻辑 1)。但这个电平抗干扰能力差,传不了几米。所以就有了 RS232 和 RS485 这两种“翻译官”。
| 特性 | TTL (UART) | RS232 | RS485 |
|---|---|---|---|
| 电平标准 | 0V / 3.3V 或 5V | -15V ~ -3V (逻辑1) / +3V ~ +15V (逻辑0) | A-B 电压差:< -200mV (逻辑1) / > +200mV (逻辑0) |
| 传输方式 | 单端 | 单端 | 差分(双绞线) |
| 最大距离 | < 1 米 | 约 15 米 | 约 1200 米 |
| 最大节点数 | 点对点 | 点对点 | 最多 256 个(标准) |
| 典型芯片 | MCU 内置 | MAX232, SP3232 | MAX485, SP3485 |
RS232 用正负电压来传输,抗干扰能力比 TTL 强,适合短距离(十几米)的工业设备调试。RS485 则是差分信号,两根线(A 和 B)互为参考,共模干扰被抵消,所以能传上千米,而且支持多节点组网。
我记得有一次在工厂调试,现场电机一启动,RS232 就乱码。换成 RS485 后,世界清净了。差分信号在工业环境里就是王道。
1.4 硬件流控 RTS/CTS:别让数据“堵车”
当两个设备速度不匹配时,比如一个快的 MCU 给一个慢的蓝牙模块发数据,就会发生数据溢出。软件流控(XON/XOFF)靠发特殊字符,但效率低,而且如果数据里恰好有这些字符就麻烦了。
硬件流控 RTS/CTS 就靠谱得多。它用了两根额外的线:
- RTS (Request To Send): 我准备好了,你可以发数据给我。
- CTS (Clear To Send): 我准备好了,你可以发数据过来。
流程是这样的:
- 设备 A 要发数据前,先拉高 RTS,问设备 B:“我要发了,你准备好了吗?”
- 设备 B 如果缓冲区有空位,就拉高 CTS 回应:“来吧,我准备好了。”
- 设备 A 看到 CTS 为高,才开始发送数据。
- 如果设备 B 的缓冲区快满了,它就拉低 CTS。设备 A 看到后,必须立即停止发送,直到 CTS 再次变高。
实战建议: 如果你的系统里,发送端和接收端的处理速度有明显差异(比如 MCU 向一个低速无线模块发数据),强烈建议开启硬件流控。我曾经在一个 4G 模块的项目里,因为没开流控,导致模块频繁重启。开了之后,稳如老狗。
不过要注意,不是所有 UART 都支持硬件流控。有些 MCU 的 UART 只有 TX/RX 两根线,那就只能用软件流控或者降低波特率来妥协。
1.5 知识体系总览
下面这张图,把咱们这一章的核心知识点串起来了。你可以把它当作一个快速索引。