STM32 I2C总线锁死原因及解决方法

本文介绍STM32 I2C总线锁死原因及解决方法。

在使用STM32 I2C总线操作外设时,有时会遇到I2C总线锁死(I2C总线为Busy状态)的问题,即便复位MCU也无法解决,本文介绍其锁死的原因和解决方法,并给出相应的参考代码。

1.故障现象

在I2C总线锁死时,使用示波器测量发现,SCL为高电平,SDA为低电平。

1)MCU操作I2C总线(读/写)时复位MCU(通过复位按键操作)比较容易再现。

2)MCU操作I2C总线(读/写)时,强制将SDA拉低(用金属摄子夹到地,并持续一段时间)会再现。

3)设想,MCU操作I2C总线过程中,SDA受外界干扰(毛刺)被拉低,也可能导致I2C总线锁死。

2.原因

1)I2C总线被设计成多主机可共享总线,这会导致总线竞争,主设备判断当前总线被占用是根据SDA线为低来判断的。当主设备检测到总线被占用,则指示总线忙,并无法操作总线。

2)主设备操作从设备(读/写)时,复位主设备,如果恰好从设备处于ACK状态(SDA拉低)或回复主设备数据位0,那么在复位完成,主设备重新接管总线时,会错误的认为总线忙,因为此时从设备并未复位,SDA仍然被拉低。从设备等待主设备拉低SCL取走ACK或者数据位0,而主设备等待从设备释放SDA。主设备和从设备互相等待,进入死锁状态。值的注意的时,对于故障现象2),STM32 I2C内部似乎有超时机制,如果SDA被拉低持续一段时间,则无法恢复。

3.解决方法

1)硬件复位

直接硬件复位外部从设备,比如通过MOS管软开关从设备电源,或通过外部设备硬件复位脚(从设备有才行)复位。

2)软件复位

情况1:

出现I2C总线锁死时正好外设回复数据位0,则需经历小于9个SCL时钟,从设备会释放SDA。

情况2:

出现I2C总线锁死时正好外设ACK,则经历9个SCL时钟,从设备会释放SDA。

综合情况1,2可知,通过软件复位解决时,当检测到总线锁死(BUSY状态),可以生成9个SCL时钟,并不断检测SDA引脚电平状态,若SDA被释放(为高)则退出,主机重新初始化I2C总线。Software Rest如下图。

I2C-bus specification中:

3)某些I2C缓冲器提供I2C总线错误恢复功能(如LTC4307)。

4.参考代码

参考代码如下(这里以STM32F4xx平台为例,其它平台类似):

DrvI2C1.h:

#ifndef __DRV_I2C1_H
#define __DRV_I2C1_H


#ifdef __cplusplus
 extern "C" {
#endif 
     

#include "datatype.h"
#include "stm32f4xx_hal.h"


#define I2C1_SCL_GPIO_PORT     (GPIOB)
#define I2C1_SCL_PIN           (GPIO_PIN_6)

#define I2C1_SDA_GPIO_PORT     (GPIOB)
#define I2C1_SDA_PIN           (GPIO_PIN_7)


extern I2C_HandleTypeDef hi2c1;


extern int32_t I2C1_Init(void);
     
     
#ifdef __cplusplus
}
#endif




#endif

DrvI2C1.c:

#include "DrvI2C1.h"


I2C_HandleTypeDef hi2c1;


static void I2C1_MspInit(I2C_HandleTypeDef* i2cHandle);
static void I2C1_MspDeInit(I2C_HandleTypeDef* i2cHandle);
static BOOL I2C1_Unlock(void);
static void I2C1_SetPortODOutput(void);


int32_t I2C1_Init(void)
{
    if (!I2C1_Unlock())
    {
        DbgPrint("I2C1 unlock failed!\r\n");
    }

    if (HAL_I2C_RegisterCallback(&hi2c1, HAL_I2C_MSPINIT_CB_ID, I2C1_MspInit) != HAL_OK)
    {
        Error_Handler();
    }

    hi2c1.Instance = I2C1;
    hi2c1.Init.ClockSpeed = 200000;
    hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;
    hi2c1.Init.OwnAddress1 = 0;
    hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
    hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
    hi2c1.Init.OwnAddress2 = 0;
    hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
    hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
    if (HAL_I2C_Init(&hi2c1) != HAL_OK)
    {
        Error_Handler();
    }

    if (HAL_I2C_RegisterCallback(&hi2c1, HAL_I2C_MSPDEINIT_CB_ID, I2C1_MspDeInit) != HAL_OK)
    {
        Error_Handler();
    }

    return 0;
}


static void I2C1_MspInit(I2C_HandleTypeDef* i2cHandle)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    __HAL_RCC_GPIOB_CLK_ENABLE();

    /**I2C1 GPIO Configuration
    PB6     ------> I2C1_SCL
    PB7     ------> I2C1_SDA
    */
    GPIO_InitStruct.Pin = I2C1_SCL_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF4_I2C1;
    HAL_GPIO_Init(I2C1_SCL_GPIO_PORT, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = I2C1_SDA_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF4_I2C1;
    HAL_GPIO_Init(I2C1_SDA_GPIO_PORT, &GPIO_InitStruct);

    /* I2C1 clock enable */
    __HAL_RCC_I2C1_CLK_ENABLE();
}


static void I2C1_MspDeInit(I2C_HandleTypeDef* i2cHandle)
{
    /* Peripheral clock disable */
    __HAL_RCC_I2C1_CLK_DISABLE();

    /**I2C1 GPIO Configuration
    PB6     ------> I2C1_SCL
    PB7     ------> I2C1_SDA
    */
    HAL_GPIO_DeInit(I2C1_SCL_GPIO_PORT, I2C1_SCL_PIN);

    HAL_GPIO_DeInit(I2C1_SDA_GPIO_PORT, I2C1_SDA_PIN);
}


static BOOL I2C1_Unlock(void)
{
    uint8_t i = 0;

    I2C1_SetPortODOutput();

    HAL_GPIO_WritePin(I2C1_SCL_GPIO_PORT, I2C1_SCL_PIN, GPIO_PIN_SET);  //Release bus
    HAL_GPIO_WritePin(I2C1_SDA_GPIO_PORT, I2C1_SDA_PIN, GPIO_PIN_SET);

    if (HAL_GPIO_ReadPin(I2C1_SDA_GPIO_PORT, I2C1_SDA_PIN) == GPIO_PIN_RESET)
    {
        for (i = 0; i < 9; i++)
        {
            HAL_GPIO_WritePin(I2C1_SCL_GPIO_PORT, I2C1_SCL_PIN, GPIO_PIN_RESET);
            DelayUS(5);  //
            HAL_GPIO_WritePin(I2C1_SCL_GPIO_PORT, I2C1_SCL_PIN, GPIO_PIN_SET);
            DelayUS(5);  //
            if (HAL_GPIO_ReadPin(I2C1_SDA_GPIO_PORT, I2C1_SDA_PIN) == GPIO_PIN_SET)
            {
                break;
            }
        }

        if (i >= 9)
        {
            return FALSE;
        }
    }

    return TRUE;
}


static void I2C1_SetPortODOutput(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    __HAL_RCC_GPIOB_CLK_ENABLE();

    GPIO_InitStruct.Pin = I2C1_SCL_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF4_I2C1;
    HAL_GPIO_Init(I2C1_SCL_GPIO_PORT, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = I2C1_SDA_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF4_I2C1;
    HAL_GPIO_Init(I2C1_SDA_GPIO_PORT, &GPIO_InitStruct);
}

注意:

1)上电即对I2C总线作检测,并执行解锁操作,见初始化的开头部分。

2)“HAL_I2C_Init()”函数内部包含对I2C总线的复位操作,因此,“I2C1_Unlock()”函数里未对I2C总线作复位操作。若HAL库里未对I2C总线作复位操作,则需添加如下代码:

static void I2C1_Reset(void)
{
    /*Reset I2C*/
    I2C1->CR1 |= I2C_CR1_SWRST;
    I2C1->CR1 &= ~I2C_CR1_SWRST;
}

3)在操作I2C外设出错时,若需要添加解锁操作,可按如下进行:

if (HAL_I2C_Master_Transmit(&hi2c1, SlaveAddr, &Value, 1, 1000) != HAL_OK)
{
    HAL_I2C_DeInit(&hi2c1);
    I2C1_Init();
}

先取消初始化I2C,再对I2C进行初始化(包含解锁操作)。

参考:

1)NXP,UM10204 I2C-bus specification and user manual

2)Microchip,AT24CM01 Datasheet

3)Analog,LTC4307 Datasheet(内有芯片采用的死锁恢复机制)

总结,本文介绍了STM32 I2C总线锁死原因及解决方法。

  • 17
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
STM32硬件I2C存在的一些缺陷主要包括时序问题和硬件冲突问题。为了解决这些问题,可以采取以下方法: 1. 时序问题:在使用STM32硬件I2C时,由于时钟和数据线之间的传输时序存在一定的差异,可能导致通信错误。为了解决这个问题,我们可以使用I2C的时钟延时功能或者配置I2C的时钟频率,以确保时序满足要求。 2. 硬件冲突问题:在多个外设同时使用I2C总线时,可能会导致硬件冲突,造成通信失败。为了解决这个问题,可以通过配置I2C的硬件地址和优先级来避免冲突。另外,还可以使用硬件锁定功能来确保只有一个外设能够访问I2C总线。 3. 硬件错误检测与恢复:在使用STM32硬件I2C时,可能会遇到一些硬件错误,例如通信线路中断或者外设故障。为了及时发现和解决这些问题,可以使用中断和错误处理机制来检测和处理错误。在错误发生时,可以通过复位I2C外设或者重新配置I2C相关寄存器来恢复正常工作。 4. 软件补偿:如果以上方法无法解决问题,还可以考虑使用软件补偿的方法。例如,在软件中实现时序校准算法,通过调整时钟延时来修正时序偏差。另外,还可以在软件层面上增加重试机制,以提高通信的可靠性和错误容忍能力。 总之,解决STM32硬件I2C的缺陷需要综合考虑硬件和软件两个方面,通过配置寄存器、使用中断和错误处理机制等方法,可以有效解决时序问题和硬件冲突问题,并提高通信的可靠性和稳定性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值