分享一篇如何在工程中将 double/float 型参数写入Flash并在需要的时候读取出来的代码。大家都知道 Flash 的读写是有寿命限制的,虽然我们读写的次数远不会达到这个次数,但是作为有强迫症的程序猿们一般都不能接受。而且读写 Flash 还要考虑安全性,不能破坏程序。STM32的 Flash 是分扇区的,Flash 的读写必须是以扇区为单位。下面介绍如何通过细分算法高效读写Flash 资源。
下面的代码是基于STM32F407VGT的 Flash 的读写,为了尽可能不对程序造成影响,我选择扇区十一作为参数保存的区域,然后对这个扇区再以 256Bit 进行细分,当这个扇区空余内存不足 256Bit 时自动对扇区十一进行擦除。通过这个方法,不用再担心反复擦除带来的寿命问题。
头文件
代码中的 sys.h 是采用的正点原子的文件。Flash_MicroSize要大于需要写入的数据的长度。
#ifndef __STMFLASH_H__
#define __STMFLASH_H__
#include "sys.h"
//FLASH起始地址
#define STM32_FLASH_BASE 0x08000000 //STM32 FLASH的起始地址
//FLASH 扇区的起始地址
#define ADDR_FLASH_SECTOR_0 ((u32)0x08000000) //扇区0起始地址, 16 Kbytes
#define ADDR_FLASH_SECTOR_1 ((u32)0x08004000) //扇区1起始地址, 16 Kbytes
#define ADDR_FLASH_SECTOR_2 ((u32)0x08008000) //扇区2起始地址, 16 Kbytes
#define ADDR_FLASH_SECTOR_3 ((u32)0x0800C000) //扇区3起始地址, 16 Kbytes
#define ADDR_FLASH_SECTOR_4 ((u32)0x08010000) //扇区4起始地址, 64 Kbytes
#define ADDR_FLASH_SECTOR_5 ((u32)0x08020000) //扇区5起始地址, 128 Kbytes
#define ADDR_FLASH_SECTOR_6 ((u32)0x08040000) //扇区6起始地址, 128 Kbytes
#define ADDR_FLASH_SECTOR_7 ((u32)0x08060000) //扇区7起始地址, 128 Kbytes
#define ADDR_FLASH_SECTOR_8 ((u32)0x08080000) //扇区8起始地址, 128 Kbytes
#define ADDR_FLASH_SECTOR_9 ((u32)0x080A0000) //扇区9起始地址, 128 Kbytes
#define ADDR_FLASH_SECTOR_10 ((u32)0x080C0000) //扇区10起始地址,128 Kbytes
#define ADDR_FLASH_SECTOR_11 ((u32)0x080E0000) //扇区11起始地址,128 Kbytes
#define Flash_MicroSize 256
FLASH_Status FLASH_ProgramDouble(uint32_t Address, int64_t Data);
void STMFLASH_WriteDouble(u32 WriteAddr, double* pBuffer, u32 NumToWrite); //从指定地址开始写入指定长度的数据
int64_t STMFLASH_ReadDoubleWord(u32 faddr); //读出字
void STMFLASH_ReadDouble(u32 ReadAddr, double* pBuffer, u32 NumToRead); //从指定地址开始读出指定长度的数据
u8 STMFlash_ReadDoubleLast(u32 ReadAddr, double* pBuffer, u32 NumToRead);
#endif
源代码
在工程文件中需要添加 stm32f4xx_flash.c 源文件,这里将需要使用到的函数都封装好了,在需要的地方直接调用函数即可。Address参数一般为需要进行读写的扇区的首地址。
#include "Delay.h"
#include "USART.h"
#include "STMFlash.h"
uint16_t STMFLASH_GetFlashSector(u32 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;
return FLASH_Sector_11;
}
/**************************************************************
函数名:FLASH_ProgramDouble(uint32_t Address, int64_t Data)
参数:待写入数据的起始地址Address、数据Data
功能:向Flash中写入一个Double类型数据
返回值:FLASH_Status状态值
***************************************************************/
FLASH_Status FLASH_ProgramDouble(uint32_t Address, int64_t Data)
{
FLASH_Status status = FLASH_COMPLETE;
assert_param(IS_FLASH_ADDRESS(Address));
status = FLASH_WaitForLastOperation();
if (status == FLASH_COMPLETE)
{
FLASH->CR &= CR_PSIZE_MASK;
FLASH->CR |= FLASH_PSIZE_WORD;
FLASH->CR |= FLASH_CR_PG;
*(__IO int64_t*)Address = Data;
status = FLASH_WaitForLastOperation();
FLASH->CR &= (~FLASH_CR_PG);
}
return status;
}
/*********************************************************************
函数名:STMFLASH_WriteDouble(u32 WriteAddr,double* pBuffer,u32 NumToWrite)
参数:待写入数据的起始地址Address、Double类型数据指针pBuffer
写入数据的长度NumToWrite
功能:将Double类型数组全部写入到Flash中
返回值:无
**********************************************************************/
void STMFLASH_WriteDouble(u32 WriteAddr, double* pBuffer, u32 NumToWrite)
{
int64_t temp;
u32 addrx = 0;
u32 endaddr = 0;
FLASH_Status status = FLASH_COMPLETE;
if (WriteAddr<STM32_FLASH_BASE || WriteAddr % 8)
{
//printf("待写入的地址是非法地址");
return;//非法地址
}
FLASH_Unlock();
addrx = WriteAddr;
if (addrx < ADDR_FLASH_SECTOR_8)
{
FLASH_DataCacheCmd(DISABLE);//FLASH擦除期间,必须禁止数据缓存
while (STMFLASH_ReadDoubleWord(addrx) != 0XFFFFFFFFFFFFFFFF) //Flash_MicroSize Byte分割一个小区间,查询可用区间
{
addrx = addrx + Flash_MicroSize;
if (ADDR_FLASH_SECTOR_8 - addrx < Flash_MicroSize) //第七扇区已满,进行擦除
{
//printf("\r\nFlash第七扇区已满,准备擦除\r\n");
addrx = WriteAddr;
status = FLASH_EraseSector(STMFLASH_GetFlashSector(addrx), VoltageRange_3);//VCC=2.7~3.6V之间!!
if (status != FLASH_COMPLETE)
{
//printf("Flash擦除失败\r\n");
break; //发生错误了
}
//printf("Flash擦除成功\r\n");
}
}
WriteAddr = addrx;
endaddr = addrx + NumToWrite * 8;
if (status == FLASH_COMPLETE) //写入数据
{
while (WriteAddr < endaddr) //写数据
{
temp = (int64_t)(*pBuffer * 100000000.0);
if (FLASH_ProgramDouble(WriteAddr, temp) != FLASH_COMPLETE)
{
//printf("Flash写入失败\r\n");
break; //写入异常
}
WriteAddr += 8;
pBuffer++;
}
}
FLASH_DataCacheCmd(ENABLE); //FLASH擦除结束,开启数据缓存
FLASH_Lock();//上锁
}
}
/*******************************************************************
函数名:STMFLASH_ReadDoubleWord(u32 faddr)
参数:准备读取数据的起始地址Address
功能:从Flash中读取一个Double类型数据
返回值:int64_t类型数据
********************************************************************/
int64_t STMFLASH_ReadDoubleWord(u32 faddr)
{
return *(__IO int64_t*)faddr;
}
/*******************************************************************
函数名:STMFLASH_ReadDouble(u32 ReadAddr,double* pBuffer,u32 NumToRead)
参数:准备读取数据的起始地址Address、存放数据的Double类型数组pBuffer
读取的长度NumToRead
功能:从Flash中读取一组Double类型数据
返回值:无
********************************************************************/
void STMFLASH_ReadDouble(u32 ReadAddr, double* pBuffer, u32 NumToRead)
{
for (u32 i = 0; i<NumToRead; i++)
{
pBuffer[i] = (double)(*(__IO int64_t*)ReadAddr) / 100000000.0;//读取8个字节.
ReadAddr += 8;//偏移8个字节.
}
}
/********************************************************************
函数名:STMFlash_ReadDoubleLast(u32 ReadAddr,double* pBuffer,u32 NumToRead)
参数:准备读取数据的起始地址Address、存放数据的Double类型数组pBuffer
读取的长度NumToRead
功能:从Flash中读取最新存入的一组Double类型数据
返回值:读取是否成功
*********************************************************************/
u8 STMFlash_ReadDoubleLast(u32 ReadAddr, double* pBuffer, u32 NumToRead)
{
u32 addrx;
addrx = ReadAddr;
while ((addrx < ADDR_FLASH_SECTOR_8) && (STMFLASH_ReadDoubleWord(addrx) != 0XFFFFFFFFFFFFFFFF))
{
addrx = addrx + Flash_MicroSize;
}
addrx = addrx - Flash_MicroSize;
if (addrx < ADDR_FLASH_SECTOR_7)
{
//printf("\r\n没有预置的标准样片值!\r\n");
return 0;
}
else
{
STMFLASH_ReadDouble(addrx, pBuffer, NumToRead);
}
return 1;
}