第一部分:I2C 子系统彻底拆解
1.1 I2C 子系统分层架构(必考)
┌─────────────────────────────────────────────────────────┐ │ 应用层 │ │ - 用户态调用:read/write/ioctl │ │ - 文件:/dev/i2c-0(用户态直接操作I2C总线) │ ├─────────────────────────────────────────────────────────┤ │ I2C 核心层(drivers/i2c/i2c-core.c) │ │ - 管理I2C总线、适配器、设备 │ │ - 提供总线/设备注册注销的API │ │ - 转发读写请求到适配器驱动(含总线锁) │ ├─────────────────────────────────────────────────────────┤ │ I2C 适配器层(drivers/i2c/busses/i2c-hi3516.c) │ │ - 硬件控制器驱动(如Hi3516的I2C控制器) │ │ - 实现 struct i2c_algorithm 的回调 │ │ - 负责实际收发时序(启动/停止/ACK/NACK) │ ├─────────────────────────────────────────────────────────┤ │ I2C 设备驱动层(你写的ICM-20602驱动) │ │ - 实现 struct i2c_driver │ │ - 只关心「发什么命令/读什么数据」,不关心硬件时序 │ └─────────────────────────────────────────────────────────┘面试话术:
> I2C子系统从下到上分为四层:设备驱动层(我写的ICM-20602驱动)、适配器层(Hi3516的I2C控制器驱动)、核心层(i2c-core,管理总线注册和设备添加)、应用层(用户态通过 /dev/i2c-* 访问)。我的驱动工作在设备驱动层,通过核心层的 API(i2c_transfer)向适配器层发起读写,适配器层再操作硬件完成实际时序。
1.2 关键结构体逐一详解(重点背)
① struct i2c_adapter(适配器 = 一个I2C控制器)
struct i2c_adapter { struct module *owner; // 所属模块 unsigned int class; // 适配器支持的功能类别 const struct i2c_algorithm *algo; // ← 传输算法回调(核心) int timeout; // 超时时间(jiffies) int retries; // 重试次数 int nr; // 总线编号(如0 → /dev/i2c-0) char name[48]; // 适配器名字("hi3516-i2c") struct i2c_bus_recovery_info *bus_recovery_info; // 总线恢复 ... };algo的本质:
struct i2c_algorithm { // 核心传输函数(适配器驱动必须实现) int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs, int num); // 每条 i2c_msg 走一次 master_xfer;多个msg用 i2c_transfer 连续调用 int (*smbus_xfer)(...); // SMBus传输(简化版,可选) u32 (*functionality)(struct i2c_adapter *); // 该控制器支持的功能位 };② struct i2c_msg(一次I2C传输的消息单位)
struct i2c_msg { __u16 addr; // 从设备地址(7bit 或 10bit) __u16 flags; // 标志:I2C_M_RD(读)、I2C_M_TEN(10位地址)等 __u16 len; // 数据长度 __u8 *buf; // 数据缓冲区 };读操作写法(例:读WHO_AM_I,地址0x75):
struct i2c_msg msgs[2]; u8 reg = 0x75; // 要读的寄存器地址 u8 val = 0; // 读回的数据 msgs[0].addr = 0x68; // 设备地址 msgs[0].flags = 0; // 写 msgs[0].len = 1; msgs[0].buf = ® msgs[1].addr = 0x68; msgs[1].flags = I2C_M_RD; // 读 msgs[1].len = 1; msgs[1].buf = &val; i2c_transfer(client->adapter, msgs, 2); // 先写寄存器地址,再读数据 // 结果:val = WHO_AM_I(应为0x12)③ struct i2c_client(I2C从设备实例)
struct i2c_client { unsigned short addr; // 设备地址(7bit) char name[I2C_NAME_SIZE]; // 设备名 struct i2c_adapter *adapter; // 它挂在哪个适配器上 struct device dev; // 内核设备模型(关联设备树) };④ struct i2c_driver(I2C设备驱动)
struct i2c_driver { int (*probe)(struct i2c_client *client); // 匹配成功调用 void (*remove)(struct i2c_client *client); const struct i2c_device_id *id_table; // 老方式匹配表 const struct of_device_id *of_match_table; // 设备树匹配表(常用) };⑤ 设备树匹配
// 设备树: i2c0 { icm20602: icm20602@68 { compatible = "invensense,icm20602"; // ← 用于of_match_table reg = <0x68>; // ← i2c地址 }; }; // 驱动: static const struct of_device_id icm20602_of_match[] = { { .compatible = "invensense,icm20602" }, {} }; MODULE_DEVICE_TABLE(of, icm20602_of_match); // 绑定流程: // 内核解析DTB → 发现i2c0上的设备@0x68 → 创建i2c_client // → 将 compatible 与驱动 of_match_table 比较 // → 匹配成功 → 调用 probe()1.3 I2C 读写操作详解
// 写操作(寄存器地址+数据) static int icm20602_write_reg(struct i2c_client *client, u8 reg, u8 value) { u8 buf[2] = {reg, value}; struct i2c_msg msg = { .addr = client->addr, .flags = 0, // 写 .buf = buf, .len = 2, }; int ret = i2c_transfer(client->adapter, &msg, 1); return (ret == 1) ? 0 : -EIO; } // 辅助API(常用封装,省去手动组装msg) i2c_smbus_write_byte_data(client, reg, val); // 写1字节 i2c_smbus_read_byte_data(client, reg); // 读1字节 i2c_master_recv(client, buf, len); // 连续读 len 字节 i2c_master_send(client, buf, len); // 连续写 len 字节注意:i2c_smbus_*是SMBus简化协议,很多设备支持;但有些严格的设备(如部分sensor)需要标准i2c_transfer。
1.4 陀螺仪 I2C 无中断 vs 有中断(重点)
① 无中断版(轮询方式)
工作原理: 应用/线程循环调用读函数 → 每读一次,I2C控制器发起一次完整事务 代码模式: while (1) { icm20602_read_all(client, &data); // 同步阻塞读取 process(data); usleep(2000); // 按需频率轮询(如500Hz → 2ms) } 特点: ✅ 实现简单;稳定(无中断丢失) ❌ 浪费CPU;数据时序不确定(可能读到旧值) ❌ 6轴不一致(读一半sensor更新) ❌ 总线频繁占用 适用:低频采样、实时性要求不高② 有中断版(DRDY中断触发)
工作原理: ICM-20602的INT/DRDY引脚,每有新数据 → 引脚脉冲(下降沿) → 接GPIO → 触发中断 → 中断中发起I2C传输读取「最新数据」 → 保证每次读都是最新且6轴时序一致 实现方式两种: 方案A:在ISR(上半部)中直接I2C传输 —— ❌ 不推荐,I2C可能睡眠 方案B:中断标记 + threaded irq / workqueue —— ✅ 推荐③ 有中断的标准实现(threaded irq)
// probe中申请中断: // 设备树:interrupt-parent = <&gpio2>; interrupts = <3 IRQ_TYPE_EDGE_FALLING>; static int icm20602_probe(struct i2c_client *client) { ret = devm_request_threaded_irq( &client->dev, client->irq, // I2C client自动从设备树获取IRQ NULL, // 上半部:NULL = 全交给线程 icm20602_irq_thread, // 下半部:线程化中断处理 IRQF_TRIGGER_FALLING, // 下降沿触发 "icm20602", data); } // 线程化中断处理(可以sleep、可以I2C传输): static irqreturn_t icm20602_irq_thread(int irq, void *dev_id) { struct icm20602_data *data = dev_id; icm20602_read_all(data->client, &data->raw); // 可以在进程上下文做I2C // 处理:更新共享数据 → 唤醒应用 return IRQ_HANDLED; }④ 有中断 vs 无中断 对比总结(面试直接背诵)
| 维度 | 无中断(轮询) | 有中断(DRDY) |
|---|---|---|
| CPU占用 | 高 | 低 |
| 数据实时性 | 可能读旧值 | 保证最新 |
| 6轴一致性 | 可能不一致 | 数据就绪后读,一致 |
| 功耗 | 高 | 低 |
| 实现复杂度 | 低 | 中 |
| 适用场景 | 低频<10Hz | 高频采样(防抖需400Hz+) |
面试加分话术:
> 防抖需要高频(400Hz左右)且实时性强的采样,所以用中断方案。ICM-20602有DRDY引脚,每有新数据触发一次中断。我在中断中使用threaded irq(线程化中断),因为I2C传输可能睡眠,不能放在原子上下文(普通ISR)里。这样既保证数据最新,又不阻塞其他系统任务。
1.5 I2C 常见问题与排查(必背)
常见问题1:I2C读写失败,返回 -EIO / -EREMOTEIO
排查: ① 地址:设备树reg与i2cdetect是否一致?7bit vs 8bit(左移一位是常见坑) ② 总线号:设备树挂的i2c0,驱动client->adapter是否为i2c-0? ③ 上电/复位:读WHO_AM_I失败→设备没上电/复位被拉低/电源时序错 ④ 速率:降到100kHz排除速率问题常见问题2:I2C总线锁死(SDA被拉低)
原因:从设备死机、传输被中断干扰导致状态机错乱 处理: ① 软件恢复:i2c_recover_bus() ② 硬件:断开设备电源,重新上电复位 ③ 优化:I2C传输中避免被高优先级中断打断(用irqsave锁)常见问题3:I2C多设备共享总线
- 地址必须唯一(每个设备地址不能相同) - 同一总线多设备访问必须串行(i2c-core内部有mutex锁) - 速率取所有设备支持的最小值 面试常问:为什么不会冲突? → i2c-core 中 i2c_adapter 有锁(mutex),每次 i2c_transfer 都加锁, 保证同一时刻只有一个设备在访问总线。第二部分:SPI 子系统彻底拆解
2.1 SPI 物理层与协议(对比I2C)
SPI 四线: ┌─────────┐ ┌─────────┐ │ Master │── SCK (时钟) ──────────│ Slave │ │ (SoC) │── MOSI (主→从) ────────│ 设备 │ │ │── MISO (从→主) ────────│ │ │ │── CS# (片选,低有效) ──│ │ └─────────┘ └─────────┘ 特点(对比I2C): 全双工(MISO/MOSI同时) 无应答(Slave不回ACK) 片选单独控制 → 多设备需多CS 无地址概念 → 物理连接决定 最高可达几十MHz2.2 SPI 分层架构(与I2C对应)
┌─────────────────────────────────────────────┐ │ 应用层:/dev/spidevX.Y │ ├─────────────────────────────────────────────┤ │ SPI核心层(drivers/spi/spi.c) │ │ - 管理SPI主机/设备 │ │ - 提供 spi_sync / spi_async │ │ - 消息队列调度 spi_message │ ├─────────────────────────────────────────────┤ │ SPI控制器层(drivers/spi/spi-hisi-sfc.c) │ │ - 实现 struct spi_master │ │ - 硬件传输(可能带DMA) │ ├─────────────────────────────────────────────┤ │ SPI设备驱动层(你的驱动) │ │ - struct spi_driver │ │ - 通过 spi_transfer 描述传输 │ └─────────────────────────────────────────────┘2.3 SPI 关键结构体
① struct spi_master(控制器)
struct spi_master { struct device dev; s16 bus_num; // 总线编号 u32 mode_bits; // 支持的SPI_MODE_0~3 u32 min_speed_hz; u32 max_speed_hz; int (*transfer)(struct spi_device *spi, struct spi_message *mesg); // 核心 };② struct spi_device(从设备)
struct spi_device { struct spi_master *master; u32 max_speed_hz; // 速率(设备树指定) u8 chip_select; // CS片选 u8 mode; // SPI模式(CPOL/CPHA) u8 bits_per_word;