STM32FLASH介绍和代码

FLASH结构

Flash 是一种非易失性存储器,其结构由主存储器块、系统存储器、OTP 区域和选项字节组成。 主存储器块被分为不同大小的扇区,包括 4 个 16 KB 的扇区、1 个 64 KB 的扇区和 7 个 128 KB 的扇区。这些扇区可被擦除和编程,用于存储应用程序和数据。 系统存储器可以在系统启动时自主加载,用于执行初始化和配置操作。这个存储器通常包含 bootloader、初始化代码和配置数据。 OTP 区域是一次性可编程的区域,可以用来存储用户数据。该区域还有 16 个额外字节,用于锁定对应的 OTP 数据块,以保护其不被意外擦除或编程。 选项字节包含配置数据,用于设置读写保护、BOR 级别、软件/硬件看门狗以及器件处于待机或停止模式下的复位。这些选项可以被编程,并在器件复位时加载。Flash 存储器是许多嵌入式系统中重要的组成部分,可以存储数据、程序和配置信息,并支持快速读写操作。

以STM32F4系列为例,FLASH分布如下图。

FLASH操作流程

  1. 对于FLASH_CR的LOCK位,需要先判断是否已经锁定。如果锁定的话,需要先解锁才能进行擦除操作。

  1. FLASH_SR寄存器的BSY位为‘0’时表示闪存空闲,可以进行操作。如果BSY位为‘1’,表示当前有其他操作正在进行,需要等待其完成后再进行擦除操作。

  1. 设置FLASH_CR寄存器中的PER位为‘1’可以启用擦除模式。

  1. 选择要擦除的页需要使用FLASH_AR寄存器进行设置。

  1. 设置FLASH_CR寄存器中的STRT位为‘1’,开始执行擦除操作。

  1. 等待BSY位变为‘0’,表示擦除操作已经完成。

  1. 最后,需要读出被擦除的页并进行验证,以确保擦除操作成功。

需要注意的是,在进行实际硬件擦除操作时,需要具备相应的硬件技术和安全措施,以避免对设备造成损坏或安全风险。

Flash操作注意事项

在进行STM32内置Flash的操作时,必须遵循一些重要的操作规则。其一是擦除操作必须先于写操作。其二是内置Flash以页为单位进行擦除,每页的大小以具体芯片型号而定,其起始地址为0x08000000。 同时,写操作必须以16位宽度为单位,且可以跨页进行写入。在进行内置Flash的擦写操作时,必须打开外部/内部高速振荡器,以确保操作的准确性和稳定性。但是需要注意的是,内置Flash最多只能进行10万次的擦写操作,因此不能进行死循环的擦写操作,否则会损坏内置Flash。 此外,在进行擦写操作时,需要注意避开用户程序可能存储的区域,以免意外擦写导致错误。虽然每一页的擦除时间为10ms,比较慢,但是不能单个字节的擦写,需要以页为单位进行擦除操作。总之,要正确而有效地进行STM32内置Flash操作,需要遵循一系列操作规则,包括正确的操作顺序、正确的宽度单位、必要的振荡器打开、避开用户程序存储区域等等。

Flash 控制寄存器解锁

  1. 当STM32芯片复位后,Flash控制寄存器(FLASH_CR)不允许执行写操作,以避免因电气干扰等原因而出现对Flash的意外操作。因此,在进行Flash控制寄存器的写操作之前,必须先执行解锁操作。解锁的顺序为:

  1. 在Flash密钥寄存器(FLASH_KEYR)中写入KEY1=0x45670123;

  1. 在Flash密钥寄存器(FLASH_KEYR)中写入KEY2=0xCDEF89AB。

如果解锁操作的顺序出错,将会返回总线错误并锁定FLASHCR寄存器,直到下一次复位。同时,也可以通过软件将FLASHCR寄存器中的LOCK位置为1来锁定FLASH_CR寄存器。

需要特别注意的是,当FLASHSR寄存器中的BSY位为1时,将不能在写模式下访问FLASHCR寄存器。在这种情况下,对该寄存器的任何写操作尝试都会导致AHB总线阻塞,直到BSY位清零。

擦除和编程操作

执行任何 Flash 编程操作(擦除或编程)时,CPU 时钟频率 (HCLK) 不能低于 1 MHz。如果 在 Flash 操作期间发生器件复位,无法保证 Flash 中的内容。 在对 Flash 执行写入或擦除操作期间,任何读取 Flash 的尝试都会导致总线 阻塞。只有在完成编程操作后,才能正确处理读操作。这意味着,写/擦除操作进行期间不能 从 Flash 中执行代码或数据获取操作

FLASH擦除具体步骤

Flash 擦除操作可针对扇区或整个 Flash(批量擦除)执行。执行批量擦除时,不会影响 OTP 扇区或配置扇区

在进行STM32芯片的Flash扇区擦除操作时,需要遵循以下具体步骤:

  1. 检查FLASH_SR寄存器中的BSY位,以确认当前未执行任何Flash操作;

  1. 在FLASH_CR寄存器中,将SER位置1,并从主存储块的12个或24个和扇区中选择要擦除的扇区(SNB);

  1. 将FLASH_CR寄存器中的STRT位置1;

  1. 等待BSY位清零。

如果要执行批量擦除,采用以下步骤:

  1. 检查FLASH_SR寄存器中的BSY位,以确认当前未执行任何Flash操作;

  1. 将FLASH_CR寄存器中的MER位置1;

  1. 将FLASH_CR寄存器中的MER和MER1位置1;

  1. 将FLASH_CR寄存器中的STRT位置1;

  1. 等待BSY位清零。

在进行扇区擦除或批量擦除操作时,都必须先确认BSY位为0,以确保当前未执行任何Flash操作。

#include "stmflash.h"

 
//读取指定地址的半字(16位数据) 
//faddr:读地址 
//返回值:对应数据.
u16 STMFLASH_ReadHalfWord(u32 faddr)
{
    return *(vu16*)faddr; 
}
#if STM32_FLASH_WREN    //如果使能了写   
//不检查的写入
//WriteAddr:起始地址
//pBuffer:数据指针
//NumToWrite:半字(16位)数   
void STMFLASH_Write_NoCheck(u32 WriteAddr,u16 *pBuffer,u16 NumToWrite)   
{                       
    u16 i;
    for(i=0;i<NumToWrite;i++)
    {
            HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD,WriteAddr,pBuffer[i]);
        WriteAddr+=2;//地址增加2.
    }  
} 
//从指定地址开始写入指定长度的数据
//WriteAddr:起始地址(此地址必须为2的倍数!!)
//pBuffer:数据指针
//NumToWrite:半字(16位)数(就是要写入的16位数据的个数.)
#if STM32_FLASH_SIZE<256
#define STM_SECTOR_SIZE 1024 //字节
#else 
#define STM_SECTOR_SIZE    2048
#endif         
u16 STMFLASH_BUF[STM_SECTOR_SIZE/2];//最多是2K字节
void STMFLASH_Write(u32 WriteAddr,u16 *pBuffer,u16 NumToWrite)    
{
    u32 secpos;       //扇区地址
    u16 secoff;       //扇区内偏移地址(16位字计算)
    u16 secremain; //扇区内剩余地址(16位字计算)       
     u16 i;    
    u32 offaddr;   //去掉0X08000000后的地址
    
    if(WriteAddr<STM32_FLASH_BASE||(WriteAddr>=(STM32_FLASH_BASE+1024*STM32_FLASH_SIZE)))return;//非法地址
    
    HAL_FLASH_Unlock();                    //解锁
    offaddr=WriteAddr-STM32_FLASH_BASE;        //实际偏移地址.
    secpos=offaddr/STM_SECTOR_SIZE;            //扇区地址  0~127 for STM32F103RBT6
    secoff=(offaddr%STM_SECTOR_SIZE)/2;        //在扇区内的偏移(2个字节为基本单位.)
    secremain=STM_SECTOR_SIZE/2-secoff;        //扇区剩余空间大小   
    if(NumToWrite<=secremain)secremain=NumToWrite;//不大于该扇区范围
    while(1) 
    {    
        STMFLASH_Read(secpos*STM_SECTOR_SIZE+STM32_FLASH_BASE,STMFLASH_BUF,STM_SECTOR_SIZE/2);//读出整个扇区的内容
        for(i=0;i<secremain;i++)    //校验数据
        {
            if(STMFLASH_BUF[secoff+i]!=0XFFFF)break;//需要擦除        
        }
        if(i<secremain)                //需要擦除
        {
            FLASH_PageErase(secpos*STM_SECTOR_SIZE+STM32_FLASH_BASE);    //擦除这个扇区
            FLASH_WaitForLastOperation(FLASH_WAITETIME);                //等待上次操作完成
            CLEAR_BIT(FLASH->CR, FLASH_CR_PER);                            //清除CR寄存器的PER位,此操作应该在FLASH_PageErase()中完成!((((FLASH_TypeDef *)((0x40000000UL + 0x00020000UL) + 0x00002000UL))->CR) &= ~((0x1UL << (1U))))

                                                                        //但是HAL库里面并没有做,应该是HAL库bug!
            for(i=0;i<secremain;i++)//复制
            {
                STMFLASH_BUF[i+secoff]=pBuffer[i];      
            }
            STMFLASH_Write_NoCheck(secpos*STM_SECTOR_SIZE+STM32_FLASH_BASE,STMFLASH_BUF,STM_SECTOR_SIZE/2);//写入整个扇区  
        }else 
        {
            FLASH_WaitForLastOperation(FLASH_WAITETIME);           //等待上次操作完成
            STMFLASH_Write_NoCheck(WriteAddr,pBuffer,secremain);//写已经擦除了的,直接写入扇区剩余区间. 
        }
        if(NumToWrite==secremain)break;//写入结束了
        else//写入未结束
        {
            secpos++;                //扇区地址增1
            secoff=0;                //偏移位置为0      
               pBuffer+=secremain;      //指针偏移
            WriteAddr+=secremain*2;    //写地址偏移(16位数据地址,需要*2)       
               NumToWrite-=secremain;    //字节(16位)数递减
            if(NumToWrite>(STM_SECTOR_SIZE/2))secremain=STM_SECTOR_SIZE/2;//下一个扇区还是写不完
            else secremain=NumToWrite;//下一个扇区可以写完了
        }     
    };    
    HAL_FLASH_Lock();        //上锁
}
#endif

//从指定地址开始读出指定长度的数据
//ReadAddr:起始地址
//pBuffer:数据指针
//NumToWrite:半字(16位)数
void STMFLASH_Read(u32 ReadAddr,u16 *pBuffer,u16 NumToRead)       
{
    u16 i;
    for(i=0;i<NumToRead;i++)
    {
        pBuffer[i]=STMFLASH_ReadHalfWord(ReadAddr);//读取2个字节.
        ReadAddr+=2;//偏移2个字节.    
    }
}

//测试用///
//WriteAddr:起始地址
//WriteData:要写入的数据
void Test_Write(u32 WriteAddr,u16 WriteData)       
{
    STMFLASH_Write(WriteAddr,&WriteData,1);//写入一个字 
}
#ifndef __STMFLASH_H__
#define __STMFLASH_H__
#include "user_includes.h"
#include "config.h"
#include "stm32f1xx_hal.h"
#include "stm32f1xx.h"
//     



//用户根据自己的需要设置
#define STM32_FLASH_SIZE     512             //所选STM32的FLASH容量大小(单位为K)
#define STM32_FLASH_WREN     1                  //使能FLASH写入(0,不是能;1,使能)
#define FLASH_WAITETIME      50000              //FLASH等待超时时间

//FLASH起始地址
#define STM32_FLASH_BASE 0x08000000         //STM32 FLASH的起始地址
#define FLASH_SAVE_ADDR  0X0801E000

u8 STMFLASH_GetStatus(void);                  //获得状态
u8 STMFLASH_WaitDone(u16 time);                  //等待操作结束
u8 STMFLASH_ErasePage(u32 paddr);              //擦除页
u8 STMFLASH_WriteHalfWord(u32 faddr, u16 dat);//写入半字
u16 STMFLASH_ReadHalfWord(u32 faddr);          //读出半字  
void STMFLASH_WriteLenByte(u32 WriteAddr,u32 DataToWrite,u16 Len);    //指定地址开始写入指定长度的数据
u32 STMFLASH_ReadLenByte(u32 ReadAddr,u16 Len);                        //指定地址开始读取指定长度数据
void STMFLASH_Write(u32 WriteAddr,u16 *pBuffer,u16 NumToWrite);        //从指定地址开始写入指定长度的数据
void STMFLASH_Read(u32 ReadAddr,u16 *pBuffer,u16 NumToRead);           //从指定地址开始读出指定长度的数据

//测试写入
void Test_Write(u32 WriteAddr,u16 WriteData);                                   
#endif

















  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值