STM32 Flash详解教程文章

 目录

Flash基本概念理解

Flash编程接口FPEC

Flash擦除/写入流程图

Flash选项字节基本概念理解

Flash电子签名

函数读取地址下存放的数据

Flash的数据处理限制部分


                                            编写不易,请勿搬运,感谢理解!!!

Flash基本概念理解

        STM32的Flash里面包含,程序存储器系统存储器选项字节,通过对Flash闪存的片上外设接口(接口地址在ram地址),可以对程序存储器跟选项字节进行擦除和编程。

·程序存储器:用来下载存放程序的位置

·系统存储器:用来存放BootoLoader芯片出厂自带程序

·选项字节:用来配置Flash读保护,写保护,等功能

        其常见在程序中的用法是,通过程序软件编程利用内部Flash数据掉电不丢失的特性与内部Flash通常不用使用完,将重要的参数存储在Flash的最后几页内

        和使用指针指向系统存储区域内,芯片的ID号地址,读取属于芯片自己的ID号,通过利用芯片不同的ID号,在程序执行时判断芯片ID号,如果不同则不执行程序,来实现程序只能在特定芯片上运行。

        最后一种用法就是通过对Flash的选项字节配置,来完成程序的读保护跟写保护,在程序中来配置。

                                                        flash分配地址详解

        在STM32F1系列芯片中,其中Flash每页的起始地址后三位均以 000 400 800 C00 开头。

Flash编程接口FPEC

        Flash编程控制器 FPEC 全称是 Flash Program/Erase Controller 用于对Flash存储器进行读写和擦除的硬件模块。该模块包含7个32位寄存器。

·FPEC键寄存器(FLASH_KEYR):用于解锁FPEC外设,允许进行Flash编程或擦除操作

·选择字节键寄存器(FLASH_OPTKEYR):用于解锁Flash选项字节的修改操作

·闪存控制寄存器(FLASH_CR):控制Flash编程和擦除操作的核心寄存器

·闪存状态寄存器(FLASH_SR):指示Flash当前状态和操作结果

·闪存地址寄存器(FLASH_AR):指定Flash操作的目标地址

·选择字节寄存器(FLASH_OBR):读取选项字节当前的配置状态

·写保护寄存器(FLASH_WRPR):指示和配置Flash的写保护状态

        7个寄存器中需要注意的是,Flash_SR寄存器的BSY位,当该位为1的时候代表正在进行flash操作,当为0的时候代表操作结束或者发生错误,通常来读取该位的值来判断flash操作是否完成结束。

        Flash_CR寄存器的PG位代表是否选择编程操作,PER位是否选择页擦除操作,MER位是否选择全擦除操作,OPTPG选择字节编程,OPTER擦除选择字节,STRT开始操作,该位为1的时候将触发擦除操作,只能由软件置1并在BSY为1的时候清除为0,LOCK锁为1代表FPEC和Flash_CR被锁住,为0代表解锁成功。

                                                            寄存器地址范围图

        在芯片复位后,FPEC模块是被保护的,Flash_CR寄存器不能被读写操作,首先需要对,Flash_KEYR写入特定键值KEY1  KEY2 来完成解锁操作,如果写入错误会在下次复位之前锁死,FPEC跟Flash_CR寄存器。

·RDPRT键 = 0x000000A5    //解除读保护

·KEY1 = 0x45670123           //KEY1+KEY2解除FPEC锁

·KEY2 = 0xCDEF89AB

        需要注意的是,在解锁FPEC对Flash_CR寄存器操作完成对Flash的读|写之后,需要设置Flash_CR中的LOCK位锁住FPEC跟Flash_CR位,来防止数据的误写入导致对Flash进行操作。

Flash擦除/写入流程图

                                                Flash写入数据流程图

                                                        Flash页擦除流程图

                                                        Flash全擦除流程图

        在三张流程图中只有擦除流程图需要置Flash_CR寄存器的STRT位为1,在上文有介绍过当为1的时候会执行擦除操作,然后当Flash_SR寄存器的BSY为0的是时候会将STRT位置0。

Flash选项字节基本概念理解

       在芯片地址中给选项字节分配了16个字节的地址,其中8个字节的地址用来备份,当前选项字节的数据,只有8个字节用来配置Flash选项字节。其中四个字节用来写保护,1个字节用来读保护,1个字节配置选项,2个字节存储用户数据。  

                                                        分配地址图

       需要注意的是,在完成对FPEC的解锁之后,还需要再次对Flash_OPTKKEYR写入KEY1 KEY2来完成对选项字节的解锁,同事置Flash_CR的OPTWRE位为1才能对选项字节进行写入操作。 

         

·RDP:写入RDPRT键(0x000000A5)后解除读保护

·USER:硬件看门狗配置 停机模式|待机模式 是否产生复位

·Data:用户自定义数据

·WRP:该位置用来配置写保护

Flash电子签名

        电子签名是芯片出厂的时候由芯片制造商写入的信息,存放地址实在系统存储区域,用来表示芯片的唯一性跟芯片的flash容量大小。

·0x1FFFF7E0 ~ 0x1FFFF7E3 存放flash大小

·0x1FFFF7E8 ~ 0x1FFFF7EF 存储 UID。

        其中UID Uniqune Device ID 唯一设备标识,共96位12字节改信息用户不可更改,可以利用指针进行访问。

函数读取地址下存放的数据

       上文中提到了在大多数单片机程序中因为自身Flash没有被使用完,因此可以对剩余Flash空间进行读写数据操作函数如下。

uint32_t FlashR_Word(uint32_t address)
{
    return *((__IO uint32_t *)(address))
}

        函数中__IO来自官方定义库文件定义如下

#define __IO  voliate

        将宏定义带入到函数中就能得到下面代码

(__IO uint32_t *)(address) = (volitate uint32_t *)(address)

        改代码作用是将函数局部变量参数转化为指向该参数的uint32_t,指针类型,同时括号外面的*就是指针解引用的意思,就是返回指针指向该地址下的数值,这里因为声明的指针类型为uint32_t类型所以就能返回一个字的地址。

        也就是32位数据,4个字节的数据,同理可以得到读取16位数据的函数,还有读取一个字节的函数,只要把函数内声明的指针类型给改了就行了

uint16_t FlashR_HWord(uint32_t address)
{
    return *((__IO uint16_t *)(address))
}

uint8_t FlashR_Byte(uint32_t address)
{
    return *((__IO uint8_t *)(address))
}
//这里因为改变了 内部声明的指针类型 跟返回值类型所以能获取的数据大小也被限制了

Flash的数据处理限制部分

        在对Flash进行数据存储的通常情况下需要再程序中定义一个 uint16_t data[512] 数组,大小是1024也就是一个Flash页面大小,先将数据写入ram的数组里面然后再写入Flash里面进行存储,通常这种情况是因为Flash本身的写入限制问题。

       首先Flash擦除的最小单位是页,同时在Flash进行写入的时候不存在数据覆盖这种写法,也就是将数据0x12345对改地址重新写入数据0x56787是写不进去的是因为Flash中的数据只能从1->0,而不能从0->1,而在擦除完单页Flash之后,该页存储的数据全部为0xFFFF FFFF 也就是全部是1。

        这就导致了如果直接对Flash进行数据写入,会发现在数据写入到最后的时候如果想要更改前面的数据大概率是需要擦除才能进行写入的,但是如果进行擦除其他位的数据又丢失,所以通常常用的数据数据手段也就是。

        将Flash单页数据读出保存在一个Flash页大小的数组里面进行更改数据,然后将整页数据重新写入到Flash数组里面来完成操作,跟将需要写入的数据先在数组里面进行覆盖,最后统一写入Flash里面。

Step 1:先从 Flash 读取数据到 RAM
Step 2:在 RAM 修改数据
Step 3:擦除 整个 Flash Page(Flash 只能整页擦除)。
Step 4:将 RAM 数据重新写入 Flash

#include "stm32f10x.h"
#include "Store.h"
#include "MyFlash.h"
//数组内512个数据 每个数据类型是 uint16_t 也就是2个字节 数组能存放1024字节数据 也就是对应Flash一页的容量
uint16_t Store_Data[512];
//在ram 里面定义一个数组 需要备份的时候统一转到闪存里面
void Flash_Init(void)
{

	if(Flash_WHWord(0x08000000)!=0xA5A5)
	{
		Earse_FlashPage(0x08000000);
		Flash_WHWord(0x08000000 ,0xA5A5 );
		for(uint16_t i = 1;i<512;i++)
		{
			Flash_WHWord(0x08000000 +i*2 ,0x0000 );
		}
	}
	//将Flash里面数据保存读到ram里面
	for(uint16_t i = 0; i<512;i++)
	{
		Store_Data[i] = FlashRH_Word(0x08000000 + i*2);
	
	}
}
void Flash_Save(void)
{
	//先擦除Flash在进行写入,不然会发现写不进去
	Earse_FlashPage(0x08000000);
	for(uint16_t i = 0;i<512;i++)
	{
		Flash_WHWord(0x08000000 + i*2,Store_Data[i] );
	}
}
//函数用来清除数组 Flash里面的数据
void Flah_Clear(void)
{
	for(uint16_t i = 0;i<512;i++)
	{
		Store_Data[i] = 0x0000;
	}
	Flash_Save();
}

                                                                代码部分

        这里需要注意的是i*2问题,因为在Flash里面最小写入单元跟最小存储单元室一个半字,也就是16位,4个字节,举个例子就是0x0800 0000是单元起始地址,到0x0800 0001 地址该单元结束,该地址指向存储单元,每个地址指向的存储单元能够存储 2个字节。

        所以在数据数据进行写入的时候,地址的起始地址都是用2的倍数开头的,因为像0x0800 0001这种是不合法地址,是没有办法进行写入半字数据。

        这种用法通常是用在单片机程序很小,自身Flash占用不完的情况下使用,而有没有办法知道自己的程序占用多少字节,同时改变程序烧录的位置,无论程序大小都不能烧录到自己保存数据的Flash数据部分这种方法也是有的。

                                        查看程序自身大小方法图

        魔术棒里面有程序的起始地址还有结束地址,只要把在Keil5里面可以更改这两个地址从而达到更改程序的烧录地址的效果。

                                欢迎指正,希望对你,有所帮助!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/968040.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Flutter项目试水

1基本介绍 本文章在构建您的第一个 Flutter 应用指导下进行实践 可作为项目实践的辅助参考资料 Flutter 是 Google 的界面工具包&#xff0c;用于通过单一代码库针对移动设备、Web 和桌面设备构建应用。在此 Codelab 中&#xff0c;您将构建以下 Flutter 应用。 该应用可以…

第六篇:数字逻辑的“矩阵革命”——域控制器中的组合电路设计

副标题 &#xff1a;用卡诺图破解车身域控制器的逻辑迷宫&#xff0c;揭秘华为DriveONE的“数字特工” ▍ 开篇&#xff1a;黑客帝国世界观映射 > "Welcome to the Real World." —— Morpheus > 在数字逻辑的世界里&#xff0c;组合电路就是构建Matr…

成为高能量体质:从身体神庙到精神圣殿的修炼之路

清晨五点&#xff0c;当城市还在沉睡&#xff0c;瑜伽垫上的汗水已经折射出第一缕阳光。这不是苦行僧的自虐&#xff0c;而是高能量体质者的日常仪式。在这个能量稀缺的时代&#xff0c;如何把自己修炼成一座小型核电站&#xff1f;答案就藏在身体的每个细胞里。 一、能量管理…

从大规模恶意攻击 DeepSeek 事件看 AI 创新隐忧:安全可观测体系建设刻不容缓

作者&#xff1a;羿莉&#xff08;萧羿&#xff09; 全球出圈的中国大模型 DeepSeek 作为一款革命性的大型语言模型&#xff0c;以其卓越的自然语言处理能力和创新性成本控制引领行业前沿。该模型不仅在性能上媲美 OpenAI-o1&#xff0c;而且在推理模型的成本优化上实现了突破…

低成本+高性能+超灵活!Deepseek 671B+Milvus重新定义知识库搭建

“老板说&#xff0c;这个项目得上Deepseek,还得再做个知识库...” 还有哪个开发者&#xff0c;最近没听到这样的抱怨&#xff1f; Deepseek爆火&#xff0c;推理端的智能提速&#xff0c;算力成本急剧下降&#xff0c;让不少原本不想用大模型&#xff0c;用不起大模型的企业&a…

CSS 实现下拉菜单效果实例解析

1. 引言 在 Web 开发过程中&#xff0c;下拉菜单是一种常见且十分实用的交互组件。很多前端教程都提供过简单的下拉菜单示例&#xff0c;本文将以一个简洁的实例为出发点&#xff0c;从 HTML 结构、CSS 样式以及整体交互逻辑三个层面进行详细解析&#xff0c;帮助大家理解纯 C…

VSCode中出现“#include错误,请更新includePath“问题,解决方法

1、出现的问题 在编写C程序时&#xff0c;想引用头文件但是出现如下提示&#xff1a; &#xff08;1&#xff09;首先检查要引用的头文件是否存在&#xff0c;位于哪里。 &#xff08;2&#xff09;如果头文件存在&#xff0c;在编译时提醒VSCode终端中"#include错误&am…

【RabbitMQ的监听器容器Simple和Direct】 实现和场景区别

在Spring Boot中&#xff0c;RabbitMQ的两种监听器容器&#xff08;SimpleMessageListenerContainer和DirectMessageListenerContainer&#xff09;在实现机制和使用场景上有显著差异。以下是它们的核心区别、配置方式及最佳实践&#xff1a; Simple类型 Direct类型 一、核心…

yolo11自训练极简教程 - 训练侦测

去年我处理过的最后一个版本是yolov10.新年再次着手处理视频识别的工作。发现自清华的v10之后&#xff0c;去年下半年v11再次发布了。国内访问github有些问题&#xff0c;但是yolo网站是可以方便访问的&#xff1a; Train - Ultralytics YOLO Docs 国外的知识库做的很棒&…

大数据学习之SparkStreaming、PB级百战出行网约车项目一

一.SparkStreaming 163.SparkStreaming概述 Spark Streaming is an extension of the core Spark API that enables scalable, high-throughput, fault-tolerant stream processing of live data streams. Spark Streaming 是核心 Spark API 的扩展&#xff0c;支持实时数据…

day5QT套接字通信

Widget.cpp #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);objtimer new QTimer (this);//连接定时器的timeout信号到启动的槽函数//connect(objtimer,&…

【免费】2007-2020年各省医疗卫生支出数据

2007-2020年各省医疗卫生支出数据 1、时间&#xff1a;2007-2020年 2、来源&#xff1a;国家统计局、统计年鉴 3、指标&#xff1a;行政区划代码、地区名称、年份、医疗卫生支出 4、范围&#xff1a;31省 5、指标说明&#xff1a;地方财政医疗卫生支出是指地方ZF从其财政预…

本地基于GGUF部署的DeepSeek实现轻量级调优之二:检索增强生成(RAG)

前文&#xff0c;我们在本地windows电脑基于GGUF文件&#xff0c;部署了DeepSeek R1 1.5B模型&#xff0c;如果想在离线模式下加载本地的DeepSeek模型自行对进行训练时&#xff0c;是不能直接使用GGUF文件进行训练的&#xff0c;但是可以对模型进行微调&#xff0c;以下说的是第…

16vue3实战-----动态路由

16vue3实战-----动态路由 1.思路2.实现2.1创建所有的vue组件2.2创建所有的路由对象文件(与上述中的vue文件一一对应)2.3动态加载所有的路由对象文件2.4根据菜单动态映射正确的路由2.5解决main页面刷新的问题2.6解决main的第一个页面匹配显示的问题2.7根据path匹配menu 1.思路 …

WPS如何接入DeepSeek(通过JS宏调用)

WPS如何接入DeepSeek 一、文本扩写二、校对三、翻译 本文介绍如何通过 WPS JS宏调用 DeepSeek 大模型&#xff0c;实现自动化文本扩写、校对和翻译等功能。 一、文本扩写 1、随便打开一个word文档&#xff0c;点击工具栏“工具”。 2、点击“开发工具”。 3、点击“查看代码”…

前端快速生成接口方法

大家好&#xff0c;我是苏麟&#xff0c;今天聊一下OpenApi。 官网 &#xff1a; umijs/openapi - npm 安装命令 npm i --save-dev umijs/openapi 在根目录&#xff08;项目目录下&#xff09;创建文件 openapi.config.js import { generateService } from umijs/openapi// 自…

云消息队列 ApsaraMQ Serverless 演进:高弹性低成本、更稳定更安全、智能化免运维

如今&#xff0c;消息队列已成为分布式架构中不可或缺的关键服务&#xff0c;为电商、物联网、游戏和教育等行业&#xff0c;提供了异步解耦、集成、高性能和高可靠的核心价值。 过去一年&#xff0c;我们发布了云消息队列 ApsaraMQ 全系列产品 Serverless 化&#xff0c;面向…

Spring依赖注入方式

写在前面&#xff1a;大家好&#xff01;我是晴空๓。如果博客中有不足或者的错误的地方欢迎在评论区或者私信我指正&#xff0c;感谢大家的不吝赐教。我的唯一博客更新地址是&#xff1a;https://ac-fun.blog.csdn.net/。非常感谢大家的支持。一起加油&#xff0c;冲鸭&#x…

Mysql索引失效的场景

对索引列使用函数或表达式&#xff0c;或参与计算&#xff08;优化方法&#xff1a;将计算移到条件右侧&#xff1a;&#xff09;例 优化 对索引列进行隐式类型转换&#xff0c;条件中的数据类型与索引列的数据类型不匹配&#xff0c;会进行隐式类型转换 以like 通配符开头索…

CTFHub-RCE系列wp

目录标题 引言什么是RCE漏洞 eval执行文件包含文件包含php://input读取源代码远程包含 命令注入无过滤过滤cat过滤空格过滤目录分隔符过滤运算符综合过滤练习 引言 题目共有如下类型 什么是RCE漏洞 RCE漏洞&#xff0c;全称是Remote Code Execution漏洞&#xff0c;翻译成中文…