解决STM32 硬件IIC死锁在BUSY状态的方法讨论

关于STM32的I2C接口死锁在BUSY状态无法恢复的现象,网上已有很多讨论,看早几年比较老的贴子,有人提到复位MCU也无法恢复、只有断电才行的状况,那可是相当严重的问题。类似复位也无法恢复的情况是存在的,技术支持矢口否认问题存在,并不是正确面对问题的态度。比如我用这款F439芯片的SDRAM控制器,在错误操作后进入HardFault状态,复位无法恢复,JTAG也无法联机,只能断电重来,官方的Erratasheet里也提到了。
如果I2C接口无法可靠工作,那么所做的设计将存在严重隐患,不可能要求用户用断电的方法恢复系统。如果像某些网友提到弃用硬件I2C,转为GPIO模拟I2C时序,那么首先I2C时钟频率不易确定,因为STM32的时钟频率可以动态调节;此外不用硬件I2C,无法用中断、DMA等高级模式,会严重降低ARM内核效率。所以务须确认和解决这个问题。

一.问题存在
我用STM32F439IGT,为了确定问题存在,让I2C控制器作Master,先人为产生I2C总线故障。产生I2C总线故障的方法简单而粗暴:在I2C总线工作过程中,用镊子把SCL和SDA两个信号短路一下,很容易进入BUSY死锁状态。长时间短路也可能产生超时。HAL_I2C_Init()、HAL_I2C_Master_Transmit()、HAL_I2C_Master_Receive()等函数返回值分别为HAL_BUSY(0x02)、HAL_TIMEOUT(0x03)。
试着用MCU复位,是可以恢复的,说明硬件没死穴。又测试不用MCU复位,而是在程序中依次调用STM32Cube_FW_F4_V1.5.0固件库提供的如下两个初始化函数:HAL_I2C_DeInit(&hi2c1)、HAL_I2C_Init(&hi2c1),并不能保证一定恢复正常。
BUSY死锁时,用万用表测试I2C信号电压,SCL、SDA均为低电平。如果调用函数:HAL_I2C_DeInit(&hi2c1),会函数释放IO口回到GPIO的默认状态(Input),此时再测SCL、SDA电压,均为高电平。这说明总线是被MCU这边的Master拉低的,而不是被Slave拉低的。当然也存在Slave刚好输出低电平拉低SDA的可能。

二.出错代码位置跟踪
单步运行,可以看到进入stm32f4xx_hal_i2c.c程序中I2C读写函数不远处(如图阴影所在行),读BUSY位,总会得到SET的结果,无法继续执行后续程序而返回。
 

三.参考文献
读了网上很多解决方案,其中比较有启发意义的有这几篇:
1. 百度文库,这个好像是ST官方客服提供的,关于死锁的可能机理和解决方案做了说明:
http://wenku.baidu.com/link?url=KB9p-TYrQcmVu1azHG66BXAcG6Pe6Bm2kWF_9ERSU35EOA8obiTVTDrZ6fZ3IOjfVAb71RCvJIiAODo4p4Sr0fUPDy0kQyyqWWJgxjfYHzO
2. STM社区,这个提到了初始化I2C引脚前应该先置为OUT及高电平。这在上电初始化时无虞,因为MCU复位后IO口为输入,并由外部上拉电阻拉为高电平。但在做故障恢复时很重要,因为此时IO口可能正被Master或Slave拉成低电平。 http://www.stmcu.org/module/forum/thread-518463-1-1.html
3. 这个解决方案和上面思想两个相仿,但是写了太多代码,又有放置位置的要求,看起来头大。仅作参考:http://bbs.ednchina.com/BLOG_ARTICLE_2154168.HTM
4. 最重要的说明,在ST官方提供的STM32F4xx用户指南:RM0090 Reference manual Rev9,第845页,关于I2C_CR1,SWRST位的Note,提到解决BUSY死锁问题:
 
意思是说SWRST位可以在出错或死锁时,用于复位I2C控制器,例如众所周知的BUSY位问题。我没有看其它老STM型号的手册,至少STM32F4xx有SWRST位,STM32L0xx用户指南提到可以用PE位复位。

四.问题的解决方案
按照ST手册的提示,经过各种尝试,本着尽量少改动代码、尽量不改动固件库里只读文件的原则,我的解决方案如下所述。假设主程序里有如下的代码,返回值ret不等于0表示出错,按stm32f4xx_hal_def.h头文件中的错误代码定义,返回值为0x02是HAL_BUSY,0x03是HAL_TIMEOUT,这两个返回值都可能得到。下面程序里红色的两行是错误处理必须的:

4.1 主程序改动,加错误处理代码2行:
unsigned char ret = Sensor_ReadData(uint8* buf);   // I2C读写函数
    if (ret != 0)  {                   //I2C故障处理
      HAL_I2C_DeInit(&hi2c1);        //释放IO口为GPIO,复位句柄状态标志
      HAL_I2C_Init(&hi2c1);          //这句重新初始化I2C控制器
    }
    else  {
      // 。。。。I2C无错误时的正常程序
    }


4.2 子程序的改动,加7行代码:
上面HAL_I2C_Init(&hi2c1)函数会调用HAL_I2C_MspInit(hi2c)函数,这个函数在stm32f4xx_hal_msp.c文件中实现,主要是初始化IO口以及外设,由STM32CubeMX工具生成或用户自行编写,非只读文件。以下节选该函数第一段,其中I2C端口用哪个pin,是由用户自己设定的,我这里用的PB6、PB7。红、绿底色的几行是为了处理BUSY死锁问题专门插入的。

void HAL_I2C_MspInit(I2C_HandleTypeDef *hi2c)
{
  GPIO_InitTypeDef GPIO_InitStruct;
  if (hi2c->Instance==I2C1)
  {
    __I2C1_CLK_ENABLE();
    // PB6    ---->   I2C1_SCL
    // PB7    ---->   I2C1_SDA
    // strong pull-up high to recover from locking in BUSY state
    GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7;      //此行原有
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;   //GPIO配置为输出
    GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;         //强上拉
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); 

    HAL_GPIO_WritePin(GPIOB, 6, GPIO_PIN_SET);       //拉高SCL
    HAL_GPIO_WritePin(GPIOB, 7, GPIO_PIN_SET);       //拉高SDA

    hi2c->Instance->CR1 = I2C_CR1_SWRST;          //复位I2C控制器
    hi2c->Instance->CR1 = 0;              //解除复位(不会自动清除)
// 以下是原有代码
GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FAST;
    GPIO_InitStruct.Alternate = GPIO_AF4_I2C1;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
  }
//。。。
}

上面程序中,把I2C端口配置成GPIO-OUTPUT,并强制拉高,是必需的。注意到手册里关于SWRST位说明的第一句:“When set, the I2C is under reset state. Before resetting this bit, make sure the I2C lines are released and the bus is free.” 意思就是置位SWRST,会使I2C控制器保持在复位状态。解除复位前,确保I2C总线已经释放到空闲状态,即SCL、SDA均为高电平,再恢复I2C控制器。所以解除复位是用户来做的,硬件不会自动清除该位。

五.结论
我用这款STM32F439IGT单片机,I2C部分没有出现断电才能解除BUSY死锁的严重问题,看来STM已经意识到这个硬BUG,并在后期产品里逐步进行了改进。
在没有硬件死穴的情况下,我这里仅增加10行程序,就可以用软件恢复故障。多次尝试,触发I2C故障时,一次就可以恢复,无需加延时等语句,也未改动现有固件库代码。

                                        
以上部分内容参考了别人的博客,以下为我的解决方式,测试有效:

我是将scl SDA配置为推挽输出,将两个管脚置高后关闭IIC,然后再重新初始化IIC达到复位IIC总线的目的,修复总线死锁问题。
 

### 回答1: STM32F103IIC是一款由意法半导体(STMicroelectronics)推出的具有I2C接口的32位ARM微控制器。当需要对STM32F103IIC进行复位时,可以按照以下步骤进行操作: 1. 确保I2C总线和其他外部设备已经断开连接,避免因外部干扰导致复位失败。 2. 在STM32F103IIC的复位引脚(RST)上施加低电平信号,一般可通过按下复位按钮或者外部控制电路来实现。确保该信号持续足够时间以确保复位的有效性(一般建议持续5毫秒以上)。 3. 解除复位引脚上的低电平信号,使其回复正常工作状态。 4. 等待一段时间,通常为几个毫秒,以确保芯片内部电路充分复位完成。 5. 在复位完成后,重新连接I2C总线和其他外部设备,以便正常进行通信或其他操作。 需要注意的是,如果复位时没有对芯片的电源进行控制,建议在复位之前将电源关闭,以避免进行错误的操作导致芯片损坏。 以上就是在使用STM32F103IIC进行复位时的基本操作步骤。具体的步骤可能会因具体的应用场景和硬件设计而有所不同,需要根据实际情况进行调整。同时,也可以参考STM32F103IIC的技术文档和用户手册来获取更详细和准确的复位操作指导。 ### 回答2: stm32f103iic芯片的复位是一种将芯片恢复到初始状态的操作。在使用stm32f103iic芯片时,可能会遇到各种问题,如系统崩溃、死循环等,此时可以通过复位来重新启动芯片并解决问题。 stm32f103iic芯片的复位有两种方式:软复位和硬复位。 软复位是通过软件编程来实现的,可以通过设置寄存器中的特定位来触发复位。软复位可以主动控制复位的时机和范围,灵活性较高。通过软复位,可以重置中央处理器核心、外设和存储器,并清除所有的寄存器值。 硬复位是通过外部电路或者特定的引脚来触发的,当电路中的复位引脚被拉低时,芯片会进入复位状态。硬复位一般无法控制复位时机和范围,但是可以在系统发生严重错误时,通过硬复位来强制重启芯片。 无论是软复位还是硬复位,都能够将芯片恢复到初始状态,重新启动系统。但是需要注意的是,在进行复位操作之前,应该保存重要的数据和状态,以防丢失。 总之,stm32f103iic芯片的复位是一种重要的功能,能够帮助解决系统中的问题,确保系统正常运行。 ### 回答3: 当STM32F103IIC芯片遇到复位条件时,系统将会进行复位操作。复位操作可以采用软件复位或硬件复位两种方式。 软件复位是通过执行软件指令将芯片进行复位。在软件中,我们可以将相应的寄存器设置为特定的值,来触发软件复位。通过软件复位,可以对特定的模块或寄存器进行复位操作,而不需要影响整个芯片。 硬件复位是通过外部信号或特定的电路进行复位。例如,我们可以通过将RESET引脚拉低来触发硬件复位。在硬件复位时,整个芯片的所有模块、寄存器和状态将会被重置为初始状态。 在STM32F103IIC芯片中,复位操作将使得所有的寄存器和模块回到默认状态。同时,复位将会清除各个模块的状态和配置。因此,在复位后,所有的寄存器和模块都将重新开始初始化。 需要注意的是,在进行复位操作之前,我们应该先保存在芯片中的重要数据。复位操作将会清除这些数据,因此在复位之前需要备份关键数据以免丢失。 总之,当STM32F103IIC芯片遇到复位条件时,系统将会进行复位操作。复位操作可以采用软件复位或硬件复位两种方式,复位将会重置整个芯片的状态和配置。在复位之前,需要备份关键数据以免丢失。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值