STM32操作访问flash,包括写入数据到flash和从flash读取数据

序言:flash相关知识背景

STM32中存储区分为:随机存取存储器RAM只读存储器ROM
其中:

  • RAM为常说的内存,比如手机的2G内存4G内存等,就是程序跑起来的时候所占用的存储空间,特点是掉电数据丢失
  • ROM为常说的硬盘,比如手机的64G和128G等,可以简单的理解为硬盘的存储空间,特点是掉电数据不丢失,所以又叫“非易失性存储器件”。
  • ROM又包含:EEPROM和flash。

画个嵌入式产品存储器件的思维导图如下(如有什么地方不对,恳请大神们进行指正):
嵌入式设备存储器件思维导图

作为ROM的一份子,flash的特点自然是掉电数据不丢失。但是,flash在STM32中比较重要,程序也是保存在这个地方,所以轻易不让用户进行随意的读写,以避免不必要的问题。

而这篇博客就先简单记录一下flash的访问流程和方法(读和写),具体原理以后理解深刻了再做补充。

一、FLASH操作流程与操作选址

1.1 FLASH操作流程

Flash操作已经属于嵌入式设备中很底层的操作了,直接对地址进行存取,简单描述,Flash操作大致需要以下流程:

1、确定要写入Flash的首地址(稍后介绍确定地址的方法)
2、解锁Flash
3、对Flash进行操作(写入数据)
4、对Flash重新上锁

1.2 如何查找并选定要写入Flash十六进制地址

要想选定安全的Flash地址进行读写,可以根据自己的STM32 MCU型号,查找数据手册,确定FLASH的地址区段,因为起始段会存储代码,所以一定要避开起始段,以避免数据错误。(我一般是根据Flash大小计算Flash的最末尾地址,往前推一段地址空间,在这里一般不会对代码中的数据产生覆盖等影响)

我此次操作Flash使用的MCU是STM32F103C8T6,所以以该型号MCU为例进行描述:

  • 在数据手册中,可以看到STM32F103C8T6的flash起始地址是0x0800 0000(如下图所示),而STM32F103C8T6的Flash大小为64K,可以计算出STM32F103C8T6的Flash地址范围是:0x0800 0000——0x0800 FFFF(计算方法参考另一篇博客:STM32内存大小与地址的对应关系以及计算方法)。这里选取0x0800 F000作为读写操作的起始地址,对于C8T6这款MCU,操作这个起始地址应该算是很安全的范围了。
    STM32F103C8T6 Flash地址

二、Flash基本知识点

2.1 Flash容量

Flash根据容量大小可以分为以下三种:

  • 1、小容量产品:Flash大小为1-32KB(STM32F10X_LD)
  • 2、中容量产品:Flash大小为64-128KB(STM32F10X_MD)
  • 3、大容量产品:Flash大小为256KB以上(STM32F10X_HD)

2.2 ST库对Flash操作的支持

ST库中对Flash操作主要提供了以下几类操作API函数:

  • 1、Flash解锁、锁定函数
void FLASH_Unlock(void);    //解锁函数:在对Flash操作之前必须解锁
void FLASH_Lock(void);      //锁定函数:同理,操作完Flash之后必须重新上锁
  • 2、Flash写操作函数
FLASH_Status FLASH_ProgramWord(uint32_t Address, uint32_t Data);             //32位字写入函数
FLASH_Status FLASH_ProgramHalfWord(uint32_t Address, uint16_t Data);         //16位半字写入函数
FLASH_Status FLASH_ProgramOptionByteData(uint32_t Address, uint8_t Data);    //用户选择字节写入函数

      注:这里需要说明,32 位字节写入实际上是写入的两次 16 位数据,写完第一次后地址+2,这与我们前面讲解的 STM32 闪存的编程每次必须写入 16 位并不矛盾。写入 8位实际也是占用的两个地址了,跟写入 16 位基本上没啥区别。

  • 3、Flash擦除函数
FLASH_Status FLASH_ErasePage(uint32_t Page_Address);
FLASH_Status FLASH_EraseAllPages(void);
FLASH_Status FLASH_EraseOptionBytes(void);
  • 4、获取Flash状态
FLASH_Status FLASH_GetStatus(void);

      获取Flash状态函数,主要是为了获取Flash的状态,以便于根据状态对Flash进行操作。该函数返回值是通过枚举类型定义的,在代码中可以看到FLASH_Status类型定义如下(具体含义看注释即可):
*

typedef enum {
    FLASH_BUSY = 1,       //忙
    FLASH_ERROR_PG,       //编程错误
    FLASH_ERROR_WRP,      //写保护错误
    FLASH_COMPLETE,       //操作完成
    FLASH_TIMEOUT         //操作超时
}FLASH_Status;
  • 5、等待操作完成函数
FLASH_Status FLASH_WaitForLastOperation(uint32_t Timeout);

注:在执行闪存写操作时,任何对闪存的读操作都会锁住总线,在写操作完成后读操作才能正确地进行;既在进行写或擦除操作时,不能进行代码或数据的读取操作。所以在每次操作之前,我们都要等待上一次操作完成这次操作才能开始。

三、OK,上干货,上代码

根据ST库提供的上述函数,我们可以自己编写Flash的读写操作代码如下:

3.1 先定义一个Flash操作的起始地址宏定义Flash状态指示标志位

#define STARTADDR 0x0800F000 //STM32F103C8T6适用

volatile FLASH_Status FLASHStatus = FLASH_BUSY; //Flash操作状态变量


3.2 编写各个读写函数

/*
 * Name:	    WriteFlashOneWord
 * Function:	向内部Flash写入32位数据
 * Input:	    WriteAddress:数据要写入的目标地址(偏移地址)
 *              WriteData:   写入的数据
 */
void WriteFlashOneWord(uint32_t WriteAddress, uint32_t WriteData)
{   
    FLASH_UnlockBank1();
    FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR);

    FLASHStatus = 1;    //清空状态指示标志位
    FLASHStatus = FLASH_ErasePage(STARTADDR);  
    if(FLASHStatus == FLASH_COMPLETE)   
    {  
        FLASHStatus = 1;    //清空状态指示标志位
        FLASHStatus = FLASH_ProgramWord(STARTADDR+WriteAddress, WriteData); //flash.c 中API函数
    }
    
    FLASHStatus = 1;    //清空状态指示标志位
    FLASH_LockBank1();    
}

/*
 * Name:	    WriteFlashData
 * Function:	向内部Flash写入数据
 * Input:	    WriteAddress:数据要写入的目标地址(偏移地址)
 *             data[]:      写入的数据首地址
 *             num:         写入数据的个数
 */
void WriteFlashData(uint32_t WriteAddress, uint8_t data[], uint32_t num)
{
    uint32_t i = 0;
    uint16_t temp = 0;
    
	FLASH_UnlockBank1();    //解锁flash
    FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR); 
    
    FLASHStatus = 1;        //清空状态指示标志位
    FLASHStatus = FLASH_ErasePage(STARTADDR);//擦除整页
	if(FLASHStatus == FLASH_COMPLETE)//flash操作完成
	{
        FLASHStatus = 1;    //清空状态指示标志位
        for(i=0; i<num; i++)
        {
            temp = (uint16_t)data[i];
            FLASHStatus = FLASH_ProgramHalfWord(STARTADDR+WriteAddress+i*2, temp);//写入数据
        }
	}
    
    FLASHStatus = 1;    //清空状态指示标志位
    
	FLASH_LockBank1();  //锁定flash
} 

/*
 * Name:	    ReadFlashNBtye
 * Function:	从内部Flash读取N字节数据
 * Input:	    ReadAddress:数据地址(偏移地址)
 *              ReadBuf:读取到的数据存放位置指针
 *              ReadNum:读取字节个数
 * Output:      读取的字节数
 */
int ReadFlashNBtye(uint32_t ReadAddress, uint8_t *ReadBuf, int32_t ReadNum)
{   
    int DataNum = 0;
    
    ReadAddress = (uint32_t)STARTADDR + ReadAddress;  
    while(DataNum < ReadNum)   
    {        
        *(ReadBuf + DataNum) = *(__IO uint8_t*) ReadAddress++;  
        DataNum++;     
    }
    
    return DataNum;    
}

/*
 * Name:	    ReadFlashData
 * Function:	从内部Flash读取num字节数据
 * Input:	    ReadAddress:数据地址(偏移地址)
 *              dest_Data:  读取到的数据存放位置指针
 *              num:        读取字节个数
 */
void ReadFlashData(uint32_t ReadAddress, uint8_t *dest_Data, uint32_t num)
{
    int i = 0;
    ReadAddress = (uint32_t)STARTADDR + ReadAddress; 
    while(i < num) 
    {
        *(dest_Data+i) = *(__IO uint16_t*) ReadAddress;
        ReadAddress += 2;
        
        i++;
    }
}
### STM32 Flash 写入操作详解 #### 1. STM32 Flash 基本原理 STM32Flash 是一种非易失性存储器,主要用于存储程序代码一些需要长期保存的数据Flash 存储器被划分为多个页面(Page),每个页面大小可能不同,具体取决于所使用的 MCU 类型[^1]。 对于 STM32F103 系列而言,其中小型存储器版本每页大小为 1KB,而大型存储器版本则可能是 2KB 或更大。因此,在编写 Flash 数据之前,需确认目标设备的具体参数。 #### 2. STM32 Flash 写入前准备 在执行 Flash 写入操作之前,必须完成以下准备工作: - **解锁 Flash 控制寄存器**:为了防止意外写入或擦除操作Flash 访问通常处于锁定状态。需要通过调用 `HAL_FLASH_Unlock()` 函数来解除锁。 - **擦除目标区域**:Flash 页面一旦写满,则无法再次写入数据,除非先将其擦除。可以使用 HAL 库中的 `HAL_FLASHEx_Erase()` 函数实现这一功能。 - **校验地址合法性**:确保要写入的目标地址位于合法范围内,并满足对齐要求(通常是字对齐)。 #### 3. STM32 Flash 写入代码示例 以下是基于 HAL 库的一个简单示例,展示如何向指定地址写入数据: ```c #include "stm32f1xx_hal.h" #define PAGE_ADDR ((uint32_t)0x08000000 + 60 * 1024 + 100) // 起始地址 #define DATA_TO_WRITE 0xAABBCCDD // 待写入数据 void Flash_Write(uint32_t address, uint32_t data) { FLASH_EraseInitTypeDef EraseInitStruct; uint32_t PageError; // 解锁 Flash HAL_FLASH_Unlock(); // 配置擦除初始化结构体 EraseInitStruct.TypeErase = TYPEERASE_PAGES; EraseInitStruct.PageAddress = address & ~(FLASH_PAGE_SIZE - 1); EraseInitStruct.NbPages = 1; // 执行擦除操作 if (HAL_FLASHEx_Erase(&EraseInitStruct, &PageError) != HAL_OK) { Error_Handler(); } // 写入单个字到 Flash 地址 if (HAL_FLASH_Program(TYPEPROGRAM_WORD, address, data) == HAL_OK) { printf("Data written successfully.\n"); } else { Error_Handler(); } // 锁定 Flash HAL_FLASH_Lock(); } int main(void) { HAL_Init(); SystemClock_Config(); Flash_Write(PAGE_ADDR, DATA_TO_WRITE); while (1) { ; } } ``` 上述代码实现了如下功能: - 定义了一个宏变量 `PAGE_ADDR` 表示目标写入地址[^4]。 - 创建了函数 `Flash_Write` 来封装 Flash 操作逻辑,包括解锁、擦除编程三个主要步骤。 - 使用 `HAL_FLASH_Program` 方法将一个 32 位整数值写入到指定位置。 #### 4. 注意事项 - **地址范围验证**:确保目标地址未超出芯片允许的最大范围,同时注意边界条件处理。 - **数据对齐需求**:大多数情况下,Flash 编程仅支持按字(Word)、半字(Half Word)单位进行操作,因此输入数据应严格遵循此规则。 - **错误检测机制**:实际应用中建议加入更完善的异常捕获流程以提高系统的健壮性可靠性。 ---
评论 26
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值