STM32 I2C 死锁问题

发布者:电子艺术大师最新更新时间:2019-04-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 信号,以释放总线。*/

            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;

  }

}


下面是修改后的效果: 

这里写图片描述

希望对您有帮助~


关键字:STM32  I2C  死锁问题 引用地址:STM32 I2C 死锁问题

上一篇:stm32f3的i2c使用小结
下一篇:STM32之I2C_FLAG_BUSY置位解决办法

推荐阅读最新更新时间:2024-03-16 16:26

基于DSP与CPLD的I2C总线接口的设计与实现
带有I2C总线接口的器件可以十分方便地将一个或多个单片机及外围器件组成单片机系统。尽管这种总线结构没有并行总线那样大的吞吐能力,但由于连接线和连接引脚少,因此其构成的系统价格低、器件间总线连接简单、结构紧凑,而且在总线上增加器件不影响系统的正常工作,系统修改和可扩展性好。即使有不同时钟速度的器件连接到总线上,也能很方便地确定总线的时钟。 如今,为了提高系统的数据处理精度和处理速度,在家用电器、通讯设备及各类电子产品中已广泛应用DSP芯片。但大多数的尚未提供I2C总线接口,本文将介绍一种基于CPLD的已实现的高速DSP的I2C总线接口方案。 1 I2C通信协议 I2C总线是一种用于IC器件之间的二线制总线。它通过SDA(串行
[嵌入式]
STM32 使用SWD方式下载不了程序
下载不了程序往往有很多原因,有可能是keil没有设置好,也有可能是板子问题,我会将两种情况进行总结。 下载不了程序的童鞋,请耐心地看到最下面哦。 一、keil的设置 打开魔法棒,进行如下操作: 1)Device 选择对应的型号,如果没有则去官方下载相应的pack。比如我的板子是F407VE,选择如下。 2)Debug 选择对应的下载器,如J-LINK或ST-LINK。 3)点击Debug 下载器旁边的Settings ,如果下载器驱动是已安装的,并且STM32是正在供电的状态,则会显示: 如果没有显示上述红框内容,则说明很可能你的下载器驱动没有装好,此时则需要查看电脑的设备管理器,看看驱动的安装情况。 4)点击De
[单片机]
<font color='red'>STM32</font> 使用SWD方式下载不了程序
STM32控制三轴加速度传感器实现分析
买的stm32F107VC开发板,带三轴加速度传感器LIS302DL,附带的示例代码。学习了几天,总结如下。 1。逻辑结构 初始化各外设(RCC,GPIO,SPI,NVIC,I2C,LCD) 检测MEMS并显示检测状态 在死循环中不断查询加速度值并描画。 2。硬件原理 与该程序相关的硬件连接图: MEMS连接图 MEMS引出脚与MCU(左)、IO扩展(右下)连接图 LCD连接图 LCD引出脚与MCU(左)、IO扩展(右上)连接图 从图中可以看出,MEMS使用I2C接口SCL和SDA连接MCU;LCD使用SPI3接口连接MCU。
[单片机]
<font color='red'>STM32</font>控制三轴加速度传感器实现分析
STM32 IIC读取AT24C02
1、数量:I2C1与I2C2 2、时钟: RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1 | RCC_APB1Periph_I2C2,ENABLE); 3、IO配置为开漏输出,在输入时亦可读取,此处使用功能复用。 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO,ENABLE ); RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_
[单片机]
基于STM32的多功能数字钟
/************************************************************************************** 程序功能:基于STM32的多功能数字钟 ************************************************************************************** 1、时钟的基准用STM32F103RCT6自带的RTC实现。 2、这三个按键的功能分别为: 设置 、 + 、 - 。(WAKEUP KEY0 KEY1) 三个按键作为这三个功能: 键盘上的WAKEUP用作 设置 ,KEY0用作
[单片机]
基于STM32的无线飞鼠(一)
无线飞鼠(一) 时间过得好快,大三已经过了一半了,这段时间忙的没有更新过博客,回顾下自己这段时间中的收获,感觉蛮多的。下边就一一分享下。 一个月前,接到一个朋友的求助,帮忙做一份毕设,其实本来自己也没有时间做的,一直在忙这做学校的创新项目,面对朋友的求助,我只好答应了,谁叫我这么爱帮助别人呢!说也很奇怪,自己经常帮助别人回答一些力所能及的问题,每天都有人加我,问一些基础的问题,我也干到很欣慰,帮助别人的过程中自己收获的也挺大的。 盆友的毕设叫做“无线飞鼠”。也就是个鼠标而已,和普通的鼠标相比优势在于能够在空中进行控制,这个东西出来好多年了,也是比较流行的,自己也想试一试,就开始着手做了。 一、选材(硬件选择) 首先
[单片机]
STM32 启动代码 __main 与用户主程序 main() 的区别
1、__main 作用 __main函数是C/C++运行时库的一个函数,嵌入式系统在进入应用主程序之前必须有一个初始化的过程,使用__main标号引导系统时必须将应用程序的入口定义为main()。 在初始化的过程中,__main函数的作用主要有两点: 1) 完成对映像文件的初始化操作 a、映像文件 链接器把多个目标文件链接成一个映像文件。 b、加载地址和执行地址 映像文件可以有两种地址:加载地址和执行地址。 加载地址是映像文件在存储器中的存储地址;执行地址就是映像文件运行时的地址。 c、加载域和执行域 文件加载的存储区叫加载域,文件运行的存储区叫执行域。 d、从加载地址到执行地址 在结构比较简单的系统中,加载地址就是执
[单片机]
<font color='red'>STM32</font> 启动代码 __main 与用户主程序 main() 的区别
STM32学习笔记:基础例子
本例子代码参考了STM32库开发实战指南中的代码,由于使用的板子是尚学STM32F103ZET6,为了配合板上已有资源,也参考了其配套代码。为了便于书写文本,我尽量将代码都写到了一个文件中,这种方式是不推荐的,在做具体工程时最好代码分类管理,使工程逻辑清晰。 现在对板上一些资源说明:板上有两个LED灯,引脚为PE5、PE6,均为ResetBits时点亮。有三个按钮,依次为黄色复位,红色PE4(按下接GND)、红色PA0(按下接3.3V,WAKE UP按钮)。ISP口为靠近电源开关的USB,也是USART1口。USART2口为PA3(Rx)、PA2(Tx)。IPD为高电平中断(按键一边接高电平),IPU为低电平中断。 接下来
[单片机]
<font color='red'>STM32</font>学习笔记:基础例子
小广播
添点儿料...
无论热点新闻、行业分析、技术干货……
设计资源 培训 开发板 精华推荐

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

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

换一换 更多 相关热搜器件
随便看看
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved