Skip to main content

I2C

概述

Linux I2C 编程接口支持主机端(Master)和从机端(Slave)之间的总线交互。编程接口围绕两种驱动程序和两种设备结构构建。

I2C“适配器驱动程序”将控制器硬件抽象化。它通常绑定到一个物理设备(可能是 PCI 设备或 platform_device)并通过 struct i2c_adapter 表示每个 I2C 总线段。连接在每条 I2C 总线段上的 I2C 设备通过 struct i2c_client 表示,并绑定到一个 struct i2c_driver,该驱动程序遵循标准的 Linux 驱动模型。内核提供了各种函数以执行标准的 I2C 协议操作。

SMBus(System Management Bus)是 I2C 的一个同级协议。大多数 SMBus 系统也兼容 I2C。相比 I2C,SMBus 对电气特性要求更为严格,并标准化了一些协议消息和使用习惯。通常支持 I2C 的控制器也能执行大多数 SMBus 操作,但 SMBus 控制器未必支持所有 I2C 协议选项。Linux 内核提供了执行多种 SMBus 协议操作的接口,可以使用基础的 I2C 操作,也可以通过向不支持这些 I2C 操作的 i2c_adapter 发出 SMBus 命令。

Linux 内核中的 I2C 驱动框架如下:

E300 7 图示


关键特性

  • 仅支持 HOST 模式
  • 支持标准模式(100Kbit/s)和快速模式(400Kbit/s)
  • 符合 Philips I2C 总线规范 版本
  • 支持 7-bit 和 10-bit 寻址模式
  • 支持 Start / Restart / Stop
  • 16 字节 FIFO
  • 单次传输长度 65535 字节
  • 模块启用/禁用功能
  • 可编程 SCL 时钟频率
  • 8-bit 位宽数据访问
  • 自动触发 Clock Stretching

运行原理

I2C 是一种双线双向串行总线,通过 SDA 和 SCL 两根线进行设备间的高效数据交换,适用于多个设备间的短距离通信。

  • SDA 和 SCL 通过上拉电阻连接至正电源电压
  • 当总线空闲时,两条线路均为高电平
  • 连接到总线的设备输出端必须为 open-drain 或 open-collector,实现“线与”功能

E300 1 图示

I2C 支持以下速率:

  • 标准模式:最高 100kbit/s
  • 快速模式:最高 400kbit/s
  • 高速模式:最高 3.4Mbit/s

I2C 数据传输格式

  • 数据在 Master 和 Slave 之间通过 SDA 线逐字节传输
  • 传输通过 SCL 线进行同步
  • 每个数据字节长度为 8-bit, 字节数不受限制
  • 每个字节后必须跟一个 ACK 位(应答位)

数据按 MSB(高位在前)顺序传输

  • 若目标设备在操作前无法接收或发送完整字节,控制器将 SCL 拉低,进入等待状态
  • 当设备准备好后,释放 SCL,总线继续传输

E300 2 图示

Start/Stop 信号

  • 所有传输以 Start 信号开始,Stop 信号结束
  • Start: 在 SCL 为高电平时,SDA 从高电平跳变为低电平
  • Stop: 在 SCL 为高电平时,SDA 从低电平跳变为高电平

E300 3 图示

  • Start 和 Stop 信号只能由 Host 发出
  • 发出 Start 信号后,总线状态为 busy
  • 发出 Stop 信号后,总线状态变为 idle
  • 若 Host 发出 Start 后再次发出重复的 Start(Sr)而非 Stop,总线仍保持 busy 状态

I2C 7-bit 地址模式数据传输

Host 可以采用以下三种方式与 Slave 进行通信:

Host 向 Slave 发送多个字节

E300 4 图示

Host 接收 Slave 的多个字节

E300 5 图示

Host 使用 combine 模式读取 Slave 数据

E300 6 图示


API 说明

i2c/master_recv

/**
* i2c_master_recv - issue a single I2c message in master receive mode
* @client: Handle to slave device
* @buf: Where to store data read from slave
* @count: How many bytes to read, must be less than 64k since msg.len is u16
* Returns negative errno, or else the number of bytes read.
*/
static inline int i2c_master_recv(const struct i2c_client *client, char *buf, int count);

i2c/master_send

/**
* i2c_master_send - issue a single I2C message in master transmit mode
* @client: Handle to slave device
* @buf: Data that will be written to the slave
* @count: How many bytes to write, must be less than 64k since msg.len is u16
* Returns negative errno, or else the number of bytes written.
*/
static inline int i2c_master_send(const struct i2c_client *client, const char *buf, int count);

i2c_transfer

/**
* i2c_transfer - execute a single or combined I2C message
* @adap: Handle to I2C bus
* @msgs: One or more messages to execute before STOP is issued to terminate the
operation; each message begins with a START.
* @num: Number of messages to be executed.
* Returns negative errno, else the number of messages executed.
*/
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);

i2c_smbus_xfer

/**
* i2c_smbus_xfer - execute SMBus protocol operations
* @adapter: Handle to I2C bus
* @addr: Address of SMBus slave on that bus
* @flags: I2C_CLIENT_* flags (usually zero or I2C_CLIENT_PEC)
* @read_write: I2C_SMBUS_READ or I2C_SMBUS_WRITE
* @command: Byte interpreted by slave
* @protocol: SMBus protocol operation to execute, such as I2C_SMBUS_PROC_CALL
* @data: Data to be read or written
* Returns a negative errno code else zero on success.
*/
s32 i2c_smbus_xfer(struct i2c_adapter *adapter, u16 addr,
unsigned short flags, char read_write,
u8 command, int protocol, union i2c_smbus_data *data);

i2c_smbus_read_byte、i2c_smbus_write_byte

/**
* i2c_smbus_read_byte - SMBus "receive byte" protocol
* @client: Handle to slave device
* Returns negative errno else the byte received from the device.
*/
s32 i2c_smbus_read_byte(const struct i2c_client *client);
/**
* i2c_smbus_write_byte - SMBus "send byte" protocol
* @client: Handle to slave device
* @value: Byte to be sent
* Returns negative errno else zero on success.
*/
s32 i2c_smbus_write_byte(const struct i2c_client *client, u8 value);

i2c_smbus_read_byte_data、i2c_smbus_write_byte_data

/**
* i2c_smbus_read_byte_data - SMBus "read byte" protocol
* @client: Handle to slave device
* @command: Byte interpreted by slave
* Returns negative errno else a data byte received from the device.
*/
s32 i2c_smbus_read_byte_data(const struct i2c_client *client, u8 command);
/**
* i2c_smbus_write_byte_data - SMBus "write byte" protocol
* @client: Handle to slave device
* @command: Byte interpreted by slave
* @value: Byte being written
* Returns negative errno else zero on success.
*/
s32 i2c_smbus_write_byte_data(const struct i2c_client *client, u8 command, u8
value);

i2c_smbus_read_word_data、i2c_smbus_write_word_data

/**
* i2c_smbus_read_word_data - SMBus "read word" protocol
* @client: Handle to slave device
* @command: Byte interpreted by slave
* Returns negative errno else a 16-bit unsigned "word" received from the
device.
*/
s32 i2c_smbus_read_word_data(const struct i2c_client *client, u8 command);
/**
* i2c_smbus_write_word_data - SMBus "write word" protocol
* @client: Handle to slave device
* @command: Byte interpreted by slave
* @value: 16-bit "word" being written
* Returns negative errno else zero on success.
*/
s32 i2c_smbus_write_word_data(const struct i2c_client *client, u8 command, u16
value);

i2c_smbus_read_i2c_block_data、i2c_smbus_write_i2c_block_data

/**
* i2c_smbus_read_i2c_block_data - read a block of bytes
* @client: Handle to slave device
* @command: Byte interpreted by slave
* @length: Number of bytes to read
* @values: Pointer to where read bytes will be stored
* Returns the number of bytes read or negative errno.
*/
s32 i2c_smbus_read_i2c_block_data(const struct i2c_client *client,
u8 command, u8 length, u8 *values);
/**
* i2c_smbus_write_i2c_block_data - write a block of bytes
* @client: Handle to slave device
* @command: Byte interpreted by slave
* @length: Number of bytes to write
* @values: Pointer to data to write
* Returns zero on success or negative errno.
*/
s32 i2c_smbus_write_i2c_block_data(const struct i2c_client *client,
u8 command, u8 length, const u8 *values);

配置流程


步骤 1:配置设备树

MTT E300 有 19 组 I2Ccontroller,其设备树文件路径为:

linux-mthreads/arch/arm64/boot/dts/m1000/m1000-main.dtsi

其设备树节点如下:

i2c0: i2c@703000 {
compatible = "mthreads,M1000-i2c";
reg = <0x0 0x703000 0x0 0x1000>;
interrupts = <GIC_SPI 0x117 IRQ_TYPE_LEVEL_HIGH>;
clock-frequency = <400000>;
reset = <&rg RESET_PERI_I2C0>;
reset-names = "peri_i2c0_rst";
clocks = <&rg PERI_I2C0 FUNC_CLK>;
clock-names = "peri_i2c0_clk";
#address-cells = <1>;
#size-cells = <0>;
status = "disabled";
};

其中 clock-frequency 属性表示 SCL 的通讯频率,值可为 100k(standard 模式)或者 400k(fast 模式)

为了区分 19 组寄存器,需要在 aliases 节点中指定每个 controller 在 Linux 系统中 I2C 的 bus 号:

aliases{
i2c0 = &i2c0;
i2c1 = &i2c1;
i2c2 = &i2c2;
i2c3 = &i2c3;
i2c4 = &i2c4;
i2c5 = &i2c5;
i2c6 = &i2c6;
i2c7 = &i2c7;
i2c8 = &i2c8;
i2c9 = &i2c9;
i2c10 = &i2c10;
i2c11 = &i2c11;
i2c12 = &i2c12;
i2c13 = &i2c13;
i2c14 = &i2c14;
i2c15 = &i2c15;
i2c16 = &i2c16;
i2c17 = &i2c17;
i2c18 = &i2c18;
}

I2C 外接了不同的外设,需要在设备树中配置子节点,参考配置如下:

&i2c5 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&i2c5_default>;
};
rt5672: rt5672@1c {
#sound-dai-cells = <0>;
compatible = "realtek,rt5672";
reg = <0x1c>;
headset-gpios = <&gpio_peri MT_GPIO_GLB_NO(153) GPIO_ACTIVE_HIGH>;
status = "okay";
};
};
  • status”okay” 表示开启该设备树节点, ”disabled” 表示关闭设备树节点。如果
  • 没有 status 设备树节点默认是开启状态。
  • 使用 I2C 前请先配置 pinctrl (见 pinctrl 章节) 选中对应 GPIO pin 的 I2C 功能。子
  • 节点中的 reg 表示设备的从机地址。

步骤 2:配置内核

在 Linux 内核的配置菜单中要选中 M1000i2c 的驱动,路径如下:

| Symbol: I2C_MT [=y] |
| --- | --- |
| Type : tristate |
| Defined at drivers/i2c/busses/Kconfig:1482 |
| Prompt: mthreads I2C driver |
| Depends on: I2C [=y] && HAS_IOMEM [=y] |
| Location |
| -> Device Drivers |
| -> I2C support |
| -> I2C support (I2C [=y]) |
| -> I2C Hardware Bus support |
| (1) ->
> mthreads I2C driver (I2C_MT [=y])

E300 8 图示

同时要在内核中选中相应的 device 的外设驱动。


开发参考

slave 驱动示例

设备树配置示例:

&i2c11{
status = "okay";
TouchPad@0x2c {
compatible = "hid-over-i2c";
reg = <0x2c>;
hid-descr-addr = <0x20>;
interrupt-parent = <&gpio_peri>;
// interrupts = <99 IRQ_TYPE_LEVEL_LOW>;
interrupts = <MT_GPIO_GLB_NO(99) IRQ_TYPE_LEVEL_LOW>;
status = "okay";
};
};

驱动参考内核路径:

i2c-dev 驱动使用示例

也可以不编写 slave 驱动而直接使用 i2c-dev 驱动。i2c-dev.c 位于:linux-mthreads/drivers/i2c/i2c-dev.c,为 i2c 的万能设备驱动,该驱动会在加载时遍历所有的 i2c 总线,将其中 i2c_adapter_type 全部注册为字符设备,例如 /dev/i2c-0/dev/i2c-1 等,应用程序可通过 openwriteioctl 调用该驱动,该驱动则会通过 ioctl 调用底层的 adapter 驱动进行 i2c 外设的读写。在内核的配置菜单中需要选中 i2c-dev 驱动,其路径如下:

Symbol: I2C_CHARDEV [=y]
| Type : tristate
| Defined at drivers/i2c/Kconfig:51
| Prompt: I2C device interface
| Depends on: I2C [=y]
| Location:
| -> Device Drivers
| -> I2C support
| -> I2C support (I2C [=y])
| (1) -
> I2C device interface (I2C_CHARDEV [=y])

在 linux 系统启动后会在 /dev/ 目录下为每条 i2c 的 bus 生成相应的节点,例如 /dev/i2c-0/dev/i2c-1,在应用层可以通过 ioctl 读写该节点进行 i2c 的通讯。

安装相关库和头文件

sudo apt install i2c-tools libi2c-dev

包含头文件

#include<stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/i2c-dev.h> //I2C 相关定义
#include <linux/i2c.h> //i2c msg 结构体定义

重要结构体

struct i2c_rdwr_ioctl1_data {
struct i2c msg *msgs; // i2c msg[] 指针
int nmsgs; // i2c msg 消息数量
};
struct i2c msg {
__u16 addr; // 从机地址
__u16 flags;
#define I2C_M_RD 0x0001 // 读数据
#define I2C_M_TEN 0x0010
#define I2C_M_DMASAFE 0x0200
#define I2C_M_RECV_LEN 0x0400
#define I2C_M_NO_RD_ACK 0x0800
#define I2C_M_IGNORE_NAK 0x1000
#define I2C_M_REV_DIR_ADDR 0x2000
#define I2C_M_NOSTART 0x4000
#define I2C_M_STOP 0x8000
__u16 len; // 消息长度
__u8 *buf; // 消息数据指针
};

初始化 I2C 设备

初始化 I2C 设备并配置重试次数和超时时间。示例代码如下:

i2c2_init()
{
i2cfg = open("/dev/i2c-2", O_RDWR); // 打开 i2c-2 设备
if (i2cfg < 0) {
cout << "i2c-2 Can't open!" << endl;
return -1;
}
}
// 设置适配器收不到 ACK 时重试的次数,默认为 1
if (ioctl(i2cfg, I2C_RETRIES, 1) < 0) {
close(i2cfg);
i2cfg = 0;
cout << "set i2c-2 retry fail!" << endl;
return -1;
}

open() 函数:用于打开指定的 I2C 设备节点(/dev/i2c-0

ioctl() 函数:

  • I2C_RETRIES:设置适配器在未收到 ACK 时的重试次数。
  • I2C_TIMEOUT:设置超时时间,单位为 10ms。

错误处理:如果任何操作失败,应关闭文件描述符并返回错误码。

I2C 写

向指定的 I2C 器件寄存器写入数据。示例代码如下:

i2c_write(unsigned char slave, unsigned char reg, unsigned char value)
{
int ret;
struct i2c_rdwr_ioctl_data ssm_msg = {0};
unsigned char buf[2] = {0};
ssm_msg.nmsgs = 1;
ssm_msg.msgs = (struct i2c_msg *)malloc(ssm_msg.nmsgs * sizeof(struct i2c_msg));
if (ssm_msg.msgs == NULL) {
cout << "Memory alloc error!" << endl;
return -1;
}

buf[0] = reg; // 寄存器首地址
buf[1] = value; // 要写入的数据
(ssm_msg.msgs[0]).flags = 0; // 写标志
(ssm_msg.msgs[0]).addr = (unsigned short)slave; //I2C 设备地址
(ssm_msg.msgs[0]).buf = buf; // 发送缓冲区
(ssm_msg.msgs[0]).len = 2; // 发送长度

ret = ioctl(i2cfd, I2C_RDWR, &ssm_msg);
if (ret < 0) {
printf("write error, ret=%#x, errorno=%#x, %s!\n", ret, errno,
strerror(errno));
free(ssm_msg.msgs);
ssm_msg.msgs = NULL;
return -1;
}

free(ssm_msg.msgs);
ssm_msg.msgs = NULL;
return 0;
}

struct i2c_msg:定义了一条 I2C 消息,包含目标设备地址、数据缓冲区以及数据的长度等信息。struct i2c_rdwr_ioctl_data:用于定义多条 I2C 消息的集合,支持同时发送多个读写操作。ioctl() 函数:通过 I2C_RDWR 控制命令,执行多条 I2C 消息的读写操作,确保数据传输的准确性和完整性。

I2C 读

从指定的 I2C 器件寄存器读取数据。示例代码如下:

// I2C 读
int i2c_read(unsigned char slave, unsigned char reg, unsigned char *buf)
{
int ret;
struct i2c_rdwr_ioctl_data ssm_msg = {0};
unsigned char regs[1] = {reg}; // 只需要一个寄存器地址
ssm_msg.nmsgs = 2; // 读取消息个数
ssm_msg.msgs = (struct i2c_msg *)malloc(2 * sizeof(struct i2c_msg));
if (ssm_msg.msgs == NULL) {
printf("Memory alloc error!\n");
return -1;
}

// msg[0]第一条写消息,发送要读取的寄存器首地址
ssm_msg.msgs[0].flags = 0;
ssm_msg.msgs[0].addr = slave; // I2C 器件地址
ssm_msg.msgs[0].buf = regs; // 读取地址
ssm_msg.msgs[0].len = 1; // reg 长度

// msg[1]第二条读消息,从指定寄存器读取数据
ssm_msg.msgs[1].flags = I2C_M_RD; // 标记为读数据
ssm_msg.msgs[1].addr = slave; // I2C 器件地址
ssm_msg.msgs[1].buf = buf; // 读数据缓冲区
ssm_msg.msgs[1].len = 1; // 读取数据长度

ret = ioctl(i2cfd, I2C_RDWR, &ssm_msg);
if (ret < 0) {
printf("read data error, ret=%#x, errno=%#x, %s!\n", ret, errno, strerror(errno));
free(ssm_msg.msgs);
return -1;
}

free(ssm_msg.msgs);
return 0;
}

上面的代码发送了两条消息:

  • 第一条:发送要读取的寄存器地址;
  • 第二条:从指定的寄存器读取数据。

调用 ioctl 函数,传入 I2C_RDWR 控制命令执行读写操作。

main 函数及完整代码

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/i2c-dev.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

int i2cfd = 0; // I2C 文件描述符

// 初始化 i2c-2 总线设备
int i2c2_init() {
i2cfd = open("/dev/i2c-2", O_RDWR);
if (i2cfd < 0) {
printf("i2c-2 Can't open!\n");
return -1;
}

if (ioctl(i2cfd, I2C_RETRIES, 1) < 0) {
close(i2cfd);
i2cfd = 0;
printf("set i2c-2 retry fail!\n");
return -1;
}
if (ioctl(i2cfd, I2C_TIMEOUT, 10) < 0) { // 单位 10ms
close(i2cfd);
i2cfd = 0;
printf("set i2c-2 timeout fail!\n");
return -1;
}
printf("i2c2 init success!\n");
return 0;
}

// I2C 写操作:向指定寄存器写入数据
int i2c_write(unsigned char slave, unsigned char reg, unsigned char value) {
int ret;
struct i2c_rdwr_ioctl_data ssm_msg = {0};
unsigned char buf[2] = {reg, value};
ssm_msg.nmsgs = 1;
ssm_msg.msgs = (struct i2c_msg *)malloc(sizeof(struct i2c_msg));
if (!ssm_msg.msgs) {
printf("Memory alloc error!\n");
return -1;
}
ssm_msg.msgs[0].flags = 0; // 写操作
ssm_msg.msgs[0].addr = slave;
ssm_msg.msgs[0].buf = buf;
ssm_msg.msgs[0].len = 2;
ret = ioctl(i2cfd, I2C_RDWR, &ssm_msg);
if (ret < 0) {
printf("write error, ret=%#x, errno=%#x, %s!\n", ret, errno, strerror(errno));
free(ssm_msg.msgs);
return -1;
}
free(ssm_msg.msgs);
return 0;
}

// I2C 读操作:从指定寄存器读取数据
int i2c_read(unsigned char slave, unsigned char reg, unsigned char *buf) {
int ret;
struct i2c_rdwr_ioctl_data ssm_msg = {0};
ssm_msg.nmsgs = 2;
ssm_msg.msgs = (struct i2c_msg *)malloc(2 * sizeof(struct i2c_msg));
if (!ssm_msg.msgs) {
printf("Memory alloc error!\n");
return -1;
}

// 第 1 条消息:写入寄存器地址
ssm_msg.msgs[0].addr = slave;
ssm_msg.msgs[0].flags = 0;
ssm_msg.msgs[0].len = 1;
ssm_msg.msgs[0].buf = &reg;
// 第 2 条消息:读取寄存器内容
ssm_msg.msgs[1].addr = slave;
ssm_msg.msgs[1].flags = I2C_M_RD;
ssm_msg.msgs[1].len = 1;
ssm_msg.msgs[1].buf = buf;
ret = ioctl(i2cfd, I2C_RDWR, &ssm_msg);
if (ret < 0) {
printf("read error, ret=%#x, errno=%#x, %s!\n", ret, errno, strerror(errno));
free(ssm_msg.msgs);
return -1;
}
free(ssm_msg.msgs);
return 0;
}

// 主函数
int main() {
// 1. 初始化 I2C
if (i2c2_init() != 0) {
printf("I2C initialization failed!\n");
return -1;
}
// 2. 写入数据:slave 地址 0x50,寄存器 0x10,写入 0xFF
if (i2c_write(0x50, 0x10, 0xFF) != 0) {
printf("I2C write failed!\n");
close(i2cfd);
return -1;
}
printf("I2C write success!\n");
// 3. 读取数据:从 0x50 的 0x10 地址读取
unsigned char read_data;
if (i2c_read(0x50, 0x10, &read_data) != 0) {
printf("I2C read failed!\n");
close(i2cfd);
return -1;
}
printf("I2C read success, data: 0x%02x\n", read_data);
// 4. 关闭 I2C
close(i2cfd);
return 0;
}

代码说明:

  • i2c2_init(): 初始化 I2C 设备,打开设备并设置重试次数和超时参数。
  • i2c_write(): 向指定的 I2C 从设备的寄存器写入数据,设备地址为 0x50,寄存器地址为 0x10,数据为 0xFF。
  • i2c_read(): 从 I2C 从设备的寄存器读取数据,设备地址为 0x50,寄存器地址为 0x10。

调试方法说明

i2c-tools 是常用调试 i2c 的开源工具,可获取挂在总线上的设备地址,以及读写 i2c 设备寄存器,常用指令如下:

i2c-tools 安装

可以直接源码编译安装,其 GitHub 路径为:i2c-tools

大部分 Linux 系统的发行版都提供了安装包,例如 Ubuntu 可以通过 sudo apt-get install i2c-tools 指令进行安装。

i2c-tools 的使用

i2c-tools 提供了一些常用的工具,下面介绍这些工具的使用方法:

  • i2cdetect

    i2cdetect 用于扫描 I2C 总线上所有连接的设备,并显示其地址。

    指令参数:

    i2cdetect -y <i2c_BUS_number>

    -y 参数:用于自动确认(避免询问确认)

    • <i2c_BUS_number>:指定 I2C 总线编号,通常是/具体取决于你的硬件。

    使用示例:

    i2cdetect -y 1

    这将扫描 i2c-1 总线上的设备,并以十六进制形式显示设备地址。

    输出示例:

    00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f
    00: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
    10: -- -- -- -- -- -- -- -- -- -- 1a -- -- -- -- --
    20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
    30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
    40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
    50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
    60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
    70: -- -- -- -- -- -- -- --

    表示 i2c-1 bus 上存在从机地址为 0x1a 的外设。

  • i2cdump

    i2cdump 用于读取 I2C 设备的寄存器内容,并以十六进制形式显示。

    指令参数:

    i2cdump -y <i2c_BUS_number> <device_address>

    注:tools 的相关指令中, 部是 7 位设备地址(不包含读写位)使

    用示例:

    i2cdump -y 1 0x50

    这将扫描 i2c-1 总线的地址 0x50 读取设备寄存器内容, 并以十六进制形式显示。

    输出示例:

    00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f
    00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    ........
  • i2cget

    i2cget 用于从指定设备读取单个字节的值。

    指令参数:

    i2cget -y <i2c_BUS_number> <device_address> <register>

    使用示例:

    i2cget -y 1 0x50 0x00

    这将从 i2c-1 总线的地址 0x50 设备的寄存器 0x00 读取一个字节的值。

  • i2cset

    i2cset 用于向 I2C 设备的寄存器写入数据。

    指令参数:

    i2cset -y <i2c_BUS_number> <device_address> <register> <value> [mode]
    • <device_address>: I2C 设备的地址。
    • <register>:设备的寄存器地址。
    • <value>:要写入的值。
    • [mode]: 写入模式 (可选, 通常为 b 表示字节)

    使用示例:

    i2cset -y 1 0x50 0x00 0x10

    这将向 i2c-1 总线的地址 0x50 设备的寄存器 0x00 写入值 0x10


常见问题

Q1: I2C 设备无法被检测到

原因:

  • 设备树中 I2C controller 未启用(status = "disabled")
  • I2C 总线编号错误
  • 硬件连接问题(SDA/SCL 接反或未接上拉电阻)

解决方法:

  • 检查设备树配置,确保 status = "okay"
  • 使用 i2cdetect -l 列出所有 I2C 总线
  • 检查硬件连接和上拉电阻

Q2: I2C 通信超时或失败

原因:

  • 时钟频率设置过高
  • 从设备地址错误
  • 总线冲突或干扰

解决方法:

  • 在设备树中降低 clock-frequency 到 100000(标准模式)
  • 确认从设备地址(7-bit 地址,不包含读写位)
  • 检查总线上是否有多个主设备冲突

Q3: I2C 读写数据不正确

原因:

  • 寄存器地址格式错误
  • 数据长度不匹配
  • 时序问题

解决方法:

  • 确认设备 datasheet 中的寄存器地址和数据格式
  • 检查读写操作的数据长度是否正确
  • 对于高速设备,确保时钟频率设置合适

最佳实践

1. 设备树配置

  • 始终在设备树中明确指定 I2C 总线频率(clock-frequency
  • 为每个 I2C 外设配置正确的 pinctrl 设置
  • 使用合适的 status 状态控制设备启用/禁用

2. 应用程序开发

  • 始终检查 ioctl 返回值并处理错误
  • 合理设置重试次数和超时时间
  • 使用正确的数据类型和内存管理

3. 调试技巧

  • 先使用 i2cdetect 确认设备存在
  • 使用 i2cdump 验证设备寄存器内容
  • 对于复杂操作,先用 i2cget/i2cset 测试基本功能

4. 性能优化

  • 对于频繁访问的设备,考虑使用内核驱动而非用户空间访问
  • 批量读写操作比单字节操作更高效
  • 合理选择 I2C 模式(标准模式 vs 快速模式)

参考资料