0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

STM32速成笔记(12)—Flash闪存

冬至子 来源:二土电子 作者:二土电子 2023-10-24 15:19 次阅读

一、Flash简介

快闪存储器(flash memory),是一种电子式可清除程序化只读存储器的形式,允许在操作中被多次擦或写的存储器。它是一种非易失性存储器,即断电数据也不会丢失。

二、STM32F1的Flash

STM32F103ZET6的Flash大小为512KB,属于大容量产品。在中文参考手册中给出了大容量产品的Flash模块组织结构图

图片

大容量产品Flsh模块组织结构图

  • 主存储器 主存储器用来存储我们的代码和定义的一些常量数据。当Boot0和Boot1都接GND时,芯片从主存储器的起始地址0x0800 0000开始运行代码。
  • 信息

系统存储器中存储的是启动程序代码。启动程序就是串口下载的代码。当Boot0接VCC,Boot1接GND时,运行的就是系统存储器中的代码。系统存储器中存储的启动代码,是ST公司在芯片出厂时就已经下载好的,用户无法修改。选择字节是用来配置写保护和杜保护功能。

  • 闪存存储器接口寄存器 闪存存储器接口寄存器,是整个闪存的控制机构,里面包含了很多的闪存的控制寄存器和状态寄存器。

在执行闪存写操作时,任何对闪存的读操作都会被锁住。只有对闪存的写操作结束后,读操作才能够正常执行。也就是说,在对闪存进行写操作或者擦除操作时,无法对闪存进行读操作。

三、Flash操作步骤

  • • 解锁和锁定

  • • 写/擦除操作

  • • 获取Flash状态

  • • 等待操作完成

  • • 读取Flash指定地址数据

    四、程序设计

    操作内部Flash时,最小单位是半字(16位)。

    44.1 读取数据

    读取数据用的是指针的方式,在之前博主的文章中有关于如何利用指针在指定地址读写数据的操作。 ```c /*

*============================================================================== *函数名称:Med_Flash_ReadHalfWord *函数功能:读取指定地址的半字(16位数据) *输入参数:faddr:读取地址 *返回值:对应读取地址数据 *备 注:对内部Flash的操作是以半字为单位,所以读写地址必须是2的倍数 *============================================================================== */ vu16 Med_Flash_ReadHalfWord (u32 faddr) { return (vu16)faddr; }

```c
/*
 *==============================================================================
 *函数名称:Med_Flash_Read
 *函数功能:从指定地址开始读出指定长度的数据
 *输入参数:ReadAddr:读取起始地址;pBuffer:数据指针;
                        NumToRead:读取(半字)数
 *返回值:无
 *备  注:对内部Flash的操作是以半字为单位,所以读写地址必须是2的倍数
 *==============================================================================
 */
void Med_Flash_Read (u32 ReadAddr,u16 *pBuffer,u16 NumToRead)
{
    u16 i;
    for(i = 0;i < NumToRead;i ++)
    {
        pBuffer[i] = Med_Flash_ReadHalfWord(ReadAddr);   // 读取2个字节.
        ReadAddr += 2;   // 偏移2个字节. 
    }
}

4.2 写入数据(不检查)

这里的不检查,是指在写入之前,不检查写入地址是否可写。

/*
 *==============================================================================
 *函数名称:Med_Flash_Write_NoCheck
 *函数功能:不检查的写入
 *输入参数:WriteAddr:写入起始地址;pBuffer:数据指针;
                        NumToWrite:写入(半字)数
 *返回值:无
 *备  注:对内部Flash的操作是以半字为单位,所以读写地址必须是2的倍数
 *==============================================================================
 */
void Med_Flash_Write_NoCheck (u32 WriteAddr,u16 *pBuffer,u16 NumToWrite)
{      
    u16 i;
    for(i = 0;i < NumToWrite;i ++)
    {
        FLASH_ProgramHalfWord(WriteAddr,pBuffer[i]);
        WriteAddr += 2;   // 地址增加2.
    }  
}

4.3 写入数据(检查)

/*
 *==============================================================================
 *函数名称:Med_Flash_Read
 *函数功能:从指定地址开始写入指定长度的数据
 *输入参数:WriteAddr:写入起始地址;pBuffer:数据指针;
                        NumToRead:写入(半字)数
 *返回值:无
 *备  注:对内部Flash的操作是以半字为单位,所以读写地址必须是2的倍数
 *==============================================================================
 */

// 根据中文参考手册,大容量产品的每一页是2K字节
#if STM32_FLASH_SIZE < 256
    #define STM32_SECTOR_SIZE   1024   // 字节
#else 
    #define STM32_SECTOR_SIZE   2048
#endif

// 一个扇区的内存
u16 STM32_FLASH_BUF[STM32_SECTOR_SIZE / 2];

void Med_Flash_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;   // 非法地址
    }
    
    FLASH_Unlock();   // 解锁
    offaddr = WriteAddr - STM32_FLASH_BASE;   // 实际偏移地址
    secpos = offaddr / STM32_SECTOR_SIZE;   // 扇区地址
    secoff = (offaddr % STM32_SECTOR_SIZE) / 2;   // 在扇区内的偏移(2个字节为基本单位)
    secremain = STM32_SECTOR_SIZE / 2 - secoff;   // 扇区剩余空间大小
    
    if (NumToWrite <= secremain)
    {
        secremain = NumToWrite;   // 不大于该扇区范围
    }
    while (1) 
    {
        // 读出整个扇区的内容
        Med_Flash_Read(secpos * STM32_SECTOR_SIZE + STM32_FLASH_BASE,STM32_FLASH_BUF,STM32_SECTOR_SIZE / 2);
        
        // 校验数据
        for (i = 0;i < secremain;i ++)
        {
            // 需要擦除 
            if (STM32_FLASH_BUF[secoff + i] != 0XFFFF)
            {
                break; 
            }    
        }
        // 需要擦除
        if (i < secremain)
        {
            FLASH_ErasePage(secpos * STM32_SECTOR_SIZE + STM32_FLASH_BASE);   // 擦除这个扇区
            
            // 复制
            for (i = 0;i < secremain;i ++)
            {
                STM32_FLASH_BUF[i + secoff] = pBuffer[i];   
            }
            // 写入整个扇区
            Med_Flash_Write_NoCheck(secpos * STM32_SECTOR_SIZE + STM32_FLASH_BASE,STM32_FLASH_BUF,STM32_SECTOR_SIZE / 2);
        }
        else
        {
            // 写已经擦除了的,直接写入扇区剩余区间
            Med_Flash_Write_NoCheck(WriteAddr,pBuffer,secremain);
        }
        
        if (NumToWrite == secremain)
        {
            break;   // 写入结束了
        }
        // 写入未结束
        else
        {
            secpos ++;   // 扇区地址增1
            secoff=0;   // 偏移位置为0   
            pBuffer+=secremain;   // 指针偏移
            WriteAddr+=secremain;   // 写地址偏移    
            NumToWrite-=secremain;   // 字节(16位)数递减
            if (NumToWrite >(STM32_SECTOR_SIZE/2))
            {
                secremain=STM32_SECTOR_SIZE/2;   // 下一个扇区还是写不完
            }
            else
            {
                secremain=NumToWrite;   // 下一个扇区可以写完了
            }
        }  
    } 
    FLASH_Lock();   // 上锁
}

宏定义如下

// STM32的Flash容量,单位为KB
#define STM32_FLASH_SIZE   512

// FLASH主存储块起始地址
#define STM32_FLASH_BASE   0x08000000

上面的读取数据和不检查的写入都比较简单,因此并没有再做分析。这里分析一下带检查的写入的程序设计思路。

  • • 首先用一小段条件编译来区分一下大容量产品和其他产品。因为大容量产品的一页(一个扇区)是2K字节,中小容量产品的一页是1K字节。定一个了一个数组,数组大小是一个扇区的大小。
// 根据中文参考手册,大容量产品的每一页是2K字节
#if STM32_FLASH_SIZE < 256
    #define STM32_SECTOR_SIZE   1024   // 字节
#else 
    #define STM32_SECTOR_SIZE   2048
#endif

// 一个扇区的内存
u16 STM32_FLASH_BUF[STM32_SECTOR_SIZE / 2];

大容量产品,一个扇区2K字节,除以2是因为在对内部Flash操作时,最小单位是半字。

  • • 接下来,判断要写入的地址是否合法,也就是是否在主存储块地址范围内。
// 判断写入地址是否在合法范围内
    if (WriteAddr < STM32_FLASH_BASE || (WriteAddr >= (STM32_FLASH_BASE + 1024 * STM32_FLASH_SIZE)))
    {
        return;   // 非法地址
    }
  • • 如果要写入的地址合法,那么解锁后计算一些参数值。
offaddr = WriteAddr - STM32_FLASH_BASE;   // 实际偏移地址

实际偏移地址 ,指的是要写入的地址与主存储块基地址(0x0800 0000)的差值。

secpos = offaddr / STM32_SECTOR_SIZE;   // 扇区地址

扇区地址指的是要写入的地址所在扇区前面的扇区数。由于所有的参数都不是浮点型,因此在做除法时,小数位都是0。最终除出来的结果就是当前扇区前面的扇区数。

secoff = (offaddr % STM32_SECTOR_SIZE) / 2;   // 在扇区内的偏移(2个字节为基本单位)

在扇区内的偏移指的是要写入的地址与其所在扇区首地址的差值。用要写入的地址取余每一个扇区的字节数,余数就是偏移地址。但是由于操作内部Flash时的最小单位是半字,因此要除以2。

secremain = STM32_SECTOR_SIZE / 2 - secoff;   // 扇区剩余空间大小

扇区内剩余空间大小只需要用扇区总的空间大小减去偏移地址即可得到。但是需要注意的是,单位都是半字。这里的剩余空间大小,并不是真正的剩余空间大小。而是指写入地址后面的扇区大小。这里不太好理解,画一个图表示一下

图片

扇区内剩余空间大小示意图

正是因为这里的扇区剩余空间大小并不是指真正的剩余空间大小。在剩余空间内,也可能存在已经写入数据的地址。所以后面需要进行判断,来确定是否需要擦除。

  • • 判断在写入地址所在扇区能否将写入内容全部写入完成
if (NumToWrite <= secremain)
    {
        secremain = NumToWrite;   // 不大于该扇区范围
    }

如果可以,直接将要写入的半字数赋值给当前扇区剩余空间大小。如果当前扇区剩余空间大小可以容纳要写入的半字数,那么只需要写入一次即可,在后续判断是否写完时,直接通过,while循环只执行一次。

  • • 读出整个扇区内容,判断是否需要擦除
// 读出整个扇区的内容
        Med_Flash_Read(secpos * STM32_SECTOR_SIZE + STM32_FLASH_BASE,STM32_FLASH_BUF,STM32_SECTOR_SIZE / 2);
        
        // 校验数据
        for (i = 0;i < secremain;i ++)
        {
            // 需要擦除 
            if (STM32_FLASH_BUF[secoff + i] != 0XFFFF)
            {
                break; 
            }    
        }

要对内部Flash某个地址写入数据时,需要确保该地址数值为0xFFFF。判断方法就是从扇区内的偏移开始,利用for循环判断读出地扇区剩余空间内,是否存在已经被写入内容的地址。for循环找到i的值,i加上在扇区内的偏移加1之后的空间,才是真正的扇区剩余空间大小。

for循环结束后,判断是否需要进行擦除

// 需要擦除
        if (i < secremain)
        {
            FLASH_ErasePage(secpos * STM32_SECTOR_SIZE + STM32_FLASH_BASE);   // 擦除这个扇区
            
            // 复制
            for (i = 0;i < secremain;i ++)
            {
                STM32_FLASH_BUF[i + secoff] = pBuffer[i];   
            }
            
            // 写入整个扇区
            Med_Flash_Write_NoCheck(secpos * STM32_SECTOR_SIZE + STM32_FLASH_BASE,STM32_FLASH_BUF,STM32_SECTOR_SIZE / 2);
        }
        else
        {
            // 写已经擦除了的,直接写入扇区剩余区间
            Med_Flash_Write_NoCheck(WriteAddr,pBuffer,secremain);
        }

擦除时,最小单元为一个扇区。在大容量产品中,也就是2048字节。

  • • 最后,将需要写入的数据,写入到对应位置。如果是需要擦除的情况,写入时是先将原来的内容提取出来,然后在后面填充上需要写入的内容,擦除整个扇区之后再一起写入。如果是不需要擦除的情况,直接写入即可。

    五、注意事项

    在操作Flash时,注意不要对代码区内容进行擦写。如果擦写的地址在代码区,会导致程序运行异常。那么如何确保我们操作的地址不是在代码区?这就需要我们知道我们的代码所占的内存是多少。在Keil5编译完成后,会显示下面的内容

图片

keil5编译后提示

  • Code 程序所占用的内存大小(存放在Flash中)
  • RO-data 程序定义的常量所占内存大小(存放在Flash中)
  • RW-data 已被初始化的全局变量所占内存大小(在程序初始化的时候,RW-data会从FLASH中拷贝到RAM中)

ZI-data 未被初始化的全局变量所占内存大小(存放在RAM中)

最后,计算程序代码所占Flash空间。flash = Code + RO-data + RW-data

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • RAM
    RAM
    +关注

    关注

    8

    文章

    1368

    浏览量

    114641
  • STM32
    +关注

    关注

    2270

    文章

    10895

    浏览量

    355729
  • FLASH闪存
    +关注

    关注

    0

    文章

    7

    浏览量

    7609
  • 快闪存储器
    +关注

    关注

    0

    文章

    15

    浏览量

    11159
  • for循环
    +关注

    关注

    0

    文章

    61

    浏览量

    2502
收藏 人收藏

    评论

    相关推荐

    STM32入门学习笔记之外置FLASH读写实验(上)

    Flash,全名叫做Flash EEPROM Memory,即平时所说的“闪存”,它结合了ROM和RAM的长处,不仅可以反复擦除,还可以快速读取数据,STM32运行的程序其实就是存放在
    的头像 发表于 02-16 14:10 9015次阅读
    <b class='flag-5'>STM32</b>入门学习<b class='flag-5'>笔记</b>之外置<b class='flag-5'>FLASH</b>读写实验(上)

    STM32入门学习笔记之外置FLASH读写实验(下)

    Flash,全名叫做Flash EEPROM Memory,即平时所说的“闪存”,它结合了ROM和RAM的长处,不仅可以反复擦除,还可以快速读取数据,STM32运行的程序其实就是存放在
    的头像 发表于 02-16 14:13 1493次阅读

    STM32学习笔记-Flash做为存储器储存数据

    的时候更容易操作。FPEC FPEC(FLASH Program/Erase controller 闪存编程/擦除控制器),STM32通过FPEC来擦除和编程FLASH。FPEC使用7
    发表于 10-07 15:55

    STM32速成

    哟管STM32速成贴?求助
    发表于 01-23 14:34

    STM32F0x HAL库学习笔记分享

    在此声明——本文摘自这里:【码神岛】STM32F0x HAL库学习笔记(5)片内FLASH的读写操作本文开发环境MCU型号:STM32F103C8T6IDE环境: MDK 5.25代码
    发表于 01-26 07:33

    flash_erase无法无错误地擦除某些闪存页面怎么解决?

    擦除。现在的问题是,第0页和第12页无法无误地擦除。代码如下(与第 12 页相同):闪存擦除(0,1);从 flash_erase() 到 -->HAL_
    发表于 12-06 06:39

    Flash闪存有哪些类型,Flash闪存分类

    Flash闪存有哪些类型 Flash闪存是非易失性存储器,这是相对于SDRAM等存储器所说的。即存储器断电后,内部的数据仍然可以保存。Flash
    发表于 03-25 16:26 1.2w次阅读

    STM32F2技术培训_片上闪存Flash

    STM32F2技术培训_片上闪存Flash
    发表于 12-03 17:35 0次下载

    闪存(Flash)

    LPC1138闪存(Flash),有需要的下来看看。
    发表于 01-13 16:45 20次下载

    你真的了解Flash闪存吗?Flash闪存具备哪些类型?

    对于Flash,大家应该并不陌生。但是请注意哦,这里谈及的Flash不是动画播放格式,这里的Flash指的是Flash闪存。在这篇文章中,小
    的头像 发表于 11-06 17:36 7853次阅读
    你真的了解<b class='flag-5'>Flash</b><b class='flag-5'>闪存</b>吗?<b class='flag-5'>Flash</b><b class='flag-5'>闪存</b>具备哪些类型?

    STM32闪存编程手册

    STM32闪存编程手册(嵌入式开发和编程)-STM32闪存编程手册 STM32开发必备技术文档之一
    发表于 08-04 12:44 51次下载
    <b class='flag-5'>STM32</b><b class='flag-5'>闪存</b>编程手册

    STM32学习心得三十三:FLASH闪存编程原理与实验

    记录一下,方便以后翻阅~主要内容:1) STM32 Flash操作介绍;2) 寄存器和库函数介绍;3) 相关实验代码解读。参考资料:《STM32F10xxx闪存编程参考手册》实验
    发表于 11-26 17:51 27次下载
    <b class='flag-5'>STM32</b>学习心得三十三:<b class='flag-5'>FLASH</b><b class='flag-5'>闪存</b>编程原理与实验

    HAL库之读写STM32F103内部的FLASH空间

    在此声明——本文摘自这里:【码神岛】STM32F0x HAL库学习笔记(5)片内FLASH的读写操作本文开发环境MCU型号:STM32F103C8T6IDE环境: MDK 5.25代码
    发表于 12-01 20:51 23次下载
    HAL库之读写<b class='flag-5'>STM32</b>F103内部的<b class='flag-5'>FLASH</b>空间

    STM32F103学习笔记——SPI读写Flash(三)

      此系列文章是小白学习STM32的一些学习笔记。小白第一次写笔记文章,有不足或是错误之处,请多体谅和交流!目录1.FLASH指令编码表2.读取FL
    发表于 12-22 19:29 11次下载
    <b class='flag-5'>STM32</b>F103学习<b class='flag-5'>笔记</b>——SPI读写<b class='flag-5'>Flash</b>(三)

    STM32F103学习笔记——SPI读写Flash(四)

      此系列文章是小白学习STM32的一些学习笔记。小白第一次写笔记文章,有不足或是错误之处,请多体谅和交流!目录1.main函数编写2.下载验证1.main函数编写  编写主函数,对FLASH
    发表于 12-22 19:32 6次下载
    <b class='flag-5'>STM32</b>F103学习<b class='flag-5'>笔记</b>——SPI读写<b class='flag-5'>Flash</b>(四)