【I2C 技术系列 11】I2C Linux 驱动框架

📚 I2C 技术系列 · 持续更新中

本系列从 OD 开漏物理层一路打到 Linux i2c子系统,把"两根线"背后那套电气/协议/仲裁/恢复/驱动全拧成一根线。

👉 想系统追更,戳这里查看完整目录:

【I2C 技术系列 00】总目录


本篇定位:裸机你已经会写 I2C(08-10),本篇讲 Linux 下 I2C 子系统怎么组织。i2c_adapter(控制器)/i2c_algorithm(算法)/i2c_client(从机)/i2c_driver(驱动)四对象,加上设备树、i2c_transfer、i2c-dev 用户态接口。读完你能写 Linux I2C 控制器驱动和设备驱动,这是你 BSP 转型的主场。
Linux I2C 框架把裸机那套"全封装"了

你裸机直接写寄存器。Linux 把控制器抽象成 i2c_adapter,把从机抽象成 i2c_client,驱动用 i2c_transfer 提交消息,框架调控制器的 master_xfer。你写的是"适配层",框架管调度/锁/恢复 。对照 【Linux内核专栏 11】驱动框架:platform / char / misc / 设备树 一起看。


目录

  • [一、Linux I2C 子系统总览](#一、Linux I2C 子系统总览)
  • 二、四大核心对象
  • [三、i2c_adapter 与 i2c_algorithm(控制器驱动)](#三、i2c_adapter 与 i2c_algorithm(控制器驱动))
    • [3.1 i2c_adapter 结构(简化)](#3.1 i2c_adapter 结构(简化))
    • [3.2 i2c_algorithm](#3.2 i2c_algorithm)
    • [3.3 控制器驱动骨架(DesignWare 风格)](#3.3 控制器驱动骨架(DesignWare 风格))
    • [3.4 注册 adapter](#3.4 注册 adapter)
  • [四、i2c_client 与 i2c_driver(设备驱动)](#四、i2c_client 与 i2c_driver(设备驱动))
    • [4.1 i2c_client](#4.1 i2c_client)
    • [4.2 i2c_driver](#4.2 i2c_driver)
    • [4.3 设备驱动骨架(传感器 BMP280 风格)](#4.3 设备驱动骨架(传感器 BMP280 风格))
    • [4.4 匹配机制](#4.4 匹配机制)
  • [五、i2c_transfer 与消息传输](#五、i2c_transfer 与消息传输)
    • [5.1 i2c_msg](#5.1 i2c_msg)
    • [5.2 i2c_transfer](#5.2 i2c_transfer)
    • [5.3 读寄存器(Sr 经典用法)](#5.3 读寄存器(Sr 经典用法))
    • [5.4 SMBus 简化 API](#5.4 SMBus 简化 API)
    • [5.5 regmap(更高层封装)](#5.5 regmap(更高层封装))
  • 六、设备树描述
    • [6.1 I2C 控制器节点](#6.1 I2C 控制器节点)
    • [6.2 从机节点](#6.2 从机节点)
    • [6.3 设备树到 client](#6.3 设备树到 client)
  • [七、i2c-dev 用户态接口](#七、i2c-dev 用户态接口)
    • [7.1 /dev/i2c-N](#7.1 /dev/i2c-N)
    • [7.2 用户态读写代码](#7.2 用户态读写代码)
    • [7.3 i2c-tools](#7.3 i2c-tools)
  • [八、sysfs 与 i2c-tools](#八、sysfs 与 i2c-tools)
    • [8.1 sysfs 路径](#8.1 sysfs 路径)
    • [8.2 看总线设备](#8.2 看总线设备)
  • [九、关键问题 Q&A](#九、关键问题 Q&A)
    • [Q1:i2c_transfer 的多个 msg 之间是 Sr 还是 STOP+START?](#Q1:i2c_transfer 的多个 msg 之间是 Sr 还是 STOP+START?)
    • [Q2:设备树 reg 是 7bit 还是 8bit?](#Q2:设备树 reg 是 7bit 还是 8bit?)
    • Q3:控制器驱动和设备驱动谁先加载?
    • [Q4:devm_ 资源管理在 I2C 驱动怎么用?](#Q4:devm_ 资源管理在 I2C 驱动怎么用?)
    • [Q5:用户态 i2c-dev 和内核设备驱动冲突吗?](#Q5:用户态 i2c-dev 和内核设备驱动冲突吗?)
    • [Q6:I2C 总线锁怎么加?](#Q6:I2C 总线锁怎么加?)
  • 十、实战案例
    • [案例 1:写一个 BMP280 设备驱动(简化)](#案例 1:写一个 BMP280 设备驱动(简化))
    • [案例 2:用户态调试 EEPROM](#案例 2:用户态调试 EEPROM)
    • [案例 3:控制器驱动调试,probe 失败](#案例 3:控制器驱动调试,probe 失败)
    • [案例 4:i2c_transfer 读寄存器返回错](#案例 4:i2c_transfer 读寄存器返回错)
    • [案例 5:多 client 共享 adapter,锁冲突?](#案例 5:多 client 共享 adapter,锁冲突?)
  • 十一、本篇小结与速查

一、Linux I2C 子系统总览

复制代码
用户态:  i2c-tools(i2cdetect/dump/get/set) / 自写程序(/dev/i2c-N)
              │
              │ syscall
              ▼
VFS:     /dev/i2c-N(i2c-dev 字符设备)
              │
              ▼
I2C 核心:i2c-core(adapter/client/driver 匹配 + i2c_transfer)
              │
              ├── 控制器驱动(i2c_adapter + i2c_algorithm)
              │       │
              │       ▼
              │   硬件寄存器(08 篇)
              │
              └── 设备驱动(i2c_driver + i2c_client)
                      │
                      ▼
                  传感器/EEPROM/PMIC
  • i2c-core:框架核心,匹配 adapter/client/driver,提供 i2c_transfer API
  • 控制器驱动(adapter driver):实现 master_xfer,操作硬件寄存器
  • 设备驱动(client driver):针对具体器件(传感器/EEPROM),用 i2c_transfer 读写
  • i2c-dev:把 controller 暴露成 /dev/i2c-N,用户态直接读写

二、四大核心对象

对象 作用 类比裸机
i2c_adapter 一个 I2C 控制器(总线) 你的 I2C 控制器实例
i2c_algorithm adapter 的传输算法(master_xfer) 你的 i2c_write/read 实现
i2c_client 一个 I2C 从机设备(挂在某 adapter) 你的从机地址 + 上下文
i2c_driver 从机设备驱动(probe/remove) 你的器件驱动逻辑

关系图

复制代码
i2c_adapter(控制器0)
  ├── i2c_algorithm(master_xfer)
  └── i2c_client(从机 0x50,EEPROM)
        └── i2c_driver(at24 驱动) ← 匹配 compatible
      i2c_client(从机 0x76,BMP280)
        └── i2c_driver(bmp280 驱动)
  • 一个 adapter 管一条 I2C 总线,挂多个 client
  • 每个 client 对应一个从机设备
  • i2c_driver 通过 compatible/name 匹配 client

三、i2c_adapter 与 i2c_algorithm(控制器驱动)

3.1 i2c_adapter 结构(简化)

c 复制代码
struct i2c_adapter {
    struct module *owner;
    const struct i2c_algorithm *algo;  // 传输算法
    void *algo_data;                    // 驱动私有数据
    struct device dev;                  // 设备模型
    int nr;                             // 总线号(i2c-0)
    char name[48];
    ...
};

3.2 i2c_algorithm

c 复制代码
struct i2c_algorithm {
    int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
    int (*master_xfer_atomic)(...);     // 原子上下文(不可睡眠)
    int (*smbus_xfer)(...);             // SMBus 传输(可选)
    u32 (*functionality)(...);          // 支持的功能(标志位)
    ...
};
  • master_xfer:核心,提交一组 i2c_msg 传输
  • functionality:报告支持的功能(I2C_FUNC_I2C / SMBUS / 10BIT 等)

3.3 控制器驱动骨架(DesignWare 风格)

c 复制代码
struct dw_i2c_priv {
    void __iomem *base;
    struct i2c_adapter adapter;
    struct i2c_msg *msgs;
    int msg_idx;
    ...
};

static int dw_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) {
    struct dw_i2c_priv *priv = i2c_get_adapdata(adap);
    int ret;
    
    // 1. 使能控制器
    writel(1, priv->base + IC_ENABLE);
    
    // 2. 逐 msg 传输
    for (int i = 0; i < num; i++) {
        ret = dw_i2c_xfer_one(priv, &msgs[i]);
        if (ret < 0) return ret;
    }
    
    return num;  // 返回成功传输的 msg 数
}

static u32 dw_i2c_func(struct i2c_adapter *adap) {
    return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_10BIT;
}

static const struct i2c_algorithm dw_i2c_algo = {
    .master_xfer = dw_i2c_xfer,
    .functionality = dw_i2c_func,
};

// probe(platform 驱动)
static int dw_i2c_probe(struct platform_device *pdev) {
    struct dw_i2c_priv *priv;
    struct resource *res;
    
    priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
    
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    priv->base = devm_ioremap_resource(&pdev->dev, res);
    
    // 初始化硬件(分频/中断/...)
    dw_i2c_init(priv);
    
    // 申请中断
    devm_request_irq(&pdev->dev, platform_get_irq(pdev, 0),
                     dw_i2c_isr, 0, "dw_i2c", priv);
    
    // 注册 adapter
    priv->adapter.owner = THIS_MODULE;
    priv->adapter.algo = &dw_i2c_algo;
    priv->adapter.dev.parent = &pdev->dev;
    snprintf(priv->adapter.name, sizeof(priv->adapter.name), "i2c-%d", pdev->id);
    i2c_set_adapdata(&priv->adapter, priv);
    
    return i2c_add_adapter(&priv->adapter);
    // 注册后,内核自动扫描设备树,创建 i2c_client
}

3.4 注册 adapter

c 复制代码
// 动态总线号
i2c_add_adapter(&adap);       // 自动分配 nr

// 固定总线号
i2c_add_numbered_adapter(&adap);  // adap.nr 必须预设
  • 注册后,框架扫描设备树/ACPI,为子节点创建 i2c_client
  • 注册后,/dev/i2c-N 出现(i2c-dev 自动绑定)

四、i2c_client 与 i2c_driver(设备驱动)

4.1 i2c_client

c 复制代码
struct i2c_client {
    unsigned short addr;          // 从机地址(7bit)
    char name[I2C_NAME_SIZE];
    struct i2c_adapter *adapter;  // 所属控制器
    struct device dev;             // 设备模型
    int irq;                       // 中断号
    ...
};
  • 一个 client = 一个从机设备实例
  • 由框架根据设备树创建

4.2 i2c_driver

c 复制代码
struct i2c_driver {
    int (*probe)(struct i2c_client *client, const struct i2c_device_id *id);
    int (*remove)(struct i2c_client *client);
    struct device_driver driver;
    const struct i2c_device_id *id_table;     // 传统匹配
    const struct of_device_id *of_match_table; // 设备树匹配
    ...
};

4.3 设备驱动骨架(传感器 BMP280 风格)

c 复制代码
struct bmp280_data {
    struct i2c_client *client;
    struct regmap *regmap;
    ...
};

static int bmp280_probe(struct i2c_client *client, const struct i2c_device_id *id) {
    struct bmp280_data *data;
    int ret;
    
    data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL);
    data->client = client;
    i2c_set_clientdata(client, data);
    
    // 读芯片 ID 验证
    ret = i2c_smbus_read_byte_data(client, BMP280_REG_ID);
    if (ret != BMP280_CHIP_ID) return -ENODEV;
    
    // 初始化(regmap/iio/输入设备...)
    bmp280_init(data);
    
    // 注册到子系统(iio/input/hwmon...)
    return bmp280_register_hwmon(data);
}

static int bmp280_remove(struct i2c_client *client) {
    // devm_ 自动清理,多数无需操作
    return 0;
}

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 = {
    .probe = bmp280_probe,
    .remove = bmp280_remove,
    .driver = {
        .name = "bmp280",
        .of_match_table = bmp280_of_match,
    },
};
module_i2c_driver(bmp280_driver);  // 自动 init/exit

4.4 匹配机制

复制代码
设备树: compatible = "bosch,bmp280"
              ↓
框架: 扫描 i2c_driver.of_match_table
              ↓
匹配: bmp280_driver.match("bosch,bmp280") → 命中
              ↓
probe: 调 bmp280_probe(client),client 包含地址/中断

五、i2c_transfer 与消息传输

5.1 i2c_msg

c 复制代码
struct i2c_msg {
    __u16 addr;    // 从机地址(7bit)
    __u16 flags;   // 标志(I2C_M_RD 读 / I2C_M_TEN 10bit / I2C_M_NOSTART ...)
    __u16 len;     // 数据长度
    __u8 *buf;     // 数据缓冲
};

5.2 i2c_transfer

c 复制代码
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
  • 提交一组 msg,原子执行(中间不发 STOP,自动产生 Sr)
  • 返回成功 msg 数,负值错误

5.3 读寄存器(Sr 经典用法)

c 复制代码
// 读 BMP280 寄存器 0xF6,2 字节
int bmp280_read_reg(struct i2c_client *client, u8 reg, u8 *buf, int len) {
    struct i2c_msg msgs[2] = {
        [0] = {
            .addr = client->addr,
            .flags = 0,                // 写
            .len = 1,
            .buf = &reg,               // 寄存器地址
        },
        [1] = {
            .addr = client->addr,
            .flags = I2C_M_RD,         // 读
            .len = len,
            .buf = buf,
        },
    };
    return i2c_transfer(client->adapter, msgs, 2);
    // 框架自动:START + 写reg + Sr + 读 + NACK + STOP
}
  • 两个 msg 一组,框架产生 Sr(不是 STOP+START)
  • 这就是 04 篇 Sr 的 Linux 实现

5.4 SMBus 简化 API

c 复制代码
// 读一字节寄存器
int val = i2c_smbus_read_byte_data(client, reg);

// 写一字节寄存器
i2c_smbus_write_byte_data(client, reg, val);

// 读一字节(当前地址)
int val = i2c_smbus_read_byte(client);

// 读 block(长度+数据)
i2c_smbus_read_block_data(client, reg, buf);

// 写 block
i2c_smbus_write_block_data(client, reg, len, buf);
  • SMBus API 封装常用操作,不用手写 i2c_msg
  • 多数器件用 SMBus API 足够

5.5 regmap(更高层封装)

c 复制代码
struct regmap_config config = {
    .reg_bits = 8,
    .val_bits = 8,
};
struct regmap *regmap = devm_regmap_init_i2c(client, &config);

regmap_read(regmap, reg, &val);
regmap_write(regmap, reg, val);
regmap_bulk_read(regmap, reg, buf, len);
  • regmap 进一步封装,支持缓存/同步/调试
  • 现代驱动首选

六、设备树描述

6.1 I2C 控制器节点

dts 复制代码
&i2c0 {
    status = "okay";
    clock-frequency = <400000>;     // 400k
    pinctrl-0 = <&i2c0_pins>;
    
    /* 子节点 = I2C 从机 */
};

6.2 从机节点

dts 复制代码
&i2c0 {
    status = "okay";
    clock-frequency = <400000>;
    
    bmp280@76 {
        compatible = "bosch,bmp280";
        reg = <0x76>;               // I2C 地址(7bit)
        interrupt-parent = <&gpio0>;
        interrupts = <5 IRQ_TYPE_EDGE_FALLING>;
        vddio-supply = <&vcc3v3>;
    };
    
    eeprom@50 {
        compatible = "atmel,24c04";
        reg = <0x50>;
        pagesize = <16>;
    };
    
    pmic@5a {
        compatible = "rockchip,rk817";
        reg = <0x5a>;
        interrupt-parent = <&gpio0>;
        interrupts = <3 IRQ_TYPE_LEVEL_LOW>;
        #clock-cells = <1>;
    };
};
  • reg = I2C 地址(7bit,不要左移)
  • compatible 匹配 i2c_driver
  • 其他属性(中断/电源/clock)给驱动用

6.3 设备树到 client

复制代码
1. 内核解析 DTS,i2c0 节点下每个子节点
2. 为每个子节点创建 i2c_client(addr = reg, irq = interrupts)
3. 用 compatible 匹配 i2c_driver
4. 匹配成功调 probe

七、i2c-dev 用户态接口

7.1 /dev/i2c-N

bash 复制代码
ls /dev/i2c-*
# /dev/i2c-0 /dev/i2c-1
  • 每个 adapter 一个 /dev/i2c-N
  • 用户态 open 后用 ioctl 读写

7.2 用户态读写代码

c 复制代码
#include <linux/i2c-dev.h>
#include <sys/ioctl.h>
#include <fcntl.h>

int fd = open("/dev/i2c-0", O_RDWR);
ioctl(fd, I2C_SLAVE, 0x50);   // 设从机地址

// 读寄存器 0x10
__u8 reg = 0x10;
write(fd, &reg, 1);           // 写寄存器地址
__u8 val;
read(fd, &val, 1);            // 读数据

// 或用 SMBus ioctl
__u8 val = i2c_smbus_read_byte_data(fd, 0x10);  // 用户态封装

close(fd);

7.3 i2c-tools

bash 复制代码
i2cdetect -y 0              # 扫描总线
i2cdump -y 0 0x50           # dump 0x50 寄存器
i2cget -y 0 0x50 0x10       # 读 0x50 的 0x10
i2cset -y 0 0x50 0x10 0xAB  # 写
  • i2c-tools 基于 i2c-dev,调试神器
  • 详见 \[13-I2C调试与排查]

八、sysfs 与 i2c-tools

8.1 sysfs 路径

复制代码
/sys/bus/i2c/
  devices/
    0-0076/         <- i2c-0 上地址 0x76 的 client
      name
      uevent
      of_node/      <- 设备树节点
      ...
    i2c-0/          <- adapter
      name
      device/
      ...
  drivers/
    bmp280/
      0-0076

8.2 看总线设备

bash 复制代码
ls /sys/bus/i2c/devices/
# 0-0076  0-0050  i2c-0

cat /sys/bus/i2c/devices/0-0076/name
# bmp280

九、关键问题 Q&A

Q1:i2c_transfer 的多个 msg 之间是 Sr 还是 STOP+START?

A :Sr(Repeated START)。i2c_transfer 一次调用内的多个 msg 之间不发 STOP,自动产生 Sr,保证原子性。这是读寄存器(写地址+Sr+读)的正确方式。若需要 STOP,分两次 i2c_transfer 调用。

Q2:设备树 reg 是 7bit 还是 8bit?

A :7bit 。reg = <0x50> 是 7bit 地址 0x50。框架自动左移加 R/W。别写 0xA0(那是 8bit),会地址错位。

Q3:控制器驱动和设备驱动谁先加载?

A :控制器驱动先(platform 驱动,probe 时注册 adapter)。框架注册 adapter 后扫描设备树子节点,创建 client 并匹配设备驱动。控制器驱动在 make menuconfig 里是 I2C_DESIGNWARE 之类,设备驱动是 BMP280 之类。

Q4:devm_ 资源管理在 I2C 驱动怎么用?

A :devm_kzalloc / devm_ioremap_resource / devm_request_irq / devm_regmap_init_i2c 都用 devm_。probe 失败或 remove 时自动释放,不用手动 free。现代 I2C 驱动 remove 多数为空(devm_ 全自动)。

Q5:用户态 i2c-dev 和内核设备驱动冲突吗?

A :可以共存,但同一 client 最好别同时用。i2c-dev 直接操作 adapter,不经设备驱动。若设备驱动已 probe 某从机,用 i2c-dev 再操作可能干扰。调试用 i2c-dev,生产用设备驱动,别混。

Q6:I2C 总线锁怎么加?

A :i2c-core 内部有 adapter 锁(__i2c_lock),同一 adapter 的传输串行化。你写设备驱动不用自己加锁(框架已加)。多 client 共享 adapter,框架保证一次只一个传输。


十、实战案例

案例 1:写一个 BMP280 设备驱动(简化)

c 复制代码
// 完整最小骨架
static int bmp280_probe(struct i2c_client *client, const struct i2c_device_id *id) {
    struct device *hwmon;
    int chip_id = i2c_smbus_read_byte_data(client, 0xD0);
    if (chip_id != 0x58) return -ENODEV;
    
    hwmon = devm_hwmon_device_register_with_groups(&client->dev,
            "bmp280", NULL, bmp280_groups);
    return PTR_ERR_OR_ZERO(hwmon);
}

static const struct of_device_id bmp280_of_match[] = {
    { .compatible = "bosch,bmp280", },
    { },
};
module_i2c_driver(&(struct i2c_driver){
    .probe = bmp280_probe,
    .driver = { .name = "bmp280", .of_match_table = bmp280_of_match },
});

设备树加 bmp280@76 { compatible = "bosch,bmp280"; reg = <0x76>; };,启动后 sysfs 出现 hwmon。

案例 2:用户态调试 EEPROM

bash 复制代码
i2cdetect -y 0           # 看到 0x50
i2cget -y 0 0x50 0x00    # 读 0x00
i2cset -y 0 0x50 0x00 0xAA  # 写 0xAA 到 0x00
i2cget -y 0 0x50 0x00    # 读回验证

案例 3:控制器驱动调试,probe 失败

现象:自定义 RISC-V SoC I2C 控制器驱动 probe 失败。

排查:

  1. dmesg 看错误
  2. 确认设备树 compatible 匹配驱动 of_match_table
  3. 确认 clock/reset/pinctrl 已配
  4. 确认寄存器地址映射对(devm_ioremap_resource)
  5. 确认中断号对

案例 4:i2c_transfer 读寄存器返回错

现象 :i2c_transfer 返回 < 2,读失败。

排查:

  1. 查 dmesg 是否有 NACK/ARB_LOST
  2. 示波器看波形,确认 START/地址/ACK
  3. 确认地址对(7bit)
  4. 确认 client->adapter 对

案例 5:多 client 共享 adapter,锁冲突?

现象:担心多传感器共总线,驱动并发访问冲突。

答 :i2c-core adapter 锁自动串行化,不用驱动管。但 设备驱动内部状态(如缓存)要自己加锁(mutex)。


十一、本篇小结与速查

核心要点

  • Linux I2C 四对象:i2c_adapter(控制器)/i2c_algorithm(传输)/i2c_client(从机)/i2c_driver(驱动)
  • 控制器驱动:实现 master_xfer,注册 i2c_adapter,操作硬件寄存器
  • 设备驱动:实现 probe/remove,用 i2c_transfer/i2c_smbus/regmap 读写器件
  • i2c_transfer 一组 msg 间自动产生 Sr(不是 STOP+START),保证原子性
  • 设备树:i2c 节点 + 子节点(reg=7bit 地址,compatible 匹配)
  • i2c-dev:/dev/i2c-N + ioctl,用户态调试
  • sysfs:/sys/bus/i2c/devices/
  • 框架自动加 adapter 锁,驱动不用管总线锁
  • devm_ 资源管理,remove 多数为空
  • i2c-tools(i2cdetect/dump/get/set)调试神器

速查表

想干啥 API/做法
写控制器驱动 i2c_add_adapter + master_xfer
写设备驱动 module_i2c_driver + probe
读寄存器 i2c_smbus_read_byte_data(client, reg)
写寄存器 i2c_smbus_write_byte_data(client, reg, val)
多 msg 原子传输 i2c_transfer(adap, msgs, num)
高层封装 devm_regmap_init_i2c
设备树地址 reg = <0x50>(7bit)
用户态读写 open /dev/i2c-N + ioctl I2C_SLAVE
扫描总线 i2cdetect -y N
看 client /sys/bus/i2c/devices/

延伸阅读

参考资料

  • Linux Documentation/i2c/(writing-clients / dev-interface / i2c-protocol)
  • Linux drivers/i2c/(i2c-core-base.c / i2c-dev.c / busses/)
  • LDD3 第 10 章(I2C)
  • Linux 内核源码 drivers/iio/pressure/bmp280-i2c.c

【I2C 技术系列 00】总目录 · 上一篇:【I2C 技术系列 10】I2C 错误处理与总线恢复 · 下一篇:\[12-I2C设备驱动实战]


✨ 感谢你的阅读 ✨

技术之路漫漫,分享是为了更好地交流。

如果本文的内容对你有启发,希望能得到你的支持:

👍 点赞 | ⭐ 收藏 | 👀 关注

如果你在调试过程中遇到了其他问题,

欢迎在 评论区 💬 留言,我们一起探讨。

关注我,一起交流底层开发的那些事儿!🚀


相关推荐
imDwAaY1 小时前
程序里的地址是真实内存地址吗?从虚拟内存到地址转换
java·linux·服务器
碧海银沙音频科技研究院1 小时前
炬芯ATS3089智能手表烧录方法
人工智能·驱动开发·嵌入式硬件·算法
INGNIGHT1 小时前
486. 预测赢家(博弈dp/区间dp)
linux·c++
深圳市恒锐丰科技杨生1 小时前
LTK52201 立体声 D 类音频功率放大器|LTKCHIP 联辉科
嵌入式硬件·硬件工程
2402_882893862 小时前
Linux 进程概念详解(下)—— 进程调度、环境变量与虚拟地址空间
linux·进程·虚拟地址空间
华清远见IT开放实验室2 小时前
百校同练,虚仿先行 | 2026秋季虚拟仿真“百校同练”实训行动正式启动,100所院校免费申报
人工智能·嵌入式硬件·物联网·具身智能·华清远见·百校同练
虚无的纽扣2 小时前
【Linux】揭开文件系统神秘的面纱:第四讲:可执行文件的组装车间
linux·运维·服务器
YMWM_2 小时前
ubuntu系统字体缩放配置
linux·运维·ubuntu
wuyk5552 小时前
样章-第03讲-FreeRTOS在16KB-RAM上的裁剪与陷阱
c语言·开发语言·stm32·单片机·嵌入式硬件·物联网·系统架构