一、选项字节与读写保护

1.1 为什么要设置读写保护
防止内部FLASH中的程序被非法读取。
在实际发布的产品中,STM32芯片的内部FLASH存储了控制程序。然而,如果不对内部FLASH采取任何保护措施,用户可以使用下载器直接读取其内容,并将其导出为bin或hex文件格式的代码副本。这可能导致别有用心的厂商利用该方法复制产品。为了防止这种情况,STM32芯片提供了多种方式来保护内部FLASH中的程序,防止其被非法读取。但在默认情况下,这些保护功能是未开启的。若要启用这些保护功能,需要修改内部FLASH选项字节(Option Bytes)中的相关配置。
1.2 选项字节的内容
选项字节是一段特殊的 FLASH 空间, STM32 芯片会根据它的内容进行读写保护配置,选项字节的构成如下图:

STM32F103 系列芯片的选项字节有 8 个配置项,即上表中的 USER、 RDP、 DATA0/1 及 WRP0/1/2/3,而表中带 n 的同类项是该项的反码,即 nUSER 的值等于 (~USER)、 nRDP 的值等于 (~RDP), STM32利用反码来确保选项字节内容的正确性。
选项字节的 8 个配置项具体的数据位配置说明如下:

当nRST_STDBY设置为0时:只要成功执行进入待机模式指令,器件就会复位,而非进入待机模式。

1.2.1 RDP
设置读保护:修改选项字节的 RDP 位的值可设置内部 FLASH 为以下保护级别:
-
0xA5:无保护;
这是 STM32 的默认保护级别。在STM32芯片的默认保护级别下,内部FLASH没有任何读保护措施,读取其内容没有任何限制。这意味着第三方可以使用调试器等工具轻松获取芯片FLASH中存储的程序,并将其以bin或hex格式下载到另一块STM32芯片中。结合PCB抄板技术,他们可以轻易地复制出相同的产品。
-
其它值:使能读保护;
将RDP(Read Protection Level,读保护级别)配置为除0xA5以外的任意数值,都会启用读保护功能。在这种情况下,如果通过调试功能(例如使用下载器或仿真器)或者从内部SRAM启动,将无法对内部FLASH进行任何访问(包括读取、写入和擦除操作)。然而,如果STM32是从内部FLASH启动的,它仍然允许对内部FLASH进行任意访问。
换句话说,任何试图从外部访问内部FLASH内容的操作都会被禁止。例如,无法通过下载器读取其内容,也无法编写一个从内部SRAM启动的程序来读取内部FLASH,因为这种操作会被阻止。相反,如果芯片是从内部FLASH启动的程序本身访问内部FLASH(例如程序中包含对内部FLASH某个地址的读取操作),则可以正常进行,不会受到限制。
当STM32芯片被设置为读保护后,内部FLASH的前4KB空间会被强制加上写保护。这意味着,即使是从FLASH启动的程序,也无法对这4KB空间进行擦除或写入操作。然而,对于前4KB以外的FLASH空间,读保护不会限制对其的擦除或写入操作。利用这一特性,可以编写IAP(In-Application Programming,应用程序内编程)代码来更新FLASH中的程序。
IAP的工作原理是通过某种通信接口(如UART、I2C、SPI等)接收外部设备发送的程序更新数据,然后利用内部FLASH的擦写功能将这些数据烧录到FLASH的指定区域(通常是前4KB之外的区域),从而实现应用程序的更新。这种原理类似于串口ISP(In-System Programming,系统内编程)程序下载功能。不过,ISP的接收数据和更新代码由ST公司提供,并存放在系统的存储区域;而IAP代码由用户自行编写,存放在用户自定义的FLASH区域,并且通信方式可以根据用户需求定制,只要能够接收数据即可。
解除读保护:当需要解除芯片的读保护时,要把选项字节的 RDP 位重新设置为 0xA5。
当解除读保护时,芯片会自动触发擦除主FLASH存储器的全部内容。这意味着解除保护后,原内部FLASH中的代码会丢失,从而防止降级后原内容被读取。
这种机制是为了确保在解除保护后,原有的程序代码不会被非法读取或复制。
如果设置了读保护后在代码中没有解除读保护,可以通过把(解除读保护的)代码写入到RAM中,在RAM中运行代码,进而解除读保护。
芯片被配置成读保护后根据不同的使用情况,访问权限如下图:

RAM自举:程序存储在STM32的内部SRAM中,并从SRAM中启动执行;
ROM自举:程序存储在STM32的系统存储器(System Memory)中,并从该区域启动。
1.2.2 Datax
Data字段是供用户自定义使用的存储区域,主要用于存储一些需要在掉电后仍能保留的配置信息或状态标志。
作用:
-
1、存储存储用户自定义数据:
-
Data字段通常包含两个字节(Data0和Data1),位于选项字节的特定地址(例如0x1FFF F804和0x1FFF F806)。这些字节可以存储用户定义的任意数据。
-
例如,可以用于存储固件版本号、设备配置参数或标志位,这些数据在设备掉电后仍能保留。
-
-
2、用于固件升级:
- 在固件升级过程中,Data字段可以用来存储升级标志位或状态信息,帮助设备在重启后判断是否需要继续执行升级操作。
-
3、灵活的配置用途:
- 由于Data字段的内容由用户定义,因此可以根据具体应用场景灵活使用。例如,可以用于存储设备的唯一标识符、校准参数或其他重要信息。
1.2.3 WRP

使用选项字节的 WRP0/1/2/3 可以设置主 FLASH 的写保护,防止它存储的程序内容被修改。
设置写保护:
写保护的配置通常以4KB为单位进行设置。除了WRP3的最后一位较为特殊外,每个WRP选项字节的每一位都对应一个4KB的FLASH区域,用于控制该区域的写访问权限。
将对应的WRP位设置为0,即可对该4KB区域启用写保护。启用写保护后,被保护的主FLASH区域中的内容将无法通过任何方式被擦除或写入。
例如WRP0设置为11111110,那么页0、页1就不能写;为11111101,那么页2、页3就不能写。
需要注意的是,写保护仅限制写操作,不影响读操作的权限,读操作的权限则由前面介绍的读保护设置决定。
设置写保护:
解除写保护的过程是写保护设置的逆操作。将对应的WRP位设置为1,即可解除对应4KB区域的写保护。
解除写保护后,主FLASH中的内容不会像解除读保护那样丢失,而是会保持原样,不会发生任何改变。
二、修改选项字节的过程
根据前面的说明,修改选项字节的内容可以修改读写保护的配置。然而,选项字节复位后的默认状态是==可读但被写保护==的。这与FLASH_CR寄存器的访问限制类似。
因此,要修改选项字节,需要先对FLASH_OPTKEYR寄存器写入解锁编码。由于修改选项字节时也需要访问FLASH_CR寄存器,因此还需要对FLASH_KEYR寄存器写入解锁编码。
修改选项字节的整个过程总结如下:
-
1、解除 FLASH_CR 寄存器的访问限制。
-
先往 FPEC 键寄存器(FLASH_KEYR) 中写入 KEY1 = 0x45670123。
-
再往 FPEC 键寄存器(FLASH_KEYR) 中写入 KEY2 = 0xCDEF89AB。
-
-
2、解除对选项字节的访问限制。
-
先往 闪存 OPTKEY 寄存器(FLASH_OPTKEYR) 中写入 KEY1 = 0x45670123。
-
再往 闪存 OPTKEY 寄存器(FLASH_OPTKEYR) 中写入 KEY2 = 0xCDEF89AB。
-
-
3、配置 FLASH_CR 的 OPTPG 位,准备修改选项字节。
-
4、直接使用指针操作修改选项字节的内容,根据需要修改 RDP、 WRP 等内容。
-
5、对于读保护的解除,由于它会擦除 FLASH 的内容,所以需要检测状态寄存器标志位以确认FLASH 擦除操作完成。
-
6、若是设置读保护及其解除,需要给芯片重新上电复位 ,以使新配置的选项字节生效;对于设置写保护及其解除,需要给芯片进行系统复位,以使新配置的选项字节生效。
- 解除读保护后需要给系统上电复位,如果是使用系统复位(NVIC_SystemReset)会出现不可预知的情况。
三、实验:设置读写保护及解除
c
// flash_rdprotect.c文件
#include "flash_rdprotect.h"
void Write_Protect(void)
{
FLASH_Unlock();
FLASH_EraseOptionBytes(); // 写之前要先擦除
FLASH_EnableWriteProtection(FLASH_WRProt_AllPages);
NVIC_SystemReset(); // 产生系统复位
}
void Write_Protect_Disable(void)
{
FLASH_Unlock();
FLASH_EraseOptionBytes();
FLASH_EnableWriteProtection(0x00000000);
NVIC_SystemReset(); // 产生系统复位
}
void Read_Protect(void)
{
FLASH_Unlock();
FLASH_EraseOptionBytes();
FLASH_ReadOutProtection(ENABLE);
}
void Read_Protect_Disable(void)
{
FLASH_Unlock();
FLASH_EraseOptionBytes();
FLASH_ReadOutProtection(DISABLE);
}
-
为什么设置写保护写的值是0xffffffff、解除写保护写的值是0x00000000:
FLASH_EnableWriteProtection函数中会对传进来的参数进行取反,将WRPx中的位设置为1则是不实施写保护。
c
// flash_rdprotect.h文件
#ifndef __FLASH_RDPROTECT_H
#define __FLASH_RDPROTECT_H
#include "stm32f10x.h"
#define WRITE_PROTECT
void Write_Protect(void);
void Write_Protect_Disable(void);
void Read_Protect(void);
void Read_Protect_Disable(void);
#endif /* __FLASH_RDPROTECT_H */
c
// main.c文件
#include "stm32f10x.h"
#include "bsp_key.h"
#include "usart.h"
#include "flash_rdprotect.h"
int main(void)
{
KEY1_GPIO_Config();
KEY2_GPIO_Config();
USART_Config();
#ifdef WRITE_PROTECT
printf("写保护状态:0x%08x \r\n", FLASH_GetWriteProtectionOptionByte());
#else
printf("写保护状态:0x%x \r\n", FLASH_GetReadOutProtectionStatus());
#endif
while(1)
{
if(KEY_Scan(KEY1_PROT, KEY1_Pin) == KEY_ON)
{
#ifdef WRITE_PROTECT
printf("即将实施写保护 \r\n");
Write_Protect();
#else
printf("即将实施读保护 \r\n");
Read_Protect();
#endif
}
if(KEY_Scan(KEY2_PROT, KEY2_Pin) == KEY_ON)
{
#ifdef WRITE_PROTECT
printf("即将解除写保护 \r\n");
Write_Protect_Disable();
#else
printf("即将解除读保护 \r\n");
Read_Protect_Disable();
#endif
}
}
}
-
FLASH_EraseOptionBytes()函数会将16个字节都擦除。如果想只擦除某一个字节:将WRP全部清除掉再重新配置。
-
FLASH_GetWriteProtectionOptionByte() 函数的返回值类型为 uint64_t,表示的是 WRP0~3 的值。当未设置写保护时,返回值为 0xFFFFFFFF;而当所有区域均设置为写保护时,返回值为 0x00000000。
四、将代码修改为RAM自举
-
1、添加"Project Targets";
-
2、在C/C++中添加宏VECT_TAB_SRAM;
此宏在SystemInit函数中。
注意,两个宏之间用","分开。
-
3、打开.sct文件;
-
4、(根据实际需要)调整ROM空间大小;

修改前的空间大小。

修改后的空间大小。
- 5、下载器配置;


- 6、添加ini文件;

- 七、将Project Targets更改为新建的Targets。