STM32片内的FLASH分成两部分:主存储块、信息块。 主存储块用于存储程序,我们写的程序一般存储在这里。 信息块又分成两部分:系统存储器、选项字节。 系统存储器存储用于存放在系统存储器自举模式下的启动程序(BootLoader),当使用ISP方式加载程序时,就是由这个程序执行。 这个区域由芯片厂写入BootLoader,然后锁死,用户是无法改变这个区域的。

问题1:选项字节总共只有16个字节吗?而内部flash每次只能写半字,而且选项字节每写入低位一字节,高位自动计算反码,这样的话我怎么写入

简单来说,你只需要按半字(16位)为单位,向选项字节的低位地址写入数据,硬件会自动帮你完成剩下的所有事情。

下面为你详细拆解这个过程:

🤔 困惑的根源:三个事实的交织

  1. 选项字节共16字节:这是对的,但这16个字节是成对出现的。
  2. Flash只能写半字:这也是对的,STM32的Flash编程最小单位是16位(半字)。
  3. 写入低字节,高字节自动为反码:这同样是正确的,但这里的“高低”指的是一个半字内部的高低8位。

💡 核心机制:正反对校验

选项字节的设计核心是冗余校验,用以防止因意外(如掉电、干扰)导致配置出错。它把16个字节空间分成了8个“正反对”。

  • 你看到的16字节字节0, 字节1, 字节2, 字节3, ..., 字节14, 字节15
  • 实际的8个配置项
    • 字节1 是 字节0 的反码
    • 字节3 是 字节2 的反码
    • ...以此类推,字节15 是 字节14 的反码

芯片在启动时,会检查每一对字节是否互为反码。如果是,则认为配置有效;如果不是,则忽略该配置,使用默认值,从而保证了系统的可靠性。

🛠️ 如何正确写入

你不需要、也不能单独去写那16个字节。正确的操作方式是利用“只能写半字”这个规则。

当你需要配置一个选项字节(例如 RDP 读保护)时,你应该:

  1. 准备数据:将你想要写入的8位数据(例如 0x5A)准备好。
  2. 组成半字:将这个8位数据放在一个16位数据的低8位
  3. 执行写入:向这个选项字节的起始地址(一个16位对齐的地址)写入这个16位的半字。

举个例子:

假设你要设置读保护(RDP)字节,它的地址是 0x1FFFF800。你想把它设置为 0xA5

  • 你不需要做:先写 0xA5 到 0x1FFFF800,再计算 0x5A 写入 0x1FFFF801
  • 你应该做:直接向地址 0x1FFFF800 写入一个半字 0x5AA5

硬件会自动完成以下操作

  • 识别到你正在对选项字节进行编程。
  • 将你写入的半字 0x5AA5 拆分:低8位 0xA5 写入 0x1FFFF800,并自动计算其反码 0x5A 写入 0x1FFFF801
  • 这样,字节0 和 字节1 就完美地构成了正反对。

📝 总结

所以,你的困惑可以这样解开:

  • 总共16字节:是物理空间,由8个“正反对”组成。
  • 每次写半字:是操作方式,正好对应一个“正反对”(2个字节)。
  • 自动计算反码:是硬件特性,在你写入半字时,硬件会自动处理,确保正反对关系成立。

你只需要关注那8个有效的配置字节,并以半字的形式写入它们对应的地址即可,硬件会帮你处理好冗余备份,确保配置的安全可靠。

问题2:向地址  0x1FFFF800  写入一个半字  0x5AA5 。既然自动计算,我不能写ox00A5?

简单来说:你既可以写 0x5AA5,也可以写 0x00A5,甚至写 0x12A5 也可以。只要你写入的半字,其低8位是 0xA5,硬件就会帮你完成正确的事。

让我们深入拆解一下“自动计算”这个概念。

🤔 “自动计算”的真实含义

“自动计算反码”这个说法,更准确的理解应该是“硬件自动填充反码”。

硬件并不关心你写入的16位数据的高8位是什么。它的操作逻辑是这样的:

  1. 接收指令:收到一个向选项字节地址(如 0x1FFFF800)写入半字的指令。
  2. 提取低位:它会提取你写入数据的低8位(在这个例子中是 0xA5)。
  3. 写入低位:将这个 0xA5 写入到物理地址 0x1FFFF800(即选项字节的 字节0)。
  4. 计算并填充高位:硬件自动计算 0xA5 的反码(即 0x5A),然后将这个 0x5A 写入到物理地址 0x1FFFF801(即选项字节的 字节1)。

所以,你写入的高8位数据,实际上被硬件忽略并覆盖了。

🛠️ 为什么可以写 0x00A5

根据上面的机制,当你执行以下操作时:

  • 你的代码:向 0x1FFFF800 写入半字 0x00A5
  • 硬件的行为
    • 看到低8位是 0xA5,于是把 0xA5 写入 0x1FFFF800
    • 自动计算 0xA5 的反码是 0x5A,于是把 0x5A 写入 0x1FFFF801
  • 最终结果:内存中 0x1FFFF800 处的16位数据变成了 0x5AA5

这和你直接写入 0x5AA5 的最终结果完全一样

💡 代码实践中的写法

正因为高8位会被忽略,所以在实际编程中,为了代码清晰,我们通常会直接写成以下两种形式之一:

  1. 使用完整半字(推荐,最清晰)
    这种写法明确地展示了最终存储在Flash中的值,可读性最高。

    c

    编辑

    // 设置读保护级别为 Level 1 (值为 0x00)
    // 其反码是 0xFF,所以写入的半字是 0xFF00
    *(__IO uint16_t*)0x1FFFF800 = 0xFF00; 
  2. 只关注低8位(也完全正确)
    这种写法强调了“我只关心低8位,高8位无所谓”的意图。

    c

    编辑

    // 设置读保护级别为 Level 1 (值为 0x00)
    // 高8位写0,反正硬件会覆盖
    *(__IO uint16_t*)0x1FFFF800 = 0x0000; 

📝 总结

你的理解没有错,写 0x00A5 是完全可行的。

  • 硬件机制:硬件只“拿走”你写入数据的低8位,然后自己计算反码填充到高8位。
  • 你的自由度:你写入的半字的高8位可以是任意值,它不会影响最终结果。
  • 最佳实践:为了代码的可读性和避免歧义,建议直接写入 (反码 << 8) | 原码 的形式(如 0x5AA5),这样看到代码的人就能立刻明白选项字节的正反对关系。

问题3:写入A5是解除读保护,那读保护怎么设置

设置读保护其实和解除保护的操作逻辑完全一样,只是写入的数值不同。

正如你所说,解除保护(进入 Level 0)需要写入 0xA5。那么,要设置读保护(进入 Level 1),你需要向选项字节地址写入一个非 0xA5 且非 0xCC 的值。

在实际工程中,最常用、最标准的做法是写入 0xAA

以下是具体的设置方法和原理:

1. 核心数值:为什么是 0xAA?

STM32 的读保护级别(RDP)是通过选项字节(Option Byte)中的特定数值来定义的:

  • Level 0 (无保护):必须是 0xA5
  • Level 1 (开启保护):可以是任意非 0xA5、非 0xCC 的值
    • 虽然理论上你可以写 0x000xFF 或 0x12,但 ST 官方文档和 HAL 库通常推荐使用 0xAA 作为标准值来代表 Level 1。
  • Level 2 (永久锁死):必须是 0xCC。(警告:一旦写入,芯片永久无法解锁,调试接口彻底关闭,请绝对避免在开发阶段使用)。

2. 具体怎么写?(结合你之前的“半字”问题)

既然你知道了要写入 0xAA,结合我们之前讨论的“自动反码”机制,你需要构造一个 16 位的半字写入。

  • 目标数据(低8位)0xAA
  • 自动反码(高8位)0xAA 的反码是 0x55
  • 最终写入的半字0x55AA

操作步骤:

  1. 解锁 Flash 和选项字节(调用 HAL_FLASH_Unlock() 和 HAL_FLASH_OB_Unlock())。
  2. 准备配置结构体(如果使用 HAL 库):c

    编辑

    FLASH_OBProgramInitTypeDef OBInit;
    OBInit.OptionType = OPTIONBYTE_RDP;
    OBInit.RDPLevel = OB_RDP_LEVEL_1; // 这里 HAL 库会自动将其定义为 0xAA
  3. 执行写入
    • HAL 库方式:调用 HAL_FLASHEx_OBProgram(&OBInit);
    • 寄存器/指针方式:直接向选项字节地址写入 0x55AA
  4. 重启生效:调用 HAL_FLASH_OB_Launch() 或软件复位。

3. 设置后的后果(非常重要!)

一旦你写入 0xAA 并重启芯片,芯片就进入了 Level 1 保护模式

  • 无法再次烧录:你将无法通过 ST-Link 或 J-Link 再次向芯片下载新的程序。如果尝试烧录,软件会报错。
  • 无法读取固件:外部工具无法读取 Flash 中的内容(防止代码被抄板)。
  • 如何解除?:如果你想再次烧录程序,必须执行“解除保护”操作(即重新写入 0xA5)。
    • 代价解除 Level 1 保护会触发芯片自动执行“全片擦除”。你的程序会被清空,以此作为对安全性的补偿(防止有人通过降级保护等级来窃取代码)。

总结

要设置读保护:
请向选项字节写入 0xAA(即写入半字 0x55AA)。

建议:在产品开发完成、准备量产发货前,再进行这一步操作。开发调试阶段请保持 0xA5(无保护)状态。

问题4:反码就是按位取反?

在 STM32 选项字节的语境下,“反码”就是最直观的按位取反

💡 具体算法

就是二进制里的 0 变 11 变 0

举个你刚才提到的例子 0xA5

  1. 写成二进制
    0xA5 = 1010 0101
  2. 按位取反
    0101 1010
  3. 转回十六进制
    0101 是 51010 是 A
    结果就是 0x5A

📌 总结

所以,当你写入 0xA5 时,硬件自动生成的反码就是 0x5A,两者拼起来就是 0x5AA5

注意:这里说的“按位取反”是纯粹的位运算逻辑(类似 C 语言里的 ~ 操作符),不是计算机组成原理里讲有符号数运算时那个复杂的“反码”(1's complement)概念,不要被那个概念绕晕了。在这里,它仅仅是一个简单的校验机制。

问题5:USER位的硬件看门狗和进入停机和待机模式是否产生复位,和我代码中启动看门狗定期没喂狗产生复位有什么区别,他这个是什么意思

简单来说,选项字节(Option Bytes)里的设置是“总开关”和“行为规则”,而你代码里的操作是“执行动作”

它们不是非此即彼的关系,而是协同工作的。

下面我为你详细拆解这两者的区别和联系。

⚙️ 选项字节:定义“游戏规则”

选项字节是STM32芯片内部一块特殊的、受保护的存储区域。它里面的配置在芯片上电时就被硬件读取,决定了芯片的一些底层行为。对于看门狗,它主要定义了两个“规则”:

  1. 看门狗模式:硬件模式 vs. 软件模式

    • 硬件模式 (Hardware Watchdog): 这是选项字节中 WDG_SW 位被清零时的状态。它的意思是:芯片一上电,独立看门狗(IWDG)的计数器就立刻开始工作,无需任何代码启动。 你的代码唯一能做的就是“喂狗”,如果忘了喂,芯片必然复位。这是一种极其强硬的安全机制,防止你的主程序因为任何原因(包括没写启动看门狗的代码)而跑飞。
    • 软件模式 (Software Watchdog): 这是更常用的模式(WDG_SW 位为1)。它的意思是:看门狗是否启动,完全由你的代码说了算。 你需要在代码里调用启动函数(如写入 0xCCCC),看门狗才会开始计时。这给了你更大的灵活性。
  2. 低功耗模式下的行为:停机/待机时是否复位

    • 这两个选项(nRST_STOP 和 nRST_STDBY)定义了当你的代码命令芯片进入“停机(Stop)”或“待机(Standby)”模式时,芯片应该如何反应。
    • 如果配置为“产生复位”:那么一旦你的代码执行了进入低功耗模式的指令,芯片不会真的“睡过去”,而是会立刻产生一个复位信号,重启整个系统。
    • 如果配置为“不产生复位”:芯片才会正常地进入低功耗模式,此时看门狗(如果已启动)会继续运行,你需要在低功耗期间也定时唤醒芯片来“喂狗”,否则还是会复位。

一句话总结:选项字节决定了看门狗的“性格”——是上电就“发飙”的硬汉,还是听代码指挥的士兵;以及它在芯片“睡觉”时是应该“叫醒”还是“重启”。

💻 代码中的操作:执行“具体动作”

你的代码是在选项字节设定的“游戏规则”下进行具体操作。

  • 启动看门狗:这行代码(如 HAL_IWDG_Start())的作用,就是在软件模式下,告诉看门狗:“现在开始计时吧!”。如果选项字节设的是硬件模式,这行代码就是多余的。
  • 定期喂狗:这行代码(如 HAL_IWDG_Refresh())的作用是重置计数器,告诉看门狗:“程序还在正常运行,别复位!”。无论硬件还是软件模式,只要你启动了看门狗,就必须喂它。

一句话总结:你的代码是在规则允许的范围内,决定何时启动看门狗,并负责按时喂狗以避免复位。

🤝 区别与联系:一张表看懂

表格

特性选项字节 (Option Bytes)代码中的操作 (Code)
角色规则制定者 (总开关)规则执行者 (具体动作)
配置内容1. 看门狗是硬件模式还是软件模式
2. 进入低功耗模式时是否复位
1. 启动看门狗 (仅软件模式有效)
2. 定期喂狗
生效时机芯片上电或复位时立即生效程序运行到相应代码行时生效
目的定义系统最底层的安全策略和行为在运行时实现具体的应用逻辑和守护功能

📝 举个实际场景

假设你正在开发一个不允许死机的工业控制器:

  1. 你通过烧录器配置选项字节

    • 将看门狗设为 硬件模式
    • 将停机/待机复位设为 不产生复位
    • 这相当于给芯片下了死命令:“上电就必须被看门狗监督,但可以正常休眠。”
  2. 然后你编写代码

    • 你不需要写任何启动看门狗的代码,因为它上电就自己跑了。
    • 你的主循环里必须包含 HAL_IWDG_Refresh()
    • 当你想让设备休眠时,调用 HAL_PWR_EnterSTOPMode(),芯片会正常进入停机模式,而不会复位。

所以,回到你的问题:

  • “选项字节...产生复位”:这是指一个配置规则。它定义了芯片在特定情况(上电、进低功耗)下的默认反应。
  • “代码中...没喂狗产生复位”:这是指一个运行时行为。它是看门狗机制被触发后的直接结果。

两者共同构成了STM32完整而灵活的看门狗安全体系。

问题6:可是硬件看门狗没有进行配置,如何设定喂狗时机,而且停机和待机不是我主官设置的,设置成复位有什么意义

我们来逐一拆解,你会发现这种看似“霸道”的设计背后,其实蕴含着极高的可靠性考量。

🤔 疑问一:硬件看门狗没配置,如何设定喂狗时机?

你说的“没配置”其实是指没有在代码里启动它。但在硬件看门狗模式下,芯片一上电,看门狗就已经开始计时了。这里的“喂狗时机”其实是由硬件强制规定的,而不是由你自由设定的。

你可以把它理解成一个从芯片上电那一刻就开始倒计时的“死线”。

  1. 默认配置:硬件看门狗会使用一套默认的超时时间(通常是几秒,具体取决于芯片型号和内部时钟)。
  2. 唯一任务:你的程序从第一行代码开始,就背负着一个绝对的、不可协商的任务:必须在这个默认的超时时间内,完成第一次喂狗。
  3. 后续节奏:一旦你成功喂了第一次,你就可以按照自己的节奏(比如在主循环里每隔500ms)继续喂狗,只要两次喂狗的间隔不超过这个默认的超时时间即可。

这种设计的意义在于“零容错”
它防止了因为程序员疏忽,忘记在 main() 函数开头写启动看门狗的代码,而导致系统在出现问题时无法自我恢复的情况。硬件看门狗模式相当于一个“兜底”机制,它强迫你的程序从诞生之初就必须是“健康”的,否则连运行的机会都没有。

🤔 疑问二:停机和待机不是我主观设置的,设置成复位有什么意义?

这正是选项字节这个“规则制定者”发挥作用的地方。虽然你的主程序逻辑可能不主动进入低功耗模式,但以下两种情况可能导致芯片“意外”进入这些状态:

  1. 程序跑飞或逻辑错误
    假设你的程序因为某个Bug(比如指针越界、栈溢出)导致程序计数器(PC)跑到了一个不该去的地方。这个地方可能恰好是某个库函数或启动文件里用于进入待机模式的代码。如果没有这个“复位”规则,芯片就会“假死”在低功耗模式里,外部很难唤醒,相当于彻底“变砖”。而如果设置了“进入待机即复位”,看门狗硬件会立刻察觉到这种异常行为,并强制重启系统,让程序回到正常的 main() 函数。

  2. 外设或库的副作用
    某些复杂的外设配置或第三方库函数,在特定错误条件下,可能会触发进入低功耗模式的指令。

所以,这个设置的意义在于“防御性编程”的终极形态
它不是在为你“计划内”的行为服务,而是在为你“计划外”的、不可预知的故障提供最后一道防线。它相当于在说:“我不关心你是怎么进到待机模式的,只要不是我主动、清醒地让你进去的,那就是出事了,立刻重启!”

📝 总结

  • 硬件看门狗:不是为了给你灵活性,而是为了给你强制性。它强制你的程序必须在最短时间内证明自己“活着”,否则就重启。
  • 低功耗复位:不是为了管理正常流程,而是为了捕获异常。它能防止因程序跑飞等严重错误导致芯片陷入无法恢复的“假死”状态。

这两者共同构成了一个极其坚固的安全堡垒,确保系统在任何情况下都有能力自我恢复,这正是高可靠性嵌入式系统(如工业控制、汽车电子)所必需的。

问题7:如果进入低功耗,就没法喂狗,岂不狗总咬我

简单直接的回答是:如果处理不当,狗真的会咬你! 你的系统会陷入“休眠 -> 看门狗超时 -> 复位 -> 重新休眠”的死循环,不仅达不到低功耗的目的,反而会因为频繁重启而更加耗电。

但这并不是无解的难题。根据芯片的不同,我们通常有三种成熟的方案来解决这个矛盾。

🛡️ 方案一:让狗也“睡觉”(最推荐)

这是最优雅、最省心的方案,但需要你的芯片硬件支持(例如 STM32L4 等系列)。

  • 原理:通过配置芯片的选项字节(Option Bytes),告诉看门狗硬件:“当主控芯片进入停机(Stop)或待机(Standby)模式时,你也暂停计时,等我醒来再继续。”
  • 操作:正如我们之前聊过的,选项字节里有 IWDG_STOP 和 IWDG_STDBY 这样的配置位。你只需要在烧录程序时或通过代码将它们设置为“冻结计数器”,看门狗就会自动配合你的低功耗策略。
  • 优点:软件逻辑最简单,你完全不用管喂狗的事,专心让芯片睡觉就行。

⏰ 方案二:定个闹钟起来喂狗(最通用)

如果你的芯片不支持方案一,或者看门狗在低功耗下必须运行,这是最经典、最可靠的解决方案。

  • 原理:在进入低功耗模式前,先设置一个RTC(实时时钟)闹钟中断。这个闹钟的唤醒时间必须短于看门狗的超时时间。
  • 流程
    1. 假设看门狗超时时间是 10 秒。
    2. 你在进入休眠前,设置一个 5 秒后响起的 RTC 闹钟。
    3. 芯片进入低功耗模式。
    4. 5 秒后,RTC 闹钟中断唤醒芯片。
    5. 芯片醒来后,第一件事就是执行中断服务函数,在里面喂狗
    6. 喂完狗后,立刻再次进入低功耗模式。
  • 优点:适用于几乎所有带RTC和看门狗的MCU,非常可靠。

⚙️ 方案三:睡前喂个“大餐”(最简单)

这是一个“凑合”的办法,适用于休眠时间不长且固定的场景。

  • 原理:在进入低功耗模式之前,先喂一次狗,并且确保你的看门狗超时时间远大于你计划的休眠时间。
  • 流程
    1. 配置看门狗超时时间为 1 小时。
    2. 你的程序计划休眠 10 分钟。
    3. 休眠前,喂一次狗。
    4. 芯片安心睡 10 分钟,醒来后立刻再次喂狗。
  • 缺点:不够灵活。如果你需要休眠很长时间(比如几小时),看门狗的计数器可能无法满足(它有最大值限制)。

📌 总结

表格

方案核心思想适用场景推荐度
让狗也“睡觉”配置选项字节,冻结看门狗计数器芯片硬件支持(如STM32L系列)⭐⭐⭐⭐⭐
定个闹钟起来喂狗使用RTC定时唤醒,在中断中喂狗通用方案,最可靠⭐⭐⭐⭐
睡前喂个“大餐”确保看门狗超时时间 > 休眠时间休眠时间短且固定的简单应用⭐⭐

所以,不用担心“狗总咬你”。只要你根据项目需求和芯片型号,选择上面任何一种方案,就能完美地让看门狗和低功耗模式和谐共处。

问题8:选项字节用户自定义区域也需要反码支持?

是的,逃不掉的,用户自定义区域(Data0/Data1)也必须遵循“正反对”规则。

虽然这块区域是留给你存数据的(比如存序列号、硬件版本),但在硬件眼里,它和配置读保护(RDP)的字节没有区别,必须满足“低8位 + 高8位反码”的结构,否则芯片启动时会认为数据损坏,直接忽略你的数据。

💡 核心机制:一视同仁

在 STM32F1/F3/F0 等经典系列中,选项字节总共 16 字节,分为 8 对。

  • 字节 4 (Data0):用户数据 0
  • 字节 5 (nData0)必须是 字节 4 的反码
  • 字节 6 (Data1):用户数据 1
  • 字节 7 (nData1)必须是 字节 6 的反码

如果你直接往 Data0 写 0x12,往 nData0 写 0x00(或者不写),芯片启动时校验发现 0x12 和 0x00 不是反码关系,就会报错,通常会直接丢弃这两个字节,让你读出来的值变成默认的 0xFF

🛠️ 怎么存数据?(还是用半字大法)

好消息是,你不需要手动去算反码,也不需要分两次写。还记得我们之前聊的“写半字”吗?在这里依然完美适用。

假设你想在 Data0 存 0x5A,在 Data1 存 0x12

你只需要做一件事:

向对应的 16 位地址,写入一个组合好的半字。

  • Data0 地址0x1FFFF804

    • 你想存:0x5A
    • 你要写的半字:0xA55A (低8位是数据,高8位是反码)
    • 代码*(__IO uint16_t*)0x1FFFF804 = 0xA55A;
    • 硬件动作:自动把 0x5A 存入 0x1FFFF804,把 0xA5 存入 0x1FFFF805
  • Data1 地址0x1FFFF806

    • 你想存:0x12
    • 你要写的半字:0xED12 (0x12 的反码是 0xED
    • 代码*(__IO uint16_t*)0x1FFFF806 = 0xED12;
    • 硬件动作:自动把 0x12 存入 0x1FFFF806,把 0xED 存入 0x1FFFF807

⚠️ 特别注意:空间真的很小

一定要记住,因为这种“正反对”机制,原本 16 字节的选项字节,真正能给你存数据的空间只有 2 个字节(Data0 和 Data1)

  • 如果你需要存的数据超过 2 个字节(比如一个 4 字节的 int 或者一个浮点数),选项字节就存不下了。
  • 这种情况下,你只能去用 Flash 的最后一页(要注意读保护擦除风险)或者 OTP(一次性可编程存储器)区域。

总结: 别试图绕过反码,直接按“半字”格式写入,让硬件帮你搞定一切。

问题9:剩下的四个半字是页的写保护,是反逻辑吗?1是不保护0是保护?

STM32 的写保护(WRP)机制确实是反逻辑的。

简单总结就是:

  • 0 = 实施保护 (锁住,禁止写入)
  • 1 = 不保护 (开放,允许写入)

💡 为什么设计成“反逻辑”?

这完全是为了配合 Flash 的物理特性。

我们知道 Flash 的特性是:擦除后全为 1
当你的芯片刚出厂或者刚被擦除时,选项字节区域的所有位默认都是 1

  • 如果设计成 1 是保护:那你一拿到芯片,所有页都被锁死了,还得专门去解锁才能用,太反人类了。
  • 设计成 0 是保护:芯片擦除后默认全是 1,意味着默认全都不保护,你可以随意读写。只有当你显式地把某些位“烧录”成 0 时,才会开启保护。

📝 具体怎么操作?

假设你要保护 Flash 的第 0 页到第 3 页(对应 WRP0 寄存器的 bit0 和 bit1)。

  1. 确定目标状态
    • bit0 (控制第0-1页):要保护 -> 设为 0
    • bit1 (控制第2-3页):要保护 -> 设为 0
    • 其他位 (控制其他页):不保护 -> 保持 1
  2. 计算 WRP0 的值
    • 二进制:1111 1100
    • 十六进制:0xFC
  3. 构造半字写入
    • 数据位:0xFC
    • 反码位:0x03 (0xFC 的按位取反)
    • 写入的半字:0x03FC

代码示例:

c

编辑

// 向 WRP0 地址 (0x1FFFF808) 写入半字
// 低8位 0xFC -> 开启第0-3页保护
// 高8位 0x03 -> 自动生成的反码
*(__IO uint16_t*)0x1FFFF808 = 0x03FC;

⚠️ 一个常见的误区

虽然写保护位是“0保护,1不保护”,但解除保护并不是简单的“写1”。

因为 Flash 只能把 1 变成 0(写入),不能把 0 变成 1(除非擦除)。
如果你想把某个已经保护(值为0)的位变成不保护(值为1),你必须先擦除整个选项字节区域(让它变回全 0xFF),然后再重新写入你想要的配置。

所以,写保护的流程通常是:

  1. 解锁选项字节。
  2. 擦除选项字节(全变 0xFF,即全不保护)。
  3. 重新写入新的配置(把需要保护的页对应的位写成 0)。

问题10:每一位保护几页?

这取决于你使用的 STM32 芯片容量(Flash 大小)。不同系列的芯片,Flash 的页大小和总页数不同,因此 WRP 寄存器中每一位所代表的页数也不一样。

主要有以下两种常见情况:

1. 中/小容量产品(Flash ≤ 128KB)

(如 STM32F101/F103 的 C8、B8、C6 等型号)

  • 1 位 = 4 页
  • 页大小:通常为 1KB。
  • 保护粒度:每设置 1 位,保护 4KB 的空间。

具体分配(以 WRP0 为例):

  • Bit 0:保护第 0 ~ 3 页
  • Bit 1:保护第 4 ~ 7 页
  • ...
  • Bit 7:保护第 28 ~ 31 页
  • (WRP1、WRP2 以此类推)

2. 大容量产品(Flash ≥ 256KB)

(如 STM32F103 的 RD、ZE 等型号)

  • 1 位 = 2 页
  • 页大小:通常为 2KB。
  • 保护粒度:每设置 1 位,保护 4KB 的空间。

具体分配(以 WRP0 为例):

  • Bit 0:保护第 0 ~ 1 页
  • Bit 1:保护第 2 ~ 3 页
  • ...
  • Bit 7:保护第 14 ~ 15 页
  • (WRP1、WRP2 以此类推)

⚠️ 特殊例外:WRP3 的 Bit 7

在大容量产品中,WRP3 寄存器的最高位(Bit 7) 是个“特例”。
它不仅仅保护几页,而是直接保护剩余的所有页(例如从第 62 页一直到最后一页)。这是一种为了覆盖大地址空间的特殊设计。

📌 总结建议

如果你不确定自己的芯片属于哪一种,最稳妥的方法是查看芯片的 参考手册(Reference Manual) 中的“Flash 存储器组织(Flash Memory Organization)”章节,或者直接查看 STM32CubeMX 工具中的 Flash 映射图,那里会明确标出每一页的大小。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐