1.FLASH简介
- STM32F1系列的FLASH包含程序存储器、系统存储器和选项字节三个部分,通过闪存存储器接口(外设)可以对程序存储器和选项字节进行擦除和编程
- 我们怎么操作这些存储器呢?这就需要用到这个闪存存储器接口了,闪存存储器接口是一个外设,是这个闪存的管理员,毕竟闪存的操作很麻烦,涉及到擦除、编程、等待忙、解锁等等操作,所以这里,我们需要把我们的指令和数据,写入到这个外设的相应寄存器,然后这个外设就会自动去操作对应的存储空间,后面写这个外设可以对程序存储器和选项字节,这两部分,进行擦除和编程,对比上面的三个部分少了系统存储器这个区域,因为系统存储器是原厂写入的Bootloader程序,不允许我们修改
- 读写FLASH的用途:
- 利用程序存储器的剩余空间来保存掉电不丢失的用户数据
- 对于我们这个C8T6芯片来说,它的程序存储器容量是64K,一般我们写个简单的程序,可能就只占前面的很小一部分空间,剩下的大片空余空间可以加以利用,比如存储一些我们自定义的数据,这样就非常方便,而且可以充分利用盗源,不过这里要注意,我们在选取存储区域时,一定不要覆盖了原有的程序,要不然程序自己把自己给破坏了,之后程序就运行不了了,一般存储少量的数据,我们就选最后几页存储就行了
- 通过在程序中编程(IAP),实现程序的自我更新
- 我们在存储用户数据时要避开程序本身,以免破坏程序,但如果,我们就非要修改程序本身会发生什么呢?那这就是第二点提到的功能,在程序中编程,利用程序,来修改程序本身,实现程序的自我更新,这个在程序中编程,就是IAP
- 利用程序存储器的剩余空间来保存掉电不丢失的用户数据
- 在线编程(In-Circuit Programming-lCP)也可以叫在电路中编程,用于更新程序存储器的全部内容,它通过JTAG、SWD协议或系统加载程序(Bootloader)下载程序
- 这个JTAG、SWD,就是仿真器下载程序,就是我们目前用的STLINK使用SWD下载程序,每次下载,都是把整个程序壳全更新掉,那系统加载程序,就是系统存储器的Bootloader,也就是串口下载,串口下载,也是更新整个程序,这就是我们一直在用的ICP下载方式,
- 在程序中编程(In-Application Programming-IAP)可以使用微控制器支持的任一种通信接口下载程序
- 怎么实现?那比如,这是整个程序存储器,我们首先需要自己写一个Bootloader程序,并且存放在程序更新时,不会覆盖的地方,比如我们放在这后面,然后,需要更新程序时,我们控制程序跳转到这个自己写的Bootloader里来,在这里面,我们就可以接收任意一种通信接口传过来的数据,比如串口、USB、蓝牙转串口、WIFI转串口等等,这个传过来的数据,就是待更新的程序,然后我们控制FLASH读写,把收到的程序,写入到前面,程序正常运行的地方,写完之后,再控制程序跳转回正常运行的地方或者直接复位,这样程序就完成了自我升级,这个过程其实就是和系统存储器这个的Bootloader-样,因为程序要实现自我升级,在升级过程中肯定需要布置一个辅助的小机器人来临时干活,只不过是系统存储器的Bootloader写死了,只能用串口下载到指定位置 ,启动方式也不方便,只能配置BOOT引脚触发启动,而我们自己写Bootloader的话,就可以想怎么收怎么收,想写到哪就写到哪,想怎么启动就怎么启动,并且这整个升级过程,程序都可以自主完成,实现在程序中编程,更进一步,就可以直接实现远程升级了,对吧,非常方便
2.闪存模块组织
我们C8T6芯片的闪存容量是64K,属于中容量产,对于小容量产品和大容量产品,闪存的分配方式有些区别,可以参考手册
首先看一下第一列的几个块,这里分为了三个块,第一个是主存储器也就是我们刚才说的程序存储器,用来存放程序代码的,这是最主要,也是容量最大的一块,下面第二个,是信息块,里面又可以分为启动程序代码和用户选择字节,其中启动程序代码就是刚才说的系统存储器,存放的是原厂写入的Bootloader,用于串口下载,然后下面这个用户选择字节也就是刚才说的选项字节,存放一些独立的参数,这个选项字节,在手册里一直都称作选择字节,英文是Option Bytes,然后最后一块是闪存存储器接口寄存器,这一块的存储器,实际上并不属于闪存,你看它的地址就知道,地址都是40开头的,说明这个存储器接口寄存器就是一个普通的外设,和之前讲的GPIO、定时器、串口等等都是一个性质的东西,这些存储器,它们的存储介质,也都是SRAM,这个闪存存储器接口就是上面这些闪存的管理员,这些寄存器,就是用来控制擦除和编程这个过程的,只有程序存储器、系统存储器和选项字节才是真正的闪存
对于主存储器这里对它进行了分页,分页是为了更好地管理闪存,擦除和写保护,都是以而为单位的,这一点和之前W25Q64那一节的闪存一样,同为闪存,它们的特性基本一样,写入前必须擦除,擦除必须以最小单位进行,擦除后数据位全变为1,数据只能1写0,不能0写1,擦除和写入之后都需要等待忙,这些都是一样的,那W25064的分配方式是先分为块Block,再分为康区Sector比较复杂,这里,就比较简单了,它只有一个基本单位,就是页,每页的大小都是1K,0~127,总共128页,总容量就是128K,对于C8T6来说,它只有64K,所以C8T6的页只有一半,0~63,总共64页,共64K,然后看一下页的地址范围:第一个页的起始地址,就是程序存储器的起始地址,0x0800 0000,之后就是一个字节一个地址,依次线分配了,看一下每页起始地址的规律首先是0000然后0400、0300、0C00...到FC00,所以,地址只要以000、400、800、C00结尾的,都一定是页的起始地址,对吧
系统存储器,它的起始地址是0x1FFF F000,它的容量是2K,再下面,选项字节,起始地址是0X1FFF F800,容量是16个字节,里面只有几个字节的配置参数,我们平时说的,芯片闪存容量是64K、128K,它指的只是主存储器的容量,下面信息块的两个东西,虽然也是闪存,但是并不统计在这个容量里,这就是闪存的分配方式
那最后,就是这个闪存接口寄存器了,里面包括KEYR键寄存器、SR状态寄存器、CR控制寄存器等等,外设的起始地址是0x4002 2000,每个寄存器都是4个字节,也就是32位,这就是这个外设的寄存器
3.FLASH基本结构
整个闪存分为程序存储器、系统存储器和选项字节三部分,这里程序存储器我以C8T6为例,它是64K的共64页,最后一页的起始地址是0800 FC00,左边这里,是闪存存储器接口,手册里还有个名称闪存编程和擦除控制器(FPEC),然后,这个控制器,就是闪存的管理员,它可以对程序存储器进行擦除和编程,也可以对选项字节进行擦除和编程,当然系统存储器是不能擦除和编程的,之后选项字节,里面有很大一部分配置位,其实是配置主程序存储器的读写保护的,所以右边画的,写入选项字节,可以配置程序存储器的读写保护,当然选项字节还有几个别的配置参数
4.操作控制器FPEC
细节问题,如何操作这个控制器FPEC,来对程序存储器和选项字节进行擦除和编程,首先,第一步,是FLASH解锁,这个和之前W25Q64-样,W25064操作之前需要写使能,这个FLASH,操作之前需要解锁,目的,都是为了防止误操作,那这里,解锁的方式,和之前独立看门狗一样,都是通过在键寄存器写入指定的键值来实现,使用键寄存器的好处就是,更能防止误操作,每一个指令,必须输密码才能完成,,,,,,,,,,,,,,,,,,,,,
4.1FLASH解锁
5.使用指针访问存储器
因为STM32内部的存储器是直接挂在总线上的,所以这时,再读写某个存储器就非常简单了,直接使用C语言的指针,来访问即可,__IO对应C语言的关键字,volatile,volatile,直译就是,易变的数据,在这个数据类型前面加上volatile,是一个安全保障措施,用一句话来说,就是防止编译器优化,首先说一下,Keil编译器默认情况下是最低优化等级,这时,加不加这个volatile,都没有影响,如果,你要提高编译器优化等级,这时候就会有问题了,用途就是,可以去除无用的繁架代码,降低代码空间,提升运行效率,但优化之后,编译器在某些地方可能会弄巧成拙,比如,你想用变量计数空循环的方式实现延时函数,那编译器优化的时候,可能会说你这段延时函数好像没用啊还自白浪赛时间,我直接给你优化掉,这就弄巧成拙了,因为我们本意就是靠浪装时间来延时,这时,我们就可以在延时的变量定义前面加上volatile,告诉编译器我无论对这个变量干什么,你都原封不动地去执行,别给我优化掉了
另外,编译器还会利用缓存来加速代码,比如如果你要频繁读写内存的某个变量,那最常见的优化方式就是先把变量转移到高速缓存里来,在STM32内核里,有一个类似缓存的工作组寄存器,这些寄存器的访问速度最快,我先把变量放在缓存里,需要读写的时候,直接访问缓存就行了,用完之后,再写回内存,这是一个优化方案,但是,如果你的程序里有多个线程,比如中断函数,在中断函数里,你改变了这个原始变量,那可能缓存并不知道你更改了,下次程序还看缓存的变量,就会造成数据更改不同步的问题,这时,我们的做法也是,读取变量定义的前面加上一个volatile,告诉编译器这个变量是易变的,每次读取你都得执行到位,要直接从内存里找,不要再用缓存优化了,所以总结-下就是,如果开启了编译器优化,在无意义加减变量,多线程更改变量,读写与硬件相关的存储器时都需要加上volatile,防止被编译器优化,如果你默认,不开编译器优化,那就无所谓了,加不加都一样
其中,读取,可以直接读,写入,需要解锁,并且执行后面的流程
5.1 程序存储器全擦除
第 步是读取LOCK位,看一下芯片锁没锁,下面,如果LOCK位=1,锁住了,就执行解锁过程,在KEYR寄存器,先写入KEY1,再写入KEY2,这里,如果它当前没锁住就不用解锁了,在库函数中,并没有这个判断,库函数是直接执行解锁过程,解锁之后,首先,置控制寄存器里的MER(Mass Erase)位为1,然后再置STRT(Start)位为1,其中置STRT为1是触发条件,STRT为1之后,芯片开始干活,然后芯片看到,MER位是1,它就知道,接下来要干的活就是全擦除,这样内部电路就会自动执行全擦除的过程,然后继续擦除也是需要花一段时间的,所以擦除过程开始后程序要执行等待,判断状态寄存器的BSY(BuSy)位是否为1,BSY位表示芯片是否处于忙状态,BSY为1,表示芯片忙,所以这里,如果判断BSY=1,就跳转回来,继续循环判断,直到BSY=0,跳出循环,这样全擦除过程就结束了,最后一步,这里写的是读出并验证所有页的数据,读出并验证所有页的数据,这个是测试程序才要做的,正常情况下,全擦除完成了,我们默认就是成功了
5.2 程序存储器页擦除
第一步,上面这块一样的,是解锁的流程,第二步,这个方框里的置控制寄存器的PER(Page Erase) 位为1,然后,在AR(Address Register)地址寄存器中选择要擦除的页,最后,置控制寄存器的STRT位为1,置STRT为1,也是触发条件,STRT为1,芯片开始干活,然后芯片看到,PER=1,它就知道,接下来要执行页擦除,然后闪存不止一页,页擦除,芯片就要知道要具体要擦哪一页,所以,它会继续看AR寄存器的数据,AR寄存器我们要提前写入一个页起始地址,这样芯片就会把我们指定的一页,给擦除掉,然后擦除开始之后,我们也需要等待BSY位,最后,读出并验证数据,这个就不用看了,这就是页擦除的过程
5.3 程序存储器编程
擦除之后,我们就可以执行写入的流程了,另外说明一下,STM32的闪存在写入之前会检查指定地址有没有擦除,如果没有擦除就写入,STM32则不执行写入操作,除非写入的全是0,这一个数据是例外,因为不擦除就写入,可能会写入错误,但全写入0的话,写入肯定是没问题的,写入的第一步,也是解锁,然后第二步,我们需要置控制寄存器的PG(Programming)位为1,表示我们即将写入数据,之后第三步,就是在指定的地址写入半字,这步,我们需要用到刚才说的这句代码使用指针,在指定地址写入数据,想写入什么数据在这里指定即可,另外这里注意写入操作,只能以半字的新式写入,其中字,Word,就是32位数据,半字,HalfWord,就是16位数据,字节,Byte,就是8位数据,如果你要写入32位,就分两次完成,这个就比较麻烦了,如果你想单独写入一个字节,还要保留另一个字节的原始数据的话,那就只能把整页数据都读到SRAM,再随意修改SRAM数据,修改全部完成之后再把墪页都擦除,最后再把整页都写回去,所以,如果你想像SRAM一样随心所欲的读写,那最好的加法就是先把闪存的一页读到SRAM中,读写完成后,再擦除一页,整体写回去,那回到流程图这里,写入数据这个代码,就是触发开始的条件,不需要像擦除一样,置STRT位了,写入半字之后,芯片会处于忙状态,等待BSY位清0,这样写入数据的过程就完成了,那每执行这样一个流程,只能写入一个半字,如果要写入很多数据,那就不断循环调用这个流程,就可以了
6.选项字节
首先这里是选项字节的组织和用途,图里的起始地址就是我们刚才说的选项字节的起始地址1FFFF800,这一块的这些数据就是前面的用户选项字节,里面总共只有16个字节,其中有一半的名称,前面都带了个n,比如RDP和nRDP,这个意思就是你在写入RDP数据时,要同时在nRDP写入数据的反码,这样写入操作才是有效的,如果芯片检测到这两个存储器不是反码的关系,那就代表数据无效,有错误,对应的功能就不执行,这是一个安全保障措施,当然这个写入反码的过程,硬件会自动计算,并写入
1位保护4x8=32页,总共保护32*4=128页,正好对应中容量的最大128页
7.器件电子签名
也就是每个芯片的身份证号,这个数据存放的基地址是1FFFF7E8,每一个芯片的这96位数据都是不一样的,使用这个唯一ID号可以做一些加密的操作,比如你想写入一段程序只能在指定设备运行,那就可以在程序的多处加入ID号判断,如果不是指定设备的ID号,就不执行程序功能,,这样即使你的程序被盗,在别的设备上也难以运行,这就是STM32的电子签名