STM32学习笔记
适用平台:本文基于 STM32F103C8T6(中容量产品,64KB Flash)讲解,使用标准库开发。
前言:为什么非要学FLASH?
学到这里,很多小伙伴可能有个疑问:我程序不是已经能跑了吗?GPIO、定时器、中断都玩溜了,这个FLASH跟我有啥关系?
别急,先想两个问题:
你的单片机掉电后,之前记的数据(比如密码、校准参数、运行次数)还在吗?RAM里的东西可是掉电就丢的。
你想过没有,以后做产品能不能像手机OTA升级一样,通过串口或蓝牙给单片机"隔空"更新程序?
这两个问题的答案,都指向了今天的主角——内部FLASH。它不只是存程序的地方,更是我们能直接操作的"掉电不丢失"的存储空间。搞懂它,你的STM32才算真正入门了。
一、FLASH简介:它到底是个啥?
STM32F1系列的内部FLASH,说白了就三块东西:
| 组成部分 | 作用 | 人话解释 |
|---|---|---|
| 程序存储器 | 存放我们烧录的程序代码 | 就是你Keil编译生成的hex文件住的家 |
| 系统存储器 | 存放ST出厂的Bootloader | 芯片"原厂自带"的引导程序,用于串口下载 |
| 选项字节 | 配置读写保护、看门狗等 | FLASH的"设置面板",管着安全权限 |
读写FLASH主要有两大用途:
存用户数据:利用程序存储器的剩余空间,保存那些掉电不能丢的参数。比如你的智能台灯要记忆亮度档位,总不能每次上电都让用户重新调吧?
IAP(在应用编程):程序运行过程中,通过串口、蓝牙等接口接收新程序,把自己"更新"掉。这就是很多设备"在线升级"的底层原理。
这里还要区分两个概念:
ICP(在线编程):通过JTAG、SWD或者系统存储器里的Bootloader,把整个FLASH重新烧一遍。这是我们平时用ST-Link下载程序的方式。
IAP(在程序中编程):程序自己跑起来后,用代码去改写FLASH里的内容。比如你做一个Bootloader分区,专门负责接收新程序。
我的理解:你可以把ICP想象成"拆机换硬盘",IAP就是"手机在线系统更新"。我们平时调程序用ICP,做产品升级就要靠IAP了。
二、FLASH的内部结构:拆解它的"五脏六腑"
2.1 闪存模块组织(中容量产品)
先上一张"户型图",看看FLASH这块地皮是怎么划分的:
图1:闪存模块组织(中容量产品)
从这张表可以看出,STM32F1的FLASH分为三大块:
1. 主存储器(程序存储器)
中容量产品共有128页(Page 0 ~ Page 127),每页1KB。
等等,C8T6不是64KB吗?怎么有128页?这里要注意:中容量产品的定义是64KB或128KB,C8T6(64KB)实际上只用到页0~页63。后面多出来的页,在C8T6上是不存在的(或者说超出范围了)。
地址从
0x0800 0000开始,每页递增0x400(也就是1024字节)。
2. 信息块
系统存储器:地址
0x1FFF F000,大小2KB,住着ST的Bootloader。用户选择字节(选项字节):地址
0x1FFF F800,大小16字节,专门管配置。
3. 闪存存储器接口寄存器
从
0x4002 2000开始,是FPEC(闪存编程和擦除控制器)的"操控面板"。包括
FLASH_ACR(访问控制)、FLASH_KEYR(密钥)、FLASH_SR(状态)、FLASH_CR(控制)、FLASH_AR(地址)等。
2.2 FLASH基本结构框图
图2:FLASH基本结构框图
这张图更直观:
左边绿色的FPEC(闪存编程和擦除控制器)是"大管家",所有擦除和编程操作都必须经过它。
右边橙色的三部分就是刚才说的:程序存储器、系统存储器、选项字节。
注意那个"配置读写保护"的箭头,它告诉我们:选项字节可以反过来控制主存储器的访问权限。这就好比小区大门的门禁系统,选项字节就是物业的管理后台。
我的理解:FPEC就像FLASH的"保安队长",你想动FLASH里的内容,得先过它这关。而且它不是"摆设",写错了钥匙或者操作顺序不对,它真给你把门锁死。
三、FLASH的解锁机制:为什么要有"钥匙"?
3.1 三个关键键值
FPEC手里有三把"钥匙":
| 键值名称 | 数值 | 用途 |
|---|---|---|
| RDPRT键 | 0x000000A5 | 解除读保护 |
| KEY1 | 0x45670123 | 解锁FPEC的第一步 |
| KEY2 | 0xCDEF89AB | 解锁FPEC的第二步 |
3.2 解锁流程
复位后,FPEC默认是上锁状态,FLASH_CR寄存器写不进去。这时候你想擦除或编程?门儿都没有。
正确解锁姿势:
往
FLASH_KEYR(密钥寄存器)写入KEY1再往同一个寄存器写入KEY2
顺序必须对,值必须准。这两步走完后,FPEC就解锁了,你可以操作FLASH_CR了。
⚠️ 风险说明:如果你解锁时写错了值,或者顺序颠倒了,FPEC会被锁死!在下次复位之前,你再也动不了FLASH了。这不是吓唬人,是手册里写的。
3.3 加锁
操作完成后,记得设置FLASH_CR的LOCK位为1,把FPEC重新锁上。这就像你进房间拿完东西要关门一样,防止程序跑飞后误写FLASH。
我的理解:这个解锁机制其实是ST给我们的一层保险。你想啊,如果程序跑飞了,指针乱指,万一指到FLASH地址上乱写一通,那程序不就彻底崩了吗?有了这把锁,至少得"对暗号"才能动手,大大降低了误操作的风险。
四、如何读写FLASH?——指针访问与操作流程
4.1 使用指针直接访问存储器
FLASH本质上就是一片地址空间。程序存储器从0x0800 0000开始,那我们直接用指针就能读写:
读操作:
uint16_t Data = *((__IO uint16_t *)(0x08000000));写操作:
*((__IO uint16_t *)(0x08000000)) = 0x1234;
这里__IO被定义为volatile。为什么非要加volatile?因为编译器可能会"自作聪明"地优化掉你的读写操作。加了volatile,就是告诉编译器:"这个地址是硬件寄存器,每次读写都必须真去访问内存,不准偷懒!"
边界意识:STM32F1的主存储器编程,最小单位是半字(16位)。你不能像操作RAM那样一个字节一个字节地写,必须16位一起写。而且写入的地址必须是偶数地址(半字对齐)。
4.2 程序存储器编程流程
图5:程序存储器编程流程
看图说话,编程(写入)的标准流程:
检查LOCK位:如果LOCK=1,先执行解锁序列(KEY1 + KEY2)。
置PG位:把
FLASH_CR的PG位(编程位)置1,告诉FPEC"我要开始写数据了"。写入半字:在目标地址写入一个16位数据。
等待BSY位清零:
FLASH_SR的BSY位(忙标志)为1表示FPEC正在干活,你得轮询等它变0。读校验:写入完成后,把刚才的地址读出来,看看是不是你写的那个数。
⚠️ 核心知识点(必考):FLASH有个"怪脾气"——写入前必须先擦除,而且擦除后所有位变成1,写入只能把1改成0,不能把0改成1。如果你直接往已经有数据的地址上写,结果会是"按位与"的关系,数据绝对不是你想要的。
4.3 程序存储器页擦除流程
图6:程序存储器页擦除流程
既然写之前必须擦除,那怎么擦?
检查并解锁(同上)。
置PER位:把
FLASH_CR的PER位(页擦除位)置1。写地址:把要擦除的页的起始地址写入
FLASH_AR(地址寄存器)。置STRT位:把
FLASH_CR的STRT位(开始位)置1,FPEC就开始擦了。等待BSY=0。
验证:读出这一页,确认全是
0xFFFF(擦除后就是全1)。
我的理解:页擦除就像"整块黑板擦掉重写"。你不可能只擦掉黑板上的某一个字,必须整页擦。所以规划FLASH存储时,要把经常改动的数据放在同一页,避免频繁擦除影响寿命。
4.4 程序存储器全擦除流程
程序存储器全擦除流程
全擦除就更简单了:
解锁。
置MER位:把
FLASH_CR的MER位(全擦除位)置1。置STRT位:开始擦除。
等BSY=0,验证所有页。
⚠️ 风险说明:全擦除会把你的程序也擦掉!如果你在程序运行过程中误触发了全擦除,那芯片就"变砖"了,只能重新用ST-Link烧程序。所以写代码时,MER位一定要谨慎操作。
五、选项字节:那些"隐藏配置"
5.1 选项字节的组织结构
信息块(选项字节)的组织结构
选项字节一共16个字节,但STM32用了一种"冗余"设计:每个配置都存了原值和反值(比如RDP和nRDP)。这样芯片上电时可以互相校验,防止配置因干扰出错。
各字段含义:
| 字段 | 作用 |
|---|---|
| RDP | 读保护级别。写入0xA5解除读保护,其他值会开启不同级别的保护 |
| USER | 配置硬件看门狗(IWDG_SW)、停机/待机模式是否产生复位 |
| Data0/1 | 用户可自定义,一般存一些设备标识或校准参数 |
| WRP0~3 | 写保护位,每一位对应保护4个存储页(中容量产品) |
我的理解:选项字节就像是FLASH的"保险箱密码和权限设置"。RDP读保护一旦开启,别人用J-Link就读不出你的程序了,对知识产权保护很重要。但注意:开启读保护后,如果要解除,FLASH会被全部擦除,这是ST设计的防破解机制。
5.2 选项字节的编程与擦除
选项字节和主存储器的操作类似,但多了几道"安检":
编程流程:
检查
FLASH_SR的BSY位,确认FPEC空闲。解锁
FLASH_CR的OPTWRE位(选项字节写使能)。置
FLASH_CR的OPTPG位(选项字节编程位)为1。写入半字到指定地址。
等待BSY=0。
读出验证。
擦除流程:
检查BSY位。
解锁OPTWRE位。
置
FLASH_CR的OPTER位(选项字节擦除位)为1。置STRT位为1。
等待BSY=0。
读出验证。
我的理解:选项字节操作比主存储器多一个OPTWRE解锁,这相当于"双重认证"。毕竟改选项字节可能影响整个芯片的访问权限,ST当然得谨慎点。
六、器件电子签名:芯片的"身份证"
每颗STM32芯片出厂时,ST都在系统存储区域烧写了一组不可更改的电子签名,包含两个信息:
| 寄存器 | 基地址 | 大小 | 用途 |
|---|---|---|---|
| 闪存容量寄存器 | 0x1FFF F7E0 | 16位 | 表示芯片的FLASH容量(单位KB)。比如C8T6读出来就是64 |
| 产品唯一身份标识寄存器 | 0x1FFF F7E8 | 96位 | 全球唯一的芯片ID,每颗芯片都不一样 |
这有啥用?
闪存容量寄存器:程序可以自动识别自己跑在什么容量的芯片上,做自适应配置。
唯一身份标识:可以做设备的"硬件指纹"。比如你想做一机一码的加密,或者给每个设备分配独立序列号,读这个寄存器就行了。因为它是出厂固化、不可更改的,比存在FLASH里的数据靠谱多了。
我的理解:这就像是芯片的"身份证号"和"户口本"。96位的唯一ID,全世界没有两颗一样的芯片,做产品追溯、防复制、加密绑定都特别好使。
七、我的理解 / 心得
学FLASH之前,我一直以为它只能存程序。现在明白了,只要规划好地址,完全可以把用户数据存在程序后面的空白页里。比如C8T6的64KB,程序只占用了前20KB,后面44KB就是"自由空间"。
关于擦除寿命:FLASH的擦写次数是有限的(手册标称1万次)。所以做数据存储时,不能频繁擦同一页。如果要做EEPROM效果,可以考虑"磨损均衡"算法,或者直接用STM32的备份寄存器、外接EEPROM。
八、踩坑记录
| 踩坑场景 | 现象 | 原因 | 解决办法 |
|---|---|---|---|
| 未解锁直接写FLASH | 程序卡死或数据没写入 | FLASH_CR处于锁定状态 | 严格按KEY1→KEY2顺序解锁 |
| 写入前未擦除 | 读出来的数据和写入值不一致 | FLASH只能1变0,不能0变1 | 写入前必须执行页擦除 |
| 擦除/编程时掉电 | 芯片变砖,无法下载程序 | 操作过程中断导致FLASH损坏 | 确保电源稳定,或加掉电检测 |
| 选项字节误操作 | 无法通过SWD下载程序 | 开启了读保护或写保护 | 按住复位键,在Keil点击下载瞬间松开复位 |
| 指针未加volatile | 开启优化后代码失效 | 编译器优化掉了寄存器访问 | 所有硬件寄存器指针前加__IO或volatile |
九、总结表格
| 知识点 | 核心内容 | 注意事项 |
|---|---|---|
| FLASH组成 | 程序存储器 + 系统存储器 + 选项字节 | C8T6程序存储器为64KB(64页) |
| 主存储器页大小 | 每页1KB | 擦除最小单位是页,写入最小单位是半字(16位) |
| 解锁键值 | KEY1=0x45670123,KEY2=0xCDEF89AB | 顺序不能错,错则锁死 |
| 编程前提 | 必须先擦除 | 擦除后全为1,写入只能1变0 |
| 编程流程 | 解锁→置PG→写半字→等BSY→校验 | 地址必须半字对齐 |
| 页擦除流程 | 解锁→置PER→写地址→置STRT→等BSY | 会擦除整页1KB数据 |
| 全擦除流程 | 解锁→置MER→置STRT→等BSY | 会擦掉程序本身,慎用! |
| 选项字节 | RDP/USER/Data/WRP,每项存原值+反值 | 操作前需额外解锁OPTWRE |
| 电子签名 | 容量寄存器+96位唯一ID,出厂固化不可改 | 可用于设备加密和序列号 |
写这篇文章的时候,我把江科大的视频又回顾了一遍,结合《STM32F10xxx参考手册》把FLASH的寄存器位一个个核对过去,才发现之前很多"想当然"的理解都是错的。比如我以为FLASH可以像RAM一样随便写,实际上必须先擦除;我以为解锁就是走个形式,实际上写错真的会锁死。
STM32的FLASH模块虽然看起来就是"擦了写、写了读",但里面的门道真不少——解锁机制、选项字节、读写保护,每一步都是ST为了可靠性设计的"保险丝"。搞懂这些,不只是为了应付考试,更是为了以后做产品时少踩坑。
📌开发环境:Keil5 + STM32F103C8T6最小系统板 + ST-Link
📌参考资料:江科大STM32入门教程、《STM32F10xxx参考手册》
如果本文对你有帮助,欢迎点赞 👍 收藏 ⭐ !有任何问题欢迎在评论区交流,一起学习进步!