第50章 读写内部FLASH—零死角玩转STM32-F429系列
?
第50章 ????读写内部FLASH全套200集视频教程和1000页PDF教程请到秉火论坛下载:www.firebbs.cn 野火视频教程优酷观看网址:http://i.youku.com/firege ? ? ? 本章参考资料:《STM32F4xx 中文参考手册》、《STM32F4xx规格书》、库说明文档《stm32f4xx_dsp_stdperiph_lib_um.chm》。 50.1 STM32的内部FLASH简介在STM32芯片内部有一个FLASH存储器,它主要用于存储代码,我们在电脑上编写好应用程序后,使用下载器把编译后的代码文件烧录到该内部FLASH中,由于FLASH存储器的内容在掉电后不会丢失,芯片重新上电复位后,内核可从内部FLASH中加载代码并运行,见图 501。 图 501 STM32的内部框架图 除了使用外部的工具(如下载器)读写内部FLASH外,STM32芯片在运行的时候,也能对自身的内部FLASH进行读写,因此,若内部FLASH存储了应用程序后还有剩余的空间,我们可以把它像外部SPI-FLASH那样利用起来,存储一些程序运行时产生的需要掉电保存的数据。 由于访问内部FLASH的速度要比外部的SPI-FLASH快得多,所以在紧急状态下常常会使用内部FLASH存储关键记录;为了防止应用程序被抄袭,有的应用会禁止读写内部FLASH中的内容,或者在第一次运行时计算加密信息并记录到某些区域,然后删除自身的部分加密代码,这些应用都涉及到内部FLASH的操作。 1.????内部FLASH的构成STM32的内部FLASH包含主存储器、系统存储器、OTP区域以及选项字节区域,它们的地址分布及大小见表 501。 表 501 STM32内部FLASH的构成
各个存储区域的说明如下: ?????主存储器 一般我们说STM32内部FLASH的时候,都是指这个主存储器区域,它是存储用户应用程序的空间,芯片型号说明中的1M FLASH、2M FLASH都是指这个区域的大小。主存储器分为两块,共2MB,每块内分12个扇区,其中包含4个16KB扇区、1个64KB扇区和7个128KB的扇区。如我们实验板中使用的STM32F429IGT6型号芯片,它的主存储区域大小为1MB,所以它只包含有表中的扇区0-扇区11。 与其它FLASH一样,在写入数据前,要先按扇区擦除,而有的时候我们希望能以小规格操纵存储单元,所以STM32针对1MB FLASH的产品还提供了一种双块的存储格式,见表 502。(2M的产品按表 501的格式) 表 502 1MB产品的双块存储格式
通过配置FLASH选项控制寄存器FLASH_OPTCR的DB1M位,可以切换这两种格式,切换成双块模式后,扇区8-11的空间被转移到扇区12-19中,扇区细分了,总容量不变。 注意如果您使用的是STM32F40x系列的芯片,它没有双块存储格式,也不存在扇区12-23,仅STM32F42x/43x系列产品才支持扇区12-23。 ?????系统存储区 系统存储区是用户不能访问的区域,它在芯片出厂时已经固化了启动代码,它负责实现串口、USB以及CAN等ISP烧录功能。 ?????OTP区域 OTP(One Time Program),指的是只能写入一次的存储区域,容量为512字节,写入后数据就无法再更改,OTP常用于存储应用程序的加密密钥。 ?????选项字节 选项字节用于配置FLASH的读写保护、电源管理中的BOR级别、软件/硬件看门狗等功能,这部分共32字节。可以通过修改FLASH的选项控制寄存器修改。 50.2 对内部FLASH的写入过程1.????解锁由于内部FLASH空间主要存储的是应用程序,是非常关键的数据,为了防止误操作修改了这些内容,芯片复位后默认会结FLASH上锁,这个时候不允许设置FLASH的控制寄存器,并且不能对修改FLASH中的内容。 所以对FLASH写入数据前,需要先给它解锁。解锁的操作步骤如下: (1)????往Flash 密钥寄存器 FLASH_KEYR中写入 KEY1 = 0x45670123 (2)????再往Flash 密钥寄存器 FLASH_KEYR中写入 KEY2 = 0xCDEF89AB 2.????数据操作位数在内部FLASH进行擦除及写入操作时,电源电压会影响数据的最大操作位数,该电源电压可通过配置FLASH_CR 寄存器中的 PSIZE位改变,见表 503。 表 503 数据操作位数
最大操作位数会影响擦除和写入的速度,其中64位宽度的操作除了配置寄存器位外,还需要在Vpp引脚外加一个8-9V的电压源,且其供电时间不得超过一小时,否则FLASH可能损坏,所以64位宽度的操作一般是在量产时对FLASH写入应用程序时才使用,大部分应用场合都是用32位的宽度。 3.????擦除扇区在写入新的数据前,需要先擦除存储区域,STM32提供了扇区擦除指令和整个FLASH擦除(批量擦除)的指令,批量擦除指令仅针对主存储区。 扇区擦除的过程如下: (1)????检查 FLASH_SR 寄存器中的"忙碌寄存器位 BSY",以确认当前未执行任何 Flash 操作; (2)????在 FLASH_CR 寄存器中,将"激活扇区擦除寄存器位SER "置 1,并设置"扇区编号寄存器位SNB",选择要擦除的扇区; (3)????将 FLASH_CR 寄存器中的"开始擦除寄存器位 STRT "置 1,开始擦除; (4)????等待 BSY 位被清零时,表示擦除完成。 4.????写入数据擦除完毕后即可写入数据,写入数据的过程并不是仅仅使用指针向地址赋值,赋值前还还需要配置一系列的寄存器,步骤如下: (1)????检查 FLASH_SR 中的 BSY 位,以确认当前未执行任何其它的内部 Flash 操作; (2)????将 FLASH_CR 寄存器中的 "激活编程寄存器位PG" 置 1; (3)????针对所需存储器地址(主存储器块或 OTP 区域内)执行数据写入操作; (4)????等待 BSY 位被清零时,表示写入完成。 50.3 查看工程的空间分布由于内部FLASH本身存储有程序数据,若不是有意删除某段程序代码,一般不应修改程序空间的内容,所以在使用内部FLASH存储其它数据前需要了解哪一些空间已经写入了程序代码,存储了程序代码的扇区都不应作任何修改。通过查询应用程序编译时产生的"*.map"后缀文件,可以了解程序存储到了哪些区域,它在工程中的打开方式见图 502,也可以到工程目录中的"Listing"文件夹中找到。 图 502 打开工程的.map文件 打开map文件后,查看文件最后部分的区域,可以看到一段以"Memory Map of the image"开头的记录(若找不到可用查找功能定位),见代码清单 501。 代码清单 501 map文件中的存储映像分布说明 1 ======================================================================= 2 Memory Map of the image //存储分布映像 3 4 Image Entry point : 0x080001ad 5 6 /*程序ROM加载空间*/ 7 Load Region LR_IROM1 (Base: 0x08000000,Size: 0x00000b50,Max: 0x00100000,ABSOLUTE) 8 9 /*程序ROM执行空间*/ 10 Execution Region ER_IROM1 (Base: 0x08000000,Size: 0x00000b3c,ABSOLUTE) 11 12 /*地址分布列表*/ 13 Base Addr Size Type Attr Idx E Section Name Object 14 15 0x08000000 0x000001ac Data RO 3 RESET startup_stm32f429_439xx.o 16 0x080001ac 0x00000000 Code RO 5359 * .ARM.Collect$$$$00000000 mc_w.l(entry.o) 17 0x080001ac 0x00000004 Code RO 5622 .ARM.Collect$$$$00000001 mc_w.l(entry2.o) 18 0x080001b0 0x00000004 Code RO 5625 .ARM.Collect$$$$00000004 mc_w.l(entry5.o) 19 0x080001b4 0x00000000 Code RO 5627 .ARM.Collect$$$$00000008 mc_w.l(entry7b.o) 20 0x080001b4 0x00000000 Code RO 5629 .ARM.Collect$$$$0000000A mc_w.l(entry8b.o) 21 /*...此处省略大部分内容*/ 22 0x08000948 0x0000000e Code RO 4910 i.USART_GetFlagStatus stm32f4xx_usart.o 23 0x08000956 0x00000002 PAD 24 0x08000958 0x000000bc Code RO 4914 i.USART_Init stm32f4xx_usart.o 25 0x08000a14 0x00000008 Code RO 4924 i.USART_SendData stm32f4xx_usart.o 26 0x08000a1c 0x00000002 Code RO 5206 i.UsageFault_Handler stm32f4xx_it.o 27 0x08000a1e 0x00000002 PAD 28 0x08000a20 0x00000010 Code RO 5363 i.__0printf$bare mc_w.l(printfb.o) 29 0x08000a30 0x0000000e Code RO 5664 i.__scatterload_copy mc_w.l(handlers.o) 30 0x08000a3e 0x00000002 Code RO 5665 i.__scatterload_null mc_w.l(handlers.o) 31 0x08000a40 0x0000000e Code RO 5666 i.__scatterload_zeroinit mc_w.l(handlers.o) 32 0x08000a4e 0x00000022 Code RO 5370 i._printf_core mc_w.l(printfb.o) 33 0x08000a70 0x00000024 Code RO 5275 i.fputc bsp_debug_usart.o 34 0x08000a94 0x00000088 Code RO 5161 i.main main.o 35 0x08000b1c 0x00000020 Data RO 5662 Region$$Table anon$$obj.o 36 这一段是某工程的ROM存储器分布映像,在STM32芯片中,ROM区域的内容就是指存储到内部FLASH的代码。 1.????程序ROM的加载与执行空间上述说明中有两段分别以"Load Region LR_ROM1"及"Execution Region ER_IROM1"开头的内容,它们分别描述程序的加载及执行空间。在芯片刚上电运行时,会加载程序及数据,例如它会从程序的存储区域加载到程序的执行区域,还把一些已初始化的全局变量从ROM复制到RAM空间,以便程序运行时可以修改变量的内容。加载完成后,程序开始从执行区域开始执行。 在上面map文件的描述中,我们了解到加载及执行空间的基地址(Base)都是0x08000000,它正好是STM32内部FLASH的首地址,即STM32的程序存储空间就直接是执行空间;它们的大小(Size)分别为0x00000b50及0x00000b3c,执行空间的ROM比较小的原因就是因为部分RW-data类型的变量被拷贝到RAM空间了;它们的最大空间(Max)均为0x00100000,即1M字节,它指的是内部FLASH的最大空间。 计算程序占用的空间时,需要使用加载区域的大小进行计算,本例子中应用程序使用的内部FLASH是从0x08000000至(0x08000000+0x00000b50)地址的空间区域。 2.????ROM空间分布表在加载及执行空间总体描述之后,紧接着一个ROM详细地址分布表,它列出了工程中的各个段(如函数、常量数据)所在的地址Base Addr及占用的空间Size,列表中的Type说明了该段的类型,CODE表示代码,DATA表示数据,而PAD表示段之间的填充区域,它是无效的内容,PAD区域往往是为了解决地址对齐的问题。 观察表中的最后一项,它的基地址是0x08000b1c,大小为0x00000020,可知它占用的最高的地址空间为0x08000b3c,跟执行区域的最高地址0x00000b3c一样,但它们比加载区域说明中的最高地址0x8000b50要小,所以我们以加载区域的大小为准。对比表 501的内部FLASH扇区地址分布表,可知仅使用扇区0就可以完全存储本应用程序,所以从扇区1(地址0x08004000)后的存储空间都可以作其它用途,使用这些存储空间时不会篡改应用程序空间的数据。 50.4 操作内部FLASH的库函数为简化编程,STM32标准库提供了一些库函数,它们封装了对内部FLASH写入数据操作寄存器的过程。 1.????FLASH解锁、上锁函数对内部FLASH解锁、上锁的函数见代码清单 502。 代码清单 502 FLASH解锁、上锁 1 2 #define FLASH_KEY1 ((uint32_t)0x45670123) 3 #define FLASH_KEY2 ((uint32_t)0xCDEF89AB) 4 /** 5 * @brief Unlocks the FLASH control register access 6 * @param None 7 * @retval None 8 */ 9 void FLASH_Unlock(void) 10 { 11 if ((FLASH->CR & FLASH_CR_LOCK) != RESET) { 12 /* Authorize the FLASH Registers access */ 13 FLASH->KEYR = FLASH_KEY1; 14 FLASH->KEYR = FLASH_KEY2; 15 } 16 } 17 18 /** 19 * @brief Locks the FLASH control register access 20 * @param None 21 * @retval None 22 */ 23 void FLASH_Lock(void) 24 { 25 /* Set the LOCK Bit to lock the FLASH Registers access */ 26 FLASH->CR |= FLASH_CR_LOCK; 27 } 解锁的时候,它对FLASH_KEYR寄存器写入两个解锁参数,上锁的时候,对FLASH_CR寄存器的FLASH_CR_LOCK位置1。 2.????设置操作位数及擦除扇区解锁后擦除扇区时可调用FLASH_EraseSector完成,见代码清单 503。 代码清单 503 擦除扇区 1 /** 2 * @brief Erases a specified FLASH Sector. 3 * 4 * @note If an erase and a program operations are requested simultaneously, 5 * the erase operation is performed before the program one. 6 * 7 * @param FLASH_Sector: The Sector number to be erased. 8 * 9 * @note For STM32F42xxx/43xxx devices this parameter can be a value between 10 * FLASH_Sector_0 and FLASH_Sector_23. 11 * 12 * @param VoltageRange: The device voltage range which defines the erase parallelism. 13 * This parameter can be one of the following values: 14 * @arg VoltageRange_1: when the device voltage range is 1.8V to 2.1V, 15 * the operation will be done by byte (8-bit) 16 * @arg VoltageRange_2: when the device voltage range is 2.1V to 2.7V, 17 * the operation will be done by half word (16-bit) 18 * @arg VoltageRange_3: when the device voltage range is 2.7V to 3.6V, 19 * the operation will be done by word (32-bit) 20 * @arg VoltageRange_4: when the device voltage range is 2.7V to 3.6V + External Vpp, 21 * the operation will be done by double word (64-bit) 22 * 23 * @retval FLASH Status: The returned value can be: FLASH_BUSY,FLASH_ERROR_PROGRAM, 24 * FLASH_ERROR_WRP,FLASH_ERROR_OPERATION or FLASH_COMPLETE. 25 */ 26 FLASH_Status FLASH_EraseSector(uint32_t FLASH_Sector,uint8_t VoltageRange) 27 { 28 uint32_t tmp_psize = 0x0; 29 FLASH_Status status = FLASH_COMPLETE; 30 31 /* Check the parameters */ 32 assert_param(IS_FLASH_SECTOR(FLASH_Sector)); 33 assert_param(IS_VOLTAGERANGE(VoltageRange)); 34 35 if (VoltageRange == VoltageRange_1) { 36 tmp_psize = FLASH_PSIZE_BYTE; 37 } else if (VoltageRange == VoltageRange_2) { 38 tmp_psize = FLASH_PSIZE_HALF_WORD; 39 } else if (VoltageRange == VoltageRange_3) { 40 tmp_psize = FLASH_PSIZE_WORD; 41 } else { 42 tmp_psize = FLASH_PSIZE_DOUBLE_WORD; 43 } 44 /* Wait for last operation to be completed */ 45 status = FLASH_WaitForLastOperation(); 46 47 if (status == FLASH_COMPLETE) { 48 /* if the previous operation is completed,proceed to erase the sector */ 49 FLASH->CR &= CR_PSIZE_MASK; 50 FLASH->CR |= tmp_psize; 51 FLASH->CR &= SECTOR_MASK; 52 FLASH->CR |= FLASH_CR_SER | FLASH_Sector; 53 FLASH->CR |= FLASH_CR_STRT; 54 55 /* Wait for last operation to be completed */ 56 status = FLASH_WaitForLastOperation(); 57 58 /* if the erase operation is completed,disable the SER Bit */ 59 FLASH->CR &= (~FLASH_CR_SER); 60 FLASH->CR &= SECTOR_MASK; 61 } 62 /* Return the Erase Status */ 63 return status; 64 } 本函数包含两个输入参数,分别是要擦除的扇区号和工作电压范围,选择不同电压时实质是选择不同的数据操作位数,参数中可输入的宏在注释里已经给出。函数根据输入参数配置PSIZE位,然后擦除扇区,擦除扇区的时候需要等待一段时间,它使用FLASH_WaitForLastOperation等待,擦除完成的时候才会退出FLASH_EraseSector函数。 3.????写入数据对内部FLASH写入数据不像对SDRAM操作那样直接指针操作就完成了,还要设置一系列的寄存器,利用FLASH_ProgramWord、FLASH_ProgramHalfWord和FLASH_ProgramByte函数可按字、半字及字节单位写入数据,见代码清单 504。 代码清单 504 写入数据 1 2 /** 3 * @brief Programs a word (32-bit) at a specified address. 4 * 5 * @note This function must be used when the device voltage range is from 2.7V to 3.6V. 6 * 7 * @note If an erase and a program operations are requested simultaneously, 8 * the erase operation is performed before the program one. 9 * 10 * @param Address: specifies the address to be programmed. 11 * This parameter can be any address in Program memory zone or in OTP zone. 12 * @param Data: specifies the data to be programmed. 13 * @retval FLASH Status: The returned value can be: FLASH_BUSY,FLASH_ERROR_PROGRAM, 14 * FLASH_ERROR_WRP,FLASH_ERROR_OPERATION or FLASH_COMPLETE. 15 */ 16 FLASH_Status FLASH_ProgramWord(uint32_t Address,uint32_t Data) 17 { 18 FLASH_Status status = FLASH_COMPLETE; 19 20 /* Check the parameters */ 21 assert_param(IS_FLASH_ADDRESS(Address)); 22 23 /* Wait for last operation to be completed */ 24 status = FLASH_WaitForLastOperation(); 25 26 if (status == FLASH_COMPLETE) { 27/* if the previous operation is completed,proceed to program the new data */ 28 FLASH->CR &= CR_PSIZE_MASK; 29 FLASH->CR |= FLASH_PSIZE_WORD; 30 FLASH->CR |= FLASH_CR_PG; 31 32 *(__IO uint32_t*)Address = Data; 33 34 /* Wait for last operation to be completed */ 35 status = FLASH_WaitForLastOperation(); 36 37 /* if the program operation is completed,disable the PG Bit */ 38 FLASH->CR &= (~FLASH_CR_PG); 39 } 40 /* Return the Program Status */ 41 return status; 42 } 看函数代码可了解到,使用指针进行赋值操作前设置了数据操作宽度,并设置了PG寄存器位,在赋值操作后,调用了FLASH_WaitForLastOperation函数等待写操作完毕。HalfWord和Byte操作宽度的函数执行过程类似。 50.5 实验:读写内部FLASH在本小节中我们以实例讲解如何使用内部FLASH存储数据。 50.5.1 硬件设计本实验仅操作了STM32芯片内部的FLASH空间,无需额外的硬件。 50.5.2 软件设计本小节讲解的是"内部FLASH编程"实验,请打开配套的代码工程阅读理解。为了方便展示及移植,我们把操作内部FLASH相关的代码都编写到"bsp_internalFlash.c"及"bsp_internalFlash.h"文件中,这些文件是我们自己编写的,不属于标准库的内容,可根据您的喜好命名文件。 1.????程序设计要点(7)????对内部FLASH解锁; (8)????找出空闲扇区,擦除目标扇区; (9)????进行读写测试。 2.????代码分析硬件定义读写内部FLASH不需要用到任何外部硬件,不过在擦写时常常需要知道各个扇区的基地址,我们把这些基地址定义到bsp_internalFlash.h文件中,见代码清单 441。 代码清单 505 各个扇区的基地址(bsp_internalFlash.h文件) 1 2 /* 各个扇区的基地址 */ 3 #define ADDR_FLASH_SECTOR_0 ((uint32_t)0x08000000) 4 #define ADDR_FLASH_SECTOR_1 ((uint32_t)0x08004000) 5 #define ADDR_FLASH_SECTOR_2 ((uint32_t)0x08008000) 6 #define ADDR_FLASH_SECTOR_3 ((uint32_t)0x0800C000) 7 #define ADDR_FLASH_SECTOR_4 ((uint32_t)0x08010000) 8 #define ADDR_FLASH_SECTOR_5 ((uint32_t)0x08020000) 9 #define ADDR_FLASH_SECTOR_6 ((uint32_t)0x08040000) 10 #define ADDR_FLASH_SECTOR_7 ((uint32_t)0x08060000) 11 #define ADDR_FLASH_SECTOR_8 ((uint32_t)0x08080000) 12 #define ADDR_FLASH_SECTOR_9 ((uint32_t)0x080A0000) 13 #define ADDR_FLASH_SECTOR_10 ((uint32_t)0x080C0000) 14 #define ADDR_FLASH_SECTOR_11 ((uint32_t)0x080E0000) 15 16 #define ADDR_FLASH_SECTOR_12 ((uint32_t)0x08100000) 17 #define ADDR_FLASH_SECTOR_13 ((uint32_t)0x08104000) 18 #define ADDR_FLASH_SECTOR_14 ((uint32_t)0x08108000) 19 #define ADDR_FLASH_SECTOR_15 ((uint32_t)0x0810C000) 20 #define ADDR_FLASH_SECTOR_16 ((uint32_t)0x08110000) 21 #define ADDR_FLASH_SECTOR_17 ((uint32_t)0x08120000) 22 #define ADDR_FLASH_SECTOR_18 ((uint32_t)0x08140000) 23 #define ADDR_FLASH_SECTOR_19 ((uint32_t)0x08160000) 24 #define ADDR_FLASH_SECTOR_20 ((uint32_t)0x08180000) 25 #define ADDR_FLASH_SECTOR_21 ((uint32_t)0x081A0000) 26 #define ADDR_FLASH_SECTOR_22 ((uint32_t)0x081C0000) 27 #define ADDR_FLASH_SECTOR_23 ((uint32_t)0x081E0000) 这些宏跟表 501中的地址说明一致。 根据扇区地址计算SNB寄存器的值在擦除操作时,需要向FLASH控制寄存器FLASH_CR的SNB位写入要擦除的扇区号,固件库把各个扇区对应的寄存器值使用宏定义到了stm32f4xx_flash.h文件。为了便于使用,我们自定义了一个GetSector函数,根据输入的内部FLASH地址,找出其所在的扇区,并返回该扇区对应的SNB位寄存器值,见代码清单 442。 代码清单 506 写入到SNB寄存器位的值(stm32f4xx_flash.h及bsp_internalFlash.c文件) 1 /*固件库定义的用于扇区写入到SNB寄存器位的宏(stm32f4xx_flash.h文件)*/
2 #define FLASH_Sector_0 ((uint16_t)0x0000)
3 #define FLASH_Sector_1 ((uint16_t)0x0008)
4 #define FLASH_Sector_2 ((uint16_t)0x0010)
5 #define FLASH_Sector_3 ((uint16_t)0x0018)
6 #define FLASH_Sector_4 ((uint16_t)0x0020)
7 #define FLASH_Sector_5 ((uint16_t)0x0028)
8 #define FLASH_Sector_6 ((uint16_t)0x0030)
9 #define FLASH_Sector_7 ((uint16_t)0x0038)
10 #define FLASH_Sector_8 ((uint16_t)0x0040)
11 #define FLASH_Sector_9 ((uint16_t)0x0048)
12 #define FLASH_Sector_10 ((uint16_t)0x0050)
13 #define FLASH_Sector_11 ((uint16_t)0x0058)
14 #define FLASH_Sector_12 ((uint16_t)0x0080)
15 #define FLASH_Sector_13 ((uint16_t)0x0088)
16 #define FLASH_Sector_14 ((uint16_t)0x0090)
17 #define FLASH_Sector_15 ((uint16_t)0x0098)
18 #define FLASH_Sector_16 ((uint16_t)0x00A0)
19 #define FLASH_Sector_17 ((uint16_t)0x00A8)
20 #define FLASH_Sector_18 ((uint16_t)0x00B0)
21 #define FLASH_Sector_19 ((uint16_t)0x00B8)
22 #define FLASH_Sector_20 ((uint16_t)0x00C0)
23 #define FLASH_Sector_21 ((uint16_t)0x00C8)
24 #define FLASH_Sector_22 ((uint16_t)0x00D0)
25 #define FLASH_Sector_23 ((uint16_t)0x00D8)
26
27 /*定义在bsp_internalFlash.c文件中的函数*/
28 /**
29 * @brief 根据输入的地址给出它所在的sector
30 * 例如:
31 uwStartSector = GetSector(FLASH_USER_START_ADDR);
32 uwEndSector = GetSector(FLASH_USER_END_ADDR);
33 * @param Address:地址
34 * @retval 地址所在的sector
35 */
36 static uint32_t GetSector(uint32_t Address)
37 {
38 uint32_t sector = 0;
39
40 if ((Address < ADDR_FLASH_SECTOR_1) && (Address >= ADDR_FLASH_SECTOR_0)) {
41 sector = FLASH_Sector_0;
42 } else if ((Address < ADDR_FLASH_SECTOR_2) && (Address >= ADDR_FLASH_SECTOR_1)) {
43 sector = FLASH_Sector_1;
44 }
45
46 /*此处省略扇区2-扇区21的内容*/
47
48 else if ((Address < ADDR_FLASH_SECTOR_23) && (Address >= ADDR_FLASH_SECTOR_22)) {
49 sector = FLASH_Sector_22;
50 } else { /*(Address < FLASH_END_ADDR) && (Address >= ADDR_FLASH_SECTOR_23))*/
51 sector = FLASH_Sector_23;
52 }
53 return sector;
54 }
代码中固件库定义的宏FLASH_Sector_0-23对应的值是跟寄存器说明一致的,见图 503。 图 503 FLASH_CR寄存器的SNB位的值 GetSector函数根据输入的地址与各个扇区的基地址进行比较,找出它所在的扇区,并使用固件库中的宏,返回扇区对应的SNB值。 读写内部FLASH一切准备就绪,可以开始对内部FLASH进行擦写,这个过程不需要初始化任何外设,只要按解锁、擦除及写入的流程走就可以了,见代码清单 443。 代码清单 507 对内部地FLASH进行读写测试(bsp_internalFlash.c文件) 1 2 /*准备写入的测试数据*/ 3 #define DATA_32 ((uint32_t)0x00000000) 4 /* 要擦除内部FLASH的起始地址 */ 5 #define FLASH_USER_START_ADDR ADDR_FLASH_SECTOR_8 6 /* 要擦除内部FLASH的结束地址 */ 7 #define FLASH_USER_END_ADDR ADDR_FLASH_SECTOR_12 8 9 /** 10 * @brief InternalFlash_Test,对内部FLASH进行读写测试 11 * @param None 12 * @retval None 13 */ 14 int InternalFlash_Test(void) 15 { 16 /*要擦除的起始扇区(包含)及结束扇区(不包含),如8-12,表示擦除8、9、10、11扇区*/ 17 uint32_t uwStartSector = 0; 18 uint32_t uwEndSector = 0; 19 20 uint32_t uwAddress = 0; 21 uint32_t uwSectorCounter = 0; 22 23 __IO uint32_t uwData32 = 0; 24 __IO uint32_t uwMemoryProgramStatus = 0; 25 26 /* FLASH 解锁 ********************************/ 27 /* 使能访问FLASH控制寄存器 */ 28 FLASH_Unlock(); 29 30 /* 擦除用户区域 (用户区域指程序本身没有使用的空间,可以自定义)**/ 31 /* 清除各种FLASH的标志位 */ 32 FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR | 33 FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR|FLASH_FLAG_PGSERR); 34 35 36 uwStartSector = GetSector(FLASH_USER_START_ADDR); 37 uwEndSector = GetSector(FLASH_USER_END_ADDR); 38 39 /* 开始擦除操作 */ 40 uwSectorCounter = uwStartSector; 41 while (uwSectorCounter <= uwEndSector) { 42 /* VoltageRange_3 以"字"的大小进行操作 */ 43 if (FLASH_EraseSector(uwSectorCounter,VoltageRange_3) != FLASH_COMPLETE) { 44 /*擦除出错,返回,实际应用中可加入处理 */ 45 return -1; 46 } 47 /* 计数器指向下一个扇区 */ 48 if (uwSectorCounter == FLASH_Sector_11) { 49 uwSectorCounter += 40; 50 } else { 51 uwSectorCounter += 8; 52 } 53 } 54 55 /* 以"字"的大小为单位写入数据 ********************************/ 56 uwAddress = FLASH_USER_START_ADDR; 57 58 while (uwAddress < FLASH_USER_END_ADDR) { 59 if (FLASH_ProgramWord(uwAddress,DATA_32) == FLASH_COMPLETE) { 60 uwAddress = uwAddress + 4; 61 } else { 62 /*写入出错,返回,实际应用中可加入处理 */ 63 return -1; 64 } 65 } 66 67 68 /* 给FLASH上锁,防止内容被篡改*/ 69 FLASH_Lock(); 70 71 72 /* 从FLASH中读取出数据进行校验***************************************/ 73 /* MemoryProgramStatus = 0: 写入的数据正确 74 MemoryProgramStatus != 0: 写入的数据错误,其值为错误的个数 */ 75 uwAddress = FLASH_USER_START_ADDR; 76 uwMemoryProgramStatus = 0; 77 78 while (uwAddress < FLASH_USER_END_ADDR) { 79 uwData32 = *(__IO uint32_t*)uwAddress; 80 81 if (uwData32 != DATA_32) { 82 uwMemoryProgramStatus++; 83 } 84 85 uwAddress = uwAddress + 4; 86 } 87 /* 数据校验不正确 */ 88 if (uwMemoryProgramStatus) { 89 return -1; 90 } else { /*数据校验正确*/ 91 return 0; 92 } 93 } 94 该函数的执行过程如下: (1)????调用FLASH_Unlock解锁; (2)????调用FLASH_ClearFlag清除各种标志位; (3)????调用GetSector根据起始地址及结束地址计算要擦除的扇区; (4)????调用FLASH_EraseSector擦除扇区,擦除时按字为单位进行操作; (5)????调用FLASH_ProgramWord函数向起始地址至结束地址的存储区域都写入数值"DATA_32"; (6)????调用FLASH_Lock上锁; (7)????使用指针读取数据内容并校验。 main函数最后我们来看看main函数的执行流程,见代码清单 444。 代码清单 508 main函数(main.c文件) 1 /**
2 * @brief 主函数
3 * @param 无
4 * @retval 无
5 */
6 int main(void)
7 {
8 /*初始化USART,配置模式为 115200 8-N-1*/
9 Debug_USART_Config();
10 LED_GPIO_Config();
11
12 LED_BLUE;
13 /*调用printf函数,因为重定向了fputc,printf的内容会输出到串口*/
14 printf("this is a usart printf demo. rn");
15 printf("rn欢迎使用秉火 STM32 F429 开发板。rn");
16 printf("正在进行读写内部FLASH实验,请耐心等待rn");
17
18 if (InternalFlash_Test()==0) {
19 LED_GREEN;
20 printf("读写内部FLASH测试成功rn");
21
22 } else {
23 printf("读写内部FLASH测试失败rn");
24 LED_RED;
25 }
26 }
main函数中初始化了用于指示调试信息的LED及串口后,直接调用了InternalFlash_Test函数,进行读写测试并根据测试结果输出调试信息。 50.5.3 下载验证用USB线连接开发板"USB TO UART"接口跟电脑,在电脑端打开串口调试助手,把编译好的程序下载到开发板。在串口调试助手可看到擦写内部FLASH的调试信息。 ? 50.6 每课一问5.????尝试擦除应用程序所在的内部FLASH扇区,观察实验现象。 6.????使用C语言的"const uint8_t value;"和"volatile const uint8_t value"语句定义的变量value有什么区别?若定义后使用内部FLASH操作擦除value的存储空间,再读取value的值,哪种定义能正常读取? (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |