FLASH簡介
- STM32F1系列的FLASH包含程序存儲器、系統存儲器和選項字節三個部分,通過閃存存儲器接口(外設)可以對程序存儲器和選項字節進行擦除和編程,讀取指定寄存器直接使用指針讀即可
- 讀寫FLASH的用途:
?????? ?????? 利用程序存儲器的剩余空間來保存掉電不丟失的用戶數據
?????? ?????? 通過在程序中編程(IAP),實現程序的自我更新,類似于OTA
- 在線編程(In-Circuit Programming – ICP)用于更新程序存儲器的全部內容,它通過JTAG、SWD協議或系統加載程序(Bootloader)下載程序
- 在程序中編程(In-Application Programming – IAP)可以使用微控制器支持的任一種通信接口下載程序
STM32F10xxx閃存編程參考手冊
閃存模塊組織
????????特征與W25Q64類似
?????? 這里的存儲器分化:只有一個頁,每頁大小都是1K字節。地址范圍:起始地址:只要以000、400、800、C00結尾的。
FLASH基本結構
FLASH解鎖
- FPEC共有三個鍵值:
?????????????? RDPRT鍵 = 0x000000A5——解除讀保護的密鑰
????????????????KEY1 = 0x45670123——這是自定義的密碼
? ? ? ? ? ????? KEY2 = 0xCDEF89AB
- 解鎖:
?????????????? 復位后,FPEC被保護,不能寫入FLASH_CR
?????????????? 在FLASH_KEYR先寫入KEY1,再寫入KEY2,解鎖
?????????????? 錯誤的操作序列會在下次復位前鎖死FPEC和FLASH_CR
- 加鎖:
?????????????? 設置FLASH_CR中的LOCK位鎖住FPEC和FLASH_CR
讀取:
使用指針訪問存儲器,因為STM32的內部存儲器是掛載在總線上的
- 使用指針讀指定地址下的存儲器:
?????? uint16_t Data = *((__IO uint16_t *)(0x08000000));
- 使用指針寫指定地址下的存儲器:——需要解鎖等提高權限
???? *((__IO uint16_t *)(0x08000000)) = 0x1234;
- 其中:
?? #define ?__IO? volatile????? //防止編譯器優化
程序存儲器編程
程序存儲器頁擦除
程序存儲器全擦除
選項字節
- RDP:寫入RDPRT鍵(0x000000A5)后解除讀保護
- USER:配置硬件看門狗和進入停機/待機模式是否產生復位
- Data0/1:用戶可自定義使用
- WRP0/1/2/3:配置寫保護,每一個位對應保護4個存儲頁(中容量)
- 帶n,當寫入USER時要同時在nUSET寫入數據的反碼,這樣才是有效的——一般函數會自動執行
選項字節編程
- 解鎖閃存
- 檢查FLASH_SR的BSY位,以確認沒有其他正在進行的編程操作
- 解鎖FLASH_CR的OPTWRE位
- 設置FLASH_CR的OPTPG位為1
- 寫入要編程的半字到指定的地址
- 等待BSY位變為0
- 讀出寫入的地址并驗證數據
選項字節擦除
- 解鎖閃存
- 檢查FLASH_SR的BSY位,以確認沒有其他正在進行的閃存操作
- 解鎖FLASH_CR的OPTWRE位
- 設置FLASH_CR的OPTER位為1
- 設置FLASH_CR的STRT位為1
- 等待BSY位變為0
- 讀出被擦除的選擇字節并做驗證
器件電子簽名
- 電子簽名存放在閃存存儲器模塊的系統存儲區域,包含的芯片識別信息在出廠時編寫,不可更改,使用指針讀指定地址下的存儲器可獲取電子簽名
- 閃存容量寄存器:
?????? ?????? 基地址:0x1FFF F7E0
?????? ?????? 大小:16位
- 產品唯一身份標識寄存器:
?????? ?????? 基地址: 0x1FFF F7E8
?????? ?????? 大小:96位
接線圖:
15-1 讀寫內部FLASH
最底層模塊MyFALSH實現讀取、擦除和編程
再此模塊上建Store模塊,實現參數數據的讀寫和存儲管理:任意讀寫參數,并且這些參數是掉電不丟失的-定義SRAM數組,把需要掉電不丟失的數據放入到SRAM中,之后調用保存的函數使SRAM數組自動備份到閃存里;上電后,Store初始化,自動會把閃存里的數據讀回SRAM數組里——閃存管理策略
使用STM32 ST-LINK Utility進行調試:可以看到閃存里的數據;
?????? 注:使用完,記得斷開連接不然設備占用,導致程序運行不了
思路:
閃存不需要初始化
讀取:直接使用指針
擦除:先解鎖,調用全擦除函數或頁擦除函數,之后加鎖
編程:先解鎖,調用函數,之后加鎖
選項字節的操作與閃存類似,有對應的函數,但是同時是用軟件進行圖形化修改
FLASH相關函數:系統分為三大部分的庫函數——對應不同的配置,此實驗只用第一部分
void FLASH_Unlock(void);?????????? //解鎖
void FLASH_Lock(void);????????????? //加鎖
//對主閃存和選項字節的操作配置,返回值FLASH_Status一個執行狀態
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); ?????? //在指定地址寫入半字
//選項字節的寫入
FLASH_Status FLASH_ProgramOptionByteData(uint32_t Address, uint8_t Data);??????????? //自定義的Data0和Data1
FLASH_Status FLASH_EnableWriteProtection(uint32_t FLASH_Pages);?? ?????? //寫保護
FLASH_Status FLASH_ReadOutProtection(FunctionalState NewState);? ?????? //讀保護
FLASH_Status FLASH_UserOptionByteConfig(uint16_t OB_IWDG, uint16_t OB_STOP, uint16_t OB_STDBY);???????? //用戶選項的三個配置位
//獲取選項字節當前的狀態
uint32_t FLASH_GetUserOptionByte(void);?????? //獲取用戶選項的三個配置位
uint32_t FLASH_GetWriteProtectionOptionByte(void); ????????? //獲取寫保護
FlagStatus FLASH_GetReadOutProtectionStatus(void); ???????? //獲取寫保護
//狀態位
FlagStatus FLASH_GetFlagStatus(uint32_t FLASH_FLAG);
void FLASH_ClearFlag(uint32_t FLASH_FLAG);
FLASH_Status FLASH_GetStatus(void);???????????? //獲取狀態
FLASH_Status FLASH_WaitForLastOperation(uint32_t Timeout);?????????? 等待上一次操作——在前面的函數中自調用了,一般不需要單獨調用
配置閃存地址空間——keil魔術棒里Debug對應設備的接口
手動分配閃存尾部的數據空間,給自己用;
擦除模式
判斷程序大小
分別是:代碼;只讀數據、讀寫數據、零初始化數據
main.c
#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(); //OLED初始化Key_Init(); //按鍵初始化Store_Init(); //參數存儲模塊初始化,在上電的時候將閃存的數據加載回Store_Data,實現掉電不丟失/*顯示靜態字符串*/OLED_ShowString(1, 1, "Flag:");OLED_ShowString(2, 1, "Data:");while (1){KeyNum = Key_GetNum(); //獲取按鍵鍵碼if (KeyNum == 1) //按鍵1按下{Store_Data[1] ++; //變換測試數據Store_Data[2] += 2;Store_Data[3] += 3;Store_Data[4] += 4;Store_Save(); //將Store_Data的數據備份保存到閃存,實現掉電不丟失}if (KeyNum == 2) //按鍵2按下{Store_Clear(); //將Store_Data的數據全部清0}OLED_ShowHexNum(1, 6, Store_Data[0], 4); //顯示Store_Data的第一位標志位OLED_ShowHexNum(3, 1, Store_Data[1], 4); //顯示Store_Data的有效存儲數據OLED_ShowHexNum(3, 6, Store_Data[2], 4);OLED_ShowHexNum(4, 1, Store_Data[3], 4);OLED_ShowHexNum(4, 6, Store_Data[4], 4);}
}
Store.c
#include "stm32f10x.h" // Device header
#include "MyFLASH.h"#define STORE_START_ADDRESS 0x0800FC00 //存儲的起始地址
#define STORE_COUNT 512 //存儲數據的個數uint16_t Store_Data[STORE_COUNT]; //定義SRAM數組/*** 函 數:參數存儲模塊初始化* 參 數:無* 返 回 值:無*/
void Store_Init(void)
{/*判斷是不是第一次使用*/if (MyFLASH_ReadHalfWord(STORE_START_ADDRESS) != 0xA5A5) //讀取第一個半字的標志位,if成立,則執行第一次使用的初始化{MyFLASH_ErasePage(STORE_START_ADDRESS); //擦除指定頁MyFLASH_ProgramHalfWord(STORE_START_ADDRESS, 0xA5A5); //在第一個半字寫入自己規定的標志位,用于判斷是不是第一次使用for (uint16_t i = 1; i < STORE_COUNT; i ++) //循環STORE_COUNT次,除了第一個標志位{MyFLASH_ProgramHalfWord(STORE_START_ADDRESS + i * 2, 0x0000); //除了標志位的有效數據全部清0}}/*上電時,將閃存數據加載回SRAM數組,實現SRAM數組的掉電不丟失*/for (uint16_t i = 0; i < STORE_COUNT; i ++) //循環STORE_COUNT次,包括第一個標志位{Store_Data[i] = MyFLASH_ReadHalfWord(STORE_START_ADDRESS + i * 2); //將閃存的數據加載回SRAM數組}
}/*** 函 數:參數存儲模塊保存數據到閃存* 參 數:無* 返 回 值:無*/
void Store_Save(void)
{MyFLASH_ErasePage(STORE_START_ADDRESS); //擦除指定頁for (uint16_t i = 0; i < STORE_COUNT; i ++) //循環STORE_COUNT次,包括第一個標志位{MyFLASH_ProgramHalfWord(STORE_START_ADDRESS + i * 2, Store_Data[i]); //將SRAM數組的數據備份保存到閃存}
}/*** 函 數:參數存儲模塊將所有有效數據清0* 參 數:無* 返 回 值:無*/
void Store_Clear(void)
{for (uint16_t i = 1; i < STORE_COUNT; i ++) //循環STORE_COUNT次,除了第一個標志位{Store_Data[i] = 0x0000; //SRAM數組有效數據清0}Store_Save(); //保存數據到閃存
}MyFLASH.c
#include "stm32f10x.h" // Device header/*** 函 數:FLASH讀取一個32位的字* 參 數:Address 要讀取數據的字地址* 返 回 值:指定地址下的數據*/
uint32_t MyFLASH_ReadWord(uint32_t Address)
{return *((__IO uint32_t *)(Address)); //使用指針訪問指定地址下的數據并返回
}/*** 函 數:FLASH讀取一個16位的半字* 參 數:Address 要讀取數據的半字地址* 返 回 值:指定地址下的數據*/
uint16_t MyFLASH_ReadHalfWord(uint32_t Address)
{return *((__IO uint16_t *)(Address)); //使用指針訪問指定地址下的數據并返回
}/*** 函 數:FLASH讀取一個8位的字節* 參 數:Address 要讀取數據的字節地址* 返 回 值:指定地址下的數據*/
uint8_t MyFLASH_ReadByte(uint32_t Address)
{return *((__IO uint8_t *)(Address)); //使用指針訪問指定地址下的數據并返回
}/*** 函 數:FLASH全擦除* 參 數:無* 返 回 值:無* 說 明:調用此函數后,FLASH的所有頁都會被擦除,包括程序文件本身,擦除后,程序將不復存在*/
void MyFLASH_EraseAllPages(void)
{FLASH_Unlock(); //解鎖FLASH_EraseAllPages(); //全擦除FLASH_Lock(); //加鎖
}/*** 函 數:FLASH頁擦除* 參 數:PageAddress 要擦除頁的頁地址* 返 回 值:無*/
void MyFLASH_ErasePage(uint32_t PageAddress)
{FLASH_Unlock(); //解鎖FLASH_ErasePage(PageAddress); //頁擦除FLASH_Lock(); //加鎖
}/*** 函 數:FLASH編程字* 參 數:Address 要寫入數據的字地址* 參 數:Data 要寫入的32位數據* 返 回 值:無*/
void MyFLASH_ProgramWord(uint32_t Address, uint32_t Data)
{FLASH_Unlock(); //解鎖FLASH_ProgramWord(Address, Data); //編程字FLASH_Lock(); //加鎖
}/*** 函 數:FLASH編程半字* 參 數:Address 要寫入數據的半字地址* 參 數:Data 要寫入的16位數據* 返 回 值:無*/
void MyFLASH_ProgramHalfWord(uint32_t Address, uint16_t Data)
{FLASH_Unlock(); //解鎖FLASH_ProgramHalfWord(Address, Data); //編程半字FLASH_Lock(); //加鎖
}
15-2 讀取芯片ID
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
//#include "OLED_Font.h"
//4-1 OLED顯示屏
int main(void){OLED_Init();OLED_ShowString(1,1,"F_SIZE:");OLED_ShowHexNum(1,8,*((__IO uint16_t *)(0x1FFFF7E0)),4);//ID地址:0x1FFFF7E0//顯示UIDOLED_ShowString(2,1,"UID:");//偏移量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){}
}