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:

cpp 复制代码
#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:

cpp 复制代码
#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总线作复位操作,则需添加如下代码:

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

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

cpp 复制代码
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总线锁死原因及解决方法。

相关推荐
想放学的刺客8 小时前
单片机嵌入式试题(第23期)嵌入式系统电源管理策略设计、嵌入式系统通信协议栈实现要点两个全新主题。
c语言·stm32·单片机·嵌入式硬件·物联网
猫猫的小茶馆8 小时前
【Linux 驱动开发】五. 设备树
linux·arm开发·驱动开发·stm32·嵌入式硬件·mcu·硬件工程
YouEmbedded9 小时前
解码内部集成电路(IIC)与OLED屏
stm32·0.96寸oled·硬件iic·软件模拟iic·图片取模·汉字取模
jghhh0110 小时前
基于上海钜泉科技HT7017单相计量芯片的参考例程实现
科技·单片机·嵌入式硬件
恶魔泡泡糖10 小时前
51单片机外部中断
c语言·单片机·嵌入式硬件·51单片机
意法半导体STM3210 小时前
【官方原创】如何基于DevelopPackage开启安全启动(MP15x) LAT6036
javascript·stm32·单片机·嵌入式硬件·mcu·安全·stm32开发
v_for_van10 小时前
STM32低频函数信号发生器(四通道纯软件生成)
驱动开发·vscode·stm32·单片机·嵌入式硬件·mcu·硬件工程
电化学仪器白超11 小时前
③YT讨论
开发语言·python·单片机·嵌入式硬件
乡野码圣11 小时前
【RK3588 Android12】硬件中断IRQ
单片机·嵌入式硬件
happygrilclh11 小时前
数码管驱动(一):ET6226M -数据手册主要点分析
单片机·嵌入式硬件