提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 前言
- 一、硬件底层:为什么I2C必须用开漏输出?
- 1.1 推挽输出 vs 开漏输出
- 1.2 线与特性(I2C多设备挂载的核心)
- 1.3 天然支持电平转换
- 二、I2C协议时序:每一段波形都有意义
- 2.1 总线空闲状态
- 2.2 Start 起始信号
- 2.3 数据传输核心规则(必考)
- 2.4 ACK应答位(SDA上升沿疑问解答)
- 2.5 Stop 停止信号
- 2.6 完整写操作数据帧结构
- 三、I2C主机写完整流程(流程图+代码对应)
- 3.1 初始化:i2c_init()
- 3.2 传输前置准备
- 3.3 产生Start起始信号
- 3.4 发送从机地址+读写位
- 3.5 等待传输完成与应答:wait_i2c_iif()
- 3.6 发送从机内部寄存器地址
- 3.7 循环发送用户数据
- 3.8 产生Stop信号,等待总线空闲
- 四、i.MX6ULL硬件I2C核心寄存器汇总
- 五、高频疑问整理(今天踩过的坑)
- 1. 为什么判断IIF不能直接写 base->I2SR == 0 ?
- 2. I2DR到底是发地址还是发数据?
- 3. 从机为什么还有内部寄存器地址?
- 4. ipg_clk和I2C是什么关系?
- 六、I2C的典型应用场景
- 学习总结
前言
打卡ARM裸机学习第八天,今天核心攻克I2C通信协议。从最底层的GPIO输出模式、线与特性,到I2C标准时序规则,再到i.MX6ULL硬件I2C寄存器配置,最后结合裸机代码和逻辑分析仪波形验证。
一、硬件底层:为什么I2C必须用开漏输出?
这张图从上到下完整讲透了I2C的物理基础:线与特性 → 上拉电阻电平转换 → GPIO内部推挽/开漏结构,是理解I2C的前提。
1.1 推挽输出 vs 开漏输出
GPIO引脚内部由两个MOS管控制输出:上管mos1接3.3V电源,下管mos2接GND地。
- 推挽输出模式 两个MOS管交替导通,一个导通另一个必然截止:
- 输出1:mos1导通、mos2截止 → 主动输出高电平3.3V
- 输出0:mos1截止、mos2导通 → 主动拉低到GND ❌ 致命问题:如果两个芯片的推挽引脚直接相连,一个输出高、一个输出低,会形成电源到地的直接短路,烧毁芯片。因此I2C总线绝对不能使用推挽模式。
- 开漏输出模式(I2C引脚必须配置) 上管mos1永久截止,只控制下管mos2,只有两种合法状态:
- 输出0:mos2导通 → 主动把引脚拉到GND低电平
- 输出1:mos2截止 → 引脚进入高阻悬空状态,芯片自己不输出任何电平 ✅ 必要条件:总线外部必须接 4.7k~10k 上拉电阻,高阻态时由上拉电阻把总线拉成高电平。
1.2 线与特性(I2C多设备挂载的核心)
图中的真值表就是开漏总线的线与逻辑:
总线上任意一个设备把引脚拉低,实际总线电平就是低;只有所有设备都释放引脚(高阻),总线才会被上拉电阻拉成高电平。
这个特性带来两个核心价值:
1.3 天然支持电平转换
图中3.3V芯片与5V芯片互联的结构,就是开漏的另一大优势: 开漏引脚只能拉低,高电平由外部上拉电阻的供电决定。因此3.3V的主控可以直接和5V的I2C器件通信,只需要把上拉电阻接到5V即可,不需要额外的电平转换芯片。
二、I2C协议时序:每一段波形都有意义
I2C只有两根信号线:SCL(时钟线,主机提供通信节拍)、SDA(数据线,半双工双向传输)。所有通信都严格遵循时钟规则。
2.1 总线空闲状态
SCL和SDA都保持高电平,总线上所有设备都释放引脚,由上拉电阻拉高。
2.2 Start 起始信号
- 规则:SCL为高电平时,SDA产生下降沿(高电平→低电平)。
- 代码对应:配置I2CR寄存器的MSTA位从0→1,硬件控制器自动生成Start信号。
- 意义:宣告一次通信开始,总线上所有从机开始监听数据。
2.3 数据传输核心规则(必考)
一句话记忆:时钟低,改数据;时钟高,读数据。
2.4 ACK应答位(SDA上升沿疑问解答)
每发送完8位数据,会紧跟第9个时钟周期,就是应答位,用于接收方向发送方反馈接收状态。
- ACK(应答):接收方把SDA拉低(低电平),表示成功收到数据。
- NACK(非应答):SDA保持高电平,表示接收失败或拒绝接收。
今天的核心疑问:为什么ACK结束后SDA会有一个上升沿?
这是今天最容易困惑的点,本质是总线控制权的交接: 应答位期间,SDA的控制权在从机手里,从机拉低SDA回复ACK。当应答的时钟结束、SCL回到低电平时,从机必须立刻释放SDA(自身下管截止)。此时总线上没有任何设备拉低,外部上拉电阻会把SDA自动拉回高电平——这就是那个上升沿的来源。
✅ 关键结论:这个上升沿是总线释放的自然回弹,和下一位数据是0还是1没有关系。如果下一位要发0,主机会在SCL低电平期间,再把SDA从高电平拉下来。
两种SDA上升沿的本质区别
| ACK后回弹 | SCL低电平期间 | 从机释放总线,上拉电阻自然回弹 | 总线控制权交接,无控制含义 |
| Stop停止信号 | SCL高电平期间 | 主机主动拉高SDA | 控制信号,宣告通信结束 |
2.5 Stop 停止信号
- 规则:SCL为高电平时,SDA产生上升沿(低电平→高电平)。
- 代码对应:MSTA位从1→0,硬件自动生成Stop信号。
- 意义:宣告本次通信结束,总线回到空闲状态。
2.6 完整写操作数据帧结构
【此处插入第二张图左侧的黄色帧结构图】 标准的I2C主机写操作帧结构: Start → 从机地址(7bit) + 写位(0) → ACK → 从机内部寄存器地址 → ACK → 数据字节1 → ACK → 数据字节2 → ACK → … → Stop
三、I2C主机写完整流程(流程图+代码对应)

这张流程图和我们的裸机i2c_write函数完全一一对应,每一步都对应硬件寄存器的操作。
3.1 初始化:i2c_init()
⚠️ 重要前提:clock_init中必须已经打开I2C对应的IPG时钟(CCGR寄存器),否则写所有寄存器都无效。
3.2 传输前置准备
对应流程图第一步:
3.3 产生Start起始信号
置位MSTA位,硬件自动在总线上输出Start信号。
3.4 发送从机地址+读写位
向I2DR数据寄存器写入:(dev_addr << 1) | 0
- 7位从机地址左移1位,挪到高7位;
- 最低位填0,代表本次是主机写操作。
3.5 等待传输完成与应答:wait_i2c_iif()
3.6 发送从机内部寄存器地址
向I2DR写入reg_addr,告诉从机要操作它内部的哪一个存储单元/寄存器,随后等待应答。
3.7 循环发送用户数据
for循环依次把data[i]写入I2DR,每发送一个字节,就调用一次等待应答函数。
3.8 产生Stop信号,等待总线空闲
四、i.MX6ULL硬件I2C核心寄存器汇总
| I2CR 控制寄存器 | IEN(bit7) | I2C模块总使能开关 |
| MSTA(bit5) | 0→1产生Start信号;1→0产生Stop信号 | |
| MTX(bit4) | 收发模式:1=主机发送,0=主机接收 | |
| TXAK(bit3) | 主机接收时,控制回复ACK/NACK | |
| RSTA(bit2) | 产生重复起始信号(读操作用) | |
| I2SR 状态寄存器 | IBB(bit5) | 总线忙标志:1=忙碌,0=空闲 |
| IAL(bit4) | 仲裁丢失标志,多主机场景使用 | |
| IIF(bit1) | 字节传输完成标志,必须软件手动清零 | |
| RXAK(bit0) | 接收应答:0=ACK,1=NACK | |
| IFDR 分频寄存器 | – | 对ipg_clk分频,设置SCL总线波特率 |
| I2DR 数据寄存器 | – | 写入=发送数据;读取=接收数据 |
五、高频疑问整理(今天踩过的坑)
1. 为什么判断IIF不能直接写 base->I2SR == 0?
I2SR是8位状态寄存器,里面同时有IBB、IAL、IIF、RXAK等多个标志位。我们只关心IIF这1位,必须用& I2SR_IIF做掩码,把其他位全部清0再判断。如果直接判断整个寄存器等于0,其他任意一位为1都会导致判断错误。
2. I2DR到底是发地址还是发数据?
I2DR就是单纯的数据寄存器,你写入什么内容,硬件就发送什么内容。第一次写入是设备地址,第二次写入是寄存器地址,后面循环写入是用户数据——功能由写入的内容决定,寄存器本身不区分类型。
3. 从机为什么还有内部寄存器地址?
I2C从设备不是单一功能的黑盒。比如AT24C02 EEPROM有256个存储单元,MPU6050陀螺仪有配置寄存器、加速度寄存器、陀螺仪寄存器。主机必须先告诉从机“操作内部第几个单元”,才能正确读写对应的数据。
4. ipg_clk和I2C是什么关系?
ipg_clk是外设总线时钟,给I2C控制器的寄存器读写提供时钟;I2C总线的SCL通信时钟,是ipg_clk经过IFDR寄存器分频之后得到的。
六、I2C的典型应用场景
I2C适合板内短距离、低速、多外设的场景,优势是只占用2个引脚,非常节省IO资源:
- 传感器类:MPU6050六轴、SHT30温湿度、BH1750光照传感器
- 存储类:AT24C系列EEPROM,掉电保存小容量参数
- 显示类:0.96寸OLED液晶屏
- 外设扩展:PCA9685等IO扩展芯片
- 实时时钟:DS3231等RTC时钟芯片
学习总结
今天从物理层的开漏、线与特性,到协议层的时序规则、应答机制,再到硬件寄存器配置和裸机代码实现,把I2C的完整体系串了起来。最容易混淆的ACK后SDA上升沿,本质是总线控制权交接的上拉回弹,和数据位无关。结合逻辑分析仪的波形和代码一步步对应,就能彻底理解I2C的工作流程。 明天继续攻克I2C主机读操作和重复起始信号。



