49. 设置FLASH的读写保护及解除

本章参考资料:《STM32F10x闪存编程参考手册》《STM32F10x 中文参考手册》、《STM32F10x规格书》、《Cortex-M3权威指南》。

49.1. 选项字节与读写保护

在实际发布的产品中,在STM32芯片的内部FLASH存储了控制程序,如果不作任何保护措施的话,可以使用下载器直接把内部FLASH的内容读取回来, 得到bin或hex文件格式的代码拷贝,别有用心的厂商会利用该方法山寨产品。为此,STM32芯片提供了多种方式保护内部FLASH的程序不被非法读取, 但在默认情况下该保护功能是不开启的,若要开启该功能,需要改写内部FLASH选项字节(Option Bytes)中的配置。

49.1.1. 选项字节的内容

选项字节是一段特殊的FLASH空间,STM32芯片会根据它的内容进行读写保护配置, 选项字节的构成见表 选项字节的构成

选项字节的构成

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

选项字节的8个配置项具体的数据位配置说明见表 选项字节具体的数据位配置说明

选项字节具体的数据位配置说明

我们主要讲解选项字节配置中的RDP位和WRP位,它们分别用于配置读保护和写保护。

49.1.2. RDP读保护

修改选项字节的RDP位的值可设置内部FLASH为以下保护级别:

  • 0xA5:无保护

这是STM32的默认保护级别,它没有任何读保护,读取内部FLASH的内容都没有任何限制。也就是说,第三方可以使用调试器等工具, 获取该芯片FLASH中存储的程序,然后可以把获得的程序以bin和hex的格式下载到另一块STM32芯片中,加上PCB抄板技术,轻易复制出同样的产品。

  • 其它值:使能读保护

把RDP配置成除0xA5外的任意数值, 都会使能读保护。在这种情况下, 若使用调试功能(使用下载器、仿真器)或者从内部SRAM自举时都不能对内部FLASH作任何访问(读写、擦除都被禁止); 而如果STM32是从内部FLASH自举时,它允许对内部FLASH的任意访问。也就是说,任何尝试从外部访问内部FLASH内容的操作都被禁止。 例如,无法通过下载器读取它的内容,或编写一个从内部SRAM启动的程序,若该SRAM启动的程序读取内部FLASH,会被禁止。 而如果是芯片原本的内部FLASH程序自己访问内部FLASH(即从FLASH自举的程序),是完全没有问题的,例如芯片本身的程序, 若包含有指针对内部FLASH某个地址进行的读取操作,它能获取正常的数据。

另外,被设置成读保护后,FLASH前4K字节的空间会强制加上写保护,也就是说,即使是从FLASH启动的程序,也无法擦写这4K字节空间的内容; 而对于前4K字节以外的空间,读保护并不影响它对其它空间的擦除/写入操作。利用这个特性, 可以编写IAP代码(In Application Program)更新FLASH中的程序, 它的原理是通过某个通讯接口获取将要更新的程序内容,然后利用内部FLASH擦写操作把这些内容烧录到自己的内部FLASH中,实现应用程序的更新, 该原理类似串口ISP程序下载功能,只不过ISP这个接收数据并更新的代码由ST提供,且存放在系统存储区域,而IAP是由用户自行编写的, 存放在用户自定义的FLASH区域,且通讯方式可根据用户自身的需求定制,如IIC、SPI等,只要能接收到数据均可。

  • 解除保护

当需要解除芯片的读保护时,要把选项字节的RDP位重新设置为0xA5。在解除保护前,芯片会自动触发擦除主FLASH存储器的全部内容, 即解除保护后原内部FLASH的代码会丢失,从而防止降级后原内容被读取到。

芯片被配置成读保护后根据不同的使用情况,访问权限不同,总结。

读保护模式下不同区域的访问限制

49.1.3. WRP写保护

使用选项字节的WRP0/1/2/3可以设置主FLASH的写保护,防止它存储的程序内容被修改。

  • 设置写保护

写保护的配置一般以4K字节为单位,除WRP3的最后一位比较特殊外,每个WRP选项字节的一位用于控制4K字节的写访问权限, 把对应WRP的位置0即可把它匹配的空间加入写保护。被设置成写保护后,主FLASH中的内容使用任何方式都不能被擦除和写入, 写保护不会影响读访问权限,读访问权限完全由前面介绍的读保护设置限制。

  • 解除写保护

解除写保护是逆过程,把对应WRP的位置1即可把它匹配的空间解除写保护。解除写保护后,主FLASH中的内容不会像解读保护那样丢失,它会被原样保留。

49.2. 修改选项字节的过程

根据前面的说明,修改选项字节的内容可修改读写保护配置,不过选项字节复位后的默认状态是始终可以读但被写保护的, 因此它具有类似前面《读写内部FLASH》章节提到的FLASH_CR寄存器的访问限制,要想修改,需要先对FLASH_OPTKEYR寄存器写入解锁编码。 由于修改选项字节时也需要访问FLASH_CR寄存器,所以同样也要对FLASH_KEYR写入解锁编码。

修改选项字节的整个过程总结如下:

  1. 解除FLASH_CR寄存器的访问限制

  • 往FPEC键寄存器 FLASH_KEYR中写入 KEY1 = 0x45670123

  • 再往FPEC键寄存器 FLASH_KEYR中写入 KEY2 = 0xCDEF89AB

  1. 解除对选项字节的访问限制

  • 往FLASH_OPTKEYR中写入 KEY1 = 0x45670123

  • 再往FLASH_OPTKEYR中写入 KEY2 = 0xCDEF89AB

  1. 配置FLASH_CR的OPTPG位,准备修改选项字节

  2. 直接使用指针操作修改选项字节的内容,根据需要修改RDP、WRP等内容

  3. 对于读保护的解除,由于它会擦除FLASH的内容,所以需要检测状态寄存器标志位以确认FLASH擦除操作完成。

  4. 若是设置读保护及其解除,需要给芯片重新上电复位,以使新配置的选项字节生效;对于设置写保护及其解除, 需要给芯片进行系统复位,以使新配置的选项字节生效。

49.3. 操作选项字节的库函数

为简化编程,STM32标准库提供了一些库函数,它们封装了前面介绍的修改选项字节时的操作过程。

49.3.1. 选项字结构体定义

对选项字节结构体定义的见 代码清单:保护及解除-1

代码清单:保护及解除-1选项字节结构体的定义(stm32f10x.h文件)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
/**
* @brief 选项字节结构体
*/
typedef struct {
    __IO uint16_t RDP;  /*RDP及nRDP*/
    __IO uint16_t USER; /*USER及nUSER,下面类似*/
    __IO uint16_t Data0;
    __IO uint16_t Data1;
    __IO uint16_t WRP0;
    __IO uint16_t WRP1;
    __IO uint16_t WRP2;
    __IO uint16_t WRP3;
} OB_TypeDef;

/*强制转换为选项字节结构体指针*/
#define OB              ((OB_TypeDef *) OB_BASE)
/*选项字节基地址 */
#define OB_BASE         ((uint32_t)0x1FFFF800)

标准库中定义的选项字节结构体,包含了RDP、USER、DATA0/1及WRP0/1/2/3这些内容,每个结构体成员指向选项字节对应选项的原始配置码及反码。不过, 根据手册中的说明可了解到,当向选项字节的这些地址写入配置时,它会自动取低位字节计算出高位字节的值再存储,即自动取反码,非常方便。 例如程序中执行操作给结构体成员WRP0赋值为0x0011时,最终它会自动写入0xEE11(0xEE是0x11的反码)。最后, 从OB_BASE宏的定义可以确认它所指向的正是前面介绍的选项字节基地址,说明若在程序中使用该结构体赋值,会直接把内容写入到选项字节地址对应的空间中。

49.3.2. 设置写保护及解除

库文件提供了FLASH_EnableWriteProtection函数,可用于设置写保护及解除,代码清单:保护及解除-2

代码清单:保护及解除-2 设置写保护及解除(stm32f10x_flash.c文件)
 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
#define RDP_Key                  ((uint16_t)0x00A5)

/**
* @brief  使能或关闭读保护
* @note   若芯片本身有对选项字节进行其它操作,
            请先读出然后再重新写入,因为本函数会擦除所有选项字节的内容

* @param  Newstate: 使能(ENABLE)或关闭(DISABLE)
* @retval FLASH Status: 可能的返回值: FLASH_ERROR_PG,
*         FLASH_ERROR_WRP, FLASH_COMPLETE or FLASH_TIMEOUT.
*/
FLASH_Status FLASH_ReadOutProtection(FunctionalState NewState)
{
    FLASH_Status status = FLASH_COMPLETE;
    /* 检查参数 */
    assert_param(IS_FUNCTIONAL_STATE(NewState));
    status = FLASH_WaitForLastOperation(EraseTimeout);
    if (status == FLASH_COMPLETE) {
        /* 写入选项字节解锁码 */
        FLASH->OPTKEYR = FLASH_KEY1;
        FLASH->OPTKEYR = FLASH_KEY2;
        FLASH->CR |= CR_OPTER_Set; //擦除选项字节
        FLASH->CR |= CR_STRT_Set;  //开始擦除
        /* 等待上一次操作完毕 */
        status = FLASH_WaitForLastOperation(EraseTimeout);
        if (status == FLASH_COMPLETE) {
            /* 若擦除操作完成,复位 OPTER 位 */
            FLASH->CR &= CR_OPTER_Reset;
            /* 准备写入选项字节 */
            FLASH->CR |= CR_OPTPG_Set;
            if (NewState != DISABLE) {
                OB->RDP = 0x00;//写入非0xA5值,进行读保护
            } else {
                OB->RDP = RDP_Key;  //写入0xA5,解除读保护
            }
            /* 等待上一次操作完毕 */
            status = FLASH_WaitForLastOperation(EraseTimeout);

            if (status != FLASH_TIMEOUT) {
                /* 若操作完毕,复位 OPTPG 位 */
                FLASH->CR &= CR_OPTPG_Reset;
            }
        } else {
            if (status != FLASH_TIMEOUT) {
                /* 复位 OPTER 位 */
                FLASH->CR &= CR_OPTER_Reset;
            }
        }
    }
    /* 返回设置结果 */
    return status;
}

该函数的输入参数可选FLASH_WRProt_Pages0to1至FLASH_WRProt_Pages62to511等宏,该参数用于指定要对哪些页进行写保护。

从该宏的定义方式可了解到,它用一个32位的数值表示WRP0/1/2/3,而宏名中的页码使用数据位1来在WRP0/1/2/3中对应的位作掩码指示。 如控制页0至页1的宏FLASH_WRProt_Pages0to1,它由WRP0最低位控制,所以其宏值为0x00000001(bit0为1);类似地, 控制页2至页3的宏FLASH_WRProt_Pages2to3,由WRP0的bit1控制,所以其宏值为0x00000002(bit1为1)。

理解了输入参数宏的结构后,即可分析函数中的具体代码。其中最核心要理解的是对输入参数的运算,输入参数FLASH_Pages自身会进行取反操作, 从而用于指示要保护页的宏对应的数据位会被置0,而在选项字节WRP中,被写0的数据位对应的页会被保护。FLASH_Pages取反后的值被分解成WRP0/1/2/3_Data四个部分, 所以在后面的代码中,可以直接把WRP0/1/2/3_Data变量的值写入到选项字节中。关于这部分运算,您可以亲自代入几个宏进行运算,加深理解。

得到数据后,函数开始对FLASH_OPTKEYR寄存器写入解锁码,然后操作FLASH_CR寄存器的OPTPG位准备写入,写入的时候它直接往指向选项字节的结构体OB赋值, 如OB->WRP0 =WRP0_Data,注意在这部分写入的时候,根据前面的运算,可知WRP0_Data中只包含了WRP0的内容, 而nWRP0的值为0,这个nWRP0的值最终会由芯片自动产生。 代码后面的WRP1/2/3操作类似。

仔细研究了这个库函数后,可知它内部并没有对FLASH_CR的访问作解锁操作,所以在调用本函数前,需要先调用FLASH_Unlock解锁。另外,库文件中并没有直接的函数用于解除保护, 但实际上解除保护也可以使用这个函数来处理,例如使用输入参数0来调用函数FLASH_EnableWriteProtection(0),根据代码的处理,它最终会向WRP0/1/2/3选项字节全写入1, 从而达到整片FLASH解除写保护的目的。

49.3.3. 设置读保护及解除

类似地,库文件中提供了函数FLASH_ReadOutProtection 来设置FLASH的读保护及解除,见 代码清单:保护及解除-3

代码清单:保护及解除-3 设置读保护及解除(stm32f10x_flash.c文件)
 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
53
#define RDP_Key                  ((uint16_t)0x00A5)


/**
* @brief  使能或关闭读保护
* @note   若芯片本身有对选项字节进行其它操作,
            请先读出然后再重新写入,因为本函数会擦除所有选项字节的内容

* @param  Newstate: 使能(ENABLE)或关闭(DISABLE)
* @retval FLASH Status: 可能的返回值: FLASH_ERROR_PG,
*         FLASH_ERROR_WRP, FLASH_COMPLETE or FLASH_TIMEOUT.
*/
FLASH_Status FLASH_ReadOutProtection(FunctionalState NewState)
{
    FLASH_Status status = FLASH_COMPLETE;
    /* 检查参数 */
    assert_param(IS_FUNCTIONAL_STATE(NewState));
    status = FLASH_WaitForLastOperation(EraseTimeout);
    if (status == FLASH_COMPLETE) {
        /* 写入选项字节解锁码 */
        FLASH->OPTKEYR = FLASH_KEY1;
        FLASH->OPTKEYR = FLASH_KEY2;
        FLASH->CR |= CR_OPTER_Set; //擦除选项字节
        FLASH->CR |= CR_STRT_Set;  //开始擦除
        /* 等待上一次操作完毕 */
        status = FLASH_WaitForLastOperation(EraseTimeout);
        if (status == FLASH_COMPLETE) {
            /* 若擦除操作完成,复位 OPTER 位 */
            FLASH->CR &= CR_OPTER_Reset;
            /* 准备写入选项字节 */
            FLASH->CR |= CR_OPTPG_Set;
            if (NewState != DISABLE) {
                OB->RDP = 0x00;//写入非0xA5值,进行读保护
            } else {
                OB->RDP = RDP_Key;  //写入0xA5,解除读保护
            }
            /* 等待上一次操作完毕 */
            status = FLASH_WaitForLastOperation(EraseTimeout);

            if (status != FLASH_TIMEOUT) {
                /* 若操作完毕,复位 OPTPG 位 */
                FLASH->CR &= CR_OPTPG_Reset;
            }
        } else {
            if (status != FLASH_TIMEOUT) {
                /* 复位 OPTER 位 */
                FLASH->CR &= CR_OPTER_Reset;
            }
        }
    }
    /* 返回设置结果 */
    return status;
}

由于读保护都是针对整个芯片的,所以读保护的配置函数相对简单,它通过输入参数ENABLE或DISABL参数来进行保护或解除。它的内部处理与前面介绍的修改选项字节过程完全一致, 当要进行读保护时,往选项字节结构体OB->RDP写入0x00(实际上写入非0xA5的值均可达到目的),而要解除读保护时,则写入0xA5。

要注意的是,本函数同样有对FLASH_CR寄存器的访问,但并没有进行解锁操作,所以调用本函数前,同样需要先使用FLASH_Unlock函数解锁。

49.4. 实验:设置读写保护及解除

在本实验中我们将以实例讲解如何修改选项字节的配置,设置读写保护及解除。

本实验要进行的操作比较特殊,由于设置成读写保护状态后,若不解除保护状态或者解除代码工作不正常,将无法给芯片的FLASH下载新的程序, 所以本程序在开发过程中使用内部SRAM调试的方式开发,便于测试程序(读写保护只影响FLASH,SRAM调试时程序下载到SRAM中,不受影响)。 工程中,提供了FLASH和SRAM调试的版本,见图 两种版本的程序

两种版本的程序

工程的FLASH版本程序包含完整的保护及解除方案,程序下载到内部FLASH后,它自身可以正常地进行保护及解除。另外, 在学习过程中如果您想亲自修改该代码进行测试,也不用担心把解除操作的代码修改至工作不正常而导致芯片无法解锁报废, 处于这种情况时,只要使用本工程的SRAM版本下载到芯片中,即可实现解锁。只要具备前面章节介绍的SRAM调试知识并备份了SRAM版本的工程即可大胆尝试。

49.4.1. 硬件设计

本实验完全针对内部FLASH的操作,对外部硬件无特殊要求。即使是在SRAM调试模式下,由于是使用Debug强制加载PC和SP指针,所以也无需设置BOOT0和BOOT1的引脚。

49.4.2. 软件设计

本实验的工程名称为“设置读写保护与解除”,学习时请打开该工程配合阅读。为了方便展示及移植, 我们把读写保护相关的代码都编写到“bsp_readWriteProtect.c”及“bsp_readWriteProtect.h”文件中, 这些文件是我们自己编写的,不属于标准库的内容,可根据您的喜好命名文件。

49.4.2.1. 代码分析

设置写保护及解除

在本工程中,定义了一个WriteProtect_Toggle 函数用于设置写保护及解除,见 代码清单:保护及解除-4

代码清单:保护及解除-4 设置写保护及解除(bsp_readWriteProtect.c文件)
 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
/**
* @brief  反转写保护的配置,用于演示若芯片处于写保护状态,则解除,若不是写保护状态,则设置成写保护
* @param  无
* @retval 无
*/
void WriteProtect_Toggle(void)
{
    /* 获取写保护寄存器的值进行判断,寄存器位为0表示有保护,为1表示无保护 */
    /*  若不等于0xFFFFFFFF,则说明有部分页被写保护了 */
    if (FLASH_GetWriteProtectionOptionByte() != 0xFFFFFFFF ) {
        FLASH_DEBUG("芯片处于写保护状态,即将执行解保护过程...");

        //解除对FLASH_CR寄存器的访问限制
        FLASH_Unlock();

        /* 擦除所有选项字节的内容 */
        FLASH_EraseOptionBytes();

        /* 对所有页解除 */
        FLASH_EnableWriteProtection(0x00000000);

        FLASH_DEBUG("配置完成,芯片将自动复位加载新配置,复位后芯片会解除写保护状态\r\n");

        /* 复位芯片,以使选项字节生效 */
        NVIC_SystemReset();
    } else { //无写保护
        FLASH_DEBUG("芯片处于无写保护状态,即将执行写保护过程...");

        //解除对FLASH_CR寄存器的访问限制
        FLASH_Unlock();

        /* 先擦除所有选项字节的内容,防止因为原有的写保护导致无法写入新的保护配置 */
        FLASH_EraseOptionBytes();

        /* 对所有页进行写保护 */
        FLASH_EnableWriteProtection(FLASH_WRProt_AllPages);

        FLASH_DEBUG("配置完成,芯片将自动复位加载新配置,复位后芯片会处于写保护状态\r\n");

        /* 复位芯片,以使选项字节生效 */
        NVIC_SystemReset();
    }
}

本函数主要演示写保护和解除功能,若芯片本身处于写保护状态,则解除保护,若芯片本身处于无写保护状态,则设置加入写保护。

WriteProtect_Toggle在操作前会先使用库函数FLASH_GetWriteProtectionOptionByte检测芯片当前的写保护状态,该函数的返回值为FLASH_WRPR寄存器的内容, 它反映了选项字节WRP0/1/2/3的配置。所以在代码中,它判断该函数的返回值不等于0xFFFFFFFF时,可知道芯片至少存在一页被写保护,则程序开始执行解除保护分支。

在解除保护分支中,先调用FLASH_Unlock解除FLASH_CR的访问限制,再使用参数0调用前面介绍的FLASH_EnableWriteProtection函数对所有页解除写保护, 解除配置写入完成后,调用库函数NVIC_SystemReset使芯片产生系统复位,从而使配置生效。

若WriteProtect_Toggle在执行判断时发现芯片本身处于无写保护的状态,则以上述同样的过程向选项字节写入配置, 调用FLASH_EnableWriteProtection函数时使用FLASH_WRProt_AllPages宏,对所有FLASH页加入写保护,最后同样调用NVIC_SystemReset产生系统复位使配置生效。

设置读保护及解除

针对读保护及其解除,本工程定义了ReadProtect_Toggle 函数,见 代码清单:保护及解除-5

代码清单:保护及解除-5 配置PCROP保护(internalFlash_reset.c文件)
 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
/**
* @brief  反转读保护的配置,用于演示
            若芯片处于读保护状态,则解除,
            若不是读保护状态,则设置成读保护
* @param  无
* @retval 无
*/
void ReadProtect_Toggle(void)
{
    if (FLASH_GetReadOutProtectionStatus () == SET ) {
        FLASH_DEBUG("芯片处于读保护状态\r\n");

        //解除对FLASH_CR寄存器的访问限制
        FLASH_Unlock();

        FLASH_DEBUG("即将解除读保护,解除读保护会把FLASH的所有内容清空");
        FLASH_DEBUG("由于解除后程序被清空,所以后面不会有任何提示输出");
        FLASH_DEBUG("等待20秒后即可给芯片下载新的程序...\r\n");

        FLASH_ReadOutProtection (DISABLE);

        //即使在此处加入printf串口调试也不会执行的,
        //因为存储程序的整片FLASH都已被擦除。
        FLASH_DEBUG("由于FLASH程序被清空,所以本代码不会被执行,串口不会有本语句输出(SRAM调试模式下例外)\r\n");
    } else {
        FLASH_DEBUG("芯片处于无读保护状态,即将执行读保护过程...\r\n");

        //解除对FLASH_CR寄存器的访问限制
        FLASH_Unlock();

        FLASH_ReadOutProtection (ENABLE);

        printf("芯片已被设置为读保护,上电复位后生效(必须重新给开发板上电,只按复位键无效)\r\n");
        printf("处于保护状态下无法正常下载新程序,必须要先解除保护状态再下载\r\n");
    }
}

类似地,本函数主要演示读保护和解除功能,若芯片本身处于读保护状态,则解除保护,若芯片本身处于无读保护状态,则设置加入读保护。

ReadProtect_Toggle在操作前会先使用库函数FLASH_GetReadOutProtectionStatus检测芯片当前的写保护状态,该函数内部通过判断选项字节的RDP值, 返回SET(读保护状态)和RESET(无读保护状态)。

判断后,若进入到解除保护分支,会先调用FLASH_Unlock解除FLASH_CR的访问限制,然后使用前面介绍的FLASH_ReadOutProtection函数以DISABLE作为参数解除读保护。

必须注意的是,该函数执行后,所有存储在内部FLASH时的代码都会被删除,以防止原程序被读出,而由于自身代码已被清除, 所以代码中在FLASH_ReadOutProtection(DISABLE)语句后的串口输出是不会被执行的,因为此时这个程序已经不存在了, 但如果使用SRAM版本的程序测试,它是会有输出的,因为这时本程序自身是存储在内部SRAM空间的。

由于解除保护后会触发芯片FLASH的整片擦除操作,所以要稍等一段时间,等待20秒后,解除操作完成,可以重新给芯片的FLASH下载新的程序。

若ReadProtect_Toggle在执行判断时发现芯片本身处于无读保护的状态,它会使用FLASH_ReadOutProtection(ENABLE)语句把芯片设置为读保护状态。 仔细对比读写保护的配置函数,可以发现读保护设置后并没有调用NVIC_SystemReset函数使芯片产生系统复位,这是因为读保护的设置与解除, 是要使用上电复位才能生效的(即重新给芯片上电),系统复位不会产生效应。

main函数

最后来看看本实验的main函数,见 代码清单:保护及解除-6

代码清单:保护及解除-6 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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
//【 !!】注意事项:
//1.当芯片处于读写保护状态时,均无法下载新的程序,需要先解除保护状态后再下载
//2.本工程包含两个版本,可在MDK的“Load”下载按钮旁边的下拉框选择:
//  FLASH版本:程序下载到STM32的FLASH中,与普通的程序无异
//  RAM版本  :程序下载到STM32的内部SRAM中,需要使用RAM调试方式,
//               只能点Debug按钮运行(该运行方法可参考SRAM调试章节的说明)

//3.若自己修改程序导致使芯片处于读写保护状态而无法下载,
//  且 FALSH程序自身又不包含自解除状态的程序,
//  可以按SRAM调试的方式运行本工程的“RAM版本”解除,解除即可重新下载。


/*
* 函数名:main
* 描述  :主函数
* 输入  :无
* 输出  :无
*/
int main(void)
{
    /*初始化USART,配置模式为 115200 8-N-1*/
    USART_Config();
    LED_GPIO_Config();
    Key_GPIO_Config();

    LED_BLUE;

    //芯片自动复位后,串口可能有小部分异常输出,如输出一个“?”号
    printf("\r\n欢迎使用野火  STM32  开发板。\r\n");
    printf("这是读写保护测试实验\r\n");

    /* 获取写保护寄存器的值进行判断,寄存器位为0表示有保护,为1表示无保护 */
    /*  若不等于0xFFFFFFFF,则说明有部分页被写保护了 */
    if (FLASH_GetWriteProtectionOptionByte() !=0xFFFFFFFF ) {
        printf("\r\n目前芯片处于写保护状态,按Key1键解除保护\r\n");
        printf("写保护寄存器的值:WRPR=0x%x\r\n",FLASH_GetWriteProtectionOptionByte());
    } else { //无写保护
        printf("\r\n目前芯片无 写 保护,按 Key1 键可设置成 写 保护\r\n");
        printf("写保护寄存器的值:WRPR=0x%x\r\n",FLASH_GetWriteProtectionOptionByte());
    }

    /*  若等于SET,说明处于读保护状态 */
    if (FLASH_GetReadOutProtectionStatus () == SET ) {
        printf("\r\n目前芯片处于读保护状态,按Key2键解除保护\r\n");
    } else {
        printf("\r\n目前芯片无 读 保护,按 Key2 键可设置成 读 保护\r\n");
    }

    while (1) {
        if ( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON  ) {
            LED1_TOGGLE;
            WriteProtect_Toggle();
        }

        if ( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON  ) {
            LED2_TOGGLE;
            ReadProtect_Toggle();
        }
    }
}

在main函数中,初始化了串口、LED、按键等外设后,根据芯片当前的保护状态输出调试信息,接着循环轮询按键, 若按了KEY1按键,则执行前面的WriteProtect_Toggle反转写保护状态,若按了KEY2键,则执行前面的ReadProtect_Toggle反转读保护状态。

49.4.3. 下载测试

本工程包含两个版本,可在MDK的“Load”下载按钮旁边的下拉框选择:

  • FLASH版本:接上串口调试助手后,直接点击MDK的“Load”按钮把程序下载到STM32的FLASH中,复位运行,串口会输出当前芯片的保护状态, 可使用KEY1和KEY2切换。切换写保护状态时,芯片会自动复位,程序重新执行;切换读保护状态时,按键后需要重新给开发板上电复位, 配置才会有效(断电时,串口与电脑的连接会断开,所以上电后注意重新打开串口调试助手),若是执行解除读保护过程, 运行后芯片FLASH中自身的代码都会消失,所以要重新给开发板下载程序。

  • RAM版本 :若无SRAM调试程序的经验,请先学习前面的《SRAM调试》章节。接上串口调试助手后, 只能使用MDK的“Debug”按钮把程序下载到STM32的内部SRAM中, 然后点击全速运行,可在串口查看调试输出。由于SRAM调试状态下,复位会使芯片程序乱飞,所以每次切换状态复位后, 都要重新点击“Debug”按钮下载SRAM程序,再全速运行才能正常查看输出。