在STM32F103ZET6中使用内部Flash存储用户数据是个实用且经济的方法,它能让你在下次上电时恢复这些数据。我会给你提供基于库函数的方案和代码示例。
一、STM32F103ZET6 内部 Flash 基础
STM32F103ZET6 内置了 512KB 的 Flash 存储器,主要用于存储程序代码。这片 Flash 被组织为 256 页,每页大小为 2KB。
它的地址范围是 0x0800 0000 到 0x0807 FFFF。当你计划把数据存进 Flash 时,务必避开存储了程序代码的区域,通常可以选择靠后或最后的页(例如第 255 页)。
关键操作特性:
写入单位:半字(16位)。
擦除单位:页(2KB)。擦除后页内所有数据变为 0xFFFF。
寿命:Flash 有写入次数限制,通常约 10,000 次擦写循环。因此,应避免频繁写入。
二、操作步骤与代码实现(库函数)
以下是使用STM32标准外设库进行Flash读写的步骤和代码。
1.宏定义与数据校验
首先定义关键参数和数据结构。
#include "stm32f10x.h"
#include "stm32f10x_flash.h"
/* 定义Flash操作参数 */
// 根据STM32F103ZET6的Flash结构,选择最后一页(第255页)的起始地址存储用户数据:cite[4]
#define USER_DATA_FLASH_PAGE_ADDR ((uint32_t)0x0807F800) // 第255页起始地址
#define FLASH_PAGE_SIZE ((uint16_t)0x800) // 2KB一页
#define USER_DATA_SIZE sizeof(UserDataStruct) // 用户数据结构体大小
/* 用户数据结构体定义 */
typedef struct {
uint32_t data1;
float data2;
uint8_t data3[20];
uint32_t checksum; // 用于存储校验和,保证数据完整性
} UserDataStruct;
/* 计算校验和 (简单的求和校验示例) */
uint32_t Calculate_Checksum(UserDataStruct* data) {
uint32_t sum = 0;
uint8_t *p = (uint8_t*)data;
// 计算除checksum本身之外所有字节的和
for(uint16_t i = 0; i < (USER_DATA_SIZE - sizeof(uint32_t)); i++) {
sum += p;
}
return sum;
}
2.Flash解锁与锁定
在对Flash进行写或擦除操作前,必须先解锁,操作完成后应及时上锁以保护Flash。
/**
* @brief 解锁Flash
*/
void FLASH_Unlock(void) {
FLASH_Unlock(); // 调用库函数解锁
}
/**
* @brief 锁定Flash
*/
void FLASH_Lock(void) {
FLASH_Lock(); // 调用库函数锁定
}
3.擦除Flash页
写入前必须先擦除目标页。
/**
* @brief 擦除指定Flash页
* @param Page_Address: 要擦除的页的起始地址
* @retval FLASH Status: 擦除操作的状态
*/
FLASH_Status FLASH_ErasePage(uint32_t Page_Address) {
FLASH_Status status = FLASH_COMPLETE;
status = FLASH_ErasePage(Page_Address); // 调用库函数页擦除:cite[1]
return status;
}
4.写入数据到Flash
使用半字(16位)编程函数写入数据。
/**
* @brief 将用户数据写入Flash
* @param pData: 指向用户数据结构的指针
* @retval FLASH Status: 写入操作的状态
*/
FLASH_Status USER_DATA_Write(UserDataStruct* pData) {
FLASH_Status status = FLASH_COMPLETE;
uint32_t address = USER_DATA_FLASH_PAGE_ADDR;
uint16_t *p = (uint16_t*)pData; // 将数据指针转换为半字指针
uint16_t data_length = (USER_DATA_SIZE + 1) / 2; // 计算需要写入的半字数(向上取整)
// 1. 计算校验和并存入结构体
pData->checksum = Calculate_Checksum(pData);
// 2. 解锁Flash
FLASH_Unlock();
// 3. 擦除目标页
status = FLASH_ErasePage(USER_DATA_FLASH_PAGE_ADDR);
if(status != FLASH_COMPLETE) {
FLASH_Lock();
return status;
}
// 4. 逐半字写入数据
for(uint16_t i = 0; i < data_length; i++) {
status = FLASH_ProgramHalfWord(address, p); // 调用库函数写入半字:cite[1]:cite[3]
if(status != FLASH_COMPLETE) {
break;
}
address += 2; // 地址增加2个字节
}
// 5. 锁定Flash
FLASH_Lock();
return status;
}
5.从Flash读取数据
从Flash读取数据无需解锁。
/**
* @brief 从Flash读取用户数据并验证
* @param pData: 指向要存储读取数据的用户数据结构体的指针
* @retval uint8_t: 0-成功, 1-数据无效或损坏
*/
uint8_t USER_DATA_Read(UserDataStruct* pData) {
uint32_t flash_address = USER_DATA_FLASH_PAGE_ADDR;
uint16_t *p = (uint16_t*)pData;
uint16_t data_length = (USER_DATA_SIZE + 1) / 2;
uint32_t stored_checksum;
// 1. 从Flash复制数据到结构体
for(uint16_t i = 0; i < data_length; i++) {
p = *(__IO uint16_t*)flash_address; // 读取半字:cite[3]
flash_address += 2;
}
// 2. 获取存储的校验和
stored_checksum = pData->checksum;
// 3. 重新计算读取数据的校验和(不包括存储的校验和字段)
// 暂时将存储的校验和清零,以便计算原数据的校验和
pData->checksum = 0;
uint32_t calculated_checksum = Calculate_Checksum(pData);
// 4. 恢复读取的校验和
pData->checksum = stored_checksum;
// 5. 验证校验和
if(calculated_checksum != stored_checksum) {
return 1; // 校验和错误,数据可能已损坏
}
return 0; // 读取和验证成功
}
6.主函数中的使用示例
UserDataStruct myData;
uint8_t read_status;
int main(void) {
// ... 系统初始化代码 ...
// 尝试从Flash读取数据
read_status = USER_DATA_Read(&myData);
if(read_status == 0) {
// 数据读取成功且有效,可以使用myData中的数据
// 例如: if(myData.data1 == 0x12345678) { ... }
} else {
// 数据无效或首次使用,初始化默认值
myData.data1 = 0;
myData.data2 = 0.0f;
memset(myData.data3, 0, sizeof(myData.data3));
// 注意:这里先不写Flash,通常在数据改变后再写入
}
while(1) {
// ... 主循环 ...
// 当需要保存数据时(例如数据有变动)
if(need_to_save_data) {
// 更新数据内容...
// myData.data1 = ...;
// 然后写入Flash
if(USER_DATA_Write(&myData) == FLASH_COMPLETE) {
// 写入成功处理
need_to_save_data = 0;
} else {
// 写入错误处理
}
}
}
}
三、重要注意事项
擦除与写入:Flash 必须先擦除再写入。擦除会将整页(2KB)置为 0xFFFF,而写入只能将 bit 从 1 改为 0。
电源稳定性:在进行Flash写或擦除操作时,必须确保电源稳定。电压波动或断电可能导致写操作失败甚至Flash数据损坏。
中断:Flash编程期间会产生总线阻塞,但某些型号在进行Flash操作时可能需要暂时禁用中断(尤其是全局中断),尤其是在时间敏感的应用中。请参考参考手册的具体说明。
磨损均衡:由于Flash写入次数有限,应避免频繁写入。如果应用需要频繁保存数据,可以考虑以下策略:
仅在数据确实发生变化时才写入。
使用多个页轮流写入(简单的磨损均衡算法)。
对于极其频繁的数据保存需求,建议使用外部的EEPROM或FRAM芯片。
选项字节:确保要写入的Flash区域没有被配置为写保护(通过选项字节设置)。
总结:
在STM32F103ZET6中使用内部Flash存储用户数据,关键在于选择正确的存储位置(如最后一页)、遵循先擦后写的流程、并进行数据校验(如校验和)以确保完整性。
提供的代码使用了STM32标准外设库的函数(如 FLASH_ErasePage、FLASH_ProgramHalfWord13),并包含了基本的校验和验证。在实际应用中,你可能需要根据具体数据结构调整 UserDataStruct 和 Calculate_Checksum 函数。
————————————————
版权声明:本文为CSDN博主「qq_40539316」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_40539316/article/details/151579804
|
|