一、学习参考资料
(1)正点原子的寄存器源码。
(2)STM32F103最小系统板开发指南-寄存器版本_V1.1(正点)
(3)STM32F103最小系统板开发指南-库函数版本_V1.1(正点)
(4)Cortex-M3权威指南(中文)
(5)STM32中文参考手册_V10
(6)stm32cubemx可视化时钟树配置
(7)其他博主文章
本文主要以stm32f1系列单片机为研究对象,从寄存器层面对时钟树的配置、中断优先级的配置进行阐述。
二、stm32官方bsp库寄存器封装的基本方式
一般按照连续的寄存器地址使用结构体指针的形式封装,将寄存器按照连续的顺序定义变量在结构体中,然后直接将利用结构体指针指向结构体第一个定义的首地址的位置。
cpp
typedef struct
{
__I uint32_t CPUID; /*!< Offset: 0x00 CPU ID Base Register */
__IO uint32_t ICSR; /*!< Offset: 0x04 Interrupt Control State Register */
__IO uint32_t VTOR; /*!< Offset: 0x08 Vector Table Offset Register */
__IO uint32_t AIRCR; /*!< Offset: 0x0C Application Interrupt / Reset Control Register */
__IO uint32_t SCR; /*!< Offset: 0x10 System Control Register */
__IO uint32_t CCR; /*!< Offset: 0x14 Configuration Control Register */
__IO uint8_t SHP[12]; /*!< Offset: 0x18 System Handlers Priority Registers (4-7, 8-11, 12-15) */
__IO uint32_t SHCSR; /*!< Offset: 0x24 System Handler Control and State Register */
__IO uint32_t CFSR; /*!< Offset: 0x28 Configurable Fault Status Register */
__IO uint32_t HFSR; /*!< Offset: 0x2C Hard Fault Status Register */
__IO uint32_t DFSR; /*!< Offset: 0x30 Debug Fault Status Register */
__IO uint32_t MMFAR; /*!< Offset: 0x34 Mem Manage Address Register */
__IO uint32_t BFAR; /*!< Offset: 0x38 Bus Fault Address Register */
__IO uint32_t AFSR; /*!< Offset: 0x3C Auxiliary Fault Status Register */
__I uint32_t PFR[2]; /*!< Offset: 0x40 Processor Feature Register */ // 如果占用两个字节,就直接定义为数组的形式
__I uint32_t DFR; /*!< Offset: 0x48 Debug Feature Register */
__I uint32_t ADR; /*!< Offset: 0x4C Auxiliary Feature Register */
__I uint32_t MMFR[4]; /*!< Offset: 0x50 Memory Model Feature Register */
__I uint32_t ISAR[5]; /*!< Offset: 0x60 ISA Feature Register */
} SCB_Type;
cpp
#define SCB ((SCB_Type *) SCB_BASE) /*!< SCB configuration struct */
上面定义之后,对寄存器的操作就变成了"SCB->VTOR &= data(寄存器对应位置零),SCB->VTOR |= data(寄存器对应位置一)"。上面有些寄存器并不是32位的,占用位数比较多的可以采用定义数据的方式。
除了上面大范围的定义寄存器的形式,对于操作某些单个寄存器的时候,可以采用寄存器地址强制转化的方式。转化为指针" unsigned volatile int * "。
cpp
#define RCC_BASE_MY (uint32_t)0x40021000 //基地址
#define RCC_CR_MY (uint32_t)0x00000000 //偏移地址
#define RCC_CFGR_MY (uint32_t)0x00000004
#define RCC_CIR_MY (uint32_t)0x00000008
#define RCC_APB2RSTR_MY (uint32_t)0x0000000c
#define MY_RCC_CR *((unsigned volatile int*)(RCC_BASE_MY+RCC_CR_MY))
#define MY_RCC_CFGR *((unsigned volatile int*)(RCC_BASE_MY+RCC_CFGR_MY))
#define MY_RCC_CIR *((unsigned volatile int*)(RCC_BASE_MY+RCC_CIR_MY))
#define MY_RCC_APB2RSTR *((unsigned volatile int*)(RCC_BASE_MY+RCC_APB2RSTR_MY))
#define MY_FLASH_ACR *((unsigned volatile int*)0x40022000) //要查m4内核手册
此时对寄存器置位和复位的操作为:" MY_RCC_CR |= (uint32_t)(1<<24) "和" MY_RCC_CFGR &= ~(uint32_t)(3<<11) "。使用上面的方式就可以单独对某个寄存器进行操作。
stm32底层代码的知识点补充:
底层配置代码经常使用"assert_param()"函数判断用户的选择是否正确,如果不正确就会调用相应的错误处理函数进行处理。
cpp
#define assert_param(expr) ((expr) ? (void)0 : assert_failed((uint8_t *)__FILE__, __LINE__))
//利用上面定义的assert_param(expr),判断expr是否正确(也就是用户的选择参数是否正确,来确定是否调用assert_failed函数,assert_failed函数需要从写。)
/* Exported functions ------------------------------------------------------- */
void assert_failed(uint8_t* file, uint32_t line);
#else
#define assert_param(expr) ((void)0)
#endif /* USE_FULL_ASSERT */
#endif /* __STM32F10x_CONF_H */
cpp
#define __I volatile const /*!< defines 'read only' permissions */
#define __O volatile /*!< defines 'write only' permissions */
#define __IO volatile /*!< defines 'read / write' permissions */
__I :输入口**(read only)** 。既然是输入,那么寄存器的值就随时会外部修改,那就不能进行优化, 每次都要重新从寄存器中读取。也不能写,即只读,不然就不是输入而是输出了。
__O :输出口**(write only)** ,也不能进行优化,不然你连续两次输出相同值,编译器认为没改变, 就忽略了后面那一次输出,假如外部在两次输出中间修改了值,那就影响输出
__IO :输入输出口**(read/write)**,同上
为什么加下划线?
原因是:避免命名冲突,一般宏定义都是大写,但因为这里的字母比较少,所以再添加下划线来区分。这样一般都可以避免命名冲突问题,因为很少人这样命名,这样命名的人肯定知道这些是有什么用的。经常写大工程时,都会发现老是命名冲突,要不是全局变量冲突,要不就是宏定义冲突,所以我们要尽量避免这些问题,不然出问题了都不知道问题在哪里。
三、stm32f103zet6的时钟树讲解
对单片机的时钟树进行配置的时候,可以结合stm32cubemx的图形化配置进行理解。
如上图所示,总线时钟需要配置的位置为"1、2、3、4、5、6"。对时钟配置的寄存器主要有下图的几个,下面将针对需要配置位置的寄存器进行讲解。
对应外设时钟的"复位"(RCC_APB1RSTR、RCC_APB2RSTR)和"使能和失能"(RCC_APB1ENR、RCC_APB2ENR)分别对应两个寄存器。
1、PLLXTPRE:HSE分频器作为PLL输入 (HSE divider for PLL entry) (属于RCC_CFGR)
2、PLLMUL:PLL倍频系数 (PLL multiplication factor)(属于RCC_CFGR)
3、SW[1:0]:系统时钟切换 (System clock switch) (属于RCC_CFGR)
4、HPRE[3:0]: AHB预分频 (AHB Prescaler)(属于RCC_CFGR)
5、PPRE1[2:0]:低速APB预分频(APB1) (APB low-speed prescaler (APB1))(属于RCC_CFGR)
6、PPRE2[2:0]:高速APB预分频(APB2) (APB high-speed prescaler (APB2))属于RCC_CFGR)
配置的时钟和总线的开启寄存器是RCC_CR寄存器 。
注意:时钟配置的时候要从后向前配置,在相应时钟和总线开启之后,从"6"的位置向前进行配置。
四、系统时钟寄存器
cpp
typedef struct
{
__IO uint32_t CR;
__IO uint32_t CFGR;
__IO uint32_t CIR;
__IO uint32_t APB2RSTR;
__IO uint32_t APB1RSTR;
__IO uint32_t AHBENR;
__IO uint32_t APB2ENR;
__IO uint32_t APB1ENR;
__IO uint32_t BDCR;
__IO uint32_t CSR;
#ifdef STM32F10X_CL
__IO uint32_t AHBRSTR;
__IO uint32_t CFGR2;
#endif /* STM32F10X_CL */
#if defined (STM32F10X_LD_VL) || defined (STM32F10X_MD_VL) || defined (STM32F10X_HD_VL)
uint32_t RESERVED0;
__IO uint32_t CFGR2;
#endif /* STM32F10X_LD_VL || STM32F10X_MD_VL || STM32F10X_HD_VL */
} RCC_TypeDef;
cpp
#define RCC ((RCC_TypeDef *) RCC_BASE)
对系统时钟树的配置使用上述的底层地址定义即可。
下面是正点时钟树的配置代码,其中中断优先级的配置后面会讲:
cpp
//不能在这里执行所有外设复位!否则至少引起串口不工作.
//把所有时钟寄存器复位
void MYRCC_DeInit(void)
{
RCC->APB1RSTR = 0x00000000;//复位结束
RCC->APB2RSTR = 0x00000000;
RCC->AHBENR = 0x00000014; //睡眠模式闪存和SRAM时钟使能.其他关闭.
RCC->APB2ENR = 0x00000000; //外设时钟关闭.
RCC->APB1ENR = 0x00000000;
RCC->CR |= 0x00000001; //使能内部高速时钟HSION
RCC->CFGR &= 0xF8FF0000; //复位SW[1:0],HPRE[3:0],PPRE1[2:0],PPRE2[2:0],ADCPRE[1:0],MCO[2:0]
RCC->CR &= 0xFEF6FFFF; //复位HSEON,CSSON,PLLON
RCC->CR &= 0xFFFBFFFF; //复位HSEBYP
RCC->CFGR &= 0xFF80FFFF; //复位PLLSRC, PLLXTPRE, PLLMUL[3:0] and USBPRE
RCC->CIR = 0x00000000; //关闭所有中断
//配置向量表
#ifdef VECT_TAB_RAM
/* 向量表的配置和中断优先级的配置后面都会讲 */
MY_NVIC_SetVectorTable(0x20000000, 0x0);
#else
MY_NVIC_SetVectorTable(0x08000000,0x0);
#endif
}
cpp
//系统时钟初始化函数
//pll:选择的倍频数,从2开始,最大值为16
void Stm32_Clock_Init(u8 PLL)
{
unsigned char temp=0;
MYRCC_DeInit(); //复位并配置向量表
RCC->CR|=0x00010000; //外部高速时钟使能HSEON
while(!(RCC->CR>>17)); //等待外部时钟就绪
RCC->CFGR=0X00000400; //APB1=DIV2;APB2=DIV1;AHB=DIV1;
PLL-=2; //抵消2个单位(因为是从2开始的,设置0就是2)
RCC->CFGR|=PLL<<18; //设置PLL值 2~16
RCC->CFGR|=1<<16; //PLLSRC ON
FLASH->ACR|=0x32; //FLASH 2个延时周期
RCC->CR|=0x01000000; //PLLON
while(!(RCC->CR>>25)); //等待PLL锁定
RCC->CFGR|=0x00000002; //PLL作为系统时钟
while(temp!=0x02) //等待PLL作为系统时钟设置成功
{
temp=RCC->CFGR>>2;
temp&=0x03;
}
}
五、中断向量表地址的配置
这里需要有stm32内存的框架知识,后面有对这部分知识的详细的讲解,不懂得可以先看后面,这里为了衔接前面的代码,先介绍中断向量表地址的配置。
stm32的内存中,对于程序员最重要的是"SRAM(0x2000 0000~0x2000 FFFF)"和"Flash(0x0800 0000~0x0808 0000)"两个位置的内存。
向量表的首地址可以设置在SRAM区或者Flash区域。下面是stm32F4的向量表设置的形式,STM32F4 在复位后,先从 0X08000004 地址取出复位中断向量的地址,并
跳转到复位中断服务程序,如图标号①所示;在复位中断服务程序执行完之后,会跳转到我们
的 main 函数,如图标号②所示;而我们的 main 函数一般都是一个死循环,在 main 函数执行过
程中,如果收到中断请求(发生重中断),此时 STM32F4 强制将 PC 指针指回中断向量表处,
如图标号③所示;然后,根据中断源进入相应的中断服务程序,如图标号④所示;在执行完中
断服务程序以后,程序再次返回 main 函数执行,如图标号⑤所示。
中断项链表的配置的寄存器在"Cortex-M3权威指南(中文)"文档中可以找到(113页)。
下面是底层硬件地址结构体封装和正点项链表地址设置代码。
cpp
/** @addtogroup CMSIS_CM3_SCB CMSIS CM3 SCB
memory mapped structure for System Control Block (SCB)
@{
*/
typedef struct
{
__I uint32_t CPUID; /*!< Offset: 0x00 CPU ID Base Register */
__IO uint32_t ICSR; /*!< Offset: 0x04 Interrupt Control State Register */
__IO uint32_t VTOR; /*!< Offset: 0x08 Vector Table Offset Register */
__IO uint32_t AIRCR; /*!< Offset: 0x0C Application Interrupt / Reset Control Register */
__IO uint32_t SCR; /*!< Offset: 0x10 System Control Register */
__IO uint32_t CCR; /*!< Offset: 0x14 Configuration Control Register */
__IO uint8_t SHP[12]; /*!< Offset: 0x18 System Handlers Priority Registers (4-7, 8-11, 12-15) */
__IO uint32_t SHCSR; /*!< Offset: 0x24 System Handler Control and State Register */
__IO uint32_t CFSR; /*!< Offset: 0x28 Configurable Fault Status Register */
__IO uint32_t HFSR; /*!< Offset: 0x2C Hard Fault Status Register */
__IO uint32_t DFSR; /*!< Offset: 0x30 Debug Fault Status Register */
__IO uint32_t MMFAR; /*!< Offset: 0x34 Mem Manage Address Register */
__IO uint32_t BFAR; /*!< Offset: 0x38 Bus Fault Address Register */
__IO uint32_t AFSR; /*!< Offset: 0x3C Auxiliary Fault Status Register */
__I uint32_t PFR[2]; /*!< Offset: 0x40 Processor Feature Register */
__I uint32_t DFR; /*!< Offset: 0x48 Debug Feature Register */
__I uint32_t ADR; /*!< Offset: 0x4C Auxiliary Feature Register */
__I uint32_t MMFR[4]; /*!< Offset: 0x50 Memory Model Feature Register */
__I uint32_t ISAR[5]; /*!< Offset: 0x60 ISA Feature Register */
} SCB_Type;
cpp
#define SCB ((SCB_Type *) SCB_BASE) /*!< SCB configuration struct */
cpp
//设置向量表偏移地址
//NVIC_VectTab:基址
//Offset:偏移量
void MY_NVIC_SetVectorTable(u32 NVIC_VectTab, u32 Offset)
{
SCB->VTOR = NVIC_VectTab|(Offset & (u32)0x1FFFFF80);//设置NVIC的向量表偏移寄存器
//用于标识向量表是在CODE区还是在RAM区
}
六、中断的优先级分组、优先级设置、外部中断的配置
6.1、stm32中断相关基础知识
中断使用的时候不允许中断嵌套。
对于stm32f10xxx系列单片机而言,有68个可屏蔽中断通道(不包含16个Cortex™-M3的中断线,16个可编程的优先等级(使用了4位中断优先级);
STM32 将中断分为 5 个组,组 0~4 。该分组的设置是由 SCB->AIRCR 寄存器的 bit10~8 来定义的。具体的分配关系如表 4.5.1 所示:
中断的使能的相关寄存器信息在"Cortex-M3权威指南(中文)"资料里面看,里面有中断使能相关的寄存器,中断只能的寄存器挂载在"SysTick_BASE"系统时钟总线上。
SETENAs: xE000_E100 -- 0xE000_E11C ; CLRENAs:0xE000E180 - 0xE000_E19C
SETPENDs:0xE000_E200 -- 0xE000_E21C ; CLRPENDs:0xE000E280 - 0xE000_E29C
中断和事件的区别:
事件机制提供了一个完全有硬件自动完成的触发到产生结果的通道,不要软件的参与,降低了CPU的负荷,节省了中断资源,提高了响应速度(硬件总快于软件),是利用硬件来提升CPU芯片处理事件能力的一个有效方法。中断信号需要经过cpu的处理,然后调用中断处理函数进行处理。
这里先只讲解外部GPIO的中断,外设的中断后面再讲。
STM32F103 的 19 个外部中断为:
线 0~15 :对应外部 IO 口的输入中断。
线 16 :连接到 PVD 输出。
线 17 :连接到 RTC 闹钟事件。
线 18 :连接到 USB 唤醒事件。
stm32的外部中断是按pin号进行分级管理的,共有16个管理器。
stm32的系统时钟和中断相关的操作的寄存器需要参考"Cortex-M3权威指南(中文)"和"STM32中文参考手册_V10"关于中断向量这些的定义是Cortex-M3相关,stm32官方直接使用的,基本外设这些寄存器是stm32官方根据需求自己定义的,所以对时钟和中断的配置需要参考两个的文档。
6.2、中断项链表偏移地址的配置以及分组设置(不是必须的,自己代码可以步配置,使用默认的)
使用到的寄存器如下所示,来自"Cortex-M3权威指南(中文)"。
下面是正点地中断向量表地址配置代码:
cpp
//设置向量表偏移地址
//NVIC_VectTab:基址
//Offset:偏移量
void MY_NVIC_SetVectorTable(u32 NVIC_VectTab, u32 Offset)
{
SCB->VTOR = NVIC_VectTab|(Offset & (u32)0x1FFFFF80);//设置NVIC的向量表偏移寄存器
//用于标识向量表是在CODE区还是在RAM区
}
根据6.1中地前两个图可以知道如何设置寄存器地分组,下面是正点的中断优先级分组的代码:
cpp
//设置NVIC分组
//NVIC_Group:NVIC分组 0~4 总共5组
void MY_NVIC_PriorityGroupConfig(u8 NVIC_Group)
{
u32 temp,temp1;
temp1=(~NVIC_Group)&0x07;//取后三位,分组数与位的设置正好是取反的关系
temp1<<=8; //移位到寄存器AIRCR中断优先级分组配置位置
temp=SCB->AIRCR; //读取先前的设置
temp&=0X0000F8FF; //清空先前分组
temp|=0X05FA0000; //写入钥匙,根据手册,改变寄存器必须写入钥匙
temp|=temp1;
SCB->AIRCR=temp; //设置分组
}
七、stm32内存架构
stm32的内存架构是非常重要的,有助于理解stm32的寄存器的配置,程序员经常关心和使用的内存区域主要有SRAM和Flash两个区域。
7.1、内存的基础知识点
RAM:随机存取存储器(英语:Random Access Memory,缩写:RAM),也叫主存,是与CPU直接交换数据的内部存储器。
ROM:(只读内存(Read-Only Memory)简称)英文简称ROM。ROM所存数据,一般是装入整机前事先写好的,整机工作过程中只能读出,而不像随机存储器那样能快速地、方便地加以改写。
内存有ROM(掉电不丢失,例如Flash)和RAM(掉电丢失,例如SRAM),下面是关于两种内存的介绍:
7.1.1、Flash
通过上图我们可以知道,FLASH属于 非易失性存储器:
FLASH又称为闪存,不仅具备电子可擦除可编程(EEPROM)的性能,还不会断电丢失数据同时可以快速读取数据,U盘和MP3里用的就是这种存储器。在以前的嵌入式芯片中,存储设备一直使用ROM(EPROM),随着技术的进步,现在嵌入式中基本都是FLASH,用作存储Bootloader以及操作系统或者程序代码或者直接当硬盘使用(U盘)。
Flash 主要有两种NOR Flash和NADN Flash:
NOR Flash的读取和我们常见的SDRAM的读取是一样,用户可以直接运行装载在NOR FLASH里面的代码,这样可以减少SRAM的容量从而节约了成本,可以随机读写。
NAND Flash没有采取内存的随机读取技术,它的读取是以一次读取一块的形式来进行的,通常是一次读取512个字节,采用这种技术的Flash比较廉价。用户不能直接运行NAND Flash上的代码,因此好多使用NAND Flash的开发板除了使用NAND Flah以外,还作上了一块小的NOR Flash来运行启动代码。
STM32单片机内部的FLASH为 NOR FLASH。
Flash 相对容量大,掉电数据不丢失,主要用来存储 代码,以及一些掉电不丢失的用户数据。
7.1.2、RAM
RAM 属于易失性存储器:
RAM随机存储器(Random Access Memory)表示既可以从中读取数据,也可以写入数据。当机器电源关闭时,存于其中的数据就会丢失。比如电脑的内存条。
RAM有两大类,一种称为静态RAM(Static RAM/SRAM),SRAM速度非常快,是目前读写最快的存储设备了,但是它也非常昂贵,所以只在要求很苛刻的地方使用,譬如CPU的一级缓冲,二级缓冲。另一种称为动态RAM(Dynamic RAM/DRAM),DRAM保留数据的时间很短,速度也比SRAM慢,不过它还是比任何的ROM都要快,但从价格上来说DRAM相比SRAM要便宜很多,计算机内存就是DRAM的。
为什么需要RAM,因为相对FlASH而言,RAM的速度快很多,所有数据在FLASH里面读取太慢了,为了加快速度,就把一些需要和CPU交换的数据读到RAM里来执行(注意这里不是全部数据,只是一部分需要的数据,这个在后面介绍STM32的内存管理中会提到)。
STM32单片机内部的 RAM 为 SRAM。
RAM相对容量小,速度快,掉电数据丢失,其作用是用来存取各种动态的输入输出数据、中间计算结果以及与外部存储器交换的数据和暂存数据。
总结:stm32的RAM内存为SRAM,ROM为Flash。
stm32的内存管理分为两部分,一部分是关于内核的,stm32参照Cortex-M内核进行内存映射地址的设置。另一部分是stm32根据不同单片机进行的内存地址的映射,两种地址设置的方式都是非常重要的,下面将对其进行张开叙述。
7.2、Cortex-M3的存储器映射
Cortex-M3的存储器映射是stm32单片机存储区映射的基本框架和标准。stm32单片机可以在此标准下进行关于自己的存储器映射的扩展。
存储器映射 是用 地址来表示 对象,因为Cortex-M3是32位的单片机,因此其PC指针可以指向2^32=4G的地址空间,也就是图中的 0x00000000到0xFFFFFFFF的区间,也就是将程序存储器、数据存储器、寄存器和输入输出端口被组织在同一个4GB的线性地址空间内,数据字节以小端格式存放在存储器中。
注意:上面介绍的内存的地址分布架构,只要单片机采用上面的架构,就需要遵守上面的地址规定,区域的首地址是不能改变的,不同的单片机使用的内存的大小是不一样的,首地址不能改变,后面不适用的映射地址可以保留。
前三个区域对于程序员来说是比较重要的。
7.3、stm32的存储器映射
STM32存储器映射表(选用的是STM32F103VE的,不同的型号Flash 和 SRAM 的地址空间不同,起始地址都是一样的),stm32地栈和堆的位置所需要的内存都在" 64K大小的SRAM"的位置:
那么我们所需要分析的STM32 内存,就是图中 0X0800 0000开始的 Flash 部分 和 0x2000 0000 开始的SRAM部分,这里还要介绍一个和Flash模块相关的部分,从0X0800 0000开始的 Flash 部分,可以对其进行划分为:主存储块、信息块、闪存存储器接口寄存器。
STM32的Flash,严格说,应该是Flash模块。该Flash模块包括:Flash主存储区(Main memory)、Flash信息区(Informationblock),以及Flash存储接口寄存器区(Flash memory interface)。
主存储器,该部分用来存放代码和数据常数(如加const类型的数据)。对于大容量产品,其被划分为256页,每页2K,小容量和中容量产品则每页只有1K字节。主存储起的起始地址为0X08000000,B0、B1都接GND的时候,就从0X08000000开始运行代码。
信息块,该部分分为2个部分,其中启动程序代码,是用来存储ST自带的启动程序,用于下载,当B0接3.3V,B1接GND时,运行的就这部分代码,用户选择字节,则一般用于配置保护等功能。
闪存储器块,该部分用于控制闪存储器读取等,是整个闪存储器的控制机构。
对于主存储器和信息块的写入有内嵌的闪存编程管理;编程与擦除的高压由内部产生。
在执行闪存写操作时,任何对闪存的读操作都会锁定总线,在写完成后才能正确进行,在进行读取或擦除操作时,不能进行代码或者数据的读取操作。
7.4、stm32的代码中内存管理
STM32 的内存管理起始就是对0X0800 0000 开始的 Flash 部分 和 0x2000 0000 开始的 SRAM 部分使用管理。根据程序中的数据存储可以划分为6个存储数据段和3种存储属性区。
上面几个6个存储数据段和3种存储属性区,将从代码的层次进行分析。
(1)Code:
程序代码部分。
.text 段
放在ROM里面,就是Flash,需要占用flash空间
(2)RO-data
(Read Only)只读数据
程序定义的常量,只读数据,字符串常量(const修饰的)
.constdata 段
放在flash里面,需要占用flash空间
(3)RW-data
(Read Write)可读可写数据
已经初始化的全局变量和静态变量(就是static修饰的变量);
.data 段
需要在 RAM里面运行,但是起初需要保存在 Flash里面,程序运行后复制到 RAM里面运行,需要占用Flash空间。
(4)ZI-data
(Zero Initialize)未初始化的全局变量和静态变量,以及初始化为0的变量;
.BSS段
ZI的数据全部是0,没必要开始就包含,只要程序运行之前将ZI数据所在的区域(RAM里面)一律清 0,不占用Flash,运行时候占用RAM。
heap 和 stack 其实也属于 ZI,只不过他不是程序编译就能确定大小的,必须在运行中才会有大小,而是是变化的,因为RAM掉电丢失,所以 RW-data 数据也得下载到ROM(flash) 中,在运行的时候复制到 RAM中运行,如下图所示(图中的地址也是错的,应该是从0x0800 0000 开始):
由上我们得知:
程序 占用 Flash = Code + RO data + RW data
程序运行 时候占用 RAM = RW data + ZI data。
Code + RO data + RW data 的大小也是生成的bin文件的大小
在 startup_stm32fxxx.s 中我们可以看到关于 Stack_Size 和 Heap_Size 的定义,图中的定义就是规定本程序中 栈 的大小为 1K , 堆的大小为 0.5K。
经过keil编译过后的文件会生成"test.map"的文件,这个文件中有编译之后的栈和堆的位置,但是这些都是不固定的,所以用户自己使用这段内存的时候,需要看堆的地址,也可以直接使用malloc进行堆上数据的申请,但是这种方式容易形成碎片内存的现象(例如刚开始申请了2,之后有申请了3,释放前面申请的2之后,在申请3的时候,前面的连续内存2就不能使用了,这样连续进行小容量的malloc申请,就会形成很对如前面没用的2的碎片内存。最后造成系统内存减少。)
7.5、stm32启动方式
第一种启动方式是最常用的用户FLASH启动,正常工作就在这种模式下,STM32的FLASH可以擦出10万次,所以不用担心芯片哪天会被擦爆!
第二种启动方式是系统存储器启动方式,即我们常说的串口下载方式(ISP),不建议使用这种,速度比较慢。STM32 中自带的BootLoader就是在这种启动方式中,如果出现程序硬件错误的话可以切换BOOT0/1到该模式下重新烧写Flash即可恢复正常。
第三种启动方式是STM32内嵌的SRAM启动。该模式用于调试。 用jlink在线仿真,则是下载到SRAM中。
以上三种启动方式我们都很熟悉,但是他的究竟是如何实现的呢?
我们先来看看《Cortex-M3权威指南》关于CM3复位后的动作:
当选择相应的启动方式时,对应的存储器空间被映射到启动空间(0x00000000)。
从闪存存储器启动:主闪存存储器被映射到启动空间(0x0000 0000) ,也就是0x08000000被映射到0x00000000。
从内嵌SRAM启动 :SRAM起始地址 0x2000 0000 被映射到0x00000000。
从系统存储器启动:系统存储器被映射到启动空间(0x0000 0000),也就是0x1FFF F000被映射到0x00000000。
(为什么是0x1FFF F000 可以查阅上文中的 2.2小节 STM32 的存储器映射分析,STM32互联型产品这个地址不一样,此地址由ST官方写入了一段BootLoader代码,可以通过官方BootLoader升级MCU固件,无法修改)。
参考链接:
STM32的内存管理相关(内存架构,内存管理,map文件分析)_stm32f103 malloc_矜辰所致的博客-CSDN博客
深入理解STM32内存管理_行稳方能走远的博客-CSDN博客
八、stm32时钟树基础总线地址
stm32对基本外设的管理都是通过总线进行管理的,外设的配置寄存器的操作形式为"基础地址+偏移地址",其中的基础地址就是总线的地址。
stm32f1系列的基础地址的映射分为两部分,有部分是片上外设(eripheral_memory_map)如上图中"A"的位置。另一个映射区域为Cortex-M3的内核寄存器(/* Memory mapping of Cortex-M3 Hardware */)如图中的"B"的位置。在stm32f1的代码文件中,两种寄存器的定义分别在"stm32f10x.h"和"core_cm3.h"的代码文件里面。
下面是"stm32f10x.h"代码中对片上外设(eripheral_memory_map)基础总线地址的定义。
cpp
/** @addtogroup Peripheral_memory_map
* @{
*/
#define FLASH_BASE ((uint32_t)0x08000000) /*!< FLASH base address in the alias region */
#define SRAM_BASE ((uint32_t)0x20000000) /*!< SRAM base address in the alias region */
#define PERIPH_BASE ((uint32_t)0x40000000) /*!< Peripheral base address in the alias region */
#define SRAM_BB_BASE ((uint32_t)0x22000000) /*!< SRAM base address in the bit-band region */
#define PERIPH_BB_BASE ((uint32_t)0x42000000) /*!< Peripheral base address in the bit-band region */
#define FSMC_R_BASE ((uint32_t)0xA0000000) /*!< FSMC registers base address */
/*!< Peripheral memory map */
#define APB1PERIPH_BASE PERIPH_BASE
#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000)
#define AHBPERIPH_BASE (PERIPH_BASE + 0x20000)
#define TIM2_BASE (APB1PERIPH_BASE + 0x0000)
#define TIM3_BASE (APB1PERIPH_BASE + 0x0400)
#define TIM4_BASE (APB1PERIPH_BASE + 0x0800)
#define TIM5_BASE (APB1PERIPH_BASE + 0x0C00)
#define TIM6_BASE (APB1PERIPH_BASE + 0x1000)
#define TIM7_BASE (APB1PERIPH_BASE + 0x1400)
#define TIM12_BASE (APB1PERIPH_BASE + 0x1800)
.................等
下面是"core_cm3.h"代码文件中对Cortex-M3的内核寄存器(/* Memory mapping of Cortex-M3 Hardware */)的定义的情况。
cpp
/* Memory mapping of Cortex-M3 Hardware */
#define SCS_BASE (0xE000E000) /*!< System Control Space Base Address */
#define ITM_BASE (0xE0000000) /*!< ITM Base Address */
#define CoreDebug_BASE (0xE000EDF0) /*!< Core Debug Base Address */
#define SysTick_BASE (SCS_BASE + 0x0010) /*!< SysTick Base Address */
#define NVIC_BASE (SCS_BASE + 0x0100) /*!< NVIC Base Address */
#define SCB_BASE (SCS_BASE + 0x0D00) /*!< System Control Block Base Address */
#define InterruptType ((InterruptType_Type *) SCS_BASE) /*!< Interrupt Type Register */
#define SCB ((SCB_Type *) SCB_BASE) /*!< SCB configuration struct */
#define SysTick ((SysTick_Type *) SysTick_BASE) /*!< SysTick configuration struct */
#define NVIC ((NVIC_Type *) NVIC_BASE) /*!< NVIC configuration struct */
#define ITM ((ITM_Type *) ITM_BASE) /*!< ITM configuration struct */
#define CoreDebug ((CoreDebug_Type *) CoreDebug_BASE) /*!< Core Debug configuration struct */
上面比较重要的就是AHB、APB1、APB2、SysTick_BASE几个总线的地址的地址是比较重要的。
总线名称 | 总线基地址 |
---|---|
APB1 | 0x4000 0000 |
APB2 | 0x4001 0000 |
AHB | 0x4002 0000 |