GD32 访问 EEPROM 有时会发现数据写操作后不生效,需要写的数据没有正确写入。查阅了网上的一些文章,都没有得到很好的解答和正确修复。STM32或其他MCU访问EEPROM也可能遇到类似问题。
我们来看下GD32的EEPROM的官方代码:
at24cxx.h
at24cxx.c
我们发现在处理硬件flag置位时为防止程序卡死,at24cxx.h 设置了重试次数的限制:
#define AT24CXX_BIT_CHECK_SUCCESS(bit) \
{ \
int i = 0; \
for (i = 0; i < AT24CXX_BIT_CHECK_COUNT; i++) \
{ \
if (!(bit)) \
break; \
delay_while_ms(1); \
} \
if (i == AT24CXX_BIT_CHECK_COUNT) \
return; \
}
在实际判断中不使用 while(1) 而是使用以上代码,check硬件flag bit时,超出尝试次数就退出并且没有给出任何出错信息。看似程序执行完了实际上有可能是超过循环次数退出。
/* wait until I2C bus is idle */
AT24CXX_BIT_CHECK_SUCCESS(i2c_flag_get(I2C1, I2C_FLAG_I2CBSY));
/* send a start condition to I2C bus */
i2c_start_on_bus(I2C1);
/* wait until SBSEND bit is set */
AT24CXX_BIT_CHECK_SUCCESS(!i2c_flag_get(I2C1, I2C_FLAG_SBSEND));
/* send slave address to I2C bus */
i2c_master_addressing(I2C1, eeprom_address, I2C_TRANSMITTER);
/* wait until ADDSEND bit is set */
AT24CXX_BIT_CHECK_SUCCESS(!i2c_flag_get(I2C1, I2C_FLAG_ADDSEND));
/* clear the ADDSEND bit */
i2c_flag_clear(I2C1,Iw2C_FLAG_ADDSEND);
我们将头文件 AT24CXX_BIT_CHECK_SUCCESS 部分做如下修改:
#if 1
#define AT24CXX_BIT_CHECK_SUCCESS(bit) while(bit)
#else
#define AT24CXX_BIT_CHECK_SUCCESS(bit) \
{ \
int i = 0; \
for (i = 0; i < AT24CXX_BIT_CHECK_COUNT; i++) \
{ \
if (!(bit)) \
break; \
delay_while_ms(1); \
} \
if (i == AT24CXX_BIT_CHECK_COUNT) \
return; \
}
#endif
这样如果发生硬件flag bit没有置位就会卡死在 while(1) 循环的位置;
我们拿MDK调试时就可以查出程序在访问EEPROM时卡死在哪里。
经过调试,我们发现第二轮或更后几轮卡死在check I2C_FLAG_ADDSEND flag bit位置:
i2c_master_addressing(I2C1, eeprom_address, I2C_TRANSMITTER);
/* wait until ADDSEND bit is set */
AT24CXX_BIT_CHECK_SUCCESS(!i2c_flag_get(I2C1, I2C_FLAG_ADDSEND));
/* clear the ADDSEND bit */
i2c_flag_clear(I2C1,I2C_FLAG_ADDSEND);
就是GD32 在第n轮,发 EEPROM 写访问地址的时候卡死了。网上大部分的分析是I2C_FLAG_ADDSEND被置位了,但由于某些异常被意外快速清掉了。带着JTAG不行,全速跑就可以。修改个其他地方让异常不出现,等各种奇怪的解释和解法。
其实我们读一下EEPROM的手册就能找到正确答案和解法。EEPROM手册告诉我们写访问的典型时间是3ms , 最多5ms。那正确的分析就来了。GD32运行比较快,两次写操作之间的程序运行时间的间隔小于5ms就可能EEPROM 还处在上一次的写操作状态,不响应I2C的地址请求。而且这样也错过了GD32 发的I2C地址,这样就会一直卡在:
/* wait until ADDSEND bit is set */
AT24CXX_BIT_CHECK_SUCCESS(!i2c_flag_get(I2C1, I2C_FLAG_ADDSEND));
对这种情况我们有两种解法:
1) 对于时效性要求不高的系统简单粗暴的解法,在写函数开始时加6ms的延时,以保证上一次操作已经完成;
void eeprom_page_write(uint8_t* p_buffer, uint8_t write_address, uint16_t number_of_byte)
{
// Page write time is 3ms typical , max 5ms
delay_1ms(6) ;
// ===================================================================
/* wait until I2C bus is idle */
AT24CXX_BIT_CHECK_SUCCESS(i2c_flag_get(I2C1, I2C_FLAG_I2CBSY));
/* send a start condition to I2C bus */
i2c_start_on_bus(I2C1);
/* wait until SBSEND bit is set */
AT24CXX_BIT_CHECK_SUCCESS(!i2c_flag_get(I2C1, I2C_FLAG_SBSEND));
/* send slave address to I2C bus */
i2c_master_addressing(I2C1, eeprom_address, I2C_TRANSMITTER);
/* wait until ADDSEND bit is set */
AT24CXX_BIT_CHECK_SUCCESS(!i2c_flag_get(I2C1, I2C_FLAG_ADDSEND));
/* clear the ADDSEND bit */
i2c_flag_clear(I2C1,I2C_FLAG_ADDSEND);
2) 对于时效性要求高的系统我们需要在 I2C_FLAG_ADDSEND check 失败几轮后,
a. 停止 I2C 访问;
/* send a stop condition to I2C bus */
i2c_stop_on_bus(I2C1);
b. 重新 check I2C_FLAG_I2CBSY flag bit, 启动I2C;
/* wait until I2C bus is idle */
AT24CXX_BIT_CHECK_SUCCESS(i2c_flag_get(I2C1, I2C_FLAG_I2CBSY));
/* send a start condition to I2C bus */
i2c_start_on_bus(I2C1);
/* wait until SBSEND bit is set */
AT24CXX_BIT_CHECK_SUCCESS(!i2c_flag_get(I2C1, I2C_FLAG_SBSEND));
c. 然后重新发送 I2C 地址请求;
/* send slave address to I2C bus */
i2c_master_addressing(I2C1, eeprom_address, I2C_TRANSMITTER);
/* wait until ADDSEND bit is set */
AT24CXX_BIT_CHECK_SUCCESS(!i2c_flag_get(I2C1, I2C_FLAG_ADDSEND));
/* clear the ADDSEND bit */
i2c_flag_clear(I2C1,I2C_FLAG_ADDSEND);
这样就能保证在 EEPROM 在上一轮写操作完成后能迅速响应下一轮写操作,而不必等固定的6ms延时。
这是正确的分析和治本的解法。STM32或其他MCU访问EEPROM也可能遇到类似问题。
第二种解法的具体实现方法请大家自己完成。可以贴在评论区里。
————————————————
版权声明:本文为CSDN博主「小庐知行」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/tideyin123/article/details/150869332
|
|