18.1 內(nèi)部 E2PROM 簡介
單片機在運行時數(shù)據(jù)均存儲在內(nèi)部 RAM(隨機存儲器)中,在掉電時無法保存數(shù)據(jù)。前面提到過可以通過增加外部存儲器 AT24C01 芯片的方式解決,但因為需要增加外部電路,性價比并不高,因此不推薦該方法。STC89C51、52 內(nèi)部都自帶有 2K 字節(jié)的 E2PROM??赏ㄟ^對 STC 單片機內(nèi)部的 E2PROM 編程來實現(xiàn),這樣節(jié)省了片外資源,使用也比較方便。
STC 單片機內(nèi)部的 E2PROM 并不是真正的 E2PROM,而是用 DATA FLASH 模擬出來的,因此操作方法與普通 E2PROM 不同。STC 單片機內(nèi)部的 E2PROM 采用的是 IAP(在應(yīng)用編程)技術(shù)實現(xiàn)讀寫操作,擦寫次數(shù)可達 100,000 次以上。所謂 IAP 指程序在運行時程序存儲器可有程序本身進行擦寫。IAP 是相對 ISP 而言的,下面進行詳細的分析。
18.2 ISP 和 IAP 區(qū)別
ISP: In System Programable 是指在系統(tǒng)編程,通俗的講,就是片子已經(jīng)焊板子上,不用取下,就可以簡單而方便地對其進行編程。比如我們通過電腦給 STC 單片機下載程序。
IAP: In Application Programable 是指在應(yīng)用編程,就是片子提供一系列的機制(硬件/軟件上的)當片子在運行程序的時候可以提供一種改變存儲器數(shù)據(jù)的方法。通俗點講,也就是說程序自己可以往程序存儲器里寫數(shù)據(jù)或修改程序。這種方式的典型應(yīng)用就是用一小段代碼來實現(xiàn)程序的下載,實際上單片機的 ISP 功能就是通過 IAP 技術(shù)來實現(xiàn)的,即片子在出廠前就已經(jīng)有一段小的 boot 程序在里面,片子上電后,開始運行這段程序,當檢測到上位機有下載要求時,便和上位機通信,然后下載程序到程序存儲區(qū)。
以 STC89C52 為例進行分析,存儲空間包括 8KB flash 程序存儲空間、512B RAM 數(shù)據(jù)存儲空間、2KB E2PROM 存儲空間。在 51 單片機中采用的是數(shù)據(jù)和程序存儲地址空間并行的哈佛結(jié)構(gòu),地址分配如下所示:
8KB flash 地址:0——1FFFH
512B RAM 地址:0——0200H
2KB E2PROM 地址:2000H——27FFH
ISP 操作對象為 8KB flash,IAP 的操作對象為 2KBE2PROM,IAP 不能對 flash 進行讀寫操作。IAP 在讀寫操作的結(jié)果為,將要寫入的值與 E2PROM 中原來的值進行與操作然后將結(jié)果存入。例如在地址 2000H 處第一次成功寫入 11010110,第二次寫入 00111010,讀出的結(jié)果將會是這兩個結(jié)果的相與 0010010,因此如果寫入數(shù)據(jù)前該處數(shù)據(jù)不為 FFH,那么寫入的數(shù)據(jù)將會不正確。IAP 的擦除操作的功能就是將數(shù)據(jù)變?yōu)?FFH,但擦除操作是以扇區(qū)為基本操作單位的,STC89C52 的 E2PROM 扇區(qū)地址安排如下表所示。每個扇區(qū)的大小為 512B。
數(shù)據(jù)存儲操作按照以下步驟進行:
1. 寫操作之前先將對應(yīng)扇區(qū)的有效數(shù)據(jù)讀取到 RAM 中暫存(這步不是必須的);
2. 對整個扇區(qū)進行擦除操作,擦除后該扇區(qū)的數(shù)據(jù)均為 FFH;
3. 將要寫入的字節(jié)寫入;
4. 將暫存的數(shù)據(jù)寫入;
STC 單片機 IAP 程序操作步驟如下:
1. 配置 ISP_CONTR 寄存器,使能第 7 位 ISPEN,讓 ISP_IAP 功能生效,并配置低三位的等待時間;
2. 寫指令:讀/寫/擦除,3 個命令;
3. 賦值 ISP_ADDRH 和 ISP_ADDRL 的地址值,分別為所要操作位置的地址高低位;
4. 關(guān)閉總中斷 EA,因為下面要寫的 2 個觸發(fā)指令必須是連續(xù)操作;
5. 執(zhí)行 ISP_IAP 觸發(fā)指令,觸發(fā)后才能進行讀寫;
6. 打開總中斷 EA,關(guān)閉 ISP_IAP 功能,清除相關(guān)寄存器。
IAP 及 E2PROM 新增特殊功能寄存器如下圖所示:
1. ISP_DATA:ISP/IAP 數(shù)據(jù)寄存器
ISP/IAP 操作時的數(shù)據(jù)存儲器,ISP/IAP 從 Flash 讀出來的數(shù)據(jù)存放在此處,向 Flash 寫的數(shù)據(jù)也需要放在此處。
2. ISP_ADDRH/ISP_ADDRL:ISP/IAP 地址寄存器
分別為地址的高、低八位,復(fù)位值為 0x0000。
3. ISP_CMD:ISP/IAP 命令寄存器
MS1 MS0=00
待機模式,無數(shù)據(jù)讀寫操作;
MS1 MS0=01
從應(yīng)用程序區(qū)對”Data Flash/E2PROM 區(qū)”進行字節(jié)讀命令
MS1 MS0=10
從應(yīng)用程序區(qū)對”Data Flash/E2PROM 區(qū)”進行字節(jié)寫命令
MS1 MS0=11
從應(yīng)用程序區(qū)對”Data Flash/E2PROM 區(qū)”進行扇區(qū)擦除命令
4. ISP_TRIG:ISP/IAP 命令觸發(fā)寄存器
在 ISPEN(ISP_CONTR.7)=1 時,對 ISP_TRIG 先寫入 0x46,再寫入 0xB9,ISP/IAP 功能才會生效。
5. ISP_CONTR:ISP/IAP 控制寄存器
ISPEN:ISP/IAP 功能允許位。ISPEN=0,禁止 ISP/IAP 讀、寫、擦除操作。ISPEN=1,允許 ISP/IAP 讀、寫、擦除操作。
SWBS:0 表示,軟件從應(yīng)用程序區(qū)啟動,1 表示,從系統(tǒng) ISP 監(jiān)控程序區(qū)啟動。需與 SWRST 配合使用。
SWRST:0 不操作,1 表示產(chǎn)生軟件系統(tǒng)復(fù)位,硬件自動復(fù)位。
SWBS=1,SWRST=1 時,表示在應(yīng)用程序區(qū)軟件復(fù)位并從系統(tǒng) ISP 監(jiān)控程序區(qū)開始執(zhí)行程序。SWBS=0,SWRST=1 時,表示在應(yīng)用程序區(qū)軟件復(fù)位并從應(yīng)用程序區(qū)開始處執(zhí)行程序。
B2~B0 表示在讀、寫、擦除操作過程中 CPU 插入的等待時間,推薦選擇如下所示。
18.3 E2PROM 驅(qū)動函數(shù)編寫
前面已經(jīng)講解了與內(nèi)部 E2PROM 有關(guān)的 6 個寄存器的功能,下面我們結(jié)合這些寄存器編寫驅(qū)動函數(shù),因為在正常的 reg52.h 中并沒有對上述 6 個特殊功能寄存器進行聲明,所以首先得進行聲明以及名字字節(jié)定義,如下代碼所示:
/****************特殊功能寄存器聲明****************/
sfr ISP_DATA = 0xE2;
sfr ISP_ADDRH = 0xE3;
sfr ISP_ADDRL = 0xE4;
sfr ISP_CMD = 0xE5;
sfr ISP_TRIG = 0xE6;
sfr ISP_CONTR = 0xE7;
/******************定義命令字節(jié)******************/
#define read_cmd 0x01 //讀命令
#define wirte_cmd 0x02 //寫命令
#define erase_cmd 0x03 //擦除命令
/****定義操作等待時間以及允許IAP操作*******/
#define enable_waitTime 0x82 //系統(tǒng)工作時鐘< 20MHz 時
接下來兩個函數(shù)分別為關(guān)閉、開啟 ISP/IAP 功能函數(shù),以便后續(xù)調(diào)用,如下所示:
void ISP_IAP_disable(void)//關(guān)閉ISP_IAP
{
EA=1;//恢復(fù)中斷
ISP_CONTR = 0x00;
ISP_CMD = 0x00;
ISP_TRIG = 0x00;
}
void ISP_IAP_trigger()//開啟
{
EA=0; //下面的2條指令必須連續(xù)執(zhí)行,故關(guān)中斷
ISP_TRIG = 0x46;//送觸發(fā)命令字0x46
ISP_TRIG = 0xB9;//送觸發(fā)命令字0xB9
}
如上所示,在開啟功能也成為功能觸發(fā)函數(shù)時需要關(guān)閉系統(tǒng)中斷 EA,保證命令字 0x46、0xB9 被連續(xù)寫入。單片機對 E2PROM 的操作包括讀、寫以及擦除,讀數(shù)據(jù)操作步驟如下所示:
1. 清零數(shù)據(jù)寄存器 ISP_DATA,這一步不是必須的;
2. 向寄存器 ISP_CMD 寫入讀數(shù)據(jù)命令;
3. 允許 ISP/IAP,并給出操作等待時間;
4. 發(fā)送要讀取的目標數(shù)據(jù)的存儲地址;
5. 開啟 ISP/IAP 功能;
6. 讀出 ISP_DATA 中的數(shù)據(jù)并保存;
7. 關(guān)閉 ISP/IAP 功能;
上面講解的是讀取單個字節(jié)的步驟,如需讀取多個字節(jié)的數(shù)據(jù)只需重復(fù)第 4 到第 6 步,讀數(shù)據(jù)函數(shù)如下所示:
void ISP_IAP_readData(uint beginAddr, uchar* pBuf, uint dataSize) //讀取數(shù)據(jù)
{
ISP_DATA=0; //清零,不清也可以
ISP_CMD = read_cmd; //指令:讀取
ISP_CONTR = enable_waitTime;//開啟ISP_IAP,并送等待時間
while(dataSize--) //循環(huán)讀取
{
ISP_ADDRH = (uchar)(beginAddr > > 8); //送地址高字節(jié)
ISP_ADDRL = (uchar)(beginAddr & 0x00ff); //送地址低字節(jié)
ISP_IAP_trigger(); //觸發(fā)
beginAddr++; //地址++
*pBuf++ = ISP_DATA; //將數(shù)據(jù)保存到接收緩沖區(qū)
}
ISP_IAP_disable();//關(guān)閉ISP_IAP功能
}
寫數(shù)據(jù)函數(shù)與讀數(shù)據(jù)函數(shù)類似,如下所示:
void ISP_IAP_writeData(uint beginAddr,uchar* pDat,uint dataSize) //寫數(shù)據(jù)
{
ISP_CONTR = enable_waitTime; //開啟ISP_IAP,并送等待時間
ISP_CMD = wirte_cmd; //送字節(jié)編程命令字
while(dataSize--)
{
ISP_ADDRH = (uchar)(beginAddr > > 8); //送地址高字節(jié)
ISP_ADDRL = (uchar)(beginAddr & 0x00ff);//送地址低字節(jié)
ISP_DATA = *pDat++;//送數(shù)據(jù)
beginAddr++;
ISP_IAP_trigger();//觸發(fā)
}
ISP_IAP_disable(); //關(guān)閉
}
擦除扇區(qū)函數(shù)如下所示:
void ISP_IAP_sectorErase(uint sectorAddr)//扇區(qū)擦除
{
ISP_CONTR = enable_waitTime; //開啟ISP_IAP;并送等待時間
ISP_CMD = erase_cmd; //送扇區(qū)擦除命令字
ISP_ADDRH = (uchar)(sectorAddr > > 8); //送地址高字節(jié)
ISP_ADDRL = (uchar)(sectorAddr & 0X00FF);//送地址低字節(jié)
ISP_IAP_trigger();//觸發(fā)
ISP_IAP_disable();//關(guān)閉ISP_IAP功能
}
值得注意的是:在擦除扇區(qū)函數(shù)中,地址只需在該扇區(qū)范圍內(nèi)即可,不要求發(fā)送該扇區(qū)的首地址。到此我們編寫完成了所有函數(shù),因此將函整合到完整的驅(qū)動代碼中。"Drive_Eeprom.h "代碼如下:
#ifndef __Eeprom_H__
#define __Eeprom_H__
extern void ISP_IAP_disable(void);//關(guān)閉ISP_IAP
extern void ISP_IAP_trigger();//觸發(fā)
extern void ISP_IAP_readData(unsigned int beginAddr, unsigned char* pBuf, unsigned int dataSize);//讀取數(shù)據(jù)
extern void ISP_IAP_writeData(unsigned int beginAddr,unsigned char* pDat,unsigned int dataSize);//寫數(shù)據(jù)
extern void ISP_IAP_sectorErase(unsigned int sectorAddr);//扇區(qū)擦除
#endif
"Drive_Eeprom.c "代碼如下:
#include< reg52.h >
#define uint unsigned int
#define uchar unsigned char
/****************特殊功能寄存器聲明****************/
sfr ISP_DATA = 0xE2;
sfr ISP_ADDRH = 0xE3;
sfr ISP_ADDRL = 0xE4;
sfr ISP_CMD = 0xE5;
sfr ISP_TRIG = 0xE6;
sfr ISP_CONTR = 0xE7;
/******************定義命令字節(jié)******************/
#define read_cmd 0x01 //讀命令
#define wirte_cmd 0x02 //寫命令
#define erase_cmd 0x03 //擦除命令
/****定義操作等待時間以及允許IAP操作*******/
#define enable_waitTime 0x82 //系統(tǒng)工作時鐘< 20MHz 時
void ISP_IAP_disable(void)//關(guān)閉ISP_IAP
{
EA=1;//恢復(fù)中斷
ISP_CONTR = 0x00;
ISP_CMD = 0x00;
ISP_TRIG = 0x00;
}
void ISP_IAP_trigger()//觸發(fā)
{
EA=0; //下面的2條指令必須連續(xù)執(zhí)行,故關(guān)中斷
ISP_TRIG = 0x46;//送觸發(fā)命令字0x46
ISP_TRIG = 0xB9;//送觸發(fā)命令字0xB9
}
void ISP_IAP_readData(uint beginAddr, uchar* pBuf, uint dataSize) //讀取數(shù)據(jù)
{
ISP_DATA=0; //清零,不清也可以
ISP_CMD = read_cmd; //指令:讀取
ISP_CONTR = enable_waitTime;//開啟ISP_IAP,并送等待時間
while(dataSize--) //循環(huán)讀取
{
ISP_ADDRH = (uchar)(beginAddr > > 8); //送地址高字節(jié)
ISP_ADDRL = (uchar)(beginAddr & 0x00ff); //送地址低字節(jié)
ISP_IAP_trigger(); //觸發(fā)
beginAddr++; //地址++
*pBuf++ = ISP_DATA; //將數(shù)據(jù)保存到接收緩沖區(qū)
}
ISP_IAP_disable();//關(guān)閉ISP_IAP功能
}
void ISP_IAP_writeData(uint beginAddr,uchar* pDat,uint dataSize) //寫數(shù)據(jù)
{
ISP_CONTR = enable_waitTime; //開啟ISP_IAP,并送等待時間
ISP_CMD = wirte_cmd; //送字節(jié)編程命令字
while(dataSize--)
{
ISP_ADDRH = (uchar)(beginAddr > > 8); //送地址高字節(jié)
ISP_ADDRL = (uchar)(beginAddr & 0x00ff);//送地址低字節(jié)
ISP_DATA = *pDat++;//送數(shù)據(jù)
beginAddr++;
ISP_IAP_trigger();//觸發(fā)
}
ISP_IAP_disable(); //關(guān)閉
}
void ISP_IAP_sectorErase(uint sectorAddr)//扇區(qū)擦除
{
ISP_CONTR = enable_waitTime; //開啟ISP_IAP;并送等待時間
ISP_CMD = erase_cmd; //送扇區(qū)擦除命令字
ISP_ADDRH = (uchar)(sectorAddr > > 8); //送地址高字節(jié)
ISP_ADDRL = (uchar)(sectorAddr & 0X00FF);//送地址低字節(jié)
ISP_IAP_trigger();//觸發(fā)
ISP_IAP_disable();//關(guān)閉ISP_IAP功能
}
18.4 E2PROM 應(yīng)用
下面我們下一個小的應(yīng)用程序來驗證我們驅(qū)動函數(shù),函數(shù)實現(xiàn)的功能為記錄開發(fā)板上電的次數(shù)。并把上電的次數(shù),顯示到 1602 液晶顯示器上,主函數(shù)如下圖所示:
#include< reg52.h >
#include"Drive_1602.h"
#include"Drive_Eeprom.h"
#define uchar unsigned char
#define uint unsigned int
sbit DU = P2^7;//數(shù)碼管段選、位選引腳定義
sbit WE = P2^6;
sbit DU_L = P2^3;
uchar pbuf[5] = {0};//數(shù)據(jù)緩沖區(qū)
uchar str[8] = {0};//字符臨時變量
void main()
{
P3=0;
P0 = 0;//關(guān)閉所有數(shù)碼管
WE = 1;
WE = 0;
DU_L = 0;
Init_1602();//1602初始化
ISP_IAP_readData(0x21f0,pbuf,sizeof(pbuf)); //讀取內(nèi)部存儲器中數(shù)值
pbuf[0]++;
str[0] = pbuf[0]/100 + '0';
str[1] = (pbuf[0]%100)/10 + '0';
str[2] = pbuf[0]%10 + '0';
str[4] = '?';
Disp_1602_str(1,1,str);//顯示上電次數(shù)
ISP_IAP_sectorErase(0x2000); //扇區(qū)擦除,一塊512字節(jié)
ISP_IAP_writeData(0x21f0,pbuf,sizeof(pbuf)); //寫EEPROM
while(1);
}
將程序下載到單片機開發(fā)板觀察現(xiàn)象是否與預(yù)想的一致。
18.5 本章小結(jié)
本章詳細介紹了單片機內(nèi)部EEPROM的讀寫原理及驅(qū)動程序的編寫。
評論