STM32 HAL库之配置数据FLASH存储

设计需求

在项目开发中,经常需要保存配置数据至非易失性存储介质中(Flash 或EEPROM中),以便上电重启后获取配置信息。常见的STM32开发中,一般将数据存储在FLASH、备份区域寄存器、外挂EEPROM。由于内部Flash不能像EEPROM一样直接按字节进行读写操作,地址数据只能由1变为0,对固定地址重复写入,必须擦除整页使得地址数据为全FF,才可重新写入数据,通常为了提高内部FLASH的擦除寿命,一般采用内部flash模拟EEPROM方法。

FLASH概述

本文以STM32F103VET6为例,介绍其内部512KByte的flash存储器。该处理器flash单元主要包括三个部分:存储单元、信息块部分、存储器相关控制寄存器。存储单元以page的形式组织,每页2Kbyte,共256页。信息块部分包括system memory(2Kbyte)和option byte(16Byte),system memory单元主要用于存储ST公司固化的bootloader,实现USART1串口更新用户程序。STM32F103VET6内部flash地址分配如下表所示。

需要注意的几点问题:

(1)、擦除和编程FLASH所需的高压是由STM32内部电路产生,且在此过程中内部晶振HSI需开启;

(2)、页写保护和读保护;

(3)、在写操作过程中,读操作将被挂起,待写操作完成后进行读操作。

2、Flash控制器FPEC ---flash programming and erase controller

默认情况下,内部的flash的FPEC和控制寄存器是处于写保护状态,要实现对FLASH的写操作,则首先unlock 存储器,即向寄存器FLASH_KEYR顺序写入两个key值。

KEY1 = 0x45670123

KEY2 = 0xCDEF89AB

FPEC控制器解锁后,通过查询FLASH_SR_BUSY位检查当前工作状态,配合FLASH_CR_PG、FLASH_CR_PER、FLASH_CR_MER分别实现对存储器的编程、页擦除、块擦除。

对于option byte的编程和擦除操作,首先对option byte部分解锁,即向FLASH_OPTKEY写入key值,配合FLASH_CR_OPTPG、FLASH_CR_OPTER实现对option byte进行编程和擦除操作。

3、option byte

选项字节主要用于配置flash的读写保护及看门狗,其分配地址如下:

RDP和WRPx分别用于内部flash的读保护、页写保护设置。系统复位后,the option byte loader---OBL自动加载option byte,并保存option byte 的部分数据至寄存器FLASH_OBR和FLASH_WRPR。RDP默认值为RDPRT key = 0x00A5,即处于非读保护状态。WRPx每位管理着2页的写保护状态。

上图可以看出:WRP0(对应寄存器FLASH_WRPR[7:0])的每位控制2页的写保护状态,共计16页;

WRP1(对应寄存器FLASH_WRPR[15:0])的每位控制2页的写保护状态,共计16页;

WRP2(对应寄存器FLASH_WRPR[23:16])的每位控制2页的写保护状态,共计16页;

WRP3(对应寄存器FLASH_WRPR[30:24])的每位控制2页的写保护状态,共计14页;

WRP3的最高位(对应寄存器FLASH_WRPR[31])控制页62至255页。

默认情况下,寄存器FLASH_WRPR的值均为0,即存储器处于写保护状态。若解除某一页存储器的写保护状态,需要对option byte进行擦除,并进行编程操作,完成后复位系统。

cpp 复制代码
#define OB_BASE               ((uint32_t)0x1FFFF800)    /*!< Flash Option Bytes base address */

typedef struct
{
  __IO uint16_t RDP;
  __IO uint16_t USER;
  __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) 

4、访问存储器时间latency的设置

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }

应用1---操作FLASH

使用__attribute__后,编译器自动将将所需的配置信息固化Flash地址中。

cpp 复制代码
const uint16_t Version __attribute__((at(0x08010000)))= 0x1234;
const uint32_t Device_SN __attribute__((at(0x08010004))) = 0x55678911;

重新变更FLASH中的SN序列号。

cpp 复制代码
  /* USER CODE BEGIN 2 */
	FlashWrite(FLASH_TYPEPROGRAM_WORD, 0x08010004, 0x88992266);
  /* USER CODE END 2 */
cpp 复制代码
void FlashWrite(uint32_t TypeProgram, uint32_t Address, uint64_t Data)
{
	FLASH_EraseInitTypeDef EraseInitstruct =
	{
			.TypeErase = FLASH_TYPEERASE_PAGES,
			.NbPages = 1,
			.PageAddress = Address
	};
	uint32_t PageError = 0;
	HAL_FLASH_Unlock();
	HAL_FLASHEx_Erase(&EraseInitstruct, &PageError);
	HAL_FLASH_Program(TypeProgram, Address, Data);
	HAL_FLASH_Lock();
}

从上面的结果可以看到,对SN序列号的更改导致了version也变成FF了,这是因为对flash地址数据变更时,必须对整页进行擦除。

应用2 --- FLASH模拟EEPROM

先聊聊本质。从应用1来看,要像读写EEPROM一样对FLASH固定地址的数据进行写操作,必须擦除整页的数据,而flash的擦除寿命是有限的,频繁的擦除势必会对MCU折损。flash模拟EEPROM策略就是对外提供虚拟物理地址,表面上看,可对同一地址进行重复写入,实质是在一页中按地址写入,当一页写满时,拷贝此页有效数据至另一页,并对此页进行整页擦除。以2K/页来说,4096/4 = 1024,即写入1024次后才会擦除一页,这样大大降低了FLASH擦除次数。

Flash模拟EEPROM接口(官网示例)

cpp 复制代码
uint16_t EE_Init(void);
uint16_t EE_ReadVariable(uint16_t VirtAddress, uint16_t* Data);
uint16_t EE_WriteVariable(uint16_t VirtAddress, uint16_t Data);

使用时需要重新定义如下几个条目。NumOfVar是写入uint16型的变量数目,VirtAddVarTab数组是定义的虚拟地址。

cpp 复制代码
#define PAGE_SIZE  (uint16_t)0x800  /* Page size = 2KByte */

/* EEPROM start address in Flash */
#define EEPROM_START_ADDRESS    ((uint32_t)0x08010000) 

#define NumbOfVar               ((uint8_t)0x03)
/* Virtual address defined by the user: 0xFFFF value is prohibited */
uint16_t VirtAddVarTab[NumbOfVar] = {0x01, 0x02, 0x03};

由于官方例子不是使用HAL库函数,需要在EEPROM源文件做一下封装兼容。

cpp 复制代码
#define FLASH_COMPLETE  HAL_OK
#define FLASH_Status    HAL_StatusTypeDef

FLASH_Status FLASH_ErasePage(uint32_t PageAddr)
{
    uint32_t PageError = 0;
    FLASH_EraseInitTypeDef EraseInitstruct =
    {
        .TypeErase = FLASH_TYPEERASE_PAGES,
        .NbPages = 1,
        .PageAddress = PAGE0_BASE_ADDRESS
    };

    EraseInitstruct.PageAddress = PageAddr;
    return HAL_FLASHEx_Erase(&EraseInitstruct, &PageError);
}

#define FLASH_ProgramHalfWord(PageAddr, Data) \
        HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, (PageAddr), (Data))

读写示例

cpp 复制代码
	HAL_FLASH_Unlock();
	EE_Init();

	EE_WriteVariable(0x01, 0x3344);
	EE_WriteVariable(0x02, 0x5671);
	EE_WriteVariable(0x03, 0x8899);
	HAL_FLASH_Lock();
cpp 复制代码
EE_ReadVariable(0x03, &ReadDat);

应用3 ------ 程序加密

待发.....

相关推荐
御风_2122 分钟前
STM32单片机使用CAN协议进行通信
stm32·单片机·嵌入式硬件
逝灮1 小时前
【蓝桥杯——物联网设计与开发】拓展模块3 - 温度传感器模块
驱动开发·stm32·单片机·嵌入式硬件·物联网·蓝桥杯·温度传感器
Anin蓝天(北京太速科技-陈)2 小时前
271-基于XC7V690T的12路光纤PCIe接口卡
嵌入式硬件·fpga开发
Wallace Zhang3 小时前
STM32F407 | Embedded IDE01 - vscode搭建Embedded IDE开发环境(支持JLINK、STLINK、DAPLINK)
ide·vscode·stm32
木宁kk4 小时前
嵌入式硬件面试题
嵌入式硬件
小菜鸟学代码··5 小时前
STM32相关知识及其创建工程
stm32·单片机·嵌入式硬件
电气_空空7 小时前
基于单片机的病房呼叫系统设计
单片机·嵌入式硬件·毕业设计·毕设
柒月玖.8 小时前
基于AT89C52单片机的6位电子密码锁设计
单片机·嵌入式硬件
Lay_鑫辰16 小时前
禾川HCQ1系列PAC脉冲控制步进驱动器
运维·人工智能·单片机·嵌入式硬件·自动化