• 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:

    1. #ifndef __DRV_I2C1_H
    2. #define __DRV_I2C1_H
    3. #ifdef __cplusplus
    4. extern "C" {
    5. #endif
    6. #include "datatype.h"
    7. #include "stm32f4xx_hal.h"
    8. #define I2C1_SCL_GPIO_PORT (GPIOB)
    9. #define I2C1_SCL_PIN (GPIO_PIN_6)
    10. #define I2C1_SDA_GPIO_PORT (GPIOB)
    11. #define I2C1_SDA_PIN (GPIO_PIN_7)
    12. extern I2C_HandleTypeDef hi2c1;
    13. extern int32_t I2C1_Init(void);
    14. #ifdef __cplusplus
    15. }
    16. #endif
    17. #endif

    DrvI2C1.c:

    1. #include "DrvI2C1.h"
    2. I2C_HandleTypeDef hi2c1;
    3. static void I2C1_MspInit(I2C_HandleTypeDef* i2cHandle);
    4. static void I2C1_MspDeInit(I2C_HandleTypeDef* i2cHandle);
    5. static BOOL I2C1_Unlock(void);
    6. static void I2C1_SetPortODOutput(void);
    7. int32_t I2C1_Init(void)
    8. {
    9. if (!I2C1_Unlock())
    10. {
    11. DbgPrint("I2C1 unlock failed!\r\n");
    12. }
    13. if (HAL_I2C_RegisterCallback(&hi2c1, HAL_I2C_MSPINIT_CB_ID, I2C1_MspInit) != HAL_OK)
    14. {
    15. Error_Handler();
    16. }
    17. hi2c1.Instance = I2C1;
    18. hi2c1.Init.ClockSpeed = 200000;
    19. hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;
    20. hi2c1.Init.OwnAddress1 = 0;
    21. hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
    22. hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
    23. hi2c1.Init.OwnAddress2 = 0;
    24. hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
    25. hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
    26. if (HAL_I2C_Init(&hi2c1) != HAL_OK)
    27. {
    28. Error_Handler();
    29. }
    30. if (HAL_I2C_RegisterCallback(&hi2c1, HAL_I2C_MSPDEINIT_CB_ID, I2C1_MspDeInit) != HAL_OK)
    31. {
    32. Error_Handler();
    33. }
    34. return 0;
    35. }
    36. static void I2C1_MspInit(I2C_HandleTypeDef* i2cHandle)
    37. {
    38. GPIO_InitTypeDef GPIO_InitStruct = {0};
    39. __HAL_RCC_GPIOB_CLK_ENABLE();
    40. /**I2C1 GPIO Configuration
    41. PB6 ------> I2C1_SCL
    42. PB7 ------> I2C1_SDA
    43. */
    44. GPIO_InitStruct.Pin = I2C1_SCL_PIN;
    45. GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
    46. GPIO_InitStruct.Pull = GPIO_NOPULL;
    47. GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    48. GPIO_InitStruct.Alternate = GPIO_AF4_I2C1;
    49. HAL_GPIO_Init(I2C1_SCL_GPIO_PORT, &GPIO_InitStruct);
    50. GPIO_InitStruct.Pin = I2C1_SDA_PIN;
    51. GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
    52. GPIO_InitStruct.Pull = GPIO_NOPULL;
    53. GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    54. GPIO_InitStruct.Alternate = GPIO_AF4_I2C1;
    55. HAL_GPIO_Init(I2C1_SDA_GPIO_PORT, &GPIO_InitStruct);
    56. /* I2C1 clock enable */
    57. __HAL_RCC_I2C1_CLK_ENABLE();
    58. }
    59. static void I2C1_MspDeInit(I2C_HandleTypeDef* i2cHandle)
    60. {
    61. /* Peripheral clock disable */
    62. __HAL_RCC_I2C1_CLK_DISABLE();
    63. /**I2C1 GPIO Configuration
    64. PB6 ------> I2C1_SCL
    65. PB7 ------> I2C1_SDA
    66. */
    67. HAL_GPIO_DeInit(I2C1_SCL_GPIO_PORT, I2C1_SCL_PIN);
    68. HAL_GPIO_DeInit(I2C1_SDA_GPIO_PORT, I2C1_SDA_PIN);
    69. }
    70. static BOOL I2C1_Unlock(void)
    71. {
    72. uint8_t i = 0;
    73. I2C1_SetPortODOutput();
    74. HAL_GPIO_WritePin(I2C1_SCL_GPIO_PORT, I2C1_SCL_PIN, GPIO_PIN_SET); //Release bus
    75. HAL_GPIO_WritePin(I2C1_SDA_GPIO_PORT, I2C1_SDA_PIN, GPIO_PIN_SET);
    76. if (HAL_GPIO_ReadPin(I2C1_SDA_GPIO_PORT, I2C1_SDA_PIN) == GPIO_PIN_RESET)
    77. {
    78. for (i = 0; i < 9; i++)
    79. {
    80. HAL_GPIO_WritePin(I2C1_SCL_GPIO_PORT, I2C1_SCL_PIN, GPIO_PIN_RESET);
    81. DelayUS(5); //
    82. HAL_GPIO_WritePin(I2C1_SCL_GPIO_PORT, I2C1_SCL_PIN, GPIO_PIN_SET);
    83. DelayUS(5); //
    84. if (HAL_GPIO_ReadPin(I2C1_SDA_GPIO_PORT, I2C1_SDA_PIN) == GPIO_PIN_SET)
    85. {
    86. break;
    87. }
    88. }
    89. if (i >= 9)
    90. {
    91. return FALSE;
    92. }
    93. }
    94. return TRUE;
    95. }
    96. static void I2C1_SetPortODOutput(void)
    97. {
    98. GPIO_InitTypeDef GPIO_InitStruct = {0};
    99. __HAL_RCC_GPIOB_CLK_ENABLE();
    100. GPIO_InitStruct.Pin = I2C1_SCL_PIN;
    101. GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
    102. GPIO_InitStruct.Pull = GPIO_NOPULL;
    103. GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    104. GPIO_InitStruct.Alternate = GPIO_AF4_I2C1;
    105. HAL_GPIO_Init(I2C1_SCL_GPIO_PORT, &GPIO_InitStruct);
    106. GPIO_InitStruct.Pin = I2C1_SDA_PIN;
    107. GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
    108. GPIO_InitStruct.Pull = GPIO_NOPULL;
    109. GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    110. GPIO_InitStruct.Alternate = GPIO_AF4_I2C1;
    111. HAL_GPIO_Init(I2C1_SDA_GPIO_PORT, &GPIO_InitStruct);
    112. }

    注意:

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

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

    1. static void I2C1_Reset(void)
    2. {
    3. /*Reset I2C*/
    4. I2C1->CR1 |= I2C_CR1_SWRST;
    5. I2C1->CR1 &= ~I2C_CR1_SWRST;
    6. }

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

    1. if (HAL_I2C_Master_Transmit(&hi2c1, SlaveAddr, &Value, 1, 1000) != HAL_OK)
    2. {
    3. HAL_I2C_DeInit(&hi2c1);
    4. I2C1_Init();
    5. }

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

    参考:

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

    2)Microchip,AT24CM01 Datasheet

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

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

  • 相关阅读:
    GBase 8c V3.0.0数据类型——序列号生成函数
    (四)Vue 中的 ajax
    Java实现对Html文本的处理
    big.LITTLE&DynamIQ
    docker的使用以及注意事项
    java BIO深入学习
    模块化 DeFi L2 “Mode” 整合 Covalent Network(CQT),以获 Web3 最大数据集的支持
    基于SpringBoot的服装生产管理系统的设计与实现
    arm-linux 原子操作
    Python TCP服务端多线程接收RFID网络读卡器上传数据
  • 原文地址:https://blog.csdn.net/propor/article/details/139848940