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); }
修改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; } }
验证结果
小贴士
-
整页擦写:FLASH 是按页擦除的,擦除会把整页都置为
0xFFFF
,所以一般在写一组参数时,先擦除再全量写入。 -
半字对齐:HAL_FLASH_Program 在 F373 上只支持半字(16 位)编程,写
uint16_t
没问题,写float
需要拆成两个uint16_t
。 -
多次更新:如果参数更新频繁,建议做双页或循环写入管理(Wear leveling),避免频繁擦写同一页带来的 FLASH 寿命缩短。