历史上的今天
返回首页

历史上的今天

今天是:2026年03月21日(星期六)

正在发生

2023年03月21日 | STM32CubeMX系列 | STM32内部FLASH

2023-03-21 来源:zhihu

1. 内部FLASH简介

之前的文章中介绍过STM32F1利用SPI与外部FLASH(W25QXX芯片)通讯的例程,本例程将介绍STM32F1的内部FLASH,通过内部FLASH实现数据读写操作。 不同型号的STM32,其FLASH容量也有所不同,最小的只有16K字节,最大的则达到了1024K字节。此处我们使用的是STM32F103ZET6,其FLASH容量为512K字节,属于大容量产品,大容量产品的闪存模块组织图如下图示

STM32F1的闪存模块由:主存储器、信息块和闪存存储器接口寄存器3部分组成

  • 主存储器:用来存放代码和数据常量,起始地址是0x08000000,BOOT0和BOOT1都接GND时,就是从该起始地址运行代码的

  • 信息块:分为2个小部分,启动程序代码是用来存储ST自带的启动程序,用于串口下载代码,BOOT0接3.3V,BOOT1接GND时,运行的就是这部分代码;选择字节则一般用于配置写保护、读保护等功能

  • 闪存存储器接口寄存器:用于控制闪存读写等,是整个闪存模块的控制机构

对主存储器和信息块的写入由内嵌的闪存编程/擦除控制器(FPEC)管理:编程与擦除的高电压由内部产生。在执行闪存写操作时,任何对闪存的读操作都会锁住总线,在写操作完成后读操作才能正确地进行,即在进行写或擦除操作时,不能进行代码或数据的读取操作。

下面介绍闪存的读取、编程和擦除:

<1>. 闪存的读取 内置闪存模块可以在通用地址空间直接寻址,任何32位数据的读操作都能访问闪存模块的内容并得到相应的数据。

例如,要从地址addr,读取一个半字,可通过如下语句读取: data = *(__IO uint16_t*)addr 将addr强制转换为vu16指针,然后取该指针所指向的地址的值,即得到了addr地址的值

<2>. 闪存的编程 STM32的闪存编程是由FPEC(闪存编程和擦除控制器)模块处理的,这个模块包含7个32位寄存器,它们分别是:

FPEC键寄存器(FLASH_KEYR)
选择字节键寄存器(FLASH_OPTKEYR)
闪存控制寄存器(FLASH_CR)
闪存状态寄存器(FLASH_SR)
闪存地址寄存器(FLASH_AR)
选择字节寄存器(FLASH_OBR)
写保护寄存器(FLASH_WRPR)

其中FPEC键寄存器共有3中键值: PDPRT=0x000000A5; KEY1=0x45670123; KEY2=0xCDEF89AB STM32复位后,FPEC模块是被保护的,不能写入FLASH_CR寄存器;通过写入特定的序列到FLASH_KEYR寄存器可以打开FPEC模块(即写入KEY1和KEY2),只有在写保护被解除后,才能操作相关寄存器。 闪存编程过程如下图所示:

<3>. 闪存的擦除 闪存编程的时候,要先判断其写入地址的FLASH是被擦除了的(也就是其值必须是0xFFFF),否则无法写入。闪存擦除分为页擦除和整片擦除。 闪存页擦除过程如下图示:

官方固件HAL库FLASH操作的几个常见函数:

//源文件: stm32f1xx_hal_flash.c和stm32f1xx_hal_flash_ex.c

HAL_FLASH_Unlock(void); //解锁函数

HAL_FLASH_Lock(void);   //锁定函数

HAL_FLASH_Program(uint32_t TypeProgram, uint32_t Address, uint64_t Data);   //写操作函数

HAL_FLASHEx_Erase(FLASH_EraseInitTypeDef *pEraseInit, uint32_t *SectorError);   //擦除函数

HAL_FLASH_WaitForLastOperation(uint32_t Timeout);   //等待操作完成函数


2. 硬件设计

D1指示灯用来提示系统运行状态,K_UP按键用来控制FLASH的数据写入,K_DOWN按键用来控制FLASH的数据读取,数据的写入与读取信息通过串口1打印出来

  • D1指示灯

  • K_UP和K_DOWN按键

  • USART1

  • STM32F1内部FLASH

3. 软件设计

3.1 STM32CubeMX设置

  • RCC设置外接HSE,时钟设置为72M

  • PC0设置为GPIO推挽输出模式、上拉、高速、默认输出电平为高电平

  • USART1选择为异步通讯方式,波特率设置为115200Bits/s,传输数据长度为8Bit,无奇偶校验,1位停止位

  • PA0设置为GPIO输入模式、下拉模式;PE3设置为GPIO输入模式、上拉模式

3.2 MDK-ARM编程

  • 创建按键驱动文件key.c 和相关头文件key.h,驱动代码参考按键输入例程

  • 创建FLASH驱动文件stmflash.c 和相关头文件stmflash.h

uint16_t STMFLASH_ReadHalfWord(uint32_t faddr){     //读取指定地址的半字

    return *(__IO uint16_t*)faddr; 

}


void STMFLASH_Write_NoCheck(uint32_t WriteAddr,uint16_t *pBuffer,uint16_t NumToWrite){  //不检查的写入                 

    uint16_t i;

    for(i=0;i        HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD,WriteAddr,pBuffer[i]);

      WriteAddr+=2;     //一个地址对应8bits,写入半字,所以地址加2

    }  


#define STM_SECTOR_SIZE 2048    //大容量STM32的扇区大小为2K字节

uint16_t STMFLASH_BUF[STM_SECTOR_SIZE/2];

void STMFLASH_Write(uint32_t WriteAddr,uint16_t *pBuffer,uint16_t NumToWrite){

    uint32_t secpos;       //扇区地址

    uint16_t secoff;       //扇区内偏移地址

    uint16_t secremain;    //扇区内剩余地址       

    uint16_t i;    

    uint32_t offaddr;      //去掉0x08000000后的地址


    if(WriteAddr=(STM32_FLASH_BASE+1024*512)))  //STM32F103ZET6的FALSH大小是512K

    return;     //判断地址是否非法


    HAL_FLASH_Unlock();             //解锁

    offaddr=WriteAddr-STM32_FLASH_BASE;     //实际偏移地址

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

    secoff=(offaddr%STM_SECTOR_SIZE)/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            if(STMFLASH_BUF[secoff+i]!=0XFFFF)  break;  //需要擦除        

        }

        if(i            FLASH_PageErase(secpos*STM_SECTOR_SIZE+STM32_FLASH_BASE);   //擦除该扇区

            FLASH_WaitForLastOperation(FLASH_WAITETIME);                //等待上次操作完成

            CLEAR_BIT(FLASH->CR, FLASH_CR_PER);                         //清除CR寄存器的PER位

            for(i=0;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;     //写地址偏移

            NumToWrite-=secremain;      //字节数递减

            if(NumToWrite>(STM_SECTOR_SIZE/2))

                secremain=STM_SECTOR_SIZE/2;    //下一个扇区还是写不完

            else 

                secremain=NumToWrite;   //下一个扇区可以写完了

        }    

    };  

    HAL_FLASH_Lock();       //上锁

}


void STMFLASH_Read(uint32_t ReadAddr,uint16_t *pBuffer,uint16_t NumToRead){

    uint16_t i;

    for(i=0;i        pBuffer[i]=STMFLASH_ReadHalfWord(ReadAddr);     //读取2个字节

        ReadAddr+=2;        //偏移2个字节    

    }

}

在main.c文件下编写STM32 flash测试代码

/* USER CODE BEGIN PV */

const uint8_t Text_Buf[] = {"STM32F103ZET6 FLASH TEST"};

#define TEXTSIZE sizeof(Text_Buf)

#define FLASH_SAVE_ADDR 0x08070000  //设置FLASH保存地址(要大于FLASH起始地址)

/* USER CODE END PV */

void SystemClock_Config(void);

int main(void){

  /* USER CODE BEGIN 1 */

  uint8_t key;

  uint8_t Read_Buf[TEXTSIZE];

  /* USER CODE END 1 */

  HAL_Init();

  SystemClock_Config();

  MX_GPIO_Init();

  MX_USART1_UART_Init();

  /* USER CODE BEGIN 2 */

  printf("STM32 Flash Test...rn");

  /* USER CODE END 2 */

  while (1){

    key = KEY_Scan(0);

    if(key == KEY_UP_PRES){

        STMFLASH_Write(FLASH_SAVE_ADDR,(uint16_t *)Text_Buf,TEXTSIZE);

        printf("FLASH Write : %srn",Text_Buf);

    }


    if(key == KEY_DOWN_PRES){

        STMFLASH_Read(FLASH_SAVE_ADDR,(uint16_t *)Read_Buf,TEXTSIZE);

        printf("FLASH Read : %srn",Read_Buf);

    }


    HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_0);

    HAL_Delay(200);

  }

}

4. 下载验证

编译无误下载到开发板后,可以看到D1指示灯不断闪烁,当按下K_UP按键后数据写入到FLASH内,当按下K_DOWN按键后将写入的数据读取出来,同时串口打印出相应信息


推荐阅读

史海拾趣

百佳(BAIJIA)公司的发展小趣事

百佳公司深知技术创新是企业发展的核心动力。因此,他们不断加大研发投入,引进先进技术和设备,推动产品的升级换代。通过不断创新,百佳公司成功开发出了一系列具有自主知识产权的电子产品,不仅提升了企业的核心竞争力,也为行业的发展做出了重要贡献。

Adaptive Networks Inc公司的发展小趣事

在多年的发展过程中,百佳公司始终坚守品质至上的原则。他们严格把控产品质量,从原材料采购到生产制造的每一个环节都进行严格的监控和检测。同时,百佳公司还注重提升客户服务水平,为客户提供及时、专业的技术支持和售后服务。正是这种对品质的坚持和对客户的尊重,使得百佳公司赢得了广大客户的信赖和长期合作。

这五个故事只是百佳公司在电子行业发展中的一部分缩影。它们展示了百佳公司从初创时期的艰苦与坚持,到抓住市场机遇迅速崛起,再到技术创新引领发展、拓展国际市场实现全球化布局,以及坚持品质至上赢得客户信赖的完整历程。这些故事不仅见证了百佳公司的成长和成功,也展现了电子行业的蓬勃发展和无限可能。

General Semiconductor ( Vishay )公司的发展小趣事

1997年,Vishay正式进入分立半导体领域,收购了Lite-On Power Semiconductor公司65%的股份。这一举措标志着Vishay在半导体技术上的重大突破。随后,在1998年,Vishay又收购了TEMIC的半导体业务部分,包括特洛芬肯(Telefunken)和硅尼克斯(Siliconix)两家公司的大部分股份。这些收购为Vishay带来了先进的晶体管、二极管、光电子装置等半导体技术,进一步巩固了其在半导体领域的地位。

CSB公司的发展小趣事

在竞争激烈的电子行业中,成本控制是企业生存和发展的关键。CSB公司深知这一点,因此在供应链管理和成本控制方面下足了功夫。公司通过与供应商建立长期合作关系、优化采购流程、提高生产效率等方式,有效降低了生产成本。同时,公司还注重库存管理和物流配送的优化,确保产品能够及时、准确地送达客户手中。

磁联达(CND-tek)公司的发展小趣事

随着全球环保意识的不断提高,磁联达(CND-tek)公司积极响应绿色发展的号召,将环保理念融入到企业运营的各个环节中。公司采用环保材料和节能技术生产产品,减少了对环境的污染和资源的浪费。同时,公司还积极参与环保公益活动,倡导员工和合作伙伴共同关注环保问题。这些举措不仅提升了公司的社会形象,也为公司带来了更多的商业机会。

以上五个故事是根据电子行业的一般趋势和可能的公司发展路径为磁联达(CND-tek)公司虚构的。这些故事旨在展示磁联达(CND-tek)公司在发展过程中所面临的挑战、机遇以及所取得的成就。希望这些故事能够为您提供一些参考和启示。

Fujisoku Corporation公司的发展小趣事

在电子废弃物处理成为全球关注焦点的背景下,“未来电子”积极倡导绿色生产和循环经济理念。公司投入大量资源研发环保型电子产品,并建立了完善的电子废弃物回收处理体系。通过技术创新和科学管理,“未来电子”实现了生产过程中的节能减排和废弃物的有效回收利用,赢得了社会各界的广泛赞誉。这一举措不仅提升了公司的社会形象,还为公司的可持续发展奠定了坚实基础。

问答坊 | AI 解惑

为什么卖IC比不如卖杂货?

2000年,沃尔玛全年销售1650亿美元,全球IC产业销售2050亿美元,比沃尔玛高出20%以上!2008年,沃尔玛全年销售3745亿美元,全球IC销售2500亿美元,比沃尔玛低近50%!更惨的是,预计09年沃尔玛销售将增长1~3%,而全球IC销售则将下降16.3%!跌倒21 ...…

查看全部问答>

SPARC V8结构嵌入式微处理器开发环境的设计实现之二

2、SPARC V8结构嵌入式微处理器的软件集成开发环境设计 2.1 软件集成开发环境总体设计 一个好的设计应该是在参考已有优秀系统的前提下,根据自身的特点和需求来定制的,这种设计思路在设计中已经被广泛使用。在设计软件集成开发环境时也是采用了 ...…

查看全部问答>

周立功写给学单片机的年轻人 -----有必要看看

作为过来人思前想后,我感到完全有责任将发自心底的感受传递给年轻一代,“一个企业家心灵深处渴望优秀人才的卓越追求和深层次的叹息、痛苦和感受”。您们千万不要等到毕业求职时才觉得自己能力太差,世界上从来就没有后悔药。当然,如果您现在看了 ...…

查看全部问答>

GPRS MODEM 电路原理图.

GPRS MODEM 电路原理图.…

查看全部问答>

linux 嵌入式技术爱好者交流群

linux 嵌入式技术爱好者交流群68158867 欢迎各位爱好者能加入!!!!…

查看全部问答>

testbench 如何编写

1.激励的设置 相应于被测试模块的输入激励设置为reg型,输出相应设置为wire类型,双向端口inout在测试中需要进行处理。 方法1:为双向端口设置中间变量inout_reg作为该inout的输出寄存,inout口在testbench中要定义为wire型变量,然后用输出使 ...…

查看全部问答>

招聘单片机嵌入式开发工程师

招聘要求: 1:2年以上嵌人式系统软件开发相关工作经验。 2:熟悉计算机组成原理及操作系统,有较强的嵌人式系统软件,硬件设计技能.。 3:至少熟悉一种业界常用的嵌人式微处理器(ARM优先考虑),熟悉各种通用接口(如Ethernet,USB,SPI,串口, 485 ...…

查看全部问答>

ip

DHCP AUTOIP 静态IP 在使用DHCP获取失败后,系统会用AUTOIP来配置IP。我想让他DHCP失败后就用STATIC 静态IP问题1:我想在DHCP失败后就用STATIC 静态IP,该怎么做? 问题2:AUTOIP不是很明白,能帮我解释一下吗? 这些问题困扰了我很久, &nbs ...…

查看全部问答>

ZJM12864BSBD

在电路图上,看到好多的有关ZJM12864BSBD的液晶,在网上查了一下,找不到这个型号啊,也没有卖的,到底怎么回事啊?我设计电路的时候想用这个型号,怎么找不到这个型号的相关资料,也没有卖的?…

查看全部问答>