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 小时前
基于STM32的智能充电桩:集成RTOS、MQTT与SQLite的先进管理系统设计思路
stm32·单片机·嵌入式硬件·mqtt·sqlite·毕业设计·智能充电桩
m0_739312874 小时前
【STM32】项目实战——OV7725/OV2604摄像头颜色识别检测(开源)
stm32·单片机·嵌入式硬件
嵌入式小章4 小时前
基于STM32的实时时钟(RTC)教学
stm32·嵌入式硬件·实时音视频
TeYiToKu4 小时前
笔记整理—linux驱动开发部分(9)framebuffer驱动框架
linux·c语言·arm开发·驱动开发·笔记·嵌入式硬件·arm
基极向上的三极管5 小时前
【AD】3-4 在原理图中放置元件
嵌入式硬件
徐嵌5 小时前
STM32项目---水质水位检测
stm32·单片机·嵌入式硬件
徐嵌5 小时前
STM32项目---畜牧定位器
c语言·stm32·单片机·物联网·iot
lantiandianzi5 小时前
基于单片机的老人生活安全监测系统
单片机·嵌入式硬件·生活
东胜物联6 小时前
探寻5G工业网关市场,5G工业网关品牌解析
人工智能·嵌入式硬件·5g
stm32发烧友6 小时前
基于STM32的智能家居环境监测系统设计
stm32·嵌入式硬件·智能家居