格雷码是一种特殊的二进制编码顺序:任意两个相邻状态之间只改变一个比特。普通二进制计数跨越某些边界时会同时翻转多位,例如从 011 变成 100。现实电路中的信号不可能绝对同时到达,如果传感器恰好在变化过程中采样,就可能读到原本不存在的中间值。格雷码把这种不确定性限制在一个正在变化的通道上。
本文介绍最常见的反射式格雷码,给出序列表、双向转换步骤和硬件应用。它是一种状态表示方法,不是新的算术数制;涉及加减和大小比较时,通常应先转换回普通二进制。
格雷码与普通二进制的区别
普通二进制是位值制,每一位分别对应二的幂。格雷码则刻意重新排列所有比特组合,使相邻组合的汉明距离恒为 1。它保留状态数量,却不让各位直接承担独立的数值权重。
| 十进制索引 | 普通二进制 | 格雷码 | 与上一行相比变化位数 |
|---|---|---|---|
| 0 | 000 | 000 | - |
| 1 | 001 | 001 | 1 |
| 2 | 010 | 011 | 1 |
| 3 | 011 | 010 | 1 |
| 4 | 100 | 110 | 1 |
| 5 | 101 | 111 | 1 |
| 6 | 110 | 101 | 1 |
| 7 | 111 | 100 | 1 |
普通二进制从 3 变为 4 时三位全部变化,而对应格雷码只从 010 变为 110。需要复习二进制位值时,可以阅读二进制数制指南;二进制对照表则便于比较十进制索引和普通二进制表示。
反射式格雷码如何生成
最常用的形式称为反射式格雷码。生成方法具有递归结构:把已有序列倒序复制,在原序列前加 0,在反射序列前加 1。
一位序列从下面两项开始:
0
1生成两位序列时:
原序列加 0:00, 01
倒序后加 1:11, 10
最终序列: 00, 01, 11, 10再次反射即可得到三位序列:
000, 001, 011, 010, 110, 111, 101, 100每一半内部都继承了只改变一位的性质;两个半区的连接位置只有新增前缀不同。标准反射序列还是循环的,最后一个状态与第一个状态也只相差一位,因此很适合需要从最大位置回到零位的旋转装置。
n 位格雷码共有 2^n 个不重复状态。它会遍历全部组合,但遍历顺序和普通二进制数值顺序不同。
二进制如何转换为格雷码
转换使用异或运算。最高位直接保留,后续每一位由相邻两位普通二进制做 XOR 得到:
格雷码最高位 = 二进制最高位
格雷码第 i 位 = 二进制相邻两位 XOR也可以写成一个整体表达式:
gray = binary XOR (binary 右移一位)示例:把二进制 1011 转成格雷码
| 位置 | 计算过程 | 结果 |
|---|---|---|
| 第一位 | 保留 1 | 1 |
| 第二位 | 1 XOR 0 | 1 |
| 第三位 | 0 XOR 1 | 1 |
| 第四位 | 1 XOR 1 | 0 |
所以 1011 对应 1110。XOR 在两个输入不同时输出 1;逻辑门指南提供了异或门真值表和相关数字电路背景。
格雷码如何转换回二进制
反向转换需要累计异或:
- 把最高位直接复制到二进制结果。
- 用已经恢复的二进制位与下一位格雷码做 XOR。
- 把新得到的二进制位继续用于下一次计算。
- 重复到最低位。
示例:把格雷码 1110 转成二进制
| 位置 | 计算过程 | 二进制结果 |
|---|---|---|
| 第一位 | 复制 1 | 1 |
| 第二位 | 1 XOR 1 | 0 |
| 第三位 | 0 XOR 1 | 1 |
| 第四位 | 1 XOR 0 | 1 |
最终恢复为 1011。此时再用二进制转十进制工具计算,可得到十进制 11。不能把 1110 直接按普通二进制求值,否则得到的是另一个数。
为什么旋转编码器采用格雷码
绝对式旋转编码器常在圆盘上设置多条同心轨道,每条轨道由一个传感器读取,并贡献一个输出位。如果采用普通二进制,跨越某些扇区边界时多条轨道需要同时变化。制造公差、轴偏心、灰尘、传感器阈值和线路延迟都会破坏理想的同时切换。
采用格雷码后,相邻扇区只有一条轨道发生变化。位于边界附近时,其余稳定通道仍然一致,读数通常只可能落在相邻两个位置之一,而不会突然跳到距离很远的状态。它不能消除噪声,也不能代替滤波和机械校准,但能显著缩小过渡误差。
线性位置编码器使用相同原理,只是把环形轨道展开为直线。只要物理相邻区域保持单比特变化,采样边界就更容易处理。
其他实际应用
异步时钟域传输
数字硬件有时需要在两个互不相关的时钟域之间传递计数器。多位普通二进制同时变化时,接收端可能采到来自两个计数状态的混合值。发送端先转为格雷码,可以把每次递增限制为单比特变化。不过接收端仍必须使用正确的同步器;这种编码降低多位不一致风险,却不会自动消除亚稳态。
卡诺图
卡诺图的行列按格雷码排序,使横向或纵向相邻单元格只改变一个布尔变量。因此,相邻方格可以组合成更简洁的逻辑项。编码负责安排邻接关系,布尔代数指南则说明如何应用吸收律、德摩根定律等规则进行化简。
状态机与遍历算法
某些状态机选择这种编码来减少同时翻转的触发器数量,从而降低瞬态毛刺或动态功耗。一些算法也按单比特变化顺序遍历组合空间,使相邻候选方案只改变一个决策。是否值得使用仍取决于电路和算法目标,不能仅因为序列整齐就默认采用。
实现时需要注意什么
对无符号整数 b,常见编程语言可用 b ^ (b >> 1) 生成反射式编码。解码时则从最高位向右累计 XOR:
编码(b):
返回 b XOR (b 右移一位)
解码(g):
b = 0
当 g 不为 0:
b = b XOR g
g = g 右移一位
返回 b序列化时应明确固定位宽,不能随意丢弃前导零。还要在协议中说明采用的是标准反射式格雷码还是其他单变化编码,因为“相邻只变一位”并不能唯一确定数值映射。
常见错误
- **直接按二进制位权求值:**应先解码,再做数值计算。
- **认为任意两个状态都只差一位:**该性质只适用于既定序列中的相邻状态。
- **忽略固定位宽:**缺少前导零会隐藏实际通道数量并破坏接口格式。
- **把它当成纠错码:**格雷码没有用于定位并修复任意错误的冗余校验位。
- **省略跨时钟同步器:**编码只能减少不一致组合,不能取代正确的时钟域设计。
- **混淆位序和字节序:**单比特变化顺序并不规定多字节数据在内存中的排列方式。
常见问题
格雷码最简单的定义是什么?
它是一种比特组合的排列方式,相邻组合之间恰好只有一个位置不同。这使连续变化的物理状态更不容易因多条信号线切换不同步而产生大幅误读。
为什么叫反射式格雷码?
因为生成更高位宽时,要把已有列表倒序反射,然后分别添加不同前缀。递归重复该操作即可得到任意位数的标准序列。
格雷码能直接做加法吗?
一般不这样做。它的各位没有普通二进制的独立位权。应先解码为普通二进制,完成算术,再在输出边界按需要重新编码。
格雷码能纠正传输错误吗?
不能。它减少相邻状态切换时的歧义,但没有通用纠错能力。需要定位和修复比特错误时,应使用汉明码等带冗余的编码。
n 位序列能表示多少个状态?
与普通二进制相同,共有 2^n 个状态。四位提供 16 个组合,十位提供 1024 个组合。
标准序列首尾也只差一位吗?
是。完整反射式序列具有循环性质。但如果设备只截取其中一段或重新排列状态,就必须单独检查新序列的两个端点。
总结
格雷码通过重新安排比特组合,让每次相邻变化只翻转一位。反射法可以递归生成序列,正向转换使用相邻 XOR,反向转换使用累计 XOR。旋转和线性编码器、异步计数器、卡诺图与部分状态机都能利用这一性质降低多位同时变化带来的风险。它适合处理表示边界,而算术与数值比较仍应回到普通二进制完成。
