平台是STM32L011 ~ Nucleo 板~
一般来说I2C挂死大家说的不是SDA恒低还是SCL恒低.但是,还有一种挂死的EEPROM,致使大家觉得EEPROM很容易挂死,明明就是操作不当啊,哪里容易挂死了.
其实是因为EEPROM他有个内部搬移Buffers时间,这个时候,EEPROM的I2C无效,看起来就像这个从机突然间不见了,这也是大家说的,I2C锁死,其实是因为从机根本就不见了.
那么如何应对呢?网上有一种方法,就是延迟5毫秒,这5毫秒刚好是最长编程时间,如果是AT24C256,岂不是要等20毫秒不成?一点都不环保.这里可以查询ACK位,确定设备是否在.其实可以使用查询ACK的方法,如果等到主机可以收到ACK.那么从机就是复活了.
另外因为STM32自身的BUG,这个确实存在的BUG,就是I2C还没收到ACK时候,NACK的标志位置位,明明还没来ACK的时刻,你怎么确定ACK来了没呢?方法1,多读几次,浪费也是在所不惜的,方法2,延迟一个合理时刻,再来查询.
对比下方法1和方法2的区别.
1)第一下,试探下EEPROM是否能正常ACK.如果ACK,给0x1234位置写入0x33.
2)内部编程期间,EEPROM是不会响应的.
3)直到EEPROM重新响应,读出数据.
4)红框位置是编程时间,我是1ms一次查询.
如果换用方法2,应该会快一点,因为他是不断查询,不是间隔查询.这个间隔查询优点就是不占掉整个总线,中途有别的任务,还可以通过插入MUTEX分时控制.但是如果编程是在2.1ms完成,那么还多浪费0.9ms.
方法2就是一直查询.查询的时间约2.1ms编程完成,之前方法还真多浪费了我足足0.9ms啊.
但是方法2带来了BUG.就是查询到ACK,而STM32还是觉得他没ACK,那么怎么办呢.有没有方法1方法2都完美结合的方法呢?有的.
既然方法1的浪费是因为1毫秒查一次,那么是否缩短一些会更好呢?又不至于一直在发数据,特别是洁癖党,看到这么多ACK还不立马用,感觉不爽.改成0.1ms,也就是100us,看起来就查询频繁一些了.
也没有浪费ACK的嫌疑了.不是查询越频繁越好,而是查询的时刻恰好就是最好.说了这么多,还是没贴代码啊.别急,首先,发出一个START,就能判断是否NACK了,但是STM32不能这么任性.因为CR2寄存器的NBYTES不能为0.
(不过好像也是合理,一般情况谁只发START不执行东西.)
#define I2C_ADDR_NACK 1 #define I2C_OK 0 uint8_t I2C_HW_Write(uint8_t Addr, uint16_t Reg, uint8_t Value) { LL_I2C_HandleTransfer(I2C1, Addr, LL_I2C_ADDRSLAVE_7BIT, 1, LL_I2C_MODE_AUTOEND, LL_I2C_GENERATE_START_WRITE); LL_I2C_TransmitData8(I2C1, 0x00); while(!LL_I2C_IsActiveFlag_TXE(I2C1)) { if(LL_SYSTICK_IsActiveCounterFlag() && LL_I2C_IsActiveFlag_NACK(I2C1)) { return I2C_ADDR_NACK; } } LL_I2C_ClearFlag_STOP(I2C1); LL_I2C_HandleTransfer(I2C1, Addr, LL_I2C_ADDRSLAVE_7BIT, 3, LL_I2C_MODE_AUTOEND, LL_I2C_GENERATE_START_WRITE); while(LL_I2C_IsActiveFlag_ADDR(I2C1)) { } LL_I2C_TransmitData8(I2C1, ((Reg & 0xFF00) >> 8)); while(!LL_I2C_IsActiveFlag_TXE(I2C1)) { } LL_I2C_TransmitData8(I2C1, (Reg & 0x00FF)); while(!LL_I2C_IsActiveFlag_TXE(I2C1)) { } LL_I2C_TransmitData8(I2C1, Value); while(!LL_I2C_IsActiveFlag_TXE(I2C1)) { } LL_I2C_ClearFlag_STOP(I2C1); return I2C_OK; } uint8_t I2C_HW_Read(uint8_t Addr, uint16_t Reg, uint8_t *Value) { LL_I2C_HandleTransfer(I2C1, Addr, LL_I2C_ADDRSLAVE_7BIT, 1, LL_I2C_MODE_AUTOEND, LL_I2C_GENERATE_START_WRITE); LL_I2C_TransmitData8(I2C1, 0x00); while(!LL_I2C_IsActiveFlag_TXE(I2C1)) { if(LL_SYSTICK_IsActiveCounterFlag() && LL_I2C_IsActiveFlag_NACK(I2C1)) { return I2C_ADDR_NACK; } } LL_I2C_ClearFlag_STOP(I2C1); LL_I2C_HandleTransfer(I2C1, Addr, LL_I2C_ADDRSLAVE_7BIT, 2, LL_I2C_MODE_SOFTEND, LL_I2C_GENERATE_START_WRITE); while(LL_I2C_IsActiveFlag_ADDR(I2C1)) { } LL_I2C_TransmitData8(I2C1, ((Reg & 0xFF00) >> 8)); while(!LL_I2C_IsActiveFlag_TXE(I2C1)) { } LL_I2C_TransmitData8(I2C1, (Reg & 0x00FF)); while(!LL_I2C_IsActiveFlag_TXE(I2C1)) { } LL_I2C_HandleTransfer(I2C1, Addr, LL_I2C_ADDRSLAVE_7BIT, 1, LL_I2C_MODE_AUTOEND, LL_I2C_GENERATE_RESTART_7BIT_READ); while(!LL_I2C_IsActiveFlag_RXNE(I2C1)) { } *Value = LL_I2C_ReceiveData8(I2C1); LL_I2C_ClearFlag_STOP(I2C1); return I2C_OK; }
有人问,为什么中间不跳出,因为在中间卡死的概率,我也没发现,这肯定就叫总线死锁了,还没发生过.
总线锁死,这个不能怪STM32,如果用过其他MCU就知道,实际是I2C自己太脆弱.不过问题也很好解决的.合适的上拉,程序靠谱,不随意复位MCU.就不会发生了.这代码我跑了一晚上,第二天醒来发现是EEPROM坏了1Bit,读取写入还是正常.
另外,题外话,我算了下,按4ms一次编程,1秒就是25次,一天86400秒,2160000次一天,就是2160K次一天,而现在新EEPROM才1KK次能力,这个超过2KK次了,岂不是可以很容易写挂他?另外有条件,最好是不修改SysTick而是再开个定时器做小于1ms的溢出,这样不破坏系统的LL_mDelay这些函数.
请问楼主的这些时序图是什么生成的
@nick 逻辑分析仪