I2C应用编程

I2CInter-Integrated Circuit是一种广泛用于嵌入式系统的串行通信协议允许多个设备通过两根数据线进行通信在 Linux 系统中I2C 设备通常挂载在 /dev 目录下使用如 /dev/i2c-X 这样的设备文件进行访问通过系统调用和特定的 I2C 操作命令应用层程序可以与 I2C 设备进行读写操作

本教程将介绍如何使用 Linux 提供的 I2C 接口进行编程详细讲解关键的结构体系统调用以及常用的 I2C 工具以帮助开发者更好地理解和操作 I2C 设备

1. 关键结构体

1.1 struct i2c_msg

i2c_msg 是 I2C 消息的描述结构体用于定义一次 I2C 通信中的单个传输消息它可以是读或写操作并包含目标设备的地址数据缓冲区和数据长度

struct i2c_msg {
    __u16 addr;    // 从设备的地址
    __u16 flags;   // 操作标志位
    __u16 len;     // 要发送/接收的数据长度
    __u8 *buf;     // 数据缓冲区指针
};

主要标志位

  • I2C_M_RD: 表示读操作如果未设置此标志则表示写操作
  • I2C_M_TEN: 表示使用 10 位地址模式
  • I2C_M_STOP: 表示在消息结束时生成 STOP 信号

1.2 struct i2c_rdwr_ioctl_data

i2c_rdwr_ioctl_data 结构体用于通过 ioctl 系统调用执行 I2C 读写操作它包含一个 i2c_msg 数组和消息的数量

struct i2c_rdwr_ioctl_data {
    struct i2c_msg *msgs;  // 指向消息数组
    __u32 nmsgs;           // 消息的数量
};

2. I2C 应用编程概述

在用户空间中I2C 编程的核心操作是

  1. 打开 I2C 设备通过 open() 函数打开 /dev/i2c-X 设备文件
  2. 设置从设备地址使用 ioctl() 系统调用设置目标从设备的地址
  3. 数据读写通过 read()write() 进行简单的读写操作或使用 ioctl() 进行复杂的多字节传输

2.1 打开 I2C 设备

使用 open() 函数打开 I2C 设备文件例如 /dev/i2c-1

int file;
file = open("/dev/i2c-1", O_RDWR);
if (file < 0) {
    perror("Failed to open the i2c bus");
    exit(1);
}

2.2 设置从设备地址

通过 ioctl() 设置与哪个从设备进行通信

int addr = 0x50; // 从设备地址
if (ioctl(file, I2C_SLAVE, addr) < 0) {
    perror("Failed to acquire bus access and/or talk to slave");
    exit(1);
}

I2C_SLAVE 是常用的 ioctl 命令用于设置 I2C 从设备地址

2.3 读写 I2C 数据

2.3.1 写入数据

使用 write() 函数将数据写入 I2C 设备

unsigned char buffer[1] = { 0xA0 };
if (write(file, buffer, 1) != 1) {
    perror("Failed to write to the i2c bus");
}

2.3.2 读取数据

使用 read() 函数从 I2C 设备读取数据

unsigned char buffer[1];
if (read(file, buffer, 1) != 1) {
    perror("Failed to read from the i2c bus");
} else {
    printf("Data read: 0x%02x\n", buffer[0]);
}

2.4 使用 ioctl 进行复杂读写操作

对于更复杂的读写操作如多字节传输可以使用 I2C_RDWR 命令结合 i2c_rdwr_ioctl_data 来实现

2.4.1 写入多个字节

假设要向寄存器 0x10 写入两个字节的数据

unsigned char outbuf[3] = { 0x10, 0x01, 0x02 };
struct i2c_msg messages[1];
messages[0].addr  = addr;
messages[0].flags = 0;  // 写操作
messages[0].len   = 3;
messages[0].buf   = outbuf;

struct i2c_rdwr_ioctl_data ioctl_data;
ioctl_data.msgs  = messages;
ioctl_data.nmsgs = 1;

if (ioctl(file, I2C_RDWR, &ioctl_data) < 0) {
    perror("Failed to write to the i2c bus");
}

2.4.2 读取寄存器数据

通常读取寄存器数据需要先写入寄存器地址再读取其数据例如读取寄存器 0x10

unsigned char reg = 0x10;
unsigned char inbuf[1];

struct i2c_msg messages[2];
messages[0].addr  = addr;
messages[0].flags = 0;  // 写操作
messages[0].len   = 1;
messages[0].buf   = &reg;

messages[1].addr  = addr;
messages[1].flags = I2C_M_RD;  // 读操作
messages[1].len   = 1;
messages[1].buf   = inbuf;

struct i2c_rdwr_ioctl_data ioctl_data;
ioctl_data.msgs  = messages;
ioctl_data.nmsgs = 2;

if (ioctl(file, I2C_RDWR, &ioctl_data) < 0) {
    perror("Failed to read from the i2c bus");
} else {
    printf("Register 0x10 read: 0x%02x\n", inbuf[0]);
}

3. 常用的 ioctl 命令

  • I2C_SLAVE: 设置从设备地址
  • I2C_RDWR: 执行多条 I2C 消息读或写对应 i2c_rdwr_ioctl_data

4. 完整的 I2C 应用层示例

以下是一个完整的示例程序展示如何通过 I2C 接口读取和写入数据

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

int main() {
    int file;
    int addr = 0x50; // I2C 从设备地址

    // 打开 I2C 设备
    if ((file = open("/dev/i2c-1", O_RDWR)) < 0) {
        perror("Failed to open the i2c bus");
        return 1;
    }

    // 设置从设备地址
    if (ioctl(file, I2C_SLAVE, addr) < 0) {
        perror("Failed to acquire bus access and/or talk to slave");
        return 1;
    }

    // 写入数据到寄存器 0x10
    unsigned char outbuf[3] = { 0x10, 0x01, 0x02 };

    if (write(file, outbuf, 3) != 3) {
        perror("Failed to write to the i2c bus");
        return 1;
    }

    // 读取寄存器 0x10 的值
    unsigned char reg = 0x10;
    unsigned char inbuf[1];

    struct i2c_msg messages[2];
    messages[0].addr  = addr;
    messages[0].flags = 0;
    messages[0].len   = 1;
    messages[0].buf   = &reg;

    messages[1].addr  = addr;
    messages[1].flags = I2C_M_RD;
    messages[1].len   = 1;
    messages[1].buf   = inbuf;

    struct i2c_rdwr_ioctl_data ioctl_data;
    ioctl_data.msgs  = messages;
    ioctl_data.nmsgs = 2;

    if (ioctl(file, I2C_RDWR, &ioctl_data) < 0) {
        perror("Failed to read from the i2c bus");
        return 1;
    } else {
        printf("Register 0x10 read: 0x%02x\n", inbuf[0]);
    }

    close(file);
    return 0;
}

5. 使用 i2c-tools 进行调试

i2c-tools 是一组用于调试 I2C 设备的工具集其中包括 i2cdetecti2cgeti2cseti2cdump 等常用工具以下是这些工具的使用介绍

5.1 i2cdetect

i2cdetect 用于扫描 I2C 总线查看哪些设备在响应

i2cdetect -y 1

5.2 i2cget

i2cget 用于读取 I2C 设备寄存器的数据

i2cget -y 1 0x50 0x00

5.3 i2cset

i2cset 用于向 I2C 设备写入数据

i2cset -y 1 0x50 0x00 0xFF

5.4 i2cdump

i2cdump 用于读取 I2C 设备的所有寄存器并以十六进制输出

i2cdump -y 1 0x50

6. 总结

I2C 是嵌入式系统中常见的通信协议在 Linux 中提供了丰富的用户空间接口来与设备交互通过使用 open()read()write()ioctl() 等系统调用开发者可以轻松对 I2C 设备进行操作此外i2c-tools 提供了一组命令行工具能够帮助开发者快速调试和测试 I2C 总线设备在嵌入式开发和硬件调试中这些工具和接口都是非常重要的

如需进一步了解 i2c-tools 的源码和开发方法你可以访问 i2c-tools 官方源码仓库