FLASH — 页擦除与写入(HAL 库)
固件库: PY32F0xx_Firmware V1.5.0
芯片: PY32F030
源文件: PUYA/PY32F0xx_Firmware_V1.5.0/Projects/PY32F030-STK/Example/FLASH/FLASH_PageEraseAndWrite/Src/main.c
功能简介
MCU 的内部 Flash 不仅能存放程序,也可以像 EEPROM 一样保存掉电不丢失的用户数据(配置参数、校准值、计数器等)。
PY32F0xx 的 Flash 写入有铁律:只能把 1 写成 0,不能把 0 写成 1;要改数据必须先整页擦除(擦除后全为 0xFF)。本示例完整演示标准流程:
- 等待用户按键(避免每次上电都擦写,延长 Flash 寿命);
- 解锁 Flash;
- 页擦除 目标地址;
- 校验空白(应为 0xFFFFFFFF);
- 按页编程 写入 64 个 32 位数据;
- 加锁 Flash;
- 回读校验,一致则点亮绿灯。
目标地址 FLASH_USER_START_ADDR = 0x08004000(避开存放用户代码的前几页)。DATA[64] 为待写入的 256 字节测试数据。
硬件准备
- PY32F030 开发板
- 板载 LED 与用户键(用于触发与指示)
- 注意:此操作会改写 Flash 内容,务必确保
0x08004000 之后没有你的程序/数据,否则可能被覆盖!
完整代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
|
#include "main.h"
#include "py32f030xx_Start_Kit.h"
#define FLASH_USER_START_ADDR 0x08004000 /* 用户数据起始地址(避开代码区) */
const uint32_t DATA[64] = { /* 256 字节测试数据,略 */ };
int main(void)
{
HAL_Init();
BSP_PB_Init(BUTTON_KEY, BUTTON_MODE_GPIO);
BSP_LED_Init(LED_GREEN);
APP_SystemClockConfig(); /* 系统时钟:HSI 24MHz */
/* 等用户按键,防止每次复位都擦写 Flash */
while (BSP_PB_GetState(BUTTON_USER)) {}
HAL_FLASH_Unlock(); /* 1. 解锁 Flash(写/擦前必须) */
APP_FlashErase(); /* 2. 页擦除 */
APP_FlashBlank(); /* 3. 校验是否为空白(全 0xFF) */
APP_FlashProgram(); /* 4. 按页编程写入 */
HAL_FLASH_Lock(); /* 5. 加锁 Flash */
APP_FlashVerify(); /* 6. 回读校验 */
BSP_LED_On(LED_GREEN); /* 成功:绿灯常亮 */
while (1) {}
}
/* 页擦除 */
static void APP_FlashErase(void)
{
uint32_t PAGEError = 0;
FLASH_EraseInitTypeDef EraseInitStruct = {0};
EraseInitStruct.TypeErase = FLASH_TYPEERASE_PAGEERASE; /* 页擦除 */
EraseInitStruct.PageAddress = FLASH_USER_START_ADDR; /* 起始页地址 */
EraseInitStruct.NbPages = sizeof(DATA) / FLASH_PAGE_SIZE;/* 需要擦除的页数 */
if (HAL_FLASHEx_Erase(&EraseInitStruct, &PAGEError) != HAL_OK)
APP_ErrorHandler(); /* PAGEError 为出错页地址,成功则 0xFFFFFFFF */
}
/* 按页编程:每次写一整页 FLASH_PAGE_SIZE 字节 */
static void APP_FlashProgram(void)
{
uint32_t flash_program_start = FLASH_USER_START_ADDR;
uint32_t flash_program_end = FLASH_USER_START_ADDR + sizeof(DATA);
uint32_t *src = (uint32_t *)DATA;
while (flash_program_start < flash_program_end)
{
/* 注意:PY32F0xx 以“页”为编程单位(FLASH_TYPEPROGRAM_PAGE) */
if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_PAGE, flash_program_start, src) == HAL_OK)
{
flash_program_start += FLASH_PAGE_SIZE; /* 源/目的指针同步推进一页 */
src += FLASH_PAGE_SIZE / 4;
}
}
}
/* 校验擦除后是否为空白 */
static void APP_FlashBlank(void)
{
uint32_t addr = 0;
while (addr < sizeof(DATA))
{
if (0xFFFFFFFF != HW32_REG(FLASH_USER_START_ADDR + addr))
APP_ErrorHandler(); /* 非空说明擦除失败 */
addr += 4;
}
}
/* 回读校验写入是否正确 */
static void APP_FlashVerify(void)
{
uint32_t addr = 0;
while (addr < sizeof(DATA))
{
if (DATA[addr / 4] != HW32_REG(FLASH_USER_START_ADDR + addr))
APP_ErrorHandler();
addr += 4;
}
}
|
代码分段讲解
1. 解锁与加锁
Flash 是“危险”资源,HAL 默认禁止写。任何擦/写前必须 HAL_FLASH_Unlock(),操作完 HAL_FLASH_Lock() 重新保护。忘记解锁会直接返回错误。
2. 擦除单位:页(Page)
PY32F0xx Flash 以页为单位擦除(每页大小由 FLASH_PAGE_SIZE 定义,不同容量芯片不同,常见 1KB/2KB)。不能只擦一个字节——擦除会把整页恢复成 0xFF。
3. 编程单位
本系列 HAL_FLASH_Program 使用 FLASH_TYPEPROGRAM_PAGE(按页写入),一次把一整页数据烧入。写入前该页必须已擦除。
4. 为什么要等按键
Flash 有擦写寿命(约 1 万次)。若程序一上电就擦写,反复复位会迅速耗尽寿命。正式用法应在“确实需要保存新数据”时才写,并尽量** wear leveling**(磨损均衡)。
5. 地址安全
0x08004000 要位于用户代码占用区之后。查看工程链接脚本(.ld / Option Bytes)确认你的程序大小,避免覆盖。
实验现象
- 按用户键后开始擦写;
- 成功回读一致,绿灯常亮;任一环节失败则卡在
APP_ErrorHandler(可加 LED 提示定位)。
常见问题
-
HAL_FLASH_Program 返回错误?
- 目标页未先擦除(仍有 0)→ 必须先
HAL_FLASHEx_Erase。
- 未调用
HAL_FLASH_Unlock()。
-
数据回读不对?
- 编程地址与读取地址不一致;或
src 指针推进量算错(应 FLASH_PAGE_SIZE/4 个 32 位)。
- 写入单位(
FLASH_TYPEPROGRAM_PAGE)与页大小不匹配。
-
程序跑飞/变砖?
- 写入地址覆盖了中断向量表或代码区。务必选在代码末端之后的空闲页;必要时先备份。
-
想存少量参数?
- 不必每次整页擦。可把一页划成“多槽”,每次找空白槽追加写,写满再整页擦(磨损均衡)。
小结
- Flash 操作流程固定:解锁 → 页擦除 →(校验空白)→ 编程 → 加锁 → 回读校验。
- 核心规则:先擦后写、按页操作、擦除即整页 0xFF。
- 操作有寿命限制,不可频繁擦写,注意地址安全与磨损均衡。
拓展应用
- 保存用户配置(如校准系数、设备 ID、工作模式)实现掉电记忆。
- 实现简易“EEPROM 模拟”:一页多槽 + 磨损均衡。
- 配合“bootloader”做固件备份/升级(双 bank)。
- 存运行计数/累计时长等需掉电保留的统计量。