四层架构
- 应用程序:决定要显示什么,比如Hello
- 库函数:决定字符怎么转, ‘H’ -> 点阵字符
- 设备驱动:决定芯片怎么写,如操作SSD1306寄存器
- HAL库:决定信号怎么发,用 HAL_I2C_Master_Transmit
硬件连接
I2C有两条数据线:
- SCL(Serial Clock):时钟线,进行时钟同步
- SDA(Serial Data):数据线,发送数据
所以I2C是半双工的,因为只有一条数据线。
I2C是一拖多的,可以一个主设备带多个从设备,比如:
| |
为什么需要上拉电阻?
SCL、SDA都是开漏输出,只能拉低,不能主动拉高。 默认状态下就是高电平,由上拉电阻拉高。任何设备拉低,整条线就变低,这样就实现了线与逻辑,是实现一拖多的基础。
为什么要用开漏输出?
不用会发生什么
如果不用开漏输出,假设: 主设备想输出高电平,接VCC; 从设备想输出低电平,接GND 就会短路导致烧坏电路。
| |
普通推挽输出有两个MOS管:
| |
开漏输出如何解决
去掉上面的P-MOS,只保留下面的N-MOS:
| |
想输出高电平,不导通就行,上拉电阻会拉高。想输出低电平,导通N-MOS,电平会被拉低。 输出高电平,代表设备不参与。
| |
任何组合都不会短路,因为没设备真的能输出高电平。 I2C总线上有多个设备,开漏输出让多个设备同时控制一个线这件事变得安全。
信号定义
Start
| |
含义: 设备说"我要开始说话了"
因为默认就是高电平,Start是高拉低更合理。
Start是一个瞬间的跳变信号,不是持续状态。
Start之后立即开始发时钟:
| |
在SCL变低后,第一次变高就开始发数据,也就是第一个时钟的时候。
Stop
| |
含义: 设备说"我说完了"
时钟线只有主设备能控制,数据线则是任何设备都能控制。
数据位
SCL低时,SDA可以变化(准备数据) SCL高时,SDA必须稳定(接收方读取)
那怎么表示01呢?SCL高电平的时候,SDA是什么就是什么。
| |
空闲
空闲的时候,两根线都是高电平,因为是开漏输出,没设备驱动,默认就是高电平。
| 情况 | SCL状态 | 说明 |
|---|---|---|
| 空闲 | 高电平 | 上拉电阻拉高,没人驱动 |
| 通信中 | 交替高低 | 主设备驱动,产生时钟 |
| 时钟拉伸 | 低电平 | 从设备故意拉低,让主设备等待 |
主设备是有绝对控制权的,Start和Stop只能主设备发起。
传输格式
主设备写
| |
注:设备地址+W = 7位地址 + 0(写位)
如果从设备不想接收了,比如缓冲区满了、设备忙碌或者发现数据错误,可以发送NACK。
在发送完8位数据后,紧接着的第九时钟就是ACK/NACK的发送时机。第九时钟的高电平期间,接收方拉低SDA=ACK。
那问题来了,接收方不拉低SDA是不是就是NACK?那怎么知道从设备是不处理ACK呢?还是发生了NACK呢? 答案是在硬件上,发送方是无法区分的,但我们可以在逻辑上区分,通过上下文判断:
情况1:设备不存在(地址阶段NACK)
| |
主设备判断:
- 地址发出去,第一个ACK就是NACK
- 这个地址上没有设备(或设备坏了)
- 应该报错、重试、或换地址
情况2:设备拒绝继续(数据阶段NACK)
| |
主设备判断:
- 地址阶段有ACK → 设备存在
- 数据阶段才出现NACK → 设备故意拒绝
- 正常结束,不算错误
主设备读
| |
如果主设备发ACK,代表还要继续读,如果主设备发NACK,说明可以停止了。 NACK后从设备释放SDA,主设备可以发Stop。
主设备读后,也要发ACK,如果要终止,需要发NACK,再发Stop,不应该在ACK后接Stop。
如果主设备发送完地址后,从设备没准备好,可以把SCL拉低,这时候主设备就会等待,这就叫时钟拉伸。 这是I2C协议允许的,SCL也是开漏设计就是为了这个。
ACK机制
传输8位数据后:
| |
- 如果是写操作:从设备发ACK,表示收到了
- 如果是读操作:主设备发ACK说明还要继续发,发NACK说明够了
ACK怎么发
- 接收方在第9时钟拉低SDA = ACK
- 接收方不碰SDA(保持高) = NACK
ACK只能在SDA发,因为SCL是时钟线,不应该进行数据发送。
协议和数据含义
协议只规定了:
- Start/Stop信号格式
- 7bit地址+1bit数据方向
- 每字节8bit+1bit ACK
芯片可以自己规定地址后面的Byte有什么含义!
例子:EEPROM(存储芯片)
| |
例子:MPU6050(陀螺仪)
| |
结论: I2C协议只规定"怎么传",不规定"传什么"。数据含义要看芯片手册。
总结表格
| 概念 | 要点 |
|---|---|
| 开漏输出 | 只能拉低,不能主动拉高,上拉电阻提供默认高 |
| 线与逻辑 | 任何设备拉低,整条线就低 |
| Start | SCL高时SDA高→低 |
| Stop | SCL高时SDA低→高 |
| 数据位 | SCL低时变化,SCL高时稳定 |
| ACK | 第9时钟,接收方拉低表示确认 |
| 读NACK | 主设备发NACK表示"读完了" |
| 协议边界 | 只规定传输格式,数据含义芯片自定义 |