资讯动态

IIC驱动从入门到量产稳定:Linux框架、裸机状态机与总线恢复实战

发布时间:2026/10/27 22:02:26 来源:尧图企业网站定制
做过嵌入式的人大多碰过这种场面板子焊回来上电屏幕不亮、传感器读不到数第一反应是芯片坏了万用表一量电源正常逻辑分析仪一挂发现 SDA 被拉死在低电平再回头看代码——IIC 驱动里那个等 ACK 的 while 循环一直转不出来。IIC很多人习惯写成 I2C本质是同一条两线总线驱动算是嵌入式里最入门的一类驱动两根线、几个时序、读几个寄存器看起来比 USB、PCIe 简单太多。可真要在量产产品上把它写稳坑一点不比复杂总线少从地址搞错、上拉选错、时钟延展不兼容到多设备共总线互锁、休眠唤醒后总线假死每一类都能让人熬夜。这篇内容把 IIC 驱动从物理电平和时序讲起一路捋到 Linux 下的三层驱动框架、设备树写法、裸机状态机实现、软件模拟总线的精度问题最后落到调试排错和长期稳定性上。刚接触驱动的新人可以把前面几节当入门地图从裸机转 Linux 的朋友重点看第三章和第四章被总线莫名锁死折磨过的老手建议直接翻到第六、七章。1. IIC 驱动到底在驱什么先把三层概念分清楚1.1 两根线背后的完整链路很多人写 IIC 驱动写不顺根子不在代码而在概念没分层。一条 IIC 总线上其实叠了三层东西最底下是控制器芯片内部那个 IIC 外设管时序生成、时钟分频、中断、DMA中间是总线SCL/SDA 两根线加上拉电阻以及挂在上面的所有从设备最上面才是设备驱动针对某颗 EEPROM、某颗温湿度传感器、某颗 IO 扩展芯片写的业务逻辑。裸机时代这三层通常混在一起写初始化寄存器、发起始条件、发地址、等 ACK、读数据全部堆在一个函数里。这样能跑但一旦总线上挂了五六颗不同芯片代码就会变成一坨互相纠缠的状态机改一处惊一片。Linux 把这三层拆得很干净控制器驱动注册i2c_adapter负责怎么把字节变电信号i2c-core负责总线管理和消息调度设备驱动注册i2c_driver只关心给我地址我读寄存器。分层之后同一份设备驱动可以跑在不同厂商的控制器上换主控几乎不用改业务代码这就是分层的价值。我在带新人时经常用一个类比控制器是快递小哥负责把包裹按规矩送到门口总线是小区道路宽度、限速、有没有堵车都在这层设备驱动是下单的人只管说清收件地址和要寄什么。你下单的人天天去研究小哥骑什么车、路上几个红绿灯效率不会高还容易把责任搞混——很多驱动写不通的锅其实最后查出来是上拉电阻或者地址焊错。1.2 为什么 IIC 驱动特别娇气IIC 用的是一主多从、开漏输出加外部上拉的电气结构。开漏意味着任何设备只能把线拉低不能主动推高高电平完全靠上拉电阻把线拽上去。这个设计带来两个后果一是总线上任意一台设备拉低整条线就是低谁也别想通信二是高电平的上升沿是 RC 充电过程上升时间跟总线电容和上拉阻值直接相关。所以 IIC 的娇气是物理层面的总线电容大了、上拉阻值选大了波形上升沿变缓从机就可能把高电平误判成低电平读出来全是 0xFF上拉阻值选小了灌电流变大有些低功耗芯片直接拉不到标准的低电平门限通信时好时坏。这些问题在实验室桌面上通常不会暴露因为线短、设备少、电容小一上整机、线束拉长、挂满十几个从设备问题立刻冒出来。我做过一个项目样机阶段一切正常转量产换了更长的柔性排线之后同一条 IIC 上两颗传感器的读取失败率从 0 涨到 3%最后就是总线电容从 80pF 涨到 300pF上拉没改。1.3 硬件 IIC 还是软件模拟三种实现怎么选实现方式优点代价适合场景控制器硬件 IIC时序精准、CPU 占用低、支持中断与 DMA引脚固定、灵活性差、部分控制器有硬件缺陷主控有可用 IIC 外设、速率 400kHzGPIO 软件模拟引脚随便挑、时序可调、易调试占用 CPU、高速率下时序抖动引脚冲突、需要非标准时序、低速从设备半硬件硬件起停 软件数据折中方案依赖具体控制器能力特定平台迁移选型的判断依据其实就三条速率要求、引脚资源、CPU 负载。跑 100kHz 读几颗传感器软件模拟完全够用还能省一个外设给更吃紧的接口跑 400kHz 以上、或者需要连续大批量读数据比如某些 6 轴 IMU 的 FIFO 突发读硬件控制器加 DMA 才是正解。还有一种情况必须用软件模拟从设备要求非标准的时序比如某些老式芯片要求停止条件后保持一段时间的总线空闲硬件控制器改不了只能用 GPIO 手动打波形。2. 写驱动之前必须记住的协议细节2.1 起始、停止、应答三件事决定成败IIC 的通信单元不是帧而是条件 字节 应答。起始条件START是 SCL 高电平期间 SDA 由高变低停止条件STOP是 SCL 高电平期间 SDA 由低变高这两个条件必须由主机产生而且电平变化的时间窗口有硬性要求——标准模式 100kHz 下起始条件的建立时间不小于 4.7μs保持时间不小于 4.0μs快速模式 400kHz 下这两个值分别缩到 0.6μs。每个字节 8 位高位在前第 9 个时钟由接收方拉低 SDA 表示应答。这里最容易踩的坑是应答的方向搞反主机写数据时从机拉低 SDA 应答主机读数据时是主机在最后一个字节回 NACK 告诉从机别再发了。我见过不少手写模拟代码读最后一个字节时忘了发 NACK从机继续往总线上推数据下一轮通信直接错位。注意很多控制器在读最后一个字节时要求软件显式设置I2C_M_NOSTART或采用i2c_smbus_read_i2c_block_data这类封装具体看控制器手册。别假设控制器会自动帮你发 NACK。2.2 七位地址、读写位、寄存器地址三层寻址别混第一次写 IIC 驱动最容易晕的就是地址。从设备有一个 7 位地址比如某颗 EEPROM 的地址引脚全接地时是 0x50。这个 7 位地址在总线上传输时要左移一位最低位放读写标志写是 0读是 1。所以总线上实际出现的字节是 0xA0写和 0xA1读。坑在于不同工具和不同 API 对地址的定义不一致Linux 的i2c_client.addr和i2c-tools用的是 7 位地址而不少裸机例程、某些厂商的 SDK 直接用 8 位地址已经左移过的。你要是把 0xA0 填进设备树的reg内核会按 7 位地址解释成 0x20然后一直等不到应答。这类问题排查起来特别费劲因为地址看着没错。还要再叠加一层寄存器地址。传感器类的从设备内部通常有一张寄存器表通信格式是写寄存器地址1 或 2 字节 读/写数据。真正的通信结构是START → 从机地址W → 寄存器地址 → 重复 START → 从机地址R → 数据 → NACK → STOP。四段拼在一起任何一段的字节序或长度错了读回来就是 0xFF 或 0x00。2.3 时钟频率与上拉电阻算一遍比试十次快上拉电阻的取值不是凭感觉有明确的计算区间。上限由上升时间决定Rp(max) tr / (0.8473 × Cb)下限由低电平灌电流决定Rp(min) (VDD − VOL) / IOL举个例子3.3V 系统总线电容估 200pF长排线 8 个器件这个数一点不夸张快速模式上升时间上限 300ns。代入得 Rp(max) 300ns / (0.8473 × 200pF) ≈ 1.77kΩ。再算下限VOL 取 0.4VIOL 取 3mARp(min) (3.3 − 0.4) / 3mA ≈ 967Ω。所以合理区间是 0.97k ~ 1.77k取 1.5k 比较稳妥。换成短排线、电容只有 80pFRp(max) 就变成 4.4kΩ这时候用 4.7k 也勉强能跑但余量不大。总线电容模式上升时间上限Rp 上限计算值推荐取值80pF100kHz 标准1000ns14.7kΩ4.7kΩ80pF400kHz 快速300ns4.4kΩ3.3kΩ200pF400kHz 快速300ns1.77kΩ1.5kΩ400pF400kHz 快速300ns0.88kΩ已超出规范需减小电容或降速上表最后一行值得特别说400pF 是 IIC 规范给出的总线电容上限超过这个值就不该硬撑应该降速到 100kHz或者加 IIC 缓冲/中继器件把总线分段。硬顶的后果是通信大部分时候正常偶尔丢一帧这种偶发问题在量产里最难定位。2.4 时钟延展与重复起始两个常被忽略的兼容性问题时钟延展clock stretching是从设备在还没准备好数据时把 SCL 拉低强制主机等待。协议是允许的但很多控制器的硬件实现不支持或者支持得不好——一旦从机拉低 SCL 超过控制器内部超时控制器就报总线错误。某些温湿度传感器、某些电量计在转换期间就会延展时钟。遇到这类从机要么换支持延展的控制器要么改用 GPIO 模拟模拟代码里必须真的去读 SCL 电平再决定是否继续拉高。重复起始repeated START是在不发 STOP 的情况下重新发起始条件用于写地址 → 读数据的原子操作。有些从设备在 STOP 之后会释放内部状态机如果中间插了 STOP第二次通信就要重新配置读回来的数据是错的。写驱动时看到i2c_transfer里传两条 msg 且没有中间 STOP就是这个道理——内核的i2c_transfer在两条消息之间默认发重复起始除非你显式设置标志位。3. Linux 下 IIC 驱动的三层框架怎么配合3.1 控制器驱动与设备驱动的分工Linux 的 IIC 子系统抽象得非常干净。控制器驱动负责实现struct i2c_algorithm里的master_xfer传一组消息和functionality报告支持的能力比如是否支持 SMBus 块读、是否支持 10 位地址然后调用i2c_add_adapter把适配器注册进内核。设备驱动则是实现struct i2c_driver的probe、remove以及of_match_table/id_table然后module_i2c_driver注册。匹配过程是这样的内核解析设备树里的compatible字符串和驱动里的of_match_table对比匹配成功就调用probe并把一个已经初始化好的struct i2c_client *传进来。这个 client 里已经填好了addr7 位地址和adapter指针驱动完全不需要自己去碰控制器寄存器。这也是为什么同一个传感器驱动能同时跑在几家不同主控上的原因。3.2 设备树节点怎么写才不出错i2c1 { status okay; clock-frequency 400000; pinctrl-names default; pinctrl-0 i2c1_pins; eeprom50 { compatible generic,i2c-eeprom; reg 0x50; pagesize 32; }; sensor48 { compatible generic,i2c-sensor; reg 0x48; interrupt-parent gpio1; interrupts 12 2; vdd-supply vcc_3v3; reset-gpios gpio1 13 1; poll-interval-ms 100; }; };几个必须检查的点reg填 7 位地址不是左移过的 8 位clock-frequency写在控制器节点上写进从设备节点是无效的很多人在这里栽过中断触发类型要和芯片手册一致边沿触发和电平触发搞错表现为中断风暴或者一次中断都进不来reset-gpios这类复位脚一定要接上很多传感器上电后处于复位态不主动拉一次就永远不应答。还有一个易漏项上电时序。有些芯片要求 VDD 稳定后延迟 10ms 以上才能访问或者要求某个使能脚先拉高再等 5ms。设备树里描述不了这种时序只能在probe里用msleep加regulator的enable顺序处理。我个人的习惯是把上电、复位、等待、验证 ID 这四步封装成一个power_on()函数probe里失败就返回-ENODEV同时打印实测到的 ID 值——这个打印在调试期救命能一秒区分没焊好和驱动写错。3.3 先别写代码用 i2c-tools 把硬件验证一遍这一步能省掉大量时间。板子起来之后先确认适配器编号再扫地址。i2cdetect -l # 列出所有适配器 i2cdetect -y 1 # 扫描 i2c-1 总线上的设备输出里地址位置显示UU表示该地址已被内核驱动占用显示具体数值表示探测到了设备但没驱动接管。如果整行全是--不用怀疑代码问题一定在硬件或引脚复用引脚复用没配对、上拉没焊、器件没供电、地址引脚接错这四种占了绝大多数。再往下验证寄存器读写i2cget -y 1 0x50 0x00 # 读 1 字节 i2cset -y 1 0x50 0x00 0x5A # 写 1 字节 i2cdump -y 1 0x50 # 整页 dump看 ID 寄存器i2cdump特别好用很多传感器的 ID 寄存器在固定偏移上dump 出来一眼就能确认型号对不对、通信通不通。这一步通了再写驱动心里就有底了。3.4 寄存器访问封装裸 i2c_transfer 还是 regmap内核提供两种常见写法。一种是直接用i2c_transfer拼消息static int sensor_read_reg(struct i2c_client *cli, u8 reg, u8 *val) { int ret; struct i2c_msg msgs[2] { { .addr cli-addr, .flags 0, .len 1, .buf reg }, { .addr cli-addr, .flags I2C_M_RD, .len 1, .buf val }, }; ret i2c_transfer(cli-adapter, msgs, 2); return (ret 2) ? 0 : (ret 0 ? ret : -EIO); }另一种是用regmapstatic const struct regmap_config sensor_regmap_cfg { .reg_bits 8, .val_bits 8, .max_register 0x7F, .cache_type REGCACHE_RBTREE, }; priv-map devm_regmap_init_i2c(cli, sensor_regmap_cfg);我现在的默认选择是regmap理由有三个自带寄存器缓存读多写少的场景能少发很多总线事务统一的调试接口/sys/kernel/debug/regmap/下能直接看寄存器快照换总线SPI 换 IIC时只改初始化那一行。代价是多一层间接调用单次访问开销略大对时序敏感的场合比如要求两次读之间必须间隔固定时间要小心缓存导致的看起来没访问。4. 从零写一个 IIC 设备驱动完整实操流程4.1 驱动骨架与 probe 的关键顺序#include linux/module.h #include linux/i2c.h #include linux/regmap.h #include linux/gpio/consumer.h #include linux/regulator/consumer.h #include linux/delay.h struct sensor_priv { struct i2c_client *client; struct regmap *map; struct gpio_desc *reset_gpio; struct regulator *vdd; struct mutex lock; }; static int sensor_power_on(struct sensor_priv *priv) { int ret, id; ret regulator_enable(priv-vdd); if (ret) return ret; if (priv-reset_gpio) { gpiod_set_value_cansleep(priv-reset_gpio, 0); msleep(5); gpiod_set_value_cansleep(priv-reset_gpio, 1); msleep(20); } regmap_read(priv-map, 0x0F, id); dev_info(priv-client-dev, chip id 0x%02X\n, id); return 0; } static int sensor_probe(struct i2c_client *cli) { struct sensor_priv *priv; priv devm_kzalloc(cli-dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; priv-client cli; mutex_init(priv-lock); priv-vdd devm_regulator_get(cli-dev, vdd); if (IS_ERR(priv-vdd)) return PTR_ERR(priv-vdd); priv-reset_gpio devm_gpiod_get_optional(cli-dev, reset, GPIOD_OUT_LOW); priv-map devm_regmap_init_i2c(cli, sensor_regmap_cfg); if (IS_ERR(priv-map)) return PTR_ERR(priv-map); sensor_power_on(priv); i2c_set_clientdata(cli, priv); return 0; }顺序上有个原则先电源、再复位、后通信。看起来是废话但我见过太多代码把regmap_init放在regulator_enable之前就去读寄存器读回来自然是 0xFF然后一脸困惑。另一个细节是错误路径devm_系列能自动回收但regulator_enable这种显式调用的必须在remove里配对的disable否则休眠时漏电待机功耗直接翻倍。4.2 读写接口与并发保护驱动对外通常暴露成sysfs属性、hwmon、iio或者字符设备。不管哪种底层都得加锁。IIC 总线是共享资源两个线程同时调用i2c_transfer会导致消息交错轻则数据错乱重则总线锁死。加锁的粒度要覆盖整组消息而不是单个字节因为多字节寄存器比如 16 位 ADC 值的高低位必须保证来自同一次转换。static int sensor_read_value(struct sensor_priv *priv, int *out) { unsigned int lo, hi; int ret; mutex_lock(priv-lock); ret regmap_read(priv-map, 0x10, lo); if (!ret) ret regmap_read(priv-map, 0x11, hi); mutex_unlock(priv-lock); if (ret) return ret; *out ((hi 8) | lo); return 0; }regmap内部也有自己的锁但那是保护单次访问的跨寄存器的一致性还得自己管。这一点在中断触发读取时尤其要注意中断上下文不能睡眠所以要么用regmap_read的原子版本配合工作队列要么在threaded_irq里做实际读取。我的习惯是request_threaded_irq硬中断里只做regmap_write清状态位和disable_irq_nosync实际读数据丢给线程处理函数既保证实时性又避免睡眠问题。4.3 中断还是轮询选之前先看数据率数据率低于 20Hz 的场景轮询完全够用代码简单还少一个中断线的硬件调试环节。数据率高、或者对延迟敏感比如运动检测、过阈值告警才需要中断。用中断要确认三件事中断触发类型与芯片手册一致、中断状态位必须显式清除否则电平触发的中断会一直挂住、中断引脚上有时需要外部上拉。还有一种情况是中断 轮询混用平时靠中断唤醒唤醒后连续轮询读若干帧确认有效数据。某些 IMU 的 FIFO 水位中断就是这样用的。5. 裸机与 RTOS 下的 IIC 驱动实现5.1 状态机写法比阻塞写法更稳裸机里最常见的写法是阻塞式拉起始、发字节、死等 ACK、超时返回 0。这种写法在小系统里没问题但一旦总线上有设备偶尔不响应整个主循环就被卡住看门狗直接复位。更稳的做法是把一次传输拆成状态机每个状态在一次调用里向前推进一步主循环里周期性调用。typedef enum { IIC_ST_IDLE, IIC_ST_START, IIC_ST_ADDR, IIC_ST_REG, IIC_ST_RESTART, IIC_ST_DATA, IIC_ST_STOP, IIC_ST_ERROR, } iic_state_t; typedef struct { iic_state_t state; uint8_t addr; uint8_t *buf; uint16_t len; uint16_t idx; uint32_t tick; int result; } iic_xfer_t; int iic_poll(iic_xfer_t *x) { switch (x-state) { case IIC_ST_START: iic_gen_start(); x-state IIC_ST_ADDR; x-tick tick_now(); break; case IIC_ST_ADDR: if (iic_send_byte(x-addr)) { x-state IIC_ST_REG; } else if (tick_now() - x-tick TIMEOUT_MS) { x-state IIC_ST_ERROR; x-result -1; } break; /* ... 后续状态同理 ... */ default: break; } return x-result; }状态机的好处是把等待变成了稍后再来超时逻辑也能统一在一处处理。代价是代码变长可读性依赖状态命名清晰。我一般会配一张状态迁移表写在注释里后来接手的人看得懂。5.2 总线死锁九脉冲恢复是必修课从机在传输中途被复位比如电源抖动、看门狗复位了从机而主机没复位它可能正处于正在输出 0的状态把 SDA 一直拉低不放。主机这时发起始条件没用因为总线已经被占死。标准恢复流程是主机把 SCL 当作普通 GPIO手动打最多 9 个时钟脉冲让从机把剩余的位数走完松开 SDA然后发一个 STOP 条件复位总线状态机。void iic_bus_recover(void) { gpio_set_output(SCL); gpio_set_input(SDA); for (int i 0; i 9; i) { gpio_write(SCL, 0); delay_us(5); gpio_write(SCL, 1); delay_us(5); if (gpio_read(SDA)) break; } /* 补一个 STOPSCL 低 - SDA 低 - SCL 高 - SDA 高 */ gpio_write(SCL, 0); gpio_write(SDA, 0); delay_us(5); gpio_write(SCL, 1); delay_us(5); gpio_write(SDA, 1); delay_us(5); }这段代码建议直接放进项目模板里所有 IIC 初始化前先跑一遍。我在三个不同项目里都靠它救回了开机偶发不亮的现场事后定位都是从机复位时序和主机不同步。5.3 GPIO 模拟 IIC 的时序精度陷阱软件模拟的时序靠delay_us或循环空转实现问题在于编译器优化等级一变循环耗时全变。Debug 版本能通信Release 版本直接挂这种换个编译选项就坏的诡异现象八成是模拟时序的延时不准。两个缓解办法一是用硬件定时器做微秒延时别用空循环二是在起始/停止条件的电平切换之间留足余量标准模式要求 4.7μs实际跑到 6~8μs 更保险反正 100kHz 的位周期有 10μs余量够。还有人会关掉对应函数的编译优化__attribute__((optimize(O0)))这种做法我不推荐因为它把不确定性藏起来了移植到别的编译器上照样炸。6. 调试排错实录常见故障速查6.1 故障现象与原因对照表现象高概率原因排查动作扫描总线全--引脚复用未配置 / 器件未供电 / 上拉缺失量电压、查 pinctrl、量上拉阻值扫描到地址但读写全 0xFF设备未退出复位 / 上电延时不足加复位时序读 ID 寄存器确认偶尔丢帧重试即好总线电容过大 / 上拉偏大 / 干扰降速到 100kHz 验证缩短排线首字节正确后续全错重复起始缺失 / 字节序错 / NACK 位置错逻辑分析仪抓完整时序逐位比对运行几小时后总线卡死从机复位不同步 / 缺少超时保护加九脉冲恢复 传输超时休眠唤醒后不工作控制器掉电未重初始化 / 从机状态丢失唤醒回调里重新配置并重读 ID多设备时好时坏地址冲突 / 时钟延展不被支持逐个摘除设备定位查手册是否延展时钟6.2 逻辑分析仪怎么抓才有意义抓 IIC 波形别只看有没有波形要抓完整的传输单元。触发条件设成 SDA 下降沿起始条件采样率至少是时钟频率的 10 倍400kHz 总线用 10MS/s 以上采集长度覆盖一次完整的读写。看的时候重点看四个地方起始条件的建立/保持时间是否达标第 9 个时钟 SDA 是否被正确拉低ACK重复起始的位置对不对停止条件后总线是否释放。有些分析仪自带 IIC 协议解码能直接把地址、数据、ACK 列出来这时候对着从机手册比对字节序几秒钟就能看出问题。我习惯把解码结果和驱动里的i2c_transfer消息数组摆在一起看一眼就能发现驱动以为发了 2 字节总线上只有 1 字节这类错误。6.3 一个真实的排查记录某次调试一颗姿态传感器现象是能读到 WHO_AM_I 正确值但连续读数据寄存器时第一组数据正确第二组开始全是 0。查了两小时代码regmap配置没问题锁也加了。最后抓波形发现两次读之间没有 STOP 条件而从机要求每次读操作后必须收到 STOP 才更新内部数据指针。原因是i2c_transfer传了两条消息写寄存器地址 读数据内核默认在两条之间发重复起始但从机不支持——它要的是写地址、STOP、再起始、读。解决办法是拆成两次独立的i2c_transfer调用各自带完整起停/* 第一次写寄存器地址带 STOP */ struct i2c_msg m1 { .addr cli-addr, .flags 0, .len 1, .buf reg }; i2c_transfer(cli-adapter, m1, 1); /* 第二次独立读带自己的 START/STOP */ struct i2c_msg m2 { .addr cli-addr, .flags I2C_M_RD, .len len, .buf buf }; i2c_transfer(cli-adapter, m2, 1);代价是两次调用之间总线可能被别的设备抢占所以必须放在同一把锁里。这个坑教科书上不会写只能靠抓波形定位。后来我把从机是否支持重复起始写进了项目 checklist每接一颗新器件都先验证这一点。7. 从能跑到跑得稳稳定性设计要点7.1 超时是底线不是可选项所有 IIC 传输都必须带超时。硬件控制器一般有自己的超时寄存器配成几毫秒即可GPIO 模拟必须自己数循环。没有超时的 IIC 代码等于给产品埋了一颗定时炸弹——从机某次没响应主线程永远卡在等 ACK看门狗复位后由于从机状态未清又卡一次形成死循环。我给团队定的规范是任何 IIC 访问函数的返回值必须能被上层区分成成功、总线错误、设备无响应三类上层据此决定重试还是上报。重试策略也有讲究。单次失败立即重试一次是合理的连续三次失败就应该走总线恢复流程而不是无限重试。无限重试在总线上有短路时会变成 CPU 满载反而掩盖了真实故障。7.2 电源管理与休眠唤醒带电池的产品里IIC 相关的外设通常要参与低功耗管理。休眠时的关键是控制器时钟是否关闭、从设备是否断电、唤醒后从设备是否需要重新配置。很多从设备掉电后寄存器全部回到默认值包括量程、采样率、中断使能这些业务相关的配置唤醒后不重配就会出现数据看着有但量程不对的隐蔽故障。我的做法是把配置寄存器列表抽成一张表probe和resume都调用同一个sensor_apply_config()函数避免两处配置不一致。同时在suspend里把中断屏蔽掉否则休眠期间中断线抖动可能把系统反复唤醒待机电流直接从微安级飙到毫安级。7.3 多设备共总线的资源竞争一条总线上挂多颗器件时除了地址不能冲突还要注意带宽分配。400kHz 总线的理论吞吐是 40KB/s 左右扣掉地址、ACK、起停开销实际有效数据率大概只有一半。如果总线上有一颗高频采样传感器在持续占用总线其他设备的响应延迟会被拉长。这时候要么给高频设备单独分一条总线要么降低采样率、用 FIFO 批量读把总线占用率压下来。我一般在设计初期就算一笔账把每个设备的采样率、每次读的字节数、协议开销加起来估一个总线的理论占用率。超过 50% 就要考虑拆总线因为实际运行时还有重试、其他主控访问、中断处理延迟这些额外开销。7.4 几条写在 checklist 里的经验第一新板子第一次上电先不写驱动用i2cdetect扫一遍扫不到就去查硬件别在代码里找原因。第二每个新器件接入前先只接它一个验证通了再加第二个避免多变量同时变化。第三驱动里务必打印芯片 ID这是区分硬件问题和软件问题最快的信号。第四上拉电阻的取值写进原理图评审清单别留给后面调试时拍脑袋。第五所有 IIC 访问路径统一走一个封装层将来要加日志、加统计、加重试只改一处。我在实际项目里体会最深的一点是IIC 驱动写出来只要半天写稳要半年。真正决定成败的往往不是那几行传输代码而是上电时序、总线恢复、超时保护、休眠重配这些边角料。这些东西在功能测试阶段一个都暴露不出来只有跑长时间稳定性、做温度循环、做反复上下电测试的时候才一个个冒出来。所以我现在写 IIC 驱动的顺序是反过来的先写超时和恢复再写功能最后写业务逻辑。前面那两块地基没打好后面加多少功能都是在流沙上盖楼。

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

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

免费获取报价 →
↑