尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

从寄存器到总线:一个嵌入式新手的 I2C 调试手记

发布时间:2026/9/24 11:08:39

资讯中心
01
ARTICLE

从寄存器到总线:一个嵌入式新手的 I2C 调试手记

从寄存器到总线:一个嵌入式新手的 I2C 调试手记
一、先搞懂物理层为什么 I2C 只有两根线1. 线与特性I2C 总线上可以挂多个设备但 SDA 和 SCL 两根线是所有设备共享的。如果像普通 GPIO 那样推挽输出一个设备输出高、另一个输出低就会直接短路烧毁。线与就是解决这个问题的只要有一个设备输出低电平总线就是低电平只有所有设备都释放总线高阻态上拉电阻才把总线拉高。逻辑上等价于与运算。2. 开漏 vs 推挽推挽开漏结构P-MOS N-MOS只有 N-MOS高电平主动推高靠上拉电阻能否并联❌ 会短路✅ 天然支持线与典型应用SPI、UARTI2C、1-Wire一句话推挽能推能拉但不能并联开漏只能拉不能推靠上拉补高电平天然支持多设备共享。这也是为什么 I2C 的 SDA/SCL 必须配置为开漏模式并且外部要接 4.7kΩ 上拉电阻。二、协议层I2C 时序拆解I2C 的核心原则就一句话SCL 低电平改变数据SCL 高电平采样数据。1. 起始/停止条件STARTSCL 高电平时SDA 下降沿高→低STOPSCL 高电平时SDA 上升沿低→高2. 数据传输每次传 8 位MSB 先发第 9 个时钟周期是应答位ACK 低电平NACK 高电平3. 完整读写流程写操作START → [地址W] → ACK → [寄存器地址] → ACK → [数据] → ACK → STOP读操作含重复起始START → [地址W] → ACK → [寄存器地址] → ACK → ReSTART → [地址R] → ACK → [数据] → NACK → STOP注意读操作中主机接收最后一个字节后必须发 NACK通知从机停止发送。三、实战i.MX6ULL 寄存器级 I2C 驱动理论懂了动手写驱动才是真考验。下面是我基于 i.MX6ULL 参考手册写的 I2C 写函数。1. 关键寄存器定义// I2CR - 控制寄存器 #define I2CR_IEN (1 7) // I2C 使能 #define I2CR_MSTA (1 5) // 主机模式写1产生START #define I2CR_MTX (1 4) // 发送模式 #define I2CR_TXAK (1 3) // 发送 ACK/NACK #define I2CR_RSTA (1 2) // 重复起始 // I2SR - 状态寄存器 #define I2SR_IBB (1 5) // 总线忙 #define I2SR_IIF (1 1) // 中断标志传输完成 #define I2SR_RXAK (1 0) // 接收到的应答2. 初始化函数void i2c_init(I2C_Type *base) { // 1. 引脚复用UART4_RX → SDA, UART4_TX → SCL IOMUXC_SetPinMux(IOMUXC_UART4_RX_DATA_I2C1_SDA, 1); IOMUXC_SetPinMux(IOMUXC_UART4_TX_DATA_I2C1_SCL, 1); // 2. 电气特性开漏 上拉 IOMUXC_SetPinConfig(IOMUXC_UART4_RX_DATA_I2C1_SDA, 0x10B0); IOMUXC_SetPinConfig(IOMUXC_UART4_TX_DATA_I2C1_SCL, 0x10B0); // 3. 配置分频IFDR0x15 对应约 100kHz base-I2CR ~I2CR_IEN; base-IFDR 0x15; base-I2CR | I2CR_IEN; }3. 等待传输完成带超时int wait_i2c_iif(I2C_Type *base) { unsigned int timeout 0; while ((base-I2SR I2SR_IIF) 0) { if (timeout 100000) return -1; // 超时保护 } base-I2SR | I2SR_IIF; // 写1清零 if (base-I2SR I2SR_RXAK) return -1; // NACK return 0; }4. 写函数void i2c_write(I2C_Type *base, u8 dev_addr, u8 reg_addr, u8 *data, int len) { // 清除标志位 base-I2SR | (I2SR_IAL | I2SR_IIF); // 设为发送模式 产生 START base-I2CR | I2CR_MTX | I2CR_MSTA; // 发送从机地址7位地址左移1位 写位0 base-I2DR (dev_addr 1) | 0; if (wait_i2c_iif(base)) goto stop; // 发送寄存器地址 base-I2DR reg_addr; if (wait_i2c_iif(base)) goto stop; // 发送数据 for (int i 0; i len; i) { base-I2DR data[i]; if (wait_i2c_iif(base)) goto stop; } stop: // 产生 STOP清除 MSTA base-I2CR ~I2CR_MSTA; delay_ms(5); // EEPROM 写周期延时 }四、踩过的坑坑1I2SR 是写1清零不是写0一开始我写base-I2SR ~I2SR_IIF;想清零标志位结果怎么都不生效。查手册才发现 i.MX6ULL 的 I2SR 是写1清零正确写法是base-I2SR | I2SR_IIF;。坑2缺少超时机制会死循环如果从机无应答或总线异常IIF永远不会置1程序直接卡死。必须加超时计数。坑3忘了使能时钟i.MX6ULL 的外设使用前必须通过 CCM 使能对应时钟否则所有寄存器操作都是无效的。坑4dev_addr 是7位还是8位代码里做了 1说明传入的是 7 位地址如 AT24C02 传0x50。如果传了 8 位地址0xA0就会出错。五、总结从物理层的开漏/线与到协议层的时序规则再到寄存器级的驱动编写I2C 这条链路走下来最大的感受是底层开发没有差不多每一个 bit 的含义、每一个寄存器的读写方式都必须精确对应手册。接下来打算把 I2C 读操作也实现一下读操作涉及重复起始和 TXAK 切换比写操作更复杂。另外 81 时钟恢复机制也值得深入实践一下。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。