STM32 IIC通信从入门到精通:硬件配置、软件模拟与深度调试实战
1. 项目概述:从“能用”到“精通”的IIC之路
搞嵌入式开发,尤其是玩STM32的,IIC(Inter-Integrated Circuit)总线绝对是个绕不开的坎。它简单,两根线(SDA数据线、SCL时钟线)就能搞定多设备通信;它也“磨人”,时序要求严格,干扰处理不当就各种通信失败。网上教程很多,但要么只讲理论,看得云里雾里;要么只给代码,出了问题不知道怎么调。今天,我就结合自己这些年踩过的坑、调过的板子,把STM32的IIC通信从理论到实例,掰开揉碎了讲清楚。目标是让你不仅能把代码跑起来,更能看懂每一行代码背后的逻辑,遇到波形异常、数据出错时,能像个老手一样,从容地拿出逻辑分析仪,精准定位问题所在。无论你是刚接触STM32的新手,还是想深入理解IIC底层机制的老鸟,这篇干货都能给你带来实实在在的帮助。
2. IIC协议核心理论:不只是两根线那么简单
很多人觉得IIC协议简单,看个起始、停止、应答的时序图就以为掌握了。实际上,隐藏在简单物理连接背后的,是一套完整的通信规则和状态机。理解透了,写代码和调试才能心中有数。
2.1 物理层与电气特性:为什么需要上拉电阻?
IIC总线是开漏(Open-Drain)或开集(Open-Collector)输出结构。这意味着总线上的设备只能主动将信号线拉低(输出0),而不能主动拉高(输出1)。总线的高电平状态,完全依靠连接在SDA和SCL线上的上拉电阻(Pull-up Resistor)将电压拉至VCC。
这就引出了第一个关键问题:上拉电阻阻值怎么选?这不是随便找个4.7kΩ或10kΩ贴上去就完事的。阻值选择是总线速度、总线电容和电源电压之间的权衡。
- 阻值太小(如1kΩ):电流大,拉高速度快,有利于高速通信,但会增加功耗,并且在设备拉低总线时,可能因电流过大而超出IO口的灌电流(Sink Current)能力,导致低电平电压抬升,甚至损坏IO口。
- 阻值太大(如10kΩ以上):功耗低,但RC时间常数大,信号上升沿变缓。在高速模式(如400kHz Fast-mode)或总线负载电容较大(线长、设备多)时,过慢的上升沿可能导致建立时间(Setup Time)或保持时间(Hold Time)不满足要求,通信出错。
一个经验公式是:Rp(min) = (VCC - VOL(max)) / IOL,其中VOL(max)是标准规定的最大低电平电压(通常0.4V),IOL是主设备IO口的最大低电平输出电流(查数据手册)。Rp(max)由总线允许的最大上升时间(tr)和总线电容(Cb)决定:Rp(max) ≤ tr / (0.8473 * Cb)。对于STM32常见的3.3V系统,总线电容在100-400pF之间,标准模式(100kHz)下,4.7kΩ是个比较通用的选择;快速模式(400kHz)下,可能需要减小到2.2kΩ甚至1.5kΩ,并确保PCB走线尽量短。
注意:STM32的硬件IIC模块(I2C外设)对时序要求非常严格,特别是Fast-mode Plus(1MHz)。如果上拉电阻不合适,波形畸变,极易导致硬件IIC模块工作异常,出现NACK(无应答)或总线忙(BUSY)标志位无法清除的问题。此时,用示波器或逻辑分析仪观察SDA和SCL的上升沿波形至关重要。
2.2 协议层与数据帧:一次完整的对话
一次标准的IIC数据传输,就像一次结构清晰的对话。我们以主设备(Master,通常是STM32)向从设备(Slave,如EEPROM AT24C02)写入一个字节为例,拆解这个过程:
- 起始条件(S):主设备在SCL为高电平时,将SDA从高拉低。这是一个“广播”信号,告诉总线上所有设备:“注意,我要开始说话了”。
- 发送从机地址(7位) + 读写位(1位):主设备先发送7位从机地址(例如AT24C02的地址是0xA0的前7位),紧接着发送1位读写控制位(0表示写,1表示读)。这相当于喊话:“地址是0x50的设备(AT24C02),我准备给你写数据”。
- 从机应答(ACK):主设备释放SDA线(输出高阻态),并在第9个时钟脉冲期间,检测SDA是否被从机拉低。如果被拉低,表示从机应答(ACK):“我在,请讲”。如果SDA保持高电平,则是无应答(NACK),表示寻址的设备不存在或忙。
- 发送数据字节:主设备在SCL低电平时改变SDA数据,在SCL高电平时保持数据稳定,从机在SCL高电平期间采样。依次发送8位数据。
- 从机应答(ACK):每发送完一个字节,主设备都会在第9个时钟检测从机的ACK。对于写操作,从机每成功接收一个字节,都应回复ACK。
- ……(重复步骤4-5发送后续字节)
- 停止条件(P):主设备在SCL为高电平时,将SDA从低拉高。表示:“我说完了,本次对话结束”。
读操作的过程类似,区别在于发送完“地址+读位(1)”后,主从角色在数据线上会切换:主设备变成接收方,负责在每个字节后发送ACK或NACK;从设备变成发送方。
实操心得:很多初学者用软件模拟IIC(GPIO模拟时序)没问题,一换硬件IIC就失败,往往卡在地址相位。硬件IIC模块在发送地址时,会自动处理读写位。例如,你要读取地址为0xA0的设备,调用HAL库函数
HAL_I2C_Mem_Read时,传入的设备地址通常是0xA0 >> 1,即0x50。这是因为库函数内部会帮你把7位地址左移1位,并补上读写位。如果你错误地传入了0xA0,硬件IIC会发出0xA0(二进制10100000),这会被从设备解读为地址0x50(1010000) + 写操作(0),与你的读意图不符,导致NACK。
2.3 时钟同步与仲裁:总线上的秩序
当有多个主设备时,IIC协议通过时钟同步和仲裁机制来避免冲突。
- 时钟同步:所有主设备都会产生自己的SCL时钟。如果某个设备的SCL输出为低电平,它会强制将总线SCL拉低。SCL的高电平周期由时钟最慢的设备决定,低电平周期则由时钟低电平期最短的设备决定。这保证了所有设备都能跟上通信节奏。
- 仲裁:当多个主设备同时开始传输时,它们会一边发送数据(地址或数据),一边检测SDA线上的实际电平。如果某个主设备发送了高电平(释放SDA),但检测到SDA线是低电平(被其他设备拉低),它就意识到自己“输”了,会立即退出主模式,转为从模式,并继续监听总线,直到检测到停止条件。仲裁的过程不会破坏正在传输的数据。
对于单主系统(绝大多数STM32应用场景),我们不需要实现仲裁,但理解这个机制有助于明白为什么IIC是真正的多主总线,以及为什么在调试时,如果程序异常将IIC引脚配置为推挽输出并输出高电平,可能会“霸占”总线,导致其他设备无法通信。
3. STM32硬件IIC与软件模拟IIC的抉择
这是项目开始前必须做的关键决策。两种方式各有优劣,选对了事半功倍。
3.1 硬件IIC(I2C外设):高效但“娇气”
STM32的硬件IIC外设负责自动生成时序、处理起始停止条件、管理ACK/NACK、控制时钟拉伸等。你只需要配置好时钟速度、从机地址,然后操作数据寄存器(DR)和状态寄存器(SR)即可。
优势:
- 解放CPU:通信过程由硬件完成,CPU可以处理其他任务,适合在操作系统或复杂应用中提高效率。
- 时序精准:由硬件时钟驱动,时序严格符合标准,不受中断或其他任务影响。
- 支持高级功能:如时钟拉伸(Clock Stretching,从设备可以拉低SCL以要求主设备等待)、多主机仲裁、SMBus协议等。
劣势与坑点:
- 配置复杂:涉及时钟配置、滤波器设置、时序寄存器(如STM32的TRISE)计算,一个参数设错就可能不工作。
- 对硬件依赖大:如前所述,对上拉电阻、PCB布局、总线电容非常敏感。波形稍有畸变就容易失败。
- 历史遗留问题:早期STM32F1系列的硬件IIC设计有缺陷,在特定中断干扰下可能卡死,导致很多开发者对其“敬而远之”,转而使用软件模拟。但在F4、H7等后续系列中,这个问题已基本解决。
- 调试不直观:出错时,你需要去查一堆状态标志位(BUSY, MSL, BTF, ADDR, STOPF, NACKF等),对初学者不友好。
配置关键步骤(以STM32CubeMX+HAL库为例):
- 引脚配置:将对应引脚(如PB6/PB7, PB8/PB9)设置为I2C_SCL和I2C_SDA模式。务必确认引脚复用功能映射正确。
- 参数配置:
- Clock Speed:选择标准模式(100kHz)或快速模式(400kHz)。不要超过从设备支持的最高速度。
- Duty Cycle:快速模式下的SCL占空比,通常保持默认(2:1)。
- Own Address:如果STM32也要作为从机,才需要设置。一般做主机时设为0。
- General Call Recognition:一般禁用。
- No Stretch Mode:时钟禁止拉伸模式。如果从设备(如某些传感器)需要时钟拉伸,此处必须禁用。这是一个常见坑点,如果从设备拉低了SCL等待,而你开启了此模式,硬件IIC会认为超时并报错。
- 时序寄存器计算(重点):对于标准模式,需要配置
I2C_TRISE。公式为:TRISE = (I2Cclk频率 in MHz) + 1。例如,APB1时钟为42MHz,则TRISE = 42 + 1 = 43。CubeMX通常会帮你算好,但手动配置寄存器时千万别忘了。
3.2 软件模拟IIC(Bit-Banging):灵活且稳定
软件模拟IIC,即用两个普通GPIO,通过程序代码精确控制其高低电平变化来模拟出SDA和SCL的时序。
优势:
- 极强的移植性和灵活性:不依赖特定硬件外设,可以在任何有GPIO的MCU上运行。引脚可以任意指定。
- 调试友好:你完全控制时序,可以在任意位置插入延时或调试语句,便于定位问题。
- 规避硬件BUG:在怀疑硬件IIC不稳定时,用软件模拟可以快速验证是硬件问题还是程序问题。
- 时序可微调:可以针对特定“挑剔”的从设备,微调SCL高/低电平的保持时间,兼容性更强。
劣势:
- 占用CPU资源:通信期间CPU被完全占用,无法执行其他任务,在高速或大数据量传输时影响系统实时性。
- 时序易受干扰:如果被高优先级中断打断,可能导致时序延长,通信失败。需要关闭中断或精心设计。
- 实现多主机和仲裁困难:虽然可以实现,但复杂度高。
软件模拟的关键实现技巧:
// 定义IO操作(以推挽输出模式模拟开漏,实际需配合外部上拉) #define IIC_SDA_HIGH() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_SET); \ GPIOB->MODER &= ~(GPIO_MODER_MODER7); // 先设高,再切输入模式(等效释放总线) #define IIC_SDA_LOW() GPIOB->MODER |= (GPIO_MODER_MODER7_0); // 先切输出模式 \ HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_RESET) #define IIC_SCL_HIGH() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_SET) #define IIC_SCL_LOW() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_RESET) #define IIC_SDA_READ() HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_7) // 起始条件:SCL高时,SDA由高变低 void IIC_Start(void) { IIC_SDA_HIGH(); // 确保SDA为高 IIC_SCL_HIGH(); Delay_us(5); // 建立时间 IIC_SDA_LOW(); // 产生下降沿 Delay_us(5); IIC_SCL_LOW(); // 钳住总线,准备发送数据 } // 发送一个字节 void IIC_SendByte(uint8_t byte) { uint8_t i; for (i = 0; i < 8; i++) { if (byte & 0x80) IIC_SDA_HIGH(); else IIC_SDA_LOW(); byte <<= 1; Delay_us(2); IIC_SCL_HIGH(); // 在SCL高电平期间,数据必须稳定 Delay_us(5); IIC_SCL_LOW(); Delay_us(2); } // 释放SDA线,准备接收ACK IIC_SDA_HIGH(); // 读取ACK IIC_SCL_HIGH(); Delay_us(3); // ... 检测SDA是否为低 ... IIC_SCL_LOW(); }注意:软件模拟时,
Delay_us的精度和时长至关重要。太快可能从设备跟不上,太慢则影响效率。需要根据从设备数据手册要求的最小时序参数(如SCL低/高电平最小时间、数据建立/保持时间)来调整。最好用逻辑分析仪实测校准。
如何选择?
- 新手入门、调试阶段、从设备兼容性差:优先选择软件模拟IIC。它简单直观,能帮你快速建立对IIC通信过程的感性认识,并排除硬件配置问题。
- 产品量产、系统复杂、需要高总线利用率或多主机功能:必须攻克硬件IIC。一旦调通,其稳定性和效率是软件模拟无法比拟的。对于F1系列,如果担心老问题,可以查阅最新的勘误手册或使用经过社区验证的驱动库。
4. 实战案例:驱动OLED屏幕(SSD1306)与EEPROM(AT24C02)
我们通过两个最经典的例子,把理论落地。一个用于输出显示(OLED),一个用于数据存储(EEPROM),覆盖了读和写两种基本操作。
4.1 驱动IIC接口OLED屏幕(SSD1306)
SSD1306是一款常用的128x64像素OLED驱动芯片,通过IIC接口控制。它的地址通常是0x78(写)或0x79(读),对应7位地址0x3C。
硬件连接:
- STM32的IIC_SCL -> OLED的SCL
- STM32的IIC_SDA -> OLED的SDA
- OLED的VCC接3.3V/5V,GND接地。
- 务必在SDA和SCL上各接一个4.7kΩ上拉电阻到VCC。
软件驱动要点: SSD1306的通信分为命令(Command)和数据(Data)。每次传输需要先发送一个控制字节(Co),用于区分后续是命令流还是数据流。
- Co = 0x00:后续字节为命令。
- Co = 0x40:后续字节为显示数据(GDDRAM数据)。
因此,写命令和写数据的函数核心区别在于第一个字节。
// 使用硬件IIC (HAL库) 向SSD1306写命令 void OLED_Write_Cmd(uint8_t cmd) { uint8_t buf[2] = {0x00, cmd}; // 控制字节 + 命令字节 HAL_I2C_Master_Transmit(&hi2c1, OLED_ADDRESS, buf, 2, HAL_MAX_DELAY); } // 使用硬件IIC (HAL库) 向SSD1306写数据 void OLED_Write_Data(uint8_t data) { uint8_t buf[2] = {0x40, data}; // 控制字节 + 数据字节 HAL_I2C_Master_Transmit(&hi2c1, OLED_ADDRESS, buf, 2, HAL_MAX_DELAY); }初始化过程就是一系列命令的集合,用于设置对比度、显示模式、扫描方向、起始行等。网上有成熟的初始化序列代码,直接使用即可。
常见问题排查:
- 屏幕不亮:首先检查电源和GND。然后检查初始化序列是否完整发送成功。可以用逻辑分析仪抓取IIC总线波形,看是否有数据发出,从机是否回复ACK。
- 屏幕亮但乱码/花屏:大概率是初始化命令顺序或参数有误。特别是设置内存地址模式(Horizontal/Vertical/Page)、列地址和页地址范围的命令。确保你发送显示数据的起始地址(Set Column Address & Set Page Address)在屏幕有效范围内。
- 通信时好时坏:重点怀疑上拉电阻和电源。OLED模块本身功耗在显示内容变化时会有波动,如果电源线细或接触不良,可能导致电压跌落,影响IIC电平。可以在VCC和GND之间加一个10uF以上的电容稳压。
4.2 读写EEPROM存储器(AT24C02/04/08...)
AT24Cxx系列是常用的IIC接口EEPROM。以AT24C02(256字节)为例,其7位地址为0x50(二进制1010000)。A0, A1, A2引脚接地。
关键特性与操作要点:
- 页写(Page Write):AT24C02支持一次最多写入8字节(一页)。写入时,先发送设备地址(写)+ 字节地址(Word Address),然后连续发送数据。字节地址会自动递增,当到达页边界(地址尾字节为0x07, 0x0F等)时,会回滚到该页首地址。如果一次性写入超过一页的数据,超出的数据会覆盖本页开头的数据,造成“翻卷”。这是最常见的错误之一。
- 随机读(Random Read):要先执行一个“哑写(Dummy Write)”来设置内部地址指针。即先以写模式发送设备地址和要读取的字节地址,然后发送一个重复起始条件(Repeated Start),再以读模式发送设备地址,开始接收数据。
- 写入周期(Write Cycle Time):每次写入操作(字节写或页写)后,EEPROM需要最多5ms的时间将数据从缓存写入非易失单元。在此期间,它不会应答(NACK)。因此,连续两次写操作之间必须加入至少5ms的延时,或者通过轮询ACK来等待写入完成。
HAL库读写示例:
// 写入一个字节到指定地址 HAL_StatusTypeDef EEPROM_WriteByte(uint16_t addr, uint8_t data) { uint8_t buf[2] = {addr, data}; // AT24C02地址只有8位,更高容量的需要16位地址 // 设备地址左移1位,最低位为0(写) if (HAL_I2C_Master_Transmit(&hi2c1, EEPROM_ADDRESS, buf, 2, HAL_MAX_DELAY) != HAL_OK) { return HAL_ERROR; } HAL_Delay(5); // 等待写入周期完成,必须加! return HAL_OK; } // 从指定地址读取一个字节 HAL_StatusTypeDef EEPROM_ReadByte(uint16_t addr, uint8_t *data) { // 先发送要读取的地址(哑写) if (HAL_I2C_Master_Transmit(&hi2c1, EEPROM_ADDRESS, (uint8_t*)&addr, 1, HAL_MAX_DELAY) != HAL_OK) { return HAL_ERROR; } // 然后启动读操作 if (HAL_I2C_Master_Receive(&hi2c1, EEPROM_ADDRESS, data, 1, HAL_MAX_DELAY) != HAL_OK) { return HAL_ERROR; } return HAL_OK; }实操心得:对于容量大于256字节的EEPROM(如AT24C256,32K字节),其地址是16位的。在发送地址时,需要先发送地址的高8位,再发送低8位。同时,设备地址中包含了部分页地址位(A2, A1, A0和P1, P0),需要仔细阅读数据手册。使用HAL库的
HAL_I2C_Mem_Write和HAL_I2C_Mem_Read函数可以简化这个过程,它们会自动处理地址的发送。
5. 高级话题与深度调试技巧
当你掌握了基础读写后,下面这些进阶内容能让你在复杂场景下游刃有余。
5.1 时钟拉伸(Clock Stretching)的处理
某些从设备(如一些低速传感器、RTC芯片)在处理数据时,可能需要主设备等待。它们会通过拉低SCL线来实现,这就是时钟拉伸。主设备必须检测到SCL被拉低,并等待其被释放(变高)后才能继续。
- 软件模拟IIC:在SCL输出高电平后,需要将SCL引脚切换为输入模式,并循环检测其电平,直到变为高电平。这相当于增加了一个“等待”环节。
void IIC_SCL_High_and_Wait(void) { SCL_GPIO_PORT->MODER &= ~(GPIO_MODER_MODERx); // 切换为输入模式 while(!(SCL_GPIO_PORT->IDR & GPIO_PIN_x)); // 等待从设备释放SCL SCL_GPIO_PORT->MODER |= (GPIO_MODER_MODERx_0); // 切换回输出模式 } - 硬件IIC(STM32):硬件IIC外设默认支持时钟拉伸。你只需要确保在I2C配置中不启用“No Stretch Mode”(时钟禁止拉伸模式)。当从设备拉伸时钟时,硬件IIC的SCL线会被拉低,时钟生成器暂停,直到检测到SCL被释放。HAL库的通信函数内部已经处理了超时,如果从设备拉伸时间过长,可能会返回
HAL_TIMEOUT错误。此时需要适当增加超时参数。
5.2 多主机与仲裁实践
在真正的多主机系统中(如两个STM32共享一条IIC总线访问同一个传感器),仲裁逻辑由硬件自动处理。但软件上需要注意:
- 总线状态检测:在尝试成为主设备并发送起始条件前,应先检查总线是否空闲(BUSY标志位)。HAL库提供了
HAL_I2C_IsDeviceReady函数,但其主要用来探测从设备,判断总线忙闲更直接的是检查I2C_ISR寄存器中的BUSY位。 - 错误恢复:仲裁失败后,硬件IIC会自动从主模式切换到从模式,并可能设置一些错误标志。你的程序需要检测这些标志(如ARLO,仲裁丢失),并执行错误恢复程序,通常包括清除标志、重新初始化IIC外设等。
- 软件设计:需要设计一套应用层的总线访问协议(如令牌环、优先级调度)来减少冲突,因为硬件仲裁虽然能防止数据破坏,但频繁的仲裁失败会极大降低总线效率。
5.3 逻辑分析仪:终极调试利器
当通信异常,而你又百思不得其解时,逻辑分析仪是你的“眼睛”。一个几十块钱的USB逻辑分析仪(配合Sigrok/PulseView软件)就足够应对IIC调试。
使用步骤:
- 连接:将分析仪的通道0和通道1分别连接到IIC总线的SCL和SDA,并共地。
- 设置:在软件中设置采样率(1MHz足够),触发方式(可设为下降沿触发,抓起始条件)。
- 抓取波形:运行你的STM32程序,开始抓取。
- 分析:
- 看起始和停止:是否有完整的起始(S)和停止(P)信号?
- 看地址和ACK:发送的从机地址是否正确?从机是否回复了ACK(第9个时钟周期SDA为低)?如果NACK,是地址错、设备不存在还是设备忙?
- 看数据:发送或接收的数据字节是否与你的代码预期一致?
- 看时序:测量SCL高低电平时间、数据建立/保持时间是否满足从设备数据手册要求(通常纳秒级,逻辑分析仪精度可能不够,但能看个大概)。
- 看干扰:总线上是否有异常的毛刺?SDA线在非切换期是否稳定?
典型问题波形:
- ACK位为高(NACK):波形显示第9个时钟脉冲期间,SDA线仍为高电平。原因:地址错误、从设备未上电、从设备忙(如EEPROM在写周期内)、总线冲突。
- SCL被持续拉低:波形显示SCL线长期为低电平。原因:某个设备(可能是主也可能是从)崩溃,将SCL引脚锁死在低电平;或者从设备正在进行时钟拉伸但未释放。这是导致总线“死锁”的常见原因。解决方案是尝试发送几个额外的时钟脉冲(软件模拟可以做到),或者重启整个IIC外设。
- 波形上升沿缓慢:SDA或SCL从低到高的变化是一条斜线,而不是陡峭的上升沿。原因:上拉电阻过大或总线电容过大。需要减小上拉电阻阻值或缩短走线。
掌握逻辑分析仪的使用,是嵌入式工程师调试通信问题的必备技能。它能将抽象的代码执行,转化为直观的电气信号,让你对通信过程有最直接的认识。
6. 常见问题排查速查表
我把调试IIC时最常见的问题、可能原因和排查方向整理成下表,方便你快速对照解决。
| 问题现象 | 可能原因 | 排查步骤 |
|---|---|---|
| 初始化失败,HAL_I2C_Init返回错误 | 1. I2C时钟未使能。 2. 引脚复用配置错误。 3. 时序参数(TRISE等)计算错误。 | 1. 检查__HAL_RCC_I2Cx_CLK_ENABLE()是否调用。2. 核对数据手册,确认所用引脚是否支持I2C复用。 3. 使用CubeMX重新生成代码,或手动核对时序寄存器值。 |
| 发送数据后,一直卡在HAL_BUSY或超时 | 1. 总线被锁死(SCL/SDA被意外拉低)。 2. 从设备无响应或损坏。 3. 上拉电阻缺失或阻值过大。 4. 硬件IIC的“时钟拉伸禁止”模式与从设备冲突。 | 1.首先用逻辑分析仪或示波器看波形! 2. 检查SCL/SDA电压,看是否被拉低。 3. 确认上拉电阻已焊接,尝试减小阻值(如换为2.2kΩ)。 4. 检查I2C初始化配置,关闭时钟拉伸禁止模式。 |
| 能收到ACK,但数据错误 | 1. 软件模拟IIC的延时时间不准确。 2. 硬件IIC时钟速度设置过快,从设备跟不上。 3. 电源噪声干扰。 4. 代码中数据字节顺序处理错误。 | 1. 用逻辑分析仪测量SCL频率和占空比,调整延时。 2. 降低I2C时钟速度(如从400kHz降到100kHz)。 3. 在MCU和从设备电源引脚就近加退耦电容(0.1uF)。 4. 核对数据手册,确认多字节数据(如16位传感器数据)的高低字节顺序。 |
| 读写EEPROM时,只能操作前256字节 | 对于容量大于256字节的EEPROM,需要发送16位地址,但代码只发送了8位地址。 | 确认EEPROM型号。使用HAL_I2C_Mem_Write/Read函数,它们支持16位内存地址。或者手动将16位地址拆分为两个字节,先发高8位,再发低8位(注意器件地址页位)。 |
| 连续写入EEPROM时,后面的数据覆盖前面的 | 发生了“页写翻卷”。写入的字节序列跨越了页边界。 | 计算页大小(AT24C02是8字节)。在写入函数中做边界检查,如果本次写入会跨页,则分成两次页写操作。 |
| 作为从机时无法被主机寻址 | 1. 自身从机地址配置错误。 2. 未使能从机模式或相关中断。 3. 地址匹配逻辑(如7位/10位地址)设置错误。 | 1. 核对I2C_OAR1寄存器中设置的自身地址。2. 确保调用了 HAL_I2C_EnableListen_IT等函数使能从机监听。3. 检查地址长度配置位(ADDMODE)。 |
最后,分享一个我调试硬件IIC的“笨”办法但非常有效:当怀疑是硬件IIC配置或硬件问题时,我会迅速用同一个MCU的另外两个GPIO口,写一个最简单的软件模拟IIC驱动,去操作同一个从设备。如果软件模拟成功了,那问题一定出在硬件IIC的配置、时序或硬件电路(上拉电阻)上;如果软件模拟也失败,那问题很可能在从设备、电源或连接上。这个方法能帮你快速定位问题的大方向。
