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