解救I2C总线BUSY死锁-Anlogic-安路社区-FPGA CPLD-ChipDebug

解救I2C总线BUSY死锁

 

 摘要:本文深入浅出地介绍如何发现I2C总线死锁,分析了各种可能的失效模式,最终找到解决方案。对于硬件工程师如何分析解决问题有一定的参考价值。

 

关键词:I2C,BUSY,死锁

 

1.概述

    江湖上一直有一个传说,那就是ST的I2C不可靠。ST作为ARM构架32位处理器的老大哥,我是不愿意相信这种鬼话的。这么多年下来,一直用I2C总线接口的EEPROM,从来没有出过问题。直到最近我玩票了一款I2C的OLED显示屏,种种迹象表明,那些遥远的传说,也许是真的。

    考虑到我们越来越多的产品需要带显示器,弄了一个0.96寸I2C接口的OLED显示屏耍耍。该显示屏驱动芯片用的是SOLOMON公司的SSD1306,单色128*64分辨率。

       这个芯片的驱动程序就是利用STHAL库函数写SSD1306命令寄存器或者显存数据,移植过程比较简单,与写EEPROM接口函数一模一样,只是从设备地址不一样而已。点亮屏幕后,调用字符串显示函数,效果如图1所示。

图片[1]-解救I2C总线BUSY死锁-Anlogic-安路社区-FPGA CPLD-ChipDebug

1 0.96OLED显示效果

 

 

       在接下来动态刷屏的过程中,发现屏幕偶尔会死机。一开始怀疑I2C的速度太快,出现误码引起的,把I2C时钟频率从400kHz降到100kHz,故障率没有改变。

       后来怀疑,是不是显示的数据太多,I2C屛显示的东西太多,通讯过程容易故障,对驱动程序一番优化,大大减少刷新次数和数据,可是在翻页的时候,还是会出现死机。

      

2. 寻道

       为了解决死机的问题,只好先采取一些临时措施,比如说:没有按键操作,时间超过5分钟,就关掉屏幕,也就是说停止I2C通讯,从而避免出错,但是在一些需要屏幕一直点亮的场合,这肯定行不通。

       第二个方案相对可行写,激活独立看门狗,万一显示任务崩溃了,通过看门复位主程序,当然,这个也只是治标不治本。有些应用场合,也是不允许轻易复位的,容易造成数据丢失。

       是时候追寻真正的原因了!

       首先,通过J-Link RTT Viewer监视I2C通讯失败返回值,如图2所示:

图片[2]-解救I2C总线BUSY死锁-Anlogic-安路社区-FPGA CPLD-ChipDebug

 

2 监视I2C通讯失败返回值

 

       通过查看函数HAL_I2C_Mem_Write返回值宏定义可以知道,2HAL_BUSY的枚举值。也就是说,屏幕之所以死机,是因为写SSD1306的时候,I2C检测到总线忙,所以数据发不出去,换句话说,就是总线“忙死”了!

      

3. 悟道

       既然I2C是因为状态为BUSY导致无法使用,首先想到的是加长超时等待时间,既然你忙,我慢慢等你呗,把timeout10ms加长到2000ms,但是故障依然没有消除。

       我也尝试采用do-while结构,一直检查写I2C返回值,结果直到看门狗复位,都没有等到I2C闲下来。看来,真的是忙死掉了。

       接下来,我想通过复位I2C状态来解决这个问题,代码如Listing 01

/**

   * @brief  write SSD1306

   * @param  pucData, writen to  ssd1306

   * @param  size, data quantity

   * @retval void

   */

static void  prvWriteData(unsigned char *pucData, uint16_t size)

{

   HAL_StatusTypeDef xRetVal;

   do

   {

    /* write data to SSD1306 */

    xRetVal =  HAL_I2C_Mem_Write(&hi2c2,OLED_SA,MEM_DATA,I2C_MEMADD_SIZE_8BIT,pucData,size,5);

 

    if(xRetVal != HAL_OK)

    {

      /* output return value by RTT */

      SEGGER_RTT_printf(0,”i2c write  data fail:%dn”,xRetVal);

                           

      /* release SDA and SCL */

      HAL_I2C_DeInit(&hi2c2);

                           

      /* initialize again */

      HAL_I2C_Init(&hi2c2);                               

    }

   }

   while(xRetVal != HAL_OK);

}

Listing 01 重新初始化I2C

 

       我想,不管你有多忙,我重新初始化I2C外设了,你总归回复正常了吧。但是,并没有解决这个问题。

       既然重新复位了I2C仍然没有解决问题,这个时候,我开始怀疑是不是屛的质量了,毕竟10来块钱的东西,有点质量问题也是难免的。

       然后对屛进行了若干改造,减少I2C 上拉电阻,增加滤波电容,各种骚操作,但是于事无补。

       上网花了10块钱又买了一个SPI接口屏幕,移植程序,发现一切都是OK的,驱动芯片都是SSD1306,这把问题又引回来I2C

       用逻辑分析仪监视I2C总线,发现屏幕死机之前,所有数据解析都是正常的,而且没有出现过误码之类的错误,这说明I2C时钟频率跑400kHz是没问题的。但是一旦出现BUSY锁死之后,发现总线SDA确实被拉倒低电平了。而且再也回不去了。这等于是I2C控制器永远失去了对I2C的控制权,所以就出现了前面所描述的故障。

       百度了一下I2C BUSY关键字,结果弹出一堆这个问题,如图3所示:

图片[3]-解救I2C总线BUSY死锁-Anlogic-安路社区-FPGA CPLD-ChipDebug

 

3 I2C BUSY检索结果

 

       基本上都是说STF1系列,I2C控制器可能存在缺陷,容易造成BUSY死锁,甚至复位微处理器都解决不了。解决方案也很简单:

       1)把SCLSDA两个GPIO口重新配置成普通输出

       2)强制把SCLSDA拉回高电平

       3)软件复位I2C控制器

       4)释放复位

       5)重新配置I2C

 

       源代码如Listing 02所示:

/**

  * @brief Fix i2c Busy Deadlock hardware bug

  * @param   handle pointer of i2c

  * @retval void

  * @remark call this function in  HAL_I2C_MspInit() defined in i2c.c

**/

void  HAL_I2C2_FixBusyDeadlock(I2C_HandleTypeDef* i2cHandle)

{

  GPIO_InitTypeDef GPIO_InitStruct = {0};

 

  /* step 1, config GPIO SCL and SDA as  output */

  GPIO_InitStruct.Pin =  GPIO_PIN_10|GPIO_PIN_11;

  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;

  GPIO_InitStruct.Speed =  GPIO_SPEED_FREQ_HIGH;

  HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

 

  /* step 2, pull up GPIO SCL and SDA */

  HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10,  GPIO_PIN_SET);

  HAL_GPIO_WritePin(GPIOB, GPIO_PIN_11,  GPIO_PIN_SET);

 

  /* step 3, software reset I2C controller */

  i2cHandle->Instance->CR1 =  I2C_CR1_SWRST;

 

  /* step 4, release reset */

  i2cHandle->Instance->CR1 = 0; 

}

Listing 解除I2C BUSY 死锁

 

       至此,问题得到了解决。在运行的过程会,偶尔发生了BUSY 死锁,通过上面的补丁,I2C马上又重新获取了总线控制权,通过RTT监视的数据也可以看出来,发生过BUSY报错,但是马上又解除了,如图4所示。

图片[4]-解救I2C总线BUSY死锁-Anlogic-安路社区-FPGA CPLD-ChipDebug

 

4 发生BUSY报错后又自动消除

 

4小结:

       我一直都是ST的忠实粉丝,觉得生态环境做得真好。但是I2C这个事情,可能真的要重视一下,死锁的后果是很严重的。有一些人宁愿用GPIO口模拟I2C时序,也不要用ST自带的I2C控制器,说明不认可的人还是蛮多的。

       鄙人还是以前那个观点,能用I2C控制器还是优先用控制器,毕竟STHAL库写得还是非常不错的,不使用控制器,中断和DMA就没法用了,一些总线状态寄存器也没法用了。

       回到I2C BUSY死锁这个问题,解决方案就是监视写I2C返回值,一旦发现是HAL_BUSY,就调用我写的HAL_I2C2_FixBusyDeadlock补丁,然后重新初始I2C总线。

请登录后发表评论

    没有回复内容