最近在搞一个项目,其中一个功能是要求将数据保存在STM32的片上Flash,并能够在程序运行时将Flash内的数据读取出来放入RAM中的对应位置,便于设备其他功能对这些不可失数据进行读取、使用。自己瞎搞Flash地址,导致自己搬石头砸自己的脚,还多次进HardFault_Handle,别问为什么,问就是访问量非法地址。
其实说简单一点就是:
那就简单理一下思路,当然要以STM32F429的芯片资料为核心来理
使用的时候需要注意以下几点:
接下来开始说一下个人操作
其实对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对应空间存过数据,懒得改了,就第一个给你们看看吧!!哈哈哈哈哈)
结果已给,打完收工!