You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

使用read()/write()系统调用读取LTC2992的I2C寄存器返回0,但I2C_RDWR可正常读取的问题咨询

read()/write()系统调用读取LTC2992的I2C寄存器返回0,但I2C_RDWR可正常读取的问题咨询

问题描述

我现在正尝试用用户空间程序读取LTC2992电源监控器的寄存器,遇到了一个无法解释的现象:

当我通过write()系统调用、i2cset或者I2C_RDWR向LTC2992的某个寄存器写入值后,用i2cgetI2C_RDWR可以正常读回该值,但使用write()+read()系统调用组合读取时,返回值始终是0。

用write()+read()的代码示例

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

int main() {
    // ... 省略device和addr的定义
    int file = open(device, O_RDWR);
    if (file < 0) {
        perror("Failed to open I2C bus");
        return 1;
    }

    if (ioctl(file, I2C_SLAVE, addr) < 0) {
        perror("Failed to set I2C address");
        close(file);
        return 1;
    }

    uint8_t reg = 0xXX; // 目标寄存器地址
    uint8_t data;

    if (write(file, &reg, 1) != 1) {
        perror("Failed to write register address");
        close(file);
        return 1;
    }

    if (read(file, &data, 1) != 1) {
        perror("Failed to read from register");
        close(file);
        return 1;
    }

    close(file);
    return 0;
}

用I2C_RDWR的代码示例

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

int main() {
    // ... 省略device和addr的定义
    int file = open(device, O_RDWR);
    if (file < 0) {
        perror("Failed to open I2C device");
        return 1;
    }

    struct i2c_rdwr_ioctl_data packets;
    struct i2c_msg messages[2];
    uint8_t reg = 0xXX; // 目标寄存器地址
    uint8_t data;

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

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

    packets.msgs = messages;
    packets.nmsgs = 2;

    if (ioctl(file, I2C_RDWR, &packets) < 0) {
        perror("I2C_RDWR ioctl failed");
        close(file);
        return 1;
    }

    close(file);
    return 0;
}

我的理解是,读取寄存器需要重复起始条件,所以猜测第一种方法失败是因为write()系统调用会生成这样的事务:
START -> [ADDR+W] -> [REG] -> STOP
read()系统调用生成的是:
START -> [ADDR+R] -> [DATA] -> STOP
也就是两次事务之间有STOP信号,没有重复起始。

但我看到某个教程里用类似的write()+read()方法成功读取了另一个器件的寄存器,而那个器件的 datasheet 也要求重复起始条件。所以我不确定我的直觉是否正确,也不确定这是不是时序问题,或者两种方法有什么关键差异?


专家解答

嗨,这个问题的核心确实和你猜测的**重复起始条件(Repeated Start)**有关,咱们来详细拆解:

1. write()+read()失效的原因

Linux的i2c-dev驱动对独立的write()read()系统调用的处理逻辑是:

  • 每次write()调用都会触发一个完整的I2C事务,发送完数据后一定会发送STOP信号,结束当前通信
  • 后续的read()是一个全新的独立事务,需要重新发送START信号+设备读地址

但LTC2992要求的寄存器读取流程是无中断的连续事务:写完寄存器地址后直接发送REPEATED START(重复起始)信号,再发起读操作,这样器件才会关联“写寄存器地址”和“读数据”这两个动作,返回对应寄存器的值。

分开的write()read()中间插入了STOP,器件会认为这是两个无关的请求,自然不会返回你期望的数据,而是返回默认的0值。

2. I2C_RDWR能正常工作的原因

I2C_RDWR是通过ioctl一次性提交一组I2C消息(你这里是“写寄存器地址”和“读数据”两个消息),驱动会把这两个消息合并成一个带重复起始的完整事务
START -> [ADDR+W] -> [REG] -> REPEATED START -> [ADDR+R] -> [DATA] -> STOP

这种方式完全符合LTC2992的通信协议要求,所以能正确读取寄存器值。

3. 为什么有些教程的write()+read()能成功?

这是因为不同I2C器件的设计兼容性不同

  • 部分器件在收到STOP信号后,会在短时间内保留最后一次指定的寄存器地址,如果你在这个时间窗口内发起读操作,就能读到正确值
  • 还有些器件的协议设计允许这种“拆分式”的事务,不需要严格的重复起始

但LTC2992显然不支持这种宽松的操作,或者你的系统中write()read()之间的系统调用延迟超过了器件的地址保留时间,所以这种方式对你的场景无效。

4. 解决方案

  • 优先使用I2C_RDWR方式:这是Linux官方推荐的处理复杂I2C事务的方法,兼容性最好,能适配所有需要重复起始条件的器件,稳定性最高
  • 如果一定要尝试write()+read()的方式,可以通过ioctl设置I2C_RETRIES(重试次数)和I2C_TIMEOUT(超时时间)来缩短操作间隔,但这种方式依赖器件特性和内核版本,可靠性远不如I2C_RDWR

你也可以用逻辑分析仪抓取两种方式的I2C波形,直观看到STOP信号的差异,这会帮你更清晰地理解问题所在。


内容来源于stack exchange

火山引擎 最新活动