您的位置:首页 > 其它

关于stm32f129的硬件I2C锁死的重要解决办法

2017-11-01 14:18 357 查看
首先感谢热心网友的无私奉献,先描述一下我的问题吧,我用的芯片是STM32F429IGTx并在和MPU9250/6050通信时测试多次软件复位发生锁死,硬件电源复位后一切正常,和摄像头通信的时候并未出现过I2C锁死的问题。

关于硬件IIC可以参见我之前写的帖子http://bbs.21ic.com/icview-1034518-1-1.html,写的也是够详细了,这里发现的另外的一个问题,

硬件iic这里没有任何问题,是iic从设备的一些问题,eeprom就没有类似问题,mpu6050就有这类拉低SDA锁死的问题,我之前的解决办法都是对mpu进行断电复位才能解决,但是现在发现了一个新的解决办法。
现象描述我就转一个博客好了 转自 “张鹏的博客”
I2C是由Philips公司发明的一种串行数据通信协议,仅使用两根信号线:SerialClock(简称SCL)和SerialData(简称SDA)。I2C是总线结构,1个Master,1个或多个Slave,各Slave设备以7位地址区分,地址后面再跟1位读写位,表示读(=1)或者写(=0),所以我们有时也可看到8位形式的设备地址,此时每个设备有读、写两个地址,高7位地址其实是相同的。

I2C数据格式如下:

无数据:SCL=1,SDA=1;

开始位(Start):当SCL=1时,SDA由1向0跳变;

停止位(Stop):当SCL=1时,SDA由0向1跳变;

数据位:当SCL由0向1跳变时,由发送方控制SDA,此时SDA为有效数据,不可随意改变SDA;

当SCL保持为0时,SDA上的数据可随意改变;

地址位:定义同数据位,但只由Master发给Slave;

应答位(ACK):当发送方传送完8位时,发送方释放SDA,由接收方控制SDA,且SDA=0;

否应答位(NACK):当发送方传送完8位时,发送方释放SDA,由接收方控制SDA,且SDA=1。

当数据为单字节传送时,格式为:

开始位,8位地址位(含1位读写位),应答,8位数据,应答,停止位。

当数据为一串字节传送时,格式为:

开始位,8位地址位(含1位读写位),应答,8位数据,应答,8位数据,应答,……,8位数据,应答,停止位。

需要注意的是:

1,SCL一直由Master控制,SDA依照数据传送的方向,读数据时由Slave控制SDA,写数据时由Master控制SDA。当8位数据传送完毕之后,应答位或者否应答位的SDA控制权与数据位传送时相反。

2,开始位“Start”和停止位“Stop”,只能由Master来发出。

3,地址的8位传送完毕后,成功配置地址的Slave设备必须发送“ACK”。否则否则一定时间之后Master视为超时,将放弃数据传送,发送“Stop”。

4,当写数据的时候,Master每发送完8个数据位,Slave设备如果还有空间接受下一个字节应该回答“ACK”,Slave设备如果没有空间接受更多的字节应该回答“NACK”,Master当收到“NACK”或者一定时间之后没收到任何数据将视为超时,此时Master放弃数据传送,发送“Stop”。

5,当读数据的时候,Slave设备每发送完8个数据位,如果Master希望继续读下一个字节,Master应该回答“ACK”以提示Slave准备下一个数据,如果Master不希望读取更多字节,Master应该回答“NACK”以提示Slave设备准备接收Stop信号。

6,当Master速度过快Slave端来不及处理时,Slave设备可以拉低SCL不放(SCL=0将发生“线与”)以阻止Master发送更多的数据。此时Master将视情况减慢或结束数据传送。

在实际应用中,并没有强制规定数据接收方必须对于发送的8位数据做出回应,尤其是在Master和Slave端都是用GPIO软件模拟的方法来实现的情况下,编程者可以事先约定数据传送的长度,slave不检查NACK,有时可以起到减少系统开销的效果。但是如果slave方是硬件i2c要求一定要标准的NACK,master方是GPIO软件模拟i2c并没有正确的发送NACK,就会出现“slave收不到stop”导致i2c挂死。

在正常情况下,I2C总线协议能够保证总线正常的读写操作。但是,当I2C主设备异常复位时(看门狗动作,板上电源异常导致复位芯片动作,手动按钮复位等等)有可能导致I2C总线死锁产生。下面详细说明一下总线死锁产生的原因。
[align=left]    在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总线进入死锁状态。[/align]
[align=left]方法[/align]
[align=left]    (1)尽量选用带复位输人的I2C从器件。[/align]
    (2)将所有的从I2C设备的电源连接在一起,通过MOS管连接到主电源,而MOS管的导通关断由I2C主设备来实现。

    (3)在I2C从设备设计看门狗的功能。
[align=left]    (4)在I2C主设备中增加I2C总线恢复程序。[/align]
[align=left]        每次I2C主设备复位后,如果检测到SDA数据线被拉低,则控制I2C中的SCL时钟线产生9个时钟脉冲(针对8位数据的情况,“9个clk可以激活”的方法来自NXP的文档,NXP(Philips)作为I2C总线的鼻祖,这样的说法是可信的),这样I2C从设备就可以完成被挂起的读操作,从死锁状态中恢复过来。[/align]
[align=left]        这种方法有很大的局限性,因为大部分主设备的I2C模块由内置的硬件电路来实现,软件并不能够直接控制SCL信号模拟产生需要时钟脉冲。[/align]
[align=left]        或者,发送I2C_Stop条件也能让从设备释放总线。
[/align]
[align=left]        如果是GPIO模拟I2C总线实现,那么在I2C操作之前,加入I2C总线状态检测 I2C_Probe ,如果总线被占用,则可尝试恢复总线,待总线释放后,再进行操作。要保证I2C操作最小单元的完整性,不被其他事件(中断、高优先级线程,等)打断。
[/align]
[align=left]  (5)在I2C总线上增加一个额外的总线恢复设备。这个设备监视I2C总线。当设备检测到SDA信号被拉低超过指定时间时,就在SCL总线上产生9个时钟脉冲,使I2C从设备完成读操作,从死锁状态上恢复出来。总线恢复设备需要有具有编程功能,一般可以用单片机或CPLD实现这一功能。[/align]
[align=left]  (6)在I2C上串人一个具有死锁恢复的I2C缓冲器,如Linear公司的LTC4307是一个双向的I2C总线缓冲器,并且具有I2C总线死锁恢复的功能。LTC4307总线输入侧连接主设备,总线输出侧连接所有从设备。当LTC4307检测到输出侧SDA或SCL信号被拉低30ms时,就自动断开I2C总线输入侧与输出侧的连接.并且在输出侧SCL信号上产生16个时钟脉冲来释放总线。当总线成功恢复后,LTC4307会再次连接输入输出侧,使总线能够正常工作。[/align]
[align=left]    好了,现象这里人家已经描述了很清楚了,很多人都说硬件iic是内部控制,没法执行操作控制scl引脚输出脉冲,其实可以做到的,在进行iic配置的时候,首先对scl硬件进行配置成开漏,而不是复用开漏,这样就掌握了scl引脚的主动权,控制其输出9个时钟脉冲,然后再配置成复用开漏,再配置iic就可以了,这就是先解决从设备的潜在问题,再进行使用,思路已经说的很清楚了,下面贴一个示例代码。[/align]
RCC_APB1PeriphClockCmd(SENSORS_I2C_RCC_CLK, ENABLE);
RCC_APB1PeriphClockCmd(SENSORS_I2C_SCL_GPIO_CLK, ENABLE);

/** 加入一段释放总线的操作,9个SCL时钟脉冲,防止从器件一些极端情况拉低占用总线不释放导致死锁 **/
GPIO_InitStructure.GPIO_Pin =  SENSORS_I2C_SCL_GPIO_PIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_Init(SENSORS_I2C_SCL_GPIO_PORT, &GPIO_InitStructure);
for(i = 0; i < 10; i++) 			//产生9个SCL时钟脉冲
{
GPIO_SetBits(SENSORS_I2C_SCL_GPIO_PORT, SENSORS_I2C_SCL_GPIO_PIN);
Delay_us(100);
GPIO_ResetBits(SENSORS_I2C_SCL_GPIO_PORT, SENSORS_I2C_SCL_GPIO_PIN);
Delay_us(100);
}
/* Enable I2Cx clock */
RCC_APB1PeriphClockCmd(SENSORS_I2C_RCC_CLK, ENABLE);

/* Enable I2C GPIO clock */
RCC_AHB1PeriphClockCmd(SENSORS_I2C_SCL_GPIO_CLK | SENSORS_I2C_SDA_GPIO_CLK, ENABLE);


[align=left]这里循环进行了10次,实际算下脉冲能保证一定有9个。[/align]
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐