imuncle.github.io
imuncle.github.io copied to clipboard
STM32 flash读写
随着代码结构的完善,越来越希望代码更加智能化,调参的步骤越少越好,要是啥也不用调就更好了。。。
回忆起以往看过的代码,感觉智能化第一步就是搞定flash读写。有了flash之后,一切都变得很方便了。
比如每辆车的电机安装方式不一样,那么云台中间位置就不一样,如果有了flash,直接运行校准程序,云台左右分别碰到限位开关,自动计算出中间位置,pitch轴直接用手扶正,靠陀螺仪判断是否水平,水平的时候就记下当前位置,写入flash中,不用再慢吞吞地进调试,手动记录中间位置了。
又比如现在都流行自己搞上位机,上位机校准的关键一点就是单片机需要存储上位机的信息,下次上电就可以直接使用了。
所以flash多好啊。
flash
首先我们需要了解一个内存映射:

stm32的flash地址起始于0x0800 0000,结束地址是0x0800 0000加上芯片实际的flash大小,不同的芯片flash大小不同。
RAM起始地址是0x2000 0000,结束地址是0x2000 0000加上芯片的RAM大小。不同的芯片RAM也不同。
Flash中的内容一般用来存储代码和一些定义为const的数据,断电不丢失, RAM可以理解为内存,用来存储代码运行时的数据,变量等等。掉电数据丢失。
STM32将外设等都映射为地址的形式,对地址的操作就是对外设的操作。
STM32的外设地址从0x4000 0000开始,可以看到在库文件中,是通过基于0x4000 0000地址的偏移量来操作寄存器以及外设的。
一般情况下,程序文件是从 0x0800 0000 地址写入,这个是STM32开始执行的地方,0x0800 0004是STM32的中断向量表的起始地址。
在使用keil进行编写程序时,其编程地址的设置一般是这样的:

程序的写入地址从0x08000000(数好零的个数)开始的,其大小为0x100000也就是1024K的空间,换句话说就是告诉编译器flash的空间是从0x08000000-0x08100000。这与STM32的内存地址映射关系是对应的。
内部flash的构成
STM32 的内部 FLASH 包含主存储器、系统存储器、 OTP 区域以及选项字节区域,它们的地址分布及大小如下:

- 主存储器:一般我们说 STM32 内部 FLASH 的时候,都是指这个主存储器区域它是存储用户应用程序的空间,芯片型号说明中的 1M FLASH、 2M FLASH 都是指这个区域的大小。与其它 FLASH 一样,在写入数据前,要先按扇区擦除
- 系统存储区:系统存储区是用户不能访问的区域,它在芯片出厂时已经固化了启动代码,它负责实现串口、 USB 以及 CAN 等 ISP 烧录功能。
- OTP 区域:OTP(One Time Program),指的是只能写入一次的存储区域,容量为 512 字节,写入后数据就无法再更改, OTP 常用于存储应用程序的加密密钥。
- 选项字节:选项字节用于配置 FLASH 的读写保护、电源管理中的 BOR 级别、软件/硬件看门狗等功能,这部分共 32 字节。可以通过修改 FLASH 的选项控制寄存器修改。
查看工程内存的分布
由于内部 FLASH 本身存储有程序数据,若不是有意删除某段程序代码,一般不应修改程序空间的内容,所以在使用内部 FLASH 存储其它数据前需要了解哪一些空间已经写入了程序代码,存储了程序代码的扇区都不应作任何修改。
通过查询应用程序编译时产生的*.map后缀文件,
打开 map 文件后,查看文件最后部分的区域,可以看到一段以 Memory Map of the image开头的记录:

从这个文件中可以看到flash究竟哪些地址被使用了。keil在下载程序的时候有三种选项:

- Erase Full Chip:烧写程序之前擦除整个Flash存储器。
- Erase Sectors:烧写程序之前擦除程序要使用的扇区。
- Do not Erase:不进行擦除操作
默认选择第二个选项,所以我们只需要把数据存储在程序没有用到的flash区域就行了,不会在下载程序的时候被覆盖。
代码实践
STM32CubeMX中不要任何特殊的配置,flash相关操作函数默认自带了,直接贴上代码:
/*FLASH写入程序*/
void writeFlashTest(uint32_t addr, uint32_t WriteFlashData)
{
/* 1/4解锁FLASH*/
HAL_FLASH_Unlock();
/* 2/4擦除FLASH*/
/*初始化FLASH_EraseInitTypeDef*/
FLASH_EraseInitTypeDef FlashSet;
FlashSet.TypeErase = FLASH_TYPEERASE_SECTORS;
FlashSet.NbSectors = 1;
FlashSet.Sector = FLASH_SECTOR_7;
FlashSet.VoltageRange = FLASH_VOLTAGE_RANGE_3;
/*设置PageError,调用擦除函数*/
uint32_t PageError = 0;
HAL_FLASHEx_Erase(&FlashSet, &PageError);
/* 3/4对FLASH烧写*/
HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, addr, WriteFlashData);
/* 4/4锁住FLASH*/
HAL_FLASH_Lock();
}
/*FLASH读取程序*/
uint32_t printFlashTest(uint32_t addr)
{
uint32_t temp = *(__IO uint32_t*)(addr);
return temp;
}
上面的程序中读取程序很简单,直接读取对应地址的数值就行了,写入数据也很简单,直接调用HAL_FLASH_Program()函数就行了,关键是flash擦除这一步有一些配置选项。
flash写入之前一定要先擦除,否则可能会导致写入失败,或写入数据错误。flash擦除需要初始化FLASH_EraseInitTypeDef结构体,定义如下:
/**
* @brief FLASH Erase structure definition
*/
typedef struct
{
uint32_t TypeErase; /*!< Mass erase or sector Erase.
This parameter can be a value of @ref FLASHEx_Type_Erase */
uint32_t Banks; /*!< Select banks to erase when Mass erase is enabled.
This parameter must be a value of @ref FLASHEx_Banks */
uint32_t Sector; /*!< Initial FLASH sector to erase when Mass erase is disabled
This parameter must be a value of @ref FLASHEx_Sectors */
uint32_t NbSectors; /*!< Number of sectors to be erased.
This parameter must be a value between 1 and (max number of sectors - value of Initial sector)*/
uint32_t VoltageRange;/*!< The device voltage range which defines the erase parallelism
This parameter must be a value of @ref FLASHEx_Voltage_Range */
} FLASH_EraseInitTypeDef;
第一个成员变量代表擦除方式,有两个方式选择:
/** @defgroup FLASHEx_Type_Erase FLASH Type Erase
* @{
*/
#define FLASH_TYPEERASE_SECTORS 0x00000000U /*!< Sectors erase only */
#define FLASH_TYPEERASE_MASSERASE 0x00000001U /*!< Flash Mass erase activation */
这里我选择的是sector方式擦除。可见flash的擦除操作不能单独擦除一个地址上的数据。
第二个成员变量是当进行块擦除的时候使用的,这里不管。
第三个成员变量指定要擦除的sector,F4系列的flash sector划分如下:

第四个变量指定要擦除的sector个数。
我的flash目标地址是0x0807E000,属于Sector 7,所以我只需要擦除一个sector就行了。
第五个变量是flash的工作电压,电压越高,擦除效率越高,一次性擦除的数据就越多,可选项如下:
/** @defgroup FLASHEx_Voltage_Range FLASH Voltage Range
* @{
*/
#define FLASH_VOLTAGE_RANGE_1 0x00000000U /*!< Device operating range: 1.8V to 2.1V */
#define FLASH_VOLTAGE_RANGE_2 0x00000001U /*!< Device operating range: 2.1V to 2.7V */
#define FLASH_VOLTAGE_RANGE_3 0x00000002U /*!< Device operating range: 2.7V to 3.6V */
#define FLASH_VOLTAGE_RANGE_4 0x00000003U /*!< Device operating range: 2.7V to 3.6V + External Vpp */
参考
谢谢你的文章!