← 返回博客二进制编码

格雷码详解:旋转编码器为什么使用它

格雷码让相邻数值之间的汉明距离保持为一位。学习其转换公式、编码器优势,以及异步 FIFO 仍需满足的 CDC 约束。

DennyDenny
阅读约需 9 分钟
Chalkboard feature illustration for “格雷码详解:旋转编码器为什么使用它”

**要点速览:**在反射二进制格雷码中,相邻数值只有一个比特不同。对于结构正确的位置边界,这一性质可以限制读数歧义,也适合用于异步 FIFO 指针。但它无法消除亚稳态、传感器故障、组合逻辑毛刺,也不能取代 CDC 时序约束。

标准 4 位二进制计数器从 01110111 前进到 10001000 时,四个比特会全部翻转。在理想化时序图中,它们同时变化;在真实芯片里,由于门延迟不一致、边沿转换需要时间且电源轨存在噪声,它们会以无法预先确定的顺序改变,导致总线上短暂闪现 11111111 或 00000000 等中间值。对于 CPU 寄存器,这通常无害:所有信号稳定后,时钟边沿才对数值采样。但对于由异步观察者读取的传感器,这些毛刺会成为真实读数。

格雷码也称为反射二进制码,它改变了边界条件:相邻码字只有一个比特不同,因此对准正确的观察者无需同时判定多个预期发生的位转换。正在变化的那一个比特仍可能出现歧义、噪声或亚稳态,所以格雷码只是减少某一类错误,并不会让接口绝对可靠。

单比特相邻为什么重要?

以旋转编码器为例——它是一块光学或磁性圆盘,用来告诉电机控制器转轴当前的角度。圆盘上分布着同心轨道,每条轨道贡献位置字的一个比特,固定角度上的传感器负责读取它们。圆盘旋转时,传感器会看到一系列二进制值。

如果轨道使用普通二进制编码,从位置 33(011011)到位置 44(100100)的边界要求三条轨道在名义上相同的角度改变状态。传感器安装位置、轨道几何形状与阈值差异都会使这些转换无法被同时观察到。控制器可能短暂解码出一个与两个相邻位置都相距甚远的中间码字。

用格雷码对同一块圆盘编码后,每个角度边界都只包含一次单比特转换:

位置二进制格雷码
0000000
1001001
2010011
3011010
4100110
5101111
6110101
7111100

观察格雷码一列。任何两行相邻值之间都恰好有一个比特翻转;第 7 行与第 0 行之间也恰好有一个比特翻转,因此该序列既是循环的,也是单位距离的。在预期的一次只跨过一个边界的模型下,阈值不确定性会被限制在旧码或相邻新码之内。但跨越多个边界的失准、污染、轨道损坏、电气噪声,以及对亚稳态样本进行解码,仍然可能产生错误。

4 位格雷码表

这一规律可以自然扩展:

十进制二进制格雷码
000000000
100010001
200100011
300110010
401000110
501010111
601100101
701110100
810001100
910011101
1010101111
1110111110
1211001010
1311011011
1411101001
1511111000

“反射”这个名称来自它的构造规则:要生成 (n+1)(n+1) 位码,先取 nn 位码,将其上下镜像,然后在原序列前添加 00,在镜像序列前添加 11 。镜像操作保证两个半区交界处只有一次单比特转换,也就是新加入的最高有效位发生变化。

二进制转格雷码

从二进制 bb 转换为格雷码 gg 的公式,是数字逻辑中最简洁的公式之一:

gi=bi⊕bi+1g_i = b_i \oplus b_{i+1}

对于最高有效位,规定bn=0b_n = 0。写成单一表达式就是:

g=b⊕(b≫1)g = b \oplus (b \gg 1)

在硬件中,每个格雷码输出都是相邻两个二进制位的一次异或;不存在进位链,理想逻辑深度只有一级异或门。但当二进制计数器的多个比特正在发生物理变化时,这并不保证组合输出无毛刺。把转换后的格雷码值寄存起来,再将它作为 CDC 信号使用。要理解为什么 XOR 是合适的运算符,可以回顾异或门作为差异检测器的作用。

**计算示例。**把二进制 10111011 转换为格雷码。

  • g3=b3⊕0=1g_3 = b_3 \oplus 0 = 1
  • g2=b2⊕b3=0⊕1=1g_2 = b_2 \oplus b_3 = 0 \oplus 1 = 1
  • g1=b1⊕b2=1⊕0=1g_1 = b_1 \oplus b_2 = 1 \oplus 0 = 1
  • g0=b0⊕b1=1⊕1=0g_0 = b_0 \oplus b_1 = 1 \oplus 1 = 0

结果为格雷码 11101110;上表也证实它对应十进制 11。

格雷码转二进制

反向转换采用前缀异或:

bi=gi⊕gi+1⊕gi+2⊕⋯⊕gnb_i = g_i \oplus g_{i+1} \oplus g_{i+2} \oplus \dots \oplus g_n

等价地,从最高有效位向下遍历:bn=gnb_n = g_n,随后令 bi=bi+1⊕gib_i = b_{i+1} \oplus g_i 。直接串行实现具有线性的异或深度;使用并行前缀异或树,则可以用更多布线与门电路换取更浅的深度。不要认为朴素的串接链是唯一实现方式。

继续上面的例子,解码格雷码 11101110:

  • b3=g3=1b_3 = g_3 = 1
  • b2=b3⊕g2=1⊕1=0b_2 = b_3 \oplus g_2 = 1 \oplus 1 = 0
  • b1=b2⊕g1=0⊕1=1b_1 = b_2 \oplus g_1 = 0 \oplus 1 = 1
  • b0=b1⊕g0=1⊕0=1b_0 = b_1 \oplus g_0 = 1 \oplus 0 = 1

恢复出的二进制为 10111011,也就是十进制 11。往返转换完全一致。

编码器之外:时钟域跨越

旋转编码器是教科书中的典型动机,但格雷码在现代系统中影响最大的用途位于 FPGA 与 SoC 内部:用于时钟域跨越(CDC)的异步 FIFO 指针。

双时钟 FIFO 的两侧都需要估计另一侧的指针,才能推导出满或空状态。把普通多位二进制计数器直接跨域传输并不安全,因为一次递增就可能改变多个比特。

稳健的格雷码指针设计通常会:

  1. 在本地域内保留二进制工作指针,以便进行算术运算;
  2. 把下一指针转换为格雷码;
  3. 在源时钟域寄存格雷码值;
  4. 将格雷码的每一个比特同步到目标时钟域;以及
  5. 按照该 FIFO 文档规定的满/空方程比较同步后的指针。

格雷编码意味着每次递增只会有一个逻辑指针位变化,却不能独自保证 CDC 正确。变化中的比特仍可能落入目标触发器的采样孔径并进入亚稳态。如果实现约束没有限制延迟或总线偏斜,布线偏斜还可能让不同源转换中的格雷码位在目标端重叠。AMD 与 Intel 都为格雷编码 FIFO 指针记录了此类约束。

同步后的指针还会滞后若干个目标时钟周期。满/空逻辑会围绕这一延迟采取保守设计;它并不是持续精确的跨域计数。正确性至关重要时,应使用经过验证的异步 FIFO 设计或厂商 IP。

硬件中的格雷码计数器

有两种实用方案:

  1. **二进制计数器加寄存转换。**计算 gray_next = binary_next XOR (binary_next >> 1),并在同一个源时钟上捕获两个下一状态值。寄存后的格雷码输出会按编码序列变化;不要把由存在波纹或偏斜的二进制计数驱动的未寄存转换器输出,当成无毛刺信号对外暴露。
  2. **原生格雷码状态机。**直接推导沿所选格雷码序列前进的下一状态逻辑。这样可以省去二进制状态寄存器,但递增、递减及 FIFO 地址算术会变得不够方便。

要试验第一种方案,可以打开4 位二进制计数器模板,加入异或网络,并寄存其四个输出。比较每次时钟之后的稳定状态。如果仿真器能够模拟门延迟,还要观察转换期间未寄存转换器的输出;它能直观展示源寄存器为何不可缺少。

缺点:算术运算并不方便

格雷码的超能力——单比特转换——也有代价:直接使用格雷码做算术非常麻烦。格雷编码数值不存在可直接使用的进位传播公式。两个格雷码数相加时,必须先把两者转换为二进制,完成加法,再转换回来。乘法与移位同样不适合直接进行。

因此,普通算术数据通路通常会先把格雷编码状态转换为二进制,而不是直接以格雷码执行完整算术。该编码适合特定边界,并不是二进制的通用替代品。卡诺图采用格雷码次序则有另一个原因:相邻单元必须只在一个输入变量上不同,这样几何分组才能对应蕴含项。

历史说明

该编码以贝尔实验室研究员 Frank Gray 的姓氏命名。他的“脉冲编码通信”专利申请于 1947 年提交,并在 1953 年以美国专利 2,632,058 授权;专利将这种方案称为反射二进制码,并强调相邻的被表示幅值只在一个脉冲位置上不同。相关单位距离序列早于该专利,因此更准确的说法是:Gray 推动了这种重要应用及其命名传承,而不是发明了所有此类序列。

常见误区

  • **以为可以直接做算术。**对格雷编码值逐位相加只会得到毫无意义的结果。务必先转换为二进制。
  • **忽略最高有效位上方补零的约定。**二进制转格雷码公式假定最高有效位上方存在一个虚拟零。忘记它会使高位符号发生错误翻转。
  • **把格雷码与移位寄存器环路混为一谈。**普通移位寄存器(例如SISO/SIPO 设计)会横向移动比特,无法在端到端范围内保持单位距离性质。应在边界处完成转换。
  • **混淆格雷码与独热码。**独热编码(任何时刻只有一个比特置位)从另一种意义上也具有抗毛刺能力,但其宽度为 O(2n)O(2^n),而不是 nn。格雷码是更紧凑的选择。

下一步

已发布的 SISO、SIPO、PISO 与 PIPO 指南比较了四种常见移位寄存器数据路径。不要用普通移位寄存器替代异步 FIFO:相关联的多位数据需要一种能在跨越时钟边界时保持一致性的协议。

要实际观察格雷码,可以载入 4 位二进制计数器模板,搭建转换器,加入输出寄存器,然后验证连续两个寄存值之间的汉明距离。接着,在能够感知延迟的模型中故意移除寄存器,再观察转换期间的行为。

主要参考资料

将想法变成真正可运行的电路。