“I2C”的那些坑

“I2C”的那些坑

一般情况下, i2c 设备焊接没什么问题,按照设备手册一步步来,基本上就顺风顺水能够用起来。如果这么一个简单的东西,有时候想要的结果死活不出来,反复的检查问题的原因,查询解决办法,核查设备的数据手册,甚至发送和接收的每一条命令与数据都知道是什么意思,仍然无法解决问题,那该怎么办呢?

本文主要针对 i2c 设备,讲解如何解决 i2c 设备主机与从机直接无法正常数据交互的问题,侧重点是针对硬件设计不太合理、i2c 设备设计不标准导致总线故障的情况,并且通过分析现象,提出解决方案。对于在设备初始化中,没有设置相应的寄存器或者发送命令,而导致的无法获取想要的数据情况,不作详细介绍。

1 i2c 基本用法

i2c 总线是一种简单、双向二线制同步串行总线。所有主机在 SCL 线上产生它们自己的时钟来传输总线上的报文,SDA 线传输每个字节必须为 8 位,每次传输可以发送的字节数量不受限制,每个字节后必须跟一个响应位。在空闲状态时,SCL 与 SDA 均为高电平。

通常一些低功耗 i2c 设备,芯片引脚使用上拉输出即可满足与其正常数据交互,还有一些 i2c 设备,则需要在总线上外加一个上拉电阻,此时相应的 I/O 配置成开漏输出,其他的按照芯片手册进行标准配置。

2 硬件问题汇总

如果正常拉高、拉低的情况下,依然无法正常读取数据。通常建议,根据负载电流更换小阻值的电阻。

如果需要详细知道原因,就具体查询 i2c 设备电气特性。大多数 i2c 设备电气特性,大致下图所示
在这里插入图片描述
通常这块内容在 i2c 设备电气特性这一块,主要讲解电平拉高拉低的最长时间、最短时间,以及处于高电平与电平的阈值与持续时间等等内容。

硬件设计,为了降低单片机的功耗与保护芯片引脚,在满足负载电流和负载电容相关要求的前提下,阻值设置通常比较大。如果同一个总线上挂载多个 i2c 设备, 即使在 I/O 口配置正确的前提下,也会导致驱动能力不足。

现象是拉高电压不足,在拉高、拉低过程中消耗时间过长。这两个问题通常还引起数据线与时钟线:拉高时,高电压持续时间过短;拉低时,低电压持续时间过短。用示波器抓取图形:从波形上看,显示是尖波、斜波、杂波等不符合 i2c 设备电气特性的波形;从数据上看,数据线高电平持续时间过小 ,上升沿时间过长 ,下降沿时间过长等等数据超出设备电气特性的有效值。典型杂波图,如下所示:
在这里插入图片描述
如果出现此类异常,建议更换小一点的电阻,用来增强总线驱动能力,提高电平转换速度。应当注意的是每个 MCU 的耐受电流不一样,减小电阻应避免超过相应引脚承受电流的最大值。

3 SDA 死锁

如果i2c 设备的数据偶尔能够正确获取,但是仍然会在总线发送数据或者命令的时候,爆出总线读写错误,那么有可能遇到下面的死锁问题,死锁时候,就是数据线被拉低,主机无法拉高。死锁一般发生在从机上,且为数据线死锁。因为i2c总线是共享的,如果需要确定,是否是从机死锁,可以参照下面两幅图,串联电阻进行测试

在这里插入图片描述
如上图所示,如果从机死锁,即从机拉低电平,此时检测到的电压为1/3 Vcc。
在这里插入图片描述
如上图所示,如果主机死锁,即主机拉低电平,此时检测到的电压为 1/11 Vcc。依据这个原理,可以准确判定死锁的具体位置,多个传感器依据类似方式进行定位。

3.1 反复重启导致死锁

3.1.1 现象

如果设备需要反复重启,很有可能在从机设备返回数据的时候,SDA被锁住。具体原因是从机设备在回数据,还没有发送完成,主机时钟消失,从机等待时钟信号, MCU重启,如果从机设备的电源没有复位,从机继续等待 MCU 时钟信号,数据一直被钳住,总线无法完成数据交互。

3.1.2 解决方式

解决重启导致总线死锁,一种方式可以如同 rt-thread 驱动解决方式一样,在系统复位的时候,提供9个时钟信号,解初总线死锁;另一种是在按下复位键初始化的时候,给从机设备电源断电重启,这个需要引脚控制。

3.1.3 9 个时钟信号

i2c 设备进行读写操作的过程中,在从机钳住总线的期间,MCU 异常复位,会导致 SDA 死锁,异常产生出现在俩个阶段:从机响应阶段、从机发送数据阶段。下面将针对这两种异常,对时钟信号进行解释,并且总结其他原因,得出结论。

a) 从机响应阶段

MCU 在开始信号后发送地址,得到从机设备响应,准备开始返回数据,在这个时候,从机将 SDA 信号拉为低电平,如果 MCU 异常复位,会导致总线上 SCL 停止发送时钟信号,从机等待 MCU 的时钟信号,产生钳住并且拉低 SDA 的现象。如果想要解锁 SDA,从机需要 9 个时钟信号,使得从机完成响应,释放 SDA 。

(b) 从机发送数据阶段

如果从机响应完成了,开始给 MCU 返回数据。这个数据有八位,每一位都有可能为低,如果在数据低位,MCU 异常复位,停止发送时钟信号,从机就会等待 MCU 的时钟信号,产生钳住并且拉低 SDA 的现象。如果想要解锁 SDA,从机需要 1-8 个时钟信号,使得从机完成数据响应,释放 SDA 。

(c)其他情况

在从机一个 8 位数据发送完成后,等待 MCU 响应, 即使属于 MCU 的,从机不再钳住 SDA,没有时钟,数据交互停止。

在主机发送数据阶段,总线所有权在主机,主机异常,数据交互停止,总线释放。所以,这些情况下,不存在 SDA 死锁的情况。

d)结论
综上所述,解锁 SDA 从机最多需要 9 个时钟信号,也就是异常复位后,MCU 至少发送需要 9 个时钟信号,完成 i2c 总线的 SDA 解锁。所以,RT_Thread 为了避免此类问题的产生,在 i2c 驱动初始化,对总线进行判断,判断是否需要解锁,如果需要,就进行解锁,确保 i2c 设备不会因为这个问题导致数据交互失败。

3.2 多个 i2c 设备导致死锁

多 i2c 设备除了异常复位导致死锁,还会形成相互干扰的问题,一般情况下,不会把同种从机地址挂在同一条总线上,但除此之外,有些 i2c 设备设计不是按照标准的 i2c 总线协议设计,在 i2c 总线共享的前提条件下,有的设备只要总线上从机地址就会有响应。这样由于从机的错误响应,使得各个 i2c 总线异常,甚至钳住总线,导致 I2C 总线进人一种死锁状态。

解决方式,这样的不标准i2c设备,单独使用一个总线,避免干扰,或者单独一个独立引脚,控制电源。

来源:RT-Thread
原文在此

<think>嗯,用户这次想了解I2C接口使能的方法。结合之前的对话历史和提供的四个引用内容,我需要整理出具体操作层面的指导。 用户可能正在调试嵌入式系统(比如STM32),因为引用[3][4]都涉及实际代码实现。从引用[1]可以确认I2C的基础特性——双线制和寻址机制,这是使能的前提;引用[2]提到I2C的同步半双工特性,暗示时序配置的重要性;引用[3]的HAL库代码展示了关键函数HAL_I2C_Mem_Read/Write,这是STM32使能I2C的核心;引用[4]则补充了OLED场景的应用验证。 用户没明确说平台,但引用[3][4]都是STM32案例,所以优先按此框架解答。需要覆盖三个层面:硬件使能(时钟和引脚)、软件配置(HAL库初始化)、功能验证(读写测试)。特别注意引用[3]中HAL_I2C_Mem_Write的调用细节:设备地址要加I2C_WR方向位,寄存器地址用I2C_MEMADD_SIZE_8BIT指定长度——这些实际代码示例比纯理论描述更有用。 考虑到用户可能遇到的,要强调GPIO复用模式和时钟使能常被遗漏(引用[3]的hi2c1实例暗示了时钟配置)。另外引用[1]说的多主机特性提示可补充总线仲裁机制,但用户当前问题聚焦“使能”,暂时不必展开。 </think>根据提供的参考资料,I2C接口使能的方法主要涉及硬件配置和软件初始化,以下是具体步骤和要点: ### 🔧 一、硬件配置使能 1. **时钟使能** 使能I2C控制器和对应GPIO的时钟(以STM32为例): ```c __HAL_RCC_I2C1_CLK_ENABLE(); // 启用I2C1控制器时钟 __HAL_RCC_GPIOB_CLK_ENABLE(); // 启用SCL/SDA所在GPIO组时钟 ``` 2. **引脚复用配置** 将SCL(时钟线)和SDA(数据线)引脚配置为复用模式: ```c GPIO_InitTypeDef GPIO_InitStruct = {0}; GPIO_InitStruct.Pin = GPIO_PIN_6 | GPIO_PIN_7; // 以PB6=SCL, PB7=SDA为例 GPIO_InitStruct.Mode = GPIO_MODE_AF_OD; // 开漏复用模式 GPIO_InitStruct.Pull = GPIO_PULLUP; // 上拉电阻使能 GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; GPIO_InitStruct.Alternate = GPIO_AF4_I2C1; // 复用为I2C1功能 HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); ``` > 📌 关键点:必须使用开漏模式(`GPIO_MODE_AF_OD`)并启用上拉电阻,确保总线电平兼容性[^1][^3]。 --- ### 💻 二、软件初始化使能 1. **I2C参数配置** 设置通信速率、地址模式等(以STM32 HAL库为例): ```c hi2c1.Instance = I2C1; hi2c1.Init.ClockSpeed = 100000; // 100kHz标准模式 hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2; // 占空比 hi2c1.Init.OwnAddress1 = 0; // 主模式可不设地址 hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT; // 7位地址模式 hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE; hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE; hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE; HAL_I2C_Init(&hi2c1); // 初始化I2C ``` 2. **验证通信** 通过读写设备寄存器测试总线是否使能成功(参考EEPROM操作[^3]): ```c uint8_t test_addr = 0x00; uint8_t test_data[2] = {0x55, 0xAA}; // 写入测试 HAL_I2C_Mem_Write(&hi2c1, 0xA0, test_addr, I2C_MEMADD_SIZE_8BIT, test_data, 2, 100); // 读取验证 HAL_I2C_Mem_Read(&hi2c1, 0xA0, test_addr, I2C_MEMADD_SIZE_8BIT, test_data, 2, 100); ``` --- ### 三、常见问题排查 1. **总线锁死** - 检查SCL/SDA是否被意外拉低,可尝试短时断电复位。 - 使用逻辑分析仪捕获波形,确认起始/停止信号是否正常[^2]。 2. **从设备无响应** - 确认设备地址正确(7位地址需左移1位,如0x50→0xA0写/0xA1读)[^3]。 - 检查上拉电阻(通常4.7kΩ)是否已接入。 3. **初始化失败** - 检查时钟配置:`__HAL_RCC_I2C1_CLK_ENABLE()`必须调用。 - 确认GPIO复用映射正确(参考芯片手册AF表)。 --- ### 📚 补充说明 - **多主机支持**:I2C支持多主机仲裁,需在初始化时配置`hi2c1.Init.OwnAddress`为主机地址[^1]。 - **速率选择**:标准模式(100kHz)、快速模式(400kHz)需匹配设备支持能力。 - **HID设备特殊处理**:若为触摸板等HID设备,需确保系统加载对应驱动(如Linux的`i2c_hid`模块)[^2]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值