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


    一、学习参考资料

            (1)正点原子的寄存器源码。

            (2)STM32F103最小系统板开发指南-寄存器版本_V1.1(正点)

            (3)STM32F103最小系统板开发指南-库函数版本_V1.1(正点)

            (4)Cortex-M3权威指南(中文)

            (5)STM32中文参考手册_V10

            (6)stm32cubemx可视化时钟树配置

            (7)其他博主文章

           本文主要以stm32f1系列单片机为研究对象,从寄存器层面对时钟树的配置、中断优先级的配置进行阐述。

    二、stm32官方bsp库寄存器封装的基本方式

            一般按照连续的寄存器地址使用结构体指针的形式封装,将寄存器按照连续的顺序定义变量在结构体中,然后直接将利用结构体指针指向结构体第一个定义的首地址的位置。

    1. typedef struct
    2. {
    3. __I uint32_t CPUID; /*!< Offset: 0x00 CPU ID Base Register */
    4. __IO uint32_t ICSR; /*!< Offset: 0x04 Interrupt Control State Register */
    5. __IO uint32_t VTOR; /*!< Offset: 0x08 Vector Table Offset Register */
    6. __IO uint32_t AIRCR; /*!< Offset: 0x0C Application Interrupt / Reset Control Register */
    7. __IO uint32_t SCR; /*!< Offset: 0x10 System Control Register */
    8. __IO uint32_t CCR; /*!< Offset: 0x14 Configuration Control Register */
    9. __IO uint8_t SHP[12]; /*!< Offset: 0x18 System Handlers Priority Registers (4-7, 8-11, 12-15) */
    10. __IO uint32_t SHCSR; /*!< Offset: 0x24 System Handler Control and State Register */
    11. __IO uint32_t CFSR; /*!< Offset: 0x28 Configurable Fault Status Register */
    12. __IO uint32_t HFSR; /*!< Offset: 0x2C Hard Fault Status Register */
    13. __IO uint32_t DFSR; /*!< Offset: 0x30 Debug Fault Status Register */
    14. __IO uint32_t MMFAR; /*!< Offset: 0x34 Mem Manage Address Register */
    15. __IO uint32_t BFAR; /*!< Offset: 0x38 Bus Fault Address Register */
    16. __IO uint32_t AFSR; /*!< Offset: 0x3C Auxiliary Fault Status Register */
    17. __I uint32_t PFR[2]; /*!< Offset: 0x40 Processor Feature Register */ // 如果占用两个字节,就直接定义为数组的形式
    18. __I uint32_t DFR; /*!< Offset: 0x48 Debug Feature Register */
    19. __I uint32_t ADR; /*!< Offset: 0x4C Auxiliary Feature Register */
    20. __I uint32_t MMFR[4]; /*!< Offset: 0x50 Memory Model Feature Register */
    21. __I uint32_t ISAR[5]; /*!< Offset: 0x60 ISA Feature Register */
    22. } SCB_Type;
    #define SCB   ((SCB_Type *)   SCB_BASE)    /*!< SCB configuration struct          */

            上面定义之后,对寄存器的操作就变成了“SCB->VTOR &= data(寄存器对应位置零),SCB->VTOR |= data(寄存器对应位置一)”。上面有些寄存器并不是32位的,占用位数比较多的可以采用定义数据的方式。

    --------------------------------------------------------------------------------------------------------------------------------

            除了上面大范围的定义寄存器的形式,对于操作某些单个寄存器的时候,可以采用寄存器地址强制转化的方式。转化为指针“ unsigned volatile int * ”。

    1. #define RCC_BASE_MY (uint32_t)0x40021000 //基地址
    2. #define RCC_CR_MY (uint32_t)0x00000000 //偏移地址
    3. #define RCC_CFGR_MY (uint32_t)0x00000004
    4. #define RCC_CIR_MY (uint32_t)0x00000008
    5. #define RCC_APB2RSTR_MY (uint32_t)0x0000000c
    6. #define MY_RCC_CR *((unsigned volatile int*)(RCC_BASE_MY+RCC_CR_MY))
    7. #define MY_RCC_CFGR *((unsigned volatile int*)(RCC_BASE_MY+RCC_CFGR_MY))
    8. #define MY_RCC_CIR *((unsigned volatile int*)(RCC_BASE_MY+RCC_CIR_MY))
    9. #define MY_RCC_APB2RSTR *((unsigned volatile int*)(RCC_BASE_MY+RCC_APB2RSTR_MY))
    10. #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()”函数判断用户的选择是否正确,如果不正确就会调用相应的错误处理函数进行处理。

    1. #define assert_param(expr) ((expr) ? (void)0 : assert_failed((uint8_t *)__FILE__, __LINE__))
    2. //利用上面定义的assert_param(expr),判断expr是否正确(也就是用户的选择参数是否正确,来确定是否调用assert_failed函数,assert_failed函数需要从写。)
    3. /* Exported functions ------------------------------------------------------- */
    4. void assert_failed(uint8_t* file, uint32_t line);
    5. #else
    6. #define assert_param(expr) ((void)0)
    7. #endif /* USE_FULL_ASSERT */
    8. #endif /* __STM32F10x_CONF_H */

            

    1. #define     __I      volatile const      /*!< defines 'read only' permissions      */
    2. #define     __O     volatile            /*!< defines 'write only' permissions     */
    3. #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、PLLXTPREHSE分频器作为PLL输入 (HSE divider for PLL entry) (属于RCC_CFGR

    2、PLLMULPLL倍频系数 (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”的位置向前进行配置。

    四、系统时钟寄存器

    1. typedef struct
    2. {
    3. __IO uint32_t CR;
    4. __IO uint32_t CFGR;
    5. __IO uint32_t CIR;
    6. __IO uint32_t APB2RSTR;
    7. __IO uint32_t APB1RSTR;
    8. __IO uint32_t AHBENR;
    9. __IO uint32_t APB2ENR;
    10. __IO uint32_t APB1ENR;
    11. __IO uint32_t BDCR;
    12. __IO uint32_t CSR;
    13. #ifdef STM32F10X_CL
    14. __IO uint32_t AHBRSTR;
    15. __IO uint32_t CFGR2;
    16. #endif /* STM32F10X_CL */
    17. #if defined (STM32F10X_LD_VL) || defined (STM32F10X_MD_VL) || defined (STM32F10X_HD_VL)
    18. uint32_t RESERVED0;
    19. __IO uint32_t CFGR2;
    20. #endif /* STM32F10X_LD_VL || STM32F10X_MD_VL || STM32F10X_HD_VL */
    21. } RCC_TypeDef;
    #define RCC                 ((RCC_TypeDef *) RCC_BASE)

             对系统时钟树的配置使用上述的底层地址定义即可。

    下面是正点时钟树的配置代码,其中中断优先级的配置后面会讲:

    1. //不能在这里执行所有外设复位!否则至少引起串口不工作.
    2. //把所有时钟寄存器复位
    3. void MYRCC_DeInit(void)
    4. {
    5. RCC->APB1RSTR = 0x00000000;//复位结束
    6. RCC->APB2RSTR = 0x00000000;
    7. RCC->AHBENR = 0x00000014; //睡眠模式闪存和SRAM时钟使能.其他关闭.
    8. RCC->APB2ENR = 0x00000000; //外设时钟关闭.
    9. RCC->APB1ENR = 0x00000000;
    10. RCC->CR |= 0x00000001; //使能内部高速时钟HSION
    11. RCC->CFGR &= 0xF8FF0000; //复位SW[1:0],HPRE[3:0],PPRE1[2:0],PPRE2[2:0],ADCPRE[1:0],MCO[2:0]
    12. RCC->CR &= 0xFEF6FFFF; //复位HSEON,CSSON,PLLON
    13. RCC->CR &= 0xFFFBFFFF; //复位HSEBYP
    14. RCC->CFGR &= 0xFF80FFFF; //复位PLLSRC, PLLXTPRE, PLLMUL[3:0] and USBPRE
    15. RCC->CIR = 0x00000000; //关闭所有中断
    16. //配置向量表
    17. #ifdef VECT_TAB_RAM
    18. /* 向量表的配置和中断优先级的配置后面都会讲 */
    19. MY_NVIC_SetVectorTable(0x20000000, 0x0);
    20. #else
    21. MY_NVIC_SetVectorTable(0x08000000,0x0);
    22. #endif
    23. }
    1. //系统时钟初始化函数
    2. //pll:选择的倍频数,从2开始,最大值为16
    3. void Stm32_Clock_Init(u8 PLL)
    4. {
    5. unsigned char temp=0;
    6. MYRCC_DeInit(); //复位并配置向量表
    7. RCC->CR|=0x00010000; //外部高速时钟使能HSEON
    8. while(!(RCC->CR>>17)); //等待外部时钟就绪
    9. RCC->CFGR=0X00000400; //APB1=DIV2;APB2=DIV1;AHB=DIV1;
    10. PLL-=2; //抵消2个单位(因为是从2开始的,设置0就是2)
    11. RCC->CFGR|=PLL<<18; //设置PLL值 2~16
    12. RCC->CFGR|=1<<16; //PLLSRC ON
    13. FLASH->ACR|=0x32; //FLASH 2个延时周期
    14. RCC->CR|=0x01000000; //PLLON
    15. while(!(RCC->CR>>25)); //等待PLL锁定
    16. RCC->CFGR|=0x00000002; //PLL作为系统时钟
    17. while(temp!=0x02) //等待PLL作为系统时钟设置成功
    18. {
    19. temp=RCC->CFGR>>2;
    20. temp&=0x03;
    21. }
    22. }

    五、中断向量表地址的配置

            这里需要有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页)。

    下面是底层硬件地址结构体封装和正点项链表地址设置代码。

    1. /** @addtogroup CMSIS_CM3_SCB CMSIS CM3 SCB
    2. memory mapped structure for System Control Block (SCB)
    3. @{
    4. */
    5. typedef struct
    6. {
    7. __I uint32_t CPUID; /*!< Offset: 0x00 CPU ID Base Register */
    8. __IO uint32_t ICSR; /*!< Offset: 0x04 Interrupt Control State Register */
    9. __IO uint32_t VTOR; /*!< Offset: 0x08 Vector Table Offset Register */
    10. __IO uint32_t AIRCR; /*!< Offset: 0x0C Application Interrupt / Reset Control Register */
    11. __IO uint32_t SCR; /*!< Offset: 0x10 System Control Register */
    12. __IO uint32_t CCR; /*!< Offset: 0x14 Configuration Control Register */
    13. __IO uint8_t SHP[12]; /*!< Offset: 0x18 System Handlers Priority Registers (4-7, 8-11, 12-15) */
    14. __IO uint32_t SHCSR; /*!< Offset: 0x24 System Handler Control and State Register */
    15. __IO uint32_t CFSR; /*!< Offset: 0x28 Configurable Fault Status Register */
    16. __IO uint32_t HFSR; /*!< Offset: 0x2C Hard Fault Status Register */
    17. __IO uint32_t DFSR; /*!< Offset: 0x30 Debug Fault Status Register */
    18. __IO uint32_t MMFAR; /*!< Offset: 0x34 Mem Manage Address Register */
    19. __IO uint32_t BFAR; /*!< Offset: 0x38 Bus Fault Address Register */
    20. __IO uint32_t AFSR; /*!< Offset: 0x3C Auxiliary Fault Status Register */
    21. __I uint32_t PFR[2]; /*!< Offset: 0x40 Processor Feature Register */
    22. __I uint32_t DFR; /*!< Offset: 0x48 Debug Feature Register */
    23. __I uint32_t ADR; /*!< Offset: 0x4C Auxiliary Feature Register */
    24. __I uint32_t MMFR[4]; /*!< Offset: 0x50 Memory Model Feature Register */
    25. __I uint32_t ISAR[5]; /*!< Offset: 0x60 ISA Feature Register */
    26. } SCB_Type;
    #define SCB                 ((SCB_Type *)           SCB_BASE)         /*!< SCB configuration struct          */
    1. //设置向量表偏移地址
    2. //NVIC_VectTab:基址
    3. //Offset:偏移量
    4. void MY_NVIC_SetVectorTable(u32 NVIC_VectTab, u32 Offset)
    5. {
    6. SCB->VTOR = NVIC_VectTab|(Offset & (u32)0x1FFFFF80);//设置NVIC的向量表偏移寄存器
    7. //用于标识向量表是在CODE区还是在RAM区
    8. }

     六、中断的优先级分组、优先级设置、外部中断的配置

     6.1、stm32中断相关基础知识

            中断使用的时候不允许中断嵌套。

            对于stm32f10xxx系列单片机而言,有68个可屏蔽中断通道(不包含16Cortex™-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权威指南(中文)”。

            下面是正点地中断向量表地址配置代码:

    1. //设置向量表偏移地址
    2. //NVIC_VectTab:基址
    3. //Offset:偏移量
    4. void MY_NVIC_SetVectorTable(u32 NVIC_VectTab, u32 Offset)
    5. {
    6. SCB->VTOR = NVIC_VectTab|(Offset & (u32)0x1FFFFF80);//设置NVIC的向量表偏移寄存器
    7. //用于标识向量表是在CODE区还是在RAM区
    8. }

            根据6.1中地前两个图可以知道如何设置寄存器地分组,下面是正点的中断优先级分组的代码:

    1. //设置NVIC分组
    2. //NVIC_Group:NVIC分组 0~4 总共5组
    3. void MY_NVIC_PriorityGroupConfig(u8 NVIC_Group)
    4. {
    5. u32 temp,temp1;
    6. temp1=(~NVIC_Group)&0x07;//取后三位,分组数与位的设置正好是取反的关系
    7. temp1<<=8; //移位到寄存器AIRCR中断优先级分组配置位置
    8. temp=SCB->AIRCR; //读取先前的设置
    9. temp&=0X0000F8FF; //清空先前分组
    10. temp|=0X05FA0000; //写入钥匙,根据手册,改变寄存器必须写入钥匙
    11. temp|=temp1;
    12. SCB->AIRCR=temp; //设置分组
    13. }

    七、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)基础总线地址的定义。

    1. /** @addtogroup Peripheral_memory_map
    2. * @{
    3. */
    4. #define FLASH_BASE ((uint32_t)0x08000000) /*!< FLASH base address in the alias region */
    5. #define SRAM_BASE ((uint32_t)0x20000000) /*!< SRAM base address in the alias region */
    6. #define PERIPH_BASE ((uint32_t)0x40000000) /*!< Peripheral base address in the alias region */
    7. #define SRAM_BB_BASE ((uint32_t)0x22000000) /*!< SRAM base address in the bit-band region */
    8. #define PERIPH_BB_BASE ((uint32_t)0x42000000) /*!< Peripheral base address in the bit-band region */
    9. #define FSMC_R_BASE ((uint32_t)0xA0000000) /*!< FSMC registers base address */
    10. /*!< Peripheral memory map */
    11. #define APB1PERIPH_BASE PERIPH_BASE
    12. #define APB2PERIPH_BASE (PERIPH_BASE + 0x10000)
    13. #define AHBPERIPH_BASE (PERIPH_BASE + 0x20000)
    14. #define TIM2_BASE (APB1PERIPH_BASE + 0x0000)
    15. #define TIM3_BASE (APB1PERIPH_BASE + 0x0400)
    16. #define TIM4_BASE (APB1PERIPH_BASE + 0x0800)
    17. #define TIM5_BASE (APB1PERIPH_BASE + 0x0C00)
    18. #define TIM6_BASE (APB1PERIPH_BASE + 0x1000)
    19. #define TIM7_BASE (APB1PERIPH_BASE + 0x1400)
    20. #define TIM12_BASE (APB1PERIPH_BASE + 0x1800)
    21. .................等

            下面是“core_cm3.h”代码文件中对Cortex-M3的内核寄存器(/* Memory mapping of Cortex-M3 Hardware */)的定义的情况。

    1. /* Memory mapping of Cortex-M3 Hardware */
    2. #define SCS_BASE (0xE000E000) /*!< System Control Space Base Address */
    3. #define ITM_BASE (0xE0000000) /*!< ITM Base Address */
    4. #define CoreDebug_BASE (0xE000EDF0) /*!< Core Debug Base Address */
    5. #define SysTick_BASE (SCS_BASE + 0x0010) /*!< SysTick Base Address */
    6. #define NVIC_BASE (SCS_BASE + 0x0100) /*!< NVIC Base Address */
    7. #define SCB_BASE (SCS_BASE + 0x0D00) /*!< System Control Block Base Address */
    8. #define InterruptType ((InterruptType_Type *) SCS_BASE) /*!< Interrupt Type Register */
    9. #define SCB ((SCB_Type *) SCB_BASE) /*!< SCB configuration struct */
    10. #define SysTick ((SysTick_Type *) SysTick_BASE) /*!< SysTick configuration struct */
    11. #define NVIC ((NVIC_Type *) NVIC_BASE) /*!< NVIC configuration struct */
    12. #define ITM ((ITM_Type *) ITM_BASE) /*!< ITM configuration struct */
    13. #define CoreDebug ((CoreDebug_Type *) CoreDebug_BASE) /*!< Core Debug configuration struct */

    上面比较重要的就是AHBAPB1APB2SysTick_BASE几个总线的地址的地址是比较重要的。

    总线名称总线基地址
    APB10x4000 0000
    APB20x4001 0000
    AHB0x4002 0000

  • 相关阅读:
    《JAVA设计模式系列》模板模式
    2022·创新峰会回顾 | 擎创科技荣获CCID双料优秀成果奖
    【幂等幂等幂等,重要的知识说三遍!】常见的九种解决方案汇总
    深入探讨基于大语言模型的数据标注
    学信息系统项目管理师第4版系列16_资源管理过程
    基于ssm的学校社团管理系统的开发与实现-计算机毕业设计源码
    测试人生 | 转行测试开发,4年4“跳”年薪涨3倍,我的目标是星辰大海(附大厂面经)!
    easyExcel 简单使用 和遇到的坑
    OpenCV数字图像处理基于C++:灰度变换
    前端实现大屏缩放自适应屏幕
  • 原文地址:https://blog.csdn.net/qq_50299271/article/details/133621573