题外话
今天是我疫情期间居家调休的最后一天。再更新一篇。上班后更新的频率可能就不会这么高了。作为学习,作为对基础知识的巩固,我还会一如既往的努力,等把stm32的大部分基础内容都更新完了。后面再安排其他的技术点来写,比如电机控制,比如单片机的通信,比如嵌入式硬件方面的基础知识,比如专业英语,比如职业规划,等等等等。草稿箱里已经堆了将近一百篇的草稿。后面一 一完善发布吧。下面我们开始今天的正题。
1、stm32存储器映射概述
如果说时钟是单片机的心脏,那么存储器映射就像是单片机的神经网络,传输大脑中枢(cpu)到各个外设的神经信号。由此可知,这块的知识点在整个单片机知识体系架构里面,也是非常重要的。
可能有一部分人会觉得,现在我们都是基于标准库、HALL库,甚至LL库来进行开发,非常方便,所有的资源都是函数库封装好的,直接调用非常方便。对于这些底层的东西,我们没有必要还像学习51单片机那样,来研究它了。
然而我想说,事实并不是这样的。对于一些比较简单的项目,功能比较单一,代码量不大项目,没什么问题。甚至一些没有用到特别复杂外设的工作量稍大的项目,也问题不大。一般常规的问题类似于我这篇文章所写的内容 :【基础知识】单片机编程注意事项及易错点总结 。大部分的bug都能够得到解决。但是如果对于更大的项目,功能更复杂,外设更多。还需要注意代码的模块化,可移植性。这样各个层级的文件会增加,全局变量、局部变量的使用都会大大增加。这个时候代码的复杂度就会成倍增加。这个时候,往往就更需要我们知道单片机的内部是怎么工作的。
那么对于存储器映射的学习,是我们了解单片机内部工作的开始。后面我们还会总结分析下stm32的启动文件,map文件等等。这些知识点,也是技术能力迈向更高水平必须要学习理解的。也有人说,掌握了单片机的存储器映射,就掌握了这款单片机40%的知识点。
2、什么是存储器映射
存储器本身不具备地址,所以把芯片内核所预先设定好的地址分配给寄存器,就是存储器映射。因为stm32的地址线是32位,也就是2的32次方,正好是对应4G的虚拟存储空间。把内核厂商(也就是ARM公司)定义的这个虚拟空间与芯片厂商(这里是ST)芯片内部外设进行对应,也就是给存储器分配地址,即存储器映射。4个G的地址这么大,用不完没关系,可以保留。
3、stm32f4存储器映射分析
下图是stm32f40x 的映射block diagram
cortex-M4内核如cortex-m3内核一样,都是把4G空间分成block 0 ~ block7 共 8个块,每块大小为512M,并指出各个block是怎么分配的。下面我们就详细阐述一下。
Block 0代码区
Aliased to flash ,system memory or SRAM depending on the Boot pins
开始运行,BOOT1、BOOT0这两个引脚的电平值选择0X0000 0000–0X001F FFFF映射到不同的存储器上,通过BOOT引脚选择启动模式。篇幅所限,关于BOOT1、BOOT0的引脚设置选择不同的启动方式,这里就不详细展开了。
主Flash:用于保存数据的区域,每个芯片都有一个参数Flash空间大小,指的就是这部分。
CCM data RAM:Code区域是用I-Code和D-Code访问,作用是为了加快数据处理速度。
system memory :STM32在出厂时,已经固化了一段程序在System memory(medium-density devices的地址为:0x1FFF_F000,大小为2KB)存储器中。这段程序就是一个固定好的,并且没法修改的Boot Loader
Options Bytes :可以按照用户的需要进行配置(如配置看门狗为硬件实现还是软件实现);
PS:存储器的重映射
通常系统启动都是从0地址处开始,但是为了支持不同的存储介质,不同的存储介质被分配了一个非0地址区域。这就是为什么要进行重映射。
因此重映射主要发生在两种情况下,一是系统启动的过程中;二是如果中途遇到需要在不同的存储器之间进行切换的时候也需要进行重映射
我们一般的单片机自举(启动)单片机地址,都是从0开始运行的,STM32启动需要重映射地址,F4xx的0X0000 0000~0x001F FFFF地址映射了到什么存储器上,那么就从该存储器上读取指令。
Block 1
SRAM运行时临时存放代码的地方。不同类型的STM32单片机的SRAM大小是不一样的,但是他们的起始地址都是0x2000 0000,终止地址都是0x2000 0000+其固定的容量大小。SRAM的理解比较简单,其作用是用来存取各种动态的输入输出数据、中间计算结果以及与外部存储器交换的数据和暂存数据,用于程序运行的堆栈开销。设备断电后,SRAM中存储的数据就会丢失。
Block 2
Block2 用于设计片内外设,根据总线速度的不同,Block2被分为了APB和AHB。在上图所示的stm32f40x的映射框图中可以看到,APB分为APB1和APB2,AHB分为AHB1和AHB2,AHB3(不在Block2的映射范围)
ps:什么是寄存器映射
在存储器Block2这块区域,设计的是片上外设,它们以4个字节为一个单元,共32bit,那么每一个单元对应不同的功能,当我们控制这些单元时就可以驱动外设工作。我们可以找到每个单元的起始地址,然后通过C语言指针的操作方式来访问这些单元,如果每次都是通过这种地址的方式来访问,不仅不好记忆还容易出错,这是我们可以根据每个单元的功能不同,以功能为名给这个存储单元取一个别名,这个别名就是我们经常说的寄存器,这个给已经分配好地址的有特定功能的内存单元取别名的过程就叫寄存器映射。
举例说明:
第一步:宏定义GPIO 口的基地址,AHB1PERIPH_BASE 依次累加 0x400的地址偏移量,就得到GPIOA~GPIOK的基地址。
#define GPIOA_BASE (AHB1PERIPH_BASE + 0x0000)
#define GPIOB_BASE (AHB1PERIPH_BASE + 0x0400)
#define GPIOC_BASE (AHB1PERIPH_BASE + 0x0800)
#define GPIOD_BASE (AHB1PERIPH_BASE + 0x0C00)
#define GPIOE_BASE (AHB1PERIPH_BASE + 0x1000)
#define GPIOF_BASE (AHB1PERIPH_BASE + 0x1400)
#define GPIOG_BASE (AHB1PERIPH_BASE + 0x1800)
#define GPIOH_BASE (AHB1PERIPH_BASE + 0x1C00)
#define GPIOI_BASE (AHB1PERIPH_BASE + 0x2000)
#define GPIOJ_BASE (AHB1PERIPH_BASE + 0x2400)
#define GPIOK_BASE (AHB1PERIPH_BASE + 0x2800)
第二步:把这个GPIO的基地址通过加上(GPIO_TypeDef *)这步骚操作,来把地址强转成具有GPIO_TypeDef 性质的指针变量,并且用#define进行宏定义,实现取了个别名的效果。这就是寄存器的映射。
#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)
#define GPIOB ((GPIO_TypeDef *) GPIOB_BASE)
#define GPIOC ((GPIO_TypeDef *) GPIOC_BASE)
#define GPIOD ((GPIO_TypeDef *) GPIOD_BASE)
#define GPIOE ((GPIO_TypeDef *) GPIOE_BASE)
#define GPIOF ((GPIO_TypeDef *) GPIOF_BASE)
#define GPIOG ((GPIO_TypeDef *) GPIOG_BASE)
#define GPIOH ((GPIO_TypeDef *) GPIOH_BASE)
#define GPIOI ((GPIO_TypeDef *) GPIOI_BASE)
#define GPIOJ ((GPIO_TypeDef *) GPIOJ_BASE)
#define GPIOK ((GPIO_TypeDef *) GPIOK_BASE)
ps:GPIO_TypeDef结构体的声明:
typedef struct
{
__IO uint32_t MODER; /*!< GPIO port mode register, Address offset: 0x00 */
__IO uint32_t OTYPER; /*!< GPIO port output type register, Address offset: 0x04 */
__IO uint32_t OSPEEDR; /*!< GPIO port output speed register, Address offset: 0x08 */
__IO uint32_t PUPDR; /*!< GPIO port pull-up/pull-down register, Address offset: 0x0C */
__IO uint32_t IDR; /*!< GPIO port input data register, Address offset: 0x10 */
__IO uint32_t ODR; /*!< GPIO port output data register, Address offset: 0x14 */
__IO uint16_t BSRRL; /*!< GPIO port bit set/reset low register, Address offset: 0x18 */
__IO uint16_t BSRRH; /*!< GPIO port bit set/reset high register, Address offset: 0x1A */
__IO uint32_t LCKR; /*!< GPIO port configuration lock register, Address offset: 0x1C */
__IO uint32_t AFR[2]; /*!< GPIO alternate function registers, Address offset: 0x20-0x24 */
} GPIO_TypeDef;
APB1总线外设地址
APB2总线外设地址
AHB1总线外设地址
AHB2总线外设地址
AHB3总线外设地址
Block3/Block4/Block5
FSMC Bank 的四个分区及控制寄存器
Block6
Reserved(保留)
Block7
internal peripherals,cortex-m4 内核内部外设地址。
4、代码展示
然后看下存储器的映射关系在代码中是如何体现的,下图是我项目中的stm32f4xx.h文件的部分代码。存储器的映射关系都在这个文件中。复制了部分代码,供大家参考学习。感兴趣的朋友可以对照自己的代码和对应芯片规格书进行研究学习。
#define FLASH_BASE ((uint32_t)0x08000000) /*!< FLASH(up to 1 MB) base address in the alias region */
#define CCMDATARAM_BASE ((uint32_t)0x10000000) /*!< CCM(core coupled memory) data RAM(64 KB) base address in the alias region */
#define SRAM1_BASE ((uint32_t)0x20000000) /*!< SRAM1(112 KB) base address in the alias region */
#define SRAM2_BASE ((uint32_t)0x2001C000) /*!< SRAM2(16 KB) base address in the alias region */
#define SRAM3_BASE ((uint32_t)0x20020000) /*!< SRAM3(64 KB) base address in the alias region */
#define PERIPH_BASE ((uint32_t)0x40000000) /*!< Peripheral base address in the alias region */
#define BKPSRAM_BASE ((uint32_t)0x40024000) /*!< Backup SRAM(4 KB) base address in the alias region */
#if defined (STM32F40_41xxx)
#define FSMC_R_BASE ((uint32_t)0xA0000000) /*!< FSMC registers base address */
#endif /* STM32F40_41xxx */
#if defined (STM32F427_437xx) || defined (STM32F429_439xx)
#define FMC_R_BASE ((uint32_t)0xA0000000) /*!< FMC registers base address */
#endif /* STM32F427_437xx || STM32F429_439xx */
#define CCMDATARAM_BB_BASE ((uint32_t)0x12000000) /*!< CCM(core coupled memory) data RAM(64 KB) base address in the bit-band region */
#define SRAM1_BB_BASE ((uint32_t)0x22000000) /*!< SRAM1(112 KB) base address in the bit-band region */
#define SRAM2_BB_BASE ((uint32_t)0x2201C000) /*!< SRAM2(16 KB) base address in the bit-band region */
#define SRAM3_BB_BASE ((uint32_t)0x22400000) /*!< SRAM3(64 KB) base address in the bit-band region */
#define PERIPH_BB_BASE ((uint32_t)0x42000000) /*!< Peripheral base address in the bit-band region */
#define BKPSRAM_BB_BASE ((uint32_t)0x42024000) /*!< Backup SRAM(4 KB) base address in the bit-band region
/*!< Peripheral memory map */
#define APB1PERIPH_BASE PERIPH_BASE
#define APB2PERIPH_BASE (PERIPH_BASE + 0x00010000)
#define AHB1PERIPH_BASE (PERIPH_BASE + 0x00020000)
#define AHB2PERIPH_BASE (PERIPH_BASE + 0x10000000)
/*!< APB1 peripherals */
#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)
#define TIM13_BASE (APB1PERIPH_BASE + 0x1C00)
#define TIM14_BASE (APB1PERIPH_BASE + 0x2000)
#define RTC_BASE (APB1PERIPH_BASE + 0x2800)
#define WWDG_BASE (APB1PERIPH_BASE + 0x2C00)
#define IWDG_BASE (APB1PERIPH_BASE + 0x3000)
#define I2S2ext_BASE (APB1PERIPH_BASE + 0x3400)
#define SPI2_BASE (APB1PERIPH_BASE + 0x3800)
#define SPI3_BASE (APB1PERIPH_BASE + 0x3C00)
#define I2S3ext_BASE (APB1PERIPH_BASE + 0x4000)
/*!< AHB1 peripherals */
#define GPIOA_BASE (AHB1PERIPH_BASE + 0x0000)
#define GPIOB_BASE (AHB1PERIPH_BASE + 0x0400)
#define GPIOC_BASE (AHB1PERIPH_BASE + 0x0800)
#define GPIOD_BASE (AHB1PERIPH_BASE + 0x0C00)
#define GPIOE_BASE (AHB1PERIPH_BASE + 0x1000)
#define GPIOF_BASE (AHB1PERIPH_BASE + 0x1400)
#define GPIOG_BASE (AHB1PERIPH_BASE + 0x1800)
#define GPIOH_BASE (AHB1PERIPH_BASE + 0x1C00)
#define GPIOI_BASE (AHB1PERIPH_BASE + 0x2000)
#define GPIOJ_BASE (AHB1PERIPH_BASE + 0x2400)
#define GPIOK_BASE (AHB1PERIPH_BASE + 0x2800)
/*!< APB2 peripherals */
#define TIM1_BASE (APB2PERIPH_BASE + 0x0000)
#define TIM8_BASE (APB2PERIPH_BASE + 0x0400)
#define USART1_BASE (APB2PERIPH_BASE + 0x1000)
#define USART6_BASE (APB2PERIPH_BASE + 0x1400)
#define ADC1_BASE (APB2PERIPH_BASE + 0x2000)
#define ADC2_BASE (APB2PERIPH_BASE + 0x2100)
#define ADC3_BASE (APB2PERIPH_BASE + 0x2200)
#define ADC_BASE (APB2PERIPH_BASE + 0x2300)
#define SDIO_BASE (APB2PERIPH_BASE + 0x2C00)
#define SPI1_BASE (APB2PERIPH_BASE + 0x3000)
/*!< FSMC Bankx registers base address */
#define FSMC_Bank1_R_BASE (FSMC_R_BASE + 0x0000)
#define FSMC_Bank1E_R_BASE (FSMC_R_BASE + 0x0104)
#define FSMC_Bank2_R_BASE (FSMC_R_BASE + 0x0060)
#define FSMC_Bank3_R_BASE (FSMC_R_BASE + 0x0080)
#define FSMC_Bank4_R_BASE (FSMC_R_BASE + 0x00A0)
今天分享的内容就到这里。结合我自己理解的同时,查阅了大量的相关文档。本人能力有限,难免存在疏漏之处。如有问题,欢迎指正。
如果觉得这篇文章对你有帮助的话,欢迎点赞、收藏、留言、关注。让我们一起学习,共同进步!也欢迎关注我个人公众号:电子工程师联盟。一起学习,共同进步!