IIC说明
- 介绍:串行、半双工总线 (可收可发不能同时收发)
- 总线:一根SDA数据线,一根SCL时钟线
模拟IIC本质
- 主控芯片 (STM32/GD32):使用模拟IIC协议:主控的引脚为通用引脚,需要模拟IIC协议的信号收发流程
- 通信芯片 (M24C02):使用硬件IIC
- 主控芯片通过通用引脚模拟SCL和SDA发送高低电平,而通信芯片通过硬件IIC直接解析并回应主控
- 首先先通过这两根模拟引脚寻址通信芯片的地址,并确定收发方向,使用read还是send,确定SDA的控制权限
- 其次收发数据:8bit数据 + 1bit ACK应答
引脚 & 芯片地址
- 引脚:主控芯片通过两根引脚模拟 SCL 和 SDA
- 芯片地址:芯片厂家预设开头 + 预留引脚 (接地为0,接电源为1)
- 预留引脚:预留几位,代表同样前缀的芯片可以挂载 2n2^n2n 颗
- 预留引脚不能悬空,否则会造成读取地址错误
常见 IIC 芯片预设前缀
| 芯片类型 | 典型型号 | 基因前缀 (7位中的高位) | 可变位 (A2/A1/A0) | 默认 7位地址 (若全接地) | 8位点名地址 (写/读) |
|---|---|---|---|---|---|
| 存储器 (EEPROM) | AT24C02 / M24C02 | 1010 |
3位 (A2,A1,A0) | 0x50 |
0xA0 / 0xA1 |
| 实时时钟 (RTC) | DS1307 / PCF8563 | 1101000 |
无 (固定) | 0x68 |
0xD0 / 0xD1 |
| 惯性传感器 | MPU6050 / MPU9250 | 110100 |
1位 (AD0引脚) | 0x68 |
0xD0 / 0xD1 |
| 大气压/气温 | BMP280 | 111011 |
1位 (SDO引脚) | 0x76 |
0xEC / 0xED |
| 温湿度传感器 | AHT10 / AHT20 | 0111000 |
无 (固定) | 0x38 |
0x70 / 0x71 |
| OLED 显示屏 | SSD1306 | 011110 |
1位 (SA0引脚) | 0x3C |
0x78 / 0x79 |
如果你在同一组 SCL/SDA 总线上挂了 8 片 M24C02,你只需要把它们的 A0, A1, A2 按照二进制编排好:
- 第一片:全接地(地址
0x50) - 第二片:A0 接高,其余接地(地址
0x51) - ...
- 第八片:全接高(地址
0x57)
很多手册里写地址是 0x50(7位),但你写代码发字节时必须左移一位变成 0xA0
- 写 :
(0x50 << 1) | 0→\rightarrow→ 发送0xA0 - 读 :
(0x50 << 1) | 1→\rightarrow→ 发送0xA1
数据格式
- 主机读写:8bit数据位 + 1bit确认位
- 主机在发送或者读取每一位都以 SCL 拉低为结束,为了下一次 SDA 操作的安全性
主机控制SCL & SDA控制权
- 不论是写还是读,SCL(时钟线)的 9 个脉冲永远由主机掌控。
- 区别只在于 SDA 这根线上,谁在控制
| 主机动作 | 前 8 位(数据位)脉冲 | 第 9 位(应答位)脉冲 | 逻辑精髓 |
|---|---|---|---|
| 主机发送 (Write) | 主机控线: 根据数据 0/1 控制 SDA 的高低点评 | 主机放手 (SDA=High): 看从机拉不拉低 SDA。 | 发完 8 位听响声 (Wait_Ack) |
| 主机接收 (Read) | 主机放手 (SDA=High): 看从机在 SDA 发过来的高低电平 | 主机控线: 根据还要不要,把 SDA 拽低(0)或抬高(1)。 | 收完 8 位给表态 (Send_Ack/Nack) |
主机发送起始和终止信号
- 主机主动发送起始 和停止信号
- 起始和终止信号,SCL都为高
- 起始:SCL高,SDA从高 → 低
- 终止:SCL高,SDA从低 → 高
主机写入 / 从机写入
- 当 SCL 从高 → 低,主机/从机才能往 SDA 上写入数据
- 写1bit,然后SCL拉高等待对方读,然后SCL再拉低,再写
- 主机在发送或者读取每一位都以 SCL 拉低为结束,为了下一次 SDA 操作的安全性
IIC 1-Bit 传输标准时序流 (主机视角)
| 阶段 | 动作 | 物理意义 | 专家级注脚 |
|---|---|---|---|
| 1. 申请准入 | SCL 从高 →\rightarrow→ 低 | 打开"变动窗口" | 只有在低电平,动 SDA 才不会被误判为 Start/Stop。 |
| 2. 摆放数据 | 主机/从机 更新 SDA | 写入 1 bit | 发送方根据要发的 0 或 1,拉低或释放 SDA 线。 |
| 3. 信号建立 | Delay_us(2) | 稳定电平 | 给电信号一点时间,让它在长长的 PCB 走线上稳下来。 |
| 4. 发令采样 | SCL 从低 →\rightarrow→ 高 | 锁定"采样窗口" | 接收方看到上升沿,立刻读取此时 SDA 的电平状态。 |
| 5. 保持稳定 | Delay_us(2) | 确保读取完成 | 此时 SDA 绝不能动,它是接收方眼里的"真理"。 |
| 6. 安全收尾 | SCL 从高 →\rightarrow→ 低 | 关闭窗口 / 释放路权 | 这步最关键! 结束这一位,并为下一位的 SDA 变动提供安全环境。 |
主机读取
- 只有当 SCL 从低电平变到高电平时,接收方 (主机/从机) 才会去读 SDA
- 也就是说,想要主机主动读取数据,或者从机被动读取数据。SCL必须进行一次从低到高的跳变
- 主机在发送或者读取每一位都以 SCL 拉低为结束,为了下一次 SDA 操作的安全性
从机
- 时钟拉伸:如果从机处理不过来(比如 EEPROM 正在写数据),可以把 SCL 拉低不放,强迫主机等它一会儿
- 主机处理时钟拉伸:主机在拉高SCL之后,加一个
while(READ_SCL == 0);处理从机的"时钟拉伸"。
IIC通信流程
- 先发1byte的通信信息:7bit地址信息 + 1bit收发方向 (主机还是从机控制SDA)
- 收发数据
IIC具体通信流程
通信芯片 (M24C02/EEPROM) 【SCL线 / SDA线】 主控 (STM32/GD32) 通信芯片 (M24C02/EEPROM) 【SCL线 / SDA线】 主控 (STM32/GD32) #mermaid-svg-77nZEaxja4gZlIjg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-77nZEaxja4gZlIjg .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-77nZEaxja4gZlIjg .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-77nZEaxja4gZlIjg .error-icon{fill:#552222;}#mermaid-svg-77nZEaxja4gZlIjg .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-77nZEaxja4gZlIjg .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-77nZEaxja4gZlIjg .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-77nZEaxja4gZlIjg .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-77nZEaxja4gZlIjg .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-77nZEaxja4gZlIjg .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-77nZEaxja4gZlIjg .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-77nZEaxja4gZlIjg .marker{fill:#333333;stroke:#333333;}#mermaid-svg-77nZEaxja4gZlIjg .marker.cross{stroke:#333333;}#mermaid-svg-77nZEaxja4gZlIjg svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-77nZEaxja4gZlIjg p{margin:0;}#mermaid-svg-77nZEaxja4gZlIjg .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-77nZEaxja4gZlIjg text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-77nZEaxja4gZlIjg .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-77nZEaxja4gZlIjg .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-77nZEaxja4gZlIjg .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-77nZEaxja4gZlIjg .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-77nZEaxja4gZlIjg #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-77nZEaxja4gZlIjg .sequenceNumber{fill:white;}#mermaid-svg-77nZEaxja4gZlIjg #sequencenumber{fill:#333;}#mermaid-svg-77nZEaxja4gZlIjg #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-77nZEaxja4gZlIjg .messageText{fill:#333;stroke:none;}#mermaid-svg-77nZEaxja4gZlIjg .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-77nZEaxja4gZlIjg .labelText,#mermaid-svg-77nZEaxja4gZlIjg .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-77nZEaxja4gZlIjg .loopText,#mermaid-svg-77nZEaxja4gZlIjg .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-77nZEaxja4gZlIjg .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-77nZEaxja4gZlIjg .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-77nZEaxja4gZlIjg .noteText,#mermaid-svg-77nZEaxja4gZlIjg .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-77nZEaxja4gZlIjg .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-77nZEaxja4gZlIjg .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-77nZEaxja4gZlIjg .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-77nZEaxja4gZlIjg .actorPopupMenu{position:absolute;}#mermaid-svg-77nZEaxja4gZlIjg .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-77nZEaxja4gZlIjg .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-77nZEaxja4gZlIjg .actor-man circle,#mermaid-svg-77nZEaxja4gZlIjg line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-77nZEaxja4gZlIjg :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 物理连接:SCL(PB6) <--->> SDA(PB7) 【起始信号】 【寻址阶段】MSB高位在前 【数据传输】写操作示例 【停止信号】 SCL保持高,SDA由高变低硬件电路触发中断/状态机7位地址 (1010000) + 写(0)逻辑门匹配【ACK】从机硬件强行拉低SDA主控读取到低电平,点名成功发送 8bit 数据 (SCL每跳一次读1bit)【ACK】从机完成接收,拉低SDA主控确认,准备下一包或停止SCL保持高,SDA由低变高释放总线,进入待机/写入周期
- 主机发送起始信号启用总线
- 主机发送一个字节数据指明从机地址和后续字节的传送方向
- 7bit地址
- 1bit方向 (0表示主机发送给从机,1表示从机发送给主机)
- 被寻址的从机发送应答信号回应主机
- 发送器发送一个字节数据
- 接收器发送应答信号回应发送器
- ... ... (循环步骤4、5)
- 通信完成后主机发送停止信号释放总线