I2C 江科大 STM32
一、开漏输出
- 开漏只有两个动作
- 写 0:MOS 导通,主动拉低总线(输出低)
- 写 1:MOS 断开,释放总线(高阻,不驱动)
✅ 重点:写 1不是芯片输出高;全部设备释放后,靠 4.7K 上拉电阻把总线拉成高电平
- 线与逻辑 任意一个设备拉低 → 总线低(一票拉低);全部释放,总线才会变高
不会短路原因:所有设备都不能主动推高电平,不会出现一高一低对撞
- 软件 I2C(江科大) GPIO固定开漏输出,硬件模式全程不变
- 通信【输出】:控制 MOS 拉低总线,改变总线电平
- 通信【输入】:MOS 保持断开,不碰总线,读取总线电平
比喻:总线 = 绳子,上拉 = 弹簧;设备手只能往下拽,不能向上推
二、1 主机 + 1 从机 SDA 通信(SCL 永远主机控制,从机只读 SCL)
- 主机发数据(主机输出,从机输入) 主机控制 MOS 拉低 / 释放 SDA;从机 MOS 断开,只读取 SDA 电平 bit0 拉低,bit1 释放
- ACK 应答(主机输入,从机输出) 主机释放 SDA,读取电平;选中从机拉低 SDA,表示应答 ACK
- 主机读数据(从机输出,主机输入) 主机释放 SDA、只产生时钟;从机控制 MOS 输出 bit0/bit1
三、多从机 I2C总线
- 硬件:所有设备 SCL 并联、SDA 并联;共用一组 STM32 引脚
- 单主机规则:从机不能主动发数据,等待主机发地址点名
- 流程:起始信号→发从机地址→全部从机接收;地址匹配的从机回复 ACK,其余从机静默不驱动总线,主机和选中从机通信
- 坑:同总线不能有地址冲突;所有设备必须共 GND
四、疑问1:同样都是「放手(MOS 断开,写 1)」,什么时候代表【输出 bit1】,什么时候代表【切换成通信输入,读总线】?
✅ 一句话结论:硬件动作完全一样(都是写 1,MOS 断开,放手)。区别不在硬件,而在【当前通信阶段的约定,谁拥有总线控制权】。
两个场景,硬件动作一模一样,含义不同
前提:GPIO 硬件始终是开漏输出模式,没有切换硬件输入模式动作:写 1 → MOS 断开,放手释放总线。
场景 1:我是发送方,我要【输出 bit=1】
👉总线控制权在我手上,现在轮到我发 bit。 我写 1(放手)。 含义:我主动发送 bit1。 其他所有设备都约定好:现在你们全部保持放手,不要拉低总线。 于是总线被上拉电阻拉高,代表 bit1。
例子:主机发送数据,发送 bit1。主机放手,所有从机本来就保持放手,总线变高,这就是主机输出 bit1。
场景 2:我是接收方,我要【读别人的数据(通信输入)】
👉总线控制权交给对方,轮到对方来发 bit。 我写 1(放手)。 含义:我不碰总线,把总线交给对方设备驱动,我只负责读取总线上的电平。 对方设备可以选择拉低(发 0)或者放手(发 1),我只是观察者。
例子:ACK 应答阶段,主机要读从机应答。主机放手,把总线交给从机;从机可以拉低 SDA,主机读取电平。
对比看(硬件动作完全相同,只是角色不一样)
表格
| 场景 | 我方动作 | 总线控制权 | 含义 |
|---|---|---|---|
| 我方发送 bit1 | 写 1,MOS 断开,放手 | 在我手里,我负责发 bit | 输出 bit1 |
| 我方准备接收数据 | 写 1,MOS 断开,放手 | 交给对方,对方发 bit | 通信输入,只读不驱动 |
关键判断规则(你用来区分的依据)
看当前通信时序,轮到谁来驱动 SDA 总线:
- ✅轮到我发数据 → 我控制 MOS,拉低 = 发 0;放手 = 发 1(输出 bit1)
- ✅轮到对方发数据 → 我全程放手,不去拉低总线,只读取总线电平(通信输入)
硬件上的 “放手” 本身没有自带标记,不会自动区分是输出 1 还是输入。 区分这件事,是软件代码靠 I2C 时序协议来规定的。 代码走到哪一步,就知道现在是谁拥有总线控制权。
绳子比喻升级
一根带弹簧(上拉电阻)的绳子。
- 轮到我发信号:我的手控制绳子
- 往下拽(拉低)→发 0
- 松手(放手)→发 1,弹簧拉高绳子
- 轮到对方发信号:我必须把手松开,不动绳子。对方的手来控制拽不拽绳子。我只盯着绳子高度,看对方发的是什么信号。
两种情况我的手都是松开的! 区别:是谁有权去拽绳子。
举 I2C 真实例子
- 主机发送地址 bit1:轮到主机发,主机放手 →输出 bit1
- 发送完地址,等待 ACK:轮到从机驱动 SDA,主机放手 →主机进入通信输入,读应答
两个动作,主机都是写 1 放手。 但是代码时序不同,角色不同,含义完全不一样。
一句话记
硬件动作相同,含义由通信时序决定。轮到我发,放手就是输出 bit1;轮到对方发,放手就是我进入输入(只读)。
疑问2:同样都是「放手(MOS 断开,写 1)」,什么时候代表【输出 bit1】,什么时候代表【切换成通信输入,读总线】?
硬件动作一模一样,是主机点名 + 通信时序,规定了当前是谁掌握总线的驱动权。
绳子比喻升级
很多个人抓同一根带弹簧的绳子。
- 主机先喊名字(点名):“AT24C02!”
- AT24C02 听到,应答一声(拽一下绳子,ACK);其他设备听到不是自己名字,手全程不动。 情况 A:主机说,我来告诉你数据(主机写)→ 主机控制拽 / 松手,AT24C02 手松开看绳子。 情况 B:主机说,你来把数据发给我(主机读)→ 主机手松开,AT24C02 控制拽 / 松手,主机看绳子。
极简笔记
- I2C 单主机:主机点名,决定本次通信的对话双方。
- 只有被点名的从机才有资格驱动 SDA;其余从机保持静默,绝不拉低总线。
- 通信阶段分两种角色:谁获得总线驱动权,谁控制 MOS 拉低 / 释放;另一方保持放手,只读取电平。
- 同一个 “放手(写 1)” 动作,含义由当前通信角色(谁被点名、轮到谁说)决定。
五、I2C SCL 时钟的作用
一句话:SCL(时钟)用来同步主机和从机,规定什么时候读取 SDA 上的数据。 I2C 是同步通信,收发双方靠同一个时钟节拍,保证采样数据不出错。
SCL 永远由主机产生、主机控制;所有从机只能读取 SCL 电平,绝对不能驱动 SCL 总线。
核心规则:在 SCL 高电平期间采样 SDA 数据
I2C 协议硬性规定:
- SCL =高电平:读取 SDA 线上的 bit(采样,读数据)
- SCL =低电平:可以修改 SDA 的电平(改变 bit)
- 主机把 SCL 拉低(SCL=0)
- 在 SCL 低的时候,主机去改变 SDA 电平(拉低 / 释放,准备好 bit0 或 bit1)
- 主机释放 SCL,SCL 变成高电平
- 从机在 SCL 高电平的时候,读取 SDA 上这一位数据
- 主机再次拉低 SCL,准备下一个 bit
- 主机拉低 SCL=0
- 在 SCL 低的这段时间,从机修改 SDA 电平(准备 bit)
- 主机释放 SCL,SCL 变高
- 主机在 SCL 高电平,读取 SDA 的 bit
- 主机拉低 SCL,进入下一位
- 起始信号:SCL 为高的时候,主机把 SDA 从高拉低
- 停止信号:SCL 为高的时候,主机把 SDA 从低释放变高
补充:为什么串口(USART)不需要时钟?
串口是异步通信,双方预先约定波特率,各自用自己内部的时钟。 I2C 是同步通信,主机输出统一时钟 SCL,所有设备跟着主机的时钟节拍干活。
一句话浓缩到笔记
SCL 时钟由主机产生,作用:统一节拍;SCL 低电平允许改变 SDA,SCL 高电平采样读取 SDA 数据;从机只监听 SCL,不能驱动 SCL。