I2C 作为嵌入式里最常用的同步串行总线,前面我们掌握了基础时序、AT24C02 简单读写,本节课继续深入 I2C 底层读取逻辑、代码封装优化、跨页写处理,并且新增 LM75 温度传感器驱动与 ARM FPU 浮点单元相关知识点,打通 I2C 从底层时序到工程化驱动的完整链路。
一、I2C 总线底层原理与读取操作实现
1. 物理层核心:线与特性 + 开漏输出
- 线与特性:I2C 总线 SDA、SCL 具备线与特性,总线上任意一个设备拉低总线,总线就为低电平;只有所有设备全部释放总线变为高阻态,依靠外部上拉电阻,总线才会变成高电平。这也是 I2C 支持多设备挂载的基础。
- 开漏输出:I2C 的通信引脚必须配置成开漏模式,不能直接推挽输出。开漏模式输出高时引脚为高阻,配合外部上拉电阻供电,能够避免不同电压域芯片通信时,出现电流倒灌,防止芯片烧毁。
2. I2C 标准通信时序规范
- 起始 / 停止信号:SCL 为高电平期间,SDA 由高变低为 Start 起始信号;SCL 高电平,SDA 由低变高为 Stop 停止信号。
- 数据采样规则:SCL 低电平阶段,才允许修改 SDA 电平;SCL 高电平阶段,SDA 电平必须保持稳定,主机采样数据。
- ACK/NACK 应答:每传输完 8bit 数据,跟随 1bit 应答位。ACK(低电平)代表接收正常,继续传输;NACK(高电平)代表传输结束。
3. I2C 读操作核心难点:重复起始、伪读
- 重复起始(Restart):读取从机寄存器数据时,先发寄存器地址,之后不发送停止信号,发送 Restart 信号,重新发送带读标志位的从机地址,切换传输方向。
- 伪读:读取寄存器时第一次读操作是伪读,作用是触发硬件产生时钟脉冲,启动后续真实数据接收。
- 时钟由主机完全控制,主机输出 SCL 时钟脉冲,从机跟随时钟输出数据。
4. 多字节读取应答处理陷阱
读取多字节数据时,要在接收倒数第二个字节之前提前配置 NACK,保证收到最后一字节后回复 NACK,终止通信。
同时注意时序风险:读完最后一字节立刻发送 Stop,有可能 SCL 还没有回到高电平,造成停止信号识别失败,总线异常,最好借助逻辑分析仪抓取波形调试验证。
二、I2C 驱动代码调试、Bug 修复与模块化封装
1. 接收逻辑经典 Bug 与修复
- 故障现象:读取多字节时,最后一组数据无法存入缓冲区。根源是提前设置 NACK,还没读取数据就判定结束,丢失最后字节。
- 修复思路:先读取数据存入缓冲区,再判断是否发送 NACK;
- 总线保护:读取最后一字节前,切换回发送模式,避免多余时钟脉冲,防止总线死锁。
2. 寄存器地址兼容性优化
原生代码只支持单字节寄存器地址,无法适配地址超过 255 的器件,如 AT24C08。
优化方案:将寄存器地址变量改为uint16_t,增加地址长度参数,高位先发,同时兼容 1 字节、2 字节寄存器地址的外设。
3. 遵循开闭原则,封装统一 I2C 传输接口
- 定义
IIC_Message结构体,整合从机地址、寄存器地址、数据缓冲区、读写方向等参数,简化函数传参。 - 对外封装
IIC_Transfer统一接口,根据消息结构体里的读写标志,自动调用底层读 / 写函数,底层驱动和上层应用代码分离,符合 OCP 开闭原则,后续新增外设无需大量修改底层 I2C 代码。
4. AT24C02 页写限制处理
AT24C02 每页仅 8 字节,跨页直接写入会发生数据回绕覆盖。
处理逻辑放在 AT24C02 应用驱动中,循环判断地址是否跨页(地址对 8 取余),分页分次调用底层 I2C 写入接口。
三、I2C 跨页写入、LM75 温度传感器与 ARM FPU 浮点单元
1. I2C 页写入完整逻辑
大批量写数据时,循环判断地址是否跨页面边界:
- 未跨页:继续写入;
- 即将跨页:写完当前页面剩余数据,增加延时(如 5ms)。
重点:EEPROM 内部烧录需要时间,底层通用 I2C 接口不会自带等待,必须在器件驱动层手动增加写入等待延时,等待存储完成再发起下一次 I2C 通信。
循环结束后,检查是否还有剩余数据,单独发送收尾数据。
2. NACK 应答时序坑点复盘
多字节读取,倒数第二个字节接收完成后,主机发送 NACK。
如果模式切换时机错误,I2C 收发模式没有提前切换,会造成 SCL 电平异常,出现偶发读取失败。建议在读取最后一个字节前提前切换工作模式,避免多余时钟脉冲干扰。
3. LM75 温度传感器驱动开发
LM75 是 I2C 接口数字温度传感器,供电范围 2.7~5.5V。
- 硬件地址:A0/A1/A2 引脚全部接地时,7 位从机地址为
1001000,读写位追加在最低位。 - 数据解析:温度原始数据占 16bit,高字节优先。将高字节左移 8 位,和低字节合并得到原始数值;LM75 最低位 LSB = 0.5℃,实际温度 = 原始数值 / 2。
- 注意:Cortex‑A7 裸机默认没有开启 FPU,直接浮点运算会触发未定义指令异常,程序卡死。
4. ARM 内核 FPU 浮点运算单元启用
- 异常原因:代码直接写浮点运算,但 FPU 未开启,内核识别不了浮点指令,触发未定义指令异常。
- 开启方式:在启动汇编代码里,操作 CP10、CP11 协处理器,配置 FPEXC 寄存器开启 FPU,放在系统初始化阶段。
- printf 打印浮点数兼容方案:部分裸机环境下
printf不支持%f。可以将浮点数放大 1000 倍转为整数,拆分整数、小数部分,再分别打印。
四、学习总结
I2C 开发不只是看懂时序波形,更要关注工程上的各种边界场景:跨页写、NACK 应答时机、总线死锁、多字节地址兼容。
驱动分层封装是嵌入式工程规范:底层 I2C 只负责总线收发;AT24C02、LM75 这类外设单独写应用驱动。
同时要留意 Cortex-A7 的 FPU,裸机开发做浮点计算必须手动开启,否则程序直接崩溃