FLASH閃存
文章目錄
- FLASH閃存
- 1.存儲器映像位置
- 2.FLASH簡介
- 3.閃存模塊組織
- 3.2閃存的共性:
- 4.FLASH基本結構
- 4.1FLASH解鎖
- 4.2使用指針訪問寄存器
- 5.選項字節
- 5.1選項字節編程
- 5.2選項字節擦除
- 6.相關函數介紹
- 7.讀取內部FLASH(實操)
- 7.1接線圖
- 7.2工程結構
- 7.3代碼
- 8.讀取芯片ID(實操)
- 8.1接線圖
- 8.2代碼
閃存是一個通用的名詞,表示一種非易失性,掉電不丟失的存儲器。比如PSI的W25Q64芯片。
本節的內容是stm32的內部閃存,也就是我們下載程序的時候,這個程序所存儲的地方。
1.存儲器映像位置
存儲器映像
類型 | 起始地址 | 存儲器 | 用途 |
---|---|---|---|
ROM | 0x0800 0000 | 程序存儲器Flash | 存儲C語言編譯后的程序代碼 |
0x1FFF F000 | 系統存儲器 | 存儲BootLoader,用于串口下載 | |
0x1FFF F800 | 選項字節 | 存儲一些獨立于程序代碼的配置參數 | |
RAM | 0x2000 0000 | 運行內存SRAM | 存儲運行過程中的臨時變量 |
0x4000 0000 | 外設寄存器 | 存儲各個外設的配置參數 | |
0xE000 0000 | 內核外設寄存器 | 存儲內核各個外設的配置參數 |
其中程序存儲器是空間最大,最主要的部分,稱作主存儲器。
2.FLASH簡介
- STM32F1系列的FLASH包含程序存儲器、系統存儲器和選項字節三個部分,通過閃存存儲器接口(外設)可以對程序存儲器和選項字節進行擦除和編程
- 讀寫FLASH的用途:
- **利用程序存儲器的剩余空間來保存掉電不丟失的用戶數據 **
- 通過在程序中編程(IAP),實現程序的自我更新
- 在線編程(In-Circuit Programming – ICP)用于更新程序存儲器的全部內容,它通過JTAG、SWD協議或系統加載程序(Bootloader)下載程序
- 在程序中編程(In-Application Programming – IAP)可以使用微控制器支持的任一種通信接口下載程序
JTAG、SWD協議就是仿真器下載程序,就是目前使用的ST_Link使用SWD下載程序。每次下載都是把整個程序完全更新掉。
Bootloader串口下載,也是更新整個程序,這就是ICP下載方式。
3.閃存模塊組織
C8T6的閃存容量是64K,屬于中容量產品。
啟動程序代碼:系統存儲器,存放的是原廠寫入的Bootloader,用于串口下載
用戶選擇字節:也就是選項字節,存放一些獨立的參數
閃存存儲器接口寄存器,實際上并不屬于閃存,地址都是40開頭的,說明這個存儲器接口寄存器,就是一個普通的外設和GPIO、定時器,串口等等都是一個性質的東西,都是SARM。
簡言之,閃存存儲器接口寄存器就是上面那些閃存的管理員,這些寄存器就是用來控制擦除和編程這個過程的
閃存的基本單位為頁,共128K,而C8T6只有一半,為64頁,共64K。所說的閃存的容量也只是主存儲器的容量。
3.2閃存的共性:
- 寫入前必須擦除
- 擦除必須以最小單位進行
- 擦除后數據位全變為1
- 數據只能1寫0,不能0寫1
- 擦除和寫入之后都需要等待忙
4.FLASH基本結構
分為程序存儲器,系統存儲器和選項字節三部分。
4.1FLASH解鎖
- FPEC共有三個鍵值:
- RDPRT鍵 = 0x000000A5
- KEY1 = 0x45670123
- KEY2 = 0xCDEF89AB
- 解鎖:
- 復位后,FPEC被保護,不能寫入FLASH_CR
- 在FLASH_KEYR先寫入KEY1,再寫入KEY2,解鎖
- 錯誤的操作序列會在下次復位前鎖死FPEC和FLASH_CR
- 加鎖: 設置FLASH_CR中的LOCK位鎖住FPEC和FLASH_CR(就是LOCK位寫入1就好了)
4.2使用指針訪問寄存器
- 使用指針讀指定地址下的存儲器:
- uint16_t Data = *((__IO uint16_t *)(0x08000000));
- 使用指針寫指定地址下的存儲器:
- *((__IO uint16_t *)(0x08000000)) = 0x1234;
- 其中:
- #define __IO volatile
讀直接讀寫入,寫需要先解鎖。
5.選項字節
- RDP:寫入RDPRT鍵(0x000000A5)后解除讀保護
- USER:配置硬件看門狗和進入停機/待機模式是否產生復位
- Data0/1:用戶可自定義使用
- WRP0/1/2/3:配置寫保護,每一個位對應保護4個存儲頁(中容量)
寫入RDP存儲器時,要在對應的存儲器寫入反碼。(寫入RDP時要在nRDP寫入對應的反碼)。如果不是反碼的關系,則代表數據無效,對應功能不執行。
5.1選項字節編程
- 檢查FLASH_SR的BSY位,以確認沒有其他正在進行的編程操作
- 解鎖FLASH_CR的OPTWRE位
- 設置FLASH_CR的OPTPG位為1
- 寫入要編程的半字到指定的地址
- 等待BSY位變為0
- 讀出寫入的地址并驗證數據*
5.2選項字節擦除
- 檢查FLASH_SR的BSY位,以確認沒有其他正在進行的閃存操作
- 解鎖FLASH_CR的OPTWRE位
- 設置FLASH_CR的OPTER位為1
- 設置FLASH_CR的STRT位為1等待BSY位變為0
- 讀出被擦除的選擇字節并做驗證
6.相關函數介紹
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);//加鎖,LOCK位設置為1
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);//用戶選項的三個配置位
//上面4個是選項字節的寫入
//對主閃存和選項字節,進行擦除和編程的函數
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);//獲取狀態
FLASH_Status FLASH_WaitForLastOperation(uint32_t Timeout);//等待上一次操作,系統內部會調用,所以不用單獨調用
7.讀取內部FLASH(實操)
7.1接線圖
7.2工程結構
這里打算弄兩個底層模塊:
最底層叫MY_FLASH:這里實現閃存最基本的三個功能:讀取、擦除和編程
上面層叫STORE:實現參數數據的讀寫和數據管理,定義一個SRAM數組,需要掉電不丟失的參數就寫到SRAM數組里,最后調用保存的函數,這個SRAM數組就自動備份到閃存里,上電后store初始化,會自動再把閃存里的數據讀回到SRAM數組里
最終實現的功能是:任意讀寫參數,并且這些參數是掉電不丟失的
7.3代碼
代碼部分主要就是對封裝完的庫函數進行調用。將數據封裝到,數組,再將數組的內容寫到閃存,在重新上電后再寫回去實現掉電不丟失。
主程序main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "LED.h"
#include "KEY.h"
#include "OLED.h"
//#include "OLED_Font.h"
#include "Store.h"uint16_t KeyNum = 0;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];void Store_Init(void){//判斷是不是第一次使用if(MyFLASH_ReadHalfWord(STORE_START_ADDRESS)!=0xA5A5){//A5A5不是固定的,只是判斷是不是某個你設置的值,唯一,標志,IDMyFLASH_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);}
}//保存:擦除界面后將數組的數據讀取到閃存界面
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]);//將數組數據備份到閃存}
}//手動清除操作
void Store_Clear(void){for(uint16_t i = 1;i<STORE_COUNT;i++){Store_Data[i] = 0x0000;}Store_Save();
}
Store.h
#ifndef __STORE_H
#define __STORE_Hextern uint16_t Store_Data[];void Store_Init(void);
void Store_Save(void);
void Store_Clear(void);#endif
MyFLASH.c
#include "stm32f10x.h" // Device header//獲取32位的字
uint32_t MyFLASH_ReadWord(uint32_t Address){//stm32的地址都是32位的return *((__IO uint32_t * )(Address));
}//獲取16位的半字
uint16_t MyFLASH_ReadHalfWord(uint32_t Address){return *((__IO uint16_t * )(Address));
}
//獲取一字節
uint8_t MyFLASH_ReadByte(uint32_t Address){return *((__IO uint8_t * )(Address));//使用指針訪問
}//全擦除
void MyFLASH_EraseAllPages(void){FLASH_Unlock();//解鎖FLASH_EraseAllPages();//全擦除FLASH_Lock();//加鎖
}//頁擦除
void MyFLASH_ErasePage(uint32_t PageAddress){FLASH_Unlock();FLASH_ErasePage(PageAddress);FLASH_Lock();
}//寫入32位字
void MyFLASH_ProgramWord(uint32_t Address,uint32_t Data){FLASH_Unlock();FLASH_ProgramWord(Address,Data);FLASH_Lock();
}//寫入16位半字
void MyFLASH_ProgramHalfWord(uint32_t Address,uint16_t Data){FLASH_Unlock();FLASH_ProgramHalfWord(Address,Data);FLASH_Lock();
}
MyFLASH.h
#ifndef __MY_FLASH
#define __MY_FLASHuint32_t MyFLASH_ReadWord(uint32_t Address);
uint16_t MyFLASH_ReadHalfWord(uint32_t Address);
uint8_t MyFLASH_ReadByte(uint32_t Address);void MyFLASH_EraseAllPages(void);
void MyFLASH_ErasePage(uint32_t PageAddress);void MyFLASH_ProgramWord(uint32_t Address, uint32_t Data);
void MyFLASH_ProgramHalfWord(uint32_t Address, uint16_t Data);#endif
8.讀取芯片ID(實操)
8.1接線圖
8.2代碼
主程序部分主要是讀取 STM32F10x 系列微控制器的唯一設備標識符(UID)并通過 OLED 顯示。
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "LED.h"
#include "KEY.h"
#include "OLED.h"
//#include "OLED_Font.h"int main(void){OLED_Init();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){}
}