今天我们重点学习单片机开发的数学底层基础 ------ 十进制、二进制、十六进制三大核心进制,为什么学习嵌入式必须要掌握这三种进制呢?
1. 二进制:单片机唯一能看懂的语言
单片机内部只有高低两种电平:高电平 = 1,低电平 = 0,所有电路、引脚、内部运算全是二进制。
- 控制 IO 口高低电平、判断引脚信号、定时器脉冲、串口数据,底层全是 0 和 1;
- 位操作(置 1、清 0、翻转引脚)只能用二进制理解,不懂二进制永远搞不懂引脚控制。
2. 十六进制:芯片手册、寄存器、地址的标准书写格式
二进制一串 01 太长,写起来麻烦,行业统一用十六进制简写二进制:
- 芯片手册里寄存器地址、配置参数、闪存地址全是 0x 开头十六进制;
- 写单片机代码配置外设、读写寄存器、存储数组,全部大量使用十六进制;
3. 十进制:日常计算、参数标定 使用
平时算延时时间、波特率、采样次数、电机参数、电压数值,都是十进制思维。写代码时经常要把我们算好的十进制参数,转换成二进制/十六进制给单片机使用。
一、十进制
十进制是我们日常生活与通用计量的标准进位制,核心规则为逢十进一,借一当十,基数为10,有10个数位,分别是0、1、2、3、4、5、6、7、8、9。
任意十进制数均可通过位权展开式表示,位权以10的n次幂为核心,整数位从右至左依次为10⁰、10¹、10²......,小数位从左至右依次为10⁻¹、10⁻²......。作为人们日常使用的计数逻辑,十进制的核心优势是符合人对数字的认知习惯,可读性强。
在嵌入式开发中,十进制是我们用来计算、标定功能参数的唯一思维载体。单片机硬件底层只识别二进制,代码编写、寄存器寻址多用十六进制,但所有的开发需求、功能参数、调试标准,全部由十进制定义。
如延时功能,需要计算100毫秒、500毫秒的延时参数;串口通信,需要标定9600、115200的波特率;做ADC电压采集,需要换算0-3.3V、0-5V的模拟电压数值;做定时器、PWM调速,需要设定1kHz、10kHz的频率。可以发现,我们规划功能、设定指标、调试误差时,脑海里用的、图纸上写的、需求文档定义的,全都是十进制数。
在整个嵌入式开发流程中,我们先用十进制完成软件参数的计算与设定,再将十进制数值转换为十六进制数写入寄存器,并最终被转换为二进制指令让硬件电路执行;而硬件采集到的原始二进制数据,最终也要转回十进制,才能被我们读懂、分析和校准。
二、二进制
十进制有10个数位,二进制有两个数位,分别是0和1。
0和1的组合是一个非常神奇的组合,因为用0和1可以描述灯泡的亮与不亮、电路的通与断。

可以使用0和1来表示多个灯泡的亮与不亮,用0表示灯泡不亮,用1表示灯泡亮,上图中0和1的组合01011完整描述了5个灯泡的当前状态。
0和1也可以描述电路电位的高低,高位为,低位为0。计算机的电路都是由电子元器件构成的,通电的电子元器件两端会有电压,称为高电位,用1来表示,没有通电的电子元器件两端没有电压,称为低电位,因此一组由电子元器件构成的电路可以使用0和1的组合来表示。

上图0101描述了四只二极管的组合状态,显然从左往右数第2和第4只二极管处于高电位,第1和第3只二级管处于低电位。实际上,可以使用四只二级管表示16个状态,如设备的多个状态,也可以表示1至16的数字,如0001表示数字1,0002表示数字2,......;也可以表示16个字符,如0001表示字符A,0002表示字符B,......。
计算机是由电子元器件构成的,因此0和1是计算机可识别的唯一语言,0和1的组合也称为二进制数,计算机除使用二进制数表示数据外,还可以进行数值和逻辑运算。

电子元器件状态的二进制可以进行最简单的加减运算(再复杂的运算最终都会分解为加减运算),运算后的结果再通过控制电路改变电子元器件的状态。因此计算机内部运算都采用二进制运算,能够识别的数也是二进制数。
可以借助十进制数来理解二进制数。在十进制中,最开始学习的是十以内的加法,之后是两位数的加法,在两位数加法的学习中,就需要逢十进一了,按进位的原则进行记数的方法叫做进位记数制,也称为"数制"或"进制"。
生活中人们常用的数制为十进制和六十进制,计算机中常用的数制为二进制、十进制、十六进制。每一种数制的进位都遵循一个规则,那就是------逢n进1。这里的n叫做基数。例如:十进制逢10进1,二进制逢2进1,十六进制逢16进1,六十进制逢60进1,时间就是六十进制。
一个数制的基数也表示该数制由多少个数字构成,例如:十进制的基数是10,它由10个数字构成;二进制的基数是2,它由2个数字构成;十六进制的基数是16,它由16个数字构成。
在十进制计数中,计数单位分别为个位、十位、百位、千位、万位、十万位......,其中个位数表示数值1、十位数表示数值10、百位数表示数值100、千位数表示数值1000、......,每个位数表示的数值叫位权。

例如十进制数1260,千位1位权是10^3,百位2位权是10^2,十位6位权是1,个位0位权是0。

应用位权,1260也可以分解成下面的算式:
1260 = 1 X 10^3 + 2 X 10^2 + 6 X 10^1 + 0 X 10^0
理解了十进制的位权,再来理解二进制的位权就很容易了。例如,二进制数1100从低位到高位的位权依次是2^0,值是1、2^1,值是2、2^2,值是4、2^3次,值是8。

在嵌入式开发中,针对二进制我们要跳出数学思维。所有单片机、数字电路无法识别复杂数值和文字,它只识别"通电"和"断电"两种物理稳态,对应二进制的1和0。不同于十进制用于计算大小、标定参数,二进制的核心价值是描述硬件状态、定义电路逻辑、传输底层信号,这是二者最本质的区别。
二进制的最小单位是1bit,这是嵌入式开发最基础的操作单元。每一个二进制位,都独立对应一个硬件功能开关、一个信号状态、一个配置点位。8位二进制组成1个字节,是我们寄存器配置、数据存储、通信传输的基础单元。
三、十六进制
十六进制的基数是16,有16个数位:
0、1、2、3、4、5、6、7、8、9、A、B、C、D、E、F
其中用A,B,C,D,E,F(字母不区分大小写)这六个字母来分别表示10,11,12,13,14,15。
应用位权方法,可以很容易地掌握十六进制数的表示方法。例如:

在计算机编程语言中,通常用数字后面跟一个英文字母来表示该数的数制。十进制一般用D(Decimal)、二进制数用B(Binary)、十六进制用H(Hexadecimal)来表示。例如:23AH、570D、110101B、0075H,...。当然也可以用这些字母的小写形式。
在编程语言中,为了方便阅读及书写,经常使用十六进制来表示二进制数。
单片机的寄存器、地址、数据存储都是以字节为单位,1字节等于8位二进制,32位寄存器就是32个0和1。如果我们写代码、查手册、配置参数都用二进制,冗长的数字串极易看错、写错、记错,可读性极差,完全不具备工程实用性。而十六进制有一个独一无二的核心优势:四位二进制数,恰好可以完整的表示一位十六进制数。
每 4 位二进制刚好覆盖 0000~1111 共 16 种组合,正好对应十六进制 0~F,没有多余、没有缺失:
|----------------|--------------|--------------|
| 4 位二进制 | 十六进制 | 十进制值 |
| 0000 | 0 | 0 |
| 0001 | 1 | 1 |
| 0010 | 2 | 2 |
| 0011 | 3 | 3 |
| 0100 | 4 | 4 |
| 0101 | 5 | 5 |
| 0110 | 6 | 6 |
| 0111 | 7 | 7 |
| 1000 | 8 | 8 |
| 1001 | 9 | 9 |
| 1010 | A | 10 |
| 1011 | B | 11 |
| 1100 | C | 12 |
| 1101 | D | 13 |
| 1110 | E | 14 |
| 1111 | F | 15 |
四、进制转换
计算机中采用的是二进制,但用计算机解决实际问题时对数值的输入输出通常使用十进制,这就有一个十进制向二进制转换或由二进制向十进制转换的过程。也就是说,在使用计算机进行数据处理时首先必须把输入的十进制数转换成计算机所能接受的二进制数;计算机在运行结束后,再把二进制数转换为人们所习惯的十进制数输出。
1、二进制数转换成十进制数
把二进制数转换成十进制数就是用"按权相加法",把二进制数首先写成加权系数展开式,然后按十进制加法规则求和。
例:把二进制数1011.11转换成十进制数。

2、十进制数转换为二进制数
前面"按权相加法"中,权的值在小数点左边和小数点右边是不一样的。所以,十进制数转换为二进制数时,整数和小数的转换方法也不同,一般先把十进制数的整数部分和小数部分分别转换后,再加以合并。
十进制整数转换为二进制整数采用"除2取余,逆排序"法。具体做法是:用2去除十进制整数,可以得到一个商和余数;再用2去除商,又会得到一个商和余数,如此进行,直到商为零时为止,然后把所有余数按逆序排列,也就是把先得到的余数作为二进制数的低位有效位,后得到的余数作为二进制数的高位有效位,依次排列起来。这就是所谓"除2取余,逆序排列"。
例:将一个十进制数25转换为二进制数。

3、十六进制和二进制之间的转换
由于十六进制的基数是2的幂,所以这两种数制之间的转换是十分容易的。一个二进制数,只要把它从低位到高位每4位组成一组,直接用十六进制数来表示就可了。
例:0011 0101 1011 1111 = 35BF
3 5 B F
反之,把十六进制数中的每一位用4位二进制数表示,就形成相应的二进制数了。
A 1 9 C
例:A19C = 1010 0001 1001 1100
4、十六进制数转换为十进制数
同二进制数转换十进制数相同,各位十六进制数与其对应权值的乘积之和即为与此十六进制相对应的十进制数。
十进制数转换为十六进制数也可使用除法进行。

二进制数是逢二进位的进位制,0、1是基本算符,计算机运算基础采用二进制。编程过程中经常会用到十六进制,而十进制的使用非常少,这是因为十六进制和二进制有天然的联系:四个二进制位可以表示从0到15的数字,这刚好是1个16进制位可以表示的数据,也就是说,将二进制转换成十六进制只要每4位进行转换就可以了。
五、二进制位运算
二进制位运算在嵌入式开发编程中是常用的一种运算,单片机的控制方式都是通过寄存器完成的,寄存器操作一般采用位运算或移位运算。
二进制标准位运算共6种,优先级从高到低排序:取反 > 左右移位 > 与 > 异或 > 或,所有运算均为逐位独立运算,相邻位互不干扰。
表 二进制位运算符
|------|------|-----------------------------|
| 运算名称 | 符号 | 运算规则(逐位) |
| 按位取反 | ~ | 0变1,1变0 |
| 左移 | << | 所有二进制位左移,右侧补0,左侧溢出舍弃 |
| 右移 | >> | 所有二进制位右移,正数左侧补0、负数补1,右侧溢出舍弃 |
| 按位与 | | 两位都为1,结果才为1,否则为0 |
| 按位异或 | ^ | 两位相同为0,不同为1 |
| 按位或 | | | 两位有一个为1,结果就为1,否则为0 |
二进制位运算示例
设定二进制数a和b为8位的二进制数:
a = 0000 0110(十进制6),b = 0000 0011(十进制3)
1、按位与运算(&)
按位与的运算符是"&",它是一个双目运算符,它对运算符两边的操作数按位进行逻辑与运算,其运算规则是:0&0=0; 0&1=0; 1&0=0; 1&1=1,即:两位同时为1,结果才为1,否则为0。
运算演示:a & b
a: 0000 0110
b: 0000 0011
结果:0000 0010 = 十进制 2
核心经典用法:
① 判断奇偶:n & 1,结果为1是奇数,为0是偶数(效率高于取模运算);
② 清零数值:n & 0,所有位直接清零,结果恒为0;
③ 保留指定位:配合掩码,精准保留需要的二进制位。
2、按位或运算(|)
按位或的运算符是"|",它是一个双目运算符,它对运算符两边的操作数按位进行逻辑或运算,其运算规则是:0|0=0; 0|1=1; 1|0=1; 1|1=1,即:两位只要有一个为1,其值为1,其它都为0。
运算演示:a | b
a: 0000 0110
b: 0000 0011
结果:0000 0111 = 十进制 7
核心经典用法:
① 开启指定位:固定将某一二进制位设为1,常用于权限标识配置;
② 数值合并:多个状态位通过或运算合并为一个数值存储。
3、按位异或运算(^)
按位异或的运算符是"^",它是一个双目运算符,它对运算符两边的操作数按位进行逻辑异或运算,其运算规则是:0^0=0; 0^1=1;1^0=1;1^1=0,即:如果两个相应位为"异"(值不同),则该位结果为1,否则为0。
运算演示:a ^ b
a: 0000 0110
b: 0000 0011
结果:0000 0101 = 十进制 5
经典用法:无临时变量交换两个数、简单加密解密、查找数组唯一数、标记状态翻转。
4、按位取反运算(~)
按位取反的运算符是"~",它是一个单目运算符,它对操作数按位进行逻辑非运算,其运算规则是::~0=1; ~1=0; ,即:0变为1,1变为0。
运算演示:~a
a: 0000 0110
~a: 1111 1001
5、左移运算(<<)
将二进制所有位整体向左移动n位,右侧空出的低位补0,左侧超出位数的高位直接舍弃。
运算演示:a << 1(6左移1位)
原数6:0000 0110
左移1位:0000 1100 = 十进制 12
核心规律:n << k = n × 2ᵏ,移位运算效率远超乘法运算,程序中优先用移位替代乘2、乘4等运算。
5、右移运算(>>)
将二进制所有位整体向右移动n位,右侧溢出低位舍弃,左侧高位补位分两种情况:
- 正数:高位统一补0;2. 负数:高位统一补1(保证符号不变)
运算演示:a >> 1(6右移1位)
原数6:0000 0110
右移1位:0000 0011 = 十进制 3
核心规律:正数 n >> k = 向下整除 2ᵏ,优先用移位替代整除运算,提升代码效率。
六、课堂练习
1、单片机 IO 引脚配置
P1 口 8 位寄存器,要求只把 P1.2、P1.5 引脚置高电平 1,其余引脚保持 0,请分别写出二进制、十六进制、十进制数值。
问题分析
单片机的P1是8位寄存器,位序从高位到低位依次为 P1.7、P1.6、P1.5、P1.4、P1.3、P1.2、P1.1、P1.0,要求仅P1.2、P1.5置1,其余位为0。
二进制数值推导
按位序给P1.2、P1.5对应的位置赋值1,其余位填0。
最终二进制结果:00100100。
十六进制数值推导
将8位二进制拆分为高4位、低4位两组:0010 和 0100,高4位0010对应十六进制数2,低4位0100对应十六进制数4。
最终十六进制结果:0x24。
十进制数值推导
8位二进制从最高位P1.7到最低位P1.0,每一位的权重为2^n,其中n为当前位的编号。提取目标置1位的对应权重:
- P1.5位的权重为2^5 = 32
- P1.5位的权重为2^2 = 4
最终十进制结果:32+4 = 36。
2、寄存器位清除
现有寄存器变量 reg = 0x37 (00110111),需要清除 bit1、bit3 位(置 0),其余位不变,写出掩码与运算算式及结果。
问题分析
首先明确初始寄存器值的二进制对应关系:reg = 0x37 是8位无符号数,展开为完整8位二进制为 0011 0111,位编号从最低位bit0到最高位bit7的初始值如下:
|-----|------|------|------|------|------|------|------|------|
| 位编号 | bit7 | bit6 | bit5 | bit4 | bit3 | bit2 | bit1 | bit0 |
| 初始值 | 0 | 0 | 1 | 1 | 0 | 1 | 1 | 1 |
要对指定位清零,首先生成"仅目标位为1、其余位全为0"的初始掩码:
- bit1对应的单比特掩码:1 << 1,二进制为 0000 0010
- bit3对应的单比特掩码:1 << 3,二进制为 0000 1000
- 合并两个目标位的掩码:(1 << 1) | (1 << 3),得到合并后掩码的二进制为 0000 1010,十六进制为 0x0A。
生成适配按位与操作的最终掩码
按位与&的运算规则是:和1相与保留原值,和0相与强制清零。因此需要对合并后的掩码做按位取反~操作,得到"仅目标位为0、其余位全为1"的最终掩码:
8位场景下取反后的掩码二进制为 1111 0101,十六进制为 0xF5。
执行按位与运算
reg原值: 0011 0111 (0x37)
& 掩码: 1111 0101 (0xF5)
---------------------------
运算结果: 0011 0101
3、'电平翻转
引脚状态 io = 0x09 (00001001),翻转 bit0、bit3 电平,其余不变,写出异或算式与结果。
问题分析
初始状态:寄存器变量 io = 0x09,将其展开为8位二进制:0000 1001。
操作目标:翻转bit0 和 bit3 的电平状态,其余位保持不变。
- 翻转规则:0 变 1,1 变 0。
- 保持规则:原值是什么,结果就是什么。
生成掩码
在嵌入式位操作中,异或运算符 ^ 是实现"位翻转"的标准方法。要翻转哪些位,就构造一个那些位为 1、其余位为 0 的掩码(Mask),然后与原数据进行异或运算。
我们需要翻转 bit0 和 bit3,所以掩码中这两位置 1,其他位置 0:
bit0 置 1:1 << 0 → 0000 0001
bit3 置 1:1 << 3 → 0000 1000
合并掩码:(1 << 0) | (1 << 3) → → 0000 1001
掩码的十六进制表示:0x09
执行异或运算
io 初始值: 0000 1001 (0x09)
^ 翻转掩码: 0000 1001 (0x09)
---------------------------
运算结果: 0000 0000