一、嵌入式Flash的主要特性以及模块构成
1.主要特性
图源:STM32F4xx中文参考手册 p59
在参考手册中,我们可以了解到,Flash由四部分构成:主存储器块,系统存储器,OTP与选项字节。根据自举模式的选择,可以将程序下载到主存储器块或系统存储器块。
所以,当我们选择主Flash或系统存储器作为自举空间时,程序都会保存到嵌入式Flash中,并在其上运行。
为了提高Flash读取指令的效率,嵌入式Flash提供了自适应实时存储器加速器 (ART Accelerator™)。该加速器通过预读取128位指令的方式,提升程序运行的速度。
图源:STM32F4xx中文参考手册 p62
在CubeMX生成的代码中,HAL_Init已经帮我们实现了这项功能。
2.模块构成
下表展示了嵌入式Flash四个块的空间大小以及地址。(注意,不同型号的嵌入式Flash会有不同。例如,F103型号的嵌入式Flash的读写最小单位是页,总内存是64KB或128KB)
图源:STM32F4xx中文参考手册 p59
当选择主Flash为自举空间时,程序就会下载到主存储器中。然而,主存储器共有1MB的内存,只存储程序会造成空间的浪费。因此,当RAM不够用时,可以将常量存在主存储器中(注意,这里必须是常量,因为只有常量区和代码区才存储在Flash中。有关这部分的内容,可以看我的另一篇文章);也可以将主存储器视为EEPROM,自己向主存储区中读写数据(区别在于EEPROM通过电路结构实现擦除,而读写嵌入式Flash需要自行擦除)。
二、嵌入式Flash的读写函数(HAL库)
1.读写函数涉及到的结构体
/*stm32f4xx_hal_flash_ex.h*/
/**
* @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;
该结构体定义了擦除模式、擦除块、起始擦除扇区、擦除扇区数量与供电电压。在调用HAL_FLASHEx_Erase函数时需要传入实例化的该结构体。
2.写函数
HAL_StatusTypeDef HAL_FLASH_Program(uint32_t TypeProgram, uint32_t Address, uint64_t Data);
HAL_StatusTypeDef HAL_FLASH_Program_IT(uint32_t TypeProgram, uint32_t Address, uint64_t Data);
前者为轮询方式写,后者为中断方式写。第一个参数为写模式,它定义了写的字节数量:双字,字,半字与字节。第二个参数为写入数据的地址,第三个参数为写入的数据。调用一次函数只能写一次值。
3.中断回调函数
void HAL_FLASH_EndOfOperationCallback(uint32_t ReturnValue);
void HAL_FLASH_OperationErrorCallback(uint32_t ReturnValue);
4.读数据
HAL库没有针对读数据的函数,可以自行用指针读取地址上的值。
三、读写程序
1.my_IntFlash.h
/*
STM32F407ZGT6 片上Flash读写驱动
*/
#ifndef __MY_INTFLASH_H__
#define __MY_INTFLASH_H__
#include "main.h"
/*定义片上Flash读写起始地址和结束地址*/
#define INT_FLASH_START_ADDRESS 0x08000000
#define INT_FLASH_END_ADDRESS 0x080FFFFF
/*定义片上Flash主存储器中各扇区的起始地址*/
#define INT_FLASH_SECTOR_0 0x08000000 //0x0800 0000 - 0x0800 3FFF 16KB
#define INT_FLASH_SECTOR_1 0x08004000 //0x0800 4000 - 0x0800 7FFF 16KB
#define INT_FLASH_SECTOR_2 0x08008000 //0x0800 8000 - 0x0800 BFFF 16KB
#define INT_FLASH_SECTOR_3 0x0800C000 //0x0800 C000 - 0x0800 FFFF 16KB
#define INT_FLASH_SECTOR_4 0x08010000 //0x0801 0000 - 0x0801 FFFF 64KB
#define INT_FLASH_SECTOR_5 0x08002000 //0x0802 0000 - 0x0803 FFFF 128KB
#define INT_FLASH_SECTOR_6 0x08004000 //0x0804 0000 - 0x0805 FFFF 128KB
#define INT_FLASH_SECTOR_7 0x08006000 //0x0806 0000 - 0x0807 FFFF 128KB
#define INT_FLASH_SECTOR_8 0x08008000 //0x0808 0000 - 0x0809 FFFF 128KB
#define INT_FLASH_SECTOR_9 0x0800A000 //0x080A 0000 - 0x080B FFFF 128KB
#define INT_FLASH_SECTOR_10 0x0800C000 //0x080C 0000 - 0x080D FFFF 128KB
#define INT_FLASH_SECTOR_11 0x0800E000 //0x080E 0000 - 0x080F FFFF 128KB
/*定义片上Flash扇区总数*/
#define INT_FLASH_TOTAL_SECTOR 12
uint32_t IntFlash_GetSectorAdd(uint8_t SectorInd);
HAL_StatusTypeDef IntFlash_Write8b(uint32_t SectorInd, uint8_t *Data, uint32_t DataSize);
HAL_StatusTypeDef IntFlash_Write16b(uint32_t SectorInd, uint16_t *Data, uint32_t DataSize);
HAL_StatusTypeDef IntFlash_Write32b(uint32_t SectorInd, uint32_t *Data, uint32_t DataSize);
HAL_StatusTypeDef IntFlash_Read8b(uint32_t Address, uint8_t *Data, uint32_t DataSize);
HAL_StatusTypeDef IntFlash_Read16b(uint32_t Address, uint16_t *Data, uint32_t DataSize);
HAL_StatusTypeDef IntFlash_Read32b(uint32_t Address, uint32_t *Data, uint32_t DataSize);
#endif
2.my_IntFlash.c
/*
STM32F407ZGT6 片上Flash读写驱动
*/
#include "my_IntFlash.h"
#include "my_usart.h"
/*片上Flash扇区容量,单位KB,依据实际情况而定*/
static uint8_t SectorSize[INT_FLASH_TOTAL_SECTOR] = {16, 16, 16, 16, 64, 128, 128, 128, 128, 128, 128, 128};
/**
* @brief 获取扇区地址
* @param SectorInd 扇区序号,范围0~INTFLASH_TOTAL_SECTOR - 1
* @return 扇区地址,返回值为0xFFFFFFFF表示扇区下标越界
*/
uint32_t IntFlash_GetSectorAdd(uint8_t SectorInd)
{
if (SectorInd >= INT_FLASH_TOTAL_SECTOR)
return 0xFFFFFFFF;
uint32_t GlobalAddress = INT_FLASH_START_ADDRESS;
for (int i = 0 ; i < SectorInd ; i++)
{
GlobalAddress += SectorSize[i] * 1024;
}
return GlobalAddress;
}
/**
* @brief 向片上Flash写入8位数据,选择扇区时应避免选择存放程序的扇区
* @param SectorInd 扇区序号,范围0~INTFLASH_TOTAL_SECTOR - 1
* @param Data 待写入的8位数据
* @return HAL_OK表示写入成功,HAL_ERROR表示写入失败
*/
HAL_StatusTypeDef IntFlash_Write8b(uint32_t SectorInd, uint8_t *Data, uint32_t DataSize)
{
/*获取第SectorInd扇区的地址*/
uint32_t Address = IntFlash_GetSectorAdd(SectorInd);
if (Address == 0xFFFFFFFF)
return HAL_ERROR;
/*数据量过多则返回错误状态*/
if (Address + DataSize - 1 > INT_FLASH_END_ADDRESS)
return HAL_ERROR;
/*计算擦除扇区的数量*/
uint32_t SectorCount = 1;
uint32_t Temp_DataSize = DataSize, Temp_SectorInd = SectorInd;
while (Temp_DataSize > SectorSize[Temp_SectorInd] * 1024)
{
Temp_DataSize -= SectorSize[Temp_SectorInd] * 1024;
Temp_SectorInd++;
SectorCount++;
}
/*擦除扇区*/
HAL_FLASH_Unlock();
FLASH_EraseInitTypeDef pEraseInit;
uint32_t SectorError;
pEraseInit.Banks = FLASH_BANK_1;
pEraseInit.TypeErase = FLASH_TYPEERASE_SECTORS;
pEraseInit.VoltageRange = FLASH_VOLTAGE_RANGE_3;
pEraseInit.NbSectors = SectorCount;
pEraseInit.Sector = SectorInd;
HAL_StatusTypeDef status = HAL_FLASHEx_Erase(&pEraseInit, &SectorError);
if (status != HAL_OK)
{
HAL_FLASH_Lock();
printf("Erase Error!\n");
return HAL_ERROR;
}
/*写入8位数据*/
for (int i = 0 ; i < DataSize ; i++)
{
HAL_FLASH_Program(FLASH_TYPEPROGRAM_BYTE, Address, Data[i]);
Address++;
}
HAL_FLASH_Lock();
return HAL_OK;
}
/**
* @brief 向片上Flash写入16位数据,选择扇区时应避免选择存放程序的扇区
* @param SectorInd 扇区序号,范围0~INTFLASH_TOTAL_SECTOR - 1
* @param Data 待写入的16位数据
* @return HAL_OK表示写入成功,HAL_ERROR表示写入失败
*/
HAL_StatusTypeDef IntFlash_Write16b(uint32_t SectorInd, uint16_t *Data, uint32_t DataSize)
{
/*获取第SectorInd扇区的地址*/
uint32_t Address = IntFlash_GetSectorAdd(SectorInd);
if (Address == 0xFFFFFFFF)
return HAL_ERROR;
/*数据量过多则返回错误状态*/
if (Address + DataSize * 2 - 1 > INT_FLASH_END_ADDRESS)
return HAL_ERROR;
/*计算擦除扇区的数量*/
uint32_t SectorCount = 1;
uint32_t Temp_DataSize = DataSize * 2, Temp_SectorInd = SectorInd;
while (Temp_DataSize > SectorSize[Temp_SectorInd] * 1024)
{
Temp_DataSize -= SectorSize[Temp_SectorInd] * 1024;
Temp_SectorInd++;
SectorCount++;
}
/*擦除扇区*/
HAL_FLASH_Unlock();
FLASH_EraseInitTypeDef pEraseInit;
uint32_t SectorError;
pEraseInit.Banks = FLASH_BANK_1;
pEraseInit.TypeErase = FLASH_TYPEERASE_SECTORS;
pEraseInit.VoltageRange = FLASH_VOLTAGE_RANGE_3;
pEraseInit.NbSectors = SectorCount;
pEraseInit.Sector = SectorInd;
HAL_StatusTypeDef status = HAL_FLASHEx_Erase(&pEraseInit, &SectorError);
if (status != HAL_OK)
{
HAL_FLASH_Lock();
printf("Erase Error!\n");
return HAL_ERROR;
}
/*写入16位数据*/
for (int i = 0; i < DataSize; i++)
{
HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, Address, Data[i]);
Address += 2;
}
HAL_FLASH_Lock();
return HAL_OK;
}
/**
* @brief 向片上Flash写入32位数据,选择扇区时应避免选择存放程序的扇区
* @param SectorInd 扇区序号,范围0~INTFLASH_TOTAL_SECTOR - 1
* @param Data 待写入的32位数据
* @return HAL_OK表示写入成功,HAL_ERROR表示写入失败
*/
HAL_StatusTypeDef IntFlash_Write32b(uint32_t SectorInd, uint32_t *Data, uint32_t DataSize)
{
/*获取第SectorInd扇区的地址*/
uint32_t Address = IntFlash_GetSectorAdd(SectorInd);
if (Address == 0xFFFFFFFF)
return HAL_ERROR;
/*数据量过多则返回错误状态*/
if (Address + DataSize * 4 - 1 > INT_FLASH_END_ADDRESS)
return HAL_ERROR;
/*计算擦除扇区的数量*/
uint32_t SectorCount = 1;
uint32_t Temp_DataSize = DataSize * 4, Temp_SectorInd = SectorInd;
while (Temp_DataSize > SectorSize[Temp_SectorInd] * 1024)
{
Temp_DataSize -= SectorSize[Temp_SectorInd] * 1024;
Temp_SectorInd++;
SectorCount++;
}
/*擦除扇区*/
HAL_FLASH_Unlock();
FLASH_EraseInitTypeDef pEraseInit;
uint32_t SectorError;
pEraseInit.Banks = FLASH_BANK_1;
pEraseInit.TypeErase = FLASH_TYPEERASE_SECTORS;
pEraseInit.VoltageRange = FLASH_VOLTAGE_RANGE_3;
pEraseInit.NbSectors = SectorCount;
pEraseInit.Sector = SectorInd;
HAL_StatusTypeDef status = HAL_FLASHEx_Erase(&pEraseInit, &SectorError);
if (status != HAL_OK)
{
HAL_FLASH_Lock();
printf("Erase Error!\n");
return HAL_ERROR;
}
/*写入32位数据*/
for (int i = 0; i < DataSize; i++)
{
HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, Address, Data[i]);
Address += 4;
}
HAL_FLASH_Lock();
return HAL_OK;
}
/**
* @brief 向片上Flash写入8位数据,地址参数应避免越界
* @param Address 起始地址
* @param Data 待写入的8位数据
* @return HAL_OK表示写入成功,HAL_ERROR表示写入失败
*/
HAL_StatusTypeDef IntFlash_Read8b(uint32_t Address, uint8_t *Data, uint32_t DataSize)
{
if (Address + DataSize - 1 > INT_FLASH_END_ADDRESS)
return HAL_ERROR;
uint32_t DataCursor = 0;
for (int i = 0 ; i < DataSize ; i++)
{
Data[DataCursor] = *(__IO uint8_t *)Address;
Address++;
DataCursor++;
}
return HAL_OK;
}
/**
* @brief 向片上Flash写入16位数据,地址参数应避免越界
* @param Address 起始地址
* @param Data 待写入的16位数据
* @return HAL_OK表示写入成功,HAL_ERROR表示写入失败
*/
HAL_StatusTypeDef IntFlash_Read16b(uint32_t Address, uint16_t *Data, uint32_t DataSize)
{
if (Address + DataSize * 2 - 1 > INT_FLASH_END_ADDRESS)
return HAL_ERROR;
uint32_t DataCursor = 0;
for (int i = 0; i < DataSize; i++)
{
Data[DataCursor] = *(__IO uint16_t *)Address;
Address += 2;
DataCursor++;
}
return HAL_OK;
}
/**
* @brief 向片上Flash写入32位数据,地址参数应避免越界
* @param Address 起始地址
* @param Data 待写入的32位数据
* @return HAL_OK表示写入成功,HAL_ERROR表示写入失败
*/
HAL_StatusTypeDef IntFlash_Read32b(uint32_t Address, uint32_t *Data, uint32_t DataSize)
{
if (Address + DataSize * 4 - 1 > INT_FLASH_END_ADDRESS)
return HAL_ERROR;
uint32_t DataCursor = 0;
for (int i = 0; i < DataSize; i++)
{
Data[DataCursor] = *(__IO uint32_t *)Address;
Address += 4;
DataCursor++;
}
return HAL_OK;
}
四、使用__attribute__将常量数组储存在Flash中
1.定义数组前的空间占用
2.定义数组后的空间占用
static const uint16_t Buffer[1024] __attribute__((at(INT_FLASH_SECTOR_9))) = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
1024个16位数组,占用ROM空间约为2KB。可为什么ROM从8.8KB跳变到42.0KB而不是10.8KB呢?别急,我们把数组改大1倍看看。
static const uint16_t Buffer[2048] __attribute__((at(INT_FLASH_SECTOR_9))) = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
再次编译,ROM从42KB到了44KB,说明多出来的1024个16位数组的确占用了2KB,那么为什么会出现8.8到42的跳变呢?重新把数组大小改为1024。我们打开.map一探究竟。
左边为定义数组前的map文件,右边为定义数组后的map文件
从图中可以看到,定义数组的大小是0x800,也就是2048个字节,这与我们想法是一致的。关键在于多出来的PAD,占用了0x7cc4,也就是31KB。而42-8.8=33.2KB,这就说明了多余占用的空间主要就是由PAD引起的。而PAD起到的就是字节对齐的作用。因为我们申请存放常量数组的空间是第9扇区。为了保证字节对齐,就会出现PAD进行强制对齐。
当我们把存放常量数组的空间改成第6扇区时,PAD占用的空间也发生了改变。
五、注意事项
由于主Flash首先起到存放程序的作用,读写数据和存放常量数组时应注意避开存放程序的扇区,避免发生程序故障的问题。