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 驱动框架如下:
关键特性
- 仅支持 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,实现“线与”功能
I2C 支持以下速率:
- 标准模式:最高 100kbit/s
- 快速模式:最高 400kbit/s
- 高速模式:最高 3.4Mbit/s
I2C 数据传输格式
- 数据在 Master 和 Slave 之间通过 SDA 线逐字节传输
- 传输通过 SCL 线进行同步
- 每个数据字节长度为 8-bit, 字节数不受限制
- 每个字节后必须跟一个 ACK 位(应答位)
数据按 MSB(高位在前)顺序传输
- 若目标设备在操作前无法接收或发送完整字节,控制器将 SCL 拉低,进入等待状态
- 当设备准备好后,释放 SCL,总线继续传输
Start/Stop 信号
- 所有传输以 Start 信号开始,Stop 信号结束
- Start: 在 SCL 为高电平时,SDA 从高电平跳变为低电平
- Stop: 在 SCL 为高电平时,SDA 从低电平跳变为高电平
- Start 和 Stop 信号只能由 Host 发出
- 发出 Start 信号后,总线状态为 busy
- 发出 Stop 信号后,总线状态变为 idle
- 若 Host 发出 Start 后再次发出重复的 Start(Sr)而非 Stop,总线仍保持 busy 状态
I2C 7-bit 地址模式数据传输
Host 可以采用以下三种方式与 Slave 进行通信:
Host 向 Slave 发送多个字节
Host 接收 Slave 的多个字节
Host 使用 combine 模式读取 Slave 数据
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])
同时要在内核中选中相应的 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 等,应用程序可通过 open、write、ioctl 调用该驱动,该驱动则会通过 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 = ®
// 第 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 0f00: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --10: -- -- -- -- -- -- -- -- -- -- 1a -- -- -- -- --20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --70: -- -- -- -- -- -- -- --表示
i2c-1bus 上存在从机地址为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 0f00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0010: 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 快速模式)

