STM8的储存器
在《深入浅出STM8单片机入门、进阶与应用实例》一书中本章节排列在前,但因为此部分内容较为底层且与其他章节关系不大,我将其移至末尾介绍,作为进入汇编的预备
STM8中的储存器
STM8储存器资源概述
-
不同系列的STM8储存器资源配置
对于不同的项目要求,需要不同的储存器资源配置,比如要在单片机上运行RTOS,那么就需要单片机有比较大的RAM,如果需要在断电后储存较多参数,那就需要单片机有比较大的EEPROM
按储存器的结构和配置可以将STM8分为三种产品系列:高密度/中密度/低密度
-
不同储存器资源配置的特点
不同系列的储存器配置划分,其储存器资源有以下三个主要的区别
-
储存器的组成:STM8的储存器资源配置随型号不同而变化,比如低密度的产品就没有boot ROM这个结构
-
储存器的容量:不同系列储存器容量差别很大,最直观地体现在RAM容量和Flash容量
-
储存器的组织结构:主要表现为页大小、块大小、容量大小等方面,后文介绍
-
-
STM8储存器资源的结构概述
以高密度产品为例,介绍储存器的结构,如图所示
其中左侧编号即对应储存器区域的起始地址,可见STM8S中不同类型的各种资源都是连在一起的
这是因为STM8S在设计时为了读写指令和寻址的方便,将RAM、EEPROM、boot ROM、Flash ROM以及相关的寄存器全部统一编址在内存空间中
随机读写RAM
-
RAM简介
RAM单元具有随机存取、读写速度快、断电后数据不能保留的特点
因此RAM是一个运算处理和CPU数据交互的临时场合,处理结束CPU就会回收相应的内存
-
RAM地址
RAM的起始地址在内存空间的最低位(0x00 0000),而因为各型号RAM容量有差别,因此结束地址各不相同,具体可查阅数据手册
堆栈区
-
堆栈简介
堆栈是一个特殊的储存区,其作用是暂存数据和地址,通常用于保护断点和现场,例如说:一个函数调用了另一个函数,那么调用前的变量或数据就要放入堆栈中,被调用的函数执行完毕后又把那些数据取出来继续运算或传递
-
堆栈的FILO特点
堆栈遵循FILO(first in last out),即先进入堆栈的数据在最后才出栈
程序FlashROM
-
Flash简介
Flash ROM是储存器中用于存放要运行程序的一个部分(运行时加载到RAM中),其中还包含了32级中断向量和UBC(User Boot Code)这两个特殊的区域,它们占据Flash的一部分空间
-
Flash地址
Flash的起始地址是008000H,32级中断向量占据的空间是固定的,为008000H-00807FH,再之后从008080H开始为UBC区,而UBC区由选项字节配置是否开启及其大小;剩下的区域都是主程序区域
-
页page与块block
为了调节UBC和剩下的FlashROM主程序大小,STM8引入了页与块的概念,将整个FlashROM分成多页或块,每一页或每一块包含了一定的字节,不同系列的STM8其页或块的大小是不同的
由表可见:小容量的STM8S单片机系列有8K的Flash,分128页,每页和每块都是64B;而中容量和大容量每页为512B,每块是128B
-
UBC用户启动区域
其有无和大小都由用户配置选项字节OPT1决定
具体通过配置UBC[7:0]实现,当UBC[7:0]=0x01时UBC为1kB(对高密度产品来说是第0页和第1页),最多可配置至128kB(也是对高密度产品,具体要查阅对应型号数据手册),如果这个值为0则表示不定义UBC
UBC包含有复位和中断向量表,可以储存IAP和通信程序
因为其重要的功能,所以这个区域总是写保护的
-
32级中断向量
也即中断一章中介绍的中断向量表,地址自0x8000至0x807F,不再赘述
数据EEPROM
-
EEPROM简介
EEPROM全称电可擦除可编程只读存储器,其最大的特点是能断电后长时间保存数据,因此用于储存具体项目所需的数据,一般情况下这个区域是写保护的,避免数据被无意破坏
-
EEPROM地址
EEPROM的起始地址是004000H,结束地址根据容量不同各不相同
EEPROM中的页和块的大小和FlashROM中的一致,例如小容量STM8S有640B的EEPROM,每页也是64B,共10页
选项字节
-
选项字节简介
EEPROM中有一页(64B或512B)用于储存选项字节,选项字节用于配置硬件特性和储存器保护状态,具体见下表
-
选项字节配置
选项字节可以在SWIM/ICP模式或IAP模式中修改,在修改时要保证FLASH_CR2的OPT位为1以及FLASH_NCR2的NOPT位为0
具体软件操作方法见基础知识一章
端口及外设寄存器
-
寄存器简介
端口即单片机与外界交互的通道,外设是单片机实现各种功能的单元,端口和外设的控制是通过各种功能寄存器实现的,在之前的章节中,我们就在通过操作寄存器来配置单片机功能
-
寄存器地址
端口及外设的寄存器存放在内存空间的0x005000H-0x0057FFH之间,在我们所写程序开头调用的头文件
iostm8s105c6.h
(具体头文件根据型号不同而不同)中,已经定义了各个寄存器地址,比如:__IO_REG8_BIT(PA_ODR,0x5000,__READ_WRITE,__BITS_PA_ODR); //以此类推,各个寄存器都和对应的物理地址联系起来,就好比一个城市名和其经纬度 //当我们配置寄存器时,实际上是在直接写对应的内存
CPU/SWIM/Debug/ITC寄存器
-
寄存器介绍
与上面控制端口和外设的寄存器相比,这些寄存器处理的是单片机的核心事务:CPU控制和运算状态、单线调试接口SWIM配置、DM调试模块、ITC中断等等不及时处理就会影响系统工作的事务
-
寄存器地址
这些核心事务的寄存器放在0x007F00-0x007FFF中,同样在单片机头文件里有定义
引导启动BootROM
-
引导启动ROM简介
这是一个特殊区域:我们一般使用ST-LINK连接SWIM接口来烧录程序,但如果借助这个区域,就能在不使用专用硬件的情况下,通过UART、SPI、CAN这些接口把程序代码、数据、选项字节和中断向量表下载到内部的Flash和EEPROM储存器中
但是并非所有的STM8都具备这个区域,而该区域的大小在不同型号的STM8中也不尽相同
-
Boot loader
boot loader 简称BL,是单片机上电后,在进入Flash ROM之前,运行在Boot ROM中的一段小程序,这段程序的作用是初始化硬件设备,建立内存的空间映射表,建立适当的系统软硬件环境,为最终调用片内相关资源作准备
储存器功能寄存器
控制寄存器
-
Flash控制寄存器1 FLASH_CR1
配置各种电源模式下Flash是否掉电,启动中断使能,编程时间
-
Flash控制寄存器2 FLASH_CR2
控制寄存器2与其互补寄存器主要用于控制和确定Flash储存器的编程方式
-
Flash控制寄存器2互补寄存器 FLASH_NCR2
保护寄存器
-
Flash保护寄存器 FLASH_FPR
保护寄存器及其互补保护寄存器主要用于保护用户启动代码UBC区域内容
其位5:0为用户启动代码保护位WPB[5:0],用以指示用户启动代码UBC的大小,其值在启动时由UBC选项字节装载
-
Flash保护寄存器互补寄存器 FLASH_NFPR
同FLASH_FPR,其位5:0为用户启动代码保护位NWPB[5:0],同样用以指示用户启动代码大小,其值为NUBC选项字节的对应字节
-
Flash程序储存器解保护寄存器 FLASH_PUKR
其位7:0为主程序储存器解密密钥PUK[7:0]
在单片机上电复位后,Flash程序储存器默认是被保护的,在需要进行改写时,要向此储存器写入
0x56
,再写入0xAE
才能解除保护状态 -
DATA EEPROM解保护寄存器 FLASH_DUKR
同上,EEPROM储存器默认也是被保护的,需要改写时,则要向此储存器先写入
0xAE
,再写入0x56
状态寄存器
-
Flash状态寄存器 FLAHS_IAPSR
储存器的编程方法
更新存储器数据的方式概述
- 要更新储存器中的数据,有多种方法,首先分为IAP和ICP,然后又可分字节编程/字编程/块编程,从中还能引出标准/快速编程方式,有些型号支持读同时写功能RWW,接下来依次介绍
ICP/IAP
-
ICP基于电路编程
in circuit programing,该方法会从头到尾更新整个储存器的内容
使用SWIM接口(SWIM的含义即单线接口模块)把用户的程序装载到微控制器中,这也就是我们一直所使用的ST-LINK把程序从电脑烧录进单片机的原理
-
IAP基于程序编程
in applicating programing,该方法允许在应用程序中对Flash程序储存器的内容重新编程
该编程方法不需要SWIM接口,而是使用STM8支持的任意通信接口来下载需要烧录进储存器的数据
如果要使用IAP,必须通过ICP方式对Flash程序储存器预先编程,利用单片机中的程序自己对自己进行编程更新
标准/快速编程/RWW
-
标准编程
如果欲写入的单元并不是空白的,则应该先擦除单元的内容再写入,这样操作的时间数就是擦除时间+写入时间
-
快速编程
如果要写入的单元本身就是空白的,就没有必要再进行擦除而可以直接写入,这样所用时间就更短
-
RWW
部分STM8单片机支持RWW特性,即read while write,拥有此功能的单片机允许用户在执行程序和读程序储存器的同时对DATA EEPROM区域进行写操作,这样执行的时间进一步被缩短(但是不能反过来在写程序储存器时对DATA EEPTOM进行读操作)
查询是否有RWW功能:一般在对应型号的单片机数据手册的Flash program and data EEPROM memory一节,或者直接在数据手册搜索Read while write,搜到了就证明支持
字节编程
-
字节编程
应用程序直接向目标地址写入单字节数据,以单个字节为单位逐个地址编程
主程序储存器、数据EEPROM、选项字节都支持该方式,注意不同区域使用字节编程的步骤和流程是不同的
-
字节编程的结束检测方式
有两种方式来检测字节编程结束:查询法和中断法
- 查询法:不断地判断标志位FLASH_IAPSR_EOP位,其为1表示编程结束
- 中断法:启用中断功能,将FLASH_CR1_IE位置1
-
主程序储存器区域下的字节编程配置流程
在主程序储存器中执行字节编程操作时,CPU会停止应用程序的运行,所用采样查询方式比较合理,步骤如下:
- 禁止中断功能,即配置FLASH_CR1_IE=0,采用查询方式
- 解锁主程序区:向Flash程序储存器解保护寄存器FLASH_PUKR先写
0x56
再写0xAE
(此处输入的数值为解锁用的硬件密钥,后文介绍),操作之后判断FLASH_IAPSR_PUL位是否为1,当其为1时才说明解锁成功 - 向目标地址写入单字节数据,若需要擦除地址数据,写入
0x00
即可 - 查询FLASH_IAPSR_EOP位,判断编程是否结束
- 查询FLASH_IAPSR的WP_PG_DIS位是否为0,若不为0说明发生了错误
- 按照实际需要对数据进行校验,再软件清除FLASH_IAPSR的PUL位,重新对主程序区域写保护
-
数据EEPROM区域下的字节编程配置流程
在数据EEPROM区域中执行字节编程操作时,要先判断单片机是否支持RWW读同时写功能,如果具备,那么在IAP模式下应用程序不停止运行,字节编程利用RWW功能进行操作,不具备的话就要停止运行,依次是否具备RWW决定了选择中断还是查询法
-
按需配置使用中断或查询法
-
解锁EEPROM区,向DATA EEPROM解保护寄存器FLASH_DUKR先写入
0xAE
再写入0x56
(不仅寄存器不同,写入顺序与解锁FLASH相反),操作完成后检测FLASH_IAPSR的DUL位,为1时解锁成功 -
向目标地址写入单字节数据,若需要擦除地址数据,写入
0x00
即可 -
查询FLASH_IAPSR_EOP位,判断编程是否结束
-
查询FLASH_IAPSR的WP_PG_DIS位是否为0,若不为0说明发生了错误
-
按照实际需要对数据进行校验,再软件清除FLASH_IAPSR的DUL位,重新对主程序区域写保护
-
-
选项字节的字节编程配置流程
与上文流程相似,细节有所不同:需要配置FLASH_CR2的OPT位为1以及FLASH_NCR2的OPT位为0,以运行对选项字节进行写操作
-
字节编程的用时
根据目标地址初始化内容的不同(可能空也可能非空),编程时间不同(标准/快速编程),如果需要获得固定的编程时间,可以强制使用标准编程,无论空不空统统先擦除再写入,配置方法是FLASH_CR1的FIX位置1
字编程
-
字的概念
字编程和字节编程几乎一样,只是写入的单位容量变大了
一个字word等于4个字节Byte(一个字有几个字节具体取决于系统总线宽度,32位系统中1个字为4字节,64位系统则1个字有8个字节),这样可以缩短编程时间
-
字编程配置流程
与字节编程的流程类似类似,细节有所不同:
- 需要配置FLASH_CR2的WPRG位为1使能字编程操作
- 同时配置FLASH_NCR2的NWPRG位为0
- 然后将欲编程的字内容从目标地址的首地址开始装载,再写入4个字节的内容即可
块编程
-
块的概念
在块编程中写入的单位容量进一步变大,(具体每个块有多少字节取决于单片机的密度类型),整个块的编程在一个编程周期内就可完成,写入效率最高;
主程序储存器区域和数据EEPROM区域可以执行块操作;
注意:在主程序储存器编程时要求用于块编程的程序代码必须全部在RAM中执行;
在数据EEPROM区域中使用块编程方式时还要考虑器件是否RWW,如果有则数据EEPROM块操作可以在主程序储存器中执行,然而数据装载阶段依旧必须在RAM中执行,对于没有RWW功能的器件则代码必须全部在RAM中执行
-
标准块编程
同标准编程和快速编程的区别,标准块编程中整个块在编程之前会被自动擦除
- 需要首先配置FLASH_CR2_PRG位为1,使能标准块编程
- 同时配置FLASH_NCR2_NFPRG位为0
- 然后向主程序储存器或数据EEPROM区域的目标地址依次写入要编程的数据
- 最后利用查询法或者中断法检测编程是否结束
-
快速块编程
快速块编程不擦除储存器内容,直接对块编程,因此编程速度是标准块编程的两倍,步骤与标准块编程一致
但是需要注意在执行快速块编程之前如果目标地址中的内容非空,则不能保证写入的数据无误
-
块擦除
允许擦除整个块
-
FLASH_CR2的ERASE位为1,使能块擦除
-
同时配置FLASH_NCR2的NERASE位为0
-
然后同时对块所有的字(4个字节)写入0来擦除整个块
字的起始地址必须以0、4、8、C作为结尾
-
最后再利用查询法或中断法来检测编程是否结束
-
读/写保护与控制
ROP储存器读出保护
-
ROP简介
STM8系列单片机具备储存器的读出保护功能Read-out protection,即ROP
该功能可以阻止在ICP模式和调试模式下用户对Flash程序储存器和数据EEPROM储存器数据的读出操作,而且一旦使能ROP后,任何尝试改变其状态的操作都会将Flash、UBC、EEPROM以及选项字节中的内容全部擦除,从而最大限度实现了数据保密性
-
使能ROP
选项字节的ROP字节被编程为
0xAA
,就会开启读出保护
MASS储存器存取安全保障系统
-
MASS简介
ROP是读出保护,而对应有写入的保护以防止软件故障对储存器意外擦写,即write protection,WP功能,这个功能由MASS,即memory access security system执行
-
MASS保护机制
在进行IAP编程前,要解除MASS的写保护状态,需要通过使用硬件密钥,也就是前文字节编程流程中提到的,向Flash程序储存器解保护寄存器FLASH_PUKR先写
0x56
,再写0xAE
,这就是两个硬件密钥(解锁EEPROM区则要配置FLASH_DUKR寄存器,且写入硬件密钥的顺序相反)解锁EEPROM区,向DATA EEPROM解保护寄存器FLASH_DUKR先写入
0xAE
再写入0x56
,操作完成后检测FLASH_IAPSR的DUL位,为1时解锁成功操作之后判断FLASH_IAPSR_PUL位是否为1,当其为1时才说明解锁成功;DUL位,为1时解锁成功
-
对主程序储存器的写操作
使用硬件密钥解锁的流程
- 向程序储存器解保护寄存器FLASH_PUKR写入第一个八位密钥
0x56
,当该密钥首次写入时,数据总线上的值没有直接锁存到寄存器中,而是和第一个硬件密钥0x56
比较 - 如果第一个密钥输入错误,FLASH_PUKR寄存器会被一直锁住,除非触发一次复位操作,因此写入只有一次机会
- 如果第一个密钥输入正确,会重复同样的流程来判断第二个密钥
0xAE
,如果第二个密钥输入也会锁定FLASH_PUKR寄存器 - 当两个密钥配置正确,主程序写保护会被成功解除,同时状态寄存器FLASH_IAPSR的PUL位会被置1,表示主程序储存器已被解锁
- 解锁完毕就可以写入内容,编程结束后要重新为其配置写保护,将FLASH_IAPSR的PUL位重新置零
- 向程序储存器解保护寄存器FLASH_PUKR写入第一个八位密钥
-
对数据EEPROM区域的写保护
解锁步骤与上文主程序储存器类似,有三点不同
- 要写入的是DATA EEPROM解保护寄存器FLASH_DUKR
- 写入顺序相反,第一个密钥是
0xAE
,第二个密钥才是0x56
- 状态寄存器FLASH_IAPSR中判断保护状态的标志位为DUL
使用EEPROM
实验设计
-
实验概述
做一个关于EEPROM区域的读写实验,利用EEPROM断电数据保留的特性来储存信息,这个信息借助数码管表达
-
实验原理
设定一个数值自动+1(到9时变回0),而这个数值在显示到数码管的同时保存到EEPROM中,然后通过断电操作验证系统是否保存了断电前的计数状态值
单片机上电后读取EEPROM单元目标地址中的数据,判断读出的数据是否为0x00,如果是就表示这是第一次断电(或上一次刚好循环到0),读出的不为0x00就表示这个数值是上一次保存下来的数值,将该值显示并送给自增变量让其加1
代码实现
-
指定目标地址
实验的重点是:怎样指定数据EEPROM单元的目标地址并写入数据
指定地址的代码:
u8 disnum_EEPROM @0x4000;
- u8是数据类型
unsigned char
,详见GPIO端口“配置使用的数据类型”一节 - 0x4000是EEPROM的起始地址,这一行代码的作用是让
disnum_EEPROM
指向地址0x4000
,实际上和前文寄存器的定义原理相同,相当于给某个经纬度上的区域命名为一个城市
- u8是数据类型
-
解锁函数
在写入数据之前,要编写解锁EEPROM的函数用于向MASS输入两个硬件密钥并检查解锁状态,还要处理解锁失败的情况
u8 unlock_EEPROM(void) { FLASH_DUKR = 0xAE; FLASH_DUKR = 0x56; //之后还要判断DUL位看解锁是否成功 if(FLASH_IAPSR & 0x08) return 0; else return 1;//解锁失败 }
-
主程序
//延时函数和数码管显示在本合集流水灯和数码管章节中已介绍,不再赘述 u8 disnum_EEPROM @0x4000;//指定EEPROM地址 u8 unlock_EEPROM(void);//解锁函数原型 unsigned char num[] = {0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0xa0,0x83,0xc6,0xa1,0x86,0x8e}; //段选码,由于只显示一位数字,因此不需要使用动态刷新数码管函数 #define LED PB_OCR//取决与数码管的段选型号连接到哪组GPIO上 void main(void) { u8 i =0;//自增控制变量 Digit_GPIO_Init();//配置连接数码管的引脚 while(unlock_EEPROM());//等待解锁成功 if(disnum_EEPROM!=0)//如果EEPROM首地址装载数值不为0 { LED=num[disnum_EEPROM];//显示EEPROM中储存的数值 i=disnum_EEPROM; delay(200); } else LED=num[i]; while(1) { i=(i+1)%1;//i自增,限定i取值为0~9 LED=num[i];//把0~9放到数码管上显示 disnum_EEPROM=i;//把0~9数值写入EEPROM首地址 while((FLASH_IAPSR&0x40)==0);//写入成功 delay(200);//延时停留 } }
-
实验现象
若出现这样的现象,说明实验成功:
首次上电运行且不产生任何干预的情况下,数码管显示数字从0到9循环
如果在其间突然断电,再次上电时不会重新开始从0计数,而是显示断电前的数字
本文来自博客园,作者:无术师,转载请注明原文链接:https://www.cnblogs.com/untit1ed/p/18691781
本文使用知识共享4.0协议许可 CC BY-NC-SA 4.0
请注意: 特别说明版权归属的文章以及不归属于本人的转载内容(如引用的文章与图片)除外
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?