STM32使用Flash模拟EEPROM来使用

一:写入单个数据

STM32F3系列单片机代码如下

#include "main.h"

#define EEPROM_PAGE_ADDR   ((uint32_t)0x0801f800U)  // 仿 EEPROM 起始地址
#define EEPROM_PAGE_SIZE   ((uint32_t)0x800)        // STM32F373RBTx系列芯片一页为2 KB

// 解锁 Flash,擦除整页,再写入半字
HAL_StatusTypeDef eeprom_write_halfword(uint32_t offset, uint16_t data)
{
    HAL_StatusTypeDef status;
    FLASH_EraseInitTypeDef EraseInit;

    // 1. 参数检查
    if (offset >= EEPROM_PAGE_SIZE || ((offset & 1) != 0))
        return HAL_ERROR;  // offset 必须在页大小范围内,且半字对齐

    // 2. 解锁 Flash
    HAL_FLASH_Unlock();

    // 3. 擦除页
    EraseInit.TypeErase   = FLASH_TYPEERASE_PAGES;
    EraseInit.PageAddress = EEPROM_PAGE_ADDR;
    EraseInit.NbPages     = 1;
    uint32_t PageError;
    status = HAL_FLASHEx_Erase(&EraseInit, &PageError);
    if (status != HAL_OK)
        goto DONE;

    // 4. 编程半字
    status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD,
                               EEPROM_PAGE_ADDR + offset,
                               (uint32_t)data);

DONE:
    // 5. 上锁 Flash
    HAL_FLASH_Lock();
    return status;
}

// 读函数
uint16_t eeprom_read_halfword(uint32_t offset)
{
    return *(__IO uint16_t*)(EEPROM_PAGE_ADDR + offset);
}
View Code

修改Flash之前的空间大小

 

修改Flash之后的空间大小(清理项目再编译)

 

验证结果

 

二:写入多个数据

代码如下

#include "main.h"

// 仿 EEPROM 起始地址(Flash 页首地址)
#define EEPROM_PAGE_ADDR   ((uint32_t)0x0801F800U)
// Flash 一页大小,STM32F373RBTx 系列为 2KB
#define EEPROM_PAGE_SIZE   ((uint32_t)0x800U)

// 参数结构体,包含电机限位和铂电阻温度标定系数
typedef struct {
    uint16_t motor1_limit;   // 电机 1 的上限编码器位置(单位:脉冲)
    uint16_t motor2_limit;   // 电机 2 的上限编码器位置(单位:脉冲)
    float    k;              // 铂电阻温度标定斜率 k
    float    b;              // 铂电阻温度标定截距 b
} Param_t;

// 在 EEPROM_PAGE(2KB)页内按字节偏移排列
#define OFF_MOTOR1_LIMIT   0U   // motor1_limit 偏移 0 字节
#define OFF_MOTOR2_LIMIT   2U   // motor2_limit 偏移 2 字节
#define OFF_K_FLOAT        4U   // k(float)偏移 4 字节,占 4 字节
#define OFF_B_FLOAT        8U   // b(float)偏移 8 字节,占 4 字节

/**
 * @brief  将参数保存到 Flash(仿 EEPROM)
 * @param  p: 指向待保存参数的指针
 * @retval HAL 状态,HAL_OK 表示成功,其他值表示错误
 */
HAL_StatusTypeDef Flash_SaveParams(const Param_t *p)
{
    HAL_StatusTypeDef status;
    FLASH_EraseInitTypeDef EraseInit = {0};
    uint32_t pageError;

    // 1) 参数检查,确保指针有效
    if (p == NULL) {
        return HAL_ERROR;
    }

    // 2) 解锁 Flash 控制寄存器,允许运行擦写操作
    HAL_FLASH_Unlock();

    // 3) 擦除整页,必须先擦除后写入
    EraseInit.TypeErase   = FLASH_TYPEERASE_PAGES;
    EraseInit.PageAddress = EEPROM_PAGE_ADDR;
    EraseInit.NbPages     = 1;  // 擦除 1 页
    status = HAL_FLASHEx_Erase(&EraseInit, &pageError);
    if (status != HAL_OK) {
        // 如果擦除失败,上锁并返回错误
        HAL_FLASH_Lock();
        return status;
    }

    // 4) 按半字(16 位)写入 motor1_limit
    status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD,
                               EEPROM_PAGE_ADDR + OFF_MOTOR1_LIMIT,
                               p->motor1_limit);
    if (status != HAL_OK) goto DONE;

    // 5) 按半字写入 motor2_limit
    status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD,
                               EEPROM_PAGE_ADDR + OFF_MOTOR2_LIMIT,
                               p->motor2_limit);
    if (status != HAL_OK) goto DONE;

    // 6) 写入浮点数 k,将 32 位分为两个 16 位依次写入
    {
        // 将 float 指针转换为 uint16_t 数组,以便拆半字写入
        uint16_t *pf = (uint16_t *)&p->k;
        // 写入低半字
        status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD,
                                   EEPROM_PAGE_ADDR + OFF_K_FLOAT,
                                   pf[0]);
        if (status != HAL_OK) goto DONE;
        // 写入高半字
        status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD,
                                   EEPROM_PAGE_ADDR + OFF_K_FLOAT + 2,
                                   pf[1]);
        if (status != HAL_OK) goto DONE;
    }

    // 7) 写入浮点数 b,同理拆半字写入
    {
        uint16_t *pf = (uint16_t *)&p->b;
        status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD,
                                   EEPROM_PAGE_ADDR + OFF_B_FLOAT,
                                   pf[0]);
        if (status != HAL_OK) goto DONE;
        status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD,
                                   EEPROM_PAGE_ADDR + OFF_B_FLOAT + 2,
                                   pf[1]);
        if (status != HAL_OK) goto DONE;
    }

DONE:
    // 8) 写入完成后,上锁 Flash 并返回状态
    HAL_FLASH_Lock();
    return status;
}

/**
 * @brief  从 Flash(仿 EEPROM)加载参数到 RAM
 * @param  p: 指向接收参数的结构体指针
 * @note   直接通过指针读取 Flash 数据,不涉及写操作
 */
void Flash_LoadParams(Param_t *p)
{
    if (p == NULL) {
        return;
    }

    // 1) 直接读取 motor1_limit 和 motor2_limit
    p->motor1_limit = *(__IO uint16_t *)(EEPROM_PAGE_ADDR + OFF_MOTOR1_LIMIT);
    p->motor2_limit = *(__IO uint16_t *)(EEPROM_PAGE_ADDR + OFF_MOTOR2_LIMIT);

    // 2) 读取浮点数 k:先读取两个半字,再拼回 float
    {
        uint16_t low  = *(__IO uint16_t *)(EEPROM_PAGE_ADDR + OFF_K_FLOAT);
        uint16_t high = *(__IO uint16_t *)(EEPROM_PAGE_ADDR + OFF_K_FLOAT + 2);
        uint16_t tmp[2] = { low, high };
        p->k = *(float *)tmp;
    }

    // 3) 读取浮点数 b:同样方法拼回
    {
        uint16_t low  = *(__IO uint16_t *)(EEPROM_PAGE_ADDR + OFF_B_FLOAT);
        uint16_t high = *(__IO uint16_t *)(EEPROM_PAGE_ADDR + OFF_B_FLOAT + 2);
        uint16_t tmp[2] = { low, high };
        p->b = *(float *)tmp;
    }
}
View Code

验证结果

小贴士

  1. 整页擦写:FLASH 是按页擦除的,擦除会把整页都置为 0xFFFF,所以一般在写一组参数时,先擦除再全量写入。

  2. 半字对齐:HAL_FLASH_Program 在 F373 上只支持半字(16 位)编程,写 uint16_t 没问题,写 float 需要拆成两个 uint16_t

  3. 多次更新:如果参数更新频繁,建议做双页或循环写入管理(Wear leveling),避免频繁擦写同一页带来的 FLASH 寿命缩短。

posted @ 2025-06-12 16:53  阿坦  阅读(145)  评论(0)    收藏  举报