很多情況下,在STM32中寫入一些數據,在某些不可控因素下其數據無法保存。因此,解決此問題就要用到FLASH.
什么是內部 Flash??
Flash 是一種非易失性存儲器,STM32 的程序和常量數據就存在 Flash 中。它的關鍵特點是:
特性 | 說明 |
---|---|
地址映射 | Flash 地址從 0x08000000 開始 |
掉電不丟 | 電源斷了之后數據還在 |
讀操作 | 和 RAM 一樣,可以直接讀 |
寫操作 | 必須擦除后寫入(寫之前必須全是 1) |
寫入單位 | 至少為半字(16位) |
擦除單位 | 以頁為單位(STM32F103 是 1KB) |
Flash 是如何讀數據的??
STM32 的 Flash 是memory-mapped(內存映射)的,即:
Flash 被掛在總線上,和 SRAM 一樣的訪問方式,讀取數據只需要訪問地址。
底層硬件會自動完成數據譯碼、電荷檢測、字線控制等,所以我們可以像訪問 RAM 一樣訪問 Flash。
STM32 讀取和寫入內部 Flash 的本質區
操作 | 是否需要解鎖 | 是否需要擦除 | 最小單位 | 是否會改變 Flash 數據 |
---|---|---|---|---|
讀取 | 否? | 否? | 1字節/2字節/4字節 | 否 (只讀) |
寫入 | 是? | 是(必須) | 半字(16位) | 是 (只能1→0) |
?為什么不需要解鎖就能讀取?
因為 讀取 Flash 是只讀操作,不會修改 Flash 結構,不存在寫保護一說,也不需要擦除。Flash 讀取只需要:
uint16_t data = *(volatile uint16_t *)0x0800FC00;
STM32 會自動從 Flash 控制器中讀取該地址的數據。
讀取代碼詳解
uint32_t MyFLASH_ReadWord(uint32_t Address)
{return *((__IO uint32_t *)(Address));
}uint16_t MyFLASH_ReadHalfWord(uint32_t Address)
{return *((__IO uint16_t *)(Address));
}uint8_t MyFLASH_ReadByte(uint32_t Address)
{return *((__IO uint8_t *)(Address));
}
通用結構分析
*((__IO 類型 *)(Address)) 是什么意思?
組成部分 | 含義 |
---|---|
(類型 *) | 把地址強制轉換成指針類型 |
__IO | volatile ,防止編譯器優化(告訴編譯器:值可能隨時變化) |
*(指針) | 取出地址中的值(解引用) |
舉個例子:?
*((__IO uint16_t *)0x0800FC00) → 讀取地址0x0800FC00的2字節數據
注意事項
不要讀取未對齊地址(比如讀取 uint16_t 不能從奇數地址讀取)
不要在 Flash 正在寫入時讀取 Flash
寫入 Flash 的完整流程?
寫入 Flash 是一個有順序、受保護的敏感操作,必須嚴格遵守 STM32 的流程!
要求 | 原因 |
---|---|
寫入前必須解鎖 | 防止誤寫程序或數據 |
寫入前必須擦除 | 因為 Flash 只能從 1 → 0 ,不能反向 |
寫入地址必須對齊 | 每次只能寫一個 “半字” |
寫入時 Flash 忙,不能再操作 Flash | 必須等待寫完成(BSY = 0) |
?
┌────────────┐│ Flash_Unlock│ ← 解鎖 Flash 寫保護└─────┬──────┘↓┌──────────────┐│Flash_ErasePage│ ← 擦除所在頁(將所有位置1)└─────┬────────┘↓┌──────────────────────┐│Flash_ProgramHalfWord │ ← 寫入一個16位值(地址必須對齊)└────────────┬─────────┘↓┌────────────┐│ Flash_Lock │ ← 鎖回 Flash,防止誤寫└────────────┘
void MyFLASH_EraseAllPages(void)
{FLASH_Unlock();FLASH_EraseAllPages();FLASH_Lock();
}void MyFLASH_ErasePage(uint32_t PageAddress)
{FLASH_Unlock();FLASH_ErasePage(PageAddress);FLASH_Lock();
}void MyFLASH_ProgramWord(uint32_t Address, uint32_t Data)
{FLASH_Unlock();MyFLASH_ErasePage(Address); FLASH_ProgramWord(Address, Data);FLASH_Lock();
}void MyFLASH_ProgramHalfWord(uint32_t Address, uint16_t Data)
{FLASH_Unlock();MyFLASH_ErasePage(Address); FLASH_ProgramHalfWord(Address, Data);FLASH_Lock();
}
把 STM32 的內部 Flash 最后一頁 0x0800FC00
用作數據存儲區
函數名 | 功能說明 |
---|---|
Store_Init() | 開機時檢查是否初始化過,若無則初始化數據,再加載到 RAM 數組 |
Store_Save() | 把當前 RAM 中的數據整體寫入 Flash(覆蓋) |
Store_Clear() | 清除 RAM 中的數據,再寫入 Flash(相當于清空) |
Store_Init() 函數解釋?
?
#define STORE_START_ADDRESS 0x0800FC00
#define STORE_COUNT 512uint16_t Store_Data[STORE_COUNT];void Store_Init(void)
{if (MyFLASH_ReadHalfWord(STORE_START_ADDRESS) != 0xA5A5){MyFLASH_ErasePage(STORE_START_ADDRESS);MyFLASH_ProgramHalfWord(STORE_START_ADDRESS, 0xA5A5);for (uint16_t i = 1; i < STORE_COUNT; i ++){MyFLASH_ProgramHalfWord(STORE_START_ADDRESS + i * 2, 0x0000);}}for (uint16_t i = 0; i < STORE_COUNT; i ++){Store_Data[i] = MyFLASH_ReadHalfWord(STORE_START_ADDRESS + i * 2);}
}
?
空間計算(為什么是 512?)
每個 uint16_t 是 2 字節(16 位),所以:
512 × 2 字節 = 1024 字節 = 1KB
正好使用 Flash 的最后一頁(1KB 大小)來存儲 512 個 uint16_t。
這是一種 對齊頁大小的設計,防止寫入跨頁、浪費空間或出錯。
步驟 | 說明 |
---|---|
讀取魔數 | 檢查 Flash 中是否已經初始化過(首地址是否為 0xA5A5) |
若未初始化 | 則擦除該頁,寫入魔數 + 寫入 511 個 0x0000 |
讀取數據 | 把 Flash 中的所有數據讀入 RAM 數組 Store_Data[] |
為什么用魔數 0xA5A5?
Flash 出廠是全 0xFFFF
,你寫入 0xA5A5
后,可以作為初始化標志,下次重啟時避免重復擦除。
Store_Save() 函數解釋(將 RAM 數據寫回 Flash)?
void Store_Save(void)
{MyFLASH_ErasePage(STORE_START_ADDRESS);for (uint16_t i = 0; i < STORE_COUNT; i ++){MyFLASH_ProgramHalfWord(STORE_START_ADDRESS + i * 2, Store_Data[i]);}
}
步驟 | 說明 |
---|---|
先擦除頁 | Flash 寫之前必須擦除 |
順序寫入 | 把 Store_Data[] 數組中 512 個半字寫入 Flash |
為什么每個數據偏移 i * 2?
因為你每個數據是 uint16_t,占用 2 字節,要避免覆蓋前一個數據。?
Store_Clear() 函數解釋(清除并保存)?
void Store_Clear(void)
{for (uint16_t i = 1; i < STORE_COUNT; i ++){Store_Data[i] = 0x0000;}Store_Save();
}
功能分析:
從第 1 個數據開始清 0(保留 Store_Data[0] 為魔數)
調用 Store_Save() 整頁重寫
main函數
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Store.h"
#include "Key.h"uint8_t KeyNum;int main(void)
{OLED_Init();Key_Init();Store_Init();OLED_ShowString(1, 1, "Flag:");OLED_ShowString(2, 1, "Data:");while (1){KeyNum = Key_GetNum();if (KeyNum == 1){Store_Data[1] ++;Store_Data[2] += 2;Store_Data[3] += 3;Store_Data[4] += 4;Store_Save();}if (KeyNum == 2){Store_Clear();}OLED_ShowHexNum(1, 6, Store_Data[0], 4);OLED_ShowHexNum(3, 1, Store_Data[1], 4);OLED_ShowHexNum(3, 6, Store_Data[2], 4);OLED_ShowHexNum(4, 1, Store_Data[3], 4);OLED_ShowHexNum(4, 6, Store_Data[4], 4);}
}
?FLASH讀取芯片ID
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"int main(void)
{OLED_Init();OLED_ShowString(1, 1, "F_SIZE:");OLED_ShowHexNum(1, 8, *((__IO uint16_t *)(0x1FFFF7E0)), 4);OLED_ShowString(2, 1, "U_ID:");OLED_ShowHexNum(2, 6, *((__IO uint16_t *)(0x1FFFF7E8)), 4);OLED_ShowHexNum(2, 11, *((__IO uint16_t *)(0x1FFFF7E8 + 0x02)), 4);OLED_ShowHexNum(3, 1, *((__IO uint32_t *)(0x1FFFF7E8 + 0x04)), 8);OLED_ShowHexNum(4, 1, *((__IO uint32_t *)(0x1FFFF7E8 + 0x08)), 8);while (1){}
}
名稱 | 地址 | 說明 |
---|---|---|
Flash 大小 | 0x1FFFF7E0 | 單位:KB(例如 64 → 64KB Flash) |
UID[0] | 0x1FFFF7E8 | UID 第 0~15 位(低 16 位) |
UID[1] | 0x1FFFF7EA | UID 第 16~31 位(高 16 位) |
UID[2] | 0x1FFFF7EC | UID 第 32~63 位(32 位) |
UID[3] | 0x1FFFF7F0 | UID 第 64~95 位(32 位) |
應用場景
防偽 / 唯一性綁定:
使用 UID 作為設備編號上傳服務器或產品注冊
Flash 容量檢測:
某些型號可能 Flash 容量不一致,可動態檢測
License 系統:
生成授權碼綁定到 UID,防止盜版
設備信息顯示:
工程生產中可直接通過 OLED 顯示設備 ID 進行追溯
?