十四、FLASH快閃記憶體
FLASH簡介
-
STM32F1系列的FLASH包含程式儲存器、系統儲存器和選項位元組三個部分,透過快閃記憶體儲存器介面(外設)可以對程式儲存器和選項位元組進行擦除和程式設計
快閃記憶體儲存器介面是一個外設,是這個快閃記憶體的管理員,把我們的指令和資料,寫入到這個外設的相應暫存器,然後這個外設就會自動去操作對應的儲存空間。
-
讀寫FLASH的用途:
-
利用程式儲存器的剩餘空間來儲存掉電不丟失的使用者資料
它的程式儲存器容量是 64K,一般我們寫個簡單的程式,可能就只佔前面的很小一部分空間,剩下的大片空餘空間,我們就可以加以利用。比如儲存一些我們自定義的資料
注意:我們在選取儲存區域時,一定不要覆蓋了原有的程式,要不然程式自己把自己給破壞了,之後程式就執行不了了。
-
透過在程式中程式設計(IAP),實現程式的自我更新
利用程式,來修改程式本身,實現程式的自我更新。
-
-
線上程式設計(In-Circuit Programming – ICP)用於更新程式儲存器的全部內容,它透過JTAG、SWD協議或系統載入程式(Bootloader)下載程式
-
在程式中程式設計(In-Application Programming – IAP)可以使用微控制器支援的任一種通訊介面下載程式
首先需要自己寫一個 BootLoader 程式,並且存放在程式更新時不會覆蓋的地方,比如我們放在整個程式儲存器的後面。
需要更新程式時,我們控制程式跳轉到這個自己寫的 BootLoader 裡來,在這裡面,我們就可以接收任意一種通訊介面傳過來的資料,比如串列埠、USB、藍芽轉串列埠、WIFI 轉串列埠等等,這個傳過來的資料,就是待更新的程式
控制 FLASH 讀寫,把收到的程式,寫入到整個程式儲存器的前面程式正常執行的地方,寫完之後,再控制程式跳轉回正常執行的地方,或者直接復位,這樣程式就完成了自我升級。
注意:在程式設計過程中,任何讀寫快閃記憶體的操作都會使 CPU 暫停,直到此次快閃記憶體程式設計結束。這其實是讀寫內部快閃記憶體儲存資料的一個弊端,就是快閃記憶體忙的時候,程式碼執行會暫停,因為執行程式碼需要讀快閃記憶體,快閃記憶體在忙,沒法讀,所以 CPU 也就沒法執行了,程式就會暫停,這會導致什麼問題呢?假如你使用內部快閃記憶體儲存資料,同時你的中斷程式碼又是在頻繁執行的,這樣,讀寫快閃記憶體的時候,中斷程式碼就無法執行了,這可能會導致中斷無法及時響應
快閃記憶體模組組織(中容量)
這個表是中容量產品的快閃記憶體分配情況。我們 C8T6 晶片的快閃記憶體容量是 64K,屬於中容量產品。對於小容量產品和大容量產品,快閃記憶體的分配方式有些區別,這個可以參考一下手冊。
首先看一下第一列的幾個塊,這裡分為了 3 個塊,
-
主儲存器:也就是程式儲存器,用來存放程式程式碼的,這是最主要,也是容量最大的一塊。
對他進行了分頁,分頁是為了更好的管理快閃記憶體。每頁的大小都是 1K,0~127,總共 128 頁,總容量就是 128K,對於 C8T6 來說,它只有 64K,所以 C8T6 的頁只有一半,0~63,總共 64 頁,共 64K。
擦除和防寫,都是以頁為單位的,寫入前必須擦除,擦除必須以最小單位進行,擦除後資料位全變為 1,資料只能 1 寫 0,不能 0 寫 1,擦除和寫入之後都需要等待忙
頁的地址範圍:
- 第一個頁的起始地址就是程式儲存器的起始地址,0x0800 0000,之後就是一個位元組一個地址,依次線性分配了。
- 每頁起始地址的規律,首先是 0000,然後 0400、0800、0C00,再之後,1000,後面按照規律,就是 1400、1800、1C00、2000、2400、2800、2C00 等等等等,最後一直到 1 FC00,所以地址只要以 000、400、800、C00 結尾的都一定是頁的起始地址,之後如果想要給一個頁的起始地址,就需要用到這個規律。
-
資訊塊
-
啟動程式程式碼:存放的是原廠寫入的 BootLoader,用於串列埠下載
起始地址是 0x1FFF F000,他的容量是 2K
-
使用者選擇位元組:也就是選項位元組,存放一些獨立的引數,這個選項位元組
起始地址是 0x1FFF F800,容量是 16 個位元組,裡面只有幾個位元組的配置引數,這個後面還會繼續說的
-
-
快閃記憶體儲存器介面暫存器:這一塊的儲存器,實際上並不屬於快閃記憶體,地址都是 40 開頭的,說明這個儲存器介面暫存器就是一個普通的外設,他的儲存介質,也都是 SRAM。這個快閃記憶體儲存器介面,就是上面這些快閃記憶體的管理員,這些暫存器,就是用來控制擦除和程式設計這個過程的。
KEYR 鍵暫存器,SR 狀態暫存器,CR 控制暫存器等等常用暫存器
外設的起始地址是 0x4002 2000,每個暫存器都是 4 個位元組,也就是 32 位
FLASH基本結構
這裡程式儲存器以 C8T6 為例,它是 64K 的,所以總共只有 64 頁,最後一頁的起始地址是 0800 FC00。
左邊是快閃記憶體儲存器介面,手冊裡還有個名稱,快閃記憶體程式設計和擦除控制器(FPEC),這兩個名稱是一個東西。然後這個控制器,就是快閃記憶體的管理員,它可以對程式儲存器進行擦除和程式設計,也可以對選項位元組進行擦除和程式設計。當然系統儲存器是不能擦除和程式設計的。
選項位元組,裡面有很大一部分配置位,其實是配置主程式儲存器的讀防寫的,所以右邊畫的,寫入選項位元組可以配置程式儲存器的讀防寫
FLASH解鎖
-
FPEC共有三個鍵值:
-
RDPRT鍵 = 0x000000A5
解除讀保護
-
KEY1 = 0x45670123
-
KEY2 = 0xCDEF89AB
KEY1和KEY2解除防寫
-
-
解鎖:
-
復位後,FPEC被保護,不能寫入FLASH_CR
-
在FLASH_KEYR先寫入KEY1,再寫入KEY2,解除防寫
要先寫入 KEY1 ,再寫入 KEY2 ,最終才能解除防寫。
所以這個鎖的安全性非常高,有兩道鎖,即使你程式跑飛了也基本不可能解鎖
-
錯誤的操作序列會在下次復位前鎖死FPEC和FLASH_CR
一旦沒有先寫入 KEY1,再寫入 KEY2,整個模組就會完全鎖死,除非復位
-
-
加鎖:設定FLASH_CR中的LOCK位鎖住FPEC和FLASH_CR
就是控制暫存器裡面有個 LOCK 位,我們在這一位寫 1,就能重新鎖住快閃記憶體了。
使用指標訪問儲存器
#define __IO volatile
// 使用指標讀指定地址下的儲存器:
uint16_t Data = *((__IO uint16_t *)(0x08000000));
// 使用指標寫指定地址下的儲存器:
*((__IO uint16_t *)(0x08000000)) = 0x1234;
volatile,是一個安全保障措施,在程式邏輯上,沒有作用,加上這個關鍵字的目的,用一句話來說,就是防止編譯器最佳化。
快閃記憶體全擦除
把所有頁,都給擦除掉。
- 讀取 LOCK 位,看一下晶片鎖沒鎖。如果 LOCK 位 = 1,R鎖住了,就執行解鎖過程,解鎖過程就是在 KEYR 暫存器,先寫入 KEY1,再寫入 KEY2;但是在庫函式中,並沒有這個判斷,庫函式是直接執行解鎖過程,管你鎖沒鎖,都執行解鎖,這個比較簡單直接,不過效果都一樣。
- 解鎖之後,首先,置控制暫存器裡的 MER(Mass Erase)位為 1,然後再置 STRT(Start)位為 1,其中置 STRT 為 1 是觸發條件,STRT 為 1 之後,晶片開始幹活,然後晶片看到 MER 位是 1,它就知道,接下來要乾的活就是全擦除,這樣內部電路就會自動執行全擦除的過程。
- 擦除也是需要花一段時間的,所以擦除過程開始後,程式要執行等待。判斷狀態暫存器的 BSY(Busy)位是否為 1,BSY 位表示晶片是否處於忙狀態,BSY 為 1,表示晶片忙,所以這裡,如果判斷 BSY = 1,就跳轉回來,繼續迴圈判斷,直到 BSY = 0,跳出迴圈,這樣全擦除過程就結束了。
- 最後一步,這裡寫的是,讀出並驗證所有頁的資料,這個是測試程式才要做的,正常情況下,全擦除完成了,我們預設就是成功了。如果還要再全讀出來驗證一下,這個工作量太大了,所以這裡的最後一步,我們就不管了。
快閃記憶體頁擦除
STM32 的快閃記憶體也是寫入前必須擦除。擦除之後,所有的資料位變為 1,擦除的最小單位就是一頁,1K,1024 位元組。
這個也是類似的過程。
-
解鎖的流程
-
置控制暫存器的 PER(Page Erase)位為 1,然後在 AR(Address Register)地址暫存器中選擇要擦除的頁,最後,置控制暫存器的 STRT 位為 1,置 STRT 為 1,也是觸發條件
STRT 為 1,晶片開始幹活,然後晶片看到,PER = 1,他就知道接下來要執行頁擦除,但是晶片要知道要具體擦哪一頁,所以,它會繼續看 AR 暫存器的資料,AR 暫存器我們要提前寫入一個頁的起始地址,這樣晶片就會把我們指定的一頁,給擦除掉。
-
然後擦除開始之後,我們也需要等待 BSY 位。
-
最後,讀出並驗證資料,這個就不用看了。
快閃記憶體寫入
注意:STM32 的快閃記憶體再寫入之前會檢查指定地址有沒有擦除,如果沒有擦除就寫入,STM32 則不執行寫入操作,除非寫入的全是 0,這一個資料是例外,因為不擦除就寫入,可能會寫入錯誤,但全寫入 0 的話,寫入肯定是沒問題的。
-
第一步,也是解鎖。
-
第二步,我們需要置控制暫存器的 PG(Programming)位為 1,表示我們即將寫入資料。
-
第三步,在指定的地址寫入半字,需要用到使用指標訪問儲存器的
*((__IO uint16_t *)(0x08000000)) = 0x1234;
這句程式碼在指定地址寫入資料。寫入資料這個程式碼,就是觸發開始的條件,不需要像擦除一樣,置 STRT 位了。注意:寫入操作,只能以半字的形式寫入。
在 STM32 中,有幾個術語,字、半字和位元組,
-
字,Word,是 32 位資料;
-
半字,HalfWord,是 16 位資料;
-
位元組,Byte,是 8 位資料,
那這裡只能以半字寫入,意思就是隻能以 16 位的形式寫入,一次性,寫入兩個位元組;
如果你要寫入 32 位,就分兩次完成;
如果想單獨寫入一個位元組,還要保留另一個位元組的原始資料的話,就只能把整頁資料都讀到 SRAM,再隨意修改 SRAM 資料,修改全部完成之後,再把整頁都擦除,最後再把整頁都寫回去。
所以,如果你想像 SRAM 一樣隨心所欲地讀寫,那最好的辦法就是先把快閃記憶體的一頁讀到 SRAM 中,讀寫完成後,再擦除一頁,整體寫回去。那回到流程圖這裡
-
-
寫入半字之後,晶片會處於忙狀態,我們等待一下 BSY 清零,這樣寫入資料的過程就完成了。
選項位元組
選項位元組介紹
- RDP:寫入RDPRT鍵(0x000000A5)後解除讀保護
- USER:配置硬體看門狗和進入停機/待機模式是否產生復位
- Data0/1:使用者可自定義使用
- WRP0/1/2/3:配置防寫,每一個位對應保護4個儲存頁(中容量)
這裡是對應的 16 個位元組,其中有一半的名稱,前面都帶了個 n,比如 RDP 和 nRDP,USER 和 nUSER,等等,這個意思就是你在寫入 RDP 資料時,要同時在 nRDP 寫入資料的反碼,其他的這些都是一樣,寫入這個儲存器時,要在帶 n 的對應的儲存器寫入反碼,這樣寫入操作才是有效的,如果晶片檢測到這兩個儲存器不是反碼的關係,那就代表資料無效,有錯誤,對應的功能就不執行,這是一個安全保障措施。
每個儲存器的功能,去掉所有帶 n 的,就剩下 8 個位元組儲存器了。
-
RDP(Read Protect),是讀保護配置位,下面有解釋,在 RDP 暫存器寫入 RDPRT 鍵,就是剛才說的 A5,然後解除讀保護;如果 RDP 不是 A5,那快閃記憶體就是讀保護狀態,無法透過偵錯程式讀取程式,避免程式被別人竊取。
-
USER,這個是一些零碎的配置位,可以配置硬體看門狗和進入停機/待機模式是否產生復位,這個瞭解即可。
-
第三個和第四個位元組,Data0 和 Data1,這個在晶片中沒有定義功能,使用者可自定義使用。
-
WRP(Write Protect)0、1、2、3,這四個位元組,配置的是防寫。
在中容量產品裡,是每一個位對應保護 4 個儲存頁,4 個位元組,總共 32 位,一位對應保護 4 頁,總共保護 32*4 = 128 頁,正好對應中容量的最大 128 頁。
對於小容量產品,也是每一位對應保護 4 個儲存頁,但是小容量產品最大隻有 32K,所以只需要一個位元組 WRP0 就行,4*8 = 32,其他 3 個位元組沒用到。
對於大容量產品,每一個位只能保護 2 個儲存頁,這樣的話 4 個位元組就不夠用了,所以這裡規定 WRP3 的最高位,這一位直接把剩下的所有頁一起都保護了,這是防寫的定義。
擦除選項位元組
-
解鎖快閃記憶體
-
檢查FLASH_SR的BSY位,以確認沒有其他正在進行的快閃記憶體操作
-
解鎖FLASH_CR的OPTWRE位
解鎖 CR 的 OPTWRE(Option Write Enable)位,這一步是選項位元組的解鎖,選項位元組裡面還有一個單獨的鎖,在解鎖快閃記憶體後,還需要再解鎖選項位元組的鎖,之後才能操作選項位元組。
解鎖選項位元組的話,看一下快閃記憶體模組組織的暫存器,整個快閃記憶體的鎖是 KEYR,裡面選項位元組的小鎖,是下面的 OPTKEYR(Option Key Register),解鎖這個小鎖,也是類似的流程,我們需要在 OPTKEYR 裡,先寫入 KEY1,再寫入 KEY2,這樣就能解鎖選項位元組的小鎖了。
-
設定FLASH_CR的OPTER位為1
-
設定FLASH_CR的STRT位為1
-
等待BSY位變為0
-
讀出被擦除的選擇位元組並做驗證
寫入選項位元組
- 解鎖快閃記憶體
- 檢查FLASH_SR的BSY位,以確認沒有其他正在進行的程式設計操作
- 解鎖FLASH_CR的OPTWRE位
- 設定FLASH_CR的OPTPG位為1
- 寫入要程式設計的半字到指定的地址
- 等待BSY位變為0
- 讀出寫入的地址並驗證資料
和普通的快閃記憶體寫入也差不多,先檢測 BSY;然後解除小鎖;之後設定 CR 的 OPTPG(Option Programming)位為 1,表示即將寫入選項位元組;再之後,寫入要程式設計的半字到指定的地址,這個是指標寫入操作;最後,等待忙。這樣寫入選項位元組就完成了。
器件電子簽名
電子簽名存放在快閃記憶體儲存器模組的系統儲存區域,包含的晶片識別資訊在出廠時編寫,不可更改,使用指標讀指定地址下的儲存器可獲取電子簽名
電子簽名,其實就是 STM32 的 ID 號,它的存放區域是系統儲存器,就是 FLASH 基本結構圖中的系統儲存器。它不僅有 BootLoader 程式,還有幾個位元組的 ID 號,系統儲存器,起始地址是 1FFF F000。
-
快閃記憶體容量暫存器:
地址:0x1FFF F7E0
大小:16位 -
產品唯一身份標識暫存器:
地址: 0x1FFF F7E8
大小:96位也就是每個晶片的身份證號。每一個晶片的這 96 位資料,都是不一樣的,使用這個唯一 ID 號,可以做一些加密的操作。
比如你想寫入一段程式,只能在指定裝置執行,那就可以在程式的多處加入 ID 號判斷,如果不是指定裝置的 ID 號,就不執行程式功能,這樣即使你的程式被盜,在別的裝置上也難以執行。
FLSHA庫函式
/*------------ 功能適用於所有STM32F10x裝置 -----*/
/* 與核心執行程式碼有關 */
void FLASH_SetLatency(uint32_t FLASH_Latency);
void FLASH_HalfCycleAccessCmd(uint32_t FLASH_HalfCycleAccess);
void FLASH_PrefetchBufferCmd(uint32_t FLASH_PrefetchBuffer);
// 解鎖。解除防寫
void FLASH_Unlock(void);
// 解鎖。解除防寫
void FLASH_Lock(void);
// 擦除指定頁
FLASH_Status FLASH_ErasePage(uint32_t Page_Address);
// 擦除全部
FLASH_Status FLASH_EraseAllPages(void);
// 擦除選項位元組
FLASH_Status FLASH_EraseOptionBytes(void);
// 在指定地址寫入字
FLASH_Status FLASH_ProgramWord(uint32_t Address, uint32_t Data);
// 在指定地址寫入半字
FLASH_Status FLASH_ProgramHalfWord(uint32_t Address, uint16_t Data);
// 寫入選項位元組的Data0和Data1
FLASH_Status FLASH_ProgramOptionByteData(uint32_t Address, uint8_t Data);
// 設定防寫
FLASH_Status FLASH_EnableWriteProtection(uint32_t FLASH_Pages);
// 設定讀保護
FLASH_Status FLASH_ReadOutProtection(FunctionalState NewState);
// 設定選項位元組中USER的三個位:IWDG_SW / RST_STOP / RST_STDBY。
FLASH_Status FLASH_UserOptionByteConfig(uint16_t OB_IWDG, uint16_t OB_STOP, uint16_t OB_STDBY);
// 獲取選項位元組中USER的三個位:IWDG_SW / RST_STOP / RST_STDBY。
uint32_t FLASH_GetUserOptionByte(void);
// 獲取寫保狀態
uint32_t FLASH_GetWriteProtectionOptionByte(void);
// 獲取讀保護狀態
FlagStatus FLASH_GetReadOutProtectionStatus(void);
// 獲取預期緩衝區狀態
FlagStatus FLASH_GetPrefetchBufferStatus(void);
// 中斷使能
void FLASH_ITConfig(uint32_t FLASH_IT, FunctionalState NewState);
// 獲取指定標誌位
FlagStatus FLASH_GetFlagStatus(uint32_t FLASH_FLAG);
// 清除指定標誌位
void FLASH_ClearFlag(uint32_t FLASH_FLAG);
// 獲取狀態
FLASH_Status FLASH_GetStatus(void);
// 等待忙。也就是等待BSY=0。呼叫擦除、寫入等等庫函式時,內部已經幫我們呼叫過該函式了,不需要我們再呼叫
FLASH_Status FLASH_WaitForLastOperation(uint32_t Timeout);
/*------------ 新功能用於所有STM32F10x器件 -----*/
void FLASH_UnlockBank1(void);
void FLASH_LockBank1(void);
FLASH_Status FLASH_EraseAllBank1Pages(void);
FLASH_Status FLASH_GetBank1Status(void);
FLASH_Status FLASH_WaitForLastBank1Operation(uint32_t Timeout);
#ifdef STM32F10X_XL
/*---- 新功能僅適用於STM32F10x_XL大容量器件 -----*/
void FLASH_UnlockBank2(void);
void FLASH_LockBank2(void);
FLASH_Status FLASH_EraseAllBank2Pages(void);
FLASH_Status FLASH_GetBank2Status(void);
FLASH_Status FLASH_WaitForLastBank2Operation(uint32_t Timeout);
FLASH_Status FLASH_BootConfig(uint16_t FLASH_BOOT);
#endif
案例:讀寫FLASH快閃記憶體
接線圖
使用到的函式
// 解鎖。解除防寫
void FLASH_Unlock(void);
// 解鎖。解除防寫
void FLASH_Lock(void);
// 擦除指定頁
FLASH_Status FLASH_ErasePage(uint32_t Page_Address);
// 擦除全部
FLASH_Status FLASH_EraseAllPages(void);
// 在指定地址寫入字
FLASH_Status FLASH_ProgramWord(uint32_t Address, uint32_t Data);
// 在指定地址寫入半字
FLASH_Status FLASH_ProgramHalfWord(uint32_t Address, uint16_t Data);
示例程式碼
main.c
#include "stm32f10x.h" // Device header
#include "OLED.h"
#include "Store.h"
#include "Key.h"
// 鍵碼
uint8_t Num;
int main()
{
OLED_Init();
Store_Init();
Key_Init();
OLED_ShowHexNum(1,1,Store_Data[1],4);
OLED_ShowHexNum(2,1,Store_Data[2],4);
OLED_ShowHexNum(3,1,Store_Data[3],4);
OLED_ShowHexNum(4,1,Store_Data[4],4);
while(1)
{
Num = Key_Num();
if(Num == 1)
{
Store_Data[1]++;
Store_Data[2]+=2;
Store_Data[3]+=3;
Store_Data[4]+=4;
Store_Save();
}
if(Num == 2)
{
Store_Clear();
}
OLED_ShowHexNum(1,1,Store_Data[1],4);
OLED_ShowHexNum(2,1,Store_Data[2],4);
OLED_ShowHexNum(3,1,Store_Data[3],4);
OLED_ShowHexNum(4,1,Store_Data[4],4);
}
}
Flash.c
#include "stm32f10x.h" // Device header
// 讀指定地址下的一個位元組
uint8_t Flash_ReadByte(uint32_t Addr)
{
return *((__IO uint8_t *)(Addr));
}
// 讀指定地址下的一個半字
uint16_t Flash_ReadHalfWord(uint32_t Addr)
{
return *((__IO uint16_t *)(Addr));
}
// 讀指定地址下的一個字
uint32_t Flash_ReadWord(uint32_t Addr)
{
return *((__IO uint32_t *)(Addr));
}
// 擦除指定頁
void Flash_ErasurePage(uint32_t Addr)
{
// 解鎖
FLASH_Unlock();
// 擦除頁
FLASH_ErasePage(Addr);
// 上鎖
FLASH_Lock();
}
// 擦除全部
void Flash_ErasureAll()
{
// 解鎖
FLASH_Unlock();
// 擦除全部
FLASH_EraseAllPages();
// 上鎖
FLASH_Lock();
}
// 在指定地址寫入半字
void Flash_WriteHalfWord(uint32_t Addr, uint16_t Data)
{
// 解鎖
FLASH_Unlock();
// 在指定地址寫入半字
FLASH_ProgramHalfWord(Addr, Data);
// 上鎖
FLASH_Lock();
}
// 在指定地址寫入半字
void Flash_WriteWord(uint32_t Addr, uint32_t Data)
{
// 解鎖
FLASH_Unlock();
// 在指定地址寫入字
FLASH_ProgramWord(Addr, Data);
// 上鎖
FLASH_Lock();
}
Flash.h
#ifndef __Flash_H
#define __Flash_H
uint8_t Flash_ReadByte(uint32_t Addr);
uint16_t Flash_ReadHalfWord(uint32_t Addr);
uint32_t Flash_ReadWord(uint32_t Addr);
void Flash_ErasurePage(uint32_t Addr);
void Flash_ErasureAll(void);
void Flash_WriteHalfWord(uint32_t Addr, uint16_t Data);
void Flash_WriteWord(uint32_t Addr, uint32_t Data);
#endif
Store.c
#include "stm32f10x.h" // Device header
#include "Flash.h"
#define STORE_START_ADDRESS 0x0800FC00 // 儲存的起始地址
#define STORE_COUNT 512 // 儲存資料的個數
// 將快閃記憶體中的資料以半字為單位讀取到RAM中
uint16_t Store_Data[STORE_COUNT];
// 初始化
void Store_Init()
{
/*判斷是不是第一次使用*/
// 讀取第一個半字的標誌位,if不成立,則執行第一次使用的初始化
if(Flash_ReadHalfWord(STORE_START_ADDRESS) == 0x7777)
{
// 將儲存區域的所有資料都讀到Store_Data陣列中
for(uint16_t i = 0;i < STORE_COUNT; i++)
{
Store_Data[i] = Flash_ReadHalfWord(STORE_START_ADDRESS + (i * 2));
}
}
else
{
// 擦除指定頁
Flash_ErasurePage(STORE_START_ADDRESS);
// 在儲存的起始位置寫入自定義的標誌位。下次檢測到該標誌位說明不是第一次使用
Flash_WriteHalfWord(STORE_START_ADDRESS, 0x7777);
// 將儲存區域的資料全部置0,除了標誌位,因此i從1開始
for(uint16_t i = 1;i < STORE_COUNT; i++)
{
// 每個地址對應一個位元組,而這裡是兩個位元組進行儲存,所以i*2。如果四個位元組進行儲存則乘以四
Flash_WriteHalfWord(STORE_START_ADDRESS + (i * 2), 0x0000);
}
}
}
// 儲存資料
void Store_Save()
{
// 擦除頁
Flash_ErasurePage(STORE_START_ADDRESS);
// 將陣列Store_Data的資料全部寫入快閃記憶體中
for(uint16_t i = 0;i < STORE_COUNT; i++)
{
Flash_WriteHalfWord(STORE_START_ADDRESS +(i * 2), Store_Data[i]);
}
}
// 資料清零
void Store_Clear()
{
// 將除了標誌位以外的資料全部清零
for(uint16_t i = 1;i < STORE_COUNT;i++)
{
Store_Data[i] = 0;
}
Store_Save();
}
Store.h
#ifndef __Store_H
#define __Store_H
extern uint16_t Store_Data[];
void Store_Init(void);
void Store_Save(void);
void Store_Clear(void);
#endif
Key.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
// 初始化按鍵
void Key_Init()
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU; // 上拉輸入
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_11;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStruct);
}
// 獲取按鍵鍵碼
uint8_t Key_Num()
{
uint8_t Num = 0;
// PB11=0說明按鍵1按下
if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == Bit_RESET)
{
Delay_ms(20);
while(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == Bit_RESET);
Num = 1;
}
// PB1=0說明按鍵2按下
if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == Bit_RESET)
{
Delay_ms(20);
while(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == Bit_RESET);
Num = 2;
}
return Num;
}
Key.h
#ifndef __Key_H
#define __Key_H
void Key_Init(void);
uint8_t Key_Num(void);
#endif