i2c 驱动编程接口 i2c_master_send 和 i2c_master_recv i2c_transfer

本文介绍I2C通信接口的基本使用方法,包括数据包结构、发送接收函数及读寄存器封装,并探讨了针对I2C总线死锁问题的多层次Reset机制。

1、通信接口
i2c发送或者接收一次数据都以数据包 struct i2c_msg 封装
[cpp] 
struct i2c_msg { 
    __u16 addr;     // 从机地址 
    __u16 flags;    // 标志 
#define I2C_M_TEN   0x0010  // 十位地址标志 
#define I2C_M_RD    0x0001  // 接收数据标志 
    __u16 len;      // 数据长度 
    __u8 *buf;      // 数据指针 
}; 
其中addr为从机地址;flags则是这次通信的标志,发送数据为0,接收数据则为 I2C_M_RD;len为此次通信的数据字节数;buf 为发送或接收数据的指针。在设备驱动中我们通常调用 i2c-core 定义的接口 i2c_master_send 和 i2c_master_recv 来发送或接收一次数据。
[cpp]
int i2c_master_send(struct i2c_client *client,const char *buf ,int count) 

    int ret; 
    struct i2c_adapter *adap=client->adapter;  // 获取adapter信息 
    struct i2c_msg msg;                        // 定义一个临时的数据包 
 
    msg.addr = client->addr;                   // 将从机地址写入数据包 
    msg.flags = client->flags & I2C_M_TEN;     // 将从机标志并入数据包 
    msg.len = count;                           // 将此次发送的数据字节数写入数据包 
    msg.buf = (char *)buf;                     // 将发送数据指针写入数据包 
 
    ret = i2c_transfer(adap, &msg, 1);         // 调用平台接口发送数据 
 
    /* If everything went ok (i.e. 1 msg transmitted), return #bytes
       transmitted, else error code. */ 
    return (ret == 1) ? count : ret;           // 如果发送成功就返回字节数 

EXPORT_SYMBOL(i2c_master_send); 
i2c_master_send 接口的三个参数:client 为此次与主机通信的从机,buf 为发送的数据指针,count 为发送数据的字节数。
[cpp] 
int i2c_master_recv(struct i2c_client *client, char *buf ,int count) 

    struct i2c_adapter *adap=client->adapter;  // 获取adapter信息 
    struct i2c_msg msg;                        // 定义一个临时的数据包 
    int ret; 
 
    msg.addr = client->addr;                   // 将从机地址写入数据包 
    msg.flags = client->flags & I2C_M_TEN;     // 将从机标志并入数据包 
    msg.flags |= I2C_M_RD;                     // 将此次通信的标志并入数据包 
    msg.len = count;                           // 将此次接收的数据字节数写入数据包 
    msg.buf = buf; 
 
    ret = i2c_transfer(adap, &msg, 1);         // 调用平台接口接收数据 
 
    /* If everything went ok (i.e. 1 msg transmitted), return #bytes
       transmitted, else error code. */ 
    return (ret == 1) ? count : ret;           // 如果接收成功就返回字节数 

EXPORT_SYMBOL(i2c_master_recv); 
i2c_master_recv 接口的三个参数:client 为此次与主机通信的从机,buf 为接收的数据指针,count 为接收数据的字节数。我们看一下 i2c_transfer 接口的参数说明:
[cpp
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num); 
其中 adap 为此次主机与从机通信的适配器;msgs 为通信的数据包,这里可以是单个或多个数据包;num 用于指定数据包的个数,如果大于1则表明将进行不止一次的通信。通信一次就需要寻址一次,如果需要多次通信就需要多次寻址,前面2个接口都是进行一次通信,所以 num 为1;有的情况下我们要读一个寄存器的值,就需要先向从机发送一个寄存器地址然后再接收数据,这样如果想自己封装一个接口就需要将 num 设置为2。接口的返回值如果失败则为负数,如果成功则返回传输的数据包个数。比如读一个寄存器的接口可以按照如下方式封装:
[cpp] 
static int read_reg(struct i2c_client *client, unsigned char reg, unsigned char *data) 

    int ret; 
 
    struct i2c_msg msgs[] = { 
        { 
            .addr   = client->addr, 
            .flags  = 0, 
            .len    = 1, 
            .buf    = &reg,  // 寄存器地址 
        }, 
        { 
            .addr   = client->addr, 
            .flags  = I2C_M_RD, 
            .len    = 1, 
            .buf    = data,  // 寄存器的值 
        }, 
    }; 
 
    ret = i2c_transfer(client->adapter, msgs, 2);  // 这里 num = 2,通信成功 ret = 2 
    if (ret < 0) 
        tp_err("%s error: %d\n", __func__, ret); 
 
    return ret; 

还可调用前面所述的接口封装:
[cpp] 
static unsigned char read_reg(struct i2c_client *client, unsigned char reg) 

    unsigned char buf; 
 
    i2c_master_send(client, &reg, 1);  // 发送寄存器地址 
    i2c_master_recv(client, &buf, 1);  // 接收寄存器的值 
 
    return  buf; 

2、reset 接口
最近因为平台的i2c总线经常发生死锁,用逻辑分析仪检测发现通常为SDA和SCL都被拉低,于是在i2c-core中加入了reset机制,总体思路如下:
(1)在i2c.driver和i2c.adapter的结构中加入reset接口,即每一个i2c设备都可以注册reset函数,每条i2c总线都有相应的reset接口
(2)当发生死锁时,首先根据i2c-timeout的信息获取当前通信的设备地址和总线编号,然后依次执行当前总线下所有i2c设备的reset函数,再尝试发送是否成功;如果总线仍然处于死锁状态则执行i2c.adapter的reset函数;如果总线还是处于死锁状态就重启机器;总共3层reset机制
(3)i2c.driver的reset函数一般操作设备的reset pin或者电源(需要根据硬件设计进行相应操作)
(4)i2c.adapter的reset函数首选进行SCL的模拟解锁方案,然后再是操作整个总线上设备的电源(需要根据硬件设计进行相应操作)
(5)重启是最后的一层机制,此时无法恢复设备的正常使用就只能重启了
因为i2c.adapter层的需要,在i2c-core中加入了遍历当前总线所有设备并执行设备reset函数的接口i2c_reset_device:
[cpp]
/**
 * i2c_reset_device - reset I2C device when bus dead
 * @adapter: the adapter being reset
 * @addr: the device address
 */ 
static int __i2c_reset_device(struct device *dev, void *addrp) 

    struct i2c_client *client = to_i2c_client(dev); 
    int addr = *(int *)addrp; 
 
    if (client && client->driver && client->driver->reset) 
        return client->driver->reset(); 
 
    return 0; 

 
int i2c_reset_device(struct i2c_adapter *adapter, int addr) 

    return device_for_each_child(&adapter->dev, &addr, __i2c_reset_device); 

EXPORT_SYMBOL(i2c_reset_device); 
需要注意的是i2c.driver的reset函数返回值需要为0,不然device_for_each_child不会继续后面的遍历。用GPIO模拟SCL解锁的参考代码如下:
[cpp] 
static int i2c_reset_adapter(void) 

    int counter = 0; 
 
    gpio_request(I2C_BUS_DATA, "gpioxx"); 
    gpio_request(I2C_BUS_CLK, "gpioxx"); 
    /* try to recover I2C bus */ 
    gpio_direction_input(I2C_BUS_DATA); 
 
    if (!__gpio_get_value(I2C_BUS_DATA)) { 
        while((!__gpio_get_value(I2C_BUS_DATA)) && ++counter < 10) 
        { 
            udelay(5); 
            gpio_direction_output(I2C_BUS_CLK, 1); 
            udelay(5); 
            gpio_direction_output(I2C_BUS_CLK, 0); 
        } 
        i2c_err("try to recover i2c bus, retry times are %d\n",counter); 
        if (counter < 10) { 
            udelay(5); 
            gpio_direction_output(I2C_BUS_DATA, 0); 
            udelay(5); 
            gpio_direction_output(I2C_BUS_CLK, 1); 
            udelay(5); 
            gpio_direction_output(I2C_BUS_DATA, 1); 
            msleep(10); 
        } else { 
            i2c_err("try to recover i2c bus failed!\n"); 
        } 
    } 
 
    gpio_free(I2C_BUS_DATA); 
    gpio_free(I2C_BUS_CLK); 
 
    return 0; 
<think>我们正在讨论Linux内核中的I2C驱动函数。用户特别询问了`i2c_master_send``i2c_master_recv`的使用方法及示例。根据引用[1][2],我们有这两个函数的原型。同时,引用[3]提供了一个使用类似函数(RT-Thread系统)进行拼接发送的示例,这可以给我们一些思路。 首先,我们回顾一下这两个函数: 1. `i2c_master_send`:用于I2C主设备向从设备发送数据。 ```c int i2c_master_send(const struct i2c_client *client, const char *buf, int count); ``` - `client`:指向I2C设备的`i2c_client`结构,其中包含了设备地址等信息。 - `buf`:要发送的数据缓冲区。 - `count`:要发送的字节数。 - 返回值:成功时返回发送的字节数,失败时返回负的错误码。 2. `i2c_master_recv`:用于I2C主设备从从设备接收数据。 ```c int i2c_master_recv(const struct i2c_client *client, char *buf, int count); ``` - `client`:同上。 - `buf`:用于存放接收数据的缓冲区。 - `count`:期望接收的字节数。 - 返回值:成功时返回接收的字节数,失败时返回负的错误码。 注意:这两个函数都是同步的,会阻塞直到传输完成或出错。 使用场景: - `i2c_master_send`:通常用于向设备写入命令或数据。注意,对于需要先写寄存器地址再读数据的操作,这两个函数不能直接完成,因为每次调用都会产生起始停止条件。如果需要复合操作(先写后读),应使用`i2c_transfer`。 - `i2c_master_recv`:用于从设备读取数据,但前提是设备已经准备好发送数据(即不需要先写入寄存器地址)。如果需要先写入寄存器地址再读取数据,则不能单独使用`i2c_master_recv`,因为这样会在读取之前产生一个停止条件,从而中断了传输。 引用[3]中提到了一个技巧:使用`RT_I2C_NO_STOP``RT_I2C_NO_START`标志来实现多次调用组合成一个完整的传输(先发送寄存器地址,然后读取数据)。但在Linux内核中,`i2c_master_send``i2c_master_recv`函数并不支持这样的标志。因此,在Linux中,我们通常使用`i2c_transfer`来实现复合操作。 不过,如果设备支持,我们可以用以下方式: 1. 对于只写操作,直接使用`i2c_master_send`。 2. 对于只读操作(设备上电后就会自动发送数据,或者已经设置好要读取的寄存器地址),可以直接使用`i2c_master_recv`。 但是,大多数I2C设备都需要先写入一个寄存器地址,然后再读取数据。这时,我们需要两个`i2c_msg`组合在一起,并使用`i2c_transfer`。然而,用户明确要求使用`i2c_master_send``i2c_master_recv`,所以我们需要说明它们的限制并给出可行的示例。 示例1:单独使用`i2c_master_send`发送命令 假设我们有一个设备,我们只需要向它发送一个命令(比如重置命令),不需要发送数据,那么我们可以这样: ```c char buf[1] = {0x01}; // 假设0x01是重置命令 int ret = i2c_master_send(client, buf, 1); if (ret < 0) { // 处理错误 } ``` 示例2:单独使用`i2c_master_recv`读取数据 假设设备在接收到读取请求时,会直接返回数据(比如设备只有一个数据寄存器,且地址在之前已经设置好),那么我们可以这样: ```c char buf[4]; // 假设我们要读取4个字节 int ret = i2c_master_recv(client, buf, 4); if (ret < 0) { // 处理错误 } ``` 但是,注意:上述示例2的情况很少见,因为通常我们需要先写入寄存器地址。所以,如果我们想要读取特定寄存器的值,我们需要先写入寄存器地址,然后读取。这需要两个独立的传输(中间有停止条件),但有些设备可能不允许这样,因为停止条件会中断设备的操作。因此,正确的做法是使用复合传输(无停止条件的重复开始)。 然而,如果我们必须使用这两个函数,并且设备允许在写读之间有一个停止条件,那么我们可以: ```c // 先发送要读取的寄存器地址 char reg_addr[1] = {0x00}; // 假设要读取0x00寄存器的值 int ret = i2c_master_send(client, reg_addr, 1); if (ret < 0) { // 处理错误 } // 然后读取数据 char data[2]; ret = i2c_master_recv(client, data, 2); if (ret < 0) { // 处理错误 } ``` 注意:这里两次调用分别产生了起始停止条件。在第一次发送后,有一个停止条件;然后第二次读取前,又有一个起始条件。这符合I2C协议,但并不是所有设备都支持。有些设备要求复合传输(即重复起始条件,没有中间的停止条件)。在这种情况下,我们必须使用`i2c_transfer`。 因此,在大多数情况下,我们不推荐使用`i2c_master_send``i2c_master_recv`来实现先写后读的操作,而应该使用`i2c_transfer`或者SMBus函数(如`i2c_smbus_read_byte_data`等)。 总结: - 对于简单的写操作,使用`i2c_master_send`。 - 对于简单的读操作(设备已经处于待读取状态),使用`i2c_master_recv`。 - 对于需要先写后读的操作,如果设备允许中间有停止条件,可以分两次调用;否则,使用`i2c_transfer`。 最后,我们给出一个使用`i2c_master_send``i2c_master_recv`的完整驱动函数示例(假设设备允许中间停止条件): ```c // 读取设备寄存器的值,假设设备允许先发送寄存器地址(写操作),然后停止,再启动读取 static int read_reg(struct i2c_client *client, u8 reg, u8 *val, int len) { int ret; // 先发送要读取的寄存器地址 ret = i2c_master_send(client, &reg, 1); if (ret < 0) { dev_err(&client->dev, "发送寄存器地址失败: %d\n", ret); return ret; } // 然后读取数据 ret = i2c_master_recv(client, val, len); if (ret < 0) { dev_err(&client->dev, "读取数据失败: %d\n", ret); return ret; } return 0; } ``` 注意:这个函数假设设备在收到寄存器地址后,会在下一次读取操作中返回该寄存器的值。但是,由于两次传输之间有停止条件,所以设备必须能够在这种操作下正常工作。 如果设备要求复合传输(无中间停止条件),则应该使用`i2c_transfer`: ```c static int read_reg(struct i2c_client *client, u8 reg, u8 *val, int len) { struct i2c_msg msgs[2] = { { .addr = client->addr, .flags = 0, // 写 .len = 1, .buf = &reg, }, { .addr = client->addr, .flags = I2C_M_RD, // 读 .len = len, .buf = val, } }; int ret = i2c_transfer(client->adapter, msgs, 2); if (ret < 0) { dev_err(&client->dev, "传输失败: %d\n", ret); return ret; } else if (ret != 2) { dev_err(&client->dev, "传输消息数量不足: %d\n", ret); return -EIO; } return 0; } ``` 因此,用户需要根据设备的数据手册决定使用哪种方式。 最后,我们生成相关问题:</think>### Linux I2C驱动:`i2c_master_send`与`i2c_master_recv`使用详解 #### 1. 函数原型与参数说明 ```c // 发送数据函数 int i2c_master_send(const struct i2c_client *client, const char *buf, int count); ``` - `client`:I2C设备客户端结构体指针 - `buf`:待发送数据缓冲区 - `count`:发送字节数 - **返回值**:成功返回发送字节数,失败返回负错误码[^1] ```c // 接收数据函数 int i2c_master_recv(const struct i2c_client *client, char *buf, int count); ``` - `client`:I2C设备客户端结构体指针 - `buf`:接收数据缓冲区 - `count`:期望接收字节数 - **返回值**:成功返回接收字节数,失败返回负错误码[^2] #### 2. 关键特性 1. **单次传输**:每次调用产生完整的I2C传输序列(起始位 + 数据 + 停止位) 2. **同步阻塞**:函数执行期间会阻塞当前线程 3. **寄存器访问限制**:需预先设置目标寄存器地址(需额外操作) 4. **不适用场景**:复合操作(如先写地址后读数据)需使用`i2c_transfer()` #### 3. 使用示例 ##### 示例1:基础数据发送 ```c // 发送温度设置命令2字节) u8 config_data[2] = {0x01, 0x25}; // 寄存器地址 + 值 int ret = i2c_master_send(client, config_data, sizeof(config_data)); if (ret < 0) { dev_err(&client->dev, "发送失败: %d\n", ret); } else { dev_info(&client->dev, "发送%d字节成功\n", ret); } ``` ##### 示例2:基础数据接收 ```c // 读取传感器状态(3字节) u8 status_buf[3]; int ret = i2c_master_recv(client, status_buf, sizeof(status_buf)); if (ret < 0) { dev_err(&client->dev, "读取失败: %d\n", ret); } else { dev_info(&client->dev, "状态: 0x%02X 0x%02X 0x%02X\n", status_buf[0], status_buf[1], status_buf[2]); } ``` ##### 示例3:复合操作实现(先写后读) ```c // 步骤1:发送寄存器地址(无停止位) u8 reg_addr = 0x05; int ret = i2c_master_send(client, &reg_addr, 1); if (ret != 1) { dev_err(&client->dev, "地址发送失败"); return ret; } // 步骤2:读取寄存器数据 u8 reg_value; ret = i2c_master_recv(client, &reg_value, 1); if (ret != 1) { dev_err(&client->dev, "数据读取失败"); } ``` #### 4. 使用注意事项 1. **错误处理**:必须检查返回值,常见错误码: - `-EIO`:总线通信错误 - `-ENXIO`:地址无响应 - `-ETIMEDOUT`:操作超时 2. **缓冲区管理**: ```c char *buf = kmalloc(BUF_SIZE, GFP_KERNEL); // 分配缓冲区 // ... 使用缓冲区 ... kfree(buf); // 释放内存 ``` 3. **时钟频率设置**: ```c // 在probe函数中设置总线频率 client->adapter->timeout = HZ; // 1秒超时 client->adapter->retries = 3; // 重试次数 ``` 4. **替代方案建议**: - 优先使用SMBus函数(如`i2c_smbus_read_byte_data()`) - 复杂操作使用`i2c_transfer()`+`i2c_msg` #### 5. 典型工作流程 ```mermaid graph TD A[probe函数] --> B[初始化I2C客户端] B --> C{操作类型} C -->|写操作| D[i2c_master_send] C -->|读操作| E[i2c_master_recv] D --> F[检查返回值] E --> F F -->|成功| G[处理数据] F -->|失败| H[错误处理] ``` > **最佳实践**:在`probe()`函数中通过`i2c_master_send()`发送设备ID读取命令,验证设备是否存在[^3]。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值