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(在應用編程)技術(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 是指在應用編程,就是片子提供一系列的機制(硬件/軟件上的)當片子在運行程序的時候可以提供一種改變存儲器數(shù)據(jù)的方法。通俗點講,也就是說程序自己可以往程序存儲器里寫數(shù)據(jù)或修改程序。這種方式的典型應用就是用一小段代碼來實現(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. 寫操作之前先將對應扇區(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 地址寄存器
分別為地址的高、低八位,復位值為 0x0000。
3. ISP_CMD:ISP/IAP 命令寄存器
MS1 MS0=00
待機模式,無數(shù)據(jù)讀寫操作;
MS1 MS0=01
從應用程序區(qū)對”Data Flash/E2PROM 區(qū)”進行字節(jié)讀命令
MS1 MS0=10
從應用程序區(qū)對”Data Flash/E2PROM 區(qū)”進行字節(jié)寫命令
MS1 MS0=11
從應用程序區(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 表示,軟件從應用程序區(qū)啟動,1 表示,從系統(tǒng) ISP 監(jiān)控程序區(qū)啟動。需與 SWRST 配合使用。
SWRST:0 不操作,1 表示產(chǎn)生軟件系統(tǒng)復位,硬件自動復位。
SWBS=1,SWRST=1 時,表示在應用程序區(qū)軟件復位并從系統(tǒng) ISP 監(jiān)控程序區(qū)開始執(zhí)行程序。SWBS=0,SWRST=1 時,表示在應用程序區(qū)軟件復位并從應用程序區(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;//恢復中斷
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ù)只需重復第 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;//恢復中斷
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 應用
下面我們下一個小的應用程序來驗證我們驅(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)象是否與預想的一致。
18.5 本章小結(jié)
本章詳細介紹了單片機內(nèi)部EEPROM的讀寫原理及驅(qū)動程序的編寫。
評論