imuncle.github.io icon indicating copy to clipboard operation
imuncle.github.io copied to clipboard

STM32 flash读写

Open imuncle opened this issue 6 years ago • 1 comments
trafficstars

随着代码结构的完善,越来越希望代码更加智能化,调参的步骤越少越好,要是啥也不用调就更好了。。。

回忆起以往看过的代码,感觉智能化第一步就是搞定flash读写。有了flash之后,一切都变得很方便了。

比如每辆车的电机安装方式不一样,那么云台中间位置就不一样,如果有了flash,直接运行校准程序,云台左右分别碰到限位开关,自动计算出中间位置,pitch轴直接用手扶正,靠陀螺仪判断是否水平,水平的时候就记下当前位置,写入flash中,不用再慢吞吞地进调试,手动记录中间位置了。

又比如现在都流行自己搞上位机,上位机校准的关键一点就是单片机需要存储上位机的信息,下次上电就可以直接使用了。

所以flash多好啊。

flash

首先我们需要了解一个内存映射:

image

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进行编写程序时,其编程地址的设置一般是这样的:

image

程序的写入地址从0x08000000(数好零的个数)开始的,其大小为0x100000也就是1024K的空间,换句话说就是告诉编译器flash的空间是从0x08000000-0x08100000。这与STM32的内存地址映射关系是对应的。

内部flash的构成

STM32 的内部 FLASH 包含主存储器、系统存储器、 OTP 区域以及选项字节区域,它们的地址分布及大小如下:

image

  • 主存储器:一般我们说 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开头的记录:

image

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

image

  • 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划分如下:

image

第四个变量指定要擦除的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 */

参考

imuncle avatar Apr 07 '19 15:04 imuncle

谢谢你的文章!

StanFCN avatar Apr 17 '24 07:04 StanFCN