STC8A芯片內部都有一定容量的Flash可以當作EEPROM。雖然擦除次數為10萬次+,低於真正EEPROM芯片的100萬次+,但是存儲一些不經常修改的數據還是沒有問題的,例如單片機的一些工作狀態參數,在最初調整正常后很少再做調整。這樣就可以省去一個EEPROM芯片的成本,大概1元左右。
1. EEPROM操作相關寄存器和基本函數
STC8的EEPROM操作涉及的寄存器一共有6個,分別是:
- 數據寄存器: IAP_DATA
- 高、低地址寄存器:IAP_ADDRH和IAP_ADDRL
- 命令寄存器:IAP_CMD
- 觸發寄存器:IAP_TRIG
- 控制寄存器:IAP_CONTR
手冊例程中給出了擦除、寫(編程)、讀(IAP模式和MOVC模式兩種)、關閉IAP模式的例程,直接拷貝到自己的項目中,根據需要稍作修改即可。
- 關閉IAP程序:void IapIdle()
- IAP模式及MOVC模式讀取程序:char IapRead(int addr)
- 寫(編程)程序:void IapProgram(int addr, char dat)
- 擦除程序:void IapErase(int addr)
2. 注意事項
以手冊中給出幾個底層例程為基礎,很容易就可編寫出項目中操作EEPROM的功能模塊。
有幾點需要注意的問題,記錄一下:
2.1 擦除操作是按照扇區進行的
與讀、寫操作不同,擦除操作是按照扇區進行的,也就是說要擦除的話,會把IapErase(int addr)函數中addr地址參數所在的512字節的扇區的內容統統刪除,給出的addr只要在同一個扇區,無論指向哪個具體的地址,都會擦除同一個扇區。原因如下:
手冊中記載“由於擦除是以 512 字節為單位進行操作的,所以執行擦除操作時所設置的目標地址的低 9位是無意義的。例如:執行擦除命令時,設置地址 1234H/1200H/1300H/13FFH,最終執行擦除的動作都是相同的,都是擦除 1200H~13FFH 這 512 字節”。
2.2 可自定義EEPROM大小的芯片,如何自定義
STC8系列芯片中的大部分型號EEPROM空間都是固定的,只有少部分型號可以自定義EEPROM空間大小,例如我使用的STC8A8K64S4A12。
該芯片有64K大小的Flash空間,在STC-ISP中按照扇區為單位(512字節,相當於0.5k)可定義0.5~64k大小的EEPROM空間。需要注意的是,自定義的EEPROM是被安排在64k Flash的末尾的,比如自定義了1k大小的EEPROM,那么前63k是Flash,最后1k是EEPROM。
在我的項目中,只有幾個系統參數需要調整,留下1個扇區0.5k大小的EEPROM空間就足夠了。
2.3 如何確定MOVC模式讀取的基地址
與IAP模式的讀取相比,MOVC模式的讀取快捷便利,但是需要提供扇區的基地址,也就是下面程序片段中的宏IAP_OFFSET。
像上面定義了0.5k大小的EEROM時,該如何確定這個基地址呢?具體方法如下:
- 64k Flash的最后一個字節的地址是0xFFFF
- 定義0.5k的EEPROM就往前查512個字節(0x200)作為基地址,也就是0xFE00
- 同樣也可計算出1k、2k、3k的基地址
1 //使用的時候,將對應宏前面的注釋符號去掉即可。 2 //#define IAP_OFFSET 0x2000 //STC8A8K08S4A12 3 //#define IAP_OFFSET 0x4000 //STC8A8K16S4A12 4 //#define IAP_OFFSET 0x6000 //STC8A8K24S4A12 5 //#define IAP_OFFSET 0x8000 //STC8A8K32S4A12 6 //#define IAP_OFFSET 0xA000 //STC8A8K40S4A12 7 //#define IAP_OFFSET 0xC000 //STC8A8K48S4A12 8 //#define IAP_OFFSET 0xE000 //STC8A8K56S4A12 9 //#define IAP_OFFSET 0xF000 //STC8A8K60S4A12 10 //#define IAP_OFFSET 0xF000 //STC8A8K60S4A12 11 /*自己計算給出*/ 12 //#define IAP_OFFSET 0xF300 //STC8A8K64S4A12-3k 13 //#define IAP_OFFSET 0xF800 //STC8A8K64S4A12-2k 14 //#define IAP_OFFSET 0xFC00 //STC8A8K64S4A12-1k 15 //#define IAP_OFFSET 0xFE00 //STC8A8K64S4A12-0.5k 16 unsigned char IapRead(int addr) 17 { 18 addr += IAP_OFFSET; //使用 MOVC 讀取 EEPROM 需要加上相應的偏移 19 return *(unsigned char code *)(addr); //使用 MOVC 讀取數據 20 }
2.4 如何確定擦除操作的時間
擦除操作需要相對准確的時間,時間短了還沒擦除干凈,時間長了又會影響Flash壽命。手冊給出了要求的時間范圍:
手冊指出,在擦除操作期間“此時 MCU 系統不給 CPU 供應時鍾, CPU 沒有時鍾,所以無法工作, 也就是說,針對 EEPROM 操作所需要的等待時間是硬件自動完成的,用戶不需要加額外的軟件延時。”
可見不能用常用的軟件延時方式來控制擦除時間。那么該如何確定具體的時間呢?
控制寄存器的B2~B0三個位的組合給出了對應的等待時間(以時鍾周期為單位),比如選擇011時,擦除操作就會等待60000個時鍾周期,如果單片機系統時鍾為12MHz,那么60000個時鍾周期耗時:1秒÷12MHz×60000個周期 = 5ms。5ms的時間正好位於要求的4~6ms之內。因此,當系統時鍾為12MHz時選擇“011”是恰當的。
如果選擇的系統時鍾沒有在手冊給出的舉例頻率之中(下表最后一列),按照上述計算方法計算一下,看是否在4~6ms之間即可。比如系統時鍾工作在11.0592MHz,1秒÷11.0592MHz×60000個周期 = 4.608ms,也在4~6微秒范圍之內,這時選擇“011”也是恰當的。(已經實際測試)
再試算一下18MHz的情況(沒有實際測試):
- 1秒÷18MHz×60000個周期 = 7.5ms,超過4~6ms范圍,選“011”長了!
- 1秒÷18MHz×100000個周期 = 5.5ms,符合4~6ms要求,選“010”組合正好!
1 //系統工作參數結構 2 typedef struct { 3 int systemSleepingTimeSpan; 4 float temperature; 5 float coolingTime; 6 float stopTime; 7 } SysParamTypedef; 8 9 /*************************************************** 10 *說明:從eeprom中讀取系統工作參數 11 *參數:db為系統工作參數結構類型的指針 12 ***************************************************/ 13 void GetDatabase(SysParamTypedef *db) 14 { 15 int i; 16 for(i = 0; i < sizeof(SysParamTypedef); i++) 17 { 18 *((char*)db + i) = IapRead(i); 19 } 20 } 21 22 /*************************************************** 23 *說明:向eeprom中寫入系統工作參數 24 *參數:db為系統工作參數結構類型的指針 25 ***************************************************/ 26 void SaveDatabase(SysParamTypedef *db) 27 { 28 int i; 29 if(IapErase(0) == FAILURE) 30 ErrorHandle(Error_IapFailure); 31 for(i = 0; i < sizeof(SysParamTypedef); i++) 32 { 33 if(IapWrite(i, *((char *)db + i)) == FAILURE) 34 ErrorHandle(Error_IapFailure); 35 } 36 }