从设备地址到寄存器地址:I²C驱动完整流程详解

摘要:
在嵌入式开发中,I²C是最常用的外设通信协议之一。很多初学者容易混淆两个概念:
- I²C设备地址(Device Address)
- 寄存器地址(Register Address)
实际驱动开发中,我们经常看到类似:
ci2c_write(0x48,0x01,0x80);那么:
- 0x48是什么?
- 0x01是什么?
- 0x80又是什么?
本文将从通信底层原理开始,完整解析一次I²C读写过程。
一、I²C通信基本结构
1.1 什么是I²C?
I²C(Inter-Integrated Circuit)是一种由 Philips(现 NXP)提出的串行通信协议。
它最大的特点:
- 两根线完成通信
- 支持多个设备挂载
- 每个设备拥有唯一地址
I²C总线包含两根信号线:
| 信号 | 作用 |
|---|---|
| SCL | 时钟线 |
| SDA | 数据线 |
结构如下:
MCU
Master
|
------------------
SCL SDA
| |
| |
----------------------------
I²C Bus
| |
| |
Temperature EEPROM
0x48 0x50
其中:
MCU:
- 负责产生时钟
- 主动发起通信
称为:
Master(主机)
传感器:
- 被动响应
- 根据地址接收数据
称为:
Slave(从机)
二、I²C设备地址是什么?
2.1 为什么需要设备地址?
假设:
一条I²C总线上连接:
MCU
|
|
+----温度传感器
|
+----EEPROM
|
+----RTC
那么MCU发送数据时:
到底给谁?
所以每个芯片都有一个地址:
例如:
| 设备 | 地址 |
|---|---|
| 温度传感器 | 0x48 |
| EEPROM | 0x50 |
| RTC | 0x68 |
通信开始:
MCU首先发送:
我要找地址0x48的设备
只有地址匹配的芯片响应。
三、I²C地址格式
常见I²C地址:
7bit地址
例如:
温度传感器:
0x48
二进制:
0100 1000
但是实际传输时:
需要加入读写位。
格式:
+------------+-----+
| 地址7bit |R/W |
+------------+-----+
例如:
写:
0x48 <<1 |0
=
0x90
读:
0x48 <<1 |1
=
0x91
所以:
0x48
不是实际发送的数据
而是设备逻辑地址
四、设备地址 ≠ 寄存器地址
这是I²C驱动最容易理解错误的地方。
例如:
一个温度传感器:
设备地址:
0x48
内部:
Sensor
地址0x48
内部寄存器:
0x00 温度寄存器
0x01 配置寄存器
0x02 状态寄存器
0x03 ID寄存器
关系:
I²C地址
|
|
找到芯片
|
|
寄存器地址
|
|
找功能
|
|
数据
举个生活例子:
城市
|
小区
|
楼栋
|
房间
对应:
设备地址
|
寄存器地址
|
寄存器数据
五、I²C写寄存器流程
假设:
修改传感器配置寄存器:
寄存器:
0x01
写入:
0x80
通信过程:
START
↓
设备地址
0x48 + W
↓
寄存器地址
0x01
↓
数据
0x80
↓
STOP
对应时序:
SCL
_|‾|_|‾|_|‾|_|‾|_|‾|
SDA
START
0x48
ACK
0x01
ACK
0x80
STOP
最终:
芯片内部:
REG[0x01]=0x80
六、I²C读寄存器流程
读取温度:
目标:
读取:
0x00寄存器
很多芯片读取需要两步。
第一步:告诉芯片我要哪个寄存器
发送:
START
↓
0x48 + W
↓
0x00
↓
STOP
第二步:读取数据
重新开始:
START
↓
0x48 + R
↓
DATA
↓
STOP
完整过程:
MCU
发送:
设备地址
0x48
↓
寄存器
0x00
↓
重新START
↓
读取数据
↓
25℃
七、STM32 HAL库中的I²C驱动
实际代码:
写寄存器
c
uint8_t reg = 0x01;
uint8_t data = 0x80;
HAL_I2C_Master_Transmit(
&hi2c1,
0x48<<1,
®,
1,
100
);
HAL_I2C_Master_Transmit(
&hi2c1,
0x48<<1,
&data,
1,
100
);
对应:
| 代码 | 含义 |
|---|---|
| 0x48 | 设备地址 |
| 0x01 | 寄存器地址 |
| 0x80 | 数据 |
八、Linux I²C驱动流程
Linux中:
应用程序不会直接操作SDA/SCL。
完整链路:
应用程序
|
read/write/ioctl
|
/dev/i2c-x
|
I2C Adapter
|
I2C Driver
|
硬件控制器
|
SDA/SCL
|
Sensor
九、Linux驱动中的关键结构
Linux:
c
struct i2c_client
{
unsigned short addr;
};
里面保存:
设备地址
0x48
读取寄存器:
常见接口:
c
i2c_smbus_read_byte_data(
client,
0x00
);
这里:
client:
代表:
哪个设备
0x00:
代表:
哪个寄存器
十、一次完整的数据读取过程
例如:
读取温度。
最终路径:
应用程序
read_temperature()
↓
Linux驱动
↓
I2C消息
↓
设备地址
0x48
↓
找到温度芯片
↓
寄存器地址
0x00
↓
读取数据
↓
0x19
↓
转换
↓
25℃
十一、驱动调试常见问题
1. 地址错误
现象:
I2C ACK失败
检查:
- 原理图
- 芯片手册
- ADDR引脚
例如:
芯片默认:
0x48
但是硬件:
ADDR拉高:
可能变成:
0x49
2. 忘记左移
错误:
c
i2c_write(0x48)
某些HAL库需要:
正确:
c
i2c_write(0x48<<1)
原因:
HAL使用8bit地址。
3. 寄存器地址长度错误
有些芯片:
8bit寄存器:
0x00
有些:
16bit寄存器:
0x1234
需要根据Datasheet确定。
十二、总结
理解I²C最重要的三个概念:
1. 设备地址
决定:
我要和哪个芯片通信
例如:
0x48
2. 寄存器地址
决定:
我要访问芯片里面哪个功能
例如:
0x00 温度
0x01 配置
3. 数据
决定:
我要写入什么或者读取什么
例如:
0x80
最终关系:
I²C通信
设备地址
↓
找到芯片
↓
寄存器地址
↓
找到功能
↓
数据
↓
完成读写
一句话总结:
I²C设备地址负责"找芯片",寄存器地址负责"找芯片内部功能",数据负责"读写具体内容"。理解这三个层级,就掌握了90%的I²C驱动开发基础。