最近在搞一個項目,其中一個功能是要求將數據保存在STM32的片上Flash,並能夠在程序運行時將Flash內的數據讀取出來放入RAM中的對應位置,便於設備其他功能對這些不可失數據進行讀取、使用。自己瞎搞Flash地址,導致自己搬石頭砸自己的腳,還多次進HardFault_Handle,別問爲什麼,問就是訪問量非法地址。
其實說簡單一點就是:
- 有一個設備要做,且設備裏面有些東西需要斷電也能存
- 不準用SD卡,就用片內的EEPROM,也就是Flash
- 用的是STM32F429的板子,以下內容都是基於F429圍繞開展
那就簡單理一下思路,當然要以STM32F429的芯片資料爲核心來理
- Flash的使用方案:之前考慮過兩種,一種是在Flash的0扇區或者1扇區作爲存儲地址,代碼下載位置從第二扇區開始;第二種是將存儲地址放在存儲代碼之後的扇區;
- 在對第一種實踐時出現了大問題,在沒有BootIAP引導的情況下,設備會默認從Flash的首地址作爲程序的開頭,就會導致從第二扇區寫的應用程序根本讀取不到,也不會進到第二扇區,程序就卡住了,找來找去沒有找到靠譜的處理方案,就暫時擱置
- 第二種方案可以實現,但是對內存概念需要清晰一點
使用的時候需要注意以下幾點:
- 程序存儲的Flash地址與數據存儲區域要嚴格劃分
- 對Flash的寫入每次都需要將扇區完全擦除,所以寫入過程需要特別注意(這裏提供一個個人思路,在需要對多組數據差時存區的情況下,在對數據進行存儲時將Flash數據讀取出來,在RAM中開闢一個暫存空間,將數據更新到暫存空間後再寫入Flash,這個沒試過,純屬個人想法,對RAM的負荷會比較大)
接下來開始說一下個人操作
其實對Flash的讀寫大同小異,操作就那麼幾個操作,關鍵問題在於操作完之後你的代碼還沒有問題,所以上面說的嚴格區分儲存區域和代碼區域就是爲了保證代碼不出問題;
以下是幾個關鍵點:
1.代碼區域設置,本人使用的開發環境是Keil
因爲F429的Flash空間有2M,但是我只針對前面的1M進行開發,所以我將代碼空間進行壓縮,長度從0x100000壓縮到了0xE0000,將最後扇區11的128K空間留出來作爲數據存儲空間。
同樣,改完代碼的Flash空間後如果用的時ST-Link下載程序的話,再Debug裏也要改,如下所示
改完就能理解爲以後用後面的空間不會和代碼區打架了,也就不容易進HardFault_Handle了。
然後就是Coding時間,因爲Flash代碼大同小異,也就是解鎖寫入又上鎖那一套,這裏我就偷個懶,不自己寫了,在原子哥的基礎上改了一下,用在我的工作內部,下面貼代碼(文件名我設的是FlashRW.c,FlashRW.h自己新建)
下面是FlashRW.c
#include "FlashRW.h"
/*==============================================================================
函數名 : STMFLASH_ReadWord()
功能 : 讀取指定地址的字(32位數據)
參數1 : faddr:讀地址
參數2 :
參數3 :
返回值 : 對應數據.
作者 : ASWaterbenben
日期 : 2020-6-14
備註 :
==============================================================================*/
uint32_t STMFLASH_ReadWord(uint32_t faddr)
{
return *(__IO uint32_t*)faddr;
}
/*==============================================================================
函數名 : STMFLASH_GetFlashSector()
功能 : 獲取某個地址所在的flash扇區
參數1 : addr:flash地址
返回值 : 0~11,即addr所在的扇區
作者 : ASWaterbenben
日期 : 2020-6-14
備註 :
==============================================================================*/
uint8_t STMFLASH_GetFlashSector(uint32_t addr)
{
if(addr<ADDR_FLASH_SECTOR_1)return FLASH_SECTOR_0;
else if(addr<ADDR_FLASH_SECTOR_2)return FLASH_SECTOR_1;
else if(addr<ADDR_FLASH_SECTOR_3)return FLASH_SECTOR_2;
else if(addr<ADDR_FLASH_SECTOR_4)return FLASH_SECTOR_3;
else if(addr<ADDR_FLASH_SECTOR_5)return FLASH_SECTOR_4;
else if(addr<ADDR_FLASH_SECTOR_6)return FLASH_SECTOR_5;
else if(addr<ADDR_FLASH_SECTOR_7)return FLASH_SECTOR_6;
else if(addr<ADDR_FLASH_SECTOR_8)return FLASH_SECTOR_7;
else if(addr<ADDR_FLASH_SECTOR_9)return FLASH_SECTOR_8;
else if(addr<ADDR_FLASH_SECTOR_10)return FLASH_SECTOR_9;
else if(addr<ADDR_FLASH_SECTOR_11)return FLASH_SECTOR_10;
else if(addr<ADDR_FLASH_SECTOR_12)return FLASH_SECTOR_11;
else if(addr<ADDR_FLASH_SECTOR_13)return FLASH_SECTOR_12;
else if(addr<ADDR_FLASH_SECTOR_14)return FLASH_SECTOR_13;
else if(addr<ADDR_FLASH_SECTOR_15)return FLASH_SECTOR_14;
else if(addr<ADDR_FLASH_SECTOR_16)return FLASH_SECTOR_15;
else if(addr<ADDR_FLASH_SECTOR_17)return FLASH_SECTOR_16;
else if(addr<ADDR_FLASH_SECTOR_18)return FLASH_SECTOR_17;
else if(addr<ADDR_FLASH_SECTOR_19)return FLASH_SECTOR_18;
else if(addr<ADDR_FLASH_SECTOR_20)return FLASH_SECTOR_19;
else if(addr<ADDR_FLASH_SECTOR_21)return FLASH_SECTOR_20;
else if(addr<ADDR_FLASH_SECTOR_22)return FLASH_SECTOR_21;
else if(addr<ADDR_FLASH_SECTOR_23)return FLASH_SECTOR_22;
return FLASH_SECTOR_23;
}
/*==============================================================================
函數名 : STMFLASH_Write()
功能 : 從指定地址開始寫入指定長度的數據
參數1 : WriteAddr:起始地址(此地址必須爲4的倍數!!)
參數2 : pBuffer:數據指針
參數3 : NumToWrite:字(32位)數(就是要寫入的32位數據的個數.)
返回值 : 對應數據.
作者 : ASWaterbenben
日期 : 2020-6-14
備註 : 因爲STM32F4的扇區實在太大,沒辦法本地保存扇區數據,所以本函數
// 寫地址如果非0XFF,那麼會先擦除整個扇區且不保存扇區數據.所以
// 寫非0XFF的地址,將導致整個扇區數據丟失.建議寫之前確保扇區裏
// 沒有重要數據,最好是整個扇區先擦除了,然後慢慢往後寫.
==============================================================================*/
void STMFLASH_Write(uint32_t WriteAddr,uint32_t *pBuffer,uint32_t NumToWrite)
{
FLASH_EraseInitTypeDef FlashEraseInit;
HAL_StatusTypeDef FlashStatus=HAL_OK;
uint32_t SectorError=0;
uint32_t addrx=0;
uint32_t endaddr=0;
if(WriteAddr<STM32_FLASH_BASE||WriteAddr%4)return; //非法地址
HAL_FLASH_Unlock(); //解鎖
addrx=WriteAddr; //寫入的起始地址
endaddr=WriteAddr+NumToWrite*4; //寫入的結束地址
if(addrx<0X1FFF0000)
{
while(addrx<endaddr) //掃清一切障礙.(對非FFFFFFFF的地方,先擦除)
{
if(STMFLASH_ReadWord(addrx)!=0XFFFFFFFF)//有非0XFFFFFFFF的地方,要擦除這個扇區
{
FlashEraseInit.TypeErase=FLASH_TYPEERASE_SECTORS; //擦除類型,扇區擦除
FlashEraseInit.Sector=STMFLASH_GetFlashSector(addrx); //要擦除的扇區
FlashEraseInit.NbSectors=1; //一次只擦除一個扇區
FlashEraseInit.VoltageRange=FLASH_VOLTAGE_RANGE_3; //電壓範圍,VCC=2.7~3.6V之間!!
if(HAL_FLASHEx_Erase(&FlashEraseInit,&SectorError)!=HAL_OK)
{
break;//發生錯誤了
}
}else addrx+=4;
FLASH_WaitForLastOperation(FLASH_WAITETIME); //等待上次操作完成
}
}
FlashStatus=FLASH_WaitForLastOperation(FLASH_WAITETIME); //等待上次操作完成
if(FlashStatus==HAL_OK)
{
while(WriteAddr<endaddr)//寫數據
{
if(HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD,WriteAddr,*pBuffer)!=HAL_OK)//寫入數據
{
break; //寫入異常
}
WriteAddr+=4;
pBuffer++;
}
}
HAL_FLASH_Lock(); //上鎖
}
/*==============================================================================
函數名 : STMFLASH_Read()
功能 : 從指定地址開始讀出指定長度的數據
參數1 : ReadAddr:起始地址
參數2 : pBuffer:數據指針
參數3 : NumToRead:字(32位)數
返回值 :
作者 : ASWaterbenben
日期 : 2020-6-14
備註 :
==============================================================================*/
void STMFLASH_Read(uint32_t ReadAddr,uint32_t *pBuffer,uint32_t NumToRead)
{
uint32_t i;
for(i=0;i<NumToRead;i++)
{
pBuffer[i]=STMFLASH_ReadWord(ReadAddr);//讀取4個字節.
ReadAddr+=4;//偏移4個字節.
}
}
//////////////////////////////////////////測試用///////////////////////////////////////////
//WriteAddr:起始地址
//WriteData:要寫入的數據
void Test_Write(uint32_t WriteAddr,uint32_t WriteData)
{
STMFLASH_Write(WriteAddr,&WriteData,1);//寫入一個字
}
對應的頭文件:FlashRW.h
#ifndef __flashrw_H
#define __flashrw_H
#ifdef __cplusplus
extern "C" {
#endif
#include "main.h"
//FLASH起始地址
#define STM32_FLASH_BASE 0x080E0000 //STM32 FLASH的起始地址,也就是之前說的11扇區起始地址
#define FLASH_WAITETIME 50000 //FLASH等待超時時間
//FLASH 扇區的起始地址
#define ADDR_FLASH_SECTOR_0 ((uint32_t)0x08000000) //扇區0起始地址, 16 Kbytes
#define ADDR_FLASH_SECTOR_1 ((uint32_t)0x08004000) //扇區1起始地址, 16 Kbytes
#define ADDR_FLASH_SECTOR_2 ((uint32_t)0x08008000) //扇區2起始地址, 16 Kbytes
#define ADDR_FLASH_SECTOR_3 ((uint32_t)0x0800C000) //扇區3起始地址, 16 Kbytes
#define ADDR_FLASH_SECTOR_4 ((uint32_t)0x08010000) //扇區4起始地址, 64 Kbytes
#define ADDR_FLASH_SECTOR_5 ((uint32_t)0x08020000) //扇區5起始地址, 128 Kbytes
#define ADDR_FLASH_SECTOR_6 ((uint32_t)0x08040000) //扇區6起始地址, 128 Kbytes
#define ADDR_FLASH_SECTOR_7 ((uint32_t)0x08060000) //扇區7起始地址, 128 Kbytes
#define ADDR_FLASH_SECTOR_8 ((uint32_t)0x08080000) //扇區8起始地址, 128 Kbytes
#define ADDR_FLASH_SECTOR_9 ((uint32_t)0x080A0000) //扇區9起始地址, 128 Kbytes
#define ADDR_FLASH_SECTOR_10 ((uint32_t)0x080C0000) //扇區10起始地址,128 Kbytes
#define ADDR_FLASH_SECTOR_11 ((uint32_t)0x080E0000) //扇區11起始地址,128 Kbytes
#define ADDR_FLASH_SECTOR_12 ((uint32_t)0x08100000) //扇區12起始地址, 16 Kbytes
#define ADDR_FLASH_SECTOR_13 ((uint32_t)0x08104000) //扇區13起始地址, 16 Kbytes
#define ADDR_FLASH_SECTOR_14 ((uint32_t)0x08108000) //扇區14起始地址, 16 Kbytes
#define ADDR_FLASH_SECTOR_15 ((uint32_t)0x0810C000) //扇區15起始地址, 16 Kbytes
#define ADDR_FLASH_SECTOR_16 ((uint32_t)0x08110000) //扇區16起始地址, 64 Kbytes
#define ADDR_FLASH_SECTOR_17 ((uint32_t)0x08120000) //扇區17起始地址, 128 Kbytes
#define ADDR_FLASH_SECTOR_18 ((uint32_t)0x08140000) //扇區18起始地址, 128 Kbytes
#define ADDR_FLASH_SECTOR_19 ((uint32_t)0x08160000) //扇區19起始地址, 128 Kbytes
#define ADDR_FLASH_SECTOR_20 ((uint32_t)0x08180000) //扇區20起始地址, 128 Kbytes
#define ADDR_FLASH_SECTOR_21 ((uint32_t)0x081A0000) //扇區21起始地址, 128 Kbytes
#define ADDR_FLASH_SECTOR_22 ((uint32_t)0x081C0000) //扇區22起始地址, 128 Kbytes
#define ADDR_FLASH_SECTOR_23 ((uint32_t)0x081E0000) //扇區23起始地址, 128 Kbytes
uint32_t STMFLASH_ReadWord(uint32_t faddr); //讀出字
void STMFLASH_Write(uint32_t WriteAddr,uint32_t *pBuffer,uint32_t NumToWrite); //從指定地址開始寫入指定長度的數據
void STMFLASH_Read(uint32_t ReadAddr,uint32_t *pBuffer,uint32_t NumToRead); //從指定地址開始讀出指定長度的數據
//測試寫入
void Test_Write(uint32_t WriteAddr,uint32_t WriteData);
#ifdef __cplusplus
}
#endif
#endif /* __flashrw_H */
把頭文件內的Flash起始地址改成自己設置的數據區即可,由於項目是用STM32CubeMX生成的,所以我這裏直接include了main.h文件。
我在mian函數使用了鍵盤,鍵盤程序很簡單,我就不多BB,自己處理,下面貼我的main函數
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "FlashRW.h"
#include "stdio.h"
#include "string.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
//TEXT_Buffer0大小爲2k
const uint8_t TEXT_Buffer0[]={"AAAAAAAATCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterAAAAAAAATCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCounterTCount"};
const uint8_t TEXT_Buffer1[]={"STM32 FLASH TE-1"};
const uint8_t TEXT_Buffer2[]={"STM32 FLASH TE-2"};
#define TEXT_LENTH sizeof(TEXT_Buffer0) //數組長度
//#define TEXT_LENTH 0x800 //數組長度
#define SIZE TEXT_LENTH/2+((TEXT_LENTH%2)?1:0)
#define FLASH_SAVE_ADDR 0X080E0000 //設置FLASH 保存地址(必須爲4的倍數,且所在扇區,要大於本代碼所佔用到的扇區.
//否則,寫操作的時候,可能會導致擦除整個扇區,從而引起部分程序丟失.引起死機.
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
uint8_t key = 0,flag = 0;;
uint8_t datatemp[SIZE];
// char *pcWriteBuffer;
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
printf("ASWaterbenben!\r\n Size is: 0x%x",SIZE);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
key=KEY_Scan(0);//鍵盤掃描函數
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
if(key==KEY0_PRES)
{
flag = 1;
}
if(key==KEY1_PRES)
{
flag = 2;
}
if(key==KEY2_PRES)
{
flag = 3;
}
if(key==WKUP_PRES)
{
flag = 4;
}
switch(flag)
{
case 1: //將Flash內容讀取到datatemp中
STMFLASH_Read(FLASH_SAVE_ADDR,(uint32_t*)datatemp,SIZE);
printf("\r\nDATA: Flash --> datatemp\r\n");
break;
case 2: //將datatemp內容存儲到Flash中
STMFLASH_Write(FLASH_SAVE_ADDR,(uint32_t*)datatemp,SIZE);
printf("\r\nDATA: datatemp --> Flash\r\n");
break;
case 3: //將TEXT_Buffer0內容存儲到Flash中
STMFLASH_Write(FLASH_SAVE_ADDR,(uint32_t*)TEXT_Buffer0,SIZE);
printf("\r\nDATA: TEXT_Buffer0 --> Flash\r\n");
break;
case 4: //讀取Flash內容並打印出來
printf("The Data Readed Is:\r\n");
for (int i=0;i<SIZE;i++)
printf("0x%x ",datatemp[i]);
break;
default:
// sprintf(pcWriteBuffer, "\r\nHave no Config !\r\n");
break;
flag = 0;
}
/* USER CODE END 3 */
}
上面的代碼放到main函數對應位置貼好,按下幾個按鍵就會出現以上情況,我的prinft函數是重定向到串口1了,不會搞的看這裏:
STM32Cube的串口設置(一)即學即用
第九步就是重定向
搞完之後就要按照我的博客慣例給個結果!這裏需要操作,大家將就看,我把所有操作都搞在一起了
主要操作是初始化—>從Flash讀取內容到datatemp—>顯示讀取出來的datatemp內容(Flash對應空間存過數據,懶得改了,就第一個給你們看看吧!!哈哈哈哈哈)
結果已給,打完收工!