关于i2c_msg的一点注意事项

  • 首先对于i2c_msg的说明:一个i2c_msg结构变量,代表着一次单方向的完整传输。

  • 正确理解上面这句话的意思,就能正确使用linux中使用i2c_msg对于i2c的读写操作。

        来看一下i2c_msg的定义(位于:linux-3.10\include\uapi\linux):

/**
 * struct i2c_msg - an I2C transaction segment beginning with START
 * @addr: Slave address, either seven or ten bits.  When this is a ten
 *	bit address, I2C_M_TEN must be set in @flags and the adapter
 *	must support I2C_FUNC_10BIT_ADDR.
 * @flags: I2C_M_RD is handled by all adapters.  No other flags may be
 *	provided unless the adapter exported the relevant I2C_FUNC_*
 *	flags through i2c_check_functionality().
 * @len: Number of data bytes in @buf being read from or written to the
 *	I2C slave address.  For read transactions where I2C_M_RECV_LEN
 *	is set, the caller guarantees that this buffer can hold up to
 *	32 bytes in addition to the initial length byte sent by the
 *	slave (plus, if used, the SMBus PEC); and this value will be
 *	incremented by the number of block data bytes received.
 * @buf: The buffer into which data is read, or from which it's written.
 *
 * An i2c_msg is the low level representation of one segment of an I2C
 * transaction.  It is visible to drivers in the @i2c_transfer() procedure,
 * to userspace from i2c-dev, and to I2C adapter drivers through the
 * @i2c_adapter.@master_xfer() method.
 *
 * Except when I2C "protocol mangling" is used, all I2C adapters implement
 * the standard rules for I2C transactions.  Each transaction begins with a
 * START.  That is followed by the slave address, and a bit encoding read
 * versus write.  Then follow all the data bytes, possibly including a byte
 * with SMBus PEC.  The transfer terminates with a NAK, or when all those
 * bytes have been transferred and ACKed.  If this is the last message in a
 * group, it is followed by a STOP.  Otherwise it is followed by the next
 * @i2c_msg transaction segment, beginning with a (repeated) START.
 *
 * Alternatively, when the adapter supports I2C_FUNC_PROTOCOL_MANGLING then
 * passing certain @flags may have changed those standard protocol behaviors.
 * Those flags are only for use with broken/nonconforming slaves, and with
 * adapters which are known to support the specific mangling options they
 * need (one or more of IGNORE_NAK, NO_RD_ACK, NOSTART, and REV_DIR_ADDR).
 */
struct i2c_msg {
	__u16 addr;	/* slave address(从机地址)			*/
	__u16 flags; // 标志位,指定进行的操作
#define I2C_M_TEN		0x0010	/* this is a ten bit chip address */    // 表示这个器件的器件地址是10Bit的
#define I2C_M_RD		0x0001	/* read data, from slave to master */   // 表示这是一个读操作,默认是把相应的位置1
#define I2C_M_STOP		0x8000	/* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NOSTART		0x4000	/* if I2C_FUNC_NOSTART */           // 表示当前i2c_msg不发送start信号
#define I2C_M_REV_DIR_ADDR	0x2000	/* if I2C_FUNC_PROTOCOL_MANGLING */ // 表示把读写标志位反转,也就是读是把相应位置0
#define I2C_M_IGNORE_NAK	0x1000	/* if I2C_FUNC_PROTOCOL_MANGLING */ // 表示当前i2c_msg忽略I2C器件的ack和nack信号
#define I2C_M_NO_RD_ACK		0x0800	/* if I2C_FUNC_PROTOCOL_MANGLING */ // 表示在读操作时不去ACK
#define I2C_M_RECV_LEN		0x0400	/* length will be first received byte */ // 表示在读操作时不去ACK
	__u16 len;		/* msg length(单位为字节)       */
	__u8 *buf;		/* pointer to msg data(数据缓冲区)*/
};
  • 看一个读函数实例

i2c的读数据操作分为:“写地址” 和 “读数据” 两步操作。

因此可以定义两个 i2c_msg 结构体变量,分别通过对结构体成员变量flag的赋值来确定传输方向,

该函数本身没啥问题,读数据也正常,只需要注意length是以字节为单位即可。函数编写如下:

/* 读取多个寄存器数据
 * 入口参数: @client: 从机地址
 *		      @reg	 : 寄存器地址
 *		      @buffer: 保存读取数据
 *		      @length: reg/buffer的长度
 */
static int i2c_read_regs(struct i2c_client *client, u8 reg, u8 *buffer, int length)
{
    int err = 0;

    /* msg[0]是发送要读取的寄存器首地址 */
    struct i2c_msg msg[] = {
        {
            .addr  = client->addr,
            .flags = 0,
            .len   = 1,     //表示寄存器地址字节长度,是以byte为单位
            .buf   = &reg,
        },
        {
            .addr  = client->addr,
            .flags = I2C_M_RD,
            .len   = length, //表示期望读到数据的字节长度(寄存器长度),是以byte为单位
            .buf   = buffer, //将读取到的数据保存在buffer中
        },
    };

    err = i2c_transfer(client->adapter, msg, 2);
    if(err != 2)C
    {
        err = -EINVAL;
        printk("read regs from i2c has been failed\n\r");
    }
    
    return err;
}
  • 再来看一个写函数实例

写数据操作也分为:“写地址” 和 “写数据” 两步操作,根据写时序图来看,没有什么问题。

函数编写如下:

/* 向i2c设备写多个寄存器数据
 * 入口参数   @client: 从机地址
 *		      @reg	 : 寄存器地址
 *		      @buffer: 需要写入的数据
 *		      @length: reg/buffer的长度
 */
static int i2c_write_regs(struct i2c_client *client,u8 reg, u8 *buffer, int length)
{
    int err = 0;
    struct i2c_msg msg[] = {
        {
            .addr  = client->addr,
            .flags = 0,    // 表示写
            .len   = 1,
            .buf   = &reg,
        },
        {
            .addr  = client->addr,
            .flags = 0,    // 表示写
            .len   = length,
            .buf   = buffer,
        },
    };

    err = i2c_transfer(client->adapter, msg, 2);
    if(err != 2)
    {
        err = -EINVAL;
        printk("write data to i2c has been failed\n\r");
    }
    
    return err;
}

但实际读出来的数据,确不对。那问题出在哪???

还是要正确理解文章开头的那一句话:一个i2c_msg结构变量,代表着一次单方向的完整传输。

  • 分析

在进行写寄存器操作时,并不是说第一个i2c_msg结构体是写了设备地址之后,第二个i2c_msg结

构体就是向设备的寄存器地址进行写数据了。

i2c_msg成员变量的buf远远没有达到这么智能的程度,它只能知道在一个单次且完整的写过程中,

第一个传递给buf的是寄存器地址。因此,尽管程序中用了结构体数组来定义了两个连续的i2c_msg

结构体变量,但是它们依旧是两个单次的写数据过程,因此i2c_msg[1]还是写地址,而不是写数

,所以在指定地址读取不到想要的值,而是其他值。

  • 正确函数写法应该是:
static int i2c_write_regs(struct i2c_client *client,u8 reg, u8 *buffer, int length)
{
    int err = 0;

    u8 data[256];
    struct i2c_msg msg;
    data[0] = reg;
    memcpy(&data[1], buffer, length);

    msg.addr = client->addr,
    msg.flags = 0;
    msg.len   = length + 1; /* +1是因为还有一个data[0]所存储的寄存器 */
    msg.buf = data;

    err = i2c_transfer(client->adapter, &msg, 1);
    if(err != 1)
    {
        err = -EINVAL;
        printk("write data to i2c has been failed\n\r");
    }
    
    return err;
}

在上述函数中,只使用一个i2c_msg结构变量,因此整个函数是一次完整单向的数据传输,使用

数组是为了让数据连续。

<think>嗯,用户问的是Linux能否在应用层直接操作I2C从设备的原理。首先,我需要回忆一下LinuxI2C子系统结构。记得I2CLinux中有内核驱动支持,通常是通过内核模块来管理设备的。那用户想知道的是应用层直接操作,也就是不经过内核驱动,对吧? 接下来,应该考虑用户的实际使用场景。可能用户是嵌入式开发者,或者在做一些需要直接控制I2C设备的工作,比如传感器或者自定义硬件。这时候,直接操作I2C可能涉及到绕过内核驱动,直接通过用户空间的接口访问硬件。这种情况下,用户可能需要更高的灵活性或者对实时性有要求。 然后,我需要确认Linux提供的机制。记得LinuxI2C dev接口,也就是/dev/i2c-*设备文件,允许用户空间程序通过ioctl调用来控制I2C适配器。这样用户就可以在应用层打开这些设备文件,发送读写命令,直接和I2C从设备通信。这就是用户所说的“直接操作”的原理吧。 不过,用户可能还存在疑问,比如这种方式是否需要特定的权限,或者和内核驱动的区别。例如,当使用I2C dev时,用户空间的程序需要正确的权限来访问设备节点,可能需要root权限或者将用户加入特定的组。另外,直接操作I2C可能会和内核中已有的驱动冲突,导致设备被多个程序访问的问题。 还需要解释为什么这样的机制是可行的。因为I2C总线本身是主从结构,主设备(通常是SoC的I2C控制器)控制通信时序,而用户空间的程序通过ioctl指定从设备地址和读写操作,底层由内核的I2C适配器驱动处理具体的时序和电气信号。这样用户层就不需要关心具体的硬件细节,只需通过标准的接口发送命令即可。 可能用户还有更深层的需求,比如他们是否应该使用内核驱动还是用户空间驱动。这时候需要比较两者的优缺点。内核驱动通常更高效,能处理中断和DMA,适合性能敏感的应用;而用户空间驱动更灵活,便于调试和快速开发,但可能牺牲一些性能和实时性。 另外,用户可能关心如何具体实现,比如使用哪些系统调用,代码示例是什么样的。例如,使用open打开/dev/i2c-0,然后用ioctl设置从设备地址,接着用read/write或者i2c_msg结构体来传输数据。这部分可能需要提供简单的示例代码或者命令,如i2c-tools中的i2cset和i2cget。 还要注意安全性问题,直接操作硬件可能会影响系统稳定性,特别是如果多个进程同时访问同一个I2C设备的话,需要适当的同步机制。比如使用I2C_RDWR ioctl可以组合多个消息在一个传输中,减少竞争条件的风险。 总结来说,用户的问题核心在于理解Linux如何允许应用层绕过传统的内核驱动模型,直接通过设备文件与I2C从设备交互。这涉及到LinuxI2C子系统设计,用户空间接口的存在,以及具体的实现方法和注意事项。需要将这些点有条理地组织起来,确保用户不仅能明白原理,还能了解实际应用中的关键点和潜在问题。</think>Linux允许在应用层直接操作I2C从设备的原理可以通过以下技术架构来解释: ### 一、底层硬件支持机制 1. **I2C总线控制器驱动** - 内核中实现了`i2c-adapter`核心驱动(如`i2c-dev.c`) - 通过`/dev/i2c-*`设备节点暴露硬件接口 - 典型驱动路径:`drivers/i2c/busses/i2c-designware-core.c` 2. **用户空间访问接口** ```c // 示例:打开I2C总线设备 int fd = open("/dev/i2c-1", O_RDWR); ioctl(fd, I2C_SLAVE, 0x50); // 设置从设备地址 ``` ### 二、关键系统调用流程 ``` 应用层程序 内核空间 硬件层 | | | |-- open() ---------->| 初始化I2C控制器 | | |<--> GPIO/I2C寄存器 | |-- ioctl(I2C_SLAVE) -| 配置从机地址 | |-- read()/write() -->| 生成START/STOP信号 | | | 处理SCL/SDA时序 | |<---数据返回---------| 完成I2C事务 | ``` ### 三、用户空间操作原理 1. **直接寄存器访问** - 通过`ioctl(I2C_RDWR)`实现原始消息传输 - 结构体定义: ```c struct i2c_msg { __u16 addr; // 从机地址 __u16 flags; // 读写标志 __u16 len; // 数据长度 __u8 *buf; // 数据缓冲区 }; ``` 2. **i2c-tools工具链** ```bash # 扫描总线设备 i2cdetect -y 1 # 写入寄存器 i2cset -y 1 0x50 0x00 0xAA # 读取寄存器 i2cget -y 1 0x50 0x00 ``` ### 四、内核与用户空间的交互对比 | 特性 | 内核驱动模式 | 用户空间直接操作 | |---------------------|----------------------|-----------------------| | 开发复杂度 | 高(需编写内核模块) | 低(用户态程序) | | 实时性 | 纳秒级中断响应 | 微秒级系统调用延迟 | | 并发控制 | 内核锁机制 | 需应用层自行处理 | | 典型应用场景 | 标准设备驱动 | 快速原型开发/调试 | ### 五、实际开发注意事项 1. **权限配置** ```bash # 设置设备访问权限 sudo chmod 666 /dev/i2c-* # 或添加用户到i2c组 sudo usermod -aG i2c $USER ``` 2. **时序控制限制** - 最大传输速率受`i2c_adapter.timeout`限制 - 不支持硬件级时钟延展处理 3. **多进程访问同步** ```c // 建议使用文件锁 flock(fd, LOCK_EX); write(fd, buf, len); flock(fd, LOCK_UN); ``` ### 六、典型应用场景 1. 传感器快速原型开发(如BME280温度传感器) 2. EEPROM芯片调试 3. 自定义I2C设备测试 4. 嵌入式系统低功耗模式下的间歇性访问 通过这种设计,Linux在保持内核稳定性的同时,为开发者提供了灵活的低层硬件访问能力。用户空间直接操作I2C本质上是内核将总线控制权委托给应用层,通过标准系统调用接口实现硬件交互。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值