小凌派-RK2206开发板:EEPROM存储案例

一、简介

在实际的应用中,保存在RAM中的数据掉电后就丢失了,保存在FLASH中的数据又不能随意改变,也就是不能用它来记录变化的数值。但是在某些特定场合,我们又确实需要记录下某些数据,而它们还时常需要改变或更新,掉电之后数据还不能丢失。比如,我们的家用电表度数,电视机里边的频道记忆,一般都是使用EEPROM来保存数据,特点就是掉电后存储的数据不丢失。

EEPROM(ElectricallyErasableProgrammablereadonlymemory)是指带电可擦可编程只读存储器。是一种掉电后数据不丢失的存储芯片。EEPROM可以在电脑上或专用设备上擦除已有信息,重新编程。一般用在即插即用。一般情况下,EEPROM拥有30万到100万次的寿命,也就是它可以反复写入30~100万次,而读取次数是无限的。

本文基于瑞芯微RK2206芯片+鸿蒙LiteOS操作系统,通过i2c总线控制EEPROM读写。

二、硬件电路设计

我使用的EEPROM型号是K24C02,它是一个常用的基于IIC通信协议的EEPROM元件,例如ATMEL公司的AT24C02、CATALYST公司的CAT24C02和ST公司的ST24C02等芯片。IIC是一个通信协议,它拥有严密的通信时序逻辑要求,而EEPROM是一个元件,只是这个元件采样了IIC协议的接口与单片机相连而已,二者并没有必然的联系,EEPROM可以用其它接口,I2C也可以用在其它很多器件上。根据K24C02芯片手册,可获知如下:

K24C02芯片的从设备地址。因本章节采用的2Kbit的EEPROM,所以该芯片I2C从设备地址为0x51。如下图所示:


图K24C02的从设备地址图

(2)K24C02芯片的读操作

K24C02芯片的读操作共分为3种,分别为当前地址读(CurrentAddressRead)、随机读(RandomRead)和连续读(SequentialRead)。

其中,当前地址读(CurrentAddressRead)操作是控制i2c与K24C02通信,通信内容为:从设备地址(1个字节,bit0为1,表示读)+数据(1个字节,K24C02发送给CPU的存储内容)。该读操作没有附带EEPROM的存储地址,存储地址是由上一次存储地址累加而来。如下图所示:


图K24C02的当前地址读操作

而随机读操作则控制i2c与K24C02进行2次通信:

第1次i2c通信:从设备地址(1个字节,bit0为0,表示写)+存储地址(1个字节,CPU发送给K24C02的存储地址)

第2次i2c通信:从设备地址(1个字节,bit0为1,表示读)+数据(1个字节,K24C02发送给CPU的存储内容)。

具体数据传输如下图所示:

图K24C02的随机地址读操作

连续读操作(SequentialRead)则控制控制i2c往K24C02发送N个字节,通信内容为:从设备地址(bit0为1,表示读)+N个数据(K24C02发送给CPU)。具体数据传输如下图所示:

图K24C02的连续读操作

(3)K24C02芯片的写操作

K24C02芯片写数据操作可分为2种,分别为字节写操作(ByteWrite)和页写操作(PageWrite)。

其中,字节写操作(ByteWrite)控制i2c与K24C02通信,通信内容为:从设备地址(1个字节,bit0为0,表示写)+存储地址(1个字节)+数据(1个字节,CPU发送给K24C0的存储内容)。具体数据传输如下图所示:

图K24C02的字节写操作

页写操作(PageWrite)则控制i2c与K24C02通信,通信内容为:从设备地址(1个字节,bit0为0,表示写)+存储地址(1个字节)+数据(N个字节,CPU发送给K24C0的存储内容)。其中,存储数据的N个字节,N不能超过Page大小(K24C02的页大小为8个字节)。

三、程序设计

程序控制RK2206芯片的I2C与K24C02芯片通信,每5秒往某一块存储空间(该存储空间地址依次累加)写入不同数据,然后再读取出来。

1、主程序设计

如图所示为EEPROM存储主程序流程图,开机LiteOS系统初始化后,进入主程序后。主程序首先初始化i2c总线。其次,程序进入主循环,每5秒将不同的数据写入到一块存储空间,然后再读取回去。其中,存储空间地址每次循环都累加32,数据也随着循环而累加1。

图主程序流程图

while(1){printf("************EepromProcess************\n");printf("BlockSize=0x%x\n",eeprom_get_blocksize());/*写EEPROM*/memset(buffer,0,sizeof(buffer));for(unsignedinti=0;iFOR_CHAR;i++){buffer[i]=data_offset+i;printf("WriteByte:%d=%c\n",addr_offset+i,buffer[i]);}ret=eeprom_write(addr_offset,buffer,FOR_CHAR);if(ret!=FOR_CHAR){printf("EepromWritefailed(%d)\n",ret);}/*读EEPROM*/memset(buffer,0,sizeof(buffer));ret=eeprom_read(addr_offset,buffer,FOR_CHAR);if(ret!=FOR_CHAR){printf("ReadBytes:failed!\n");}else{for(unsignedinti=0;iFOR_CHAR;i++){printf("ReadByte:%d=%c\n",addr_offset+i,buffer[i]);}}data_offset++;if(data_offset=CHAR_END){data_offset=CHAR_START;}addr_offset+=FOR_ADDRESS;if(addr_offset=200){addr_offset=0;}printf("\n");LOS_Msleep(5000);}
2、EEPROM初始化程序设计

主程序通过控制RK2206芯片的接口对i2c总线进行初始化。

defineEEPROM_I2C_ADDRESS0x51staticI2cBusIom_i2cBus={.scl={.gpio=GPIO0_PA1,.func=MUX_FUNC3,.type=PULL_NONE,.drv=DRIVE_KEEP,.dir=LZGPIO_DIR_KEEP,.val=LZGPIO_LEVEL_KEEP},.sda={.gpio=GPIO0_PA0,.func=MUX_FUNC3,.type=PULL_NONE,.drv=DRIVE_KEEP,.dir=LZGPIO_DIR_KEEP,.val=LZGPIO_LEVEL_KEEP},.id=FUNC_ID_I2C0,.mode=FUNC_MODE_M2,};staticunsignedintm_i2c_freq=100000;unsignedinteeprom_init(){if(I2cIoInit(m_i2cBus)!=LZ_HARDWARE_SUCCESS){printf("%s,%d:I2cIoInitfailed!\n",__FILE__,__LINE__);return__LINE__;}if(LzI2cInit(EEPROM_I2C_BUS,m_i2c_freq)!=LZ_HARDWARE_SUCCESS){printf("%s,%d:I2cInitfailed!\n",__FILE__,__LINE__);return__LINE__;}/*GPIO0_A0=I2C1_SDA_M1*/PinctrlSet(GPIO0_PA0,MUX_FUNC3,PULL_NONE,DRIVE_KEEP);/*GPIO0_A1=I2C1_SCL_M1*/PinctrlSet(GPIO0_PA1,MUX_FUNC3,PULL_NONE,DRIVE_KEEP);return0;}
3、EEPROM读操作程序设计

蜂鸣器控制程序当开启蜂鸣器时,打开蜂鸣器,并且设置PWM波的周期为100毫秒,其中占空比50%;当关闭蜂鸣器时,则停止蜂鸣器。

defineEEPROM_I2C_ADDRESS0x51/*EEPROM型号:K24C02,2Kbit(256Byte),32页,每页8个字节(Byte)*/defineEEPROM_PAGE8unsignedinteeprom_readbyte(unsignedintaddr,unsignedchar*data){unsignedintret=0;unsignedcharbuffer[1];LzI2cMsgmsgs[2];/*K24C02的存储地址是0~255*/if(addr=EEPROM_ADDRESS_MAX){printf("%s,%s,%d:addr(0x%x)=EEPROM_ADDRESS_MAX(0x%x)\n",__FILE__,__func__,__LINE__,addr,EEPROM_ADDRESS_MAX);return0;}buffer[0]=(unsignedchar)addr;msgs[0].addr=EEPROM_I2C_ADDRESS;msgs[0].flags=0;msgs[0].buf=buffer[0];msgs[0].len=1;msgs[1].addr=EEPROM_I2C_ADDRESS;msgs[1].flags=I2C_M_RD;msgs[1].buf=data;msgs[1].len=1;ret=LzI2cTransfer(EEPROM_I2C_BUS,msgs,2);if(ret!=LZ_HARDWARE_SUCCESS){printf("%s,%s,%d:LzI2cTransferfailed(%d)!\n",__FILE__,__func__,__LINE__,ret);return0;}return1;}unsignedinteeprom_read(unsignedintaddr,unsignedchar*data,unsignedintdata_len){unsignedintret=0;if(addr=EEPROM_ADDRESS_MAX){printf("%s,%s,%d:addr(0x%x)=EEPROM_ADDRESS_MAX(0x%x)\n",__FILE__,__func__,__LINE__,addr,EEPROM_ADDRESS_MAX);return0;}if((addr+data_len)EEPROM_ADDRESS_MAX){printf("%s,%s,%d:addr+len(0x%x)EEPROM_ADDRESS_MAX(0x%x)\n",__FILE__,__func__,__LINE__,addr+data_len,EEPROM_ADDRESS_MAX);return0;}ret=eeprom_readbyte(addr,data);if(ret!=1){printf("%s,%s,%d:EepromReadBytefailed(%d)\n",__FILE__,__func__,__LINE__,ret);return0;}if(data_len1){ret=LzI2cRead(EEPROM_I2C_BUS,EEPROM_I2C_ADDRESS,data[1],data_len-1);if(ret0){printf("%s,%s,%d:LzI2cReadfailed(%d)!\n",__FILE__,__func__,__LINE__,ret);return0;}}returndata_len;}
4、EEPROM写操作程序设计

主程序根据存储地址、存储数据和数据长度的不同,选用字节写操作或页面写操作。具体源代码如下所示:

defineEEPROM_I2C_ADDRESS0x51/*EEPROM型号:K24C02,2Kbit(256Byte),32页,每页8个字节(Byte)*/defineEEPROM_PAGE8unsignedinteeprom_writebyte(unsignedintaddr,unsignedchardata){unsignedintret=0;LzI2cMsgmsgs[1];unsignedcharbuffer[2];/*K24C02的存储地址是0~255*/if(addr=EEPROM_ADDRESS_MAX){printf("%s,%s,%d:addr(0x%x)=EEPROM_ADDRESS_MAX(0x%x)\n",__FILE__,__func__,__LINE__,addr,EEPROM_ADDRESS_MAX);return0;}buffer[0]=(unsignedchar)(addr0xFF);buffer[1]=data;msgs[0].addr=EEPROM_I2C_ADDRESS;msgs[0].flags=0;msgs[0].buf=buffer[0];msgs[0].len=2;ret=LzI2cTransfer(EEPROM_I2C_BUS,msgs,1);if(ret!=LZ_HARDWARE_SUCCESS){printf("%s,%s,%d:LzI2cTransferfailed(%d)!\n",__FILE__,__func__,__LINE__,ret);return0;}/*K24C02芯片需要时间完成写操作,在此之前不响应其他操作*/eeprog_delay_usec(1000);return1;}unsignedinteeprom_writepage(unsignedintaddr,unsignedchar*data,unsignedintdata_len){unsignedintret=0;LzI2cMsgmsgs[1];unsignedcharbuffer[EEPROM_PAGE+1];/*K24C02的存储地址是0~255*/if(addr=EEPROM_ADDRESS_MAX){printf("%s,%s,%d:addr(0x%x)=EEPROM_ADDRESS_MAX(0x%x)\n",__FILE__,__func__,__LINE__,addr,EEPROM_ADDRESS_MAX);return0;}if((addr%EEPROM_PAGE)!=0){printf("%s,%s,%d:addr(0x%x)isnotpageaddr(0x%x)\n",__FILE__,__func__,__LINE__,addr,EEPROM_PAGE);return0;}if((addr+data_len)EEPROM_ADDRESS_MAX){printf("%s,%s,%d:addr+data_len(0x%x)EEPROM_ADDRESS_MAX(0x%x)\n",__FILE__,__func__,__LINE__,addr+data_len,EEPROM_ADDRESS_MAX);return0;}if(data_lenEEPROM_PAGE){printf("%s,%s,%d:data_len(%d)EEPROM_PAGE(%d)\n",__FILE__,__func__,__LINE__,data_len,EEPROM_PAGE);return0;}buffer[0]=addr;memcpy(buffer[1],data,data_len);msgs[0].addr=EEPROM_I2C_ADDRESS;msgs[0].flags=0;msgs[0].buf=buffer[0];msgs[0].len=1+data_len;ret=LzI2cTransfer(EEPROM_I2C_BUS,msgs,1);if(ret!=LZ_HARDWARE_SUCCESS){printf("%s,%s,%d:LzI2cTransferfailed(%d)!\n",__FILE__,__func__,__LINE__,ret);return0;}/*K24C02芯片需要时间完成写操作,在此之前不响应其他操作*/eeprog_delay_usec(1000);returndata_len;}unsignedinteeprom_write(unsignedintaddr,unsignedchar*data,unsignedintdata_len){unsignedintret=0;unsignedintoffset_current=0;unsignedintpage_start,page_;unsignedcharis_data_front=0;unsignedcharis_data_back=0;unsignedintlen;if(addr=EEPROM_ADDRESS_MAX){printf("%s,%s,%d:addr(0x%x)=EEPROM_ADDRESS_MAX(0x%x)\n",__FILE__,__func__,__LINE__,addr,EEPROM_ADDRESS_MAX);return0;}if((addr+data_len)EEPROM_ADDRESS_MAX){printf("%s,%s,%d:addr+len(0x%x)EEPROM_ADDRESS_MAX(0x%x)\n",__FILE__,__func__,__LINE__,addr+data_len,EEPROM_ADDRESS_MAX);return0;}/*判断addr是否是页地址*/page_start=addr/EEPROM_PAGE;if((addr%EEPROM_PAGE)!=0){page_start+=1;is_data_front=1;}/*判断addr+data_len是否是页地址*/page_=(addr+data_len)/EEPROM_PAGE;if((addr+data_len)%EEPROM_PAGE!=0){page_+=1;is_data_back=1;}offset_current=0;/*处理前面非页地址的数据,如果是页地址则不执行*/for(unsignedinti=addr;i(page_start*EEPROM_PAGE);i++){ret=eeprom_writebyte(i,data[offset_current]);if(ret!=1){printf("%s,%s,%d:EepromWriteBytefailed(%d)\n",__FILE__,__func__,__LINE__,ret);returnoffset_current;}offset_current++;}/*处理后续的数据,如果数据长度不足一个Page,则不执行*/for(unsignedintpage=page_start;pagepage_;page++){len=EEPROM_PAGE;if((page==(page_-1))(is_data_back)){len=(addr+data_len)%EEPROM_PAGE;}ret=eeprom_writepage(page*EEPROM_PAGE,data[offset_current],len);if(ret!=len){printf("%s,%s,%d:EepromWritePagefailed(%d)\n",__FILE__,__func__,__LINE__,ret);returnoffset_current;}offset_current+=EEPROM_PAGE;}returndata_len;}
四、编译过程

1、搭建和下载源代码

我已将OpenHarmony源代码上传到Gitee社区中,大家可以根据以下网址下载。

注意:编译环境可根据以下网址来操作:

2、打开sdk下面路径的文件

/vor/lockzhiner/rk2206/samples/b3_eeprom/eeprom_

注意:Gitee上的EEPROM案例为通用案例,请大家根据上述的需求修改相关源代码。

3、修改编译脚本

修改vor/lockzhiner/rk2206/sample路径下文件,指定eeprom_example参与编译。

"./b3_eeprom:eeprom_example",

修改device/lockzhiner/rk2206/sdk_liteos路径下Makefile文件,添加-leeprom_example参与编译。

hardware_LIBS=-lhal_iothardware-lhardware-leeprom_example

3、编译固件

4、烧写固件

请参考Gitee网址的说明手册(“烧录打印”章节):

五、实验结果

程序编译烧写到开发板后,按下开发板的RESET按键,通过串口软件查看日志如下:

************EepromProcess************BlockSize=0x8WriteByte:3=!WriteByte:4="WriteByte:5=ReadByte:6=$ReadByte:7=%ReadByte:8=ReadByte:9='ReadByte:10=(ReadByte:11=)ReadByte:12=*ReadByte:13=+ReadByte:14=,ReadByte:15=-ReadByte:16=.ReadByte:17=/ReadByte:18=0ReadByte:19=1ReadByte:20=2ReadByte:21=3ReadByte:22=4ReadByte:23=5ReadByte:24=6ReadByte:25=7ReadByte:26=8ReadByte:27=9ReadByte:28=:ReadByte:29=;ReadByte:30=ReadByte:31==ReadByte:32=

好了,今天的课程就到这里。

发布于 2025-03-24
116
目录

    推荐阅读