1. 內(nèi)部FLASH簡介
????????之前的文章中介紹過STM32F1利用SPI與外部FLASH(W25QXX芯片)通訊的例程,本例程將介紹STM32F1的內(nèi)部FLASH,通過內(nèi)部FLASH實(shí)現(xiàn)數(shù)據(jù)讀寫操作。 不同型號的STM32,其FLASH容量也有所不同,最小的只有16K字節(jié),最大的則達(dá)到了1024K字節(jié)。此處我們使用的是STM32F103ZET6,其FLASH容量為512K字節(jié),屬于大容量產(chǎn)品,大容量產(chǎn)品的閃存模塊組織圖如下圖示
STM32F1的閃存模塊由:主存儲器、信息塊和閃存存儲器接口寄存器3部分組成
- 主存儲器:用來存放代碼和數(shù)據(jù)常量,起始地址是0x08000000,BOOT0和BOOT1都接GND時,就是從該起始地址運(yùn)行代碼的
- 信息塊:分為2個小部分,啟動程序代碼是用來存儲ST自帶的啟動程序,用于串口下載代碼,BOOT0接3.3V,BOOT1接GND時,運(yùn)行的就是這部分代碼;選擇字節(jié)則一般用于配置寫保護(hù)、讀保護(hù)等功能
- 閃存存儲器接口寄存器:用于控制閃存讀寫等,是整個閃存模塊的控制機(jī)構(gòu)
對主存儲器和信息塊的寫入由內(nèi)嵌的閃存編程/擦除控制器(FPEC)管理:編程與擦除的高電壓由內(nèi)部產(chǎn)生。在執(zhí)行閃存寫操作時,任何對閃存的讀操作都會鎖住總線,在寫操作完成后讀操作才能正確地進(jìn)行,即在進(jìn)行寫或擦除操作時,不能進(jìn)行代碼或數(shù)據(jù)的讀取操作。
下面介紹閃存的讀取、編程和擦除:
<1>. 閃存的讀取 內(nèi)置閃存模塊可以在通用地址空間直接尋址,任何32位數(shù)據(jù)的讀操作都能訪問閃存模塊的內(nèi)容并得到相應(yīng)的數(shù)據(jù)。
例如,要從地址addr,讀取一個半字,可通過如下語句讀?。?
data = *(__IO uint16_t*)addr
?將addr強(qiáng)制轉(zhuǎn)換為vu16指針,然后取該指針?biāo)赶虻牡刂返闹担吹玫搅薬ddr地址的值
<2>. 閃存的編程 STM32的閃存編程是由FPEC(閃存編程和擦除控制器)模塊處理的,這個模塊包含7個32位寄存器,它們分別是:
FPEC鍵寄存器(FLASH_KEYR)
選擇字節(jié)鍵寄存器(FLASH_OPTKEYR)
閃存控制寄存器(FLASH_CR)
閃存狀態(tài)寄存器(FLASH_SR)
閃存地址寄存器(FLASH_AR)
選擇字節(jié)寄存器(FLASH_OBR)
寫保護(hù)寄存器(FLASH_WRPR)
其中FPEC鍵寄存器共有3中鍵值:?PDPRT=0x000000A5; KEY1=0x45670123; KEY2=0xCDEF89AB
?STM32復(fù)位后,F(xiàn)PEC模塊是被保護(hù)的,不能寫入FLASH_CR寄存器;通過寫入特定的序列到FLASH_KEYR寄存器可以打開FPEC模塊(即寫入KEY1和KEY2),只有在寫保護(hù)被解除后,才能操作相關(guān)寄存器。 閃存編程過程如下圖所示:
<3>. 閃存的擦除 閃存編程的時候,要先判斷其寫入地址的FLASH是被擦除了的(也就是其值必須是0xFFFF),否則無法寫入。閃存擦除分為頁擦除和整片擦除。 閃存頁擦除過程如下圖示:
官方固件HAL庫FLASH操作的幾個常見函數(shù):
//源文件: stm32f1xx_hal_flash.c和stm32f1xx_hal_flash_ex.c
HAL_FLASH_Unlock(void); //解鎖函數(shù)
HAL_FLASH_Lock(void); //鎖定函數(shù)
HAL_FLASH_Program(uint32_t TypeProgram, uint32_t Address, uint64_t Data); //寫操作函數(shù)
HAL_FLASHEx_Erase(FLASH_EraseInitTypeDef *pEraseInit, uint32_t *SectorError); //擦除函數(shù)
HAL_FLASH_WaitForLastOperation(uint32_t Timeout); //等待操作完成函數(shù)
?
2、 硬件設(shè)計(jì)
led2指示燈用來提示系統(tǒng)運(yùn)行狀態(tài),s1按鍵用來控制FLASH的數(shù)據(jù)寫入,s2按鍵用來控制FLASH的數(shù)據(jù)讀取,數(shù)據(jù)的寫入與讀取信息通過串口1打印出來
- LED2指示燈
- S2和S1按鍵
- USART1
- STM32F1內(nèi)部FLASH
?3、STM32CubeMX設(shè)置
- RCC設(shè)置外接HSE,時鐘設(shè)置為72M
- PE5設(shè)置為GPIO推挽輸出模式、上拉、高速、默認(rèn)輸出電平為高電平
- USART1選擇為異步通訊方式,波特率設(shè)置為115200Bits/s,傳輸數(shù)據(jù)長度為8Bit,無奇偶校驗(yàn),1位停止位
- PE3,PE4設(shè)置為GPIO輸入模式、上拉模式
- 輸入工程名,選擇工程路徑(不要有中文),選擇MDK-ARM V5;勾選Generated periphera initialization as a pair of ‘.c/.h’ files per IP ;點(diǎn)擊GENERATE CODE,生成工程代碼
4、程序編程
- 創(chuàng)建按鍵驅(qū)動文件key.c 和相關(guān)頭文件key.h
如果要對FLASH進(jìn)行寫入數(shù)據(jù),需要執(zhí)行以下四步:
- 解鎖FLASH
- 擦除FLASH
- 寫入FLASH
- 鎖住FLASH
- 創(chuàng)建FLASH驅(qū)動文件stmflash.c 和相關(guān)頭文件stmflash.h
#ifndef __STMFLASH_H__
#define __STMFLASH_H__
#include "main.h"
//FLASH起始地址
#define STM32_FLASH_BASE 0x08000000 //STM32 FLASH的起始地址
#define FLASH_WAITETIME 50000
extern void FLASH_PageErase(uint32_t PageAddress);
uint16_t STMFLASH_ReadHalfWord(uint32_t faddr); //讀出半字
void STMFLASH_Write_NoCheck(uint32_t WriteAddr,uint16_t *pBuffer,uint16_t NumToWrite);
void STMFLASH_Write(uint32_t WriteAddr,uint16_t *pBuffer,uint16_t NumToWrite); //從指定地址開始寫入指定長度的數(shù)據(jù)
void STMFLASH_Read(uint32_t ReadAddr,uint16_t *pBuffer,uint16_t NumToRead); //從指定地址開始讀出指定長度的數(shù)據(jù)
#endif
#include "stmflash.h"
uint16_t STMFLASH_ReadHalfWord(uint32_t faddr)
{
return *(__IO uint16_t*)faddr;
}
void STMFLASH_Write_NoCheck(uint32_t WriteAddr,uint16_t *pBuffer,uint16_t NumToWrite)
{
uint16_t i;
for(i=0;i<NumToWrite;i++)
{
HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD,WriteAddr,pBuffer[i]);
WriteAddr+=2; //地址增加2.
}
}
#define STM_SECTOR_SIZE 2048 //大容量STM32的扇區(qū)大小為2K
uint16_t STMFLASH_BUF[STM_SECTOR_SIZE/2];
void STMFLASH_Write(uint32_t WriteAddr,uint16_t *pBuffer,uint16_t NumToWrite)
{
uint32_t secpos; //扇區(qū)地址
uint16_t secoff; //扇區(qū)內(nèi)偏移地址(16位字計(jì)算)
uint16_t secremain; //扇區(qū)內(nèi)剩余地址(16位字計(jì)算)
uint16_t i;
uint32_t offaddr; //去掉0X08000000后的地址
if(WriteAddr<STM32_FLASH_BASE||(WriteAddr>=(STM32_FLASH_BASE+1024*512)))return;//非法地址
HAL_FLASH_Unlock(); //解鎖
offaddr=WriteAddr-STM32_FLASH_BASE; //實(shí)際偏移地址.
secpos=offaddr/STM_SECTOR_SIZE; //扇區(qū)地址 0~127 for STM32F103RBT6
secoff=(offaddr%STM_SECTOR_SIZE)/2; //在扇區(qū)內(nèi)的偏移(2個字節(jié)為基本單位.)
secremain=STM_SECTOR_SIZE/2-secoff; //扇區(qū)剩余空間大小
if(NumToWrite<=secremain)secremain=NumToWrite;//不大于該扇區(qū)范圍
while(1)
{
STMFLASH_Read(secpos*STM_SECTOR_SIZE+STM32_FLASH_BASE,STMFLASH_BUF,STM_SECTOR_SIZE/2);//讀出整個扇區(qū)的內(nèi)容
for(i=0;i<secremain;i++) //校驗(yàn)數(shù)據(jù)
{
if(STMFLASH_BUF[secoff+i]!=0XFFFF)break;//需要擦除
}
if(i<secremain) //需要擦除
{
FLASH_PageErase(secpos*STM_SECTOR_SIZE+STM32_FLASH_BASE); //擦除這個扇區(qū)
FLASH_WaitForLastOperation(FLASH_WAITETIME); //等待上次操作完成
CLEAR_BIT(FLASH->CR, FLASH_CR_PER); //清除CR寄存器的PER位,此操作應(yīng)該在FLASH_PageErase()中完成!
//但是HAL庫里面并沒有做,應(yīng)該是HAL庫bug!
for(i=0;i<secremain;i++)//復(fù)制
{
STMFLASH_BUF[i+secoff]=pBuffer[i];
}
STMFLASH_Write_NoCheck(secpos*STM_SECTOR_SIZE+STM32_FLASH_BASE,STMFLASH_BUF,STM_SECTOR_SIZE/2);//寫入整個扇區(qū)
}else
{
FLASH_WaitForLastOperation(FLASH_WAITETIME); //等待上次操作完成
STMFLASH_Write_NoCheck(WriteAddr,pBuffer,secremain);//寫已經(jīng)擦除了的,直接寫入扇區(qū)剩余區(qū)間.
}
if(NumToWrite==secremain)break;//寫入結(jié)束了
else//寫入未結(jié)束
{
secpos++; //扇區(qū)地址增1
secoff=0; //偏移位置為0
pBuffer+=secremain; //指針偏移
WriteAddr+=secremain*2; //寫地址偏移(16位數(shù)據(jù)地址,需要*2)
NumToWrite-=secremain; //字節(jié)(16位)數(shù)遞減
if(NumToWrite>(STM_SECTOR_SIZE/2))secremain=STM_SECTOR_SIZE/2;//下一個扇區(qū)還是寫不完
else secremain=NumToWrite;//下一個扇區(qū)可以寫完了
}
};
HAL_FLASH_Lock(); //上鎖
}
void STMFLASH_Read(uint32_t ReadAddr,uint16_t *pBuffer,uint16_t NumToRead)
{
uint16_t i;
for(i=0;i<NumToRead;i++)
{
pBuffer[i]=STMFLASH_ReadHalfWord(ReadAddr);//讀取2個字節(jié).
ReadAddr+=2;//偏移2個字節(jié).
}
}
- 在main.c文件下編寫STM32 flash測試代碼
/* USER CODE BEGIN 0 */
const uint8_t Text_Buf[] = {"STM32F103ZET6 FLASH TEST"};
#define TEXTSIZE sizeof(Text_Buf)
#define FLASH_SAVE_ADDR 0x08070000
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
///***省略***///
/* USER CODE BEGIN WHILE */
uint8_t key;
uint8_t Read_Buf[TEXTSIZE];
printf1("STM32 Flash Test...\r\n");
while (1)
{
key = KEY_Scan(0);
if(key == 1){
STMFLASH_Write(FLASH_SAVE_ADDR,(uint16_t *)Text_Buf,TEXTSIZE);
printf1("FLASH Write : %s\r\n",Text_Buf);
}
if(key == 2){
STMFLASH_Read(FLASH_SAVE_ADDR,(uint16_t *)Read_Buf,TEXTSIZE);
printf1("FLASH Read : %s\r\n",Read_Buf);
}
HAL_GPIO_TogglePin(GPIOE,GPIO_PIN_5);
HAL_Delay(200);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
5、下載驗(yàn)證
編譯無誤下載到開發(fā)板后,可以看到LED2指示燈不斷閃爍,當(dāng)按下S1按鍵后數(shù)據(jù)寫入到FLASH內(nèi),當(dāng)按下S2按鍵后將寫入的數(shù)據(jù)讀取出來,同時串口打印出相應(yīng)信息
6、參考文獻(xiàn)?
STM32CubeMX系列 | STM32內(nèi)部FLASH - 知乎 (zhihu.com)
?STM32CUBEMX-讀寫內(nèi)部Flash_stm32cubemx flash-CSDN博客文章來源:http://www.zghlxwxcb.cn/news/detail-845825.html
STM32CubeMX學(xué)習(xí)筆記(51)——讀寫內(nèi)部Flash_cubemx flash-CSDN博客文章來源地址http://www.zghlxwxcb.cn/news/detail-845825.html
到了這里,關(guān)于STM32CubeMX學(xué)習(xí)筆記16--- STM32內(nèi)部FLASH的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!