十六、FLASH快閃記憶體

7七柒發表於2024-03-08

十四、FLASH快閃記憶體

FLASH簡介

  • STM32F1系列的FLASH包含程式儲存器、系統儲存器和選項位元組三個部分,透過快閃記憶體儲存器介面(外設)可以對程式儲存器和選項位元組進行擦除和程式設計

    快閃記憶體儲存器介面是一個外設,是這個快閃記憶體的管理員,把我們的指令和資料,寫入到這個外設的相應暫存器,然後這個外設就會自動去操作對應的儲存空間。

  • 讀寫FLASH的用途:

    1. 利用程式儲存器的剩餘空間來儲存掉電不丟失的使用者資料

      它的程式儲存器容量是 64K,一般我們寫個簡單的程式,可能就只佔前面的很小一部分空間,剩下的大片空餘空間,我們就可以加以利用。比如儲存一些我們自定義的資料

      注意:我們在選取儲存區域時,一定不要覆蓋了原有的程式,要不然程式自己把自己給破壞了,之後程式就執行不了了。

    2. 透過在程式中程式設計(IAP),實現程式的自我更新

      利用程式,來修改程式本身,實現程式的自我更新。

  • 線上程式設計(In-Circuit Programming – ICP)用於更新程式儲存器的全部內容,它透過JTAG、SWD協議或系統載入程式(Bootloader)下載程式

  • 在程式中程式設計(In-Application Programming – IAP)可以使用微控制器支援的任一種通訊介面下載程式

    首先需要自己寫一個 BootLoader 程式,並且存放在程式更新時不會覆蓋的地方,比如我們放在整個程式儲存器的後面。

    需要更新程式時,我們控制程式跳轉到這個自己寫的 BootLoader 裡來,在這裡面,我們就可以接收任意一種通訊介面傳過來的資料,比如串列埠、USB、藍芽轉串列埠、WIFI 轉串列埠等等,這個傳過來的資料,就是待更新的程式

    控制 FLASH 讀寫,把收到的程式,寫入到整個程式儲存器的前面程式正常執行的地方,寫完之後,再控制程式跳轉回正常執行的地方,或者直接復位,這樣程式就完成了自我升級。

注意:在程式設計過程中,任何讀寫快閃記憶體的操作都會使 CPU 暫停,直到此次快閃記憶體程式設計結束。這其實是讀寫內部快閃記憶體儲存資料的一個弊端,就是快閃記憶體忙的時候,程式碼執行會暫停,因為執行程式碼需要讀快閃記憶體,快閃記憶體在忙,沒法讀,所以 CPU 也就沒法執行了,程式就會暫停,這會導致什麼問題呢?假如你使用內部快閃記憶體儲存資料,同時你的中斷程式碼又是在頻繁執行的,這樣,讀寫快閃記憶體的時候,中斷程式碼就無法執行了,這可能會導致中斷無法及時響應

快閃記憶體模組組織(中容量)

這個表是中容量產品的快閃記憶體分配情況。我們 C8T6 晶片的快閃記憶體容量是 64K,屬於中容量產品。對於小容量產品和大容量產品,快閃記憶體的分配方式有些區別,這個可以參考一下手冊。

image

首先看一下第一列的幾個塊,這裡分為了 3 個塊,

  1. 主儲存器:也就是程式儲存器,用來存放程式程式碼的,這是最主要,也是容量最大的一塊。

    對他進行了分頁,分頁是為了更好的管理快閃記憶體。每頁的大小都是 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 結尾的都一定是頁的起始地址,之後如果想要給一個頁的起始地址,就需要用到這個規律。
  2. 資訊塊

    • 啟動程式程式碼:存放的是原廠寫入的 BootLoader,用於串列埠下載

      起始地址是 0x1FFF F000,他的容量是 2K

    • 使用者選擇位元組:也就是選項位元組,存放一些獨立的引數,這個選項位元組

      起始地址是 0x1FFF F800,容量是 16 個位元組,裡面只有幾個位元組的配置引數,這個後面還會繼續說的

  3. 快閃記憶體儲存器介面暫存器:這一塊的儲存器,實際上並不屬於快閃記憶體,地址都是 40 開頭的,說明這個儲存器介面暫存器就是一個普通的外設,他的儲存介質,也都是 SRAM。這個快閃記憶體儲存器介面,就是上面這些快閃記憶體的管理員,這些暫存器,就是用來控制擦除和程式設計這個過程的。

    KEYR 鍵暫存器,SR 狀態暫存器,CR 控制暫存器等等常用暫存器

    外設的起始地址是 0x4002 2000,每個暫存器都是 4 個位元組,也就是 32 位

FLASH基本結構

image

這裡程式儲存器以 C8T6 為例,它是 64K 的,所以總共只有 64 頁,最後一頁的起始地址是 0800 FC00。

左邊是快閃記憶體儲存器介面,手冊裡還有個名稱,快閃記憶體程式設計和擦除控制器(FPEC),這兩個名稱是一個東西。然後這個控制器,就是快閃記憶體的管理員,它可以對程式儲存器進行擦除和程式設計,也可以對選項位元組進行擦除和程式設計。當然系統儲存器是不能擦除和程式設計的。

選項位元組,裡面有很大一部分配置位,其實是配置主程式儲存器的讀防寫的,所以右邊畫的,寫入選項位元組可以配置程式儲存器的讀防寫

FLASH解鎖

  • FPEC共有三個鍵值:

    1. RDPRT鍵 = 0x000000A5

      解除讀保護

    2. KEY1 = 0x45670123

    3. KEY2 = 0xCDEF89AB

      KEY1和KEY2解除防寫

  • 解鎖:

    1. 復位後,FPEC被保護,不能寫入FLASH_CR

    2. 在FLASH_KEYR先寫入KEY1,再寫入KEY2,解除防寫

      要先寫入 KEY1 ,再寫入 KEY2 ,最終才能解除防寫。

      所以這個鎖的安全性非常高,有兩道鎖,即使你程式跑飛了也基本不可能解鎖

    3. 錯誤的操作序列會在下次復位前鎖死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,是一個安全保障措施,在程式邏輯上,沒有作用,加上這個關鍵字的目的,用一句話來說,就是防止編譯器最佳化。

快閃記憶體全擦除

image

把所有頁,都給擦除掉。

  1. 讀取 LOCK 位,看一下晶片鎖沒鎖。如果 LOCK 位 = 1,R鎖住了,就執行解鎖過程,解鎖過程就是在 KEYR 暫存器,先寫入 KEY1,再寫入 KEY2;但是在庫函式中,並沒有這個判斷,庫函式是直接執行解鎖過程,管你鎖沒鎖,都執行解鎖,這個比較簡單直接,不過效果都一樣。
  2. 解鎖之後,首先,置控制暫存器裡的 MER(Mass Erase)位為 1,然後再置 STRT(Start)位為 1,其中置 STRT 為 1 是觸發條件,STRT 為 1 之後,晶片開始幹活,然後晶片看到 MER 位是 1,它就知道,接下來要乾的活就是全擦除,這樣內部電路就會自動執行全擦除的過程。
  3. 擦除也是需要花一段時間的,所以擦除過程開始後,程式要執行等待。判斷狀態暫存器的 BSY(Busy)位是否為 1,BSY 位表示晶片是否處於忙狀態,BSY 為 1,表示晶片忙,所以這裡,如果判斷 BSY = 1,就跳轉回來,繼續迴圈判斷,直到 BSY = 0,跳出迴圈,這樣全擦除過程就結束了。
  4. 最後一步,這裡寫的是,讀出並驗證所有頁的資料,這個是測試程式才要做的,正常情況下,全擦除完成了,我們預設就是成功了。如果還要再全讀出來驗證一下,這個工作量太大了,所以這裡的最後一步,我們就不管了。

快閃記憶體頁擦除

image

STM32 的快閃記憶體也是寫入前必須擦除。擦除之後,所有的資料位變為 1,擦除的最小單位就是一頁,1K,1024 位元組。

這個也是類似的過程。

  1. 解鎖的流程

  2. 置控制暫存器的 PER(Page Erase)位為 1,然後在 AR(Address Register)地址暫存器中選擇要擦除的頁,最後,置控制暫存器的 STRT 位為 1,置 STRT 為 1,也是觸發條件

    STRT 為 1,晶片開始幹活,然後晶片看到,PER = 1,他就知道接下來要執行頁擦除,但是晶片要知道要具體擦哪一頁,所以,它會繼續看 AR 暫存器的資料,AR 暫存器我們要提前寫入一個頁的起始地址,這樣晶片就會把我們指定的一頁,給擦除掉。

  3. 然後擦除開始之後,我們也需要等待 BSY 位。

  4. 最後,讀出並驗證資料,這個就不用看了。

快閃記憶體寫入

image

注意:STM32 的快閃記憶體再寫入之前會檢查指定地址有沒有擦除,如果沒有擦除就寫入,STM32 則不執行寫入操作,除非寫入的全是 0,這一個資料是例外,因為不擦除就寫入,可能會寫入錯誤,但全寫入 0 的話,寫入肯定是沒問題的。

  1. 第一步,也是解鎖。

  2. 第二步,我們需要置控制暫存器的 PG(Programming)位為 1,表示我們即將寫入資料。

  3. 第三步,在指定的地址寫入半字,需要用到使用指標訪問儲存器的 *((__IO uint16_t *)(0x08000000)) = 0x1234; 這句程式碼在指定地址寫入資料。寫入資料這個程式碼,就是觸發開始的條件,不需要像擦除一樣,置 STRT 位了。

    注意:寫入操作,只能以半字的形式寫入。

    在 STM32 中,有幾個術語,字、半字和位元組,

    • 字,Word,是 32 位資料;

    • 半字,HalfWord,是 16 位資料;

    • 位元組,Byte,是 8 位資料,

    那這裡只能以半字寫入,意思就是隻能以 16 位的形式寫入,一次性,寫入兩個位元組;

    如果你要寫入 32 位,就分兩次完成;

    如果想單獨寫入一個位元組,還要保留另一個位元組的原始資料的話,就只能把整頁資料都讀到 SRAM,再隨意修改 SRAM 資料,修改全部完成之後,再把整頁都擦除,最後再把整頁都寫回去。

    所以,如果你想像 SRAM 一樣隨心所欲地讀寫,那最好的辦法就是先把快閃記憶體的一頁讀到 SRAM 中,讀寫完成後,再擦除一頁,整體寫回去。那回到流程圖這裡

  4. 寫入半字之後,晶片會處於忙狀態,我們等待一下 BSY 清零,這樣寫入資料的過程就完成了。

選項位元組

選項位元組介紹

image

  • 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 的最高位,這一位直接把剩下的所有頁一起都保護了,這是防寫的定義。

擦除選項位元組

  1. 解鎖快閃記憶體

  2. 檢查FLASH_SR的BSY位,以確認沒有其他正在進行的快閃記憶體操作

  3. 解鎖FLASH_CR的OPTWRE位

    解鎖 CR 的 OPTWRE(Option Write Enable)位,這一步是選項位元組的解鎖,選項位元組裡面還有一個單獨的鎖,在解鎖快閃記憶體後,還需要再解鎖選項位元組的鎖,之後才能操作選項位元組。

    解鎖選項位元組的話,看一下快閃記憶體模組組織的暫存器,整個快閃記憶體的鎖是 KEYR,裡面選項位元組的小鎖,是下面的 OPTKEYR(Option Key Register),解鎖這個小鎖,也是類似的流程,我們需要在 OPTKEYR 裡,先寫入 KEY1,再寫入 KEY2,這樣就能解鎖選項位元組的小鎖了。

  4. 設定FLASH_CR的OPTER位為1

  5. 設定FLASH_CR的STRT位為1

  6. 等待BSY位變為0

  7. 讀出被擦除的選擇位元組並做驗證

寫入選項位元組

  1. 解鎖快閃記憶體
  2. 檢查FLASH_SR的BSY位,以確認沒有其他正在進行的程式設計操作
  3. 解鎖FLASH_CR的OPTWRE位
  4. 設定FLASH_CR的OPTPG位為1
  5. 寫入要程式設計的半字到指定的地址
  6. 等待BSY位變為0
  7. 讀出寫入的地址並驗證資料

和普通的快閃記憶體寫入也差不多,先檢測 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快閃記憶體

接線圖
image

使用到的函式

// 解鎖。解除防寫
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

相關文章