stm32变量放在flash_STM32 使用 Flash 存储数据时的一种管理办法

使用 stm32f3xx,需要存储一些掉电不丢失的校准信息,查阅手册得知:1、stm32 写 flash 的长度是固定的 16bit;2、擦除时必须整块(2Kbytes)擦除,给出某 flash 块内的地址,执行擦除命令就可以了;3、参考手册给出了最小擦写次数为 10K。

以上三点对于实际使用时的影响,首先,写数据必须以 16bit 为单位,很多 32bit 长度的值就不能直接使用类似 A = B 的赋值语句的方法去操作了,可以统一转化为指向 16bit 无符号整形值的指针来处理。举例,有一个 32bit 长度的 float 变量 v_float,要存入地址为 (FLASH_ADDRESS)的 flash中:

#define FLASH_ADDRESS (0x0803F800)

if(FLASH->CR &= FLASH_CR_LOCK) //解锁flash

{

FLASH->KEYR = (FLASH_KEYR_FKEYR & FLASH_KEY1);

FLASH->KEYR = (FLASH_KEYR_FKEYR & FLASH_KEY2);

while(FLASH->CR & FLASH_CR_LOCK);

}

while(FLASH->SR &= FLASH_SR_BSY);

FLASH->CR |= FLASH_CR_PG; //功能选择,写flash

{

*((unsigned short *)(FLASH_ADDRESS) + 0 ) = *((unsigned short *)(&v_float) + 0 );

*((unsigned short *)(FLASH_ADDRESS) + 1 ) = *((unsigned short *)(&v_float) + 1 );

}

FLASH->CR &= ~FLASH_CR_PG;

FLASH->CR |= FLASH_CR_LOCK; //锁flash

如果需要读取写入到 flash 中的浮点数到 ram 中的变量 a_float,使用如下语句:

a_float = *((float *) FLASH_ADDRESS);

这样,写入和读取就完成了,下面是擦除,按照参考手册的流程来就可以了:

if(FLASH->CR &= FLASH_CR_LOCK) //解锁flash

{

FLASH->KEYR = (FLASH_KEYR_FKEYR & FLASH_KEY1);

FLASH->KEYR = (FLASH_KEYR_FKEYR & FLASH_KEY2);

while(FLASH->CR & FLASH_CR_LOCK);

}

while(FLASH->SR &= FLASH_SR_BSY); //Wait until flash not busy

FLASH->CR |= FLASH_CR_PER; //功能选择,擦除页

FLASH->AR = FLASH_ADDRESS; //写入flash地址

FLASH->CR |= FLASH_CR_STRT; //开始擦除

while(FLASH->SR &= FLASH_SR_BSY); //等待

if(FLASH->SR &= FLASH_SR_EOP)

{

FLASH->SR |= FLASH_SR_EOP; //重置EOP

}

FLASH->CR &= ~FLASH_CR_PER;

FLASH->CR |= FLASH_CR_LOCK; //锁flash

以上代码会擦除 FLASH_ADDRESS 所在的整个 flash 页。

实现少量数据的读写和擦除操作以后,下一步要开始组织 flash 中存储的数据,这样以后阅读和修改都更为方便,我使用类似 stm32 官方寄存器配置文件的方式,用结构体来组织。假设我要将某个人的个人信息储存在 flash 中地址为(FLASH_ADDRESS)的位置,包括:姓名、性别、身高、体重,代码如下:

typedef struct //构造结构体

{

unsigned char name[16];

unsigned char male;

float height;

float weight;

}Personal_Information_TypeDef;

#define FLASH_ADDRESS (0x0803F800) //flash地址

#define Personal_Data ((Personal_Information_TypeDef *) FLASH_ADDRESS )

以上代码在起始地址为(FLASH_ADDRESS)的 flash 中,定义了用于储存个人信息的结构体指针 Personal_Data,也就是说 Personal_Data 这个指针的值就是 FLASH_ADDRESS,只不过除了这个指针的值以外,我们还定义了这个指针所指向的数据的结构。要读出这些储存于 flash 中的值,使用读取结构体指针的方式:

Temp_variable = Personal_Data -> male;

写 flash 的时候,因为每次只能写 16bit,所以除了 short 类型以外,类似 int 和 float 这种 32bit 的数据,都要取地址强制转化为 16bit 类型后再取值最后写入,方法类似一开始的 float 类型数据写入 flash 的操作。我为了操作方便,在 ram 中建立了一个和 flash 内结构相同的结构体,每次需要写入 flash 的时候,就将 ram 中结构体的所有值全部写入 flash:

typedef struct //构造结构体

{

unsigned char name[16];

unsigned char male;

float height;

float weight;

}Personal_Information_TypeDef;

#define Length (5) //结构体总长(16bit单位)

#define FLASH_ADDRESS (0x0803F800) //flash地址

#define Personal_Data ((Personal_Information_TypeDef *) FLASH_ADDRESS )

Personal_Information_TypeDef Personal_Data_Mirror; //ram中的结构体

/* 写入过程 */

(unsigned short *)WriteAddress = (unsigned short *)Personal_Data;

(unsigned short *)ReadAddress = (unsigned short *)(&Personal_Data_Mirror);

if(FLASH->CR &= FLASH_CR_LOCK) //解锁flash

{

FLASH->KEYR = (FLASH_KEYR_FKEYR & FLASH_KEY1);

FLASH->KEYR = (FLASH_KEYR_FKEYR & FLASH_KEY2);

while(FLASH->CR & FLASH_CR_LOCK);

}

FLASH->CR |= FLASH_CR_PG; //功能选择,写入

while(Length > 0)

{

*WriteAddress = *ReadAddress; //Ram to Flash program, 16bit each

while(FLASH->SR &= FLASH_SR_BSY);

WriteAddress += 1;

ReadAddress += 1;

Length -= 1;

}

FLASH->CR &= ~FLASH_CR_PG; //Clear PG bit

FLASH->CR |= FLASH_CR_LOCK; //Lock flash

以上的写入过程之前,必须确保要写入的 flash 位置首先擦除过,或者说要保证要写入数据的地方的值为0xFFFFFFFF,否则无法写入,硬件会有标志位来报错。

这样以结构体为单位擦写 flash 的好处是,如果需要修改要储存的数据数量或类型的话,只需要修改结构体定义就可以了,而且用结构体来管理变量,程序的可读性较好。

最后就是 flash 的擦写次数问题了,最少10k次的擦写寿命,对于某些需要频繁更新的内容还是太少了,比EEPROM 通常的 100k 少了一个数量级,而且即使是改动一个变量,也必须首先擦除整个 flash 块,更加速了 flash 的消耗。但是 stm32 的 flash 容量还是不错的,动辄 256Kbytes,所以我们可以用容量换寿命,具体思路就是不要在同一个地址重复擦写,写的时候不停的变换地址,写满以后再擦除。比如,需要储存的结构体长度为 20x16bit,那么一个 2Kbytes 的 flash 页就可以储存 50 个相同的结构体,那么执行完 50 次写操作以后才需要执行一次擦除操作,flash 的使用寿命随之大为延长。

还是以储存一个结构体为例说明如何实现这种储存方式,首先定义结构体,除了你需要储存的数据以外,还要额外增加一个变量,用于识别你当前读写的 flash 地址:

typedef struct //构造结构体

{

unsigned char flag; //用于识别当前地址的标记

unsigned char name[16];

unsigned char male;

float height;

float weight;

}Personal_Information_TypeDef;

#define Length (6) //结构体总长(16bit单位)

#define FLASH_ADDRESS (0x0803F800) //flash地址

#define Personal_Data ((Personal_Information_TypeDef *) FLASH_ADDRESS )

Personal_Information_TypeDef Personal_Data_Mirror; //ram中的结构体

这部分除了结构体中增加了一个标记(flag)变量以外,其它部分相同,但是思想上,我们其实是在 flash 中定义了一个结构体数组,只不过没有使用通常的[]来遍历数组变量,取而代之的是直接使用指针来操作。每次写入时,将 flag 变量固定写为 0x00。需要读取 flash 数据时,就可以根据标记变量 flag 的值找到最新的 flash 数据地址:

#define FLASH_ADDRESS_MAX; //最大偏移量,防止跨区块操作

unsigned short FlashAddress_Offset = 0; //用于储存flash地址偏移量的临时变量

while( (Personal_Data + FlashAddress_Offset) -> flag == 0x00)

{

FlashAddress_Offset += 1;

if( (FlashAddress_Offset + FlashAddress_Offset) > MAX_OFFSET)

{

break;

}

}

找到写有数据的 flash 地址以后,后继的写操作和读操作和单个结构体的操作相似,写的地址变为:(Personal_Data + FlashAddress_Offset)

读的地址是:

(Personal_Data + FlashAddress_Offset - 1)

具体实现时要注意,结构体的长度要算好,不能出现两个结构体交叉写入;擦除 flash 需要时间,此间最好不进行需要读取 flash 的操作。


版权声明:本文为weixin_39915204原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。