一、簡介
在實際的應用中,保存在RAM中的數據掉電后就丟失了,保存在FLASH中的數據又不能隨意改變,也就是不能用它來記錄變化的數值。但是在某些特定場合,我們又確實需要記錄下某些數據,而它們還時常需要改變或更新,掉電之后數據還不能丟失。比如,我們的家用電表度數,電視機里邊的頻道記憶,一般都是使用EEPROM來保存數據,特點就是掉電后存儲的數據不丟失。
EEPROM (Electrically Erasable Programmable read only memory)是指帶電可擦可編程只讀存儲器。是一種掉電后數據不丟失的存儲芯片。EEPROM可以在電腦上或專用設備上擦除已有信息,重新編程。一般用在即插即用。一般情況下,EEPROM擁有30萬到100萬次的壽命,也就是它可以反復寫入30~100萬次,而讀取次數是無限的。
本文基于瑞芯微RK2206芯片 + Harmony LiteOS操作系統,通過i2c總線控制EEPROM讀寫。
二、硬件電路設計
我使用的EEPROM型號是K24C02,它是一個常用的基于IIC通信協議的EEPROM元件,例如ATMEL公司的AT24C02、CATALYST公司的CAT24C02和ST公司的ST24C02等芯片。IIC是一個通信協議,它擁有嚴密的通信時序邏輯要求,而EEPROM是一個元件,只是這個元件采樣了IIC協議的接口與單片機相連而已,二者并沒有必然的聯系,EEPROM可以用其它接口,I2C也可以用在其它很多器件上。根據K24C02芯片手冊,可獲知如下:
(1)K24C02芯片的從設備地址。因本章節采用的2Kbit的EEPROM,所以該芯片I2C從設備地址為0x51。如下圖所示:
圖K24C02的從設備地址圖
(2)K24C02芯片的讀操作
K24C02芯片的讀操作共分為3種,分別為當前地址讀(Current Address Read)、隨機讀(Random Read)和連續讀(Sequential Read)。
其中,當前地址讀(Current Address Read)操作是控制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的隨機地址讀操作
連續讀操作(Sequential Read)則控制控制i2c往K24C02發送N個字節,通信內容為:從設備地址(bit0為1,表示讀) + N個數據(K24C02發送給CPU)。具體數據傳輸如下圖所示:
圖 K24C02的連續讀操作
(3)K24C02芯片的寫操作
K24C02芯片寫數據操作可分為2種,分別為字節寫操作(Byte Write)和頁寫操作(Page Write)。
其中,字節寫操作(Byte Write)控制i2c與K24C02通信,通信內容為:從設備地址(1個字節,bit0為0,表示寫) + 存儲地址(1個字節) + 數據(1個字節,CPU發送給K24C0的存儲內容)。具體數據傳輸如下圖所示:
圖 K24C02的字節寫操作
頁寫操作(Page Write)則控制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("************ Eeprom Process ************\n");
printf("BlockSize = 0x%x\n", eeprom_get_blocksize());
/* 寫EEPROM */
memset(buffer, 0, sizeof(buffer));
for (unsigned int i = 0; i < FOR_CHAR; i++)
? ? ? ? {
? ? ? ? ? ? buffer[i] = data_offset + i;
? ? ? ? ? ? printf("Write Byte: %d = %c\n", addr_offset + i, buffer[i]);
? ? ? ? }
? ? ? ? ret = eeprom_write(addr_offset, buffer, FOR_CHAR);
? ? ? ? if (ret != FOR_CHAR)
? ? ? ? {
? ? ? ? ? ? printf("EepromWrite failed(%d)\n", ret);
? ? ? ? }
? ? ? ?
? ? ? ? /* 讀EEPROM */
? ? ? ? memset(buffer, 0, sizeof(buffer));
? ? ? ? ret = eeprom_read(addr_offset, buffer, FOR_CHAR);
? ? ? ? if (ret != FOR_CHAR)
? ? ? ? {
? ? ? ? ? ? printf("Read Bytes: failed!\n");
? ? ? ? }
? ? ? ? else
? ? ? ? {
? ? ? ? ? ? for (unsigned int i = 0; i < FOR_CHAR; i++)
? ? ? ? ? ? {
? ? ? ? ? ? ? ? printf("Read Byte: %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總線進行初始化。
#define EEPROM_I2C_BUS 0
#define EEPROM_I2C_ADDRESS 0x51
static I2cBusIo m_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,
};
static unsigned int m_i2c_freq = 100000;
unsigned int eeprom_init()
{
if (I2cIoInit(m_i2cBus) != LZ_HARDWARE_SUCCESS) {
printf("%s, %d: I2cIoInit failed!\n", __FILE__, __LINE__);
return __LINE__;
}
if (LzI2cInit(EEPROM_I2C_BUS, m_i2c_freq) != LZ_HARDWARE_SUCCESS) {
printf("%s, %d: I2cInit failed!\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);
return 0;
}
3、EEPROM讀操作程序設計
蜂鳴器控制程序當開啟蜂鳴器時,打開蜂鳴器,并且設置PWM波的周期為100毫秒,其中占空比50%;當關閉蜂鳴器時,則停止蜂鳴器。
#define EEPROM_I2C_BUS 0
#define EEPROM_I2C_ADDRESS 0x51
/* EEPROM型號:K24C02,2Kbit(256Byte),32頁,每頁8個字節(Byte) */
#define EEPROM_ADDRESS_MAX 256
#define EEPROM_PAGE 8
unsigned int eeprom_readbyte(unsigned int addr, unsigned char *data)
{
unsigned int ret = 0;
unsigned char buffer[1];
LzI2cMsg msgs[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);
return 0;
}
buffer[0] = (unsigned char)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: LzI2cTransfer failed(%d)!\n", __FILE__, __func__, __LINE__, ret);
return 0;
}
return 1;
}
unsigned int eeprom_read(unsigned int addr, unsigned char *data, unsigned int data_len)
{
unsigned int ret = 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);
return 0;
}
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);
return 0;
}
ret = eeprom_readbyte(addr, data);
if (ret != 1) {
printf("%s, %s, %d: EepromReadByte failed(%d)\n", __FILE__, __func__, __LINE__, ret);
return 0;
}
if (data_len > 1) {
ret = LzI2cRead(EEPROM_I2C_BUS, EEPROM_I2C_ADDRESS, &data[1], data_len - 1);
if (ret < 0) {
? ? ? ? ? ? printf("%s, %s, %d: LzI2cRead failed(%d)!\n", __FILE__, __func__, __LINE__, ret);
? ? ? ? ? ? return 0;
? ? ? ? }
}
? ? return data_len;
}
4、EEPROM寫操作程序設計
主程序根據存儲地址、存儲數據和數據長度的不同,選用字節寫操作或頁寫操作。具體源代碼如下所示:
#define EEPROM_I2C_BUS 0
#define EEPROM_I2C_ADDRESS 0x51
/* EEPROM型號:K24C02,2Kbit(256Byte),32頁,每頁8個字節(Byte) */
#define EEPROM_ADDRESS_MAX 256
#define EEPROM_PAGE 8
unsigned int eeprom_writebyte(unsigned int addr, unsigned char data)
{
unsigned int ret = 0;
LzI2cMsg msgs[1];
unsigned char buffer[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);
return 0;
}
buffer[0] = (unsigned char)(addr & 0xFF);
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: LzI2cTransfer failed(%d)!\n", __FILE__, __func__, __LINE__, ret);
return 0;
}
/* K24C02芯片需要時間完成寫操作,在此之前不響應其他操作*/
eeprog_delay_usec(1000);
return 1;
}
unsigned int eeprom_writepage(unsigned int addr, unsigned char *data, unsigned int data_len)
{
unsigned int ret = 0;
LzI2cMsg msgs[1];
unsigned char buffer[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);
return 0;
}
if ((addr % EEPROM_PAGE) != 0) {
printf("%s, %s, %d: addr(0x%x) is not page addr(0x%x)\n", __FILE__, __func__, __LINE__, addr, EEPROM_PAGE);
return 0;
}
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);
return 0;
}
if (data_len > EEPROM_PAGE) {
printf("%s, %s, %d: data_len(%d) > EEPROM_PAGE(%d)\n", __FILE__, __func__, __LINE__, data_len, EEPROM_PAGE);
return 0;
}
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: LzI2cTransfer failed(%d)!\n", __FILE__, __func__, __LINE__, ret);
return 0;
}
/* K24C02芯片需要時間完成寫操作,在此之前不響應其他操作*/
eeprog_delay_usec(1000);
return data_len;
}
unsigned int eeprom_write(unsigned int addr, unsigned char *data, unsigned int data_len)
{
unsigned int ret = 0;
unsigned int offset_current = 0;
unsigned int page_start, page_end;
unsigned char is_data_front = 0;
unsigned char is_data_back = 0;
unsigned int len;
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);
return 0;
}
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);
return 0;
}
/* 判斷addr是否是頁地址 */
page_start = addr / EEPROM_PAGE;
if ((addr % EEPROM_PAGE) != 0) {
page_start += 1;
is_data_front = 1;
}
/* 判斷addr + data_len是否是頁地址 */
page_end = (addr + data_len) / EEPROM_PAGE;
if ((addr + data_len) % EEPROM_PAGE != 0) {
page_end += 1;
is_data_back = 1;
}
offset_current = 0;
/* 處理前面非頁地址的數據,如果是頁地址則不執行 */
for (unsigned int i = addr; i < (page_start * EEPROM_PAGE); i++) {
? ? ? ? ret = eeprom_writebyte(i, data[offset_current]);
? ? ? ? if (ret != 1) {
? ? ? ? ? ? printf("%s, %s, %d: EepromWriteByte failed(%d)\n", __FILE__, __func__, __LINE__, ret);
? ? ? ? ? ? return offset_current;
? ? ? ? }
? ? ? ? offset_current++;
}
? ? /* 處理后續的數據,如果數據長度不足一個Page,則不執行 */
? ? for (unsigned int page = page_start; page < page_end; page++) {
? ? ? ? len = EEPROM_PAGE;
? ? ? ? if ((page == (page_end - 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: EepromWritePage failed(%d)\n", __FILE__, __func__, __LINE__, ret);
? ? ? ? ? ? return offset_current;
? ? ? ? }
? ? ? ? offset_current += EEPROM_PAGE;
}
? ? return data_len;
四、編譯過程
1、搭建和下載源代碼
我已將OpenHarmony源代碼上傳到Gitee社區中,大家可以根據以下網址下載。
https://gitee.com/Lockzhiner-Electronics/lockzhiner-rk2206-openharmony3.0lts
注意:編譯環境可根據以下網址來操作:https://gitee.com/Lockzhiner-Electronics/lockzhiner-rk2206-openharmony3.0lts/blob/master/vendor/lockzhiner/rk2206/README_zh.md
2、打開sdk下面路徑的文件
/vendor/lockzhiner/rk2206/samples/b3_eeprom/eeprom_example.c
注意:Gitee上的EEPROM案例為通用案例,請大家根據上述的需求修改相關源代碼。
3、修改編譯腳本
修改 vendor/lockzhiner/rk2206/sample 路徑下 BUILD.gn 文件,指定 eeprom_example 參與編譯。
"./b3_eeprom:eeprom_example",
修改 device/lockzhiner/rk2206/sdk_liteos 路徑下 Makefile 文件,添加 -leeprom_example 參與編譯。
hardware_LIBS = -lhal_iothardware -lhardware -leeprom_example
4、編譯固件
hb set -root .
hb set
hb build -f
5、燒寫固件
請參考Gitee網址的說明手冊(“燒錄打印”章節):https://gitee.com/Lockzhiner-Electronics/lockzhiner-rk2206-openharmony3.0lts/blob/master/device/rockchip/README_zh.md
五、實驗結果
程序編譯燒寫到開發板后,按下開發板的RESET按鍵,通過串口軟件查看日志如下:
************ Eeprom Process ************
BlockSize = 0x8
Write Byte: 3 = !
Write Byte: 4 = "
Write Byte: 5 = #
Write Byte: 6 = $
Write Byte: 7 = %
Write Byte: 8 = &
Write Byte: 9 = '
Write Byte: 10 = (
Write Byte: 11 = )
Write Byte: 12 = *
Write Byte: 13 = +
Write Byte: 14 = ,
Write Byte: 15 = -
Write Byte: 16 = .
Write Byte: 17 = /
Write Byte: 18 = 0
Write Byte: 19 = 1
Write Byte: 20 = 2
Write Byte: 21 = 3
Write Byte: 22 = 4
Write Byte: 23 = 5
Write Byte: 24 = 6
Write Byte: 25 = 7
Write Byte: 26 = 8
Write Byte: 27 = 9
Write Byte: 28 = :
Write Byte: 29 = ;
Write Byte: 30 = <
Write Byte: 31 = =
Write Byte: 32 = >
Read Byte: 3 = !
Read Byte: 4 = "
Read Byte: 5 = #
Read Byte: 6 = $
Read Byte: 7 = %
Read Byte: 8 = &
Read Byte: 9 = '
Read Byte: 10 = (
Read Byte: 11 = )
Read Byte: 12 = *
Read Byte: 13 = +
Read Byte: 14 = ,
Read Byte: 15 = -
Read Byte: 16 = .
Read Byte: 17 = /
Read Byte: 18 = 0
Read Byte: 19 = 1
Read Byte: 20 = 2
Read Byte: 21 = 3
Read Byte: 22 = 4
Read Byte: 23 = 5
Read Byte: 24 = 6
Read Byte: 25 = 7
Read Byte: 26 = 8
Read Byte: 27 = 9
Read Byte: 28 = :
Read Byte: 29 = ;
Read Byte: 30 = <
Read Byte: 31 = =
Read Byte: 32 = >
......
審核編輯:湯梓紅
-
EEPROM
+關注
關注
9文章
1020瀏覽量
81605 -
開發板
+關注
關注
25文章
5050瀏覽量
97472 -
RK2206
+關注
關注
0文章
34瀏覽量
1788 -
小凌派
+關注
關注
0文章
27瀏覽量
1202
發布評論請先 登錄
相關推薦
評論