stm32的时钟、中断的配置(针对寄存器),一些基础知识

一、学习参考资料

(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。

heapstack 其实也属于 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_SizeHeap_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
相关推荐
嵌新程1 小时前
day03(单片机高级)RTOS
stm32·单片机·嵌入式硬件·freertos·rtos·u575
Lin2012301 小时前
STM32 Keil5 attribute 关键字的用法
stm32·单片机·嵌入式硬件
电工小王(全国可飞)2 小时前
STM32 RAM在Memory Map中被分为3个区域
stm32·单片机·嵌入式硬件
maxiumII2 小时前
Diving into the STM32 HAL-----DAC笔记
笔记·stm32·嵌入式硬件
美式小田4 小时前
单片机学习笔记 9. 8×8LED点阵屏
笔记·单片机·嵌入式硬件·学习
兰_博5 小时前
51单片机-独立按键与数码管联动
单片机·嵌入式硬件·51单片机
时光の尘5 小时前
C语言菜鸟入门·关键字·float以及double的用法
运维·服务器·c语言·开发语言·stm32·单片机·c
嵌入式大圣7 小时前
单片机结合OpenCV
单片机·嵌入式硬件·opencv
日晨难再8 小时前
嵌入式:STM32的启动(Startup)文件解析
stm32·单片机·嵌入式硬件
yufengxinpian9 小时前
集成了高性能ARM Cortex-M0+处理器的一款SimpleLink 2.4 GHz无线模块-RF-BM-2340B1
单片机·嵌入式硬件·音视频·智能硬件