
1. IIC到底在传什么先从物理连接和寻址说起1.1 两根线怎么撑起一堆设备刚接触嵌入式的时候很多人第一次看到IIC原理图都会愣一下整个总线就两根线一根SDA数据线、一根SCL时钟线结果板上挂了四五个芯片各自还能收发数据这怎么做到的IIC全称Inter-Integrated Circuit是飞利浦现在的NXP在几十年前搞出来的芯片间通信方案。它的核心思想特别朴素把时钟线和数据线做成公共总线所有设备都挂在这两根线上谁要说话先报自己的“门牌号”。这种设计决定了它的两个天然优势——接线少、扩展容易。你要挂8个传感器还是这两根线地址不同就行。但“公共总线”这四个字背后有个硬件层面的关键设计所有设备的SDA和SCL引脚必须是开漏输出。开漏的意思是设备只能把线拉低不能主动拉高。那谁来把线拉高答案就是每个IIC电路板上都少不了的上拉电阻。两根线上各接一个电阻到电源所有设备都不拉低的时候线就被电阻拉成高电平某个设备把线拉低线就变成低电平。这样多个设备同时操作也不会短路这是IIC能“一总线挂多设备”的根本原因。IIC总线上的角色分两种主设备和从设备。主设备负责产生时钟、发起通信从设备只能被动响应。绝大多数场景下单片机是唯一的主设备传感器、存储器、IO扩展芯片都是从设备。总线上也可以挂多个主设备但先别急着去玩多主通信能把单主模式跑稳已经是很多项目的及格线了。1.2 设备地址和寄存器地址别搞混IIC寻址靠的是设备地址。标准模式是7位地址加上读写位凑成一个字节。比如你写代码时经常看到0xA0这种地址它实际上是把7位设备地址0x50左移了一位最低位填0表示写、填1表示读。很多新手在这里第一次犯晕EEPROM的地址明明是0x50为什么代码里写0xA0就是因为这移位的一位。7位寻址理论上能挂128个设备但实际上有个限制地址0x00是广播地址所有从设备都要响应0x01到0x07保留给特殊用途。真正能自由分配的地址大概是从0x08到0x77。实际项目中我见过的板子挂十几个IIC设备已经是极限了因为还要考虑总线电容和地址冲突的问题。寄存器地址和设备地址是另一对容易混淆的概念。设备地址定位的是“跟哪个芯片说话”寄存器地址定位的是“这个芯片内部的哪个存储位置”。比如一个温湿度传感器设备地址是0x38内部有温度寄存器、湿度寄存器、配置寄存器你读写数据时要先发设备地址再发寄存器地址然后才是数据。这个流程在后面读写时序那一节会详细拆解。1.3 IIC能跑多快速率等级和实际选择IIC规范里定义了几档速率标准模式100kHz、快速模式400kHz、快速模式Plus 1MHz、高速模式3.4MHz。最早期的IIC只有100kHz现在绝大多数传感器和存储芯片都支持400kHz部分新器件支持1MHz。实际项目里选速率不是能跑多快就跑多快。IIC总线速率受两个因素制约上拉电阻和总线电容。这两者的关系直接影响信号上升时间后面专门有一节讲怎么算。我的经验是板上设备少、走线短的时候直接上400kHz设备多、走线长、传感器线缆外接的时候老老实实降回100kHz。一次通信多花几十微秒对绝大多数应用毫无影响但稳定性是实打实的。还有一个很多教程没提的点IIC的速率是指SCL时钟频率不是数据吞吐量。8个数据位加1个ACK位传一个字节实际需要9个时钟。所以100kHz的IIC理论吞吐也就11KB/s左右传大块数据并不合适。如果要从SD卡读照片、从Flash读几千字节固件SPI或者SDIO才是对口的方案。IIC适合的是小数据量的控制、寄存器和传感器读取。2. IIC上拉电阻到底取多大一张表搞定从1k到4.7k的取舍2.1 开路输出结构决定了非接上拉不可前面提到了开漏输出这里把原理说透。IIC设备的SDA/SCL引脚内部结构本质上是一个MOS管开关一端接地一端接到引脚。当设备要输出低电平时MOS管导通把引脚拉低到地当设备要输出高电平时MOS管截止引脚变成高阻态什么都不驱动。问题来了如果引脚保持高阻态线上电平是谁决定的如果没有外部上拉电阻这根线上会悬空电平不确定逻辑全是乱的。所以必须在外部接一个电阻到电源。这个电阻负责在没有任何设备拉低的时候把线拉到确定的高电平。这就是“上拉电阻”存在的意义。推挽输出为什么不行推挽结构上面一个管子拉高、下面一个管子拉低驱动能力强。但两个设备接在同一条线上一个输出高、一个输出低就等于一个把电源短路到地直接冒烟。IIC不是用软件来解决这种冲突的是从电路结构上就杜绝了这种可能。2.2 上拉电阻的计算方法从上升时间反推上拉电阻的取值本质上是和总线电容在玩一个RC充放电游戏。当设备释放总线时上拉电阻对总线电容充电电压从低电平爬升到高电平。这个爬升过程有多快决定了信号能不能在规定的时序内稳定下来。充放电的时间常数是τ R_p × C_bus从低电平到高电平的上升时间大约为t_r ≈ 0.8473 × R_p × C_bus。IIC规范对上升时间有明确要求标准模式100kHz要求上升时间不超过1000ns快速模式400kHz要求不超过300ns。举个实打实的例子假设你的板子总线上挂了4个设备走线总长10cm左右总线电容C_bus大概在100pF附近。要跑400kHz要求t_r 300ns 0.8473 × R_p × 100pF 300ns R_p 3.54kΩ所以上拉电阻必须小于3.5kΩ才能保证400kHz的上升时间达标。这是上限不是越小越好。下限怎么定上拉电阻的下限由输出低电平时的灌电流决定。设备拉低时电流从电源流过电阻进到MOS管里。IIC规范要求VOL不超过0.4V设备在VOL0.4V时的灌电流能力通常按3mA算快速模式的标准。以3.3V系统为例R_p (VCC - VOL) / IOL (3.3 - 0.4) / 0.003 966Ω所以1kΩ基本是下限。如果VCC是5V下限就变成(5 - 0.4) / 0.003 1.53kΩ这时候选1k就会让VOL超标逻辑低电平可能被识别成中间电平。2.3 典型取值和选型建议综合上限和下限IIC上拉电阻的典型取值范围是1kΩ到4.7kΩ。我自己的习惯是3.3V系统、走线短、设备少2-3个、跑400kHz时选2.2kΩ5V系统选3.3kΩ或4.7kΩ如果总线需要引出排线连接外部设备或者板子上设备超过5个、总线电容偏大就用2.2k甚至1k同时考虑把速率降到100kHz。系统电压总线电容目标速率推荐上拉电阻3.3V50pF以下400kHz2.2kΩ3.3V100pF400kHz2.2kΩ~3.3kΩ3.3V200pF100kHz3.3kΩ~4.7kΩ5V100pF400kHz3.3kΩ5V200pF100kHz4.7kΩ电阻选大了上升沿变缓波形出现圆角速率稍微提一点就可能误码选小了低电平抬高功耗增加在恶劣情况下低电平不达标。如果对取值拿不准优先选2.2kΩ这是最通用的值。但具体项目里还是建议用示波器实测一下SDA的上升沿看有没有超过规范。2.4 总线电容的估算为什么设备多了要降速总线电容来自每个芯片引脚的寄生电容加上PCB走线的电容。单个IIC芯片引脚电容大约5-10pFPCB走线每厘米约0.5-1pF。算下来6个设备的板子总线电容轻松超过100pF。总线上所有设备的电容是并联关系设备越多电容越大上升时间越慢。所以一个常见规律是板子上IIC设备数量越多上拉电阻越小但电阻过小又会违反低电平规范这时唯一的出路是降低通信速率。我见过一个项目一块板上挂了8个IIC设备上拉电阻用1k才勉强跑到100kHz。后来改成两颗1k并联也就是500Ω结果低电平升到了0.6V从机直接不认了。最后还是老老实实降速到100kHz解决。如果总线电容实在太大超过400pF光靠调电阻就不太现实了这时候可以用IIC总线缓冲器比如PCA9600这种芯片它能隔离两侧电容重新驱动总线信号。不过绝大多数开发板到不了这个程度先把电阻和速率调好就够了。3. IIC时序拆解看懂START、ACK、读写的完整节奏3.1 起始条件和停止条件SCL高电平期间的SDA边沿IIC通信的帧结构所有动作都是从**起始条件START**开始的。起始条件是SCL保持高电平时SDA从高电平跳变到低电平。这个边沿是主设备的一种“宣告”告诉总线上所有设备我要开始通信了大家准备监听地址。**停止条件STOP**恰好相反SCL保持高电平时SDA从低电平跳变到高电平。所有从设备听到停止条件后结束本次通信释放总线。为什么非要在SCL高电平时检测边沿因为在正常数据传输阶段SDA的电平变化只允许发生在SCL低电平期间SCL高电平期间SDA必须保持稳定。所以“SCL高时SDA跳变”就成了一个不会跟数据混淆的特殊标志。这个设计相当巧妙相当于在时域上给控制和数据划了两条完全独立的轨道。发送起始条件和停止条件时SCL的时序也要注意细节先确保SCL为高然后SDA跳变然后SCL拉低。软件模拟IIC时很多人最后一个字节发完就结束少了停止条件从机状态机卡死在半路下一次通信就出问题。停止条件不是可有可无的它是一个必须发的“句号”。3.2 字节传输和应答信号第9个时钟不是传数据地址广播之后就是正式的数据传输阶段。IIC规定每个字节必须是8位从最高位MSB开始发送发完8位后主设备释放SDA然后产生第9个时钟脉冲这个脉冲是给**应答信号ACK**用的。应答的规则是接收方在第9个时钟的低电平期间把SDA拉低表示“我收到了”。如果是主设备发数据、从设备接收那么从设备在每收到一个字节后拉低SDA应答如果是主设备读取数据那么主设备在收到每个字节后拉低SDA应答但在收到最后一个字节前主设备要发送一个非应答信号NACK即保持SDA为高然后发送停止条件。NACK不是错误它往往是一种流程控制信号。比如读取EEPROM时主机读满自己期望的字节数后发一个NACK告诉从机“我不要了结束吧”。从机看到NACK就会停止发送。如果主机在应该发ACK的时候发了NACK或者反过来从机的状态机会错乱表现就是读出来的数据少一位或者多一位。设备地址发出去之后从机也会应答。如果总线上没有这个地址的设备或者设备忙没准备好第9个时钟SDA不会被拉低表现为NACK。所以调试IIC时第一步永远是检查地址发送后有没有ACK。没有ACK后面的数据收发全是白搭——先查地址对不对、上拉电阻有没有接、SDA有没有被拉死。3.3 一个完整的读写序列以EEPROM为例拿最常见的AT24C022Kbit串行EEPROM来拆一个完整的写操作和读操作。写单个字节到指定地址主机发的东西依次是START条件设备地址字节0xA07位地址0x50左移一位最低位为0表示写寄存器地址比如0x00指定往哪个地址写要写的数据比如0x55STOP条件注意从机电脑的EEPROM在收到一个字节后会触发内部写周期需要时间把数据真正固化到存储单元里AT24C02大约要5ms。在这段时间内从机会保持NACK称为“写周期”。很多程序控制逻辑复杂查了半天查不出来其实只要在写完后延时5ms以上再操作就能解决。读操作稍微复杂一点。随机读的模式是START条件设备地址0xA0写方向寄存器地址0x00你要读的地址再次发送START条件这叫做重复起始条件REPEATED START设备地址0xA1最低位为1表示读此时从机开始发送数据主机每收到一个字节发ACK主机收到需要的字节数后最后一个字节回NACKSTOP条件重复起始条件的作用是在不释放总线的情况下把通信方向从写切换成读。很多软件模拟IIC的代码里重复起始处理和起始处理完全一样这会有隐患——重复起始时SCL本来就是低电平如果还走“SCL高时SDA拉低”的逻辑SCL还没拉高SDA就变了从机可能识别不了。正确的做法是先让SDA和SCL同时为低然后SCL保持低SDA拉高再SCL拉高SDA再拉低这样才形成一个完整的重复起始边沿。3.4 时钟拉伸和其他需要知道的特殊机制时钟拉伸Clock Stretching是IIC协议里一个重要的从机保护机制。某些从设备比如MLX90614红外测温传感器处理数据需要时间处理不过来的时候会把SCL拉低不放。因为开漏结构下从机有能力拉低SCL主机此时必须等待——士别三日当刮目相待协议要求主机在释放SCL后检查SCL是否为高如果被从机拉着不放就继续等。软件模拟IIC时最容易忽略这个机制。很多人的软件IIC发送时钟是纯延时驱动的SCL拉高后延时一下就发数据从机来不及处理数据就把SCL拉低这时主机还在按自己的节奏走时序就乱了。正确做法是SCL拉高后轮询读取SCL引脚电平直到它确实变为高电平才继续。这个做法能兼容几乎所有带时钟拉伸的从设备多写几行代码换来的是广泛的兼容性。4. 用STM32实操IIC软件模拟代码硬件外设IO扩展芯片4.1 为什么我建议先把软件模拟跑通很多人问我STM32不是有硬件IIC外设吗为什么还要用软件模拟这里有个背景STM32早期的硬件IIC确实存在一些设计上的小毛病状态机复杂、事件标志容易弄混加上HAL库对IIC的封装处理也比较绕很多教程干脆建议直接用软件模拟。即便现在芯片和库都改进了不少我还是建议学习阶段先手写一遍软件IIC——因为只有自己把SCL和SDA的每一拍都控制过才能真正理解时序里那些微妙的细节。软件模拟IIC的好处是逻辑透明出问题能直接看到是卡在哪个等待上。缺点是占CPU、时序精度取决于主频和延时函数的一致性。在STM32上跑400kHz软件模拟是能扛住的但你要是用Arduino的digitalWrite一个引脚一个引脚翻转速度就会很惨。我一般用寄存器直接操作IO口翻转延时用空循环或者定时器微秒级延时实测在72MHz的STM32F103上软件模拟400kHz没有问题。4.2 软件模拟IIC的核心代码我用STM32标准库风格写一套最精简的软件IIC主站代码方便直接移植。先定义IO口操作宏以PB6为SCL、PB7为SDA为例#define SCL_H GPIOB-BSRR GPIO_Pin_6 #define SCL_L GPIOB-BRR GPIO_Pin_6 #define SDA_H GPIOB-BSRR GPIO_Pin_7 #define SDA_L GPIOB-BRR GPIO_Pin_7 #define SDA_READ (GPIOB-IDR GPIO_Pin_7)要注意的是SDA引脚要配置成开漏输出这样在读的时候可以直接切换成输入模式读电平不需要在输出和输入之间切换开漏输出在高电平时引脚处于高阻态外部上拉会把电平拉高此时读IDR寄存器也能读到正确电平。起始条件函数void IIC_Start(void) { SDA_H; SCL_H; delay_us(5); // 建立时间 SDA_L; // SCL高期间SDA拉低产生START delay_us(5); SCL_L; // 拉低SCL准备传数据 }停止条件函数void IIC_Stop(void) { SDA_L; SCL_H; delay_us(5); SDA_H; // SCL高期间SDA拉高产生STOP delay_us(5); }写一个字节并检查ACKuint8_t IIC_WriteByte(uint8_t data) { uint8_t i, ack; for (i 0; i 8; i) { if (data 0x80) SDA_H; else SDA_L; data 1; delay_us(2); SCL_H; // 等待从机时钟拉伸 while (SCL_READ 0); delay_us(2); SCL_L; } // 第9个时钟读ACK SDA_H; // 释放SDA delay_us(2); SCL_H; while (SCL_READ 0); ack SDA_READ; // 为0表示从机应答 delay_us(2); SCL_L; return ack; }读一个字节形参为0表示发送ACK为1表示发送NACKuint8_t IIC_ReadByte(uint8_t ack_flag) { uint8_t i, data 0; SDA_H; // 释放SDA for (i 0; i 8; i) { data 1; SCL_H; while (SCL_READ 0); delay_us(2); if (SDA_READ) data | 0x01; SCL_L; delay_us(2); } if (ack_flag) { SDA_H; // 发送NACK } else { SDA_L; // 发送ACK } SCL_H; while (SCL_READ 0); delay_us(2); SCL_L; SDA_H; return data; }这段代码我把“等待SCL被拉高”的逻辑加进去了这样就能兼容需要时钟拉伸的从设备。另外注意每次操作之间加的2微秒延时这是为了保证满足IIC的建立时间和保持时间具体数值只要不低于数据手册的最小要求就行不用卡得很死。写一次EEPROM的完整函数就变成void AT24C02_WriteByte(uint8_t addr, uint8_t data) { IIC_Start(); IIC_WriteByte(0xA0); // 设备地址写方向 IIC_WriteByte(addr); // 写入的寄存器地址 IIC_WriteByte(data); // 数据 IIC_Stop(); delay_ms(10); // 等待写周期结束 }读EEPROM时注意先发一个伪写地址写方向再发重复起始和读方向地址uint8_t AT24C02_ReadByte(uint8_t addr) { uint8_t data; IIC_Start(); IIC_WriteByte(0xA0); // 写方向 IIC_WriteByte(addr); // 要读的地址 IIC_Start(); // 重复起始 IIC_WriteByte(0xA1); // 读方向 data IIC_ReadByte(1); // 最后一个字节返回NACK IIC_Stop(); return data; }这套代码在STM32F1/F4上实测可以直接跑逻辑简单也方便调试。真要在工程里用可以把这些函数封装成独立文件读写接口对上就行。4.3 STM32硬件IIC外设的配置要点硬件IIC外设的优势是省CPU、响应快、能处理多主仲裁和DMA传输。但用之前要确认自己用的库版本跟芯片匹配。如果坚持用HAL库的硬件IIC有几个配置点必须注意第一是GPIO配置SCL和SDA都要配置成开漏输出、速度要够GPIO_Speed_HIGH同时使能内部上拉或者外部接好上拉电阻。第二是IIC的时钟配置要根据系统时钟算好I2C时钟分频让SCL稳定在设定的频率。第三是使能I2C外设的应答功能默认就是ENABLE但要确认在读取最后一个字节之前要关闭应答再产生停止条件否则主机会多收一个字节。HAL库使用I2C的阻塞式传输HAL_I2C_Master_Transmit和HAL_I2C_Master_Receive是最简单的。但有个小坑HAL库I2C的接收函数你传入的缓冲长度如果是1它会在收完这1个字节后自动发NACK并产生STOP但如果你传入的缓冲长度大于1它会在除最后一个字节外的所有字节都自动回ACK。这一点不搞清楚读EEPROM很容易数据错位。4.4 IO扩展芯片实操PCF8574挂4个就能扩展16路热搜词里有“iic io扩展芯片”这块确实是IIC在项目里的高频应用。最典型的器件是PCF8574一个8位IO扩展芯片IIC地址是0x20加上三个引脚A0/A1/A2的电平设置也就是0x20到0x27一片能扩展8个IO一个IIC总线上最多挂8片扩展出64个IO。PCF8574的用法极其简单就一个寄存器。写数据主设备发设备地址一个字节数据这个字节的每一位对应P0到P7的输出电平。读数据主设备发设备地址读方向从设备把当前引脚电平发出来。下面是一个控制4片PCF8574的示例把总线上的8个地址全部扫描一遍uint8_t pcf_addr[] {0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27}; for (int i 0; i 8; i) { if (IIC_WriteByte(pcf_addr[i]) 0) // 返回0表示有ACK { printf(PCF8574 found at 0x%02X\n, pcf_addr[i]); IIC_Stop(); } }扫描总线的思路是非常通用的调试手段遇到任何IIC设备先写一个扫描程序把所有地址试一遍能确认设备到底在哪个地址上、是否正常应答。这里要注意扫描时向每个地址发一个字节通常用写方向发0x00如果返回ACK就说明这个地址有设备响应。PCF8574输出的时候初始状态要提前想好。它上电后所有引脚是高电平而不是低电平。很多控制继电器的电路默认高电平是“断开”那没问题但如果你接的是灌电流负载上电瞬间所有IO都是高会把负载全部误触发。项目上遇到过控制LED灯条上电就全亮后来在初始化函数里先写一个所有引脚置低的值才解决。5. IIC通信异常的快速定位波形台阶、死锁、ACK丢失排查5.1 波形出现台阶先别急着怪代码“IIC波形出现台阶”是很多人在示波器上看到的异常现象我最早遇到时以为是逻辑分析仪采样率不够或者探头接触不良后来才发现是实实在在的信号问题。波形台阶的核心原因是多个设备同时驱动总线或者总线电容分布不均匀。具体场景有两种。一种是总线上某个设备在SCL低电平期间释放SDA的同时另一个设备还没释放完两边一先一后放开SDA的电压曲线就会出现两段爬升形成台阶。另一种是总线电容过大且分布不均信号上升沿被不同位置的大电容“拖”出阶梯状尤其是连接线较长的时候。识别台阶后先确认是不是上升沿的RC效果在示波器上量一下台阶发生的位置如果每个通信波形里台阶都出现在同一个位置多半是跟某个从设备的行为有关如果台阶出现在长距离走线的末端则是电容不均导致的。前者重点查从设备的上电时序和复位是否干净后者优先减小上拉电阻、缩短线缆或降低速率。还有一种台阶是SDA电平在SCL高电平期间不稳定这就不只是“台阶”了而是数据冲突。IIC规范要求SCL高期间SDA稳定如果这个期间SDA还在跳变从机可能把它误识别成START/STOP条件整个通信就崩了。出现这种情况多半是软件模拟IIC时SDA翻转和SCL翻转的先后顺序没按协议来。5.2 SDA一直为低总线死锁的恢复方法现象是通信一次之后SDA一直被拉低发什么命令都没反应总线像被锁死了。死锁的典型成因是主设备在通信中途异常复位看门狗、断电、程序卡死从设备还维持着对SDA的低电平输出。此时主设备认为自己刚上电一切都正常但SDA已经被从机拽住起始条件根本发不出来因为SDA无法从高跳低。恢复办法有两个。第一个是“9个时钟法”让主机连续产生9个SCL时钟脉冲并且在这期间保持SDA为高。开漏结构下第9个时钟后从机状态机会被复位释放SDA。注意9个时钟期间SDA必须保持高拉低会变成普通数据起不到复位效果。第二个办法是给从机断电复位或者直接发一个START信号——如果软件上能检测到SDA异常为低先拉低SCL做几个空转时钟。这个问题的根源是协议机制里没有“看门狗”的概念双方都不知道对方什么时候会突然中断。工程上的经验是主设备代码里加IIC超时检测任何等待SCL拉高的循环都要有超时退出机制不能死等。同时通信的最后一帧一定要发STOP不要在数据发一半时断电或者复位。5.3 ACK丢失和通信飘忽不定常见问题速查现象可能原因优先级排查方向寻址后无ACK设备地址错误用总线扫描程序逐一尝试地址寻址后无ACK上拉电阻没接或阻值过大示波器看SDA上升沿是否过于平缓寻址后无ACK从设备未上电或复位异常量从设备的VCC和RESET引脚电平读写中途丢失ACK速率过快不满足从设备时序降到100kHz试一下能稳定说明就是时序问题通信时好时坏电源纹波或地线电位漂移共地是否可靠VCC是否稳定通信时好时坏SCL干扰线缆过长示波器看SCL高电平期间有没有毛刺数据错位读操作少了NACK确认最后一个字节发的是NACK数据错位主机发了多余时钟查软件模拟IIC的循环次数是否多跑一拍通信飘忽不定是最让人头疼的。我调试过一个项目传感器数据十次里有两三次是错的程序逻辑反复检查都没毛病。后来用示波器看波形发现SCL高电平时SDA上有微弱的毛刺排查到是传感器线缆和电源线走得太近串扰过来的。把线缆分开、加磁环之后问题消失。这种问题在纯逻辑层面永远找不到所以IIC调试一定要养成“先看波形、再改代码”的习惯。5.4 用逻辑分析仪快速定位IIC异常调试IIC示波器不是每次都方便特别是出差、现场调试的时候一个USB逻辑分析仪比示波器轻便得多采样率也不用太高——测100kHz的IIC采样率4MHz足够。逻辑分析仪抓IIC波形的要点是触发条件设成SDA下降沿起始条件采样深度要能覆盖完整的一帧。抓到之后先看起始条件是否合法然后看地址字节对不对再看ACK位是否存在最后数一下时钟个数——整个帧的SCL脉冲数是不是“字节数乘以9”再加起始停止的边沿。多数时候看一眼波形就能定位问题。如果是软件模拟IIC还可以在代码里加上调试断言在关键步骤翻转一个空闲GPIO用示波器观察这个GPIO和IIC波形的对应关系。比如在发送起始条件前、发送停止条件后各翻转一次引脚就能确认软件流程到底走到哪一步。这个方法看起来土但在定位“卡死在哪里”的时候效率比在代码里插打印高得多。6. 我在实际项目里的一些习惯和忠告做嵌入式这些年IIC相关的调试经验攒了不少最后分享几条个人觉得特别管用的做法。第一所有IIC通信代码必须带超时。不管是用硬件外设还是软件模拟等待ACK或者等待SCL拉高的循环一定要设一个超时上限。IIC总线上什么幺蛾子都可能出现没有超时的代码一旦总线卡死整个系统就跟着完蛋。这个超时不说精确到微秒随便一个几百上千次的计数都行。第二新板子第一次上电先别写业务代码先把总线扫描跑起来。这能验证设备地址、上拉电阻、硬件焊接是否正常也能确认多个设备的地址是否有冲突。扫描代码几十行能省后面一整天的调试时间。第三调试阶段先跑100kHz稳定了再往上提。很多项目功能验证阶段就跑400kHz出了问题就开始怀疑时序、怀疑代码结果降速一试全部正常。速率这个东西能做多快得在保证最恶劣供电、最长线缆的前提下才算数。第四软件模拟IIC和硬件IIC各有用武之地。硬件IIC适合大数据量传输和主中断驱动的场景软件模拟IIC适合时序要精确控制、设备行为不规范的场景。两个都掌握根据情况选才是工程上该有的态度。我自己的项目里凡是只需要温度、湿度这类小数据传感器的优先用软件模拟出问题好查凡是Flash、触摸屏这类大数据量的才开硬件外设。