目录一FLASH理论知识学习1 FLASH 内部的构成1主存储器2系统存储区3OTP区域4选项字节5OTP和选项字节区别6bootload7boot0 和BOOT_ADD1选项字节启动设置① BOOT0 0 → Main Flash② BOOT0 1、BOOT_ADD1 0 → System Memory③ BOOT0 1、BOOT_ADD1 1 → SRAM8为什么主flash存储器是0x0800 00002128 位宽数据读取3字节、半字、字和双字数据写入4扇区擦除与全部擦除5三种 Flash 对比6flash寄存器简单介绍二Flash实验11static uint32_t GetSector(uint32_t Address)2InternalFlash_Test三Flash实验21 选项字节Option Bytes—— 芯片的「配置区」2读保护RDP—— 锁住「读」3 写保护WRP—— 锁住「写」4一张图记住三者的关系四st-link-utiltiy1上位机ST-LINK Utility爽在哪2但上位机替代不了写代码3一句话4软件界面5选项字节的操作一FLASH理论知识学习1 FLASH 内部的构成1主存储器一般我们说STM32内部FLASH的时候都是指这个主存储器区域它是存储用户应 用程序的空间芯片型号说明中的1MFLASH、2MFLASH都是指这个区域的大小。主存储器最大 2MB仅 F427/F437 型号才有此时分成2 个 bank块每个 bank 12 个扇区共 24 个扇区扇区 0~23。而我们实验板的 STM32F429IGT6 是1MB只有1 个 bank共 12 个扇区扇区 0~11包含 4 个 16KB 扇区、1 个 64KB 扇区和 7 个 128KB 扇区。与其它FLASH一样在写入数据前要先按扇区擦除而有的时候我们希望能以小 规格操纵存储单元。所以STM32F42x/43x针对1MBFLASH的产品还提供了一种双块的存储格式通过配置 FLASH 选项控制寄存器 FLASH_OPTCR 的 nDB1M 位双 bank 模式开关可以把 1MB 从「1 个 bank、12 扇区」切成「2 个 bank、每个 bank 512KB、8 个扇区」。用途是 IAP 边跑边升级擦除一个 bank 时另一个 bank 里的程序还能继续执行升级完再切换过去不用停机。总容量不变。2系统存储区系统存储区是用户不能修改的区域出厂固化的只读 ROM但能读、能执行——BOOT01 时 CPU 就是在跑它。它负责实现串口、USB以及CAN等ISP烧录功能3OTP区域OTP(One Time Program)指的是只能写入一次的存储区域容量为512字节写入后 数据就无法再更改这 512 字节是分 16 块、每块 32 字节旁边还有 16 个额外的锁定位各自锁定对应的块——这就是一次性的实现机制写完一块就锁死对应位之后这块再也改不了。它适合存一些写进去以后不希望再修改的数据例如芯片唯一配置参数产品序列号出厂编号永久授权信息校准参数产品型号/硬件版本某些不可变的安全相关信息例如你做一批设备可以在生产烧录时OTP ┌──────────────────────┐ │ Product ID │ │ Serial Number │ │ Hardware Version │ │ Calibration Data │ │ Manufacturing Info │ └──────────────────────┘写进去以后就相当于这个信息盖章了不能再随便改所以 OTP不适合存运行参数 用户设置 计数器 日志 频繁变化的数据因为它是一次性编程区域。4选项字节它不是给你的应用程序存普通数据的而是给 STM32 自己配置运行行为的。Option Bytes │ ├── 读保护 RDP ├── BOR 电压等级 ├── 软件/硬件看门狗配置 ├── Stop/Standby 模式复位行为 └── 其他芯片启动/保护配置RDP —— Read Out Protection读保护你做产品以后不希望别人把 Flash 里的程序读出来RDP ↓ 开启读保护 ↓ 限制外部读取 Flash这就是 Option Bytes 在控制。BOR Brown-out Reset 例如电源电压掉到某个危险范围VCC │ │──────────── 正常 │ │ ├──── BOR 阈值 │ │ 电压下降 ↓ 芯片复位Option Bytes 可以配置相应的 BOR 等级。看门狗配置可以通过 Option Bytes 配置IWDG ├── 硬件启动 └── 软件启动也就是说你可以让芯片上电后直接启用独立看门狗而不是完全由程序决定。5OTP和选项字节区别OTPOption Bytes全称One-Time ProgrammableOption Bytes主要用途保存永久数据配置芯片行为F429512 B芯片配置区域是否给用户存普通数据✅ 可以❌ 不是这个用途是否频繁修改❌ 不适合❌ 不适合典型内容序列号、校准值、产品信息RDP、BOR、看门狗、复位配置谁使用你的程序/生产烧录程序STM32 Flash/启动/电源控制逻辑6bootloadst官方的bootload 和我们写的bootload 写的地方不一样两个 bootloader 住的地方不一样位置性质谁能改ST 官方 bootloader系统存储器0x1FFF 0000出厂固化的 ROM谁都改不了连你烧录器都擦不掉我们写的 bootloader主 Flash 最前面几个扇区如 Sector 0~10x0800 0000起你自己烧的代码你能擦能写ST 官方的 bootloader 根本不在主 Flash 里而是在独立的系统存储器里所以它跟你的程序零冲突、永远擦不坏。0x0800 0000 主 Flash1MB ← 你的程序 / 你写的 bootloader 都在这 0x1FFE C000 选项字节 ← RDP/WRP 那些 0x1FFF 0000 系统存储器30KB ← ST 官方 bootloader 固化在这 0x2000 0000 SRAM启动方式也不一样怎么进去原理ST 官方拉高 BOOT0 引脚硬件把0x1FFF 0000映射到0x0000 0000上电直接从系统存储器跑我们的软件跳转上电先进我们主 Flash 里的 bootloader跑完后跳转到 APP 地址不用动 BOOT0所以两者虽然都叫bootloader、都能下载程序、都能跳转 APP但官方的是靠引脚切换进入我们的是程序自己决定上电先跑 bootloader → 判断要不要升级 → 跳 APP。[上电] → BOOT00 → 主 Flash → (我们写的 bootloader) → 跳转 → APP └ BOOT01 → 系统存储器 → ST 官方 bootloader → 串口 ISP 下载7boot0 和BOOT_ADD1选项字节启动设置⚠️F429 没有 BOOT1 物理引脚这是 F1 和 F429 的关键区别F1F103BOOT1 是物理引脚PB2拨引脚选启动源F429F42x/F43xBOOT1 改成了选项字节里的 BOOT_ADD1改启动源要改选项字节不是拨引脚F429 真实启动表BOOT0 引脚BOOT_ADD1选项字节启动空间0无所谓主 Flash 0x0800 000010默认系统存储器11SRAM 0x2000 0000STM32 复位后CPU 不会直接“无脑执行 0x0000 0000跳转至0x0800 0000STM32 上电/复位 ↓ 读取 BOOT0 / BOOT_ADD1 ↓ 决定启动地址 ↓ ┌──────────┼──────────┐ ↓ ↓ ↓ Main Flash System SRAM Memory ↓ ↓ ↓ 用户程序 ST Bootloader RAM程序① BOOT0 0 → Main Flash此时BOOT_ADD1可以是高也可以是低x是指的是随机这是我们平时最常用的BOOT0 0 ↓ Main Flash ↓ 0x08000000 ↓ 你的 STM32 程序例如你用 Keil 下载main.c ↓ 编译 ↓ .hex / .bin ↓ 烧录到 0x08000000然后重新上电BOOT0 0 ↓ CPU 从 Main Flash 启动 ↓ 执行你的程序所以你的开发板平时基本就是这个状态。② BOOT0 1、BOOT_ADD1 0 → System Memory这个就是你刚才问的 ST 官方 Bootloader 出场的地方。BOOT0 1 BOOT_ADD1 0 ↓ System Memory ↓ ST 出厂固化的 Bootloader ↓ USART / USB 等接口 ↓ 接收新的固件 ↓ 写入 Main Flash这就是所谓的ISPIn-System Programming例如你不用 ST-Link而是想通过串口给 STM32 下载程序。可以PC │ │ USART ↓ STM32 │ ↓ System Memory Bootloader │ ↓ 写入 Main Flash所以你以后看到STM32 串口 ISP 下载脑子里就应该马上想到System Memory → ST 官方 Bootloader容易混淆的地方BOOT01、BOOT_ADD10 并不是“选择串口/USB”。它只是让 CPU 去执行 System Memory 里的 ST 官方 Bootloader。 而这个 Bootloader 程序本身才负责检测串口、USB 等接口并通过它们接收固件。 上电 ↓ 读取 BOOT0/BOOT_ADD1 ↓ ┌──────────────────┐ │ System Memory │ │ ST 官方Bootloader │ └────────┬─────────┘ ↓ CPU 开始执行 ↓ Bootloader 检测可用接口 ↓ ↓ USART USB ↓ ↓ 接收固件数据 ↓ 写入 Main Flash 0x08000000...③ BOOT0 1、BOOT_ADD1 1 → SRAM这种模式是BOOT0 1 BOOT_ADD1 1 ↓ Internal SRAM ↓ 执行 SRAM 中的程序这里有一个很关键的区别程序不是从 Flash 执行而是从 SRAM 执行。例如Flash 0x08000000 │ │ 你的程序 │ X │ │ SRAM 0x20000000 │ │ 程序 ↓ CPU 执行主要用于一些特殊调试、测试场景因为 SRAM 是掉电丢失的断电 ↓ SRAM 内容没了 ↓ 重新启动 ↓ 又不能靠 SRAM 中原来的程序启动所以一般很少使用。STM32 │ 上电/复位 │ BOOT 引脚判断 │ ┌───────────┼───────────┐ ↓ ↓ ↓ Main Flash System Memory SRAM │ │ │ ↓ ↓ ↓ 正常运行 官方Bootloader 调试 │ │ │ ↓ │ 下载新程序 │ │ └──────←────┘8为什么主flash存储器是0x0800 0000Cortex-M 内核把所有存储器统一编址到一个 4GB32 位空间布局是所有厂商都必须遵守的0x0000 0000 ┬─ Code 区域512MB ← Flash 必须放这里 0x1FFF FFFF ┘ 0x2000 0000 ┬─ SRAM 区域512MB ← RAM 放这 0x3FFF FFFF ┘ 0x4000 0000 ┬─ 外设区域512MB ← GPIO/TIM/USART 寄存器都在这 0x5FFF FFFF ┘ 0x6000 0000 ┬─ 外部存储器1GB ← 你学的 FMC SDRAM 就是映射到 0xD000 0000 0x9FFF FFFF ┘ 0xE000 0000 ┬─ 系统区域NVIC/SysTick/调试 ← Cortex-M 内核专用 0xFFFF FFFF ┘Flash 是存代码的只能落在最顶上的 Code 区域0x0000 0000 ~ 0x1FFF FFFF。但 0x0000 0000 这个地址被征用了Cortex-M 有个硬性规定复位后CPU 固定从0x0000 0000读两个东西地址内容0x0000 0000初始 MSP栈顶指针0x0000 0004复位向量PC第一条要执行的指令地址也就是说向量表必须出现在0x0000 0000CPU 上电第一件事就是去 0 号地址拿栈指针和复位地址。因此ST 给 Flash 一个真实地址0x0800 00000x0000 0000 ┬ 别名区重映射窗口 │ 上电后根据 BOOT 引脚把下面某一块映射到这里 0x07FF FFFF ┘ 0x0800 0000 ┬ 主 Flash 真实地址1MB← 你的程序真正烧在这 0x080F FFFF ┘ ... 0x1FFF 0000 ┬ 系统存储器官方 bootloader当你写 bootloader 跳转 APP 时APP 的向量表在0x0800 0000 偏移处而 CPU 默认还从0x0000 0000 Flash 开头读向量表。所以跳转前必须做一件事SCB-VTOR 0x08000000 APP偏移; // 把向量表基址重新指到 APP 的向量表这个SCB-VTOR向量表偏移寄存器干的活本质就是手动把向量表从 0 号地址改到 APP 真实地址——跟 BOOT 引脚硬件映射是同一个道理只不过这次是软件改。2128 位宽数据读取这条不是指 CPU 数据总线而是Flash 内部读接口一次能吐出 128 bit 4 个字。F4 上面有个ART 加速器Adaptive Real-Time Memory Accelerator配合预取Prefetch 指令缓存I-cache 数据缓存D-cache一起工作CPU 要执行指令按 32 位一条取Flash 一次给 128 位4 条指令丢进缓存CPU 顺序执行时直接从缓存拿等于 0 等待周期。这就是为什么 F429 能跑到180 MHz而 Flash 本身速度远跟不上却不拖后腿的原因。这条跟性能有关面试爱问F4 为什么能 180M 不降速。3字节、半字、字和双字数据写入指写 Flash 时数据宽度可以选8 / 16 / 32 / 64 位对应 HAL 里的枚举写入宽度HAL 枚举8 位FLASH_TYPEPROGRAM_BYTE16 位FLASH_TYPEPROGRAM_HALFWORD32 位FLASH_TYPEPROGRAM_WORD64 位FLASH_TYPEPROGRAM_DOUBLEWORD但这里藏着一个最本质的坑一定要记住NOR Flash 的写操作只能把 bit 从1变成0不能把0变回1。 想把0变回1唯一办法是整扇区擦除擦除后所有 bit 回到1即0xFF。所以你会看到擦除 → 写 → 读校验这个三步流程。已经写过 0 的地方不能再直接写 1必须先擦。4扇区擦除与全部擦除Flash 的最小擦除单位是扇区不能只擦几个字节。所以想改一个字节 → 得把整个扇区擦掉重写你昨天记住的那个存配置参数用最后 128KB 扇区Sector 11就是因为擦除太贵得单独圈一个扇区别跟主程序挤在一起不然每次改配置都要把程序也擦了。这就是内部 Flash 存参数 vs EEPROM 的区别EEPROM你学过的 AT24C02能字节擦写Flash 只能整扇区。所以工业上要么外挂 EEPROM要么用内部 Flash 读-改-写 备份区的玩法。5三种 Flash 对比F1 内部 FlashF429 内部 FlashW25Q256 外扩 Flash容量取决于型号64K~512K1MB32MB接口总线直连总线直连SPI擦除单位页1KB扇区16/64/128KB扇区 4KB / 块32KB/ 64KB/整片写单位半字16位字/半字/字节/双字页 256 字节擦写次数~1万次~1万次~10万次用途存程序存程序 配置存字库/文件6flash寄存器简单介绍Flash 访问控制寄存器 (FLASH_ACR)Flash 密钥寄存器 (FLASH_KEYR)Flash 状态寄存器 (FLASH_SR)Flash控制寄存器 (FLASH_CR)Flash 选项密钥寄存器 (FLASH_OPTKEYR)Flash 选项控制寄存器 (FLASH_OPTCR)Flash 选项控制扩展寄存器 (FLASH_OPTCR1)Flash 控制器 │ ┌────────────────┼────────────────┐ │ │ │ ↓ ↓ ↓ FLASH_ACR FLASH_KEYR FLASH_SR │ │ │ Flash访问配置 解锁Flash 查看Flash状态 │ │ ↓ FLASH_CR │ ├── Program ├── Sector Erase └── Mass Erase Option Bytes │ ├── FLASH_OPTKEYR │ ↓ │ 解锁OB │ ├── FLASH_OPTCR │ ↓ │ OB配置 │ └── FLASH_OPTCR1 ↓ 扩展配置二Flash实验11static uint32_t GetSector(uint32_t Address)把 FLASH 地址映射成所属扇区号/* ════════════════════════════════════════════════════════════ * GetSector — 把 FLASH 地址映射成所属扇区号 * * 原型static uint32_t GetSector(uint32_t Address) * Address — [输入] FLASH 字节地址 * 返值地址所在的扇区号FLASH_SECTOR_0 ~ FLASH_SECTOR_7 * * 原理逐个扇区判断地址落在哪个区间。判断条件「下边界 ≤ Address 上边界」 * 等于上边界就归下一个扇区如 0x08004000 属于 Sector1 而不是 Sector0。 * * 调用示例 * FirstSector GetSector(FLASH_USER_START_ADDR); // 0x08020000 → FLASH_SECTOR_5 * ════════════════════════════════════════════════════════════ */ static uint32_t GetSector(uint32_t Address) { uint32_t sector 0; // 扇区号返回值初值 0兜底 if((Address ADDR_FLASH_SECTOR_1) (Address ADDR_FLASH_SECTOR_0)) { sector FLASH_SECTOR_0; // 地址落在 0x08000000 ~ 0x08003FFF 之间 → 属于 Sector016KB } else if((Address ADDR_FLASH_SECTOR_2) (Address ADDR_FLASH_SECTOR_1)) { sector FLASH_SECTOR_1; // 地址落在 0x08004000 ~ 0x08007FFF 之间 → 属于 Sector116KB } else if((Address ADDR_FLASH_SECTOR_3) (Address ADDR_FLASH_SECTOR_2)) { sector FLASH_SECTOR_2; // 地址落在 0x08008000 ~ 0x0800BFFF 之间 → 属于 Sector216KB } else if((Address ADDR_FLASH_SECTOR_4) (Address ADDR_FLASH_SECTOR_3)) { sector FLASH_SECTOR_3; // 地址落在 0x0800C000 ~ 0x0800FFFF 之间 → 属于 Sector316KB } else if((Address ADDR_FLASH_SECTOR_5) (Address ADDR_FLASH_SECTOR_4)) { sector FLASH_SECTOR_4; // 地址落在 0x08010000 ~ 0x0801FFFF 之间 → 属于 Sector464KB } else if((Address ADDR_FLASH_SECTOR_6) (Address ADDR_FLASH_SECTOR_5)) { sector FLASH_SECTOR_5; // 地址落在 0x08020000 ~ 0x0803FFFF 之间 → 属于 Sector5128KB } else if((Address ADDR_FLASH_SECTOR_7) (Address ADDR_FLASH_SECTOR_6)) { sector FLASH_SECTOR_6; // 地址落在 0x08040000 ~ 0x0805FFFF 之间 → 属于 Sector6128KB } else { sector FLASH_SECTOR_7; // 地址 0x08060000 → 属于 Sector7128KB兜底 } return sector; }2InternalFlash_Test内部flash 测试/* ════════════════════════════════════════════════════════════ * InternalFlash_Test — 内部 FLASH 读写测试擦除→写入→回读校验 * * 原型int InternalFlash_Test(void) * 形参无 * 返值0 擦除 / 写入 / 校验全部成功 * -1 擦除失败 / 写入失败 / 回读数据不一致 * * 原理 * ① HAL_FLASH_Unlock 解锁 FLASH 控制寄存器写前必须解锁 * ② GetSector 起始/结束地址 → 扇区号算出擦除个数 * ③ HAL_FLASHEx_Erase 擦除 Sector5~6擦成 0xFF * ④ HAL_FLASH_Program 按字循环写入 0x87654321地址 4 * ⑤ HAL_FLASH_Lock 上锁防止后续误写 * ⑥ 回读校验 *(uint32_t*)Address 逐字比较统计错误数 * * 调用示例 * if (InternalFlash_Test() 0) { LED_G(0); } // 绿灯亮 测试通过 * ════════════════════════════════════════════════════════════ */ int InternalFlash_Test(void) { uint32_t FirstSector 0; // 起始扇区号 uint32_t NbOfSectors 0; // 要擦除的扇区个数 uint32_t SectorError 0; // 擦除出错的扇区号HAL 回传 uint32_t Address 0; // 当前操作的地址指针 __IO uint32_t Data32 0; // 回读时暂存读出的字 __IO uint32_t ErrCnt 0; // 校验不一致的字计数 FLASH_EraseInitTypeDef EraseInitStruct; // 擦除配置结构体 /* ① 解锁写 FLASH 控制寄存器前必须先解锁防止误擦写 */ HAL_FLASH_Unlock(); //库函数 /* ② 地址 → 扇区号END-1 让结束边界落进 Sector6 */ FirstSector GetSector(FLASH_USER_START_ADDR); // 传入0x08020000通过封装函数因素映射到Sector5 NbOfSectors GetSector(FLASH_USER_END_ADDR - 1) - FirstSector 1; // 0x0805FFFF → Sector6 → 6-51 2 /* ③ 擦除FLASH 只能 1→0写前必须整扇区擦成 0xFF */ EraseInitStruct.TypeErase FLASH_TYPEERASE_SECTORS; // 按扇区擦除 EraseInitStruct.VoltageRange FLASH_VOLTAGE_RANGE_3; // 3.3V 属于 电压范围选项32.7~3.6V EraseInitStruct.Sector FirstSector; // 从 Sector5 开始 EraseInitStruct.NbSectors NbOfSectors; // 擦 2 个扇区5、6 if (HAL_FLASHEx_Erase(EraseInitStruct, SectorError) ! HAL_OK) { return -1; // 擦除失败SectorError 记录出错扇区号 printf(擦除失败\r\n); } /* ④ 写入按字4 字节循环写 FLASH_TEST_DATA地址每次 4 */ Address FLASH_USER_START_ADDR; while (Address FLASH_USER_END_ADDR) //FLASH_USER_END_ADDRADDR_FLASH_SECTOR_7写56扇区 { if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, Address, FLASH_TEST_DATA) HAL_OK) { Address Address 4; // 写入成功地址指向下一个字 } else { return -1; // 写入失败 } } /* ⑤ 上锁写完后锁上防止后续程序误写 */ HAL_FLASH_Lock(); /* ⑥ 回读校验逐字读回比较统计不一致个数 */ Address FLASH_USER_START_ADDR; ErrCnt 0; while (Address FLASH_USER_END_ADDR) //FLASH_USER_END_ADDRADDR_FLASH_SECTOR_7读56扇区 { Data32 *(__IO uint32_t *)Address; // 直接读 FLASH 地址上的 32 位数据赋值给Data32临时变量 if (Data32 ! FLASH_TEST_DATA) //Data32不等于0x87654321时ErrCnt { ErrCnt; // 数据不一致错误计数 1 } Address Address 4; // 指向下一个字 } /* 校验结果ErrCnt0 全部一致否则有错 */ if (ErrCnt) //ErrCnt值非0时if成立 { return -1; // 有数据不一致 } else { return 0; // 全部一致测试通过 } }这里有个 C 语言的「初始化 vs 赋值」的区别我拆开给你看 __IO uint32_t Data32 0; // ① 声明 初始化只执行一次 ... while (Address FLASH_USER_END_ADDR) { Data32 *(__IO uint32_t *)Address; // ② 赋值每次循环都执行 ... } 两行 的含义完全不同 位置 语句 什么时候执行 赋的值是什么 函数开头 uint32_t Data32 0 只执行 1 次进入函数时 0初始化 while 里 Data32 *(地址) 每次循环都执行 FLASH 地址上读出的数据 时间线走一遍 进函数 Data32 0 ← 初始化为 0 第1次循环 Data32 FLASH[0x08020000] 的值 ← 读到 0x87654321 第2次循环 Data32 FLASH[0x08020004] 的值 ← 读到 0x87654321被覆盖了 第3次循环 Data32 FLASH[0x08020008] 的值 ← ... ... 所以循环里 Data32 每次都被新读出来的 FLASH 数据覆盖不是清零。它就是个「临时中转站」——从 FLASH 地址读一个 32 位字暂存到 Data32立刻跟 FLASH_TEST_DATA 比较比较完就丢掉、下次循环再装下一个字。 一个小知识顺便记 那个 __IO 就是 volatile易变意思是每次都用它都要老老实实去内存读不许编译器缓存优化。因为 Data32 装的是从硬件 FLASH 实时读来的值编译器不能自作聪明复用旧值。 一句话Data32 是「读一个、比一个、丢一个」的临时变量循环里每次装的是新读的数据不是 0。三Flash实验21 选项字节Option Bytes—— 芯片的「配置区」是什么芯片内部 Flash 末尾有一小块特殊区域F429 在0x1FFFC000专门存芯片的出厂配置参数跟存程序的普通 Flash 是两码事。能写什么它肚子里装的 4 样字段缩写管什么读保护RDP芯片能不能被外部读写保护WRP哪些扇区不能被写用户选项USER看门狗、复位脚、待机等开关掉电复位BOR电压掉到多少自动复位初始状态出厂默认全是关闭/最宽松——RDP 无保护、WRP 无保护、看门狗软件控制、BOR 默认阈值。特点改它要OB_Unlock解锁改完OB_Launch触发复位才生效不像普通 Flash 随便写。2读保护RDP—— 锁住「读」是什么保护你烧进芯片的程序不让外人用调试器/SW-D 把固件读出来防止抄板子、抄代码。能写什么就 3 个级别级别字节值效果能解除吗Level 00xAA无保护谁都能读——出厂默认Level 10x55调试口禁用读不出程序✅ 能解除但解除会擦掉整个 FlashLevel 20xCC永久锁死谁都读不了❌不可逆芯片变砖初始状态Level 0无保护。一句话Level 1 是产品出货标配防抄Level 2 是自毁按钮别碰。3 写保护WRP—— 锁住「写」是什么保护某些 Flash 扇区不被擦除/写入防止程序跑飞或 bug 把关键数据误擦掉。能写什么F429 有 12 个扇区Sector 0~11你可以挑任意几个扇区设写保护OBInit.WRPSector OB_WRP_SECTOR_5 | OB_WRP_SECTOR_6; // 保护 Sector5、6典型用途保护 Bootloader 区、参数存储区让它们只读谁也擦不掉。初始状态全部扇区无写保护WRP 全 0xFF。4一张图记住三者的关系选项字节0x1FFFC000配置区 ├── RDP 读保护 → 锁「读」防抄固件Level 0/1/2 ├── WRP 写保护 → 锁「写」防误擦挑扇区 ├── USER → 看门狗/复位脚开关 └── BOR → 掉电复位电压读保护和写保护的区别就一个字RDP 管别人读不读得到WRP 管自己会不会写坏。四st-link-utiltiy1上位机ST-LINK Utility爽在哪学习/调试点一下 Option Bytes 界面改 RDP、设 WRPApply就生效——不用写OB_Unlock → OBProgram → OB_Launch那一串代码救砖这才是它最大的价值。你代码写错了、误设 Level 1 锁死调试口上位机一键解除读保护擦掉 Flash把你救回来2但上位机替代不了写代码关键点上位机省的是「手动调试」的步骤不是「理解」。学习/调试验证 → 上位机点一下爽 产品量产/固件自保护 → 必须写代码因为量产时你不可能拿着 ST-LINK Utility 一个个芯片去点。产品要出厂自动加读保护就得在固件里写HAL_FLASH_OB_Unlock(); OBInit.RDPLevel OB_RDP_LEVEL_1; // 出厂自动设读保护 HAL_FLASHEx_OBProgram(OBInit); HAL_FLASH_OB_Launch();这段代码通常就烧录在固件里上电自动跑一次把你辛辛苦苦写的程序保护起来。3一句话*上位机是「遥控器」代码是「自动挡」**——调试用遥控器省事量产必须上自动挡。而且反过来也成立你懂了代码才看得懂上位机界面那些格子到底在操作什么。所以顺序应该是——先看懂底层你今天已经搞懂了 RDP/WRP/选项字节再用上位机偷懒而不是反过来只会上位机、不懂底层。这就是为什么我建议你代码 上位机两条腿一起走代码打通理解上位机拿来提效和救砖 4软件界面第一个是查看flash 数据起始地址第二个是查看多少字节第三个是以字半字字节形式显示。并且这个软件可以直接任意修改闪存内部的数据。5选项字节的操作勾选页一次点击四页同时勾选但这是f1系列的规则F1 vs F4 写保护根本不是一套规则F1你视频课看到的F4你的 F429Flash 组织单位页 Page1KB 或 2KB扇区 Sector16K/64K/128K扇区/页数量多几十到 256 页少就 12 个扇区写保护粒度1 bit 4 页中容量或 8 页大容量1 bit 1 扇区你看到的现象点一下勾 4 页 ✅点一下勾 1 个扇区为什么会有这个差别本质是「页/扇区的数量」决定了编码方式F1页小(1KB) → 页数多(64~256页) → WRP 只有 32 bit 不够用 → 只能 1 bit 管多页4 页或 8 页 F4扇区大(16K起) → 扇区少(12个) → WRP 12 bit 绰绰有余 → 1 bit 管 1 个扇区粒度精细所以记住这条学 F429一律用 F4 的规则别套 F1 的印象。这种 F1/F4 不一致的地方还不少最典型的就是写保护F1 按页、1 bit 管多页F4 按扇区、1 bit 管 1 扇区Flash 擦除F1 叫FLASH_ErasePage按页擦F4 叫HAL_FLASHEx_Erase按扇区擦而且扇区大小还不等电压范围参数F4 才有FLASH_VOLTAGE_RANGE_3这个东西F1 没有