STM32标准库+HAL库 | CPU片内FLASH存储器数据掉电读写

一、片内FLASH

        在STM32芯片内部有一个FLASH存储器,它主要用于存储代码,我们在电脑上编写好应用程序后,使用下载器把编译后的代码文件烧录到该内部FLASH中, 由于FLASH存储器的内容在掉电后不会丢失,芯片重新上电复位后,内核可从内部FLASH中加载代码并运行。

        从下图所示的官方数据手册可知,STM32的Flash地址起始0x0800 0000结束地址是0x0800 0000加上芯片实际的Flash大小,不同的芯片Flash大小不同,FLASH一般用来存储代码和一些定义为const的数据断电不丢失。RAM起始地址是0x2000 0000,结束地址是0x2000 0000加上芯片的RAM大小,不同的芯片RAM也不同,是MCU的内存,用来存储代码运行时的数据,变量等等,掉电数据丢失。

        对FLASH进行操作时,有必要提前知道FLASH的内存大小,方便后面芯片选型和开发过程中对FLASH数据读写,掉电保存等操作。如下图可知,STM32内部FLASH的容量类型可根据它的型号名确定,本次HAL库使用的STM32G431RBT6芯片,其FLASH空间大小为128KB;标准库使用的是STM32F407VET6芯片,其FLASH空间大小为512KB。

        如下图所示,是从某元器件商城查询到的常用AT24C02及W25Q16系列存储器的价格,如果只是做单个原型设备,那么一个小存储器芯片的价格可能是不痛不痒的但对能进行大批量生产的电子产品,成本压缩几毛钱,都能创造一笔不菲的收入,甚至节约下来的成本可以供很多工程师的月工资。因此对于数据存储量不是很大的设备产品,就可以考虑直接使用MCU内置的FLASH进行数据掉电存储读写。

         因此如果MCU内部FLASH存储了应用程序后还有剩余的空间,我们可以把它像外部SPI-FLASH那样利用起来,存储一些程序运行时产生的需要掉电保存的数据。由于访问内部FLASH的速度要比外部的SPI-FLASH快得多,所以在紧急状态下常常会使用内部FLASH存储关键记录;为了防止应用程序被抄袭, 有的应用会禁止读写内部FLASH中的内容,或者在第一次运行时计算加密信息并记录到某些区域,然后删除自身的部分加密代码,这些应用都涉及到内部FLASH的操作。

二、FLASH读写编程思路

1、写Flash思路

0、确定写数据地址

1、FLASH解锁

2、擦除待写区域数据

3、写入数据

4、FLASH上锁

写Flash时会用到的HAL库API接口:

//对 FLASH 进行写操作前必须先解锁,解锁操作也就是必须在 FLASH_KEYR 寄存器写入特定的序列;有解锁当然就有上锁,为了保护Flash,读写和擦除全部完需要的Flash空间后,需要上锁操作。
//FLASH解锁
HAL_StatusTypeDef HAL_FLASH_Unlock(void);

//擦除数据
void FLASH_PageErase(uint32_t Page, uint32_t Banks);
void FLASH_MassErase(uint32_t Banks);

//写数据
HAL_StatusTypeDef HAL_FLASH_Program(uint32_t TypeProgram, uint32_t Address, uint64_t Data);
void FLASH_Program_DoubleWord(uint32_t Address, uint64_t Data);
void FLASH_Program_Fast(uint32_t Address, uint32_t DataAddress);

//FLASH上锁
HAL_StatusTypeDef HAL_FLASH_Lock(void);

写FLASH时的标准库API接口:

//FLASH解锁
void FLASH_Unlock(void);

//擦除数据
FLASH_Status FLASH_EraseSector(uint32_t FLASH_Sector, uint8_t VoltageRange);
FLASH_Status FLASH_EraseAllSectors(uint8_t VoltageRange);
FLASH_Status FLASH_EraseAllBank1Sectors(uint8_t VoltageRange);
FLASH_Status FLASH_EraseAllBank2Sectors(uint8_t VoltageRange);

//写数据
FLASH_Status FLASH_ProgramDoubleWord(uint32_t Address, uint64_t Data);
FLASH_Status FLASH_ProgramWord(uint32_t Address, uint32_t Data);
FLASH_Status FLASH_ProgramHalfWord(uint32_t Address, uint16_t Data);
FLASH_Status FLASH_ProgramByte(uint32_t Address, uint8_t Data);

//FLASH上锁
void FLASH_Lock(void);

提醒:同一个库的不同版本,API的命名也能会有变动。不同芯片其库内的函数封装也可能存在差异。

2、读Flash数据

1、确定读数据地址

2、指针偏移间接读取

3、读取数据成功

三、HAL库FLASH读写

①、flash.c

#include "flash.h"



//STM32G431RBT6的FLASH为128KB,因此FLASH地址起始地址:0x0800 0000,结束地址是:0x0802 0000

/**
  * @brief  HAL库版写一个uint64_t类型的数据
  * @param  addr: 存储数据的地址
  * @param  data: 写入的数据
  * @retval 成功返回0, 失败返回-1
  */
int Flash_HAL_Write_Data(uint32_t addr, uint64_t data)
{
    //1、FLASH解锁
    HAL_FLASH_Unlock();
    __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_OPTVERR);
    //2、FLASH擦除
    FLASH_EraseInitTypeDef EraseInitStruct;
    EraseInitStruct.TypeErase = FLASH_TYPEERASE_PAGES;    //页擦除
    EraseInitStruct.Banks = FLASH_BANK_1;
    EraseInitStruct.Page = 15-1;    //从第几个页开始擦除(0开始)
    EraseInitStruct.NbPages = 5;    //擦除多少个页
    uint32_t PageError = 0;            //记录擦除出错时的起始地址
    if(HAL_FLASHEx_Erase(&EraseInitStruct, &PageError)!=HAL_OK)
    {
        printf("FLASH擦除出错,开始出错地址:%#x\r\n", PageError);
        return -1;
    }
    //3、FLASH写入
    if(HAL_FLASH_Program(TYPEPROGRAM_DOUBLEWORD, addr, data)!=HAL_OK)
    {
        printf("FLASH写入失败\r\n");
        return -1;
    }
    //4、FLASH上锁
    HAL_FLASH_Lock();
    return 0;
}

/**
  * @brief  HAL库版写N个uint64_t类型的数据
  * @param  addr: 存储数据的地址
  * @param  data: 数据数组
  * @param  num: 数据的个数
  * @retval 成功返回0, 失败返回-1
  */
int Flash_HAL_Write_N_Data(uint32_t addr, uint64_t *data, uint16_t num)
{
    //1、FLASH解锁
    HAL_FLASH_Unlock();
    __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_OPTVERR);
    
    //2、FLASH擦除
    FLASH_EraseInitTypeDef EraseInitStruct;
    EraseInitStruct.TypeErase = FLASH_TYPEERASE_PAGES;    //页擦除
    EraseInitStruct.Banks = FLASH_BANK_1;
    EraseInitStruct.Page = 15-1;    //从第几个页开始擦除(0开始)
    EraseInitStruct.NbPages = 5;    //擦除多少个页
    uint32_t PageError = 0;            //记录擦除出错时的起始地址
    if(HAL_FLASHEx_Erase(&EraseInitStruct, &PageError)!=HAL_OK)
    {
        printf("FLASH擦除出错,开始出错地址:%#x\r\n", PageError);
        return -1;
    }
    //3、FLASH写入
    for(uint16_t i=0; i<num; i++)
    {
        if(HAL_FLASH_Program(TYPEPROGRAM_DOUBLEWORD, addr, data[i])!=HAL_OK)
        {
            printf("FLASH写入失败\r\n");
            return -1;
        }
        addr += sizeof(uint64_t);
    }
    //4、FLASH上锁
    HAL_FLASH_Lock();    
    return 0;
}

/**
  * @brief  HAL库版读取N个uint64_t类型的数据
  * @param  addr: 读取数据的地址(用户空间的地址)
  * @param  data: 数据数组
  * @param  num: 数据的个数
  * @retval NONE
  */
void Flash_HAL_Read_N_Data(uint32_t addr, uint64_t *data, uint32_t num)
{

    for(uint32_t i=0; i<num; i++)
    {
        data[i] = *(volatile uint64_t*)addr;
        addr += sizeof(uint64_t);//根据读取的数据类型进行内存地址递增
    }
}

/**
  * @brief  HAL库版读取N个uint8_t类型的数据
  * @param  addr: 读取数据的地址
  * @param  data: 数据数组
  * @param  num: 数据的个数
  * @retval NONE
  */
void Flash_HAL_Read_N_Byte(uint32_t addr, uint8_t *data, uint32_t num)
{

    for(uint32_t i=0; i<num; i++)
    {
        data[i] = *(volatile uint8_t*)addr;
        addr += sizeof(uint8_t);//根据读取的数据类型进行内存地址递增
    }
}

②、flash.h

#ifndef __FLASH_H
#define __FLASH_H

#include "stm32g4xx_hal.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
//STM32G431RBT6的FLASH大小为128KB,只有63个页
#define ADDR_FLASH_PAGE_0     ((uint32_t)0x08000000) /* Base @ of Page 0, 2 Kbytes */
#define ADDR_FLASH_PAGE_1     ((uint32_t)0x08000800) /* Base @ of Page 1, 2 Kbytes */
#define ADDR_FLASH_PAGE_2     ((uint32_t)0x08001000) /* Base @ of Page 2, 2 Kbytes */
#define ADDR_FLASH_PAGE_3     ((uint32_t)0x08001800) /* Base @ of Page 3, 2 Kbytes */
#define ADDR_FLASH_PAGE_4     ((uint32_t)0x08002000) /* Base @ of Page 4, 2 Kbytes */
#define ADDR_FLASH_PAGE_5     ((uint32_t)0x08002800) /* Base @ of Page 5, 2 Kbytes */
#define ADDR_FLASH_PAGE_6     ((uint32_t)0x08003000) /* Base @ of Page 6, 2 Kbytes */
#define ADDR_FLASH_PAGE_7     ((uint32_t)0x08003800) /* Base @ of Page 7, 2 Kbytes */
#define ADDR_FLASH_PAGE_8     ((uint32_t)0x08004000) /* Base @ of Page 8, 2 Kbytes */
#define ADDR_FLASH_PAGE_9     ((uint32_t)0x08004800) /* Base @ of Page 9, 2 Kbytes */
#define ADDR_FLASH_PAGE_10    ((uint32_t)0x08005000) /* Base @ of Page 10, 2 Kbytes */
#define ADDR_FLASH_PAGE_11    ((uint32_t)0x08005800) /* Base @ of Page 11, 2 Kbytes */
#define ADDR_FLASH_PAGE_12    ((uint32_t)0x08006000) /* Base @ of Page 12, 2 Kbytes */
#define ADDR_FLASH_PAGE_13    ((uint32_t)0x08006800) /* Base @ of Page 13, 2 Kbytes */
#define ADDR_FLASH_PAGE_14    ((uint32_t)0x08007000) /* Base @ of Page 14, 2 Kbytes */
#define ADDR_FLASH_PAGE_15    ((uint32_t)0x08007800) /* Base @ of Page 15, 2 Kbytes */
#define ADDR_FLASH_PAGE_16    ((uint32_t)0x08008000) /* Base @ of Page 16, 2 Kbytes */
#define ADDR_FLASH_PAGE_17    ((uint32_t)0x08008800) /* Base @ of Page 17, 2 Kbytes */
#define ADDR_FLASH_PAGE_18    ((uint32_t)0x08009000) /* Base @ of Page 18, 2 Kbytes */
#define ADDR_FLASH_PAGE_19    ((uint32_t)0x08009800) /* Base @ of Page 19, 2 Kbytes */
#define ADDR_FLASH_PAGE_20    ((uint32_t)0x0800A000) /* Base @ of Page 20, 2 Kbytes */
#define ADDR_FLASH_PAGE_21    ((uint32_t)0x0800A800) /* Base @ of Page 21, 2 Kbytes */
#define ADDR_FLASH_PAGE_22    ((uint32_t)0x0800B000) /* Base @ of Page 22, 2 Kbytes */
#define ADDR_FLASH_PAGE_23    ((uint32_t)0x0800B800) /* Base @ of Page 23, 2 Kbytes */
#define ADDR_FLASH_PAGE_24    ((uint32_t)0x0800C000) /* Base @ of Page 24, 2 Kbytes */
#define ADDR_FLASH_PAGE_25    ((uint32_t)0x0800C800) /* Base @ of Page 25, 2 Kbytes */
#define ADDR_FLASH_PAGE_26    ((uint32_t)0x0800D000) /* Base @ of Page 26, 2 Kbytes */
#define ADDR_FLASH_PAGE_27    ((uint32_t)0x0800D800) /* Base @ of Page 27, 2 Kbytes */
#define ADDR_FLASH_PAGE_28    ((uint32_t)0x0800E000) /* Base @ of Page 28, 2 Kbytes */
#define ADDR_FLASH_PAGE_29    ((uint32_t)0x0800E800) /* Base @ of Page 29, 2 Kbytes */
#define ADDR_FLASH_PAGE_30    ((uint32_t)0x0800F000) /* Base @ of Page 30, 2 Kbytes */
#define ADDR_FLASH_PAGE_31    ((uint32_t)0x0800F800) /* Base @ of Page 31, 2 Kbytes */
#define ADDR_FLASH_PAGE_32    ((uint32_t)0x08010000) /* Base @ of Page 32, 2 Kbytes */
#define ADDR_FLASH_PAGE_33    ((uint32_t)0x08010800) /* Base @ of Page 33, 2 Kbytes */
#define ADDR_FLASH_PAGE_34    ((uint32_t)0x08011000) /* Base @ of Page 34, 2 Kbytes */
#define ADDR_FLASH_PAGE_35    ((uint32_t)0x08011800) /* Base @ of Page 35, 2 Kbytes */
#define ADDR_FLASH_PAGE_36    ((uint32_t)0x08012000) /* Base @ of Page 36, 2 Kbytes */
#define ADDR_FLASH_PAGE_37    ((uint32_t)0x08012800) /* Base @ of Page 37, 2 Kbytes */
#define ADDR_FLASH_PAGE_38    ((uint32_t)0x08013000) /* Base @ of Page 38, 2 Kbytes */
#define ADDR_FLASH_PAGE_39    ((uint32_t)0x08013800) /* Base @ of Page 39, 2 Kbytes */
#define ADDR_FLASH_PAGE_40    ((uint32_t)0x08014000) /* Base @ of Page 40, 2 Kbytes */
#define ADDR_FLASH_PAGE_41    ((uint32_t)0x08014800) /* Base @ of Page 41, 2 Kbytes */
#define ADDR_FLASH_PAGE_42    ((uint32_t)0x08015000) /* Base @ of Page 42, 2 Kbytes */
#define ADDR_FLASH_PAGE_43    ((uint32_t)0x08015800) /* Base @ of Page 43, 2 Kbytes */
#define ADDR_FLASH_PAGE_44    ((uint32_t)0x08016000) /* Base @ of Page 44, 2 Kbytes */
#define ADDR_FLASH_PAGE_45    ((uint32_t)0x08016800) /* Base @ of Page 45, 2 Kbytes */
#define ADDR_FLASH_PAGE_46    ((uint32_t)0x08017000) /* Base @ of Page 46, 2 Kbytes */
#define ADDR_FLASH_PAGE_47    ((uint32_t)0x08017800) /* Base @ of Page 47, 2 Kbytes */
#define ADDR_FLASH_PAGE_48    ((uint32_t)0x08018000) /* Base @ of Page 48, 2 Kbytes */
#define ADDR_FLASH_PAGE_49    ((uint32_t)0x08018800) /* Base @ of Page 49, 2 Kbytes */
#define ADDR_FLASH_PAGE_50    ((uint32_t)0x08019000) /* Base @ of Page 50, 2 Kbytes */
#define ADDR_FLASH_PAGE_51    ((uint32_t)0x08019800) /* Base @ of Page 51, 2 Kbytes */
#define ADDR_FLASH_PAGE_52    ((uint32_t)0x0801A000) /* Base @ of Page 52, 2 Kbytes */
#define ADDR_FLASH_PAGE_53    ((uint32_t)0x0801A800) /* Base @ of Page 53, 2 Kbytes */
#define ADDR_FLASH_PAGE_54    ((uint32_t)0x0801B000) /* Base @ of Page 54, 2 Kbytes */
#define ADDR_FLASH_PAGE_55    ((uint32_t)0x0801B800) /* Base @ of Page 55, 2 Kbytes */
#define ADDR_FLASH_PAGE_56    ((uint32_t)0x0801C000) /* Base @ of Page 56, 2 Kbytes */
#define ADDR_FLASH_PAGE_57    ((uint32_t)0x0801C800) /* Base @ of Page 57, 2 Kbytes */
#define ADDR_FLASH_PAGE_58    ((uint32_t)0x0801D000) /* Base @ of Page 58, 2 Kbytes */
#define ADDR_FLASH_PAGE_59    ((uint32_t)0x0801D800) /* Base @ of Page 59, 2 Kbytes */
#define ADDR_FLASH_PAGE_60    ((uint32_t)0x0801E000) /* Base @ of Page 60, 2 Kbytes */
#define ADDR_FLASH_PAGE_61    ((uint32_t)0x0801E800) /* Base @ of Page 61, 2 Kbytes */
#define ADDR_FLASH_PAGE_62    ((uint32_t)0x0801F000) /* Base @ of Page 62, 2 Kbytes */
#define ADDR_FLASH_PAGE_63    ((uint32_t)0x0801F800) /* Base @ of Page 63, 2 Kbytes */

#define FLASH_USER_START_ADDR   ADDR_FLASH_PAGE_15   /* Start @ of user Flash area */
#define FLASH_USER_END_ADDR     ADDR_FLASH_PAGE_18   /* End @ of user Flash area */


int Flash_HAL_Write_Data(uint32_t addr, uint64_t data);
int Flash_HAL_Write_N_Data(uint32_t addr, uint64_t *data, uint16_t num);
void Flash_HAL_Read_N_Data(uint32_t addr, uint64_t *data, uint32_t num);
void Flash_HAL_Read_N_Byte(uint32_t addr, uint8_t *data, uint32_t num);


#endif

③、字符串读写测试

④、整形数读写测试

四、标志库FLASH读写

①、flash.c

#include "flash.h"


/**
  * @brief  清除用户FLASH扇区的数据
  * @param  NONE
  * @retval NONE
  */
int Flash_Clean_User_Area_Data(uint32_t addr)
{
    //1、FLASH解锁
    FLASH_Unlock();
    //2、FLASH数据擦除
    if(FLASH_EraseSector(Flash_Addr_Get_Sector(addr), VoltageRange_3) != FLASH_COMPLETE)
    {
        printf("FLASH擦除出错\r\n");
        //4、FLASH上锁
        FLASH_Lock();
        return -1;
    }
    //4、FLASH上锁
    FLASH_Lock();
    return 0;
}

/**
  * @brief  写N个字节(uint8_t)的数据
  * @param  addr: 存储数据的地址
  * @param  data: 数据数组
  * @param  num: 数据的个数
  * @retval 成功返回0,失败返回-1
  */
int Flash_Write_N_Byte(uint32_t addr, uint8_t *data, uint16_t num)
{
    //1、FLASH解锁
    FLASH_Unlock();
    //2、擦除数据
    //数据擦除操作会将一整个扇区擦除,如果需要连续写,最初用一次就行了
    //3、FLASH写入
    for(uint16_t i=0; i<num; i++)
    {
        if(FLASH_ProgramByte(addr, data[i]) != FLASH_COMPLETE)
        {
            printf("写多字节Byte数据失败\r\n");
            FLASH_Lock();
            return -1;
        }
        addr += sizeof(uint8_t);
    }
    //4、FLASH上锁
    FLASH_Lock();
    
    return 0;
}

/**
  * @brief  写N个半字(uint16_t)的数据
  * @param  addr: 存储数据的地址
  * @param  data: 数据数组
  * @param  num: 数据的个数
  * @retval 成功返回0,失败返回-1
  */
int Flash_Write_N_HalfWord(uint32_t addr, uint16_t *data, uint16_t num)
{
    //1、FLASH解锁
    FLASH_Unlock();
    //2、擦除数据
    //数据擦除操作会将一整个扇区擦除,如果需要连续写,最初用一次就行了
    //3、FLASH写入
    for(uint16_t i=0; i<num; i++)
    {
        if(FLASH_ProgramHalfWord(addr, data[i]) != FLASH_COMPLETE)
        {
        printf("写多个半字HalfWord数据失败\r\n");
        FLASH_Lock();
        return -1;
        }
        addr += sizeof(uint16_t);
    }
    //4、FLASH上锁
    FLASH_Lock();
    
    return 0;
}

/**
  * @brief  写N个字(uint32_t)的数据
  * @param  addr: 存储数据的地址
  * @param  data: 数据数组
  * @param  num: 数据的个数
  * @retval 成功返回0,失败返回-1
  */
int Flash_Write_N_Word(uint32_t addr, uint32_t *data, uint16_t num)
{
    //1、FLASH解锁
    FLASH_Unlock();
    //2、擦除数据
    //数据擦除操作会将一整个扇区擦除,如果需要连续写,最初用一次就行了
    //3、FLASH写入
    for(uint16_t i=0; i<num; i++)
    {
        if(FLASH_ProgramWord(addr, data[i]) != FLASH_COMPLETE)
        {
            printf("写多个字Word数据失败\r\n");
            FLASH_Lock();
            return -1;
        }
        addr += sizeof(uint32_t);
    }
    //4、FLASH上锁
    FLASH_Lock();
    
    return 0;
}


/**
  * @brief  读N个字节(uint8_t)的数据
  * @param  addr: 数据的存储地址
  * @param  data: 数据数组
  * @param  num: 需要读取的数据个数
  * @retval 成功返回0,失败返回-1
  */
void Flash_Read_N_Byte(uint32_t addr, uint8_t *data, uint16_t num)
{
    for(uint16_t i=0; i<num; i++)
    {
        data[i] = *(volatile uint8_t*)addr;
        addr += sizeof(uint8_t);//根据读取的数据类型进行内存地址递增
    }
}

/**
  * @brief  读N个半字(uint16_t)的数据
  * @param  addr: 数据的存储地址
  * @param  data: 数据数组
  * @param  num: 需要读取的数据个数
  * @retval 成功返回0,失败返回-1
  */
void Flash_Read_N_HalfWord(uint32_t addr, uint16_t *data, uint16_t num)
{
    for(uint16_t i=0; i<num; i++)
    {
        data[i] = *(volatile uint16_t*)addr;
        addr += sizeof(uint16_t);//根据读取的数据类型进行内存地址递增
    }
}

/**
  * @brief  读N个字(uint32_t)的数据
  * @param  addr: 数据的存储地址
  * @param  data: 数据数组
  * @param  num: 需要读取的数据个数
  * @retval 成功返回0,失败返回-1
  */
void Flash_Read_N_Word(uint32_t addr, uint32_t *data, uint16_t num)
{
    for(uint16_t i=0; i<num; i++)
    {
        data[i] = *(volatile uint32_t*)addr;
        addr += sizeof(uint32_t);//根据读取的数据类型进行内存地址递增
    }
}

/**
  * @brief  计算FLASH地址所在的扇区
  * @param  addr:FLASH地址
  * @retval 返回所在扇区数
  */
uint32_t Flash_Addr_Get_Sector(uint32_t addr)
{
    uint32_t sector = 0;
    
    if((addr < ADDR_FLASH_SECTOR_1) && (addr >= ADDR_FLASH_SECTOR_0))
    {
        sector = FLASH_Sector_0;  
    }
    else if((addr < ADDR_FLASH_SECTOR_2) && (addr >= ADDR_FLASH_SECTOR_1))
    {
        sector = FLASH_Sector_1;  
    }
    else if((addr < ADDR_FLASH_SECTOR_3) && (addr >= ADDR_FLASH_SECTOR_2))
    {
        sector = FLASH_Sector_2;  
    }
    else if((addr < ADDR_FLASH_SECTOR_4) && (addr >= ADDR_FLASH_SECTOR_3))
    {
        sector = FLASH_Sector_3;  
    }
    else if((addr < ADDR_FLASH_SECTOR_5) && (addr >= ADDR_FLASH_SECTOR_4))
    {
        sector = FLASH_Sector_4;  
    }
    else if((addr < ADDR_FLASH_SECTOR_6) && (addr >= ADDR_FLASH_SECTOR_5))
    {
        sector = FLASH_Sector_5;  
    }
    else if((addr < ADDR_FLASH_SECTOR_7) && (addr >= ADDR_FLASH_SECTOR_6))
    {
        sector = FLASH_Sector_6;  
    }
    else if((addr < ADDR_FLASH_SECTOR_8) && (addr >= ADDR_FLASH_SECTOR_7))
    {
        sector = FLASH_Sector_7;  
    }
}

②、flash.h

#ifndef __FLASH_H
#define __FLASH_H

#include "stm32f4xx.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

//STM32F407VET6的FLASH内存空间大小为512KB,起始地址:0x0800 0000,结束地址:0x0807 FFFF

/* Base address of the Flash sectors */ 
#define ADDR_FLASH_SECTOR_0     ((uint32_t)0x08000000) /* Base address of Sector 0, 16 Kbytes   */
#define ADDR_FLASH_SECTOR_1     ((uint32_t)0x08004000) /* Base address of Sector 1, 16 Kbytes   */
#define ADDR_FLASH_SECTOR_2     ((uint32_t)0x08008000) /* Base address of Sector 2, 16 Kbytes   */
#define ADDR_FLASH_SECTOR_3     ((uint32_t)0x0800C000) /* Base address of Sector 3, 16 Kbytes   */
#define ADDR_FLASH_SECTOR_4     ((uint32_t)0x08010000) /* Base address of Sector 4, 64 Kbytes   */
#define ADDR_FLASH_SECTOR_5     ((uint32_t)0x08020000) /* Base address of Sector 5, 128 Kbytes  */
#define ADDR_FLASH_SECTOR_6     ((uint32_t)0x08040000) /* Base address of Sector 6, 128 Kbytes  */
#define ADDR_FLASH_SECTOR_7     ((uint32_t)0x08060000) /* Base address of Sector 7, 128 Kbytes  */
#define ADDR_FLASH_SECTOR_8     ((uint32_t)0x08080000) /* Base address of Sector 8, 128 Kbytes  */

//用户自由使用的FLASH起始地址,需要根据实际代码占用的内存空间进行变动
#define FLASH_USER_START_ADDR   ADDR_FLASH_SECTOR_5   /* Start address of user Flash area */
#define FLASH_USER_END_ADDR     ADDR_FLASH_SECTOR_6  /* End address of user Flash area */


int Flash_Clean_User_Area_Data(uint32_t addr);
int Flash_Write_N_Byte(uint32_t addr, uint8_t *data, uint16_t num);
int Flash_Write_N_HalfWord(uint32_t addr, uint16_t *data, uint16_t num);
int Flash_Write_N_Word(uint32_t addr, uint32_t *data, uint16_t num);

void Flash_Read_N_Byte(uint32_t addr, uint8_t *data, uint16_t num);
void Flash_Read_N_HalfWord(uint32_t addr, uint16_t *data, uint16_t num);
void Flash_Read_N_Word(uint32_t addr, uint32_t *data, uint16_t num);

uint32_t Flash_Addr_Get_Sector(uint32_t addr);


#endif

③、字符串读写测试

④、整形数读写测试

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/549739.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

车载摄像头畸变校正解决方案,打造无畸变高清视界

在车载摄像头日益普及的今天&#xff0c;摄像头图像的畸变问题成为了制约图像质量提升的一大瓶颈。畸变不仅影响画面的美观度&#xff0c;更关键的是它可能导致智能驾驶系统对环境的误判&#xff0c;进而威胁到行车安全。美摄科技凭借其在图像处理领域的深厚实力&#xff0c;推…

双位置继电器RXMD2-1MRK001984 DC220V JOSEF约瑟

系列型号&#xff1a; RXMD2 1MRK 001 984双位置继电器&#xff1b; RXMD2 1MRK 001 985双位置继电器&#xff1b; RXMD2 1MRK 001 986双位置继电器&#xff1b; 用途 该继电器主要用于直流操作的继电保护和自动化回路中作为大容量双稳态元件进行切换和闭锁。 特点 本继电器…

Vue3项目中快速引入ElementUI框架

ElementUI介绍 ElementUI是一个强大的PC端UI组件框架&#xff0c;它不依赖于vue&#xff0c;但是却是当前和vue配合做项目开发的一个比较好的ui框架&#xff0c;其包含了布局&#xff08;layout)&#xff0c;容器&#xff08;container&#xff09;等各类组件&#xff0c;基本上…

Linux-线程

进程 与 线程: 参考自&#xff1a; Linux多线程编程初探 - 峰子_仰望阳光 - 博客园 (cnblogs.com) 进程:   典型的UNIX/Linux进程可以看成只有一个控制线程&#xff1a;一个进程在同一时刻只做一件事情。 有了多个控制线程后&#xff0c;在程序设计时可以把进程设计成在同一时…

代码随想录刷题随记23-回溯3

代码随想录刷题随记23-回溯3 39. 组合总和 leetcode链接 注意同一个 数字可以 无限制重复被选取 怎么体现这个可以重复取的思想很重要 解题代码&#xff1a; class Solution { public:void backtrace( vector<vector<int>>& ret,vector<int> &pat…

Spring Cloud 集成 Redis 发布订阅

目录 前言步骤引入相关maven依赖添加相关配置 使用方法发布订阅发布一个消息 注意总结 前言 在当今的软件开发领域&#xff0c;分布式系统已经成为一种主流的架构模式&#xff0c;尤其是在处理大规模、高并发、高可用的业务场景时。然而&#xff0c;随着系统复杂性的增加&…

Ubuntu20从0开始选择合适版本手动安装cuda,torch-geometric,jax

一个全新的ubuntu20台式机&#xff0c;在Additional Drivers安装nvidia-470-server&#xff08;一开始安装450&#xff0c;cunda版本只能到11.0&#xff0c;torch有些库用不了&#xff0c;可以直接切换点击Apply Changes重启就行&#xff09; nvidia-smi查看CUDA Version可到…

Java基础_22线程死锁,object类下面线程方法,生产者消费者

周二的回顾 1.线程的概念是进程(应用程序软件)最小的基本单位 2.在Java中代码咋写线程1.继承Thread类2.实现Runnable接口3.实现Callable接口 3.Thread相关的方法4.同步锁目的: 当多个线程操作同一个资源的时候&#xff0c;会发生数据不安全性&#xff01;&#xff01;&#x…

Jenkins上面使用pnpm打包

问题 前端也想用Jenkins的CI/CD工作流。 步骤 Jenkins安装NodeJS插件 安装完成&#xff0c;记得重启Jenkins。 全局配置nodejs Jenksinfile pipeline {agent anytools {nodejs "18.15.0"}stages {stage(Check tool version) {steps {sh node -vnpm -vnpm config…

[温故] 红黑树算法

前言 最近在突然想起一些基础的东西, 向着温故知新, 有了些新的感悟和大家分享一下. 排序算法是数据结构的一个重要组成部分, 当时学习的时候没有少折腾, 这里来看看大佬们怎么运用这些数据结构来构建庞大的计算机体系的. 二叉树是排序算法的一个衍生, 基于二叉树的构建不同…

阿里Canal使用

Canal 是阿里巴巴开源的一款基于 MySQL 数据库增量日志解析&#xff0c;提供实时的数据订阅和消费服务的工具。它可以用来读取 MySQL 的 binlog 日志并转换成 JSON 格式的事件消息&#xff0c;然后将这些消息发布到下游的消息中间件&#xff0c;比如 RabbitMQ&#xff0c;以实现…

重磅消息:CnosDB 文档网站升级全新框架啦!

我们很高兴地宣布&#xff0c;CnosDB 文档网站迎来了一次重大升级&#xff01;现在&#xff0c;我们采用了全新的强大的开源文档框架&#xff0c;为用户提供更流畅、更直观的浏览体验。 全新框架带来的优势&#xff1a; 更快速的加载速度&#xff1a;现在您可以更快地访问并查…

【Linux】磁盘扩容到根目录逻辑卷(LVM)

目录 一、物理卷和逻辑卷 1.物理卷和逻辑卷的区别 2.在Linux系统中查看所有物理卷的信息 3.在Linux系统中查看所有逻辑卷的信息 二、文件系统 三、实操-对root&#xff08;/&#xff09;目录进行扩容 1.使用lsblk命令查看新加入的磁盘信息 2.fdisk -l命令查看系统中磁盘…

【切换网络连接后】VMware虚拟机网络配置【局域网通信】

初次安装Linux虚拟机以及切换网络都需要配置虚拟机网络&#xff0c; 从而使得win主机内通过远程连接工具能够连接该虚拟机&#xff0c; 而不是在虚拟机内操作。 本片文章你将了解到网络切换后如何配置虚拟机网络的一些基础操作&#xff0c;以及局域网通信的一些基础知识。 …

HTTP/1.1特性总结

优点 【简单&#xff0c;灵活和易于扩展&#xff0c;应用广泛和跨平台】 1.简单&#xff1a; http基本的报文格式就是headerbody&#xff0c;头部信息也是key-value简单的文本形式&#xff0c;易于理解&#xff0c;降低了学习和使用的门槛 2.灵活和易于扩展&#xff1a; &…

电动汽车退役锂电池SOC主动均衡控制MATLAB仿真

微❤关注“电气仔推送”获得资料&#xff08;专享优惠&#xff09; 仿真简介 模型选用双向反激变换器作为主动均衡拓扑电路&#xff0c;均衡策略采用基于SOC的主动均衡策略&#xff0c;旨在解决电动汽车退役锂电池的不一致性问题。模型选用双向反激变换器作为主动均衡拓扑电路…

基于小程序实现的餐饮外卖系统

作者主页&#xff1a;Java码库 主营内容&#xff1a;SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app等设计与开发。 收藏点赞不迷路 关注作者有好处 文末获取源码 技术选型 【后端】&#xff1a;Java 【框架】&#xff1a;spring…

HTML图片标签和超链接标签

目录 图片标签 图片路径属性 图片替换文本属性 图片宽度和高度属性 图片边框属性 图片提示属性 超链接标签 链接属性 跳转方式属性 图片标签 在HTML中&#xff0c;可以使用<img>标签为网页添加图片 在HTML中&#xff0c;使用图片标签一般包含下面的四个属性 …

雨云免费云服务器领取步骤详解

随着云计算技术的日益普及&#xff0c;越来越多的用户开始选择使用云服务器来满足他们的数据存储和计算需求。雨云作为一家具有自主知识产权的国产云计算服务提供商&#xff0c;其免费云服务器服务备受关注。接下来&#xff0c;本文将为大家详细介绍雨云免费云服务器的领取步骤…

供应链金融机器学习建模实战

随着全球贸易的不断发展和供应链的日益复杂化&#xff0c;供应链金融作为一种新型金融工具&#xff0c;正逐渐受到企业和金融机构的关注和重视。供应链金融是指通过金融手段来优化和改进供应链中的资金流动和货物流动&#xff0c;以实现企业间的合作共赢。 供应链金融的核心是将…