3. I2C硬件基础
地址,是I2C通信的起点。没有地址,主设备就不知道跟谁说话。我刚开始接触I2C时,觉得地址就是个简单的编号,后来踩了几个坑才明白——地址格式、保留地址、广播地址,这些细节搞不清楚,设备就是死活不通。
这一节,咱们把I2C地址的底裤扒干净。
3.1 7位地址格式:最常用的寻址方式
绝大多数I2C设备都使用7位地址。为什么?因为够用,而且省字节。
7位地址,取值范围是0x00到0x7F,一共128个地址。但别高兴太早——其中16个是保留地址,真正能用的只有112个。
| 地址范围 | 用途 | 说明 |
|---|---|---|
| 0x00 | 广播地址 | 所有从设备都会响应 |
| 0x01 - 0x07 | 保留 | 用于特殊总线协议 |
| 0x08 - 0x77 | 可用地址 | 普通设备地址,共112个 |
| 0x78 - 0x7F | 保留 | 10位地址扩展、特殊用途 |
7位地址在总线上的传输格式是这样的:
起始位 | 7位地址 | 读写位 | 应答位 | 数据...
注意,地址字节是8位——高7位是地址,最低位是读写方向。读为1,写为0。
3.2 10位地址格式:什么时候用?
7位地址只有112个可用,对于复杂系统来说,不够用。比如一个大型服务器主板上,可能有几十个I2C设备,地址冲突是常有的事。
10位地址,取值范围0x000到0x3FF,共1024个地址。它通过两个字节来传输:
第一字节: 1111 0XX R/W (XX是地址高2位)
第二字节: AAAAAAAA (地址低8位)
第一字节的高5位固定为11110,这是10位地址的标志。后面跟着地址的高2位,然后是读写位。第二字节就是地址的低8位。
我遇到过这样一个场景:一个系统里挂了8个相同的温度传感器,每个传感器只有3个地址引脚,最多支持8个不同地址。但系统里还有其他I2C设备,地址冲突了。最后我把温度传感器换成10位地址版本,才解决了问题。
3.3 广播地址:一呼百应
广播地址是0x00。主设备发送广播地址时,所有从设备都应该应答——当然,前提是从设备支持广播。
广播地址的用途:
- 系统复位:主设备发送广播,让所有从设备复位到初始状态
- 固件升级:批量更新设备固件时,先广播进入升级模式
- 状态同步:某些系统需要所有设备同时执行某个操作
但要注意,广播地址不是所有设备都支持。很多传感器芯片压根不实现广播响应。我在项目中用过一次广播复位,结果发现某个从设备没反应,后来查手册才知道它不支持广播。
3.4 保留地址:哪些不能用?
I2C规范定义了一些保留地址,这些地址不能分配给普通设备。我整理了一个表格,方便你查阅:
| 地址 | 用途 | 说明 |
|---|---|---|
| 0x00 | 广播地址 | 通用呼叫地址 |
| 0x01 | 起始字节 | 用于慢速设备 |
| 0x02 | CBUS地址 | 保留给CBUS协议 |
| 0x03 | 保留 | 未来扩展 |
| 0x04 - 0x07 | 高速模式 | Hs模式主设备地址 |
| 0x78 - 0x7F | 10位地址 | 10位地址扩展标志 |
嗯,这里要注意:0x78到0x7F这8个地址,是10位地址的保留区域。如果你用7位地址的设备,千万别把地址设在这个范围内,否则会跟10位地址设备冲突。
3.5 地址冲突:实战中的坑
地址冲突是我在项目中遇到最多的问题。两个设备用了同一个地址,总线就乱了。
常见的解决方案:
- 地址引脚:很多芯片有ADDR引脚,通过拉高拉低改变地址
- 地址复用:使用I2C多路复用器(如PCA9548)
- 地址转换:使用地址转换芯片(如LTC4316)
- 更换设备:实在不行就换一个地址不同的型号
我曾经在一个项目里,用了两个相同型号的ADC芯片,地址引脚都接地了,结果地址冲突。后来我加了一个I2C多路复用器,才把问题解决。那段时间,我天天盯着逻辑分析仪看波形,眼睛都快瞎了。
3.6 地址与寻址模式知识结构
下面这张图,把I2C地址体系的核心逻辑串起来了:
这张图把I2C地址体系分成了三个分支:7位地址、10位地址、特殊地址。每个分支都列出了核心参数。你写驱动时,对照这张图就能快速定位地址问题。
3.7 Linux内核中的地址处理
在Linux内核里,I2C地址的处理有固定的套路。我写驱动时,一般这样配置:
// 7位地址设备
static struct i2c_board_info my_i2c_devices[] = {
{
I2C_BOARD_INFO("temp_sensor", 0x48),
.platform_data = &temp_data,
},
};
// 10位地址设备
static struct i2c_board_info my_i2c_devices_10bit[] = {
{
I2C_BOARD_INFO("adc_10bit", 0x290),
.flags = I2C_CLIENT_TEN,
.platform_data = &adc_data,
},
};
注意10位地址设备的flags字段,必须设置I2C_CLIENT_TEN。否则内核会按7位地址处理,导致地址不匹配。