历史上的今天
返回首页

历史上的今天

今天是:2025年04月08日(星期二)

正在发生

2019年04月08日 | STM32 I2C 死锁问题

2019-04-08 来源:eefocus

背景

其实这篇文章在很久之前就写过解决方法了。在经过不断的实践和深究后发现,硬件 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 信号,以释放总线。*/

            hi2c->Instance->CR1 |= I2C_CR1_STOP;

          return HAL_TIMEOUT;

        }

      }


      /* Write data to DR */

      hi2c->Instance->DR = (*hi2c->pBuffPtr++);

      hi2c->XferCount--;

      hi2c->XferSize--;


      if((__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_BTF) == SET) && (hi2c->XferSize != 0U))

      {

        /* Write data to DR */

        hi2c->Instance->DR = (*hi2c->pBuffPtr++);

        hi2c->XferCount--;

        hi2c->XferSize--;

      }


      /* Wait until BTF flag is set */

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

      {

        if(hi2c->ErrorCode == HAL_I2C_ERROR_AF)

        {

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

          hi2c->Instance->CR1 |= I2C_CR1_STOP;

          return HAL_ERROR;

        }

        else

        {

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

            hi2c->Instance->CR1 |= I2C_CR1_STOP;

          return HAL_TIMEOUT;

        }

      }

    }


    /* Generate Stop */

    hi2c->Instance->CR1 |= I2C_CR1_STOP;


    hi2c->State = HAL_I2C_STATE_READY;

    hi2c->Mode = HAL_I2C_MODE_NONE;


    /* Process Unlocked */

    __HAL_UNLOCK(hi2c);


    return HAL_OK;

  }

  else

  {

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

    hi2c->Instance->CR1 |= I2C_CR1_STOP;

    /* Process Unlocked */

    __HAL_UNLOCK(hi2c);

    return HAL_BUSY;

  }

}


下面是修改后的效果: 

这里写图片描述

希望对您有帮助~


推荐阅读

史海拾趣

昆泰芯微电子(CONNTEK)公司的发展小趣事

为了加快产品国产化进程,昆泰芯微电子与世强先进(深圳)科技股份有限公司签订了授权代理合作协议。通过这一战略合作,昆泰芯微电子成功将旗下高端传感器产品触达至终端市场,实现了市场的快速扩张。同时,这一合作也进一步提升了昆泰芯微电子在行业中的知名度和影响力。

Digital Voice Systems Inc公司的发展小趣事

Digital Voice Systems, Inc.(DVSI)于1988年成立,当时正值数字通信技术的兴起阶段。公司创始人凭借对语音编解码技术的深刻理解和前瞻性的市场洞察力,决定投身于这一领域。他们开发出了具有专利保护的基于鲁棒性多带激励模型(MBE Model)的低码率语音压缩算法,如IMBE和AMBE编解码系统。这些算法在当时的市场上独树一帜,为DVSI赢得了第一桶金。

在创立初期,DVSI面临着资金、技术、市场等多方面的挑战。然而,他们凭借着坚定的信念和不懈的努力,逐步克服了这些困难。他们不断投入研发,优化算法,提高产品的性能和稳定性;同时,他们积极开拓市场,与各大通信设备制造商建立合作关系,将产品推向市场。

随着时间的推移,DVSI的产品逐渐在市场上获得了认可。他们的编解码系统被广泛应用于移动通信、卫星通信、军事通信等领域,为客户提供了高效、稳定的语音通信解决方案。DVSI也因此逐渐崭露头角,成为了电子行业中一颗耀眼的明星。

故事二至五框架概述

  1. 技术创新与突破:DVSI在语音编解码技术方面的持续创新,如推出新一代的高效压缩算法,进一步提升了产品的竞争力。
  2. 市场拓展与国际化:随着公司实力的增强,DVSI开始拓展国际市场,与全球多家知名企业建立合作关系,实现了国际化发展。
  3. 合作与竞争:在电子行业中,DVSI与其他企业的合作与竞争并存。他们通过合作共赢的方式,共同推动行业的发展;同时,也面临着来自竞争对手的挑战和竞争压力。
  4. 企业文化与团队建设:DVSI注重企业文化建设,倡导创新、协作、共赢的价值观。他们注重团队建设,吸引了一批优秀人才加入公司,为公司的发展提供了有力的人才保障。

以上故事和框架概述仅供参考,您可以根据这些线索进一步挖掘和编写关于DVSI公司的故事。

柯爱亚(ceaiya)公司的发展小趣事

为了保持技术领先地位,柯爱亚不断加大研发投入,引进先进的研发设备和人才。公司在功率电感、变压器等领域取得了多项技术成果,并申请了多项专利。这些技术成果不仅提升了柯爱亚产品的竞争力,还推动了整个电子行业的发展。

Helium公司的发展小趣事

为了进一步提升网络性能和用户体验,Helium团队自主编译并开源了LongFi协议。这一协议能够将物联网主流LoRaWAN无线协议应用到Helium区块链应用层,实现了物联网与区块链的深度融合。LongFi协议的推出不仅提高了数据传输的效率和稳定性,还降低了物联网设备的能耗和成本。这一技术创新为Helium赢得了更多用户和合作伙伴的信任和支持,推动了其业务的持续发展。

Clover Display Limited公司的发展小趣事

技术创新是Clover Display Limited得以持续发展的核心动力。公司高度重视研发团队的建设,不仅在香港设立了一支强大的研发团队,还不断引进国内外优秀人才。这些研发人员致力于开发液晶显示器(LCD)及液晶显示屏(LCM)的新应用,为公司带来了众多具有市场竞争力的产品。在他们的努力下,Clover Display Limited在液晶显示技术领域取得了多项突破,为公司的快速发展奠定了坚实基础。

BH Electronics公司的发展小趣事

在快速发展的过程中,BH Electronics始终注重产品品质的提升。公司引入了先进的品质管理体系,对生产过程中的每一个环节进行严格把控。同时,BH Electronics还加强了与供应商的合作,确保原材料的质量符合公司要求。这些措施有效提升了产品的整体品质,赢得了客户的信任和好评。

问答坊 | AI 解惑

TG值的影响因素

各位大虾:     FR-4系列板材的TG值的主要影响因素有哪些?…

查看全部问答>

Microchip最新中文资料下载

PIC24系列参考手册部分章节中文翻译 Section 7. Reset http://ww1.microchip.com/downloads/en/DeviceDoc/39712a_cn.pdf Section 8. Interrupts http://ww1.microchip.com/downloads/en/DeviceDoc/39707a_cn.pdf Section 12. I/O Ports http: ...…

查看全部问答>

关于发帖的呼吁

各位坛友:        我们欢迎大家发原创帖,但不反对分享别人的好帖!每个看问题的角度不一样,对同一问题理解不一样。一个贴别人看来没味道,但或对其他XDJM有帮组。…

查看全部问答>

AN 559: High Definition (HD) Video Reference Design (V1)

Introduction The Altera® V-Series of reference designs deliver high-quality up, down, and cross conversion of standard definition (SD), high definition (HD) and 3 gigabits per second (Gbps) video streams in interlaced or pro ...…

查看全部问答>

CE5.0下的多线程同步问题

在CE5.0下实验多线程同步。实现了3个死循环的子线程,用Event事件来同步。子线程1需要等待子线程2和子线程3对数据的操作完成后才开始下一次的循环。 我在子线程1中使用WaitForMultipleObjects(2,hEvent,TRUE,INFINITE)来等待,其中HANDLE hEvent[2 ...…

查看全部问答>

nk.bin大小

    wince6 R2,MIPS cpu,我用原厂提供的bsp,原厂定制好的平台,编译产生的nk.bin有52M.请问一般产生的nk.bin有多大啊,是否与平台定制有关?…

查看全部问答>

EVC 运行时卡住

我装了EVC  可是在新建一个项目 或者在打开一个工程的时候 它就卡在  loading class  information 那里了 在下没有积分  还请各位好心的大大能帮我解答一下    谢谢~~~~~…

查看全部问答>

STM32有没有模拟UART库

                                 项目中要用7-8个uart,目前是两块板共8uart控制,但是我总觉得稳定性会受影响,不知道STM32有没有模拟uart的库函数…

查看全部问答>

请教:st公司nand flash芯片版本之间的区别?

最近在使用一款st公司nand flash芯片,但发现型号说明上有版本,比如NAND01GW3B2AZA6和NAND01GW3B2CZA6,就是两个不同的版本。但未发现这两个版本在功能上有什么区别。想请有使用经验的大侠指点一下,或者说去哪能找到说明。版本说明如下图所示:…

查看全部问答>

关于helper2416 项目的完成度报告

Helper2416项目开始于2013/3/20,目前随着学生的培训结束,也告一个段落。项目做了2个月。2013/3/8到2013/3/20是培训RTOS。 Helper2416项目已经完成的有以下模块: 1 系统启动代码2 系统中断架构3 raw os 的移植4 实现串口发送、接收信,基于中断 ...…

查看全部问答>