PUYA FLASH — 页擦除与写入(HAL 库)

PUYA PY32F0xx_Firmware V1.5.0 内部 Flash 页擦除与写入(FLASH_PageEraseAndWrite)示例教学(HAL 库版本)

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)。本示例完整演示标准流程:

  1. 等待用户按键(避免每次上电都擦写,延长 Flash 寿命);
  2. 解锁 Flash;
  3. 页擦除 目标地址;
  4. 校验空白(应为 0xFFFFFFFF);
  5. 按页编程 写入 64 个 32 位数据;
  6. 加锁 Flash;
  7. 回读校验,一致则点亮绿灯。

目标地址 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 提示定位)。

常见问题

  1. HAL_FLASH_Program 返回错误?

    • 目标页未先擦除(仍有 0)→ 必须先 HAL_FLASHEx_Erase
    • 未调用 HAL_FLASH_Unlock()
  2. 数据回读不对?

    • 编程地址与读取地址不一致;或 src 指针推进量算错(应 FLASH_PAGE_SIZE/4 个 32 位)。
    • 写入单位(FLASH_TYPEPROGRAM_PAGE)与页大小不匹配。
  3. 程序跑飞/变砖?

    • 写入地址覆盖了中断向量表或代码区。务必选在代码末端之后的空闲页;必要时先备份。
  4. 想存少量参数?

    • 不必每次整页擦。可把一页划成“多槽”,每次找空白槽追加写,写满再整页擦(磨损均衡)。

小结

  • Flash 操作流程固定:解锁 → 页擦除 →(校验空白)→ 编程 → 加锁 → 回读校验
  • 核心规则:先擦后写、按页操作、擦除即整页 0xFF
  • 操作有寿命限制,不可频繁擦写,注意地址安全与磨损均衡。

拓展应用

  • 保存用户配置(如校准系数、设备 ID、工作模式)实现掉电记忆。
  • 实现简易“EEPROM 模拟”:一页多槽 + 磨损均衡。
  • 配合“bootloader”做固件备份/升级(双 bank)。
  • 存运行计数/累计时长等需掉电保留的统计量。
世界是你们
使用 Hugo 构建
主题 StackJimmy 设计