【STM32】嵌入式(片上)Flash的读写(以STM32F407ZGT6为例,HAL库)

一、嵌入式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首先起到存放程序的作用,读写数据和存放常量数组时应注意避开存放程序的扇区,避免发生程序故障的问题。

  • 15
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Bahair_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值