背景
24AA64_24LC64类比AT24C02是嵌入式开发过程中常用存储芯片,这些芯片基本上都是管脚兼容,可以做到PIN2PIN替换,但是硬件替换后了,软件可以正常跑吗?这就对软件设计提出要求。这个问题也是本文探讨的问题之一,并给出工程代码,通过简单修改相关参数可以适配不同的EEPROM。
EEPROM存储芯片接口最常用的就是接口的I2C,从软件使用角度进行分析说明。软件设计采用分层理念,保证尽量简洁,方便器件替换后进行更改。
文章使用资源含工程代码、手册、文档等
24AA64-24LC64STM32软件采用分层设计思想,方便替换EERPOM,方便随时添加、删除存储变量,含工程代码说明文档资源-CSDN文库
简介
Microchip Technology Inc. 24AA64/24LC64(24XX64*) 是一款 64 Kb 电可擦除 PROM。该器件由 8 个 1K x 8 位模块组成具有 2 线串行接口的存储器。低压设计允许在低至 1.8V 的电压下工作,具有待机电压有功电流分别为 1 μA 和 1 mA。它专为高级、低功耗应用而开发,例如个人通信或数据收购。24XX64 还具有对高达 32 字节数据的页面写入功能。功能地址行允许同一总线上最多有 8 个设备,最多512 KB 地址空间。24XX64 提供标准 8 引脚 PDIP、表面贴装 SOIC、TSSOP和 MSOP 软件包。
硬件设计
这里STM32_I2C3_SCL/STM32_I2C3_SDA连接STM32F407如下表所示:
Label | 24AA64T/24LC64 | STM32F407 |
STM32_I2C3_SCL | SCL | PH7 |
STM32_I2C3_SDA | SDA | PH8 |
器件地址
器件的地址换算,如下图所示,根据原理图设计,这里取A0=0/A1=0/A2=1。
IIC时序
字节写
按照主节点的 START 条件,控制代码(4 位)、片选(3 位)和R/W 位(为逻辑低电平)被同步到总线。这向地址高字节将寻址的从属接收器follow 之后,在第九个 clock cycle。因此,主服务器发送的下一个字节是字的高阶字节address 的 URL 中,并且将被写入24XX64.下一个字节是 Least Significant地址字节。收到另一个确认后主设备将传输来自 24XX64 的信号要写入寻址存储器的数据字
位置。24XX64 再次确认,并且master 生成 STOP 条件。这将启动内部写入周期,在此期间,24XX64不会生成 Acknowledge 信号(图 4-1)。如果尝试使用 WP 引脚写入数组高举。
页写
写控制字节、字地址和第一个数据字节传输到 24XX64 的方式与在字节写入中。但主服务器不会生成 STOP 条件,而是最多传输 31 个额外的字节它们临时存储在片上页面缓冲区中并将在 master 具有传输了 STOP 条件。收到每个word 的 5 个较低地址指针位在内部递增 1。如果 master 应该传输更多在生成 STOP 条件之前小于 32 字节,地址计数器将滚动,之前的接收到的数据将被覆盖。与字节写入一样操作中,一旦收到 STOP 条件,就会触发一个内部写入周期将开始(图 4-2)。如果尝试用于在 WP 引脚保持高电平的情况下写入数组,设备将确认命令,但不确认写入循环,不会写入任何数据,并且设备将立即接受新命令。
时序很重要,代码其实都是在模拟操作出来时序,这里有个需要注意的细节,就是每次写字节、页写完成后,下次写入的时间延时是多少。如下图所示,从手册中获取页延时是2ms。
软件设计
综述
软件采用STM32CubeIDE创建工程,使用串口1为调试打印串口。
根据系统需要随时存储一些变量,可以把所有需要存储的变量打包到同一个结构体里面,针对该结构体进行存储。例如
extern struct FlashParagma_t
{
INT32U Flag;//0x5A0FF0A5
uint16_t number_of_boots;
uint16_t test_data2;
uint16_t test_data3;
uint16_t test_data4;
uint16_t test_data5;
INT32U CheckSum;
}FlashParagma;
实际使用过程中,仅仅需要存储该结构体即可,当然可以复制存储一份,作为备份参数使用,备份参数和实际存储的位置要保证物理隔离,不要有在存储器上有重叠地址出现。
如下图所示,核心代码架构如下图所示,应用层程序对上述结构体进行维护,通过调用存储器读写函数进行结构体的存储,存储器读写函数通过调用函数I2C的时序函数进行补充。
读数据设计
关键函数如下,从24AA64_24LC64读取数据
INT8U FlashParagmaRead (void)
{
if(FM24C_R_SUCCESS != fm24c_read_nbyte(FLASH_PARAGRAM_START_ADDR, sizeof(FlashParagma) / sizeof(INT8U), ((INT8U *)&FlashParagma)))
{
printf("FlashParagmaRead err!\r\n");
}
else
{
if (FlashParagmaCheck (&FlashParagma) != OS_TRUE)
{
printf("FlashParagmaCheck err!\r\n");
}
else
return OS_TRUE;
}
//AT24CXX_Read(FLASH_PARAGRAM_START_ADDR, ((INT8U *)&FlashParagma), sizeof(FlashParagma) / sizeof(INT8U));
return OS_FALSE;
}
uint8_t fm24c_read_nbyte(uint32_t addr, uint16_t len, uint8_t *buf)
{
uint32_t i = 0;
I2C_start_1(); //启动I2C总线
I2C_write_byte_1(FM24C_DEVICE_ADDR_W); //发送器件写地址
d_us_1();
if (I2C_check_ack_1() == 0) //检测应答
return I2C_R_BIT_ERR;
I2C_write_byte_1((addr >> 8)&0xff); //发送将要处理数据的地址,高8位
d_us_1();
if (I2C_check_ack_1() == 0) //检测应答
return I2C_R_BIT_ERR;
I2C_write_byte_1(addr&0xff); //发送将要处理数据的地址,低8位
d_us_1();
if (I2C_check_ack_1() == 0) //检测应答
return I2C_R_BIT_ERR;
I2C_start_1(); //启动I2C总线
I2C_write_byte_1(FM24C_DEVICE_ADDR_R); //发送器件地址,读
d_us_1();
if (I2C_check_ack_1() == 0) //检测应答
return I2C_R_BIT_ERR;
d_us_1();
*buf++ = I2C_read_byte_1(); //读取第一个数据
d_us_1();
for (i = 1; i < len; i++)
{
d_us_1();
I2C_send_bit_0_1(); //主机应答(ACK),继续接受数据
*buf++ = I2C_read_byte_1(); //读取数据
}
I2C_send_bit_1_1(); //产生NO ACK信号结束读取数据
I2C_stop_1(); //停止I2C总线
return FM24C_R_SUCCESS;
}
写数据设计
将数据写入到24AA64_24LC64,这里每次页写入延时为20ms,其实看手册,可以修改为2ms,如果有大量数据写入的话,可以明显提升写入速度。这里写成20ms为了兼容其他型号的额EERPOM。
为了兼容同系列的EEPROM还是依照分页处理进行写入。写入的具体做法是:依据写入首地址是否为页起始地址和写入长度是否为页长度的整数倍,进行分情况进行写入操作。涉及到页写入时,需要延时一段时间,如果对时间要求较高,这个延时时间可以优化。这里需要注意的是不同的器件写入延时不一样,如果追求速度,可以去仔细调节一下代码延时。写入相对复杂,这段代码需要认真研读一下,关键函数如下
INT8U FlashParagmaWrite (void)
{
FlashParagmaFormat (&FlashParagma);
if(FM24C_W_SUCCESS == fm24c_write_nbyte(FLASH_PARAGRAM_START_ADDR, sizeof(FlashParagma) / sizeof(INT8U), ((INT8U *)&FlashParagma)))
{
return OS_TRUE;
}
//AT24CXX_Write(FLASH_PARAGRAM_START_ADDR, ((INT8U *)&FlashParagma), sizeof(FlashParagma) / sizeof(INT8U));
printf("FlashParagmaWrite err!\r\n");
return OS_FALSE;
}
#define MAXSIZE24C02 FM24C_TOTAL_SIZE //AT24C02���256�ֽ� 2K bits 32?
#define I2C_PAGESIZE FM24C_PAGE_SIZE //AT24C02ÿҳ8�ֽ�
uint8_t I2C_PageWrite(uint8_t* pBuffer, uint8_t WriteAddr, uint8_t n)
{
return fm24c_write_nbyte_no_pass(WriteAddr, n, pBuffer);
}
uint8_t I2C_BufferWrite(uint8_t* pBuffer, uint8_t WriteAddr, uint16_t n)
{
uint8_t NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, ret;
if(n > MAXSIZE24C02)
return FM24C_W_N_BYTE_ERR;
Addr = WriteAddr % I2C_PAGESIZE;
count = I2C_PAGESIZE - Addr;
NumOfPage = n / I2C_PAGESIZE;
NumOfSingle = n % I2C_PAGESIZE;
//If WriteAddr is I2C_PAGESIZE aligned
if(Addr == 0)
{
// If n < I2C_PAGESIZE
if(NumOfPage == 0)
{
ret = I2C_PageWrite(pBuffer, WriteAddr, NumOfSingle);
if(ret != FM24C_W_SUCCESS)
{
return ret;
}
delay_ms(20);
}
// If n > I2C_PAGESIZE
else
{
while(NumOfPage--)
{
ret = I2C_PageWrite(pBuffer, WriteAddr, I2C_PAGESIZE);
if(ret != FM24C_W_SUCCESS)
{
return ret;
}
delay_ms(20);
WriteAddr += I2C_PAGESIZE;
pBuffer += I2C_PAGESIZE;
}
if(NumOfSingle!=0)
{
// if(I2C_PageWrite(pBuffer, WriteAddr, NumOfSingle) == false)
// return false;
ret = I2C_PageWrite(pBuffer, WriteAddr, NumOfSingle);
if(ret != FM24C_W_SUCCESS)
{
return ret;
}
delay_ms(20);
}
}
}
// If WriteAddr is not I2C_PAGESIZE aligned
else
{
/* If n < I2C_PAGESIZE */
if(NumOfPage== 0)
{
// if(I2C_PageWrite(pBuffer, WriteAddr, NumOfSingle) == false)
// return false;
ret = I2C_PageWrite(pBuffer, WriteAddr, NumOfSingle);
if(ret != FM24C_W_SUCCESS)
{
return ret;
}
delay_ms(20);
}
/* If n > I2C_PAGESIZE */
else
{
n -= count;
NumOfPage = n / I2C_PAGESIZE;
NumOfSingle = n % I2C_PAGESIZE;
if(count != 0)
{
// if(I2C_PageWrite(pBuffer, WriteAddr, count) == false)
// return false;
ret = I2C_PageWrite(pBuffer, WriteAddr, count);
if(ret != FM24C_W_SUCCESS)
{
return ret;
}
delay_ms(20);
WriteAddr += count;
pBuffer += count;
}
while(NumOfPage--)
{
// if(I2C_PageWrite(pBuffer, WriteAddr, I2C_PAGESIZE) == false)
// return false;
ret = I2C_PageWrite(pBuffer, WriteAddr, I2C_PAGESIZE);
if(ret != FM24C_W_SUCCESS)
{
return ret;
}
delay_ms(20);
WriteAddr += I2C_PAGESIZE;
pBuffer += I2C_PAGESIZE;
}
if(NumOfSingle != 0)
{
// if(I2C_PageWrite(pBuffer, WriteAddr, NumOfSingle) == false)
// return false;
ret = I2C_PageWrite(pBuffer, WriteAddr, NumOfSingle);
if(ret != FM24C_W_SUCCESS)
{
return ret;
}
delay_ms(20);
}
}
}
//return true;
return FM24C_W_SUCCESS;
}
uint8_t fm24c_write_nbyte(uint32_t addr, uint32_t len, uint8_t *buf)
{
return I2C_BufferWrite(buf, addr, len);
}
测试记录
例程代码实现了统计开机次数计时,每次开机从EEPROM读取结构体变量的值,判断校验是否正确,不正确的话进行恢复出厂设置操作,正确的话,就将开机次数加1,写入到EEPROM.串口打印字符如下图所示,每次启动后,启动次数加1
主函数代码如下
/* USER CODE BEGIN 2 */
FlashInit();
FlashParagma.number_of_boots++;
FlashParagmaWrite();
/* USER CODE END 2 */
文章使用资源含工程代码、手册、文档等
24AA64-24LC64STM32软件采用分层设计思想,方便替换EERPOM,方便随时添加、删除存储变量,含工程代码说明文档资源-CSDN文库