[STM32F1] STM32F103ZET6 Flash存储用户数据方法

[复制链接]
101|1
晓伍 发表于 2025-10-12 11:43 | 显示全部楼层 |阅读模式
在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

jf101 发表于 2025-10-12 22:09 | 显示全部楼层
STM32F103ZET6中使用内部Flash存储用户数据是个实用且经济的方法
您需要登录后才可以回帖 登录 | 注册

本版积分规则

108

主题

4389

帖子

1

粉丝
快速回复 在线客服 返回列表 返回顶部