STM32C5 EDATA 使用指南:配置、问题排查与读写示例
本文记录 STM32C5 系列 MCU 中 EDATA(Embedded Data Flash)的配置和使用过程,包括 STM32CubeMX / STM32CubeProgrammer 配置、HAL API 使用、实际测试以及使用过程中遇到的 HardFault 问题。
目标: 使用 STM32C5 内部 EDATA 保存掉电后仍需要保留的用户数据,例如密码、设备参数、校准参数、计数值等。
1. 什么是 EDATA?
STM32C5 部分型号提供了独立的数据 Flash 区域(EDATA),可以用于保存需要掉电保持的数据。
与普通 RAM 相比:
RAM
├── 掉电后数据丢失
└── 适合运行时变量
EDATA
├── 掉电后数据保持
└── 适合保存配置参数
典型应用包括:
- 用户密码
- 设备配置参数
- 校准参数
- 产品序列号
- 使用次数
- 用户设置
- 需要掉电保存的状态信息
EDATA 的一个重要特点是:
它不是普通 RAM,而是 Flash,因此写入之前需要考虑 Flash 的擦除、编程粒度和寿命等问题。
2. 开发环境
本文测试环境:
| 项目 |
配置 |
| MCU |
STM32C542 |
| 开发板 |
NUCLEO-C542RC |
| IDE |
VS Code |
| 工程 |
STM32CubeMX2 / CMake |
| Programmer |
STM32CubeProgrammer |
| HAL |
STM32C5 HAL |
| 数据存储 |
EDATA |
3. EDATA 配置
3.1 在 STM32CubeMX2 中配置
首先创建 STM32C542 工程。
根据芯片型号进入 Flash / EDATA 相关配置。
需要确认工程已经启用了 EDATA 相关 HAL 支持。
在 HAL 配置中,需要能够看到类似:
#define USE_HAL_FLASH_OB_EDATA 1
如果该宏没有打开,部分 EDATA Option Bytes 相关 API 可能不会生成对应的声明。
4. 使用 STM32CubeProgrammer 开启 EDATA
实际测试中,一个容易忽略的地方是:
EDATA_EN 需要在 Option Bytes 中正确配置。
使用 STM32CubeProgrammer 连接 STM32C542。
进入:
Option Bytes
找到 EDATA 相关配置。
确认:
EDATA_EN = 1
然后执行 Option Bytes 编程。
重新连接 MCU 后再次确认配置已经生效。
5. 确认 EDATA 地址和容量
不同 STM32C5 型号的 EDATA 地址和容量可能不同,因此不要直接复制其他型号的地址。
在工程中可以查看对应设备 HAL / 头文件定义。
例如本次 STM32C542 测试环境中可以看到类似:
FLASH_EDATA_BASE
FLASH_EDATA_PAGE_SIZE
FLASH_EDATA_BANK_SIZE
测试环境中的参数:
EDATA BASE = 0x09000000
EDATA PAGE SIZE = 1536 bytes
EDATA BANK SIZE = 24576 bytes
因此实际使用时建议通过芯片对应的 HAL 定义获取地址,而不是自己硬编码。
例如:
uint32_t address = FLASH_EDATA_BASE;
6. EDATA 与普通 Flash 的区别
使用 EDATA 时,首先要区分:
Main Flash
↓
程序代码 / 常规 Flash 数据
EDATA
↓
用户持久化数据
不要简单地认为:
FLASH_BASE + offset
就可以得到 EDATA 地址。
EDATA 有自己的地址映射和控制方式。
因此在调试时尤其需要注意:
- EDATA 地址范围
- Flash Bank
- Bank Swap
- Option Bytes
- EDATA Enable 状态
7. 第一个测试:读取 EDATA
建议在真正进行复杂数据存储之前,先做一个最小测试。
例如:
#define TEST_EDATA_ADDR FLASH_EDATA_BASE
读取:
uint8_t value = *(volatile uint8_t *)TEST_EDATA_ADDR;
然后通过串口输出:
printf("EDATA value = 0x%02X\r\n", value);
如果 Flash 尚未写入,读取到的值通常会处于擦除状态。
8. EDATA 擦除
Flash 与 RAM 最大的区别之一就是:
Flash 写入通常不能像 RAM 一样直接覆盖任意数据。
因此在需要重新写入之前,需要根据芯片规定执行擦除。
STM32C5 HAL 提供了 EDATA 擦除相关接口。
例如:
HAL_FLASH_EDATA_EraseByAddr(
&hflash,
flash_addr,
size_byte,
timeout_ms
);
实际工程中需要使用正确的 Flash HAL handle。
例如:
hal_flash_handle_t *pFLASH;
或者通过工程提供的 Flash handle 获取函数:
pFLASH = mx_flash_gethandle();
具体 handle 获取方式取决于 CubeMX2 生成的工程结构。
9. 遇到的第一个问题:EDATA API 没有声明
最开始调用:
HAL_FLASH_EDATA_EraseByAddr(...)
时,编译器提示:
implicit declaration of function
'HAL_FLASH_EDATA_EraseByAddr'
经过检查发现,相关 API 的声明受到 HAL 配置宏控制。
需要确认:
USE_HAL_FLASH_OB_EDATA
已经正确开启。
例如:
#define USE_HAL_FLASH_OB_EDATA 1
重新编译后,相关 API 才可以正常使用。
10. 遇到的第二个问题:调用 EDATA 擦除后 HardFault
在测试过程中遇到一个比较典型的问题:
调用 EDATA 擦除函数后进入:
HardFault_Handler
调试时发现程序最终执行到类似:
if (flash_addr < (FLASH_BASE + FLASH_SIZE))
然后发生 HardFault。
这个问题比较容易让人误以为:
EDATA 地址错误。
但实际上不能仅仅根据这一行代码判断。
11. 为什么会在 Flash_GetBank() 附近出现 HardFault?
EDATA 的地址空间与 Main Flash 并不是简单的连续关系。
因此,当 HAL 内部根据 Flash 地址判断 Bank 时:
flash_addr
如果没有按照对应 STM32C5 EDATA 的地址映射进行处理,就可能进入不适合当前地址空间的判断路径。
调试过程中发现:
FLASH_GetBank()
↓
判断 flash_addr
↓
FLASH_BASE + FLASH_SIZE
↓
HardFault
因此排查 EDATA 问题时,不应该只检查:
flash_addr
还应该检查:
EDATA Enable
Option Bytes
Flash instance
Flash handle
Bank configuration
HAL version
EDATA address mapping
12. 另一个重要问题:Flash Handle
调试过程中还观察到了 Flash handle:
pFLASH = 0x200005D8
instance = 0x40022000
HAL_FLASH = 0x40022000
说明 handle 本身指向了 Flash 外设实例。
因此遇到 EDATA 操作异常时,建议首先检查:
pFLASH != NULL
以及:
pFLASH->instance
是否指向正确的 Flash 外设。
13. EDATA 操作前的 Unlock
Flash 操作通常需要先解除 Flash 锁定。
在测试过程中加入:
HAL_FLASH_ITF_Unlock(pFLASH->instance);
然后再进行 EDATA 擦除。
示意:
hal_flash_handle_t *pFLASH;
pFLASH = mx_flash_gethandle();
if (pFLASH == NULL)
{
printf("FLASH handle error\r\n");
return;
}
HAL_FLASH_ITF_Unlock(pFLASH->instance);
status = HAL_FLASH_EDATA_EraseByAddr(
pFLASH,
FLASH_EDATA_BASE,
FLASH_EDATA_PAGE_SIZE,
1000
);
注意:具体 Unlock / Lock API 以及调用顺序应以当前 STM32C5 HAL 版本和对应设备参考代码为准。
14. 推荐的 EDATA 使用流程
实际项目中可以按照下面的流程设计:
Power ON
│
▼
初始化 Flash HAL
│
▼
检查 EDATA 状态
│
▼
读取 EDATA
│
┌─────┴─────┐
│ │
数据有效 数据无效
│ │
▼ ▼
正常使用 写入默认值
│
▼
EDATA 擦除
│
▼
EDATA 编程
│
▼
Lock Flash
15. 保存一个 uint8_t 数据
假设需要保存一个密码状态:
uint8_t password = 0x55;
可以将其保存到 EDATA 中。
定义地址:
#define USER_DATA_ADDR FLASH_EDATA_BASE
然后:
uint8_t password = 0x55;
写入之前,需要按照芯片规定的 Flash 编程粒度进行处理。
不要简单假设所有 STM32C5 EDATA 都可以直接调用一个 byte-write API。
应该首先确认当前芯片 EDATA 的:
- Programming granularity
- Alignment requirement
- Erase unit
- Write sequence
然后按照 HAL 提供的接口进行编程。
16. 更推荐:使用结构体保存参数
实际产品中不建议只保存一个 byte。
可以定义一个配置结构:
typedef struct
{
uint32_t magic;
uint8_t password;
uint8_t reserved[3];
uint32_t device_id;
uint32_t calibration;
} UserConfig_t;
例如:
#define USER_CONFIG_MAGIC 0x43464731U
启动时:
UserConfig_t config;
memcpy(
&config,
(const void *)FLASH_EDATA_BASE,
sizeof(UserConfig_t)
);
检查:
if (config.magic == USER_CONFIG_MAGIC)
{
// 数据有效
}
else
{
// 第一次使用
}
17. 为什么需要 Magic?
建议在 EDATA 数据结构中加入 Magic。
例如:
typedef struct
{
uint32_t magic;
uint8_t password;
uint8_t reserved[3];
} UserConfig_t;
判断:
if (config.magic == USER_CONFIG_MAGIC)
{
printf("Valid EDATA data\r\n");
}
else
{
printf("EDATA is empty or invalid\r\n");
}
这样可以区分:
第一次启动
↓
EDATA 未初始化
正常启动
↓
EDATA 已经保存有效数据
18. EDATA 数据写入的推荐设计
实际产品建议不要每次修改一个变量就立即擦除整个区域。
例如:
password = new_password;
不建议简单设计成:
修改密码
↓
擦除 EDATA
↓
写入密码
如果频繁修改,就会增加 Flash 擦写次数。
更好的方式是设计数据记录:
EDATA
┌──────────────────────┐
│ Record 0 │
├──────────────────────┤
│ Record 1 │
├──────────────────────┤
│ Record 2 │
├──────────────────────┤
│ ... │
└──────────────────────┘
每次更新:
写入新的 Record
↓
启动时寻找最新有效 Record
这样可以减少单个地址的擦写压力。
19. 一个简单的数据记录结构
例如:
typedef struct
{
uint32_t magic;
uint32_t version;
uint8_t password;
uint8_t reserved[3];
uint32_t crc;
} EDataRecord_t;
其中:
magic
↓
判断是否有效
version
↓
判断数据版本
password
↓
实际用户数据
crc
↓
检查数据完整性
20. CRC 校验
如果 EDATA 保存的是比较重要的参数,可以增加 CRC。
例如:
typedef struct
{
uint32_t magic;
uint32_t version;
uint8_t password;
uint8_t reserved[3];
uint32_t crc;
} EDataRecord_t;
保存:
生成数据
↓
计算 CRC
↓
写入 EDATA
读取:
读取 EDATA
↓
检查 Magic
↓
检查 Version
↓
重新计算 CRC
↓
比较 CRC
这样可以避免数据异常时直接使用错误配置。
21. EDATA 与 EEPROM 的区别
EDATA 可以解决很多传统 EEPROM 的使用场景,但它本质上仍然属于 Flash 存储。
因此:
| 特性 |
RAM |
EEPROM |
EDATA |
| 掉电保持 |
❌ |
✅ |
✅ |
| 访问速度 |
高 |
较低 |
较高 |
| 擦除 |
不需要 |
通常由器件管理 |
需要 |
| 写入寿命 |
不适用 |
较高 |
需要考虑 |
| 数据存储 |
临时 |
参数 |
参数 |
| 适合频繁写入 |
✅ |
✅ |
需要谨慎 |
因此 EDATA 更适合:
配置参数
校准参数
密码
设备信息
少量状态数据
而不是:
每毫秒写一次的数据
日志
大容量文件
22. 调试 EDATA 时建议检查的项目
如果 EDATA 操作出现异常,可以按照下面的顺序排查。
1. 确认芯片型号
STM32C542
不要直接使用其他 STM32C5 型号的地址和容量。
2. 确认 EDATA 已启用
检查:
EDATA_EN = 1
3. 确认 EDATA 地址
检查:
FLASH_EDATA_BASE
4. 确认容量
检查:
FLASH_EDATA_PAGE_SIZE
FLASH_EDATA_BANK_SIZE
5. 确认 Flash Handle
检查:
pFLASH
pFLASH->instance
6. 确认 Flash 已解锁
HAL_FLASH_ITF_Unlock(...)
7. 确认擦除粒度
不要随意指定任意字节数。
8. 确认编程粒度和地址对齐
根据当前 STM32C5 数据手册和 HAL API 要求进行。
9. 检查 HAL 版本
不同 HAL 版本中 Flash API 的实现可能存在差异。
10. 单步进入 HAL
如果发生 HardFault,建议查看:
Call Stack
PC
LR
Fault Status Register
flash_addr
Flash instance
23. 推荐的最小测试程序
建议在真正开发产品功能之前,先完成:
EDATA TEST
测试内容:
1. 检查 EDATA Enable
2. 获取 Flash Handle
3. Unlock
4. Erase
5. Program
6. Read
7. Compare
8. Lock
测试逻辑:
Start
│
▼
Get Flash Handle
│
▼
Unlock Flash
│
▼
Erase EDATA
│
▼
Program Data
│
▼
Read Data
│
▼
Compare Data
│
┌────────┴────────┐
▼ ▼
PASS FAIL
24. 实际项目中的推荐架构
建议不要让业务代码直接操作 Flash。
例如不要在:
password.c
里面到处调用:
HAL_FLASH_EDATA_EraseByAddr()
可以单独建立:
Core
├── Inc
│ └── sys_data_flash.h
│
└── Src
└── sys_data_flash.c
然后提供简单接口:
HAL_StatusTypeDef SYS_DataFlash_Init(void);
HAL_StatusTypeDef SYS_DataFlash_Read(
uint32_t offset,
void *data,
uint32_t size
);
HAL_StatusTypeDef SYS_DataFlash_Write(
uint32_t offset,
const void *data,
uint32_t size
);
上层业务只需要:
SYS_DataFlash_Write(
PASSWORD_OFFSET,
&password,
sizeof(password)
);
这样可以把:
Flash底层操作
↓
数据存储层
↓
业务层
分离开。
25. 总结
STM32C5 的 EDATA 可以作为内部非易失性数据存储区域,用于保存:
- 密码
- 配置参数
- 校准参数
- 产品信息
- 少量需要掉电保持的数据
使用时最重要的几个步骤是:
① 确认 MCU 型号
↓
② 开启 EDATA
↓
③ 配置 Option Bytes
↓
④ 确认 EDATA_EN = 1
↓
⑤ 获取正确 Flash Handle
↓
⑥ Unlock
↓
⑦ 按要求擦除
↓
⑧ 按规定编程
↓
⑨ 读取验证
↓
⑩ Lock
同时需要特别注意:
EDATA 的地址映射、擦除粒度、编程粒度和 Flash Bank 处理不能直接套用其他 STM32 系列的代码。
如果出现 HardFault,应重点检查:
EDATA_EN
Flash address
Flash handle
Flash instance
Bank configuration
HAL version
Erase/program alignment
26. 后续工作
下一步可以进一步实现一个完整的 EDATA 数据管理模块,包括:
┌──────────────────────────┐
│ Application │
├──────────────────────────┤
│ SYS_DataFlash API │
├──────────────────────────┤
│ EDATA Record Management │
├──────────────────────────┤
│ STM32 HAL │
├──────────────────────────┤
│ EDATA Flash │
└──────────────────────────┘
并加入:
- Magic
- Version
- CRC
- 数据版本升级
- 多记录管理
- 掉电保护
- Flash 擦写寿命管理
这样就可以从简单的 uint8_t 数据保存,扩展成一个适合实际产品使用的内部参数存储系统。
参考资料
开发过程中建议以当前 MCU 对应版本的官方资料为准:
- STM32C542 Datasheet
- STM32C5 Reference Manual
- STM32C5 HAL Driver Documentation
- STM32CubeMX / STM32CubeMX2 generated project
- STM32CubeProgrammer Option Bytes configuration
- ST 官方 EDATA 示例代码