TaterLi's LazyBlog

自言自语,不喜绕路,科学上网,远离天国.

@TaterLi2月前

12/30
16:05
技术控

I2C另类挂死分析 – AT24C256

平台是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这些函数.

I2C另类挂死分析 – AT24C256