字符收得到,却读不出来
一句话总结
UART 没有时钟线。所以双方必须信任同一个位时间,一旦这种信任被打破,帧就会从后半部分开始崩溃。
为什么需要它
一个传感器每 3 秒发送一行测量值。终端上打印出的是无法辨认的字符,但长度是对的,换行也准时到来。换了传感器依然如此,换了线缆也依然如此。这种表现几乎总是速度的问题。如果是断线,就什么也收不到;如果是噪声,只是偶尔出错,而不会每次都在同一个位置以同样的方式出错。
UART 之所以容易出这类故障,是因为它的设计就是如此。只有一根线,没有办法告诉对方“现在是位边界”。发送方按自己的时钟排列各个位,接收方按自己的时钟去切分。没有任何地方能保证两个时钟一致。唯一的,只有每个帧一次对齐位置的机会,也就是起始位。
工作原理
空闲时线路为高电平。有数据要发送时,会把线路拉低一个位时间,这就是起始位。接收方看到这个下降沿,就据此对齐自己的时钟。接着数据位从低位开始依次发出,根据配置附加一个奇偶校验位,最后以至少一位的高电平停止位结束。所谓 8N1 配置,指的是 8 位数据、无校验、1 位停止位。
接收方要做的事很简单:把距离起始沿 (k + 0.5) 个位时间的位置,读作第 k 位。之所以选择位格的正中央,是因为那里离边沿最远。
시작 d0 d1 d2 d3 d4 d5 d6 d7 정지
────┐ ┌─────┐ ┌─────────────────┐ ┌────────
└─────┘ └───────────┘ └─────┘
^ ^ ^ ^ ^ ^ ^ ^ ^ ^
표본은 언제나 칸의 한가운데
这种结构决定了误差预算。在 8N1 中,最后读取的位置是停止位,也就是距离起始沿 9.5 个位时间的地方。要让它不越出格子,累积误差必须在 0.5 位之内,所以两个时钟允许的差异是 0.5 ÷ 9.5,约 5.3%。这个数字不是从规范里抄来的,而是直接由帧结构得出的。因此错位不是从帧的前部,而是从后部开始暴露,因为前面的位还留有余量。
在嵌入式设备中,产生这种误差最常见的途径不是噪声,而是分频比。固件假定 12 MHz 来设置分频比,但电路板上装的晶振是 11.0592 MHz,那么用同样的分频比得到的波特率是 9600 × 11.0592 ÷ 12 = 8847.36 Bd。这超过了 8%,也就超出了上面的预算。本课程的采集文件正是这种情况,在波形上表现为位边界在帧内逐渐偏移。
这里出现两种保护机制。帧错误(framing error)表示停止位的位置被读成了低电平。速度一旦错位,最后一位最先出错,所以很容易被检出。奇偶校验是把 1 的个数调整为奇数或偶数的方法,所以只有当位被翻转了奇数个时才会被发现。如果翻转了偶数个,就会照样通过。sigrok 的 UART 解码器文档也把这两者分别当作 parity error 和 frame error 来处理(文档)。
修复的一方出乎意料地容易,因为答案就在采集文件里。如果帧是不间断地连续发送的,那么所有边沿都位于同一个位网格上,所以任意两个边沿之间都是整数个位时间。先用最短的间隔取一个粗略的值,再逐步拉长基线,对那个整数重新取整,精度就会提高。不能一次就用整个长度——一旦粗略值的误差乘以位数超过 0.5 位,取整就会选出错误的整数。
在现场相遇的样子
收到“字符乱码”的报告时,首先要问:是每次都在同一个位置乱,还是随机的?同一个位置就是速度问题,随机就是电气问题。然后看帧错误计数器是否在增加。增加就是时序问题,如果没有增加而值却异常,情况就更糟——那是未被检测到的损坏。
我们经常看到一些设计文档,开启了奇偶校验就说“现在安全了”。在本课程的 8E1 采集中,11 个帧里有 10 个出错,而奇偶校验和帧错误检出的只有 8 个。还有 3 个没有任何标记,就带着错误的值通过了。奇偶校验是“有问题”的信号,而不是“没有问题”的保证。
下一项实验要做什么
你会拿到三份采集文件:一份正常,两份时钟错位。亲手编写阈值函数和帧解码器,一边抽稀采样点,一边找出读取开始崩溃的位置,在错位的采集中重新测出真实的位时间,让可读的句子重新出现。最后用数字统计奇偶校验抓住了什么、漏掉了什么。