资讯动态

# 嵌入式Linux驱动工程师面试 — **驱动总线子系统深挖面经(I2C / SPI / SDIO / 中断专题)

发布时间:2026/8/20 23:12:09 来源:尧图企业网站定制
第一部分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-* 访问。我的驱动工作在设备驱动层通过核心层的 APIi2c_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_TEN10位地址等 __u16 len; // 数据长度 __u8 *buf; // 数据缓冲区 };读操作写法例读WHO_AM_I地址0x75struct 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 reg; 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_clientI2C从设备实例struct i2c_client { unsigned short addr; // 设备地址7bit char name[I2C_NAME_SIZE]; // 设备名 struct i2c_adapter *adapter; // 它挂在哪个适配器上 struct device dev; // 内核设备模型关联设备树 };④ struct i2c_driverI2C设备驱动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: icm2060268 { 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 无中断 对比总结面试直接背诵维度无中断轮询有中断DRDYCPU占用高低数据实时性可能读旧值保证最新6轴一致性可能不一致数据就绪后读一致功耗高低实现复杂度低中适用场景低频10Hz高频采样防抖需400Hz面试加分话术 防抖需要高频400Hz左右且实时性强的采样所以用中断方案。ICM-20602有DRDY引脚每有新数据触发一次中断。我在中断中使用threaded irq线程化中断因为I2C传输可能睡眠不能放在原子上下文普通ISR里。这样既保证数据最新又不阻塞其他系统任务。1.5 I2C 常见问题与排查必背常见问题1I2C读写失败返回 -EIO / -EREMOTEIO排查 ① 地址设备树reg与i2cdetect是否一致7bit vs 8bit左移一位是常见坑 ② 总线号设备树挂的i2c0驱动client-adapter是否为i2c-0 ③ 上电/复位读WHO_AM_I失败→设备没上电/复位被拉低/电源时序错 ④ 速率降到100kHz排除速率问题常见问题2I2C总线锁死SDA被拉低原因从设备死机、传输被中断干扰导致状态机错乱 处理 ① 软件恢复i2c_recover_bus() ② 硬件断开设备电源重新上电复位 ③ 优化I2C传输中避免被高优先级中断打断用irqsave锁常见问题3I2C多设备共享总线- 地址必须唯一每个设备地址不能相同 - 同一总线多设备访问必须串行i2c-core内部有mutex锁 - 速率取所有设备支持的最小值 面试常问为什么不会冲突 → i2c-core 中 i2c_adapter 有锁mutex每次 i2c_transfer 都加锁 保证同一时刻只有一个设备在访问总线。第二部分SPI 子系统彻底拆解2.1 SPI 物理层与协议对比I2CSPI 四线 ┌─────────┐ ┌─────────┐ │ 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;

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价