单片机的引脚只有两种状态:高电平或者低电平。既然机器只认 0 和 1,为什么我们写代码时几乎离不开十六进制?
答案藏在一根线上。
一根线 = 一个二进制位
拿 8051 的 P1 口来说,它一共有 8 根引脚,也就是 8 个二进制位。假设现在要把 P1.3 拉到高电平、其余保持低电平,写成二进制是这样:
P1 = 0b00001000;
这一串 00001000 从左到右对应 P1.7 到 P1.0。数一下是能数出来的,但引脚一多就会出事——如果同时要控制 12 个引脚,你得对着屏幕一位一位地数,数错一位就调半天。
换成十六进制:
P1 = 0x08;
清爽了。秘诀在于:一位十六进制,正好等于四位二进制。
四根线一组
为什么是四?因为 2 的 4 次方等于 16——四位二进制能表示的组合数,恰好是一位十六进制能表示的数字个数。这不是巧合,是这个进制被发明的理由。
| 二进制 | 十六进制 | 十进制 |
|---|---|---|
0000 |
0 |
0 |
0001 |
1 |
1 |
1000 |
8 |
8 |
1010 |
A |
10 |
1111 |
F |
15 |
所以从二进制转十六进制,不用算,从右往左每四位切一刀,每刀查一次表就行:
1010 0110
A 6 → 0xA6
反过来也一样。这就是为什么十六进制在嵌入式里这么好用:它保留了二进制的"位感",但短了四倍。
十六进制不是给机器看的,是给人看的。机器眼里只有 0 和 1,而一个十六进制位永远对应四根线,数位和引脚一一对得上——这才是它真正的价值。
为什么有人写 0FH,有人写 0x0F
同一个值 15,在不同场合有不同写法,仅仅是为了让编译器知道这是几进制:
| 写法 | 用在哪 | 含义 |
|---|---|---|
0FH |
汇编(Keil A51) | 后缀 H 表示十六进制 |
0x0F |
C51 / C 语言 | 前缀 0x 表示十六进制 |
0b00001111 |
部分编译器扩展 | 前缀 0b 表示二进制 |
15 |
任何地方 | 十进制 |
汇编里写 0FH 时必须以数字开头。要表示十六进制的 A~F 开头的值,前面得补个 0——写 AH 会被当成符号名,正确的是 0AH。这个坑几乎是每个人第一次写汇编都会踩的。
落到代码里
真正常用的其实就三类操作。以下面这段 CC2530 的代码为例:
// 1. 直接赋值:整个口的 8 根线一起定
P1 = 0xA6; // 1010 0110
// 2. 位或:只把某几位置 1,其余不动
P1 |= (1 << 3); // 把 P1.3 拉高 → 1010 1110
// 3. 位与取反:只把某几位清 0,其余不动
P1 &= ~(1 << 4); // 把 P1.4 拉低 → 1010 0110
三个写法的区别值得单独说清楚——直接赋值会覆盖整个寄存器,而位或、位与只动目标位。什么时候该用哪个,见 改寄存器的三个层次。
一句话总结
- 一位十六进制 = 四位二进制 = 四根线
- 二进制转十六进制:从右往左四位一切,查表
- 汇编用后缀
H,C 语言用前缀0x - 汇编里
0AH不能写成AH
想练手的话,进制转换与寄存器实训 里有一整套 16/2 进制互转和位运算的题目,答对自动出下一题。