【InterFace】STM32 I2C 死锁问题

发布者:WiseThinker最新更新时间:2019-08-08 来源: eefocus关键字:STM32  I2C  死锁问题 手机看文章 扫描二维码
随时随地手机看文章

#背景

其实这篇文章在很久之前就写过解决方法了。在经过不断的实践和深究后发现,硬件 I2C 死锁的问题在ST的官方手册中的勘误手册(errata)中早就提供解决方案,只是我没有重视官方的文档,一直在网络寻求帮助。


即使已经有官方的解决方案,但是还有很多人(包括以前的我)在怀疑 STM32 系列的 I2C 有硬件 BUG。这也告诉我们:网上资源虽丰富,但还是得通过“金睛火眼”来辨别。


讲真的,为了解决 I2C 问题,我在网上看了 N 多篇的文章、Blog 和帖子,还没看到几个人说 “STM32 硬件 I2C 没问题”,反而是看到很多类似这样的:“都听说STM32 硬件 I2C 有问题,一试,发现还真是有问题,改用 IO 模拟吧”。


不再哔哔哔~~~


下面我将提供 STM32F207 和 STM32F103 系列的 I2C 死锁(一直为 BUSY 状态或 START 一直置位)问题的解决方案。同时在底部,依旧保存了我以前的解决方案(SDA为 LOW),如果你遇到的是SDA 被置为 LOW 的问题而已,你完全可以采用旧的解决方案。


#I2C 死锁描述

本文所描述的 I2C 死锁问题,表现为:当 I2C 通讯出现异常后,SDA 和 SCL 均为高(即 IDLE 状态),在调用 HAL_I2C_Master_Transmit 或者 HAL_I2C_Master_Receive 一直返回 BUSY 或 TIMEOUT。通过逻辑分析仪查看总线一直为HIGH。

通常这种异常发生:


在 Slave 设备拔除总线后,Master 出现异常

一次通讯被异常中断,导致 Master 出现异常

在 STM32F207 中,上述的问题能通过 MCU 软件复位来解决。但是对于 STM32F103 的 MCU 软件复位并不能完全解决问题,经常是需要断电重启。在正常场景中,我们当然是不希望需要通过软件复位或断电解决啦!那您就得继续往下看了。


通过 Debug,可以看到,在出现异常时,I2C相关寄存器的值,如下面两图所示。


图1:一直为 BUSY 状态时的 I2C 寄存器状态

这里写图片描述

图2:START 位一直被置位时的 I2C 寄存器状态

这里写图片描述

#STM32F207 解决方案

相对于 STM32F103 来说,STM32F207 的解决方案是比较简单的,仅需要对 进行 I2C 外设复位。也许你会说,这算什么解决办法!!!拜托,人家之前并不知道它还有外设复位寄存器位嘛~~~


首先咱们先来看看勘误手册的描述。


这里写图片描述

Example:用于总线复位的函数


static HAL_StatusTypeDef I2CResetBus(void)

{

__HAL_I2C_DISABLE(&hi2c1);

/* 1. Set SWRST bit in I2Cx_CR1 register. */

hi2c1.Instance->CR1 |=  I2C_CR1_SWRST;

HAL_Delay(2);

/* 2. Clear SWRST bit in I2Cx_CR1 register. */

hi2c1.Instance->CR1 &=  ~I2C_CR1_SWRST;

HAL_Delay(2);

/* 3. Enable the I2C peripheral by setting the PE bit in I2Cx_CR1 register */

MX_I2C1_Init();

__HAL_I2C_ENABLE(&hi2c1);

HAL_Delay(2);

#ifdef I2C_TEST

printf("I2CResetBusrn");

#endif

hi2c1.ErrorCode = HAL_I2C_ERROR_NONE;

hi2c1.State = HAL_I2C_STATE_READY;

hi2c1.PreviousState = I2C_STATE_NONE;

hi2c1.Mode = HAL_I2C_MODE_NONE;

return HAL_OK;

}


上面的函数里面有一行 MX_I2C1_Init() 用于给 I2C 进行配置。这里是因为 I2C 进行复位后,寄存器的值均会被修改掉。只能再配置一遍。


#STM32F103解决方案

STM32F103 的方法比较麻烦,首先,咱们先看看勘误手册的描述。

这里写图片描述
这里写图片描述

我对勘误手册的理解是:将管脚配置为普通输出管脚后,实现电平的反转,以达到解除死锁,再将其恢复为 I2C 配置。


Example:解决代码


static void User_I2C2_GeneralPurposeOutput_Init(I2C_HandleTypeDef* i2cHandle)

{


GPIO_InitTypeDef GPIO_InitStruct;

if(i2cHandle->Instance==I2C2)

{

/*   PB10     ------> I2C2_SCL; PB11     ------> I2C2_SDA */

GPIO_InitStruct.Pin = GPIO_PIN_10|GPIO_PIN_11;

GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;

GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;

HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

}

}



static void User_I2C2_AlternateFunction_Init(I2C_HandleTypeDef* i2cHandle)

{


GPIO_InitTypeDef GPIO_InitStruct;

if(i2cHandle->Instance==I2C2)

{

/*   PB10     ------> I2C2_SCL; PB11     ------> I2C2_SDA */

GPIO_InitStruct.Pin = GPIO_PIN_10|GPIO_PIN_11;

GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;

GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;

HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

}

}


HAL_StatusTypeDef I2CResetBus(void)

{

hi2c2.ErrorCode = HAL_I2C_ERROR_AF;

/* 1. Disable the I2C peripheral by clearing the PE bit in I2Cx_CR1 register */

__HAL_I2C_DISABLE(&hi2c2);

HAL_GPIO_DeInit(GPIOB, GPIO_PIN_10|GPIO_PIN_11);

/* 2. Configure the SCL and SDA I/Os as General Purpose Output Open-Drain, High level (Write 1 to GPIOx_ODR) */

User_I2C2_GeneralPurposeOutput_Init(&hi2c2);

HAL_Delay(1);

HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10 | GPIO_PIN_11, GPIO_PIN_SET);

HAL_Delay(1);

/* 3. Check SCL and SDA High level in GPIOx_IDR */

if ((HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_10) != GPIO_PIN_SET)||(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_11) != GPIO_PIN_SET))

{

#ifdef I2C_TEST

printf("3.PB10=%d, PB11=%drn", HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_10), HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_11));

#endif

return HAL_ERROR;

}

/* 4. Configure the SDA I/O as General Purpose Output Open-Drain, Low level (Write 0 to GPIOx_ODR).

* 5. Check SDA Low level in GPIOx_IDR.

* 6. Configure the SCL I/O as General Purpose Output Open-Drain, Low level (Write 0 to GPIOx_ODR)

* 7. Check SCL Low level in GPIOx_IDR.

* */

HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10|GPIO_PIN_11, GPIO_PIN_RESET);

HAL_Delay(1);

if ((HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_10) != GPIO_PIN_RESET)||(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_11) != GPIO_PIN_RESET))

{

#ifdef I2C_TEST

printf("4-7.PB10=%d, PB11=%drn", HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_10), HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_11));

#endif

return HAL_ERROR;

}

/*

* 8. Configure the SCL I/O as General Purpose Output Open-Drain, High level (Write 1 to GPIOx_ODR).

* 9. Check SCL High level in GPIOx_IDR.

* 10. Configure the SDA I/O as General Purpose Output Open-Drain , High level (Write 1 to GPIOx_ODR).

* 11. Check SDA High level in GPIOx_IDR.

*/

HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10|GPIO_PIN_11, GPIO_PIN_SET);

HAL_Delay(1);

if ((HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_10) != GPIO_PIN_SET)||(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_11) != GPIO_PIN_SET))

{

#ifdef I2C_TEST

printf("8-11.PB10=%d, PB11=%drn", HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_10), HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_11));

#endif

return HAL_ERROR;

}

/* 12. Configure the SCL and SDA I/Os as Alternate function Open-Drain. */

HAL_GPIO_DeInit(GPIOB, GPIO_PIN_10|GPIO_PIN_11);

User_I2C2_AlternateFunction_Init(&hi2c2);

/* 13. Set SWRST bit in I2Cx_CR1 register. */

hi2c2.Instance->CR1 |=  I2C_CR1_SWRST;

HAL_Delay(2);

/* 14. Clear SWRST bit in I2Cx_CR1 register. */

hi2c2.Instance->CR1 &=  ~I2C_CR1_SWRST;

HAL_Delay(2);

/* 15. Enable the I2C peripheral by setting the PE bit in I2Cx_CR1 register */

MX_I2C2_Init();

__HAL_I2C_ENABLE(&hi2c2);

HAL_Delay(2);

#ifdef I2C_TEST

printf("I2CResetBusrn");

#endif

hi2c2.ErrorCode = HAL_I2C_ERROR_NONE;

hi2c2.State = HAL_I2C_STATE_READY;

hi2c2.PreviousState = I2C_STATE_NONE;

hi2c2.Mode = HAL_I2C_MODE_NONE;

return HAL_OK;

}


#SDA 为 LOW的解决方案

最近在项目中设计了一个 IIC 模拟从机的程序。为了图方便,我随便拿了个 STM32F207 的开发板做 IIC Master,用 STM32CUBE 做了个程序,Master 的 数据发送和接收,都是直接调用 HAL 库的函数。

通过逻辑分析仪测试发现,**每次主机出现错误后,IIC SDA 会被拉低,导致整个 IIC 总线被锁死了。后续的数据传输异常。**现象如下图所示:


这里写图片描述

这里写图片描述

后来我查看了 HAL 库的 IIC 的 HAL_I2C_Master_Transmit 函数。

发现:当出现 TIMEOUT 或 ERROR 时,STM32 Master 并不会产生 STOP 信号,或者,将总线释放(SDA 和 SCL 置高)。这样就会导致,当出现 TIMEOUT 或者 ERROR 后, 下一次进入HAL_I2C_Master_Transmit ,Master 会认为 IIC 总线为 BUSY,而放弃通讯,造成 SDA 被锁死的现象。

然后,我在 HAL_I2C_Master_Transmit 函数做了些改动,如下面的程序所示。

NOTE:如果是用 HAL_I2C_Master_Transmit 生成的程序,做修改时,必须把这段程序复制出来,保存到别的文件中,不然,在使用 STM32CUBE 再修改程序时,原来的修改会被覆盖掉。


/**

  * @brief  Transmits in master mode an amount of data in blocking mode.

  * @param  hi2c Pointer to a I2C_HandleTypeDef structure that contains

  *                the configuration information for the specified I2C.

  * @param  DevAddress Target device address: The device 7 bits address value

  *         in datasheet must be shifted to the left before calling the interface

  * @param  pData Pointer to data buffer

  * @param  Size Amount of data to be sent

  * @param  Timeout Timeout duration

  * @retval HAL status

  */

HAL_StatusTypeDef HAL_I2C_Master_Transmit(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout)

{

  uint32_t tickstart = 0x00U;


  /* Init tickstart for timeout management*/

  tickstart = HAL_GetTick();


  if(hi2c->State == HAL_I2C_STATE_READY)

  {

    /* Wait until BUSY flag is reset */

    // 下面的代码就是用于检测 IIC 总线是否为 BUSY,当 SDA 和 SCL 同时为高,才会被认为是空闲(IDLE),否则,会被认为是 BUSY。

    if(I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_BUSY, SET, I2C_TIMEOUT_BUSY_FLAG, tickstart) != HAL_OK)

    {

      return HAL_BUSY;

    }


    /* Process Locked */

    __HAL_LOCK(hi2c);


    /* Check if the I2C is already enabled */

    if((hi2c->Instance->CR1 & I2C_CR1_PE) != I2C_CR1_PE)

    {

      /* Enable I2C peripheral */

      __HAL_I2C_ENABLE(hi2c);

    }


    /* Disable Pos */

    hi2c->Instance->CR1 &= ~I2C_CR1_POS;


    hi2c->State     = HAL_I2C_STATE_BUSY_TX;

    hi2c->Mode      = HAL_I2C_MODE_MASTER;

    hi2c->ErrorCode = HAL_I2C_ERROR_NONE;


    /* Prepare transfer parameters */

    hi2c->pBuffPtr    = pData;

    hi2c->XferCount   = Size;

    hi2c->XferOptions = I2C_NO_OPTION_FRAME;

    hi2c->XferSize    = hi2c->XferCount;


    /* Send Slave Address */

    if(I2C_MasterRequestWrite(hi2c, DevAddress, Timeout, tickstart) != HAL_OK)

    {

      if(hi2c->ErrorCode == HAL_I2C_ERROR_AF)

      {

        /* 此处为我自行添加的部分,当出现错误时,产生 Stop 信号,以释放总线。*/

        hi2c->Instance->CR1 |= I2C_CR1_STOP;

        /* Process Unlocked */

        __HAL_UNLOCK(hi2c);


        return HAL_ERROR;

      }

      else

      {


        /*此处为我自行添加的部分,当出现错误时,产生 Stop 信号,以释放总线。*/

        hi2c->Instance->CR1 |= I2C_CR1_STOP;

         /* Process Unlocked */

        __HAL_UNLOCK(hi2c);

        return HAL_TIMEOUT;

      }

    }


    /* Clear ADDR flag */

    __HAL_I2C_CLEAR_ADDRFLAG(hi2c);


    while(hi2c->XferSize > 0U)

    {

      /* Wait until TXE flag is set */

      if(I2C_WaitOnTXEFlagUntilTimeout(hi2c, Timeout, tickstart) != HAL_OK)

      {

        if(hi2c->ErrorCode == HAL_I2C_ERROR_AF)

        {

          /* 此处为我自行添加的部分,当出现错误时,产生 Stop 信号,以释放总线。*/

          hi2c->Instance->CR1 |= I2C_CR1_STOP;

          return HAL_ERROR;

        }

        else

        {

            /* 此处为我自行添加的部分,当出现错误时,产生 Stop 信号,以释放总线。*/

[1] [2]
关键字:STM32  I2C  死锁问题 引用地址:【InterFace】STM32 I2C 死锁问题

上一篇:【STM32CUBEMX】HAL 库的 Timeout=1 异常分析
下一篇:STM32Cube_FW_F0_V1.10.0 官方库的I2C 调试

推荐阅读最新更新时间:2024-11-11 10:40

STM32 USB接口介绍
以下内容基于STM32H7系列进行介绍,同样适用于STM32F4/F7系列芯片。 USB on-the-go 高速(OTG_HS) STM32的USB接口支持OTG模式,芯片手册上描述如下: USB OTG 是一款双角色设备 (DRD) 控制器,同时支持从机功能和主机功能,完全符合 USB 2.0 规范的 On-The-Go 补充标准。此外,该控制器也可配置为“仅主机”模式或“仅从机” 模式,完全符合 USB 2.0 规范。 OTG通俗一点来说,就是既可以作为从设备和主机通信(比如电脑读写手机上的内容),又可以作为主机和从设备通信(比如手机读写U盘) USB 通信速率 STM32 USB支持三种速度模式,高速模式(High Spe
[单片机]
<font color='red'>STM32</font> USB接口介绍
STM32中断优先级和开关总中断(很老很经典)
一、中断优先级: STM32(Cortex-M3)中的优先级概念 STM32(Cortex-M3)中有两个优先级的概念——抢占式优先级和响应优先级,有人把响应优先级称作'亚优先级'或'副优先级',每个中断源都需要被指定这两种优先级。 具有高抢占式优先级的中断可以在具有低抢占式优先级的中断处理过程中被响应,即中断嵌套,或者说高抢占式优先级的中断可以嵌套低抢占式优先级的中断。 当两个中断源的抢占式优先级相同时,这两个中断将没有嵌套关系,当一个中断到来后,如果正在处理另一个中断,这个后到来的中断就要等到前一个中断处理完之后才能被处理。如果这两个中断同时到达,则中断控制器根据他们的响应优先级高低来决
[单片机]
<font color='red'>STM32</font>中断优先级和开关总中断(很老很经典)
基于STM32的红外测温仪的设计与实现
红外测温仪是一种将红外技术与微电子技术相结合的新型温度测量仪器。与传统接触式测温仪器相比,具有测温精度高、非接触、不影响被测对象温度场、响应速度快及稳定性好等一系列优点,在电力、石油、化工、医疗等领域得到广泛应用 。 热释电红外测温仪是利用热释电效应工作的一种新型红外测温仪。与其他传统测温仪相比,具有不需制冷、能在室温下工作和光谱响应宽等优点,且其灵敏度高、响应速度快、抗干扰能力强 。本文利用热释电探测器,结合32 bit ARM核处理器低功耗、高性能和低成本的优点,设计了一个以ARM微控制器STM32为核心的红外测温仪。 1 红外测温的原理 一切温度高于绝对零度的物体都在不停地向周围空间发出红外辐射能量,其辐射能量的大小及其波
[单片机]
基于<font color='red'>STM32</font>的红外测温仪的设计与实现
基于STM32芯片的电源监控器应用方案
电源对电子设备的重要性不言而喻,它是保证系统稳定运行的基础,而保证系统能稳定运行后,又有低功耗的要求。在很多应用场合中都对电子设备的功耗要求非常苛刻,如某些传感器信息采集设备,仅靠小型的电池提供电源,要求工作长达数年之久,且期间不需要任何维护;由于智慧穿戴设备的小型化要求,电池体积不能太大导致容量也比较小,所以也很有必要从控制功耗入手,提高设备的续行时间。因此,STM32 有专门的电源管理外设监控电源并管理设备的运行模式,确保系统正常运行,并尽量降低器件的功耗。 电源监控器 STM32芯片主要通过引脚 VDD 从外部获取电源,在它的内部具有电源监控器用于检测 VDD的电压,以实现复位功能及掉电紧急处理功能,保证系统可靠地运行。
[单片机]
基于<font color='red'>STM32</font>芯片的电源监控器应用方案
STM32休眠与唤醒
这两天研究了STM32的低功耗知识,低功耗里主要研究的是STM32的待机模式和停机模式。让单片机进入的待机模式和停机模式比较容易,实验中通过设置中断口PA1来响应待机和停机模式。 voidEXTI1_IRQHandler(void) { if(!GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_1)) { delay_ms(10); while(!GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_1)); if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_1)) { EXTI_ClearITPendingBit(EXTI_Line1); RTC
[单片机]
STM32连接射频si4438模块
SI4438射频模块参数: 1、频率范围:425-525 MHz 2、数字接收信号强度指示(RSSI) 3、64字节收发数据寄存器(FIFO) 4、跳频功能 等! 使用SI的WDS工具生成代码 1、 选择仿真模式 2、 芯片选择si4438 B1模式 3、 Radio Configuration Application 4、 Select Application 1、 Select Project 选择Bidirectional packet ,双向通信模式 2、 Configure project 配置工程 Frequency and power: 频率和功率的设置, base freq基频,中心频率, Chan
[单片机]
STM32串口使用IDLE中断接收不定长数据原理与源程序
今天说一下STM32单片机的接收不定长度字节数据的方法。由于STM32单片机带IDLE中断,所以利用这个中断,可以接收不定长字节的数据,由于STM32属于ARM单片机,所以这篇文章的方法也适合其他的ARM单片机。 IDLE中断什么时候发生? IDLE就是串口收到一帧数据后,发生的中断。什么是一帧数据呢?比如说给单片机一次发来1个字节,或者一次发来8个字节,这些一次发来的数据,就称为一帧数据,也可以叫做一包数据。 如何判断一帧数据结束,就是我们今天讨论的问题。因为很多项目中都要用到这个,因为只有接收到一帧数据以后,你才可以判断这次收了几个字节和每个字节的内容是否符合协议要求。 看了前面IDLE中断的定义,你就会明白了
[单片机]
<font color='red'>STM32</font>串口使用IDLE中断接收不定长数据原理与源程序
STM32的CAN的波特率的计算
STM32里的CAN 支持2.0A,2.0B, 带有FIFO,中断等, 这里主要提一下内部的时钟应用. bxCAN挂接在APB1总线上,采用总线时钟,所以我们需要知道APB1的总线时钟是多少. 我们先看看下图,看看APB1总线时钟: APB1时钟取自AHB的分频, 而AHB又取自系统时钟的分频, 系统时钟可选HSI,HSE, PLLCLK, 这个在例程的RC设置里都有的, 然后再看看有了APB1的时钟后,如何算CAN的总线速率, 先看下图: 有了上边的这个图,基本就清楚了. 总线时钟MHz (3+TS1+TS2)*(BRP+1) ===============================
[单片机]
<font color='red'>STM32</font>的CAN的波特率的计算
小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved