ZYNQ/FMQL系列Linux之I2C开发实战

文章目录

    • [一、I2C 基础](#一、I2C 基础)
      • [1.1 简介](#1.1 简介)
      • [1.2 I2C 总线核心知识](#1.2 I2C 总线核心知识)
      • [1.3 I2C 接口资源示例](#1.3 I2C 接口资源示例)
    • [二、I2C 设备树标准写法](#二、I2C 设备树标准写法)
      • [2.1 设备树常见路径](#2.1 设备树常见路径)
      • [2.2 控制器节点的标准写法](#2.2 控制器节点的标准写法)
      • [2.3 从设备节点的标准写法](#2.3 从设备节点的标准写法)
      • [2.4 注意事项](#2.4 注意事项)
    • [三、I2C 调试工具 i2c-tools](#三、I2C 调试工具 i2c-tools)
      • [3.1 安装 i2c-tools](#3.1 安装 i2c-tools)
      • [3.2 扫描总线与设备](#3.2 扫描总线与设备)
      • [3.3 读取寄存器](#3.3 读取寄存器)
      • [3.4 写入寄存器](#3.4 写入寄存器)
    • [四、I2C 应用层开发实例](#四、I2C 应用层开发实例)
      • [4.1 标准编程流程](#4.1 标准编程流程)
      • [4.2 通信驱动示例](#4.2 通信驱动示例)
      • [4.3 理解通信原理](#4.3 理解通信原理)
      • [4.4 HM832IS 功率传感器实例](#4.4 HM832IS 功率传感器实例)
    • 常见问题排查
      • [问题 1:`ls /dev/i2c-*` 无设备节点](#问题 1:ls /dev/i2c-* 无设备节点)
      • [问题 2:`i2cdetect` 扫描不到设备](#问题 2:i2cdetect 扫描不到设备)
      • [问题 3:I2C 读写超时或数据乱码](#问题 3:I2C 读写超时或数据乱码)
      • [问题 4:设备树配置后不生效](#问题 4:设备树配置后不生效)

一、I2C 基础

1.1 简介

ZYNQ 系列是 Xilinx 常用的嵌入式异构平台,PS 端集成 ARM 处理器与常用外设控制器,I2C 总线广泛用于连接传感器、EEPROM、OLED、陀螺仪等低速外设。ZYNQ I2C 开发常见问题如下所示。

  • Vivado 中未使能 PS I2C 或 MIO 引脚分配错误;
  • PetaLinux 设备树未使能 I2C 控制器
  • /dev/i2c-* 设备节点不存在;
  • i2cdetect 扫描不到设备;
  • SCL/SDA 接反、上拉电阻缺失、供电异常;
  • 应用层代码读写寄存器异常;
  • 设备树修改后未重新编译 BOOT.BIN / image.ub。

本文从 I2C 设备树配置、i2c-tools 调试、C 语言应用层设计,给出项目中常见的开发流程。

1.2 I2C 总线核心知识

I2C 是两线制同步串行通信总线,仅需 SCL(时钟线)、SDA(数据线)和 GND(共地线),即可实现多设备通信,支持 7 位 / 10 位设备地址。

ZYNQ PS 端通常集成多个 I2C 控制器,常见特点如下所示。

  • 支持主模式通信;
  • 支持标准模式 100kHz、快速模式 400kHz,具体上限以芯片 TRM 和板级设计为准;
  • 引脚通过 MIO 复用,需要在 Vivado PS 配置或板级 pinctrl 中确认;
  • Linux 下通常生成 /dev/i2c-0、/dev/i2c-1 等设备节点。

注意:Zynq 的 I2C 引脚不是固定 40Pin 定义,必须根据开发板原理图和 Vivado MIO 分配确认。

1.3 I2C 接口资源示例

控制器 常见设备节点 说明
PS I2C0 /dev/i2c-0 Zynq-7000 / ZynqMP 常用 PS I2C
PS I2C1 /dev/i2c-1 具体是否引出以开发板原理图为准

不同开发板的 MIO 分配不同,SCL/SDA 对应引脚必须以原理图和 Vivado 配置为准。

二、I2C 设备树标准写法

Zynq 平台 I2C 设备树的组织方式很直接:控制器节点 负责描述总线本身(地址、中断、时钟、引脚复用、总线速率),从设备子节点负责描述挂在这条总线上的具体芯片(地址、驱动匹配字符串、中断、私有参数)。

2.1 设备树常见路径

  • Zynq-7000 系列
bash 复制代码
kernel/arch/arm/boot/dts/zynq-*.dts
  • ZynqMP 系列
bash 复制代码
kernel/arch/arm64/boot/dts/xilinx/zynqmp-*.dts
  • PetaLinux 工程
bash 复制代码
project-spec/meta-user/recipes-bsp/device-tree/files/system-user.dtsi

2.2 控制器节点的标准写法

控制器节点中已由板级 DTS 定义的属性(如 #address-cells、#size-cells、pinctrl-0)在 system-user.dtsi 中通常不需要重复。覆写只针对项目中需要改变或新增的部分,以一个 "完整" 的 I2C 控制器节点为例,如下所示。

dts 复制代码
# ZYNQ 的 zynq-7000.dtsi 里已经定义了 i2c0、i2c1 节点
i2c0: i2c@e0004000 {
    compatible = "cdns,i2c-r1p10";
    status = "disabled";
    clocks = <&clkc 38>;
    interrupt-parent = <&intc>;
    interrupts = <0 25 4>;
    clock-frequency = <400000>;
    reg = <0xe0004000 0x1000>;
    #address-cells = <1>;
    #size-cells = <0>;
};

i2c1: i2c@e0005000 {
    compatible = "cdns,i2c-r1p10";
    status = "okay";
    clocks = <&clkc 39>;
    interrupt-parent = <&intc>;
    interrupts = <0 48 4>;
    clock-frequency = <400000>;
    reg = <0xe0005000 0x1000>;
    #address-cells = <1>;
    #size-cells = <0>;

    temp-sensor@48 {
        compatible = "nst,nst175";
        reg = <0x48>;
    };
};

aliases {
    ethernet0 = &gem0;
    i2c0 = &i2c1;          /* 用 label 引用,不写路径字符串 */
};

&i2c0 {
    status = "disabled";
    clock-frequency = <400000>;
};

&i2c1 {
    status = "okay";
    clock-frequency = <400000>;
    
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_i2c0_default>;

    /* 从设备挂在这里 */
    temp-sensor@48 {
        compatible = "nst,nst175";
        reg = <0x48>;
    };
};

设备树说明:

  • Zynq-7000 的 PS I2C 控制器(e0004000 / e0005000 是 Zynq-7000 TRM 里 I2C0/I2C1 的固定基址),compatible = "cdns,i2c-r1p10" 对应 Cadence I2C IP。
  • 中断号对照(Zynq-7000):I2C0 → SPI 25(0x19),I2C1 → SPI 48(0x30),与 TRM 一致。
  • status :"okay" 使能控制器。ZynqMP 的 zynqmp.dtsi 中 I2C 节点默认通常是 "disabled",必须在板级或 system-user.dtsi 中覆写。
  • clock-frequency:总线时钟,单位 Hz。Zynq PS I2C 常用 100000(100 kHz)或 400000(400 kHz)。调试阶段建议先保持 100 kHz,确认通信正常后再尝试 400 kHz。
  • #address-cells = <1> 和 #size-cells = <0> :这对属性定义从设备子节点 reg 的格式。I2C 从设备的 reg 只表示"设备地址"这一个值,因此 #address-cells 为 1、#size-cells 为 0。没有这对属性,子节点的 reg 无法被正确解析。

2.3 从设备节点的标准写法

从设备是控制器节点的直接子节点,这是 Linux 内核文档明确规定的 I2C 设备声明方式:I2C 从设备不在硬件层面自动枚举,必须通过设备树子节点静态声明,总线注册时由 i2c-core 自动实例化。最小从设备节点只需要两个属性,如下所示。

dts 复制代码
&i2c0 {
    status = "okay";

    sensor@48 {
        compatible = "vendor,device-name";
        reg = <0x48>;
    };
    
    eeprom@50 {
        compatible = "atmel,24c256";
        reg = <0x50>;
    };

    mpu6050@68 {
        compatible = "invensense,mpu6050";
        reg = <0x68>;
    };
};
  • compatible:驱动匹配字符串。内核中对应的 I2C 驱动通过 of_match_table 与此字符串匹配,匹配成功后该从设备才会被绑定到驱动。写法必须是 "厂商前缀,型号",并且要和内核源码中驱动实际使用的 compatible 字符串一致,不能自造。
  • reg :I2C 从设备的 7 位地址 。这是最容易出错的地方:芯片数据手册常给出 8 位"写地址"(如 0xA0),设备树中必须去掉最低的 R/W 位,写 0x50,而不是 0xA0。节点名称中的 @ 后缀通常与 reg 保持一致(如 sensor@48 对应 reg = <0x48>),便于阅读。

如果从设备有中断输出连接到 SoC,还需要在子节点中添加中断相关属性,如下所示。

dts 复制代码
sensor@48 {
    compatible = "vendor,device-name";
    reg = <0x48>;
    interrupt-parent = <&intc>;
    interrupts = <...>;
};

⚠️ 注意:

  • interrupt-parent 指向中断控制器(ZYNQ 通常为 &intc),interrupts 的格式取决于具体 SoC 的中断描述符。如果从设备没有中断引脚,这两项不需要写。
  • 除以上通用属性外,从设备节点还可以包含驱动定义的私有属性,例如 EEPROM 的 pagesize、size,OLED 的 width、height,传感器的 reset-gpios 等。这些属性由对应绑定文档定义,按需添加即可。
  • PetaLinux 下推荐在用户设备树中覆写,而不是直接修改内核源码树中的 DTS。

一个实用的 bring-up 技巧:如果只是想在硬件调试阶段确认地址可达,可以先写一个只有 reg、没有 compatible 的子节点。这样不会触发驱动绑定,但该地址仍然可以被 i2cdetect 探测到,适合在驱动尚未确认 compatible 字符串时快速验证物理链路。

2.4 注意事项

  1. reg 写成 8 位地址:这是最常见的错误。设备树中的 I2C reg 始终是 7 位地址,不带 R/W 位。
  2. 控制器节点缺少 #address-cells / #size-cells:如果板级 DTS 已经定义,覆写时可以省略;但如果在自建的控制器节点中从头写,必须显式声明,否则子节点 reg 无法被正确解释。
  3. pinctrl-0 引用了不存在的 label:Zynq 的 pinctrl label 依芯片系列和板级 DTS 而不同,pinctrl_i2c0_default 不是通用固定名称。不确定时先查板级 DTS 中实际使用的 label,或者干脆不写 pinctrl-0,依赖已有配置。
  4. compatible 与驱动不匹配:I2C 从设备的 compatible 必须与内核驱动中的 of_match_table 一致。不确定时在内核源码的 drivers/ 对应驱动中搜索 compatible 字符串,或查阅 Documentation/devicetree/bindings/ 下的绑定文档。
  5. 修改设备树后未强制重编:PetaLinux 中如果只执行 petalinux-build 而没有触发 device-tree 包重新编译,烧录的镜像可能仍然携带旧设备树。可在修改 system-user.dtsi 后先执行 petalinux-build -c device-tree -x distclean,再完整编译,避免排查时误判。

三、I2C 调试工具 i2c-tools

3.1 安装 i2c-tools

(一)软件包移植

(1)从 内核官网 下载或 git 克隆,如下所示。

  • 源码

    git clone git://git.kernel.org/pub/scm/utils/i2c-tools/i2c-tools.git

  • 压缩包

bash 复制代码
# 阿里地址
# https://mirrors.edge.kernel.org/pub/software/utils/i2c-tools/
# 解压
tar -vxf i2c-tools-4.3.tar.gz

(2)指定交叉编译工具链并编译,有两种常用方式,如下所示。

  • 方式 A:通过 make 参数传入(推荐,不改源码)

假设交叉编译器前缀是 arm-linux-gnueabihf-,直接执行以下命令,如下所示。

bash 复制代码
# 动态编译
make CC=arm-linux-gnueabihf-gcc
# 静态编译
make CC=arm-linux-gnueabihf-gcc USE_STATIC_LIB=1
  • 方式 B:修改 Makefile

打开源码目录下的 Makefile 文件,找到 CC ?= gcc 和 AR ?= ar 等行,将前缀改为对应的交叉工具链,如下所示。

makefile 复制代码
CC = arm-linux-gnueabihf-gcc
AR = arm-linux-gnueabihf-ar
STRIP = arm-linux-gnueabihf-strip

保存 Makefile 文件,执行以下命令编译,如下所示。

bash 复制代码
# 动态编译
make
# 静态编译
make USE_STATIC_LIB=1

采用 make 编译的可执行程序在运行时会依赖编译生成的动态库,采用 make USE_STATIC_LIB=1 静态编译的可执行程序在运行时不需要动态库。嵌入式板子的库环境可能不全,静态编译可以避免依赖问题。

编译完成后,工具通常在 tools/ 目录下,核心可执行文件有五个:i2cdetect、i2cdump、i2cget、i2cset、i2ctransfer,将这些可执行文件复制到板子的 /usr/sbin 或 /usr/bin 目录。

(二)PetaLinux 集成

在 PetaLinux 中添加 i2c-tools,最标准的方法是通过图形化配置菜单将其集成到根文件系统(rootfs)中。PetaLinux 没有像 apt 那样的在线包管理器,所有软件都需要在构建阶段打包进镜像。

  • 方法一:通过 petalinux-config 菜单配置(推荐)

(1)在 PetaLinux 工程根目录下,执行 petalinux-config 命令进入 Rootfs 配置菜单,如下所示。

bash 复制代码
petalinux-config -c rootfs

(2)定位并启用 i2c-tools,根据 PetaLinux 版本,菜单路径可能略有差异,请依次尝试,保存配置后退出菜单,如下所示。

  • 路径 A(常见于多数版本) :
    Filesystem Packages → base → i2c-tools,找到 i2c-tools 后,按 Y 键将其选中为 [*] 状态。
  • 路径 B(部分版本) :
    Filesystem Packages → console → tools → i2c-tools,同样,选中并保存。

补充建议 :如果菜单中 i2c-tools 项下还有 dev 和 dbg 等子包,建议一并勾选,它们包含额外的调试辅助工具。

3.2 扫描总线与设备

在 linux 系统下通过 i2cdetect 命令扫描 I2C 总线列出系统中存在的 I2C 适配器或扫描某条总线上挂载了哪些设备,如下所示。

  • 基本语法
bash 复制代码
# -y 忽略提示
# -a 不忽略内核中有驱动的外设
# -q|-r 通常忽略
# I2CBUS I2C 总线,取值为 0 、 1 、 2 等整数
# FIRST LAST 通常忽略
i2cdetect [-y] [-a] [-q|-r] I2CBUS [FIRST LAST]
  • 示例 1
bash 复制代码
# 列出 I2C 适配器下的所有 I2C 设备
i2cdetect -l
# 输出示例会显示 i2c-0、i2c-1 等适配器
# i2c-0   i2c   Cadence I2C at e0005000   I2C adapter
  • 示例 2
bash 复制代码
# 扫描 I2C0 总线上挂载了哪些设备
# i2cdetect -y <总线号>
sudo i2cdetect -y 0

3.3 读取寄存器

在 linux 系统下通过 i2cget 命令读取单个寄存器,如下所示。

  • 基本语法
bash 复制代码
# -f 强制读取,默认情况下如果设备在内核有驱动则不支持应用层读取数据
# -y 忽略提示
# -a 通常忽略
# I2CBUS I2C 总线,取值为 0 、 1 、 2 等整数
# CHIP-ADDRESS 设备地址
# DATA-ADDRESS 寄存器地址
# MODE b 写入寄存器地址后直接重启总线切换为读模式读取数据,中间没有停止信号,c 相对与 b ,在产生重启信号
#      前还产生了停止信号,然后在读数据, w 与 c 一样,只不过一次性读取 2Byte
i2cget [-f] [-y] [-a] I2CBUS CHIP-ADDRESS [DATA-ADDRESS [MODE]]
  • 示例
bash 复制代码
# 读取 MPU6050 的 `WHO_AM_I` 寄存器 `0x75`
sudo i2cget -f -y 0 0x68 0x75
# 正常通常返回 0x68

3.4 写入寄存器

在 linux 系统下通过 i2cset 命令写入单个寄存器,如下所示。

  • 基本语法
bash 复制代码
# -f 强制写入,默认情况下如果设备在内核有驱动则不支持应用层写入数据
# -y 忽略提示
# -m MASK 通常忽略
# -r 通常忽略
# -a 通常忽略
# I2CBUS I2C 总线,取值为 0 、 1 、 2 等整数
# CHIP-ADDRESS 设备地址
# DATA-ADDRESS 寄存器地址
# VALUE 写入的数据
i2cset [-f] [-y] [-m MASK] [-r] [-a] I2CBUS CHIP-ADDRESS DATA-ADDRESS [VALUE]
  • 示例
bash 复制代码
# 向 MPU6050 寄存器写入数据
sudo i2cset -f -y 0 0x68 0x6B 0x00
# 将 `PWR_MGMT_1` 写为 `0x00`,用于唤醒 MPU6050

排查技巧:扫描不到设备时,优先检查供电、GND、SCL/SDA 是否接反、上拉电阻、设备地址和 I2C 设备树状态。

四、I2C 应用层开发实例

4.1 标准编程流程

Linux 用户层 I2C 编程的核心是围绕 i2c-dev 驱动暴露的 /dev/i2c-N 字符设备节点来操作总线。整体流程可以清晰地分为以下几步。

(一)确认并打开设备节点

首先需确保内核已加载 i2c-dev 模块,并能在 /dev 下看到对应的总线节点(如 /dev/i2c-0),之后以读写方式打开它,如下所示。

c 复制代码
int fd = open("/dev/i2c-0", O_RDWR);
if (fd < 0) {
    perror("Failed to open i2c bus");
    // 错误处理
}

(二)设置从机地址

在与具体从设备通信前,通常需要先告诉驱动你要和哪个地址的设备对话。这通过 ioctl 的 I2C_SLAVE 或 I2C_SLAVE_FORCE 命令完成,如下所示。

c 复制代码
if (ioctl(fd, I2C_SLAVE, slave_addr) < 0) {
    // 错误处理
}

注意 :这个设置是一次性 的。之后如果通过 read/write 进行普通传输,驱动会一直使用这个地址,直到你再次调用 ioctl 更改它。

(三)数据传输(三种方式)

I2C 数据传输方式的选择取决于具体项目的需求和设备协议,如下所示。

  • 方式一:read / write (简单原始传输)
    适用于最简单的时序。先设置好从机地址,再直接调用 read 或 write 函数。例如,要读取一个寄存器,通常是先 write 寄存器地址,再 read 数据。这种方式需要分两次系统调用完成,在这两次调用之间,总线控制权可能会被其他进程抢占。
  • 方式二:I2C_RDWR (复合/原子传输,推荐)
    这是最灵活、最推荐的方式。通过调用 ioctl(fd, I2C_RDWR, &rdwr_data) 函数提交一个 struct i2c_msg 数组 ,可以在一次系统调用中完成 "先写后读" 等多个时序步骤,中间不会释放总线,对时序敏感的设备更安全。
  • 方式三:SMBus 协议
    这是一套基于 I2C 的标准化协议,兼容性更好,适合许多标准传感器。SMBus = I2C 的物理层 + 标准化的命令/数据帧格式 + 更严格的时序与可靠性约束。通过调用 ioctl(fd, I2C_SMBUS, &args) 函数使用,它规定了 command + length + data 的固定帧格式、必须 ACK、35ms 超时和可选 PEC;

(四)关闭设备

通信结束后,调用 close(fd) 函数关闭文件描述符,释放资源。

4.2 通信驱动示例

(一)资源管理

Linux 用户层操作 I2C 依赖内核的 i2c-dev 驱动。它把每个 I2C 适配器(总线)注册成一个字符设备,如下所示。

text 复制代码
/dev/i2c-0  次设备号 0
/dev/i2c-1  次设备号 1
...
  • 设备描述符管理
c 复制代码
#define I2C_DEV_MAX_NUM 5

struct i2c_dev_info {
    int major;
    int fd;
    pthread_mutex_t i2c_dev_mutex;
};

static struct i2c_dev_info g_i2c_dev_info[I2C_DEV_MAX_NUM] = {
    { .major = 0, .fd = -1, .i2c_dev_mutex = PTHREAD_MUTEX_INITIALIZER },
    { .major = 1, .fd = -1, .i2c_dev_mutex = PTHREAD_MUTEX_INITIALIZER },
    { .major = 2, .fd = -1, .i2c_dev_mutex = PTHREAD_MUTEX_INITIALIZER },
    { .major = 3, .fd = -1, .i2c_dev_mutex = PTHREAD_MUTEX_INITIALIZER },
    { .major = 4, .fd = -1, .i2c_dev_mutex = PTHREAD_MUTEX_INITIALIZER },
};

/* 保护 open/close 对 fd 状态的修改 */
static pthread_mutex_t g_i2c_open_mutex = PTHREAD_MUTEX_INITIALIZER;

static int i2c_index_valid(enum I2C_INDEX index)
{
    return (unsigned)index < I2C_DEV_MAX_NUM;
}
  • 打开设备
c 复制代码
int i2c_open(enum I2C_INDEX index)
{
    int fd;
    char dev_path[DRIVER_PATH_MAX] = {0};
    int ret = 0;

    if (!i2c_index_valid(index))
        return ERR_INVALID_PARA;

    pthread_mutex_lock(&g_i2c_open_mutex);

    if (g_i2c_dev_info[index].fd != -1) {
        pthread_mutex_unlock(&g_i2c_open_mutex);
        return 0;
    }

  	// major 在这里实际是总线编号,对应 /dev/i2c-N 的 N
    snprintf(dev_path, sizeof(dev_path), "/dev/i2c-%d", g_i2c_dev_info[index].major);
    fd = open(dev_path, O_RDWR);
    if (fd < 0) {
        Ru_Log_Error("can't open i2c dev %s: %s", dev_path, strerror(errno));
        ret = DRV_ERR_OPEN_FAILED;
        goto out;
    }

    g_i2c_dev_info[index].fd = fd;

out:
    pthread_mutex_unlock(&g_i2c_open_mutex);
    return ret;
}
  • 关闭设备
c 复制代码
int i2c_close(enum I2C_INDEX index)
{
    int fd;

    if (!i2c_index_valid(index))
        return ERR_INVALID_PARA;

    pthread_mutex_lock(&g_i2c_open_mutex);

    /* 先拿设备锁,确保没有正在进行的传输 */
    pthread_mutex_lock(&g_i2c_dev_info[index].i2c_dev_mutex);
    fd = g_i2c_dev_info[index].fd;
    g_i2c_dev_info[index].fd = -1;
    pthread_mutex_unlock(&g_i2c_dev_info[index].i2c_dev_mutex);

    pthread_mutex_unlock(&g_i2c_open_mutex);

    if (fd != -1)
        close(fd);

    return 0;
}

(二)设备加/解锁

  • 加锁
c 复制代码
/*
 * 获取某条总线的设备锁,并把 fd 返回给调用者。
 * 成功返回 0;失败返回 ERR_INVALID_PARA。
 * 调用者必须在使用完 fd 后调用 i2c_unlock(index)。
 */
static int i2c_lock(enum I2C_INDEX index, int *fd)
{
    if (!i2c_index_valid(index))
        return ERR_INVALID_PARA;

    pthread_mutex_lock(&g_i2c_dev_info[index].i2c_dev_mutex);

    if (g_i2c_dev_info[index].fd == -1) {
        pthread_mutex_unlock(&g_i2c_dev_info[index].i2c_dev_mutex);
        return ERR_INVALID_PARA;
    }

    *fd = g_i2c_dev_info[index].fd;
    return 0;
}
  • 解锁
c 复制代码
static void i2c_unlock(enum I2C_INDEX index)
{
    pthread_mutex_unlock(&g_i2c_dev_info[index].i2c_dev_mutex);
}

(三)数据传输

  • 方式一:read / write
c 复制代码
int i2c_read(enum I2C_INDEX index, U16 slave_addr, U8 *recv_data, U32 recv_len)
{
    int fd;
    int ret;
    ssize_t n;

    if (recv_len > 0 && recv_data == NULL)
        return ERR_INVALID_PARA;

    ret = i2c_lock(index, &fd);
    if (ret != 0)
        return ret;

    ret = ioctl(fd, I2C_SLAVE_FORCE, slave_addr);
    if (ret < 0) {
        ret = -errno;
        i2c_unlock(index);
        return ret;
    }

    n = read(fd, recv_data, recv_len);
    if (n < 0) {
        ret = -errno;
        i2c_unlock(index);
        return ret;
    }
    if ((U32)n != recv_len) {
        i2c_unlock(index);
        return -EIO;
    }

    i2c_unlock(index);
    return 0;
}
  • 方式二:I2C_RDWR
c 复制代码
/*
 * 核心传输接口:一次 I2C_RDWR 提交 "写 + 读"。
 * - write_len == 0:只读
 * - read_len  == 0:只写
 * - 两者都非 0:先写后读,内核一次 i2c_transfer 完成,原子性最好
 * 成功返回 0,失败返回负错误码(-errno)或 ERR_INVALID_PARA。
 */
int i2c_rdwr_xfer(enum I2C_INDEX index, U16 slave_addr, U8 *write_buf, U32 write_len,
                   U8 *read_buf, U32 read_len)
{
    int fd;
    int ret;
    int nmsgs = 0;
    struct i2c_msg msgs[2];
    struct i2c_rdwr_ioctl_data rdwr;

    if (slave_addr > 0x3ff)
        return ERR_INVALID_PARA;
    if (write_len > 0 && write_buf == NULL)
        return ERR_INVALID_PARA;
    if (read_len > 0 && read_buf == NULL)
        return ERR_INVALID_PARA;
    if (write_len > UINT16_MAX || read_len > UINT16_MAX)
        return ERR_INVALID_PARA;

    ret = i2c_lock(index, &fd);
    if (ret != 0)
        return ret;

    if (write_len > 0) {
        msgs[nmsgs].addr  = slave_addr;
        msgs[nmsgs].flags = 0;
        msgs[nmsgs].len   = (__u16)write_len;
        msgs[nmsgs].buf   = write_buf;
        nmsgs++;
    }

    if (read_len > 0) {
        msgs[nmsgs].addr  = slave_addr;
        msgs[nmsgs].flags = I2C_M_RD;
        msgs[nmsgs].len   = (__u16)read_len;
        msgs[nmsgs].buf   = read_buf;
        nmsgs++;
    }

    if (nmsgs == 0) {
        i2c_unlock(index);
        return 0;
    }

    rdwr.msgs  = msgs;
    rdwr.nmsgs = nmsgs;

    ret = ioctl(fd, I2C_RDWR, &rdwr);
    i2c_unlock(index);

    if (ret < 0)
        return -errno;
    if (ret != nmsgs)
        return -EIO;

    return 0;
}

/*
 * 读寄存器:i2c_write_read 的薄封装。
 * 不再调用 I2C_SLAVE_FORCE,I2C_RDWR 使用 msgs[].addr。
 * 成功返回 0,失败返回负错误码。
 */
int i2c_msg_read(enum I2C_INDEX index, U8 dev_addr, U8 reg_addr, U8 *recv_data, int length)
{
    U8 reg = reg_addr;

    if (length <= 0 || recv_data == NULL)
        return ERR_INVALID_PARA;

    return i2c_rdwr_xfer(index, dev_addr, &reg, 1, recv_data, (U32)length);
}
  • 方式三:SMBus 协议
c 复制代码
int i2c_smbus_access(int fd, char read_write, U8 addr,
                     int size, union i2c_smbus_data *data)
{
    struct i2c_smbus_ioctl_data args;
    int err;

    args.read_write = read_write;
    args.command = addr;
    args.size = size;
    args.data = data;

    err = ioctl(fd, I2C_SMBUS, &args);
    if (err == -1)
        return -errno;
    return 0;
}

int i2c_smbus_read(enum I2C_INDEX index, U8 dev_addr, U8 reg_addr, U8 *recv_data, int length)
{
    int fd;
    int ret;
    int i;
    union i2c_smbus_data data;

    if (length <= 0 || recv_data == NULL)
        return ERR_INVALID_PARA;

    if (length > I2C_SMBUS_BLOCK_MAX)
        length = I2C_SMBUS_BLOCK_MAX;

    ret = i2c_lock(index, &fd);
    if (ret != 0)
        return ret;

    ret = ioctl(fd, I2C_SLAVE_FORCE, dev_addr);
    if (ret < 0) {
        ret = -errno;
        i2c_unlock(index);
        return ret;
    }

    data.block[0] = (__u8)length;
    ret = i2c_smbus_access(fd, I2C_SMBUS_READ, reg_addr,
                           length == I2C_SMBUS_BLOCK_MAX ?
                           I2C_SMBUS_I2C_BLOCK_BROKEN : I2C_SMBUS_I2C_BLOCK_DATA,
                           &data);
    if (ret < 0) {
        i2c_unlock(index);
        return ret;
    }

    /* 防止设备返回长度超过调用者缓冲区 */
    if (data.block[0] > (__u8)length) {
        i2c_unlock(index);
        return -EIO;
    }

    for (i = 1; i <= data.block[0]; i++)
        recv_data[i - 1] = data.block[i];

    ret = (int)data.block[0];
    i2c_unlock(index);
    return ret;
}

int i2c_smbus_write(enum I2C_INDEX index, U8 dev_addr, U8 reg_addr, U8 *values, int length)
{
    int fd;
    int ret;
    int i;
    union i2c_smbus_data data;

    if (length <= 0 || values == NULL)
        return ERR_INVALID_PARA;

    if (length > I2C_SMBUS_BLOCK_MAX)
        length = I2C_SMBUS_BLOCK_MAX;

    for (i = 1; i <= length; i++)
        data.block[i] = values[i - 1];
    data.block[0] = (__u8)length;

    ret = i2c_lock(index, &fd);
    if (ret != 0)
        return ret;

    ret = ioctl(fd, I2C_SLAVE_FORCE, dev_addr);
    if (ret < 0) {
        ret = -errno;
        i2c_unlock(index);
        return ret;
    }

    ret = i2c_smbus_access(fd, I2C_SMBUS_WRITE, reg_addr,
                           length == I2C_SMBUS_BLOCK_MAX ?
                           I2C_SMBUS_I2C_BLOCK_BROKEN : I2C_SMBUS_I2C_BLOCK_DATA,
                           &data);
    i2c_unlock(index);

    if (ret < 0)
        return ret;

    return length;
}

4.3 理解通信原理

(一)I2C 协议的最小单元

一次 I2C 传输在物理层由主机(SoC 的 I2C 控制器)产生 SCL 时钟,SDA 上出现以下内容。

text 复制代码
START | 7 位地址 + R/W | ACK | data0 | ACK | data1 | ACK | ... | STOP
  • START:SCL 高时 SDA 由高变低。
  • 地址帧 :7 位从机地址左移 1 位,最低位是 R/W:0 写,1 读。
  • ACK:每 8 位后,接收方把 SDA 拉低一个时钟,表示 "收到"。
  • NACK:接收方不拉低,通常表示"无此设备"或"不再需要数据"。
  • STOP:SCL 高时 SDA 由低变高。
  • Repeated START:不发 STOP,直接再发 START,用于 "写地址 → 读数据" 不释放总线。

所有 i2c_msg 最终都会被内核翻译成上面这些帧。

(二)I2C 的总线时序

I2C 的总线时序只发生在内核调用适配器 master_xfer() 函数的时候,如下所示。

  • open("/dev/i2c-N"):只建立 fd 到内核 i2c_adapter 的通道,不产生总线波形。
  • ioctl(fd, I2C_SLAVE_FORCE, addr):只修改 client->addr 这个软件状态,不产生 START/地址/ACK。
  • read() / write():各提交 1 个 i2c_msg,产生一次单消息传输。
  • ioctl(fd, I2C_RDWR, &rdwr):提交多个 i2c_msg ,内核调用一次 i2c_transfer() 函数完成,消息之间通常用 repeated START,适合 "写寄存器地址再读数据"。
  • ioctl(fd, I2C_SMBUS, &args):内核把 SMBus 协议翻译成一组 i2c_msg,最终仍调用 i2c_transfer() 函数。

构造 i2c_msg → 内核 i2c_transfer() → 适配器 master_xfer() → SCL/SDA 上出现 START、地址、ACK、数据、STOP。

(1)用户层调用 open("/dev/i2c-N", O_RDWR) 函数,内核 i2cdev_open() 函数主要做以下操作。

  1. 根据次设备号找到 struct i2c_adapter *adap;
  2. 分配 struct i2c_client *client;
  3. client->adapter = adap;
  4. 把 client 挂到 file->private_data。

#mermaid-svg-DAOugw8Io8TeWEds{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-DAOugw8Io8TeWEds .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-DAOugw8Io8TeWEds .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-DAOugw8Io8TeWEds .error-icon{fill:#552222;}#mermaid-svg-DAOugw8Io8TeWEds .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-DAOugw8Io8TeWEds .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-DAOugw8Io8TeWEds .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-DAOugw8Io8TeWEds .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-DAOugw8Io8TeWEds .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-DAOugw8Io8TeWEds .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-DAOugw8Io8TeWEds .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-DAOugw8Io8TeWEds .marker{fill:#333333;stroke:#333333;}#mermaid-svg-DAOugw8Io8TeWEds .marker.cross{stroke:#333333;}#mermaid-svg-DAOugw8Io8TeWEds svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-DAOugw8Io8TeWEds p{margin:0;}#mermaid-svg-DAOugw8Io8TeWEds .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-DAOugw8Io8TeWEds .cluster-label text{fill:#333;}#mermaid-svg-DAOugw8Io8TeWEds .cluster-label span{color:#333;}#mermaid-svg-DAOugw8Io8TeWEds .cluster-label span p{background-color:transparent;}#mermaid-svg-DAOugw8Io8TeWEds .label text,#mermaid-svg-DAOugw8Io8TeWEds span{fill:#333;color:#333;}#mermaid-svg-DAOugw8Io8TeWEds .node rect,#mermaid-svg-DAOugw8Io8TeWEds .node circle,#mermaid-svg-DAOugw8Io8TeWEds .node ellipse,#mermaid-svg-DAOugw8Io8TeWEds .node polygon,#mermaid-svg-DAOugw8Io8TeWEds .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-DAOugw8Io8TeWEds .rough-node .label text,#mermaid-svg-DAOugw8Io8TeWEds .node .label text,#mermaid-svg-DAOugw8Io8TeWEds .image-shape .label,#mermaid-svg-DAOugw8Io8TeWEds .icon-shape .label{text-anchor:middle;}#mermaid-svg-DAOugw8Io8TeWEds .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-DAOugw8Io8TeWEds .rough-node .label,#mermaid-svg-DAOugw8Io8TeWEds .node .label,#mermaid-svg-DAOugw8Io8TeWEds .image-shape .label,#mermaid-svg-DAOugw8Io8TeWEds .icon-shape .label{text-align:center;}#mermaid-svg-DAOugw8Io8TeWEds .node.clickable{cursor:pointer;}#mermaid-svg-DAOugw8Io8TeWEds .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-DAOugw8Io8TeWEds .arrowheadPath{fill:#333333;}#mermaid-svg-DAOugw8Io8TeWEds .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-DAOugw8Io8TeWEds .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-DAOugw8Io8TeWEds .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-DAOugw8Io8TeWEds .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-DAOugw8Io8TeWEds .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-DAOugw8Io8TeWEds .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-DAOugw8Io8TeWEds .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-DAOugw8Io8TeWEds .cluster text{fill:#333;}#mermaid-svg-DAOugw8Io8TeWEds .cluster span{color:#333;}#mermaid-svg-DAOugw8Io8TeWEds div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-DAOugw8Io8TeWEds .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-DAOugw8Io8TeWEds rect.text{fill:none;stroke-width:0;}#mermaid-svg-DAOugw8Io8TeWEds .icon-shape,#mermaid-svg-DAOugw8Io8TeWEds .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-DAOugw8Io8TeWEds .icon-shape p,#mermaid-svg-DAOugw8Io8TeWEds .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-DAOugw8Io8TeWEds .icon-shape .label rect,#mermaid-svg-DAOugw8Io8TeWEds .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-DAOugw8Io8TeWEds .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-DAOugw8Io8TeWEds .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-DAOugw8Io8TeWEds :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} open(/dev/i2c-N)
i2cdev_open
找到 i2c_adapter
分配 i2c_client
file->private_data = client
无总线波形

此时 client->addr 还没有实际意义,SDA/SCL 上没有任何波形。

(2)用户层调用 ioctl(fd, I2C_SLAVE_FORCE, slave_addr) 函数,I2C_SLAVE_FORCE 只改软件状态,内核中它只设置当前 fd 对应 i2c_client 的默认从地址,如下所示。

c 复制代码
case I2C_SLAVE_FORCE:
    client->addr = arg;
    return 0;
  • 不产生 START;
  • 不发地址帧;
  • 不产生 ACK;
  • 对后续 read() / write() 函数有效;
  • 对 I2C_RDWR 无效 ,因为 I2C_RDWR 使用每个 i2c_msg.addr。

#mermaid-svg-lj2Sn76AwuY0S2Ts{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-lj2Sn76AwuY0S2Ts .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-lj2Sn76AwuY0S2Ts .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-lj2Sn76AwuY0S2Ts .error-icon{fill:#552222;}#mermaid-svg-lj2Sn76AwuY0S2Ts .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-lj2Sn76AwuY0S2Ts .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-lj2Sn76AwuY0S2Ts .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-lj2Sn76AwuY0S2Ts .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-lj2Sn76AwuY0S2Ts .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-lj2Sn76AwuY0S2Ts .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-lj2Sn76AwuY0S2Ts .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-lj2Sn76AwuY0S2Ts .marker{fill:#333333;stroke:#333333;}#mermaid-svg-lj2Sn76AwuY0S2Ts .marker.cross{stroke:#333333;}#mermaid-svg-lj2Sn76AwuY0S2Ts svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-lj2Sn76AwuY0S2Ts p{margin:0;}#mermaid-svg-lj2Sn76AwuY0S2Ts .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-lj2Sn76AwuY0S2Ts .cluster-label text{fill:#333;}#mermaid-svg-lj2Sn76AwuY0S2Ts .cluster-label span{color:#333;}#mermaid-svg-lj2Sn76AwuY0S2Ts .cluster-label span p{background-color:transparent;}#mermaid-svg-lj2Sn76AwuY0S2Ts .label text,#mermaid-svg-lj2Sn76AwuY0S2Ts span{fill:#333;color:#333;}#mermaid-svg-lj2Sn76AwuY0S2Ts .node rect,#mermaid-svg-lj2Sn76AwuY0S2Ts .node circle,#mermaid-svg-lj2Sn76AwuY0S2Ts .node ellipse,#mermaid-svg-lj2Sn76AwuY0S2Ts .node polygon,#mermaid-svg-lj2Sn76AwuY0S2Ts .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-lj2Sn76AwuY0S2Ts .rough-node .label text,#mermaid-svg-lj2Sn76AwuY0S2Ts .node .label text,#mermaid-svg-lj2Sn76AwuY0S2Ts .image-shape .label,#mermaid-svg-lj2Sn76AwuY0S2Ts .icon-shape .label{text-anchor:middle;}#mermaid-svg-lj2Sn76AwuY0S2Ts .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-lj2Sn76AwuY0S2Ts .rough-node .label,#mermaid-svg-lj2Sn76AwuY0S2Ts .node .label,#mermaid-svg-lj2Sn76AwuY0S2Ts .image-shape .label,#mermaid-svg-lj2Sn76AwuY0S2Ts .icon-shape .label{text-align:center;}#mermaid-svg-lj2Sn76AwuY0S2Ts .node.clickable{cursor:pointer;}#mermaid-svg-lj2Sn76AwuY0S2Ts .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-lj2Sn76AwuY0S2Ts .arrowheadPath{fill:#333333;}#mermaid-svg-lj2Sn76AwuY0S2Ts .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-lj2Sn76AwuY0S2Ts .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-lj2Sn76AwuY0S2Ts .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-lj2Sn76AwuY0S2Ts .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-lj2Sn76AwuY0S2Ts .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-lj2Sn76AwuY0S2Ts .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-lj2Sn76AwuY0S2Ts .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-lj2Sn76AwuY0S2Ts .cluster text{fill:#333;}#mermaid-svg-lj2Sn76AwuY0S2Ts .cluster span{color:#333;}#mermaid-svg-lj2Sn76AwuY0S2Ts div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-lj2Sn76AwuY0S2Ts .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-lj2Sn76AwuY0S2Ts rect.text{fill:none;stroke-width:0;}#mermaid-svg-lj2Sn76AwuY0S2Ts .icon-shape,#mermaid-svg-lj2Sn76AwuY0S2Ts .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-lj2Sn76AwuY0S2Ts .icon-shape p,#mermaid-svg-lj2Sn76AwuY0S2Ts .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-lj2Sn76AwuY0S2Ts .icon-shape .label rect,#mermaid-svg-lj2Sn76AwuY0S2Ts .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-lj2Sn76AwuY0S2Ts .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-lj2Sn76AwuY0S2Ts .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-lj2Sn76AwuY0S2Ts :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} ioctl(fd, I2C_SLAVE_FORCE, addr)
client->addr = addr
仅修改软件状态
无 START / 地址 / ACK

(3)用户层调用 read() 函数,内核 i2cdev_read() 函数调用 i2c_master_recv() 函数构造 i2c_msg,如下所示。

c 复制代码
struct i2c_msg msg = {
    .addr  = client->addr,
    .flags = I2C_M_RD,
    .len   = recv_len,
    .buf   = recv_data,
};
i2c_transfer(client->adapter, &msg, 1);
  • 波形
text 复制代码
START | addr<<1 | 1(R) | ACK | data0 | ACK | data1 | ACK | ... | NACK | STOP

主机发 START 后先发 addr<<1|1(7 位从地址加读位),从机回 ACK 表示地址匹配并准备发送数据;随后从机逐个驱动 data0、data1......到 SDA,主机每收到一个字节就回 ACK 表示 "继续发",... 表示按读取长度重复 "从机发数据 + 主机回 ACK";当主机收到最后一个字节后回 NACK 表示 "不要再发了",然后发 STOP 释放总线。

  • 时序图

从设备 I2C 适配器 内核 i2c-dev 用户层 从设备 I2C 适配器 内核 i2c-dev 用户层 #mermaid-svg-NfLCh9nwIyAO1kBQ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-NfLCh9nwIyAO1kBQ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-NfLCh9nwIyAO1kBQ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-NfLCh9nwIyAO1kBQ .error-icon{fill:#552222;}#mermaid-svg-NfLCh9nwIyAO1kBQ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-NfLCh9nwIyAO1kBQ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-NfLCh9nwIyAO1kBQ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-NfLCh9nwIyAO1kBQ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-NfLCh9nwIyAO1kBQ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-NfLCh9nwIyAO1kBQ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-NfLCh9nwIyAO1kBQ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-NfLCh9nwIyAO1kBQ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-NfLCh9nwIyAO1kBQ .marker.cross{stroke:#333333;}#mermaid-svg-NfLCh9nwIyAO1kBQ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-NfLCh9nwIyAO1kBQ p{margin:0;}#mermaid-svg-NfLCh9nwIyAO1kBQ .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-NfLCh9nwIyAO1kBQ text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-NfLCh9nwIyAO1kBQ .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-NfLCh9nwIyAO1kBQ .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-NfLCh9nwIyAO1kBQ .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-NfLCh9nwIyAO1kBQ .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-NfLCh9nwIyAO1kBQ #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-NfLCh9nwIyAO1kBQ .sequenceNumber{fill:white;}#mermaid-svg-NfLCh9nwIyAO1kBQ #sequencenumber{fill:#333;}#mermaid-svg-NfLCh9nwIyAO1kBQ #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-NfLCh9nwIyAO1kBQ .messageText{fill:#333;stroke:none;}#mermaid-svg-NfLCh9nwIyAO1kBQ .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-NfLCh9nwIyAO1kBQ .labelText,#mermaid-svg-NfLCh9nwIyAO1kBQ .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-NfLCh9nwIyAO1kBQ .loopText,#mermaid-svg-NfLCh9nwIyAO1kBQ .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-NfLCh9nwIyAO1kBQ .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-NfLCh9nwIyAO1kBQ .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-NfLCh9nwIyAO1kBQ .noteText,#mermaid-svg-NfLCh9nwIyAO1kBQ .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-NfLCh9nwIyAO1kBQ .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-NfLCh9nwIyAO1kBQ .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-NfLCh9nwIyAO1kBQ .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-NfLCh9nwIyAO1kBQ .actorPopupMenu{position:absolute;}#mermaid-svg-NfLCh9nwIyAO1kBQ .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-NfLCh9nwIyAO1kBQ .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-NfLCh9nwIyAO1kBQ .actor-man circle,#mermaid-svg-NfLCh9nwIyAO1kBQ line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-NfLCh9nwIyAO1kBQ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} read(fd, buf, len) i2c_master_recv(client, buf, len) i2c_transfer(adap, msg1, 1) START + addr + R ACK 读数据 data + ACK/NACK STOP 返回读字节数或错误 n 或 -errno

如果地址发出去没有 ACK,用户层可能收到 -ENXIO 或 -EREMOTEIO。

(4)用户层调用 write() 函数,内核 i2cdev_write() 函数调用 i2c_master_send() 函数构造 i2c_msg,如下所示。

c 复制代码
struct i2c_msg msg = {
    .addr  = client->addr,
    .flags = 0,
    .len   = send_len,
    .buf   = send_data,
};
i2c_transfer(client->adapter, &msg, 1);
  • 波形
text 复制代码
START | addr<<1 | 0(W) | ACK | data0 | ACK | data1 | ACK | ... | STOP

主机发 START 后先发 addr<<1|0(7 位从地址加写位),从机回 ACK 表示地址匹配;随后主机连续发送 data0、data1......每个字节后从机都回 ACK 表示收到,... 表示按写入长度重复 "数据字节 + ACK";所有数据发完后主机发 STOP 释放总线。

  • 时序图

从设备 I2C 适配器 内核 i2c-dev 用户层 从设备 I2C 适配器 内核 i2c-dev 用户层 #mermaid-svg-nUMINRHjoa2waFiZ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-nUMINRHjoa2waFiZ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-nUMINRHjoa2waFiZ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-nUMINRHjoa2waFiZ .error-icon{fill:#552222;}#mermaid-svg-nUMINRHjoa2waFiZ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-nUMINRHjoa2waFiZ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-nUMINRHjoa2waFiZ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-nUMINRHjoa2waFiZ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-nUMINRHjoa2waFiZ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-nUMINRHjoa2waFiZ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-nUMINRHjoa2waFiZ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-nUMINRHjoa2waFiZ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-nUMINRHjoa2waFiZ .marker.cross{stroke:#333333;}#mermaid-svg-nUMINRHjoa2waFiZ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-nUMINRHjoa2waFiZ p{margin:0;}#mermaid-svg-nUMINRHjoa2waFiZ .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-nUMINRHjoa2waFiZ text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-nUMINRHjoa2waFiZ .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-nUMINRHjoa2waFiZ .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-nUMINRHjoa2waFiZ .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-nUMINRHjoa2waFiZ .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-nUMINRHjoa2waFiZ #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-nUMINRHjoa2waFiZ .sequenceNumber{fill:white;}#mermaid-svg-nUMINRHjoa2waFiZ #sequencenumber{fill:#333;}#mermaid-svg-nUMINRHjoa2waFiZ #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-nUMINRHjoa2waFiZ .messageText{fill:#333;stroke:none;}#mermaid-svg-nUMINRHjoa2waFiZ .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-nUMINRHjoa2waFiZ .labelText,#mermaid-svg-nUMINRHjoa2waFiZ .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-nUMINRHjoa2waFiZ .loopText,#mermaid-svg-nUMINRHjoa2waFiZ .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-nUMINRHjoa2waFiZ .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-nUMINRHjoa2waFiZ .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-nUMINRHjoa2waFiZ .noteText,#mermaid-svg-nUMINRHjoa2waFiZ .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-nUMINRHjoa2waFiZ .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-nUMINRHjoa2waFiZ .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-nUMINRHjoa2waFiZ .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-nUMINRHjoa2waFiZ .actorPopupMenu{position:absolute;}#mermaid-svg-nUMINRHjoa2waFiZ .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-nUMINRHjoa2waFiZ .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-nUMINRHjoa2waFiZ .actor-man circle,#mermaid-svg-nUMINRHjoa2waFiZ line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-nUMINRHjoa2waFiZ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} write(fd, buf, len) i2c_master_send(client, buf, len) i2c_transfer(adap, msg1, 1) START + addr + W ACK data0 ACK data1 ACK STOP 返回写字节数或错误 n 或 -errno

(5)I2C_RDWR 写寄存器 + 读数据是 I2C 最常用的复合事务:先写寄存器地址,再用 repeated START 切到读,取回数据 。因为一个 i2c_msg 只能表示一个方向(flags = 0 是写,flags = I2C_M_RD 是读),所以需要两个 i2c_msg,如下所示。

  • 波形
text 复制代码
START | addr+W | ACK | reg | ACK | repeated START | addr+R | ACK | data | NACK | STOP

主机先发 START 和 addr+W(从地址加写位),从机回 ACK 表示地址匹配;接着主机发 reg(要读的寄存器地址),从机回 ACK;然后主机不发 STOP,而是发 repeated START 把方向切到读,再发 addr+R(同一从地址加读位),从机回 ACK 后返回一个数据字节 data;主机收到该字节后回 NACK,表示 "这是最后一个字节,不要再发了",最后发 STOP 释放总线。

  • 结构体
c 复制代码
struct i2c_msg msgs[2] = {
    { .addr = addr, .flags = 0,        .len = 1,      .buf = &reg  },
    { .addr = addr, .flags = I2C_M_RD, .len = length, .buf = recv  },
};
  • read/write 与 I2C_RDWR 的差异

read/write 依赖 I2C_SLAVE_FORCE 设置的 client->addr,每次只能发一个方向、一个 i2c_msg,写寄存器再读要两次系统调用、中间可能插入其他传输,如下所示。
#mermaid-svg-7pjqhG2IaXUL2GTf{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-7pjqhG2IaXUL2GTf .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-7pjqhG2IaXUL2GTf .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-7pjqhG2IaXUL2GTf .error-icon{fill:#552222;}#mermaid-svg-7pjqhG2IaXUL2GTf .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-7pjqhG2IaXUL2GTf .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-7pjqhG2IaXUL2GTf .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-7pjqhG2IaXUL2GTf .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-7pjqhG2IaXUL2GTf .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-7pjqhG2IaXUL2GTf .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-7pjqhG2IaXUL2GTf .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-7pjqhG2IaXUL2GTf .marker{fill:#333333;stroke:#333333;}#mermaid-svg-7pjqhG2IaXUL2GTf .marker.cross{stroke:#333333;}#mermaid-svg-7pjqhG2IaXUL2GTf svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-7pjqhG2IaXUL2GTf p{margin:0;}#mermaid-svg-7pjqhG2IaXUL2GTf .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-7pjqhG2IaXUL2GTf .cluster-label text{fill:#333;}#mermaid-svg-7pjqhG2IaXUL2GTf .cluster-label span{color:#333;}#mermaid-svg-7pjqhG2IaXUL2GTf .cluster-label span p{background-color:transparent;}#mermaid-svg-7pjqhG2IaXUL2GTf .label text,#mermaid-svg-7pjqhG2IaXUL2GTf span{fill:#333;color:#333;}#mermaid-svg-7pjqhG2IaXUL2GTf .node rect,#mermaid-svg-7pjqhG2IaXUL2GTf .node circle,#mermaid-svg-7pjqhG2IaXUL2GTf .node ellipse,#mermaid-svg-7pjqhG2IaXUL2GTf .node polygon,#mermaid-svg-7pjqhG2IaXUL2GTf .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-7pjqhG2IaXUL2GTf .rough-node .label text,#mermaid-svg-7pjqhG2IaXUL2GTf .node .label text,#mermaid-svg-7pjqhG2IaXUL2GTf .image-shape .label,#mermaid-svg-7pjqhG2IaXUL2GTf .icon-shape .label{text-anchor:middle;}#mermaid-svg-7pjqhG2IaXUL2GTf .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-7pjqhG2IaXUL2GTf .rough-node .label,#mermaid-svg-7pjqhG2IaXUL2GTf .node .label,#mermaid-svg-7pjqhG2IaXUL2GTf .image-shape .label,#mermaid-svg-7pjqhG2IaXUL2GTf .icon-shape .label{text-align:center;}#mermaid-svg-7pjqhG2IaXUL2GTf .node.clickable{cursor:pointer;}#mermaid-svg-7pjqhG2IaXUL2GTf .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-7pjqhG2IaXUL2GTf .arrowheadPath{fill:#333333;}#mermaid-svg-7pjqhG2IaXUL2GTf .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-7pjqhG2IaXUL2GTf .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-7pjqhG2IaXUL2GTf .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-7pjqhG2IaXUL2GTf .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-7pjqhG2IaXUL2GTf .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-7pjqhG2IaXUL2GTf .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-7pjqhG2IaXUL2GTf .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-7pjqhG2IaXUL2GTf .cluster text{fill:#333;}#mermaid-svg-7pjqhG2IaXUL2GTf .cluster span{color:#333;}#mermaid-svg-7pjqhG2IaXUL2GTf div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-7pjqhG2IaXUL2GTf .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-7pjqhG2IaXUL2GTf rect.text{fill:none;stroke-width:0;}#mermaid-svg-7pjqhG2IaXUL2GTf .icon-shape,#mermaid-svg-7pjqhG2IaXUL2GTf .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-7pjqhG2IaXUL2GTf .icon-shape p,#mermaid-svg-7pjqhG2IaXUL2GTf .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-7pjqhG2IaXUL2GTf .icon-shape .label rect,#mermaid-svg-7pjqhG2IaXUL2GTf .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-7pjqhG2IaXUL2GTf .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-7pjqhG2IaXUL2GTf .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-7pjqhG2IaXUL2GTf :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} I2C_SLAVE_FORCE + read/write
ioctl 设置 client->addr
read 或 write
一次 i2c_transfer 只有 1 个 msg
写寄存器再读需两次系统调用
两次之间总线锁释放

I2C_RDWR 每个 i2c_msg 自带地址和方向,一次 ioctl 提交多个 msg、内核一次 i2c_transfer 用 repeated START 完成,因而能做原子的 "写寄存器 + 读数据" 复合事务,如下所示。
#mermaid-svg-hl4LbwEMAxQL0eeW{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-hl4LbwEMAxQL0eeW .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-hl4LbwEMAxQL0eeW .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-hl4LbwEMAxQL0eeW .error-icon{fill:#552222;}#mermaid-svg-hl4LbwEMAxQL0eeW .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-hl4LbwEMAxQL0eeW .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-hl4LbwEMAxQL0eeW .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-hl4LbwEMAxQL0eeW .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-hl4LbwEMAxQL0eeW .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-hl4LbwEMAxQL0eeW .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-hl4LbwEMAxQL0eeW .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-hl4LbwEMAxQL0eeW .marker{fill:#333333;stroke:#333333;}#mermaid-svg-hl4LbwEMAxQL0eeW .marker.cross{stroke:#333333;}#mermaid-svg-hl4LbwEMAxQL0eeW svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-hl4LbwEMAxQL0eeW p{margin:0;}#mermaid-svg-hl4LbwEMAxQL0eeW .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-hl4LbwEMAxQL0eeW .cluster-label text{fill:#333;}#mermaid-svg-hl4LbwEMAxQL0eeW .cluster-label span{color:#333;}#mermaid-svg-hl4LbwEMAxQL0eeW .cluster-label span p{background-color:transparent;}#mermaid-svg-hl4LbwEMAxQL0eeW .label text,#mermaid-svg-hl4LbwEMAxQL0eeW span{fill:#333;color:#333;}#mermaid-svg-hl4LbwEMAxQL0eeW .node rect,#mermaid-svg-hl4LbwEMAxQL0eeW .node circle,#mermaid-svg-hl4LbwEMAxQL0eeW .node ellipse,#mermaid-svg-hl4LbwEMAxQL0eeW .node polygon,#mermaid-svg-hl4LbwEMAxQL0eeW .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-hl4LbwEMAxQL0eeW .rough-node .label text,#mermaid-svg-hl4LbwEMAxQL0eeW .node .label text,#mermaid-svg-hl4LbwEMAxQL0eeW .image-shape .label,#mermaid-svg-hl4LbwEMAxQL0eeW .icon-shape .label{text-anchor:middle;}#mermaid-svg-hl4LbwEMAxQL0eeW .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-hl4LbwEMAxQL0eeW .rough-node .label,#mermaid-svg-hl4LbwEMAxQL0eeW .node .label,#mermaid-svg-hl4LbwEMAxQL0eeW .image-shape .label,#mermaid-svg-hl4LbwEMAxQL0eeW .icon-shape .label{text-align:center;}#mermaid-svg-hl4LbwEMAxQL0eeW .node.clickable{cursor:pointer;}#mermaid-svg-hl4LbwEMAxQL0eeW .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-hl4LbwEMAxQL0eeW .arrowheadPath{fill:#333333;}#mermaid-svg-hl4LbwEMAxQL0eeW .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-hl4LbwEMAxQL0eeW .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-hl4LbwEMAxQL0eeW .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-hl4LbwEMAxQL0eeW .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-hl4LbwEMAxQL0eeW .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-hl4LbwEMAxQL0eeW .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-hl4LbwEMAxQL0eeW .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-hl4LbwEMAxQL0eeW .cluster text{fill:#333;}#mermaid-svg-hl4LbwEMAxQL0eeW .cluster span{color:#333;}#mermaid-svg-hl4LbwEMAxQL0eeW div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-hl4LbwEMAxQL0eeW .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-hl4LbwEMAxQL0eeW rect.text{fill:none;stroke-width:0;}#mermaid-svg-hl4LbwEMAxQL0eeW .icon-shape,#mermaid-svg-hl4LbwEMAxQL0eeW .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-hl4LbwEMAxQL0eeW .icon-shape p,#mermaid-svg-hl4LbwEMAxQL0eeW .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-hl4LbwEMAxQL0eeW .icon-shape .label rect,#mermaid-svg-hl4LbwEMAxQL0eeW .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-hl4LbwEMAxQL0eeW .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-hl4LbwEMAxQL0eeW .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-hl4LbwEMAxQL0eeW :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} I2C_RDWR
每个 i2c_msg 自带 addr
一次 ioctl 提交多个 msg
一次 i2c_transfer 完成
repeated START
写寄存器再读是原子的

  • 总结
对比项 I2C_SLAVE_FORCE + read/write I2C_RDWR
从地址来源 client->addr 每个 msg.addr
一次系统调用 只能一个方向、一个 msg 可多个 msg、多方向、多地址
写寄存器再读 两次系统调用,中间可能插入其他传输 一次 i2c_transfer,repeated START,原子
是否需要用户层锁 需要保护"设地址 + 读写" 仍建议锁 fd,但地址不依赖 client->addr
灵活性 低 高
推荐场景 简单单次读写 寄存器读、复杂时序、多消息组合
  • I2C_RDWR 的原子传输时序图

从设备 I2C 适配器 内核 i2c-dev 用户层 从设备 I2C 适配器 内核 i2c-dev 用户层 #mermaid-svg-IiU8mMVO8UGiIno1{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-IiU8mMVO8UGiIno1 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-IiU8mMVO8UGiIno1 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-IiU8mMVO8UGiIno1 .error-icon{fill:#552222;}#mermaid-svg-IiU8mMVO8UGiIno1 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-IiU8mMVO8UGiIno1 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-IiU8mMVO8UGiIno1 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-IiU8mMVO8UGiIno1 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-IiU8mMVO8UGiIno1 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-IiU8mMVO8UGiIno1 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-IiU8mMVO8UGiIno1 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-IiU8mMVO8UGiIno1 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-IiU8mMVO8UGiIno1 .marker.cross{stroke:#333333;}#mermaid-svg-IiU8mMVO8UGiIno1 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-IiU8mMVO8UGiIno1 p{margin:0;}#mermaid-svg-IiU8mMVO8UGiIno1 .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-IiU8mMVO8UGiIno1 text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-IiU8mMVO8UGiIno1 .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-IiU8mMVO8UGiIno1 .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-IiU8mMVO8UGiIno1 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-IiU8mMVO8UGiIno1 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-IiU8mMVO8UGiIno1 #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-IiU8mMVO8UGiIno1 .sequenceNumber{fill:white;}#mermaid-svg-IiU8mMVO8UGiIno1 #sequencenumber{fill:#333;}#mermaid-svg-IiU8mMVO8UGiIno1 #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-IiU8mMVO8UGiIno1 .messageText{fill:#333;stroke:none;}#mermaid-svg-IiU8mMVO8UGiIno1 .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-IiU8mMVO8UGiIno1 .labelText,#mermaid-svg-IiU8mMVO8UGiIno1 .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-IiU8mMVO8UGiIno1 .loopText,#mermaid-svg-IiU8mMVO8UGiIno1 .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-IiU8mMVO8UGiIno1 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-IiU8mMVO8UGiIno1 .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-IiU8mMVO8UGiIno1 .noteText,#mermaid-svg-IiU8mMVO8UGiIno1 .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-IiU8mMVO8UGiIno1 .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-IiU8mMVO8UGiIno1 .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-IiU8mMVO8UGiIno1 .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-IiU8mMVO8UGiIno1 .actorPopupMenu{position:absolute;}#mermaid-svg-IiU8mMVO8UGiIno1 .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-IiU8mMVO8UGiIno1 .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-IiU8mMVO8UGiIno1 .actor-man circle,#mermaid-svg-IiU8mMVO8UGiIno1 line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-IiU8mMVO8UGiIno1 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} msgs0: 写 reg msgs1: 读 data alt ret == nmsgs ret \< 0 ioctl(fd, I2C_RDWR, &rdwr) i2cdev_ioctl_rdwr() i2c_transfer(adap, msgs, 2) START + addr + W ACK reg ACK repeated START + addr + R ACK 读数据 data + ACK/NACK STOP 返回完成的 msg 数 成功 -errno

关键点:

  • I2C_RDWR 不依赖 client->addr;
  • 内核用一次 i2c_transfer(adap, msgs, nmsgs);
  • 两个 msg 之间不发 STOP,而是 repeated START;
  • 中间不会被其他 I2C 传输插入,原子性更好。

(8)锁保护的是 "协议事务",需要锁住 I2C_SLAVE_FORCE + read,因为 I2C_SLAVE_FORCE 改的是 client->addr,read 用的是 client->addr,如下所示。

  • 用户层加锁
c 复制代码
pthread_mutex_lock(&g_i2c_dev_info[index].i2c_dev_mutex);

ret = ioctl(fd, I2C_SLAVE_FORCE, slave_addr);
n = read(fd, recv_data, recv_len);

pthread_mutex_unlock(&g_i2c_dev_info[index].i2c_dev_mutex);
  • 加锁前时序图

I2C 总线 共享 fd 线程 B 线程 A I2C 总线 共享 fd 线程 B 线程 A #mermaid-svg-W5lPSuotFWrIQkK3{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-W5lPSuotFWrIQkK3 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-W5lPSuotFWrIQkK3 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-W5lPSuotFWrIQkK3 .error-icon{fill:#552222;}#mermaid-svg-W5lPSuotFWrIQkK3 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-W5lPSuotFWrIQkK3 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-W5lPSuotFWrIQkK3 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-W5lPSuotFWrIQkK3 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-W5lPSuotFWrIQkK3 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-W5lPSuotFWrIQkK3 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-W5lPSuotFWrIQkK3 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-W5lPSuotFWrIQkK3 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-W5lPSuotFWrIQkK3 .marker.cross{stroke:#333333;}#mermaid-svg-W5lPSuotFWrIQkK3 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-W5lPSuotFWrIQkK3 p{margin:0;}#mermaid-svg-W5lPSuotFWrIQkK3 .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-W5lPSuotFWrIQkK3 text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-W5lPSuotFWrIQkK3 .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-W5lPSuotFWrIQkK3 .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-W5lPSuotFWrIQkK3 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-W5lPSuotFWrIQkK3 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-W5lPSuotFWrIQkK3 #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-W5lPSuotFWrIQkK3 .sequenceNumber{fill:white;}#mermaid-svg-W5lPSuotFWrIQkK3 #sequencenumber{fill:#333;}#mermaid-svg-W5lPSuotFWrIQkK3 #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-W5lPSuotFWrIQkK3 .messageText{fill:#333;stroke:none;}#mermaid-svg-W5lPSuotFWrIQkK3 .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-W5lPSuotFWrIQkK3 .labelText,#mermaid-svg-W5lPSuotFWrIQkK3 .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-W5lPSuotFWrIQkK3 .loopText,#mermaid-svg-W5lPSuotFWrIQkK3 .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-W5lPSuotFWrIQkK3 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-W5lPSuotFWrIQkK3 .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-W5lPSuotFWrIQkK3 .noteText,#mermaid-svg-W5lPSuotFWrIQkK3 .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-W5lPSuotFWrIQkK3 .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-W5lPSuotFWrIQkK3 .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-W5lPSuotFWrIQkK3 .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-W5lPSuotFWrIQkK3 .actorPopupMenu{position:absolute;}#mermaid-svg-W5lPSuotFWrIQkK3 .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-W5lPSuotFWrIQkK3 .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-W5lPSuotFWrIQkK3 .actor-man circle,#mermaid-svg-W5lPSuotFWrIQkK3 line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-W5lPSuotFWrIQkK3 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 没有锁时地址可能被覆盖 设 client->>addr = 0x50 设 client->>addr = 0x51 read() 实际使用 client->>addr = 0x51

  • 加锁后时序图

I2C 总线 共享 fd 线程 B 线程 A I2C 总线 共享 fd 线程 B 线程 A #mermaid-svg-8XrYDycztwHh95H5{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-8XrYDycztwHh95H5 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-8XrYDycztwHh95H5 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-8XrYDycztwHh95H5 .error-icon{fill:#552222;}#mermaid-svg-8XrYDycztwHh95H5 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-8XrYDycztwHh95H5 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-8XrYDycztwHh95H5 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-8XrYDycztwHh95H5 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-8XrYDycztwHh95H5 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-8XrYDycztwHh95H5 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-8XrYDycztwHh95H5 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-8XrYDycztwHh95H5 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-8XrYDycztwHh95H5 .marker.cross{stroke:#333333;}#mermaid-svg-8XrYDycztwHh95H5 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-8XrYDycztwHh95H5 p{margin:0;}#mermaid-svg-8XrYDycztwHh95H5 .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-8XrYDycztwHh95H5 text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-8XrYDycztwHh95H5 .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-8XrYDycztwHh95H5 .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-8XrYDycztwHh95H5 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-8XrYDycztwHh95H5 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-8XrYDycztwHh95H5 #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-8XrYDycztwHh95H5 .sequenceNumber{fill:white;}#mermaid-svg-8XrYDycztwHh95H5 #sequencenumber{fill:#333;}#mermaid-svg-8XrYDycztwHh95H5 #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-8XrYDycztwHh95H5 .messageText{fill:#333;stroke:none;}#mermaid-svg-8XrYDycztwHh95H5 .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-8XrYDycztwHh95H5 .labelText,#mermaid-svg-8XrYDycztwHh95H5 .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-8XrYDycztwHh95H5 .loopText,#mermaid-svg-8XrYDycztwHh95H5 .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-8XrYDycztwHh95H5 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-8XrYDycztwHh95H5 .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-8XrYDycztwHh95H5 .noteText,#mermaid-svg-8XrYDycztwHh95H5 .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-8XrYDycztwHh95H5 .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-8XrYDycztwHh95H5 .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-8XrYDycztwHh95H5 .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-8XrYDycztwHh95H5 .actorPopupMenu{position:absolute;}#mermaid-svg-8XrYDycztwHh95H5 .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-8XrYDycztwHh95H5 .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-8XrYDycztwHh95H5 .actor-man circle,#mermaid-svg-8XrYDycztwHh95H5 line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-8XrYDycztwHh95H5 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} lock 设 client->>addr = 0x50 read() 使用 0x50 传输 unlock lock 设 client->>addr = 0x51 read() 使用 0x51 传输 unlock

注意:这是进程内锁 ,跨进程的总线互斥由内核 i2c_transfer() 函数中的适配器锁保证。

(9)SMBus 块读与写时序与波形,如下所示。

  • 读波形
text 复制代码
START | addr+W | ACK | command(reg) | ACK |
repeated START | addr+R | ACK | length | ACK | data0 | ACK | ... | NACK | STOP

主机先以写方向发 START + addr+W,从机回 ACK 表示地址匹配;主机再发 command(reg)(通常是要读的寄存器/命令字节),从机回 ACK;随后主机不发 STOP,而是发 repeated START 把方向切到读,再发 addr+R,从机回 ACK 后先返回一个 length 字节 ,告诉主机后面有多少个数据字节,主机回 ACK 表示继续;接着从机逐个返回 data0、data1......,每收到一个字节主机回 ACK,... 表示按 length 重复"数据字节 + 主机 ACK";主机收到最后一个数据字节后回 NACK 表示"不要再发了",最后发 STOP 释放总线。

  • 读时序图

从设备 I2C 适配器 内核 i2c-dev 用户层 从设备 I2C 适配器 内核 i2c-dev 用户层 #mermaid-svg-JuABdeYj0nNJAbe9{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-JuABdeYj0nNJAbe9 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-JuABdeYj0nNJAbe9 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-JuABdeYj0nNJAbe9 .error-icon{fill:#552222;}#mermaid-svg-JuABdeYj0nNJAbe9 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-JuABdeYj0nNJAbe9 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-JuABdeYj0nNJAbe9 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-JuABdeYj0nNJAbe9 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-JuABdeYj0nNJAbe9 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-JuABdeYj0nNJAbe9 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-JuABdeYj0nNJAbe9 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-JuABdeYj0nNJAbe9 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-JuABdeYj0nNJAbe9 .marker.cross{stroke:#333333;}#mermaid-svg-JuABdeYj0nNJAbe9 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-JuABdeYj0nNJAbe9 p{margin:0;}#mermaid-svg-JuABdeYj0nNJAbe9 .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-JuABdeYj0nNJAbe9 text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-JuABdeYj0nNJAbe9 .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-JuABdeYj0nNJAbe9 .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-JuABdeYj0nNJAbe9 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-JuABdeYj0nNJAbe9 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-JuABdeYj0nNJAbe9 #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-JuABdeYj0nNJAbe9 .sequenceNumber{fill:white;}#mermaid-svg-JuABdeYj0nNJAbe9 #sequencenumber{fill:#333;}#mermaid-svg-JuABdeYj0nNJAbe9 #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-JuABdeYj0nNJAbe9 .messageText{fill:#333;stroke:none;}#mermaid-svg-JuABdeYj0nNJAbe9 .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-JuABdeYj0nNJAbe9 .labelText,#mermaid-svg-JuABdeYj0nNJAbe9 .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-JuABdeYj0nNJAbe9 .loopText,#mermaid-svg-JuABdeYj0nNJAbe9 .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-JuABdeYj0nNJAbe9 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-JuABdeYj0nNJAbe9 .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-JuABdeYj0nNJAbe9 .noteText,#mermaid-svg-JuABdeYj0nNJAbe9 .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-JuABdeYj0nNJAbe9 .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-JuABdeYj0nNJAbe9 .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-JuABdeYj0nNJAbe9 .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-JuABdeYj0nNJAbe9 .actorPopupMenu{position:absolute;}#mermaid-svg-JuABdeYj0nNJAbe9 .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-JuABdeYj0nNJAbe9 .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-JuABdeYj0nNJAbe9 .actor-man circle,#mermaid-svg-JuABdeYj0nNJAbe9 line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-JuABdeYj0nNJAbe9 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} ioctl(fd, I2C_SMBUS, &args) i2c_smbus_xfer() i2c_transfer(adap, 翻译后的 msgs, n) START + addr + W ACK command(reg) ACK repeated START + addr + R ACK 读 length + data data + ACK/NACK STOP 返回结果 data.block 或 -errno

  • 写波形
text 复制代码
START | addr<<1 | 0(W) | ACK | command(reg) | ACK | length | ACK | data0 | ACK | data1 | ACK | ... | dataN | ACK | STOP

主机发 START 后以写方向发 addr<<1|0,从机回 ACK 表示地址匹配;主机再发 command(reg)(通常是要写的寄存器/命令字节),从机回 ACK;接着主机发 length字节,告诉从机后面要写多少个数据字节,从机回 ACK;然后主机连续发送 data0、data1......dataN,... 表示按 length 重复"数据字节 + 从机 ACK",每个数据字节后都由从机回 ACK;所有数据发完后主机发 STOP 释放总线。

  • 写时序图

从设备 I2C 适配器 内核 i2c-dev 用户层 从设备 I2C 适配器 内核 i2c-dev 用户层 #mermaid-svg-74zjo0WF87pJw8Eg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-74zjo0WF87pJw8Eg .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-74zjo0WF87pJw8Eg .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-74zjo0WF87pJw8Eg .error-icon{fill:#552222;}#mermaid-svg-74zjo0WF87pJw8Eg .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-74zjo0WF87pJw8Eg .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-74zjo0WF87pJw8Eg .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-74zjo0WF87pJw8Eg .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-74zjo0WF87pJw8Eg .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-74zjo0WF87pJw8Eg .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-74zjo0WF87pJw8Eg .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-74zjo0WF87pJw8Eg .marker{fill:#333333;stroke:#333333;}#mermaid-svg-74zjo0WF87pJw8Eg .marker.cross{stroke:#333333;}#mermaid-svg-74zjo0WF87pJw8Eg svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-74zjo0WF87pJw8Eg p{margin:0;}#mermaid-svg-74zjo0WF87pJw8Eg .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-74zjo0WF87pJw8Eg text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-74zjo0WF87pJw8Eg .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-74zjo0WF87pJw8Eg .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-74zjo0WF87pJw8Eg .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-74zjo0WF87pJw8Eg .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-74zjo0WF87pJw8Eg #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-74zjo0WF87pJw8Eg .sequenceNumber{fill:white;}#mermaid-svg-74zjo0WF87pJw8Eg #sequencenumber{fill:#333;}#mermaid-svg-74zjo0WF87pJw8Eg #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-74zjo0WF87pJw8Eg .messageText{fill:#333;stroke:none;}#mermaid-svg-74zjo0WF87pJw8Eg .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-74zjo0WF87pJw8Eg .labelText,#mermaid-svg-74zjo0WF87pJw8Eg .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-74zjo0WF87pJw8Eg .loopText,#mermaid-svg-74zjo0WF87pJw8Eg .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-74zjo0WF87pJw8Eg .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-74zjo0WF87pJw8Eg .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-74zjo0WF87pJw8Eg .noteText,#mermaid-svg-74zjo0WF87pJw8Eg .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-74zjo0WF87pJw8Eg .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-74zjo0WF87pJw8Eg .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-74zjo0WF87pJw8Eg .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-74zjo0WF87pJw8Eg .actorPopupMenu{position:absolute;}#mermaid-svg-74zjo0WF87pJw8Eg .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-74zjo0WF87pJw8Eg .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-74zjo0WF87pJw8Eg .actor-man circle,#mermaid-svg-74zjo0WF87pJw8Eg line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-74zjo0WF87pJw8Eg :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} command=reg block0=length block1..=data alt 适配器有 smbus_xfer 适配器无 smbus_xfer ioctl(fd, I2C_SMBUS, &args) i2c_smbus_xfer() algo->>smbus_xfer() i2c_smbus_xfer_emulated() i2c_transfer(adap, msg, 1) START + addr + W ACK command(reg) ACK length ACK data0 ACK ... ACK dataN ACK STOP 返回结果 0 或 -errno

(四)ACK/NACK 与错误码的对应
#mermaid-svg-Roa168IHjWrYfkCN{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Roa168IHjWrYfkCN .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Roa168IHjWrYfkCN .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Roa168IHjWrYfkCN .error-icon{fill:#552222;}#mermaid-svg-Roa168IHjWrYfkCN .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Roa168IHjWrYfkCN .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Roa168IHjWrYfkCN .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Roa168IHjWrYfkCN .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Roa168IHjWrYfkCN .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Roa168IHjWrYfkCN .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Roa168IHjWrYfkCN .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Roa168IHjWrYfkCN .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Roa168IHjWrYfkCN .marker.cross{stroke:#333333;}#mermaid-svg-Roa168IHjWrYfkCN svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Roa168IHjWrYfkCN p{margin:0;}#mermaid-svg-Roa168IHjWrYfkCN .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Roa168IHjWrYfkCN .cluster-label text{fill:#333;}#mermaid-svg-Roa168IHjWrYfkCN .cluster-label span{color:#333;}#mermaid-svg-Roa168IHjWrYfkCN .cluster-label span p{background-color:transparent;}#mermaid-svg-Roa168IHjWrYfkCN .label text,#mermaid-svg-Roa168IHjWrYfkCN span{fill:#333;color:#333;}#mermaid-svg-Roa168IHjWrYfkCN .node rect,#mermaid-svg-Roa168IHjWrYfkCN .node circle,#mermaid-svg-Roa168IHjWrYfkCN .node ellipse,#mermaid-svg-Roa168IHjWrYfkCN .node polygon,#mermaid-svg-Roa168IHjWrYfkCN .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Roa168IHjWrYfkCN .rough-node .label text,#mermaid-svg-Roa168IHjWrYfkCN .node .label text,#mermaid-svg-Roa168IHjWrYfkCN .image-shape .label,#mermaid-svg-Roa168IHjWrYfkCN .icon-shape .label{text-anchor:middle;}#mermaid-svg-Roa168IHjWrYfkCN .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Roa168IHjWrYfkCN .rough-node .label,#mermaid-svg-Roa168IHjWrYfkCN .node .label,#mermaid-svg-Roa168IHjWrYfkCN .image-shape .label,#mermaid-svg-Roa168IHjWrYfkCN .icon-shape .label{text-align:center;}#mermaid-svg-Roa168IHjWrYfkCN .node.clickable{cursor:pointer;}#mermaid-svg-Roa168IHjWrYfkCN .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Roa168IHjWrYfkCN .arrowheadPath{fill:#333333;}#mermaid-svg-Roa168IHjWrYfkCN .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Roa168IHjWrYfkCN .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Roa168IHjWrYfkCN .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Roa168IHjWrYfkCN .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Roa168IHjWrYfkCN .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Roa168IHjWrYfkCN .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Roa168IHjWrYfkCN .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Roa168IHjWrYfkCN .cluster text{fill:#333;}#mermaid-svg-Roa168IHjWrYfkCN .cluster span{color:#333;}#mermaid-svg-Roa168IHjWrYfkCN div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Roa168IHjWrYfkCN .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Roa168IHjWrYfkCN rect.text{fill:none;stroke-width:0;}#mermaid-svg-Roa168IHjWrYfkCN .icon-shape,#mermaid-svg-Roa168IHjWrYfkCN .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Roa168IHjWrYfkCN .icon-shape p,#mermaid-svg-Roa168IHjWrYfkCN .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Roa168IHjWrYfkCN .icon-shape .label rect,#mermaid-svg-Roa168IHjWrYfkCN .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Roa168IHjWrYfkCN .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Roa168IHjWrYfkCN .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Roa168IHjWrYfkCN :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
适配器发出地址/数据
从机是否拉低 SDA
ACK,继续传输
NACK
i2c_transfer 返回错误
用户层收到 -ENXIO / -EREMOTEIO / -EIO

现象 总线含义 常见错误码
地址帧后从机拉低 SDA ACK,设备存在 正常
地址帧后无 ACK 无此地址设备或设备未上电 -ENXIO、-EREMOTEIO
数据字节后无 ACK 从机拒收或总线异常 -EIO、-EREMOTEIO
ioctl(I2C_RDWR) 返回 < nmsgs 部分消息失败 -EIO 或 -errno
read/write 返回字节数不符 传输不完整 -EIO

(三)总结

在 I2C 通信编程中,用户层到总线的整体调用链和关系,如下所示。

  • 调用链图

#mermaid-svg-TDzxbmlcUG48VIY2{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-TDzxbmlcUG48VIY2 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-TDzxbmlcUG48VIY2 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-TDzxbmlcUG48VIY2 .error-icon{fill:#552222;}#mermaid-svg-TDzxbmlcUG48VIY2 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-TDzxbmlcUG48VIY2 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-TDzxbmlcUG48VIY2 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-TDzxbmlcUG48VIY2 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-TDzxbmlcUG48VIY2 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-TDzxbmlcUG48VIY2 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-TDzxbmlcUG48VIY2 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-TDzxbmlcUG48VIY2 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-TDzxbmlcUG48VIY2 .marker.cross{stroke:#333333;}#mermaid-svg-TDzxbmlcUG48VIY2 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-TDzxbmlcUG48VIY2 p{margin:0;}#mermaid-svg-TDzxbmlcUG48VIY2 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-TDzxbmlcUG48VIY2 .cluster-label text{fill:#333;}#mermaid-svg-TDzxbmlcUG48VIY2 .cluster-label span{color:#333;}#mermaid-svg-TDzxbmlcUG48VIY2 .cluster-label span p{background-color:transparent;}#mermaid-svg-TDzxbmlcUG48VIY2 .label text,#mermaid-svg-TDzxbmlcUG48VIY2 span{fill:#333;color:#333;}#mermaid-svg-TDzxbmlcUG48VIY2 .node rect,#mermaid-svg-TDzxbmlcUG48VIY2 .node circle,#mermaid-svg-TDzxbmlcUG48VIY2 .node ellipse,#mermaid-svg-TDzxbmlcUG48VIY2 .node polygon,#mermaid-svg-TDzxbmlcUG48VIY2 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-TDzxbmlcUG48VIY2 .rough-node .label text,#mermaid-svg-TDzxbmlcUG48VIY2 .node .label text,#mermaid-svg-TDzxbmlcUG48VIY2 .image-shape .label,#mermaid-svg-TDzxbmlcUG48VIY2 .icon-shape .label{text-anchor:middle;}#mermaid-svg-TDzxbmlcUG48VIY2 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-TDzxbmlcUG48VIY2 .rough-node .label,#mermaid-svg-TDzxbmlcUG48VIY2 .node .label,#mermaid-svg-TDzxbmlcUG48VIY2 .image-shape .label,#mermaid-svg-TDzxbmlcUG48VIY2 .icon-shape .label{text-align:center;}#mermaid-svg-TDzxbmlcUG48VIY2 .node.clickable{cursor:pointer;}#mermaid-svg-TDzxbmlcUG48VIY2 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-TDzxbmlcUG48VIY2 .arrowheadPath{fill:#333333;}#mermaid-svg-TDzxbmlcUG48VIY2 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-TDzxbmlcUG48VIY2 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-TDzxbmlcUG48VIY2 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-TDzxbmlcUG48VIY2 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-TDzxbmlcUG48VIY2 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-TDzxbmlcUG48VIY2 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-TDzxbmlcUG48VIY2 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-TDzxbmlcUG48VIY2 .cluster text{fill:#333;}#mermaid-svg-TDzxbmlcUG48VIY2 .cluster span{color:#333;}#mermaid-svg-TDzxbmlcUG48VIY2 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-TDzxbmlcUG48VIY2 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-TDzxbmlcUG48VIY2 rect.text{fill:none;stroke-width:0;}#mermaid-svg-TDzxbmlcUG48VIY2 .icon-shape,#mermaid-svg-TDzxbmlcUG48VIY2 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-TDzxbmlcUG48VIY2 .icon-shape p,#mermaid-svg-TDzxbmlcUG48VIY2 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-TDzxbmlcUG48VIY2 .icon-shape .label rect,#mermaid-svg-TDzxbmlcUG48VIY2 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-TDzxbmlcUG48VIY2 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-TDzxbmlcUG48VIY2 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-TDzxbmlcUG48VIY2 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} I2C_SLAVE_FORCE + read/write
I2C_RDWR
I2C_SMBUS
用户层 i2c_xfer / read / write / SMBus
传输类型
构造 1 个 i2c_msg
构造 n 个 i2c_msg
构造 SMBus 参数
read() / write()
ioctl(fd, I2C_RDWR, &rdwr)
ioctl(fd, I2C_SMBUS, &args)
内核 i2cdev_read / i2cdev_write
内核 i2cdev_ioctl_rdwr
内核 i2cdev_ioctl_smbus
i2c_master_recv / i2c_master_send
i2c_transfer(adap, msgs, nmsgs)
i2c_smbus_xfer / i2c_smbus_xfer_emulated
适配器 algo->master_xfer()
SCL/SDA 波形:START、地址、ACK、数据、STOP

用户层代码 内核路径 总线动作
open("/dev/i2c-N") i2cdev_open 无
ioctl(fd, I2C_SLAVE_FORCE, addr) i2cdev_ioctl 无,只改 client->addr
read(fd, buf, len) i2c_master_recv → i2c_transfer(1 msg) START addr+R data... NACK STOP
write(fd, buf, len) i2c_master_send → i2c_transfer(1 msg) START addr+W data... STOP
ioctl(fd, I2C_RDWR, msgs) i2cdev_ioctl_rdwr → i2c_transfer(n msg) START addr+W reg ACK repeated START addr+R data NACK STOP
ioctl(fd, I2C_SMBUS, args) i2c_smbus_xfer / i2c_smbus_xfer_emulated 标准 SMBus 时序,最终也是 i2c_transfer
用户层 mutex 用户层 保护 client->addr 和复合事务不被线程拆开
  • 关系图

#mermaid-svg-jQiDWUbqaeRKqaeT{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-jQiDWUbqaeRKqaeT .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-jQiDWUbqaeRKqaeT .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-jQiDWUbqaeRKqaeT .error-icon{fill:#552222;}#mermaid-svg-jQiDWUbqaeRKqaeT .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-jQiDWUbqaeRKqaeT .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-jQiDWUbqaeRKqaeT .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-jQiDWUbqaeRKqaeT .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-jQiDWUbqaeRKqaeT .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-jQiDWUbqaeRKqaeT .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-jQiDWUbqaeRKqaeT .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-jQiDWUbqaeRKqaeT .marker{fill:#333333;stroke:#333333;}#mermaid-svg-jQiDWUbqaeRKqaeT .marker.cross{stroke:#333333;}#mermaid-svg-jQiDWUbqaeRKqaeT svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-jQiDWUbqaeRKqaeT p{margin:0;}#mermaid-svg-jQiDWUbqaeRKqaeT .edge{stroke-width:3;}#mermaid-svg-jQiDWUbqaeRKqaeT .section--1 rect,#mermaid-svg-jQiDWUbqaeRKqaeT .section--1 path,#mermaid-svg-jQiDWUbqaeRKqaeT .section--1 circle,#mermaid-svg-jQiDWUbqaeRKqaeT .section--1 polygon,#mermaid-svg-jQiDWUbqaeRKqaeT .section--1 path{fill:hsl(240, 100%, 76.2745098039%);}#mermaid-svg-jQiDWUbqaeRKqaeT .section--1 text{fill:#ffffff;}#mermaid-svg-jQiDWUbqaeRKqaeT .node-icon--1{font-size:40px;color:#ffffff;}#mermaid-svg-jQiDWUbqaeRKqaeT .section-edge--1{stroke:hsl(240, 100%, 76.2745098039%);}#mermaid-svg-jQiDWUbqaeRKqaeT .edge-depth--1{stroke-width:17;}#mermaid-svg-jQiDWUbqaeRKqaeT .section--1 line{stroke:hsl(60, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-jQiDWUbqaeRKqaeT .disabled,#mermaid-svg-jQiDWUbqaeRKqaeT .disabled circle,#mermaid-svg-jQiDWUbqaeRKqaeT .disabled text{fill:lightgray;}#mermaid-svg-jQiDWUbqaeRKqaeT .disabled text{fill:#efefef;}#mermaid-svg-jQiDWUbqaeRKqaeT .section-0 rect,#mermaid-svg-jQiDWUbqaeRKqaeT .section-0 path,#mermaid-svg-jQiDWUbqaeRKqaeT .section-0 circle,#mermaid-svg-jQiDWUbqaeRKqaeT .section-0 polygon,#mermaid-svg-jQiDWUbqaeRKqaeT .section-0 path{fill:hsl(60, 100%, 73.5294117647%);}#mermaid-svg-jQiDWUbqaeRKqaeT .section-0 text{fill:black;}#mermaid-svg-jQiDWUbqaeRKqaeT .node-icon-0{font-size:40px;color:black;}#mermaid-svg-jQiDWUbqaeRKqaeT .section-edge-0{stroke:hsl(60, 100%, 73.5294117647%);}#mermaid-svg-jQiDWUbqaeRKqaeT .edge-depth-0{stroke-width:14;}#mermaid-svg-jQiDWUbqaeRKqaeT .section-0 line{stroke:hsl(240, 100%, 83.5294117647%);stroke-width:3;}#mermaid-svg-jQiDWUbqaeRKqaeT .disabled,#mermaid-svg-jQiDWUbqaeRKqaeT .disabled circle,#mermaid-svg-jQiDWUbqaeRKqaeT .disabled text{fill:lightgray;}#mermaid-svg-jQiDWUbqaeRKqaeT .disabled text{fill:#efefef;}#mermaid-svg-jQiDWUbqaeRKqaeT .section-1 rect,#mermaid-svg-jQiDWUbqaeRKqaeT .section-1 path,#mermaid-svg-jQiDWUbqaeRKqaeT .section-1 circle,#mermaid-svg-jQiDWUbqaeRKqaeT .section-1 polygon,#mermaid-svg-jQiDWUbqaeRKqaeT .section-1 path{fill:hsl(80, 100%, 76.2745098039%);}#mermaid-svg-jQiDWUbqaeRKqaeT .section-1 text{fill:black;}#mermaid-svg-jQiDWUbqaeRKqaeT .node-icon-1{font-size:40px;color:black;}#mermaid-svg-jQiDWUbqaeRKqaeT .section-edge-1{stroke:hsl(80, 100%, 76.2745098039%);}#mermaid-svg-jQiDWUbqaeRKqaeT .edge-depth-1{stroke-width:11;}#mermaid-svg-jQiDWUbqaeRKqaeT .section-1 line{stroke:hsl(260, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-jQiDWUbqaeRKqaeT .disabled,#mermaid-svg-jQiDWUbqaeRKqaeT .disabled circle,#mermaid-svg-jQiDWUbqaeRKqaeT .disabled text{fill:lightgray;}#mermaid-svg-jQiDWUbqaeRKqaeT .disabled text{fill:#efefef;}#mermaid-svg-jQiDWUbqaeRKqaeT .section-2 rect,#mermaid-svg-jQiDWUbqaeRKqaeT .section-2 path,#mermaid-svg-jQiDWUbqaeRKqaeT .section-2 circle,#mermaid-svg-jQiDWUbqaeRKqaeT .section-2 polygon,#mermaid-svg-jQiDWUbqaeRKqaeT .section-2 path{fill:hsl(270, 100%, 76.2745098039%);}#mermaid-svg-jQiDWUbqaeRKqaeT .section-2 text{fill:#ffffff;}#mermaid-svg-jQiDWUbqaeRKqaeT .node-icon-2{font-size:40px;color:#ffffff;}#mermaid-svg-jQiDWUbqaeRKqaeT .section-edge-2{stroke:hsl(270, 100%, 76.2745098039%);}#mermaid-svg-jQiDWUbqaeRKqaeT .edge-depth-2{stroke-width:8;}#mermaid-svg-jQiDWUbqaeRKqaeT .section-2 line{stroke:hsl(90, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-jQiDWUbqaeRKqaeT .disabled,#mermaid-svg-jQiDWUbqaeRKqaeT .disabled circle,#mermaid-svg-jQiDWUbqaeRKqaeT .disabled text{fill:lightgray;}#mermaid-svg-jQiDWUbqaeRKqaeT .disabled text{fill:#efefef;}#mermaid-svg-jQiDWUbqaeRKqaeT .section-3 rect,#mermaid-svg-jQiDWUbqaeRKqaeT .section-3 path,#mermaid-svg-jQiDWUbqaeRKqaeT .section-3 circle,#mermaid-svg-jQiDWUbqaeRKqaeT .section-3 polygon,#mermaid-svg-jQiDWUbqaeRKqaeT .section-3 path{fill:hsl(300, 100%, 76.2745098039%);}#mermaid-svg-jQiDWUbqaeRKqaeT .section-3 text{fill:black;}#mermaid-svg-jQiDWUbqaeRKqaeT .node-icon-3{font-size:40px;color:black;}#mermaid-svg-jQiDWUbqaeRKqaeT .section-edge-3{stroke:hsl(300, 100%, 76.2745098039%);}#mermaid-svg-jQiDWUbqaeRKqaeT .edge-depth-3{stroke-width:5;}#mermaid-svg-jQiDWUbqaeRKqaeT .section-3 line{stroke:hsl(120, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-jQiDWUbqaeRKqaeT .disabled,#mermaid-svg-jQiDWUbqaeRKqaeT .disabled circle,#mermaid-svg-jQiDWUbqaeRKqaeT .disabled text{fill:lightgray;}#mermaid-svg-jQiDWUbqaeRKqaeT .disabled text{fill:#efefef;}#mermaid-svg-jQiDWUbqaeRKqaeT .section-4 rect,#mermaid-svg-jQiDWUbqaeRKqaeT .section-4 path,#mermaid-svg-jQiDWUbqaeRKqaeT .section-4 circle,#mermaid-svg-jQiDWUbqaeRKqaeT .section-4 polygon,#mermaid-svg-jQiDWUbqaeRKqaeT .section-4 path{fill:hsl(330, 100%, 76.2745098039%);}#mermaid-svg-jQiDWUbqaeRKqaeT .section-4 text{fill:black;}#mermaid-svg-jQiDWUbqaeRKqaeT .node-icon-4{font-size:40px;color:black;}#mermaid-svg-jQiDWUbqaeRKqaeT .section-edge-4{stroke:hsl(330, 100%, 76.2745098039%);}#mermaid-svg-jQiDWUbqaeRKqaeT .edge-depth-4{stroke-width:2;}#mermaid-svg-jQiDWUbqaeRKqaeT .section-4 line{stroke:hsl(150, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-jQiDWUbqaeRKqaeT .disabled,#mermaid-svg-jQiDWUbqaeRKqaeT .disabled circle,#mermaid-svg-jQiDWUbqaeRKqaeT .disabled text{fill:lightgray;}#mermaid-svg-jQiDWUbqaeRKqaeT .disabled text{fill:#efefef;}#mermaid-svg-jQiDWUbqaeRKqaeT .section-5 rect,#mermaid-svg-jQiDWUbqaeRKqaeT .section-5 path,#mermaid-svg-jQiDWUbqaeRKqaeT .section-5 circle,#mermaid-svg-jQiDWUbqaeRKqaeT .section-5 polygon,#mermaid-svg-jQiDWUbqaeRKqaeT .section-5 path{fill:hsl(0, 100%, 76.2745098039%);}#mermaid-svg-jQiDWUbqaeRKqaeT .section-5 text{fill:black;}#mermaid-svg-jQiDWUbqaeRKqaeT .node-icon-5{font-size:40px;color:black;}#mermaid-svg-jQiDWUbqaeRKqaeT .section-edge-5{stroke:hsl(0, 100%, 76.2745098039%);}#mermaid-svg-jQiDWUbqaeRKqaeT .edge-depth-5{stroke-width:-1;}#mermaid-svg-jQiDWUbqaeRKqaeT .section-5 line{stroke:hsl(180, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-jQiDWUbqaeRKqaeT .disabled,#mermaid-svg-jQiDWUbqaeRKqaeT .disabled circle,#mermaid-svg-jQiDWUbqaeRKqaeT .disabled text{fill:lightgray;}#mermaid-svg-jQiDWUbqaeRKqaeT .disabled text{fill:#efefef;}#mermaid-svg-jQiDWUbqaeRKqaeT .section-6 rect,#mermaid-svg-jQiDWUbqaeRKqaeT .section-6 path,#mermaid-svg-jQiDWUbqaeRKqaeT .section-6 circle,#mermaid-svg-jQiDWUbqaeRKqaeT .section-6 polygon,#mermaid-svg-jQiDWUbqaeRKqaeT .section-6 path{fill:hsl(30, 100%, 76.2745098039%);}#mermaid-svg-jQiDWUbqaeRKqaeT .section-6 text{fill:black;}#mermaid-svg-jQiDWUbqaeRKqaeT .node-icon-6{font-size:40px;color:black;}#mermaid-svg-jQiDWUbqaeRKqaeT .section-edge-6{stroke:hsl(30, 100%, 76.2745098039%);}#mermaid-svg-jQiDWUbqaeRKqaeT .edge-depth-6{stroke-width:-4;}#mermaid-svg-jQiDWUbqaeRKqaeT .section-6 line{stroke:hsl(210, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-jQiDWUbqaeRKqaeT .disabled,#mermaid-svg-jQiDWUbqaeRKqaeT .disabled circle,#mermaid-svg-jQiDWUbqaeRKqaeT .disabled text{fill:lightgray;}#mermaid-svg-jQiDWUbqaeRKqaeT .disabled text{fill:#efefef;}#mermaid-svg-jQiDWUbqaeRKqaeT .section-7 rect,#mermaid-svg-jQiDWUbqaeRKqaeT .section-7 path,#mermaid-svg-jQiDWUbqaeRKqaeT .section-7 circle,#mermaid-svg-jQiDWUbqaeRKqaeT .section-7 polygon,#mermaid-svg-jQiDWUbqaeRKqaeT .section-7 path{fill:hsl(90, 100%, 76.2745098039%);}#mermaid-svg-jQiDWUbqaeRKqaeT .section-7 text{fill:black;}#mermaid-svg-jQiDWUbqaeRKqaeT .node-icon-7{font-size:40px;color:black;}#mermaid-svg-jQiDWUbqaeRKqaeT .section-edge-7{stroke:hsl(90, 100%, 76.2745098039%);}#mermaid-svg-jQiDWUbqaeRKqaeT .edge-depth-7{stroke-width:-7;}#mermaid-svg-jQiDWUbqaeRKqaeT .section-7 line{stroke:hsl(270, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-jQiDWUbqaeRKqaeT .disabled,#mermaid-svg-jQiDWUbqaeRKqaeT .disabled circle,#mermaid-svg-jQiDWUbqaeRKqaeT .disabled text{fill:lightgray;}#mermaid-svg-jQiDWUbqaeRKqaeT .disabled text{fill:#efefef;}#mermaid-svg-jQiDWUbqaeRKqaeT .section-8 rect,#mermaid-svg-jQiDWUbqaeRKqaeT .section-8 path,#mermaid-svg-jQiDWUbqaeRKqaeT .section-8 circle,#mermaid-svg-jQiDWUbqaeRKqaeT .section-8 polygon,#mermaid-svg-jQiDWUbqaeRKqaeT .section-8 path{fill:hsl(150, 100%, 76.2745098039%);}#mermaid-svg-jQiDWUbqaeRKqaeT .section-8 text{fill:black;}#mermaid-svg-jQiDWUbqaeRKqaeT .node-icon-8{font-size:40px;color:black;}#mermaid-svg-jQiDWUbqaeRKqaeT .section-edge-8{stroke:hsl(150, 100%, 76.2745098039%);}#mermaid-svg-jQiDWUbqaeRKqaeT .edge-depth-8{stroke-width:-10;}#mermaid-svg-jQiDWUbqaeRKqaeT .section-8 line{stroke:hsl(330, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-jQiDWUbqaeRKqaeT .disabled,#mermaid-svg-jQiDWUbqaeRKqaeT .disabled circle,#mermaid-svg-jQiDWUbqaeRKqaeT .disabled text{fill:lightgray;}#mermaid-svg-jQiDWUbqaeRKqaeT .disabled text{fill:#efefef;}#mermaid-svg-jQiDWUbqaeRKqaeT .section-9 rect,#mermaid-svg-jQiDWUbqaeRKqaeT .section-9 path,#mermaid-svg-jQiDWUbqaeRKqaeT .section-9 circle,#mermaid-svg-jQiDWUbqaeRKqaeT .section-9 polygon,#mermaid-svg-jQiDWUbqaeRKqaeT .section-9 path{fill:hsl(180, 100%, 76.2745098039%);}#mermaid-svg-jQiDWUbqaeRKqaeT .section-9 text{fill:black;}#mermaid-svg-jQiDWUbqaeRKqaeT .node-icon-9{font-size:40px;color:black;}#mermaid-svg-jQiDWUbqaeRKqaeT .section-edge-9{stroke:hsl(180, 100%, 76.2745098039%);}#mermaid-svg-jQiDWUbqaeRKqaeT .edge-depth-9{stroke-width:-13;}#mermaid-svg-jQiDWUbqaeRKqaeT .section-9 line{stroke:hsl(0, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-jQiDWUbqaeRKqaeT .disabled,#mermaid-svg-jQiDWUbqaeRKqaeT .disabled circle,#mermaid-svg-jQiDWUbqaeRKqaeT .disabled text{fill:lightgray;}#mermaid-svg-jQiDWUbqaeRKqaeT .disabled text{fill:#efefef;}#mermaid-svg-jQiDWUbqaeRKqaeT .section-10 rect,#mermaid-svg-jQiDWUbqaeRKqaeT .section-10 path,#mermaid-svg-jQiDWUbqaeRKqaeT .section-10 circle,#mermaid-svg-jQiDWUbqaeRKqaeT .section-10 polygon,#mermaid-svg-jQiDWUbqaeRKqaeT .section-10 path{fill:hsl(210, 100%, 76.2745098039%);}#mermaid-svg-jQiDWUbqaeRKqaeT .section-10 text{fill:black;}#mermaid-svg-jQiDWUbqaeRKqaeT .node-icon-10{font-size:40px;color:black;}#mermaid-svg-jQiDWUbqaeRKqaeT .section-edge-10{stroke:hsl(210, 100%, 76.2745098039%);}#mermaid-svg-jQiDWUbqaeRKqaeT .edge-depth-10{stroke-width:-16;}#mermaid-svg-jQiDWUbqaeRKqaeT .section-10 line{stroke:hsl(30, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-jQiDWUbqaeRKqaeT .disabled,#mermaid-svg-jQiDWUbqaeRKqaeT .disabled circle,#mermaid-svg-jQiDWUbqaeRKqaeT .disabled text{fill:lightgray;}#mermaid-svg-jQiDWUbqaeRKqaeT .disabled text{fill:#efefef;}#mermaid-svg-jQiDWUbqaeRKqaeT .section-root rect,#mermaid-svg-jQiDWUbqaeRKqaeT .section-root path,#mermaid-svg-jQiDWUbqaeRKqaeT .section-root circle,#mermaid-svg-jQiDWUbqaeRKqaeT .section-root polygon{fill:hsl(240, 100%, 46.2745098039%);}#mermaid-svg-jQiDWUbqaeRKqaeT .section-root text{fill:#ffffff;}#mermaid-svg-jQiDWUbqaeRKqaeT .section-root span{color:#ffffff;}#mermaid-svg-jQiDWUbqaeRKqaeT .section-2 span{color:#ffffff;}#mermaid-svg-jQiDWUbqaeRKqaeT .icon-container{height:100%;display:flex;justify-content:center;align-items:center;}#mermaid-svg-jQiDWUbqaeRKqaeT .edge{fill:none;}#mermaid-svg-jQiDWUbqaeRKqaeT .mindmap-node-label{dy:1em;alignment-baseline:middle;text-anchor:middle;dominant-baseline:middle;text-align:center;}#mermaid-svg-jQiDWUbqaeRKqaeT :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} I2C 用户层通信
打开设备
open /dev/i2c-N
只建立 fd 到 adapter 的通道
无总线波形
设置从地址
I2C_SLAVE_FORCE
只改 client->addr
无 START/ACK
单消息传输
read
write
每次 1 个 i2c_msg
一次 i2c_transfer
多消息原子传输
I2C_RDWR
每个 msg 自带 addr
一次 i2c_transfer 提交多个 msg
repeated START
适合写寄存器再读
SMBus
I2C_SMBUS
内核翻译成 i2c_msg
最终走 i2c_transfer
错误
ENXIO
EREMOTEIO
EIO
对应 NACK 或传输不完整

I2C 通信的 "编码本质" 是构造 i2c_msg,交给内核 i2c_transfer() 函数,由适配器 master_xfer() 函数在 SCL/SDA 上产生 START、地址、ACK、数据和 STOP。I2C_SLAVE_FORCE 只是设置默认地址,I2C_RDWR 才是多消息原子事务的核心。

4.4 HM832IS 功率传感器实例

(一)硬件连接

  • 从机地址

从机地址由 A0/A1 引脚决定(7 位地址),必须与硬件原理图匹配,如下所示。

A1 A0 7 位地址 十六进制
GND GND 1000000 0x40(常用)
GND VS 1000001 0x41
GND SDA 1000010 0x42
GND SCL 1000011 0x43
VS GND 1000100 0x44
... ... ... ...

⚠️ 这里 HM832IS A0/A1 引脚均接 GND,故从机地址为 0x40。

  • 采样电阻
最大电流 推荐采样电阻 分流电压(满量程)
1 A 20 mΩ 20 mV
5 A 10 mΩ 50 mV
15 A 2 mΩ 30 mV
50 A 1 mΩ 50 mV

分流电压满量程 81.92 mV,建议工作点不超过 80%,以留出余量。采样电阻为 1.667mΩ,最大电流 15A。

(二)软件架构与流程

  • 架构图

#mermaid-svg-WkDHHJdSA8G6wthC{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-WkDHHJdSA8G6wthC .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-WkDHHJdSA8G6wthC .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-WkDHHJdSA8G6wthC .error-icon{fill:#552222;}#mermaid-svg-WkDHHJdSA8G6wthC .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-WkDHHJdSA8G6wthC .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-WkDHHJdSA8G6wthC .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-WkDHHJdSA8G6wthC .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-WkDHHJdSA8G6wthC .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-WkDHHJdSA8G6wthC .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-WkDHHJdSA8G6wthC .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-WkDHHJdSA8G6wthC .marker{fill:#333333;stroke:#333333;}#mermaid-svg-WkDHHJdSA8G6wthC .marker.cross{stroke:#333333;}#mermaid-svg-WkDHHJdSA8G6wthC svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-WkDHHJdSA8G6wthC p{margin:0;}#mermaid-svg-WkDHHJdSA8G6wthC .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-WkDHHJdSA8G6wthC .cluster-label text{fill:#333;}#mermaid-svg-WkDHHJdSA8G6wthC .cluster-label span{color:#333;}#mermaid-svg-WkDHHJdSA8G6wthC .cluster-label span p{background-color:transparent;}#mermaid-svg-WkDHHJdSA8G6wthC .label text,#mermaid-svg-WkDHHJdSA8G6wthC span{fill:#333;color:#333;}#mermaid-svg-WkDHHJdSA8G6wthC .node rect,#mermaid-svg-WkDHHJdSA8G6wthC .node circle,#mermaid-svg-WkDHHJdSA8G6wthC .node ellipse,#mermaid-svg-WkDHHJdSA8G6wthC .node polygon,#mermaid-svg-WkDHHJdSA8G6wthC .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-WkDHHJdSA8G6wthC .rough-node .label text,#mermaid-svg-WkDHHJdSA8G6wthC .node .label text,#mermaid-svg-WkDHHJdSA8G6wthC .image-shape .label,#mermaid-svg-WkDHHJdSA8G6wthC .icon-shape .label{text-anchor:middle;}#mermaid-svg-WkDHHJdSA8G6wthC .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-WkDHHJdSA8G6wthC .rough-node .label,#mermaid-svg-WkDHHJdSA8G6wthC .node .label,#mermaid-svg-WkDHHJdSA8G6wthC .image-shape .label,#mermaid-svg-WkDHHJdSA8G6wthC .icon-shape .label{text-align:center;}#mermaid-svg-WkDHHJdSA8G6wthC .node.clickable{cursor:pointer;}#mermaid-svg-WkDHHJdSA8G6wthC .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-WkDHHJdSA8G6wthC .arrowheadPath{fill:#333333;}#mermaid-svg-WkDHHJdSA8G6wthC .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-WkDHHJdSA8G6wthC .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-WkDHHJdSA8G6wthC .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-WkDHHJdSA8G6wthC .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-WkDHHJdSA8G6wthC .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-WkDHHJdSA8G6wthC .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-WkDHHJdSA8G6wthC .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-WkDHHJdSA8G6wthC .cluster text{fill:#333;}#mermaid-svg-WkDHHJdSA8G6wthC .cluster span{color:#333;}#mermaid-svg-WkDHHJdSA8G6wthC div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-WkDHHJdSA8G6wthC .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-WkDHHJdSA8G6wthC rect.text{fill:none;stroke-width:0;}#mermaid-svg-WkDHHJdSA8G6wthC .icon-shape,#mermaid-svg-WkDHHJdSA8G6wthC .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-WkDHHJdSA8G6wthC .icon-shape p,#mermaid-svg-WkDHHJdSA8G6wthC .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-WkDHHJdSA8G6wthC .icon-shape .label rect,#mermaid-svg-WkDHHJdSA8G6wthC .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-WkDHHJdSA8G6wthC .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-WkDHHJdSA8G6wthC .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-WkDHHJdSA8G6wthC :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} HM832IS 硬件
平台 I2C 接口层 (i2c_api.c)
传感器驱动层 (hm832is.c)
应用层 (main.c)
周期采集
数据打印
业务逻辑
初始化 hm832is_init
校准计算
寄存器读写
单位换算
i2c_write
i2c_read
i2c_rdwr_xfer
寄存器 0x00~0xFF
I²C 从机 0x40

  • 初始化流程图

#mermaid-svg-jnuanAN3T0VzhqDj{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-jnuanAN3T0VzhqDj .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-jnuanAN3T0VzhqDj .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-jnuanAN3T0VzhqDj .error-icon{fill:#552222;}#mermaid-svg-jnuanAN3T0VzhqDj .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-jnuanAN3T0VzhqDj .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-jnuanAN3T0VzhqDj .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-jnuanAN3T0VzhqDj .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-jnuanAN3T0VzhqDj .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-jnuanAN3T0VzhqDj .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-jnuanAN3T0VzhqDj .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-jnuanAN3T0VzhqDj .marker{fill:#333333;stroke:#333333;}#mermaid-svg-jnuanAN3T0VzhqDj .marker.cross{stroke:#333333;}#mermaid-svg-jnuanAN3T0VzhqDj svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-jnuanAN3T0VzhqDj p{margin:0;}#mermaid-svg-jnuanAN3T0VzhqDj .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-jnuanAN3T0VzhqDj .cluster-label text{fill:#333;}#mermaid-svg-jnuanAN3T0VzhqDj .cluster-label span{color:#333;}#mermaid-svg-jnuanAN3T0VzhqDj .cluster-label span p{background-color:transparent;}#mermaid-svg-jnuanAN3T0VzhqDj .label text,#mermaid-svg-jnuanAN3T0VzhqDj span{fill:#333;color:#333;}#mermaid-svg-jnuanAN3T0VzhqDj .node rect,#mermaid-svg-jnuanAN3T0VzhqDj .node circle,#mermaid-svg-jnuanAN3T0VzhqDj .node ellipse,#mermaid-svg-jnuanAN3T0VzhqDj .node polygon,#mermaid-svg-jnuanAN3T0VzhqDj .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-jnuanAN3T0VzhqDj .rough-node .label text,#mermaid-svg-jnuanAN3T0VzhqDj .node .label text,#mermaid-svg-jnuanAN3T0VzhqDj .image-shape .label,#mermaid-svg-jnuanAN3T0VzhqDj .icon-shape .label{text-anchor:middle;}#mermaid-svg-jnuanAN3T0VzhqDj .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-jnuanAN3T0VzhqDj .rough-node .label,#mermaid-svg-jnuanAN3T0VzhqDj .node .label,#mermaid-svg-jnuanAN3T0VzhqDj .image-shape .label,#mermaid-svg-jnuanAN3T0VzhqDj .icon-shape .label{text-align:center;}#mermaid-svg-jnuanAN3T0VzhqDj .node.clickable{cursor:pointer;}#mermaid-svg-jnuanAN3T0VzhqDj .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-jnuanAN3T0VzhqDj .arrowheadPath{fill:#333333;}#mermaid-svg-jnuanAN3T0VzhqDj .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-jnuanAN3T0VzhqDj .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-jnuanAN3T0VzhqDj .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-jnuanAN3T0VzhqDj .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-jnuanAN3T0VzhqDj .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-jnuanAN3T0VzhqDj .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-jnuanAN3T0VzhqDj .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-jnuanAN3T0VzhqDj .cluster text{fill:#333;}#mermaid-svg-jnuanAN3T0VzhqDj .cluster span{color:#333;}#mermaid-svg-jnuanAN3T0VzhqDj div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-jnuanAN3T0VzhqDj .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-jnuanAN3T0VzhqDj rect.text{fill:none;stroke-width:0;}#mermaid-svg-jnuanAN3T0VzhqDj .icon-shape,#mermaid-svg-jnuanAN3T0VzhqDj .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-jnuanAN3T0VzhqDj .icon-shape p,#mermaid-svg-jnuanAN3T0VzhqDj .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-jnuanAN3T0VzhqDj .icon-shape .label rect,#mermaid-svg-jnuanAN3T0VzhqDj .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-jnuanAN3T0VzhqDj .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-jnuanAN3T0VzhqDj .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-jnuanAN3T0VzhqDj :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 否
是
否
是
否
是
否
是
否
是
hm832is_init 入口

  1. 保存参数

i2c / addr7 / shuntRes
2. 读制造商 ID 0xFE
通信成功?
返回 -2

通信失败
ID == 0x5959?
3. 计算校准值

CAL = 0.00512 / (LSB × R)
4. 写校准寄存器 0x05
写入成功?
返回 -3

校准写入失败
5. 回读校准值

反推 currentLSB / powerLSB
回读成功?
6. 写配置寄存器 0x00 = 0x4527
写入成功?
返回 -4

配置写入失败
7. 延时 30 ms

等待首次转换完成
8. calibrated = 1
返回 0 成功

  • 采集流程图

#mermaid-svg-SV55wJYdRNkjMPuQ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-SV55wJYdRNkjMPuQ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-SV55wJYdRNkjMPuQ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-SV55wJYdRNkjMPuQ .error-icon{fill:#552222;}#mermaid-svg-SV55wJYdRNkjMPuQ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-SV55wJYdRNkjMPuQ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-SV55wJYdRNkjMPuQ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-SV55wJYdRNkjMPuQ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-SV55wJYdRNkjMPuQ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-SV55wJYdRNkjMPuQ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-SV55wJYdRNkjMPuQ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-SV55wJYdRNkjMPuQ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-SV55wJYdRNkjMPuQ .marker.cross{stroke:#333333;}#mermaid-svg-SV55wJYdRNkjMPuQ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-SV55wJYdRNkjMPuQ p{margin:0;}#mermaid-svg-SV55wJYdRNkjMPuQ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-SV55wJYdRNkjMPuQ .cluster-label text{fill:#333;}#mermaid-svg-SV55wJYdRNkjMPuQ .cluster-label span{color:#333;}#mermaid-svg-SV55wJYdRNkjMPuQ .cluster-label span p{background-color:transparent;}#mermaid-svg-SV55wJYdRNkjMPuQ .label text,#mermaid-svg-SV55wJYdRNkjMPuQ span{fill:#333;color:#333;}#mermaid-svg-SV55wJYdRNkjMPuQ .node rect,#mermaid-svg-SV55wJYdRNkjMPuQ .node circle,#mermaid-svg-SV55wJYdRNkjMPuQ .node ellipse,#mermaid-svg-SV55wJYdRNkjMPuQ .node polygon,#mermaid-svg-SV55wJYdRNkjMPuQ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-SV55wJYdRNkjMPuQ .rough-node .label text,#mermaid-svg-SV55wJYdRNkjMPuQ .node .label text,#mermaid-svg-SV55wJYdRNkjMPuQ .image-shape .label,#mermaid-svg-SV55wJYdRNkjMPuQ .icon-shape .label{text-anchor:middle;}#mermaid-svg-SV55wJYdRNkjMPuQ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-SV55wJYdRNkjMPuQ .rough-node .label,#mermaid-svg-SV55wJYdRNkjMPuQ .node .label,#mermaid-svg-SV55wJYdRNkjMPuQ .image-shape .label,#mermaid-svg-SV55wJYdRNkjMPuQ .icon-shape .label{text-align:center;}#mermaid-svg-SV55wJYdRNkjMPuQ .node.clickable{cursor:pointer;}#mermaid-svg-SV55wJYdRNkjMPuQ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-SV55wJYdRNkjMPuQ .arrowheadPath{fill:#333333;}#mermaid-svg-SV55wJYdRNkjMPuQ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-SV55wJYdRNkjMPuQ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-SV55wJYdRNkjMPuQ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-SV55wJYdRNkjMPuQ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-SV55wJYdRNkjMPuQ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-SV55wJYdRNkjMPuQ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-SV55wJYdRNkjMPuQ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-SV55wJYdRNkjMPuQ .cluster text{fill:#333;}#mermaid-svg-SV55wJYdRNkjMPuQ .cluster span{color:#333;}#mermaid-svg-SV55wJYdRNkjMPuQ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-SV55wJYdRNkjMPuQ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-SV55wJYdRNkjMPuQ rect.text{fill:none;stroke-width:0;}#mermaid-svg-SV55wJYdRNkjMPuQ .icon-shape,#mermaid-svg-SV55wJYdRNkjMPuQ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-SV55wJYdRNkjMPuQ .icon-shape p,#mermaid-svg-SV55wJYdRNkjMPuQ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-SV55wJYdRNkjMPuQ .icon-shape .label rect,#mermaid-svg-SV55wJYdRNkjMPuQ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-SV55wJYdRNkjMPuQ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-SV55wJYdRNkjMPuQ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-SV55wJYdRNkjMPuQ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 否
是
采集周期到达
读总线电压 0x02

V = raw × 1.25 mV
读电流寄存器 0x04

I = raw × currentLSB
读功率寄存器 0x03

P = raw × powerLSB
数据合理?
上报错误 / 重试
输出 / 存储 / 上报
等待下一周期

  • 完整数据流程图

#mermaid-svg-AepMqecj8moStzmv{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-AepMqecj8moStzmv .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-AepMqecj8moStzmv .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-AepMqecj8moStzmv .error-icon{fill:#552222;}#mermaid-svg-AepMqecj8moStzmv .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-AepMqecj8moStzmv .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-AepMqecj8moStzmv .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-AepMqecj8moStzmv .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-AepMqecj8moStzmv .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-AepMqecj8moStzmv .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-AepMqecj8moStzmv .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-AepMqecj8moStzmv .marker{fill:#333333;stroke:#333333;}#mermaid-svg-AepMqecj8moStzmv .marker.cross{stroke:#333333;}#mermaid-svg-AepMqecj8moStzmv svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-AepMqecj8moStzmv p{margin:0;}#mermaid-svg-AepMqecj8moStzmv .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-AepMqecj8moStzmv .cluster-label text{fill:#333;}#mermaid-svg-AepMqecj8moStzmv .cluster-label span{color:#333;}#mermaid-svg-AepMqecj8moStzmv .cluster-label span p{background-color:transparent;}#mermaid-svg-AepMqecj8moStzmv .label text,#mermaid-svg-AepMqecj8moStzmv span{fill:#333;color:#333;}#mermaid-svg-AepMqecj8moStzmv .node rect,#mermaid-svg-AepMqecj8moStzmv .node circle,#mermaid-svg-AepMqecj8moStzmv .node ellipse,#mermaid-svg-AepMqecj8moStzmv .node polygon,#mermaid-svg-AepMqecj8moStzmv .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-AepMqecj8moStzmv .rough-node .label text,#mermaid-svg-AepMqecj8moStzmv .node .label text,#mermaid-svg-AepMqecj8moStzmv .image-shape .label,#mermaid-svg-AepMqecj8moStzmv .icon-shape .label{text-anchor:middle;}#mermaid-svg-AepMqecj8moStzmv .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-AepMqecj8moStzmv .rough-node .label,#mermaid-svg-AepMqecj8moStzmv .node .label,#mermaid-svg-AepMqecj8moStzmv .image-shape .label,#mermaid-svg-AepMqecj8moStzmv .icon-shape .label{text-align:center;}#mermaid-svg-AepMqecj8moStzmv .node.clickable{cursor:pointer;}#mermaid-svg-AepMqecj8moStzmv .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-AepMqecj8moStzmv .arrowheadPath{fill:#333333;}#mermaid-svg-AepMqecj8moStzmv .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-AepMqecj8moStzmv .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-AepMqecj8moStzmv .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-AepMqecj8moStzmv .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-AepMqecj8moStzmv .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-AepMqecj8moStzmv .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-AepMqecj8moStzmv .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-AepMqecj8moStzmv .cluster text{fill:#333;}#mermaid-svg-AepMqecj8moStzmv .cluster span{color:#333;}#mermaid-svg-AepMqecj8moStzmv div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-AepMqecj8moStzmv .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-AepMqecj8moStzmv rect.text{fill:none;stroke-width:0;}#mermaid-svg-AepMqecj8moStzmv .icon-shape,#mermaid-svg-AepMqecj8moStzmv .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-AepMqecj8moStzmv .icon-shape p,#mermaid-svg-AepMqecj8moStzmv .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-AepMqecj8moStzmv .icon-shape .label rect,#mermaid-svg-AepMqecj8moStzmv .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-AepMqecj8moStzmv .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-AepMqecj8moStzmv .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-AepMqecj8moStzmv :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 应用层
驱动层
I²C 总线
HM832IS 寄存器
HM832IS 内部 ADC
采样层
物理层
负载电流 I

(0~15 A)
供电电压 VBUS

(0~36 V)
采样电阻 Rshunt

1.667 mΩ
分流电压 V_SHUNT

I × R = 0~25 mV
ΔΣ ADC

500kHz 采样
16bit 量化
0x01 分流电压

2.5 µV/LSB
0x02 总线电压

1.25 mV/LSB
0x04 电流

500.1 µA/LSB
0x03 功率

12.503 mW/LSB
0x05 校准寄存器

CAL=6142
0x00 配置寄存器

0x4527
i2c_write_read

写指针+Sr+读
hm832is_read_reg
currentLSB

powerLSB
hm832is_read_voltage
hm832is_read_current
hm832is_read_power
float v, i, p
printf / 上报

  • 数据换算链路图(核心公式)

#mermaid-svg-wB5oQzMzFaYAXdX2{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-wB5oQzMzFaYAXdX2 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-wB5oQzMzFaYAXdX2 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-wB5oQzMzFaYAXdX2 .error-icon{fill:#552222;}#mermaid-svg-wB5oQzMzFaYAXdX2 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-wB5oQzMzFaYAXdX2 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-wB5oQzMzFaYAXdX2 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-wB5oQzMzFaYAXdX2 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-wB5oQzMzFaYAXdX2 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-wB5oQzMzFaYAXdX2 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-wB5oQzMzFaYAXdX2 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-wB5oQzMzFaYAXdX2 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-wB5oQzMzFaYAXdX2 .marker.cross{stroke:#333333;}#mermaid-svg-wB5oQzMzFaYAXdX2 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-wB5oQzMzFaYAXdX2 p{margin:0;}#mermaid-svg-wB5oQzMzFaYAXdX2 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-wB5oQzMzFaYAXdX2 .cluster-label text{fill:#333;}#mermaid-svg-wB5oQzMzFaYAXdX2 .cluster-label span{color:#333;}#mermaid-svg-wB5oQzMzFaYAXdX2 .cluster-label span p{background-color:transparent;}#mermaid-svg-wB5oQzMzFaYAXdX2 .label text,#mermaid-svg-wB5oQzMzFaYAXdX2 span{fill:#333;color:#333;}#mermaid-svg-wB5oQzMzFaYAXdX2 .node rect,#mermaid-svg-wB5oQzMzFaYAXdX2 .node circle,#mermaid-svg-wB5oQzMzFaYAXdX2 .node ellipse,#mermaid-svg-wB5oQzMzFaYAXdX2 .node polygon,#mermaid-svg-wB5oQzMzFaYAXdX2 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-wB5oQzMzFaYAXdX2 .rough-node .label text,#mermaid-svg-wB5oQzMzFaYAXdX2 .node .label text,#mermaid-svg-wB5oQzMzFaYAXdX2 .image-shape .label,#mermaid-svg-wB5oQzMzFaYAXdX2 .icon-shape .label{text-anchor:middle;}#mermaid-svg-wB5oQzMzFaYAXdX2 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-wB5oQzMzFaYAXdX2 .rough-node .label,#mermaid-svg-wB5oQzMzFaYAXdX2 .node .label,#mermaid-svg-wB5oQzMzFaYAXdX2 .image-shape .label,#mermaid-svg-wB5oQzMzFaYAXdX2 .icon-shape .label{text-align:center;}#mermaid-svg-wB5oQzMzFaYAXdX2 .node.clickable{cursor:pointer;}#mermaid-svg-wB5oQzMzFaYAXdX2 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-wB5oQzMzFaYAXdX2 .arrowheadPath{fill:#333333;}#mermaid-svg-wB5oQzMzFaYAXdX2 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-wB5oQzMzFaYAXdX2 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-wB5oQzMzFaYAXdX2 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-wB5oQzMzFaYAXdX2 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-wB5oQzMzFaYAXdX2 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-wB5oQzMzFaYAXdX2 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-wB5oQzMzFaYAXdX2 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-wB5oQzMzFaYAXdX2 .cluster text{fill:#333;}#mermaid-svg-wB5oQzMzFaYAXdX2 .cluster span{color:#333;}#mermaid-svg-wB5oQzMzFaYAXdX2 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-wB5oQzMzFaYAXdX2 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-wB5oQzMzFaYAXdX2 rect.text{fill:none;stroke-width:0;}#mermaid-svg-wB5oQzMzFaYAXdX2 .icon-shape,#mermaid-svg-wB5oQzMzFaYAXdX2 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-wB5oQzMzFaYAXdX2 .icon-shape p,#mermaid-svg-wB5oQzMzFaYAXdX2 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-wB5oQzMzFaYAXdX2 .icon-shape .label rect,#mermaid-svg-wB5oQzMzFaYAXdX2 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-wB5oQzMzFaYAXdX2 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-wB5oQzMzFaYAXdX2 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-wB5oQzMzFaYAXdX2 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 物理量
V_SHUNT = I × Rshunt

15A × 1.667mΩ = 25 mV
V_BUS

直接测量 0~36V
分流寄存器 raw

= V_SHUNT / 2.5µV

= 10000
总线寄存器 raw

= V_BUS / 1.25mV

= 9596
校准寄存器

CAL = 6142
用于功率
电流寄存器 raw

= (raw1 × CAL) / 2048

= 29980 → 超量程?

实际: 6179
功率寄存器 raw

= (raw3 × raw2) / 20000

= 2966
V = raw2 × 1.25mV

= 11.995 V
I = raw3 × currentLSB

= 6179 × 500.1µA

= 3.090 A
P = raw4 × powerLSB

= 2966 × 12.503mW

= 37.08 W
输出

(三)功率采集功能实现

  • hm832is_app.h 头文件
c 复制代码
/**
 * @file    hm832is_app.h
 * @brief   HM832IS 应用层采集接口(基于已有 i2c_write / i2c_read)
 */

#ifndef HM832IS_APP_H
#define HM832IS_APP_H

#include "i2c_api.h"

/* 平台类型(若已在 i2c_api.h 中定义则删除这三行) */
typedef unsigned char  U8;
typedef unsigned short U16;
typedef unsigned int   U32;

/* ======================= 寄存器地址 ======================= */
#define HM832IS_REG_CONFIG        0x00
#define HM832IS_REG_SHUNT_V       0x01
#define HM832IS_REG_BUS_V         0x02
#define HM832IS_REG_POWER         0x03
#define HM832IS_REG_CURRENT       0x04
#define HM832IS_REG_CALIBRATION   0x05
#define HM832IS_REG_MASK_ENABLE   0x06
#define HM832IS_REG_ALERT_LIMIT   0x07
#define HM832IS_REG_MANUFACTURER  0xFE
#define HM832IS_REG_DIE_ID        0xFF

/* ======================= 内部常数 ======================= */
#define HM832IS_INTERNAL_SCALE    0.00512f
#define HM832IS_BUS_V_LSB         0.00125f      /* 1.25 mV */
#define HM832IS_SHUNT_V_LSB       0.0000025f    /* 2.5 µV */

/* 默认配置:连续模式,分流+总线,1.1ms 转换,4 次平均 */
#define HM832IS_CONFIG_CONTINUOUS 0x4527

/* 默认从机地址(A0=A1=GND) */
#define HM832IS_DEFAULT_ADDR      0x40

/* ======================= 硬件参数 ======================= */
#define HM832IS_SHUNT_RES_OHM     0.001667f   /* 采样电阻 1.667 mΩ */
#define HM832IS_MAX_CURRENT_A     15.0f       /* 最大电流 15 A */
#define HM832IS_CURRENT_LSB       0.0005f     /* 推荐 500 µA/bit */

/* ======================= 派生参数 ======================= */
/* CAL = 0.00512 / (0.0005 × 0.001667) ≈ 6142 */
/* currentLSB = 0.00512 / (6142 × 0.001667) ≈ 500.1 µA/bit */
/* powerLSB   = 500.1 µA × 25 = 12.503 mW/bit */
/* 满量程电流 = 32767 × 500.1 µA ≈ 16.38 A */
/* 满量程功率(12V)= 16.38 × 12 ≈ 196.6 W */

/* ======================= 设备对象 ======================= */
typedef struct {
    I2C_INDEX i2c;          /* 平台 I2C 总线索引 */
    U16       addr7;        /* 7 位从机地址 */
    float     shuntRes;     /* 采样电阻(Ω) */
    float     currentLSB;   /* 电流 LSB(A/bit) */
    float     powerLSB;     /* 功率 LSB(W/bit) */
    int       calibrated;   /* 校准完成标志 */
} hm832is_dev;

/* ======================= 公开接口 ======================= */

int hm832is_init(hm832is_dev *dev, I2C_INDEX i2c, U16 addr7, float shuntRes);
int hm832is_read_voltage(hm832is_dev *dev, float *voltageV);
int hm832is_read_current(hm832is_dev *dev, float *currentA);
int hm832is_read_power(hm832is_dev *dev, float *powerW);
int hm832is_read_shunt(hm832is_dev *dev, float *shuntV);
int hm832is_read_reg(hm832is_dev *dev, U8 reg, U16 *value);
int hm832is_write_reg(hm832is_dev *dev, U8 reg, U16 value);

#endif /* HM832IS_APP_H */
  • hm832is_app.c 文件
c 复制代码
/**
 * @file    hm832is_app.c
 * @brief   HM832IS 应用层采集实现(复用平台 i2c_write / i2c_read / i2c_rdwr_xfer)
 */

#include "hm832is_app.h"
#include <stdio.h>
#include <string.h>

/* 延时宏:若平台无 usleep 请替换 */
#include <unistd.h>
#define HM832IS_DELAY_MS(ms)   usleep((ms) * 1000)

/* ======================= 寄存器读写 ======================= */

/**
 * @brief 写 16 位寄存器(大端)
 * 报文: [reg][data_H][data_L]  → 一次 i2c_write
 */
int hm832is_write_reg(hm832is_dev *dev, U8 reg, U16 value)
{
    U8 buf[3] = {
        reg,
        (U8)(value >> 8),
        (U8)(value & 0xFF)
    };
    return (i2c_write(dev->i2c, dev->addr7, buf, 3) == 0) ? 0 : -1;
}

/**
 * @brief 读 16 位寄存器(组合传输,无 STOP)
 * 报文: i2c_write_read(reg, 1) → 读 2 字节
 */
int hm832is_read_reg(hm832is_dev *dev, U8 reg, U16 *value)
{
    U8 rbuf[2] = { 0, 0 };

    if (i2c_rdwr_xfer(dev->i2c, dev->addr7,
                       &reg, 1,
                       rbuf, 2) != 0) {
        return -1;
    }

    *value = ((U16)rbuf[0] << 8) | rbuf[1];
    return 0;
}

/* ======================= 初始化 ======================= */

int hm832is_init(hm832is_dev *dev, I2C_INDEX i2c, U16 addr7, float shuntRes)
{
    if (!dev || shuntRes <= 0.0f) {
        return -1;
    }

    memset(dev, 0, sizeof(*dev));
    dev->i2c      = i2c;
    dev->addr7    = addr7;
    dev->shuntRes = shuntRes;

    /* 1. 验证通信 */
    U16 mfr = 0;
    if (hm832is_read_reg(dev, HM832IS_REG_MANUFACTURER, &mfr) != 0) {
        printf("[HM832IS] read MFR failed\n");
        return -2;
    }
    printf("[HM832IS] Manufacturer ID: 0x%04X\n", mfr);
    if (mfr != 0x5959) {
        printf("[HM832IS] device not found at 0x%02X\n", addr7);
        return -2;
    }

    U16 die = 0;
    if (hm832is_read_reg(dev, HM832IS_REG_DIE_ID, &die) == 0) {
        printf("[HM832IS] Die ID: 0x%04X\n", die);
    }

    /* 2. 写入校准值:使用 500 µA/bit */
    float initLSB = HM832IS_CURRENT_LSB;   /* 0.0005f */
    U16 calVal = (U16)(HM832IS_INTERNAL_SCALE / (initLSB * shuntRes));
    if (calVal == 0) {
        calVal = 1;
    }

    if (hm832is_write_reg(dev, HM832IS_REG_CALIBRATION, calVal) != 0) {
        printf("[HM832IS] write calibration failed\n");
        return -3;
    }

    /* 3. 回读校准值反推实际 LSB */
    U16 actualCal = 0;
    if (hm832is_read_reg(dev, HM832IS_REG_CALIBRATION, &actualCal) != 0 ||
        actualCal == 0) {
        printf("[HM832IS] readback calibration failed\n");
        return -3;
    }

    dev->currentLSB = HM832IS_INTERNAL_SCALE / (actualCal * shuntRes);
    dev->powerLSB   = dev->currentLSB * 25.0f;

    printf("[HM832IS] Cal=%u, currentLSB=%.7f A/bit, powerLSB=%.6f W/bit\n",
           actualCal, dev->currentLSB, dev->powerLSB);

    /* 4. 写配置寄存器 */
    if (hm832is_write_reg(dev, HM832IS_REG_CONFIG,
                          HM832IS_CONFIG_CONTINUOUS) != 0) {
        printf("[HM832IS] write config failed\n");
        return -4;
    }

    HM832IS_DELAY_MS(30);

    dev->calibrated = 1;
    printf("[HM832IS] init OK\n");
    return 0;
}

/* ======================= 数据读取 ======================= */

int hm832is_read_voltage(hm832is_dev *dev, float *voltageV)
{
    U16 raw = 0;
    if (!dev || !voltageV) return -1;
    if (hm832is_read_reg(dev, HM832IS_REG_BUS_V, &raw) != 0) return -1;
    *voltageV = (float)raw * HM832IS_BUS_V_LSB;
    return 0;
}

int hm832is_read_current(hm832is_dev *dev, float *currentA)
{
    U16 raw = 0;
    if (!dev || !currentA || !dev->calibrated) return -1;
    if (hm832is_read_reg(dev, HM832IS_REG_CURRENT, &raw) != 0) return -1;
    *currentA = (float)raw * dev->currentLSB;
    return 0;
}

int hm832is_read_power(hm832is_dev *dev, float *powerW)
{
    U16 raw = 0;
    if (!dev || !powerW || !dev->calibrated) return -1;
    if (hm832is_read_reg(dev, HM832IS_REG_POWER, &raw) != 0) return -1;
    *powerW = (float)raw * dev->powerLSB;
    return 0;
}

int hm832is_read_shunt(hm832is_dev *dev, float *shuntV)
{
    U16 raw = 0;
    if (!dev || !shuntV) return -1;
    if (hm832is_read_reg(dev, HM832IS_REG_SHUNT_V, &raw) != 0) return -1;
    *shuntV = (float)(int16_t)raw * HM832IS_SHUNT_V_LSB;
    return 0;
}
  • 测试示例
c 复制代码
/**
 * @file    main.c
 * @brief   HM832IS 周期采集应用
 */

#include "hm832is_app.h"
#include <stdio.h>
#include <signal.h>
#include <unistd.h>

#define SAMPLE_INTERVAL_MS  10     			/* 采集周期 10ms */
#define MAX_RETRY           3       		/* 连续失败重试次数 */

static volatile sig_atomic_t g_running = 1;

static void sig_handler(int sig)
{
    (void)sig;
    g_running = 0;
}

int main(void)
{
    hm832is_dev dev;
    int fail_count = 0;

    signal(SIGINT, sig_handler);
    signal(SIGTERM, sig_handler);

    if (hm832is_init(&dev, I2C_DEV, HM832IS_DEFAULT_ADDR,
                     HM832IS_SHUNT_RES_OHM) != 0) {
        printf("[APP] HM832IS init failed\n");
        return -1;
    }

    printf("\n[APP] Start sampling, press Ctrl+C to exit\n");
    printf("--------------------------------------------------------------\n");
    printf("%-8s %-12s %-12s %-12s\n",
           "Time", "Voltage(V)", "Current(A)", "Power(W)");
    printf("--------------------------------------------------------------\n");

    int sample = 0;
    while (g_running) {
        float v = 0.0f, i = 0.0f, p = 0.0f;

        if (hm832is_read_voltage(&dev, &v) == 0 &&
            hm832is_read_current(&dev, &i) == 0 &&
            hm832is_read_power(&dev, &p) == 0) {
            printf("[%04d]   %-12.3f %-12.3f %-12.3f\n",
                   sample, v, i, p);
            sample++;
            fail_count = 0;
        } else {
            if (++fail_count >= MAX_RETRY) {
                printf("[WARN] %d consecutive failures, reinit device\n", fail_count);
                if (hm832is_init(&dev, I2C_DEV, HM832IS_DEFAULT_ADDR,
                                 SHUNT_RES_OHM) != 0) {
                    printf("[APP] Reinit failed, continue\n");
                }
                fail_count = 0;
            }
        }

        usleep(SAMPLE_INTERVAL_MS * 1000);
    }

    printf("\n[APP] Exiting...\n");
    return 0;
}

预期输出:

text 复制代码
[HM832IS] Manufacturer ID: 0x5959
[HM832IS] Die ID: 0x2726
[HM832IS] Cal=6142, currentLSB=0.0005001 A/bit, powerLSB=0.012503 W/bit
[HM832IS] init OK

[APP] Start sampling, press Ctrl+C to exit
--------------------------------------------------------------
Time     Voltage(V)   Current(A)   Power(W)
--------------------------------------------------------------
[0000]   11.965       3.312        39.843
[0001]   11.964       3.310        39.810
[0002]   11.966       3.313        39.845

常见问题排查

问题 1:ls /dev/i2c-* 无设备节点

序号 可能原因 对应解决
1 Vivado 中未使能 PS I2C 检查 Vivado PS 配置,使能对应 I2C 控制器
2 MIO 引脚未分配或冲突 检查 MIO 分配,确认无引脚冲突
3 设备树中 I2C 节点仍为 status = "disabled" 检查 system-user.dtsi,将对应 I2C 节点改为 status = "okay"
4 内核未包含 I2C 控制器驱动 检查内核配置,确认包含对应 I2C 控制器驱动
5 修改设备树后未更新启动镜像 重新编译并更新 BOOT.BIN / image.ub

问题 2:i2cdetect 扫描不到设备

序号 可能原因 对应解决
1 SCL/SDA 接反 核对原理图和接线,确认 SCL/SDA 未接反
2 外设未供电或 GND 未共地 确认 3.3V 供电正常,并确保 GND 共地
3 设备地址错误 确认 MPU6050 地址为 0x68(或 0x69,取决于 AD0)
4 缺少上拉电阻 增加 4.7kΩ 上拉电阻
5 总线被其他设备占用 排查总线上其他设备,必要时断开逐一验证
6 I2C 时钟过高 降低时钟到 100kHz 后重新扫描

问题 3:I2C 读写超时或数据乱码

序号 可能原因 对应解决
1 时钟频率过高 降低 I2C 时钟频率
2 上拉电阻缺失或阻值不合适 增加或调整合适的上拉电阻
3 线缆过长、干扰严重 缩短杜邦线,远离干扰源
4 电平不匹配 检查外设电平,必要时做电平转换
5 寄存器地址或读写时序错误 先用 i2cget/i2cset 验证寄存器读写,再核对时序

问题 4:设备树配置后不生效

序号 可能原因 对应解决
1 修改的不是实际编译使用的 DTS 确认实际编译使用的 DTS 路径,修改正确文件
2 system-user.dtsi 未被包含 检查板级 DTS 是否包含 system-user.dtsi
3 pinctrl label 写错 确认节点 label 与板级 DTS 一致,修正 pinctrl label
4 设备树编译报错 检查 PetaLinux 设备树编译日志,修复报错
5 未更新启动镜像 重新执行 petalinux-build 并更新启动介质
相关推荐
qq_3718021131 分钟前
Ubuntu Linux中安装WorkBuddy
linux·运维·服务器·ubuntu
无足鸟ICT39 分钟前
【RHCA+】iptraf-ng(网络监控分析)
linux·网络
独隅1 小时前
WebStorm 在 Linux 环境下的实战安装与高效开发指南
linux·ide·webstorm
坤坤子吖3 小时前
Linux网络编程:TCP服务器的多进程与多线程实现
linux·服务器·开发语言·网络·c++·笔记·学习
阡陌数智3 小时前
Claude API新版本重大变更与安全升级指南
java·linux·安全
无敌贵点大王3 小时前
从寄存器层面重学STM32(7)——定时器
stm32·单片机·嵌入式硬件
奶茶树3 小时前
【Linux】环境开发工具(vim,gcc,g++,Makefile)
linux·运维·vim
碧海银沙音频科技研究院3 小时前
粉色笔记本电脑每次要等把电池电耗尽才能开机问题解决方法
嵌入式硬件
木子欢儿7 小时前
国内 macOS/Linux 安装 rust
linux·运维·服务器·macos·rust