PY32F002A 选项字节实战:把 RESET 引脚变成普通 GPIO

手把手教你用代码修改 PY32 单片机的选项字节,将 nRST 复位引脚改为通用 GPIO,附带完整代码讲解和恢复方法

前言

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 里的测试步骤:

  1. 编译下载程序,断电重新上电
  2. 按下按键 → RESET 变成 GPIO → LED 亮
  3. 修改宏定义重新编译下载 → 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 常亮,停在这里
    }
  }
}

逻辑很简单:

  1. 初始化外设,等待按键按下
  2. 读取当前选项字节里的 NRST_MODE 配置
  3. 如果当前模式跟我想改的不一样 → 调用 APP_FlashOBProgram() 去修改
  4. 修改完成后验证一下,成功就点亮 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();   // 锁定选项字节

写完后记得上锁,防止意外修改。

⑧ 关键一步:触发复位

1
HAL_FLASH_OB_Launch();

这一步非常重要。 选项字节修改后不会立即生效,必须让芯片复位重新加载。这个函数会让芯片产生一次系统复位,复位后新的选项字节才生效。


四、实际操作步骤

4.1 把 RESET 改成 GPIO

  1. 确保代码中定义了 RSTPIN_MODE_GPIO

    1
    
    #define RSTPIN_MODE_GPIO
    
  2. 编译工程,下载到 PY32F002A

  3. 断电,重新上电(必须!)

  4. 按下开发板上的按键,LED 亮起 → 修改成功,RESET 现在变成 GPIO 了

4.2 把 GPIO 改回 RESET

  1. 注释掉 RSTPIN_MODE_GPIO,打开 RSTPIN_MODE_RST

    1
    2
    
    /* #define RSTPIN_MODE_GPIO */
    #define RSTPIN_MODE_RST
    
  2. 重新编译,下载到芯片

  3. 断电,重新上电

  4. 按下按键,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 编写。

世界是你们
使用 Hugo 构建
主题 StackJimmy 设计