STM32F103读保护RDP原理与J-Flash实操指南
2026/9/11 17:26:10 网站建设 项目流程

简介:本资源是一套面向STM32F103嵌入式开发者的Flash读写保护实战工程包,聚焦代码安全防护核心需求,适用于固件工程师、竞赛备赛学生及物联网设备研发人员。资源完整呈现读保护(RDP)设置与解除的全流程实现,涵盖选项字节编程、J-Flash工具操作逻辑及误锁恢复关键步骤,可直接移植到Keil MDK项目中验证。压缩包含272个文件,以46个C源码、56个头文件(.h)、46个编译中间文件(.o/.d/.crf)为主干,辅以hex/bin烧录镜像、uvprojx/uvoptx工程配置、bat自动化脚本及调试用axf/map文件,结构完整、开箱即用,总大小9.57MB。目前已有1688人学习下载,提供从底层寄存器配置到工具链协同的全链路参考,特别适合需快速掌握STM32 Flash安全机制、规避量产芯片误锁风险的中高级开发者。

1. STM32F103的读保护不是“上锁”,而是用RDP位触发芯片级访问熔断机制

很多人第一次遇到error: flash download failed - target dll has been cancelled,第一反应是J-Flash配置错了、ST-Link接触不良,甚至重装驱动——但真正卡住的,往往是早已被设为RDP Level 1的Flash。STM32F103的读保护(Readout Protection)本质不是软件密码,而是一组写入后不可逆的硬件熔丝位:一旦选项字节中RDP(Readout Protection)字段从0xAA(未启用)被编程为0x55(Level 1),芯片立即切断所有调试接口(SWD/JTAG)对主Flash的读取通路,同时禁止通过任何方式(包括串口ISP、DFU)读出已烧录代码。这不是“加密”,而是物理级访问阻断。它不防破解者用探针读取Flash裸片,但能有效阻止量产阶段被同行抄板、固件逆向或产线偷取程序。适用场景非常明确:小批量出货前最后一道安全工序、Bootloader与Application分离部署时保护引导区、或客户要求提供“不可提取固件”的交付物。注意:RDP Level 1下仍允许擦除和重新编程Flash(即可以升级新固件),但无法读回旧代码;而Level 2则彻底锁定调试接口,连擦除都不可行——本文聚焦可逆的Level 1操作,这是工程实践中95%以上真实需求的交点。

2. RDP位的物理位置、写入约束与选项字节结构解析

2.1 选项字节存储区在Flash中的精确地址与扇区归属

STM32F103的选项字节(Option Bytes)并非独立存储器,而是映射在主Flash末尾的特定扇区中。根据Reference Manual RM0008第3.4节,F103系列(512KB Flash型号如STM32F103ZET6)的选项字节位于地址0x1FFFF800起始的16字节区域,该区域属于Flash的最后一个扇区(Sector 7,大小为2KB)。关键点在于:擦除选项字节必须整扇区擦除,不能只擦这16字节。这意味着执行FLASH_EraseOptionBytes()时,实际会擦除整个Sector 7(0x1FFFF000–0x1FFFF7FF),但只有0x1FFFF800–0x1FFFF80F这16字节被定义为选项字节,其余空间为保留位。若该扇区已存有用户数据(如EEPROM模拟区),必须提前备份——这是新手最常踩的坑:擦完选项字节,连带把校准参数全清了。

2.2 RDP字段的二进制编码与状态机约束

选项字节共16字节,其中RDP控制位位于偏移0x00处的OPTBYTE[0],其有效值仅有两个:

  • 0xAA:RDP Level 0,无保护,可自由读写Flash
  • 0x55:RDP Level 1,启用读保护,调试接口禁读主Flash

注意0xCC是非法值,写入会导致芯片永久锁定(RDP Level 2),且无法恢复。官方文档明确警告:RDP Level 2是“one-time programmable”,一旦写入,唯一解法是整片报废。因此所有工具(J-Flash、ST-Link Utility、OpenOCD)在写RDP时都会强制校验输入值,禁止直接输入0xCC。实际开发中,应始终用宏定义替代硬编码:

#define RDP_LEVEL_0 ((uint8_t)0xAA) #define RDP_LEVEL_1 ((uint8_t)0x55)

避免手误。

2.3 写入选项字节的四步原子操作流程

RDP位写入不是普通内存写,需严格遵循时序,否则触发FLASH_ERROR_PG(编程错误)。以标准库v3.5为例,完整流程如下:

  1. 解锁Flash控制寄存器:调用FLASH_Unlock(),写入KEY1=0x45670123和KEY2=0xCDEF89AB到FLASH_KEYR
  2. 解锁选项字节区:调用FLASH_OB_Unlock(),写入KEY1/KEY2到FLASH_OPTKEYR
  3. 擦除选项字节扇区:调用FLASH_EraseOptionBytes(),等待FLASH_FLAG_EOP置位
  4. 编程RDP值:调用FLASH_ProgramOptionByteData(0x1FFFF800, RDP_LEVEL_1),写入0x55到首地址

关键逻辑说明:步骤3的擦除是必须的,因为选项字节是NOR Flash结构,写入前必须擦除(NOR特性:bit只能1→0,擦除才变回1)。若跳过此步直接编程,FLASH_ProgramOptionByteData会返回FLASH_ERROR_PG,J-Flash界面则报cannot load flash device description——这正是热词中高频出现的错误根源。

2.4 验证RDP状态的两种可靠方法

仅靠J-Flash界面上的“Read Protection: Enabled”提示不可信。必须用底层指令验证:

  • 方法一(调试器读取):在Keil MDK中打开Memory Browser,地址栏输入0x1FFFF800,查看首字节是否为0x55。若显示??或读取超时,说明RDP已生效且调试接口被阻断。
  • 方法二(代码自检):在未启用RDP的固件中加入以下函数,烧录后运行:
uint8_t GetRDPStatus(void) { uint32_t rdp_addr = 0x1FFFF800; // 注意:此处读取的是当前生效的选项字节,非Flash中存储的原始值 return *(volatile uint8_t*)rdp_addr; } // 调用后打印:if (GetRDPStatus() == 0x55) printf("RDP Level 1 ACTIVE\n");

重要提示:该函数只能在RDP未启用时运行。一旦RDP设为0x55,此函数将读取到0xFF(因硬件阻断),故仅用于设保前校验。

3. 使用J-Flash v9.70完成RDP设置与解除的实操细节

3.1 J-Flash工程配置的关键参数设置

J-Flash v9.70(对应热词--------------------------- j-flash v9.70 error ---------------------------)对F103的支持依赖正确的Device文件。若打开工程时报the current flash utility is out dated,说明Device文件版本过旧。解决路径:

  1. 下载最新版J-Flash(Segger官网),安装时勾选“STM32 Device Support”
  2. 在J-Flash菜单Options → Project Settings → Devices中,确认Device选择为STM32F103ZE(按实际型号选,如F103C8选STM32F103C8
  3. Programming页签中,必须勾选Erase sectors used by option bytes—— 此选项对应前述“整扇区擦除”要求,未勾选将导致RDP写入失败。

3.2 设置RDP Level 1的六步操作序列

以下是J-Flash GUI中零失误的操作流(适配所有v9.x版本):

  1. 连接ST-Link,点击Target → Connect,确认连接成功(右下角显示Connected
  2. 点击Options → Production Programming,弹出对话框
  3. Option Bytes区域,找到Read protection下拉框,选择Level 1
  4. 关键动作:勾选Erase all sectors before programming(确保选项字节扇区被擦)
  5. 点击Program按钮,J-Flash自动执行:擦除Sector 7 → 编程RDP=0x55 → 校验
  6. 完成后点击Target → Disconnect,再重新连接——若连接成功但无法读取Flash内容(如Keil中View → Memory Windows输入0x08000000显示全FF),即RDP生效

参数说明表:J-Flash核心选项含义
| 选项名称 | 默认值 | 必须修改? | 说明 |
|----------|--------|------------|------|
|Erase sectors used by option bytes| Off | 是 | 强制擦除Sector 7,否则RDP写入失败 |
|Verify after programming| On | 是 | 校验RDP值是否写入0x1FFFF8000x55|
|Reset target after programming| On | 推荐 | 复位后RDP立即生效,避免缓存干扰 |
|Use fast programming| On | 否 | 对选项字节无效,可关闭以提升稳定性 |

3.3 解除RDP Level 1的“单次擦除”操作

解除RDP不是“写回0xAA”,而是触发芯片内部的RDP复位机制:当检测到选项字节扇区被整扇区擦除时,硬件自动将RDP恢复为0xAA。因此解除流程比设置更简单:

  1. 在J-Flash中,Options → Production Programming
  2. Option Bytes区域,Read protection选择Disabled
  3. 唯一必需操作:勾选Erase all sectors before programming(再次擦除Sector 7)
  4. 点击Program,J-Flash执行擦除→自动恢复RDP→校验
  5. 重新连接Target,此时可正常读取Flash内容

为什么不能直接编程0xAA?
因为RDP位具有写保护逻辑:硬件只允许从0xAA0x55(设保),或通过整扇区擦除触发0x550xAA(解保)。直接编程0xAA会被忽略,J-Flash日志显示Programming skipped: value already programmed

3.4 常见错误日志定位与修复

当J-Flash报错时,日志窗口(View → Log Window)是第一排查入口:

  • Error: Flash download failed - target dll has been cancelled90%概率是RDP已启用且未擦除扇区。解决方案:先执行3.3节解除流程,再重试。
  • Error: Cannot load flash device description:Device文件缺失或损坏。重装J-Flash并勾选Device Support。
  • Warning: Option bytes not programmedErase sectors used by option bytes未勾选。
  • Verification failed at address 0x1FFFF800:ST-Link供电不足或接触不良,检查VCC引脚电压是否稳定在3.3V。

4. 基于标准库v3.50的RDP操作代码移植与防误操作加固

4.1 将RDP操作封装为可复用函数模块

直接使用标准库的stm32f10x_flash.c存在风险:FLASH_EraseOptionBytes()未做RDP状态预检,若在RDP已启用状态下调用,函数会卡死。因此需封装健壮版本:

#include "stm32f10x_flash.h" #include "stm32f10x_rcc.h" // 检查当前RDP状态(仅在RDP未启用时有效) FlagStatus IsRDPEnabled(void) { if (*(volatile uint8_t*)0x1FFFF800 == 0x55) { return SET; // RDP Level 1 active } return RESET; } // 安全设置RDP Level 1(调用前确保IsRDPEnabled()==RESET) FLASH_Status FLASH_SetRDP_Level1(void) { FLASH_Status status = FLASH_COMPLETE; // 1. 解锁Flash FLASH_Unlock(); FLASH_OB_Unlock(); // 2. 擦除选项字节扇区(Sector 7) status = FLASH_EraseOptionBytes(); if (status != FLASH_COMPLETE) goto ERROR; // 3. 编程RDP=0x55 status = FLASH_ProgramOptionByteData(0x1FFFF800, 0x55); if (status != FLASH_COMPLETE) goto ERROR; // 4. 启动系统复位使RDP生效 NVIC_SystemReset(); ERROR: FLASH_Lock(); FLASH_OB_Lock(); return status; } // 解除RDP(自动触发硬件复位) FLASH_Status FLASH_UnlockRDP(void) { FLASH_Status status = FLASH_COMPLETE; FLASH_Unlock(); FLASH_OB_Unlock(); // 关键:擦除Sector 7,硬件自动恢复RDP=0xAA status = FLASH_EraseOptionBytes(); FLASH_Lock(); FLASH_OB_Lock(); return status; }

逻辑说明FLASH_SetRDP_Level1()末尾调用NVIC_SystemReset()是必须的。因为RDP状态变更需复位才能生效,否则后续Flash操作可能异常。而FLASH_UnlockRDP()无需编程,仅擦除即可,符合硬件设计规范。

4.2 在Bootloader中嵌入RDP状态检查防误刷

若项目采用双Bank Bootloader(如Application在0x08004000,Bootloader在0x08000000),必须防止用户误刷RDP已启用的Application导致Bootloader失效。在Bootloader启动时加入检查:

void CheckRDPAndJump(void) { if (IsRDPEnabled() == SET) { // RDP已启用,禁止跳转至Application,进入安全模式 GPIO_InitTypeDef GPIO_InitStruct; RCC_APB2PeriphClockCmd(RCC_APB2PERIPH_GPIOA, ENABLE); GPIO_InitStruct.GPIO_Pin = GPIO_Pin_5; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStruct); while(1) { // LED快闪报警 GPIO_SetBits(GPIOA, GPIO_Pin_5); Delay_ms(100); GPIO_ResetBits(GPIOA, GPIO_Pin_5); Delay_ms(100); } } // 否则正常跳转至Application JumpToApplication(0x08004000); }

此设计确保:即使Application被设RDP,Bootloader仍可运行并给出物理告警,避免设备变砖。

4.3 Keil工程中规避keilkill.bat引发的RDP误操作

项目正文提及keilkill.bat,这是Keil编译后自动清理临时文件的脚本。若用户将RDP设置代码误放入main()而非单独调试工程,keilkill.bat在每次编译后执行,可能导致RDP被反复擦写。正确做法

  • 创建独立工程RDP_Configurator,仅含RDP设置函数和main()调用FLASH_SetRDP_Level1()
  • 在Keil中,Project → Options → Output取消勾选Create HEX File(避免生成无意义HEX)
  • Debug页签中,Settings → Flash Download仅勾选STM32F10x Flash算法,禁用Run to main(),改为手动全速运行
  • 烧录后立即断电,防止复位后代码二次执行

终极技巧:用J-Flash命令行固化RDP流程
对于产线批量操作,避免GUI误触。J-Flash提供命令行工具JFlash.exe

JFlash.exe -openprj STM32F103_RDP.jflash -auto -exitonerror

其中STM32F103_RDP.jflash是预配置好的工程文件,已设定RDP Level 1及擦除选项。将此命令集成到批处理脚本,实现一键设保。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询