I2C接口是一种使用非常普遍的MCU与外部设备的接口方式,在STM32中也集成了I2C接口,我们也常常使用它来与外围的传感器等设备通讯。
最近在我们使用STM32F1VET6读取压力和温湿度传感器数据时,就是使用I2C接口来实现通讯的。但在使用I2C和STM32F1的标准库读取数据时出现了死机的现象。其现象是这样的,程序可以顺利的运行,但I2C没有数据返回。用示波器查看波形时,发现SCL的电平时钟为高,而SDA的电平时钟为低。如果拔掉对应的设备,SCL的波形则恢复正常。接上设备恢复正常,但运行一会现象依旧。
一开始以为是连接的设备有问题,于是换了一台设备,发现依旧如此。难道真的是I2C出现了死锁现象。那我们看看究竟怎么样的情况下I2C才会发生死锁现象呢?
在I2C主设备进行读写操作的过程中,主设备在开始信号后控制SCL产生8个时钟脉冲,然后拉低SCL信号为低电平,在这个时候,从设备输出应答信号,将SDA信号拉为低电平。如果这个时候主设备异常复位,SCL就会被释放为高电平。此时,如果从设备没有复位,就会继续I2C的应答,将SDA一直拉为低电平,直到SCL变为低电平,才会结束应答信号。
而对于I2C主设备来说.复位后检测SCL和SDA信号,如果发现SDA信号为低电平,则会认为I2C总线被占用,会一直等待SCL和SDA信号变为高电平。这样,I2C主设备等待从设备释放SDA信号,而同时I2C从设备又在等待主设备将SCL信号拉低以释放应答信号,两者相互等待,I2C总线进人一种死锁状态。同样,当I2C进行读操作,I2C从设备应答后输出数据,如果在这个时刻I2C主设备异常复位而此时I2C从设备输出的数据位正好为0,也会导致I2C总线进入死锁状态。
既然判断了是I2C出现了死锁,那我们有什么办法解决了?网上有不少高手提供了多种的解决方案。当然有些方法实现起来不那么容易,因为我们的硬件已经确定,软件业已经编好,做很大的改变是不现实的。
所以我选择在出现死锁时将I2C接口重新配置,并且将对应的GPIO端口配置为输出,并将电位拉高,然后再冲新配置I2C端口,但现象并未消除。于是我们将I2C的时钟也重新使能现象消除。这种不断重置I2C接口的方法虽然能够使得在死锁时能够恢复使用,但不停地解除和配置接口总是感觉并那么理想。
在我们的试验中我们发现,可以从软件和硬件的角度来解决这一问题。首先我们来说下软件方法:在I2C主设备中增加I2C总线恢复程序。每次I2C主设备复位后,如果检测到SDA数据线被拉低,则控制I2C中的SCL时钟线产生9个时钟脉冲(针对8位数据的情况),这样I2C从设备就可以完成被挂起的读操作,从死锁状态中恢复过来。
这种方法有很大的局限性,因为大部分主设备的I2C模块由内置的硬件电路来实现,软件并不能够直接控制SCL信号模拟产生需要时钟脉冲。其次我们说一下硬件方式:如硬件尚可修改,可以增加一个额外的总线恢复设备或者串入一个具有死锁恢复的I2C缓冲器都可以从硬件上解决这个问题。其实我们发现用GPIO模拟I2C通讯时,死锁现象是不会出现的。所以如果硬件不能改变,但软件容易改变时,我们可以考虑使用GPIO来模拟I2C通讯。