ESC
输入关键词搜索文章标题和内容

I2C子系统:从协议时序到设备驱动开发的完整实战

本文由 linuxROS 整理发布,首发于 linuxros.cn,转载请注明出处。

I2C子系统:从协议时序到设备驱动开发的完整实战

I2C是嵌入式里用得最多的总线之一,传感器、EEPROM、PMIC全挂在上面,但写I2C驱动不只是调几个API的事——协议时序没搞清楚,上拉电阻选不对,总线电容算错,出来的就是一堆NACK和超时。本文基于Linux 6.8内核,从I2C协议基础出发,讲清子系统三层架构、设备树配置、设备驱动开发、数据传输API、控制器驱动原理,最后给调试技巧和常见问题排查看

一、I2C协议基础

两线制主从模块

I2C只用两根线:SDA(数据)和SCL(时钟)。所有设备共享这两根线,通过地址区分从机,主机负责产生时钟和发起传输,从机被动响应。
| 信号线 | 方向 | 作用 |
|:-------|:-----|:-----|
| SDA | 双向 | 数据收发,开漏输出 |
| SCL | 主→从 | 时钟信号,主机驱动 |

两根线都是开漏输出,必须外接上拉电阻(典型4.7kΩ),空闲时均为高电平。

7/10位地址

标准I2C地址7位,有效范围0x03~0x77,0x00~0x02、0x78~0x7F保留)。总线上最多挂127个从机。10位地址扩展(1024个),但实际用得少,大部分传感器和EEPROM都是7位地址。
注意:设备手册上写的地址有时是8位格式(7位地址左移1位+ R/W位),比如BMP280手册0xEC/0xED,实际7位地址0x76。写设备树reg字段时用7位地址。

传输时序

一次完整的I2C传输:

START → 从机地址(7bit) + R/W(1bit) → ACK → 数据(8bit) → ACK → ... → STOP

关键信号:
| 信号 | 产生方 | 说明 |
|:-----|:-------|:-----|
| START | 主机 | SCL高电平期间,SDA由高拉低 |
| STOP | 主机 | SCL高电平期间,SDA由低拉高 |
| ACK | 接收方 | 1个时钟周期,SDA拉低表示应答 |
| NACK | 接收方 | 1个时钟周期,SDA保持高表示无应答 |

数据在SCL高电平期间必须稳定,SDA只在SCL低电平期间才能变化(START和STOP除外)。

四种传输速率

模式 速率 上拉电阻 典型应用
标准模式 100kbps 4.7kΩ~10kΩ 低速传感器、RTC
快速模式 400kbps 2.2kΩ~4.7kΩ 大部分传感器、EEPROM
高速模式 3.4Mbps <1kΩ 高速ADC、摄像头
超快模式 5Mbps 单向传输 极少数场景

速率越高,上拉电阻要越小,但功耗也越大,实际项目中400kbps覆盖90%的场景:

二、I2C子系统架构

Linux I2C子系统分三层,各层职责明确,驱动开发只需要关注最上层。

flowchart TB A["I2C设备驱动<br/>i2c_driver"] -->|"注册/注销<br/>i2c_register_driver"| B["I2C核心<br/>i2c-core"] B -->|"适配器操作<br/>master_xfer"| C["I2C控制器驱动层<br/>i2c_adapter / i2c_algorithm"] A --- A1["- 实现probe/remove<br/>- 通过i2c_msg收发数据<br/>- 解析设备树匹配设备"] B --- B1["- 管理i2c_driver和i2c_client<br/>- 提供i2c_transfer等API<br/>- SMBus协议模拟<br/>- 设备树解析与匹配"] C --- C1["- 操作控制器硬件寄存器<br/>- 实现master_xfer回调<br/>- 处理START/STOP/ACK时序<br/>- SoC厂商提供,一般不需自己写"] style A fill:#E3F2FD style B fill:#FFF8E1 style C fill:#E8F5E9 style A1 fill:#E3F2FD style B1 fill:#FFF8E1 style C1 fill:#E8F5E9

各层职责

I2C设备驱动*(我们写的部分)。
- 实现i2c_driver的probe和remove
- 通过i2c_transfer、i2c_smbus_*等API与从机通信
- 通过of_device_id匹配设备树中的设备节。
I2C核心(内核提供)。
- 管理i2c_driver和i2c_client的注册与匹配
- 向上提供统一的i2c_transfer等数据传输API
- 向下调用控制器驱动的master_xfer
- SMBus协议模拟(控制器不支持SMBus时由软件模拟。
I2C控制器驱动层*(SoC厂商提供):

  • 操作控制器硬件寄存器,产生START/STOP/ACK等信息- 实现i2c_algorithm的master_xfer回调
  • 处理中断、DMA、时钟拉伸等底层细节

驱动开发者绝大多数时候只写设备驱动层,控制器驱动由SoC厂商(NXP、Rockchip、Allwinner等)已经写好。

三、设备树配置

I2C设备要在设备树里声明,内核才能帮你创建i2c_client并匹配驱动。

I2C控制器节。

SoC的设备树通常已经定义好了I2C控制器节点:

/* SoC级设备树(厂商提供) */
i2c1: i2c@fe5a0000 {
    compatible = "rockchip,rk3568-i2c";
    reg = <0x0 0xfe5a0000 0x0 0x1000>;
    clocks = <&cru CLK_I2C1>;
    interrupts = <GIC_SPI 48 IRQ_TYPE_LEVEL_HIGH>;
    #address-cells = <1>;
    #size-cells = <0>;
    status = "disabled";
};

I2C设备子节。

在板级设备树中使能控制器并添加从设备。

/* 板级设备*/
&i2c1 {
    status = "okay";
    clock-frequency = <400000>;  /* 400kbps快速模块*/

    /* BMP280气压传感器,7位地址0x76 */
    bmp280@76 {
        compatible = "bosch,bmp280";
        reg = <0x76>;
    };

    /* AT24C02 EEPROM7位地址0x50 */
    eeprom@50 {
        compatible = "atmel,24c02";
        reg = <0x50>;
        pagesize = <8>;
    };
};

关键字段说明。
| 字段 | 说明 |
|:-----|:-----|
| compatible | 驱动匹配字符串,必须与驱动中of_device_id一致 |
| reg | 从机7位地址(不是8位),如0x76而非0xEC |
| clock-frequency | 总线时钟频率,不写则默认100kHz |

完整DTS示例

/* i2c-sensor-board.dtsi - IMU传感。+ EEPROM */
&i2c1 {
    status = "okay";
    clock-frequency = <400000>;

    /* MPU6050六轴IMU,AD0接地时地址0x68 */
    imu@68 {
        compatible = "invensense,mpu6050";
        reg = <0x68>;
        interrupt-parent = <&gpio3>;
        interrupts = <21 IRQ_TYPE_EDGE_FALLING>;
        mount-matrix = "1", "0", "0",
                       "0", "1", "0",
                       "0", "0", "1";
    };

    /* AT24C08 EEPROM,A0/A1/A2接地时地址0x50 */
    eeprom@50 {
        compatible = "atmel,24c08";
        reg = <0x50>;
        pagesize = <16>;
        read-only;
    };
};

mount-matrix是IMU传感器的安装矩阵,描述芯片坐标系与设备坐标系的映射关系,驱动读取后用于数据校正。

四、I2C设备驱动开销

i2c_driver结构建

#include <linux/i2c.h>
#include <linux/module.h>

/* 设备私有数据 */
struct bmp280_data {
    struct i2c_client *client;
    struct mutex lock;
    int calib_t1, calib_t2, calib_t3;
};

static int bmp280_probe(struct i2c_client *client,
                        const struct i2c_device_id *id)
{
    struct bmp280_data *data;

    if (!i2c_check_functionality(client->adapter,
                                  I2C_FUNC_SMBUS_BYTE_DATA))
        return -EIO;

    data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL);
    if (!data)
        return -ENOMEM;

    data->client = client;
    mutex_init(&data->lock);
    i2c_set_clientdata(client, data);

    /* 读取芯片ID验证通信 */
    int chip_id = i2c_smbus_read_byte_data(client, 0xD0);
    if (chip_id != 0x58) {
        dev_err(&client->dev, "wrong chip id: 0x%02x\n", chip_id);
        return -ENODEV;
    }

    dev_info(&client->dev, "BMP280 detected, id=0x%02x\n", chip_id);
    return 0;
}

static void bmp280_remove(struct i2c_client *client)
{
    /* devm资源自动释放,无需手动清理 */
    dev_info(&client->dev, "BMP280 removed\n");
}

ID表与设备树匹。

I2C驱动有两种匹配方式,都要写:

/* 方式1:i2c_device_id - 传统非设备树匹配 */
static const struct i2c_device_id bmp280_id[] = {
    { "bmp280", 0 },
    { }
};
MODULE_DEVICE_TABLE(i2c, bmp280_id);

/* 方式2:of_device_id - 设备树匹配(优先*/
static const struct of_device_id bmp280_of_match[] = {
    { .compatible = "bosch,bmp280" },
    { }
};
MODULE_DEVICE_TABLE(of, bmp280_of_match);

MODULE_DEVICE_TABLE宏把匹配表导出到模块信息,modprobe根据它自动加载对应驱动。

完整驱动示例:BMP280气压传感。

// bmp280.c - BMP280气压传感器驱动(Linux 6.8。#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/mutex.h>
#include <linux/regmap.h>

#define BMP280_REG_ID       0xD0
#define BMP280_REG_RESET    0xE0
#define BMP280_REG_CTRL_MEAS 0xF4
#define BMP280_REG_CONFIG   0xF5
#define BMP280_REG_TEMP_MSB 0xFA

#define BMP280_ID_VAL       0x58
#define BMP280_RESET_CMD    0xB6

/* 过采样配置*/
#define BMP280_OSRS_T_X1    (1 << 5)  /* 温度x1过采*/
#define BMP280_OSRS_P_X1    (1 << 2)  /* 气压x1过采*/
#define BMP280_MODE_NORMAL  (3 << 0)  /* 正常工作模式 */

struct bmp280_data {
    struct i2c_client *client;
    struct mutex lock;
    /* 校准参数(简化,实际有更多) */
    u16 dig_t1;
    s16 dig_t2, dig_t3;
};

static int bmp280_read_calibration(struct bmp280_data *data)
{
    struct i2c_client *client = data->client;
    int ret;

    /* 读取温度校准参数 T1/T2/T3 */
    ret = i2c_smbus_read_word_swapped(client, 0x88);
    if (ret < 0)
        return ret;
    data->dig_t1 = ret;

    ret = i2c_smbus_read_word_swapped(client, 0x8A);
    if (ret < 0)
        return ret;
    data->dig_t2 = ret;

    ret = i2c_smbus_read_word_swapped(client, 0x8C);
    if (ret < 0)
        return ret;
    data->dig_t3 = ret;

    return 0;
}

static s32 bmp280_compensate_temp(struct bmp280_data *data, s32 adc_temp)
{
    s64 var1, var2;

    var1 = ((s64)(adc_temp >> 3) - ((s64)data->dig_t1 << 1))
            * ((s64)data->dig_t2 >> 11);
    var2 = (((s64)(adc_temp >> 4) - (s64)data->dig_t1)
            * ((s64)(adc_temp >> 4) - (s64)data->dig_t1) >> 12)
            * ((s64)data->dig_t3 >> 14);
    return (var1 + var2) * 5 + 128 >> 8;  /* 单位0.01°C */
}

static int bmp280_read_raw(struct bmp280_data *data, int *temp_centi)
{
    struct i2c_client *client = data->client;
    u8 buf[3];
    s32 adc_temp;
    int ret;

    mutex_lock(&data->lock);

    /* 读取3字节温度数据 */
    ret = i2c_smbus_read_i2c_block_data(client, BMP280_REG_TEMP_MSB, 3, buf);
    if (ret < 0) {
        mutex_unlock(&data->lock);
        return ret;
    }

    adc_temp = (buf[0] << 12) | (buf[1] << 4) | (buf[2] >> 4);
    *temp_centi = bmp280_compensate_temp(data, adc_temp);

    mutex_unlock(&data->lock);
    return 0;
}

static int bmp280_probe(struct i2c_client *client,
                        const struct i2c_device_id *id)
{
    struct bmp280_data *data;
    int chip_id, ret, temp;

    /* 检查控制器是否支持所需功能 */
    if (!i2c_check_functionality(client->adapter,
                                  I2C_FUNC_SMBUS_BYTE_DATA |
                                  I2C_FUNC_SMBUS_READ_I2C_BLOCK))
        return -EIO;

    data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL);
    if (!data)
        return -ENOMEM;

    data->client = client;
    mutex_init(&data->lock);
    i2c_set_clientdata(client, data);

    /* 读取芯片ID */
    chip_id = i2c_smbus_read_byte_data(client, BMP280_REG_ID);
    if (chip_id != BMP280_ID_VAL) {
        dev_err(&client->dev, "chip id mismatch: 0x%02x\n", chip_id);
        return -ENODEV;
    }

    /* 软复*/
    ret = i2c_smbus_write_byte_data(client, BMP280_REG_RESET, BMP280_RESET_CMD);
    if (ret < 0)
        return ret;
    msleep(10);

    /* 读取校准参数 */
    ret = bmp280_read_calibration(data);
    if (ret < 0) {
        dev_err(&client->dev, "failed to read calibration: %d\n", ret);
        return ret;
    }

    /* 配置:温度x1过采。+ 气压x1过采。+ 正常模式 */
    ret = i2c_smbus_write_byte_data(client, BMP280_REG_CTRL_MEAS,
                                     BMP280_OSRS_T_X1 | BMP280_OSRS_P_X1 |
                                     BMP280_MODE_NORMAL);
    if (ret < 0)
        return ret;

    /* 读取一次温度验*/
    ret = bmp280_read_raw(data, &temp);
    if (ret < 0) {
        dev_err(&client->dev, "failed to read temperature: %d\n", ret);
        return ret;
    }
    dev_info(&client->dev, "BMP280: temp=%d.%02d C\n",
             temp / 100, temp % 100);

    return 0;
}

static void bmp280_remove(struct i2c_client *client)
{
    dev_info(&client->dev, "BMP280 removed\n");
}

static const struct i2c_device_id bmp280_id[] = {
    { "bmp280", 0 },
    { }
};
MODULE_DEVICE_TABLE(i2c, bmp280_id);

static const struct of_device_id bmp280_of_match[] = {
    { .compatible = "bosch,bmp280" },
    { }
};
MODULE_DEVICE_TABLE(of, bmp280_of_match);

static struct i2c_driver bmp280_driver = {
    .driver = {
        .name = "bmp280",
        .of_match_table = bmp280_of_match,
    },
    .probe = bmp280_probe,
    .remove = bmp280_remove,
    .id_table = bmp280_id,
};
module_i2c_driver(bmp280_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("linuxros");
MODULE_DESCRIPTION("BMP280 pressure sensor driver");

module_i2c_driver宏展开后自动注册/注销驱动,不用手写module_init和module_exit。

五、I2C数据传输API

i2c_transfer:通用传输

最灵活的传输方式,支持组合消息(一次START条件发多段数据)。

/* 写寄存器:先发寄存器地址,再发数*/
static int sensor_write_reg(struct i2c_client *client, u8 reg, u8 val)
{
    struct i2c_msg msg;
    u8 buf[2] = { reg, val };

    msg.addr = client->addr;
    msg.flags = 0;           /* 写操*/
    msg.len = 2;
    msg.buf = buf;

    return i2c_transfer(client->adapter, &msg, 1);
}

/* 读寄存器:先写寄存器地址,再读数据(restart)/
static int sensor_read_reg(struct i2c_client *client, u8 reg, u8 *val)
{
    struct i2c_msg msgs[2];
    u8 buf;

    /* 第一段:写寄存器地址 */
    msgs[0].addr = client->addr;
    msgs[0].flags = 0;       /* */
    msgs[0].len = 1;
    msgs[0].buf = &reg;

    /* 第二段:读数*/
    msgs[1].addr = client->addr;
    msgs[1].flags = I2C_M_RD; /* */
    msgs[1].len = 1;
    msgs[1].buf = &buf;

    int ret = i2c_transfer(client->adapter, msgs, 2);
    if (ret < 0)
        return ret;

    *val = buf;
    return 0;
}

i2c_transfer返回成功传输的消息数,出错返回负值。注意:返回值不直接表示成功,而是等于消息数才表示全部成功

i2c_master_send / i2c_master_recv

单次发送或接收的快捷函数:

来自 linuxros.cn · linuxROS
/* 发送数*/
int ret = i2c_master_send(client, buf, len);

/* 接收数据 */
int ret = i2c_master_recv(client, buf, len);

内部就是封装了一个i2c_msg然后调i2c_transfer。适合简单的整块读写,不适合"先写地址再读数据"这种组合操作集

i2c_msg结构建

struct i2c_msg {
    __u16 addr;    /* 从机7位地址 */
    __u16 flags;   /* 传输标志 */
    __u16 len;     /* 数据长度 */
    __u8  *buf;    /* 数据缓冲*/
};

常用flags。
| 标志 | | 说明 |
|:-----|:---|:-----|
| 0 | 0 | 写操作(默认) |
| I2C_M_RD | 0x0001 | 读操作 |
| I2C_M_TEN | 0x0010 | 10位地址 |
| I2C_M_STOP | 0x8000 | 强制在消息后发STOP |

SMBus操作

SMBus是I2C的子集协议,定义了更严格的时序和标准化的读写协议。Linux内核提供了SMBus封装API,大多数传感器都支持。

/* 读单字节寄存*/
int val = i2c_smbus_read_byte_data(client, reg);

/* 写单字节寄存*/
int ret = i2c_smbus_write_byte_data(client, reg, val);

/* 。6位字(小端序,字节交换) */
int val = i2c_smbus_read_word_swapped(client, reg);

/* 。6位字 */
int ret = i2c_smbus_write_word_swapped(client, reg, val);

/* 读多字节*/
int ret = i2c_smbus_read_i2c_block_data(client, reg, len, buf);

/* 写多字节*/
int ret = i2c_smbus_write_i2c_block_data(client, reg, len, buf);

/* 读单字节(无寄存器地址*/
int val = i2c_smbus_read_byte(client);

/* 写单字节(无寄存器地址*/
int ret = i2c_smbus_write_byte(client, val);

SMBus API返回值:成功返回读取的数据(read)或0(write),失败返回负值
使用前用i2c_check_functionality检查控制器支持情况。

if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA))
    return -EIO;

传输方式对比

方式 适用场景 优点 缺点
i2c_transfer 组合读写、复杂时序 灵活,支持restart 代码稍多
i2c_master_send/recv 整块读写 简单 不支持组合操作
i2c_smbus_read_byte_data 单字节寄存器读写 最简单 只能一字节
i2c_smbus_read_i2c_block_data 多字节寄存器读写 简洁,支持块传递 控制器必须支持
i2c_smbus_read_word_swapped 16位寄存器 自动字节交换 只能一字节

实际开发中优先用SMBus API,不够灵活时再退回i2c_transfer。

六、I2C控制器驱动

i2c_adapter + i2c_algorithm

控制器驱动注册一个i2c_adapter,描述一个I2C控制器实例,并绑定i2c_algorithm描述操作方法。

struct i2c_adapter {
    struct module *owner;
    unsigned int class;
    const struct i2c_algorithm *algo;  /* 操作方法 */
    void *algo_data;
    struct device dev;                  /* 内嵌device */
    int nr;                             /* 总线编号 */
    char name[48];
    /* ... */
};

struct i2c_algorithm {
    int (*master_xfer)(struct i2c_adapter *adap,
                       struct i2c_msg *msgs, int num);
    int (*smbus_xfer)(struct i2c_adapter *adap,
                      u16 addr, unsigned short flags,
                      char read_write, u8 command,
                      int size, union i2c_smbus_data *data);
    u32 (*functionality)(struct i2c_adapter *adap);
};

master_xfer是核心回调,接收一组i2c_msg,操作硬件完成传输。functionality返回控制器支持的功能掩码。

何时需要写控制器驱动

*绝大多数情况不需要 SoC厂商已经在主线内核或BSP里提供了控制器驱动:

SoC 控制器驱动文件 compatible
RK3568 drivers/i2c/busses/i2c-rk3x.c rockchip,rk3568-i2c
i.MX6 drivers/i2c/busses/i2c-imx.c fsl,imx6q-i2c
STM32MP drivers/i2c/busses/i2c-stm32f7.c st,stm32mp15-i2c
Allwinner drivers/i2c/busses/i2c-sun6i-p2wi.c allwinner,sun6i-a31-p2wi

需要自己写控制器驱动的场景:- 自研FPGA实现I2C控制。- 使用冷门SoC,主线内核没有支。- 需要优化控制器性能(如DMA传输。

七、调试技。

i2c-tools

最常用的I2C调试工具集,Ubuntu直接apt install i2c-tools。

# 列出系统所有I2C总线
i2cdetect -l

# 扫描总线0上的从机设备
i2cdetect -y 0

# 读取0x76设备0xD0寄存器(BMP280芯片ID)
i2cget -y 0 0x76 0xD0

# 写入0x50设备0x00寄存器(EEPROM)
i2cset -y 0 0x50 0x00 0xAA

# dump整个0x50设备的寄存器
i2cdump -y 0 0x50

i2cdetect扫描结果中--表示地址无应答,UU表示该地址已被驱动占用,数字表示检测到设备。

i2c-stub:虚拟I2C设备

不需要真实硬件就能测试I2C驱动。

# 加载i2c-stub模块,模拟地址0x76的设备
sudo modprobe i2c-stub chip_addr=0x76

# 查看虚拟总线编号
i2cdetect -l
# i2c-3  i2c     Stub device  ...

# 在虚拟设备上读写测试
i2cset -y 3 0x76 0xD0 0x58   # 模拟BMP280的ID寄存器
i2cget -y 3 0x76 0xD0         # 读回验证

开发驱动时先用stub验证逻辑,再上真机调试,效率高很多。

逻辑分析仪抓。

软件调试搞不定时,上逻辑分析仪看实际波形。
1. Saleae Logic / sigrok:抓取SDA/SCL信号,自动解码I2C协议
2. *关注。 - START/STOP是否正确产生
- 从机地址是否匹配(注意7位vs8位)
- ACK/NACK位置——NACK说明从机没响。 - 时钟频率是否在从机支持范围内
- 信号上升沿是否太缓(上拉电阻过大或总线电容过大。

八、常见问题

Q1:probe不被调用。

驱动加载了但probe没执行,最常见的原因是匹配失败。
1. 设备树compatible与驱动of_device_id不一致——逐字对比,大小写、逗号都要一致。2. 设备树reg地址与驱动id_table不匹配——检查7位地址是否正确
3. 控制器节点status = "disabled"——板级DTS里必须status = "okay"
4. I2C控制器驱动未加载——先确认i2cdetect -l能看到总线

排查步骤。

# 查看已注册的I2C设备
ls /sys/bus/i2c/devices/

# 查看驱动匹配置cat /sys/bus/i2c/drivers/bmp280/modalias

# 查看设备树节点是否生成
ls /proc/device-tree/i2c@fe5a0000/

Q2:i2c_transfer返回-ENXIO。

-ENXIO(No such device or address)表示从机无应答(NACK):

  1. 从机地址错误——对照数据手册确认7位地址,注意AD0/AD1等引脚配置2. 硬件连接问题——SDA/SCL是否接反、上拉电阻是否焊接、供电是否正常。3. **从机未就绪——某些传感器上电后需要几毫秒初始化时间,立即访问会NACK
  2. *总线被占——其他主机或从机拉低了SDA/SCL

```bash

用i2cdetect确认设备是否在线

i2cdetect -y 1

如果0x76位置显示"--",说明总线上确实没有这个地址的设```

Q3:I2C通信偶尔失败。

间歇性失败是最头疼的,常见原因。
| 原因 | 现象 | 解决 |
|:-----|:-----|:-----|
| 上拉电阻过大 | 上升沿缓慢,高速时采样错误 | 减小上拉电阻。>400kbps用1.2kΩ~4.7kΩ|
| 总线电容过大 | 信号畸变,长走线/多设备时明显 | 缩短走线,减少挂载设备数 |
| 时钟拉伸 | 从机拉低SCL请求等待,主机超时 | 增加超时时间,或换用支持时钟拉伸的控制器 |
| 中断延迟 | 实时性要求高的场景,中断响应 | 使用RT内核或线程化中断 |
| 电源噪声 | 传感器供电不稳导致通信异常 | 加去耦电容,检查LDO输出 |

用逻辑分析仪抓波形,看上升沿时间和信号完整性,基本能定位问题。

九、总结

I2C驱动开发速查看

环节 关键字
协议 两线开销上拉7位地址,START→地址+R/W→ACK→数据→STOP
子系统架 设备驱动→核心层→控制器驱动,三层解
设备 compatible匹配驱动,reg7位地址,clock-frequency设速率
驱动注册 i2c_driver+of_device_id+i2c_device_id,module_i2c_driver
数据传输 优先SMBus API,复杂时序用i2c_transfer
控制器驱动 SoC厂商提供,一般不需要自己写
调试 i2c-tools扫描读写,i2c-stub虚拟设备,逻辑分析仪看波形
常见故障 probe不调→匹配失败,ENXIO→地址/硬件错,间歇失败→上拉电容/时钟拉伸

本文首发于linuxros.cn,转载请注明出处。

版权声明

作者linuxROS
协议本作品采用 CC BY-NC-SA 4.0 许可协议:署名-非商业性使用-相同方式共享
关注欢迎关注微信公众号 linuxROS,获取更多机器人 / 嵌入式 / Linux 干货
返回首页