2、从机地址的硬件决定:硬件引脚配置与数据手册解读
I2C 从机地址怎么来的?很多人第一反应是「在驱动里写死一个 0x50 不就完了」。嗯,确实能跑,但换一块板子就翻车。我见过太多工程师拿着 A 板的驱动往 B 板上一烧,I2C 通信死活不通,最后发现是地址引脚电平没对上。
说白了,从机地址不是软件随便定的。它由硬件引脚决定,数据手册里写得清清楚楚。你读懂了手册,就拿到了地址的「最终解释权」。
2.1 地址引脚:硬件上的「拨码开关」
大多数 I2C 从机芯片会留出 1~3 个地址配置引脚,比如 A0、A1、A2。这些引脚接高电平(VDD)或低电平(GND),组合起来决定芯片的 7 位从机地址。
举个例子,一颗常见的温度传感器 TMP102:
- 它只有一个地址引脚 ADD0
- ADD0 接 GND → 地址 0x48
- ADD0 接 VDD → 地址 0x49
- ADD0 接 SDA → 地址 0x4A
- ADD0 接 SCL → 地址 0x4B
你看,一个引脚就能变出 4 个地址。我在项目中遇到过一块板子上挂了 4 颗 TMP102,就是靠这 4 种接法区分开的。
关键点:地址引脚的电平必须在芯片上电前就确定。上电后芯片会锁存引脚状态,中途再改电平是无效的。
2.2 数据手册里的地址公式
数据手册一般会给一个地址计算表。我习惯先找到 「Slave Address」 或 「Address Map」 章节。里面通常长这样:
| 引脚配置 | A2 | A1 | A0 | 7位地址 | 写地址 | 读地址 |
|---|---|---|---|---|---|---|
| 全部接 GND | 0 | 0 | 0 | 0x50 | 0xA0 | 0xA1 |
| A0 接 VDD | 0 | 0 | 1 | 0x51 | 0xA2 | 0xA3 |
| A1 接 VDD | 0 | 1 | 0 | 0x52 | 0xA4 | 0xA5 |
| A1、A0 接 VDD | 0 | 1 | 1 | 0x53 | 0xA6 | 0xA7 |
注意看,7 位地址左移 1 位,最低位补 0 就是写地址,补 1 就是读地址。这是 I2C 协议的老规矩了。
我的习惯:拿到新芯片,第一件事就是复印地址表贴在工作台上。调试时不用翻手册,扫一眼就知道该发哪个地址。
2.3 硬件原理图怎么读地址
光看手册还不够,你得去读板子的原理图。找到芯片的 A0/A1/A2 引脚,看它们连到了哪里。
常见的接法有三种:
- 直接接 VDD 或 GND:最干脆,地址固定。
- 通过 10kΩ 电阻上拉或下拉:防干扰,本质还是固定电平。
- 悬空(NC):有些芯片内部有弱下拉,悬空等效于 GND。但我不建议依赖这个,万一芯片批次不同就翻车了。
我曾经调试一块温湿度传感器,原理图上 A0 标着「NC」,我以为默认是 0。结果通信一直失败,用逻辑分析仪抓出来地址是 0x4F 而不是 0x4E。后来查勘误表才发现,这颗芯片的 NC 引脚内部是弱上拉,等效于 1。从那以后,我遇到 NC 引脚都会用万用表量一下实际电平。
避坑指南:原理图上标「NC」不代表芯片内部没处理。一定要看数据手册的「Pin Description」里对 NC 引脚的说明。有些 NC 是「Do Not Connect」,有些是「Internal Pull-up/Pull-down」。
2.4 地址冲突:多设备共线时的噩梦
I2C 总线上每个从机地址必须唯一。如果两个设备地址一样,总线就会乱套。你发一个地址,两个设备同时应答,数据就打架了。
我遇到过最典型的情况:板子上用了两颗 EEPROM(AT24C02),原理图把 A0/A1/A2 都接了 GND。结果两颗芯片地址都是 0x50,读出来的数据全是乱码。最后只能飞线改其中一颗的 A0 到 VDD,地址变成 0x51 才解决。
所以设计阶段就要算好:
- 总线上有几颗 I2C 设备?
- 每颗设备有多少个可用地址?
- 地址会不会重叠?
下面这张图帮你理清地址决策流程:
2.5 实战:从原理图到驱动地址
假设你拿到一块板子,上面有一颗 LM75A 温度传感器。原理图上 A0 接了 GND,A1 接了 VDD,A2 接了 GND。
查数据手册的地址表:
| A2 | A1 | A0 | 7位地址 |
|---|---|---|---|
| 0 | 1 | 0 | 0x4A |
那么写地址就是 0x94,读地址是 0x95。在 Linux 驱动里,你只需要填 7 位地址 0x4A:
static struct i2c_board_info lm75a_info = {
.type = "lm75a",
.addr = 0x4A, // 7位地址,驱动框架会自动左移
};
或者用设备树:
&i2c1 {
lm75a@4a {
compatible = "national,lm75a";
reg = <0x4A>; // 注意这里是7位地址
};
};
注意:设备树里 reg 属性填的是 7 位地址,不是 8 位读写地址。内核会自己处理左移和读写位。你填错了就等着抓狂吧。
2.6 常见坑点总结
- 坑点 1:数据手册里的地址是 7 位还是 8 位?有些手册直接给 8 位地址(比如 0xA0),你得自己右移 1 位再填到驱动里。
- 坑点 2:地址引脚有内部上拉/下拉,原理图没画出来。用万用表量一下最保险。
- 坑点 3:同一颗芯片不同批次,地址引脚内部处理可能不一样。我吃过这个亏,后来都养成了「到手先量」的习惯。
- 坑点 4:有些芯片支持「软件地址编程」,通过写入某个寄存器来改地址。这种芯片上电后默认地址是固定的,你得先发默认地址去改它。
地址搞定了,下一步就是时钟拉伸。嗯,那是另一个故事了。