前言
PY32F002A 是一款超小型的 32 位单片机,TSSOP8 封装只有 8 个引脚。对于这么少的引脚来说,每一根都很珍贵。
但出厂时,芯片的 PA7/RESET 引脚默认是复位功能。这意味着你少了一个 GPIO 可用。如果项目里引脚不够用,能不能把这根引脚"解放"出来,当成普通 GPIO 用呢?
能。 方法就是修改芯片的 选项字节(Option Byte)。
本文基于普冉官方的 FLASH_OptionByteWrite_RST 例程,手把手教你如何通过代码把 RESET 引脚改成 GPIO。
一、选项字节是什么?
你可以把选项字节理解为芯片的"配置开关"。它存储在 Flash 的一个特殊区域里(地址 0x1FFF 0F00 起),芯片上电时会先读取这些配置,再决定怎么工作。
选项字节里可以配置的东西包括:
| 配置项 |
作用 |
NRST_MODE |
复位引脚模式:复位功能 or GPIO |
BOR_LEV |
欠压复位电压阈值 |
IWDG_SW |
独立看门狗:硬件启动 or 软件启动 |
BOOT1 |
启动模式选择 |
注意:修改选项字节后,必须断电重新上电才能生效。
二、例程功能说明
这个例程实现了一个很实用的功能:
按下开发板上的按键,就把 RESET 引脚改成 GPIO(或改回来),然后用 LED 亮起来告诉你"改好了"。
官方 readme 里的测试步骤:
- 编译下载程序,断电重新上电
- 按下按键 → RESET 变成 GPIO → LED 亮
- 修改宏定义重新编译下载 → RESET 恢复 → LED 亮
三、代码逐行讲解
3.1 核心宏定义
1
2
|
#define RSTPIN_MODE_GPIO
/* #define RSTPIN_MODE_RST */
|
这两个宏二选一:
- 定义
RSTPIN_MODE_GPIO → 编译出来的程序会把 RESET 改成 GPIO
- 定义
RSTPIN_MODE_RST → 编译出来的程序会把引脚恢复成 RESET 功能
3.2 main 函数主流程
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
|
int main(void)
{
HAL_Init(); // HAL 库初始化
BSP_LED_Init(LED_GREEN); // 初始化 LED
BSP_PB_Init(BUTTON_KEY, BUTTON_MODE_GPIO); // 初始化按键
while (BSP_PB_GetState(BUTTON_USER)) // 等待按键按下
{
}
// 检查当前 RST 引脚模式
#if defined(RSTPIN_MODE_GPIO)
if(READ_BIT(FLASH->OPTR, FLASH_OPTR_NRST_MODE) == OB_RESET_MODE_RESET)
#else
if(READ_BIT(FLASH->OPTR, FLASH_OPTR_NRST_MODE) == OB_RESET_MODE_GPIO)
#endif
{
APP_FlashOBProgram(); // 模式不对,执行修改
}
while (1)
{
// 检查修改是否生效,生效则点亮 LED
#if defined(RST_PIN_MODE_GPIO)
if(READ_BIT(FLASH->OPTR, FLASH_OPTR_NRST_MODE) == OB_RESET_MODE_GPIO)
#else
if(READ_BIT(FLASH->OPTR, FLASH_OPTR_NRST_MODE) == OB_RESET_MODE_RESET)
#endif
{
BSP_LED_On(LED_GREEN);
while(1); // LED 常亮,停在这里
}
}
}
|
逻辑很简单:
- 初始化外设,等待按键按下
- 读取当前选项字节里的
NRST_MODE 配置
- 如果当前模式跟我想改的不一样 → 调用
APP_FlashOBProgram() 去修改
- 修改完成后验证一下,成功就点亮 LED
3.3 选项字节修改函数(重点)
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
|
static void APP_FlashOBProgram(void)
{
FLASH_OBProgramInitTypeDef OBInitCfg = {0};
HAL_FLASH_Unlock(); // ① 解锁 Flash
HAL_FLASH_OB_Unlock(); // ② 解锁选项字节区域
// ③ 配置要修改哪些选项
OBInitCfg.OptionType = OPTIONBYTE_USER;
OBInitCfg.USERType = OB_USER_BOR_EN | OB_USER_BOR_LEV
| OB_USER_IWDG_SW | OB_USER_NRST_MODE | OB_USER_nBOOT1;
// ④ 配置具体数值
#if defined(RSTPIN_MODE_GPIO)
OBInitCfg.USERConfig = OB_BOR_DISABLE | OB_BOR_LEVEL_3p1_3p2
| OB_IWDG_SW | OB_RESET_MODE_GPIO | OB_BOOT1_SYSTEM;
#else
OBInitCfg.USERConfig = OB_BOR_DISABLE | OB_BOR_LEVEL_3p1_3p2
| OB_IWDG_SW | OB_RESET_MODE_RESET | OB_BOOT1_SYSTEM;
#endif
// ⑤ 写入选项字节
HAL_FLASH_OBProgram(&OBInitCfg);
HAL_FLASH_Lock(); // ⑥ 锁定 Flash
HAL_FLASH_OB_Lock(); // ⑦ 锁定选项字节
// ⑧ 触发复位加载新选项字节
HAL_FLASH_OB_Launch();
}
|
逐段解析:
①② 双重解锁
1
2
|
HAL_FLASH_Unlock(); // 先解锁整个 Flash
HAL_FLASH_OB_Unlock(); // 再解锁选项字节区域
|
选项字节是敏感区域,必须双重解锁才能写入。少了任何一步都会写入失败。
③ 指定要改哪些选项
1
2
3
|
OBInitCfg.OptionType = OPTIONBYTE_USER;
OBInitCfg.USERType = OB_USER_BOR_EN | OB_USER_BOR_LEV
| OB_USER_IWDG_SW | OB_USER_NRST_MODE | OB_USER_nBOOT1;
|
这里用 位或(|) 告诉 HAL 库:我要同时修改这 5 个选项。即使只想改 NRST_MODE,也必须把所有字段都带上,因为写入时是整个 User Option Byte 一起写的。
④ 设置具体配置值
1
2
|
OB_RESET_MODE_GPIO // 把 RESET 改成 GPIO
OB_RESET_MODE_RESET // 恢复 RESET 复位功能
|
其他几个配置的含义:
| 宏 |
含义 |
OB_BOR_DISABLE |
关闭欠压复位 |
OB_BOR_LEVEL_3p1_3p2 |
欠压阈值:上升沿 3.2V,下降沿 3.1V |
OB_IWDG_SW |
看门狗由软件启动 |
OB_BOOT1_SYSTEM |
从 System Memory 启动 |
⑤ 执行写入
1
|
HAL_FLASH_OBProgram(&OBInitCfg);
|
调用 HAL 库函数,把配置写入 Flash 选项字节区域。
⑥⑦ 重新上锁
1
2
|
HAL_FLASH_Lock(); // 锁定 Flash
HAL_FLASH_OB_Lock(); // 锁定选项字节
|
写完后记得上锁,防止意外修改。
⑧ 关键一步:触发复位
这一步非常重要。 选项字节修改后不会立即生效,必须让芯片复位重新加载。这个函数会让芯片产生一次系统复位,复位后新的选项字节才生效。
四、实际操作步骤
4.1 把 RESET 改成 GPIO
-
确保代码中定义了 RSTPIN_MODE_GPIO:
1
|
#define RSTPIN_MODE_GPIO
|
-
编译工程,下载到 PY32F002A
-
断电,重新上电(必须!)
-
按下开发板上的按键,LED 亮起 → 修改成功,RESET 现在变成 GPIO 了
4.2 把 GPIO 改回 RESET
-
注释掉 RSTPIN_MODE_GPIO,打开 RSTPIN_MODE_RST:
1
2
|
/* #define RSTPIN_MODE_GPIO */
#define RSTPIN_MODE_RST
|
-
重新编译,下载到芯片
-
断电,重新上电
-
按下按键,LED 亮起 → 恢复成功
五、注意事项(非常重要)
5.1 改完后怎么重新下载程序?
把 RESET 改成 GPIO 后,你会发现:按复位键没法复位芯片了,因为那个引脚已经不是复位功能。
这时候如果要重新下载程序,有两种方法:
方法一:断电重新上电下载
大多数下载器支持"上电下载"模式,断电 → 点下载 → 上电,就能下载。
方法二:先改回 RESET 再下载
如果程序里已经跑了改回 RESET 的逻辑,可以先断电上电,按下按键触发恢复。
5.2 别把芯片锁死
选项字节里如果配错了,可能导致芯片无法正常启动。改之前确保你能恢复。
官方例程很贴心地在 readme 里提醒了:
该样例将 RST 的按键改成 GPIO 功能,在使用其他样例前请将 RST 按键改回 RST 功能。
5.3 修改必须断电才生效
很多新手(包括我)第一次玩的时候,改完选项字节看到 LED 亮了,以为已经生效。实际上:
HAL_FLASH_OBProgram() 只是把值写进了 Flash
HAL_FLASH_OB_Launch() 只是触发了一次软件复位
- 真正的选项字节加载发生在硬件复位时(断电重新上电)
所以 readme 里每一步都强调"断电重新上电",这不是废话,是真的有必要。
六、完整代码
以下是精简后的核心代码,去掉了板级支持包依赖,方便移植到自己的项目:
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
|
#include "py32f0xx_hal.h"
#define RSTPIN_MODE_GPIO // 改成 GPIO
// #define RSTPIN_MODE_RST // 改回复位功能
static void APP_FlashOBProgram(void)
{
FLASH_OBProgramInitTypeDef OBInitCfg = {0};
HAL_FLASH_Unlock();
HAL_FLASH_OB_Unlock();
OBInitCfg.OptionType = OPTIONBYTE_USER;
OBInitCfg.USERType = OB_USER_BOR_EN | OB_USER_BOR_LEV
| OB_USER_IWDG_SW | OB_USER_NRST_MODE | OB_USER_nBOOT1;
#if defined(RSTPIN_MODE_GPIO)
OBInitCfg.USERConfig = OB_BOR_DISABLE | OB_BOR_LEVEL_3p1_3p2
| OB_IWDG_SW | OB_RESET_MODE_GPIO | OB_BOOT1_SYSTEM;
#else
OBInitCfg.USERConfig = OB_BOR_DISABLE | OB_BOR_LEVEL_3p1_3p2
| OB_IWDG_SW | OB_RESET_MODE_RESET | OB_BOOT1_SYSTEM;
#endif
HAL_FLASH_OBProgram(&OBInitCfg);
HAL_FLASH_Lock();
HAL_FLASH_OB_Lock();
HAL_FLASH_OB_Launch(); // 复位加载新选项字节
}
int main(void)
{
HAL_Init();
// ... 你的初始化代码 ...
// 判断当前模式,如果不对就修改
#if defined(RSTPIN_MODE_GPIO)
if (READ_BIT(FLASH->OPTR, FLASH_OPTR_NRST_MODE) == OB_RESET_MODE_RESET)
#else
if (READ_BIT(FLASH->OPTR, FLASH_OPTR_NRST_MODE) == OB_RESET_MODE_GPIO)
#endif
{
APP_FlashOBProgram();
}
while (1)
{
// 正常工作...
}
}
|
七、总结
| 操作 |
关键函数 |
| 解锁 Flash |
HAL_FLASH_Unlock() |
| 解锁选项字节 |
HAL_FLASH_OB_Unlock() |
| 写入选项字节 |
HAL_FLASH_OBProgram() |
| 锁定 Flash |
HAL_FLASH_Lock() |
| 锁定选项字节 |
HAL_FLASH_OB_Lock() |
| 复位加载新配置 |
HAL_FLASH_OB_Launch() |
把 RESET 改成 GPIO 是一个非常实用的技巧,特别是对于 PY32F002A 这种只有 8 个引脚的芯片。但一定要记得:改完后 RESET 键就不能复位了,改之前想清楚怎么恢复。
本文基于普冉 PY32F0xx HAL 库例程 FLASH_OptionByteWrite_RST 编写。