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

发布时间:2024-04-03  

一、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; 

            }    

文章来源于:电子工程世界    原文链接
本站所有转载文章系出于传递更多信息之目的,且明确注明来源,不希望被转载的媒体或个人可与我们联系,我们将立即进行删除处理。

我们与500+贴片厂合作,完美满足客户的定制需求。为品牌提供定制化的推广方案、专属产品特色页,多渠道推广,SEM/SEO精准营销以及与公众号的联合推广...详细>>

利用葫芦芯平台的卓越技术服务和新产品推广能力,原厂代理能轻松打入消费物联网(IOT)、信息与通信(ICT)、汽车及新能源汽车、工业自动化及工业物联网、装备及功率电子...详细>>

充分利用其强大的电子元器件采购流量,创新性地为这些物料提供了一个全新的窗口。我们的高效数字营销技术,不仅可以助你轻松识别与连接到需求方,更能够极大地提高“闲置物料”的处理能力,通过葫芦芯平台...详细>>

我们的目标很明确:构建一个全方位的半导体产业生态系统。成为一家全球领先的半导体互联网生态公司。目前,我们已成功打造了智能汽车、智能家居、大健康医疗、机器人和材料等五大生态领域。更为重要的是...详细>>

我们深知加工与定制类服务商的价值和重要性,因此,我们倾力为您提供最顶尖的营销资源。在我们的平台上,您可以直接接触到100万的研发工程师和采购工程师,以及10万的活跃客户群体...详细>>

凭借我们强大的专业流量和尖端的互联网数字营销技术,我们承诺为原厂提供免费的产品资料推广服务。无论是最新的资讯、技术动态还是创新产品,都可以通过我们的平台迅速传达给目标客户...详细>>

我们不止于将线索转化为潜在客户。葫芦芯平台致力于形成业务闭环,从引流、宣传到最终销售,全程跟进,确保每一个potential lead都得到妥善处理,从而大幅提高转化率。不仅如此...详细>>