Cortex-M4的内存系统
Cortex-M4采用的是哈佛总线结构,指令和数据的访问可以并行实现。 它实际使用的是一种基于AMBA(Advanced Microcontroller Bus Architecture)的AHB(AMBA High-performance Bus) Lite的通用总线接口协议。 这是一种事实上的总线标准,可以通过不同的总线控制器接入8/16/32位的设备。 地址总线有32位,可以访问4GB的地址空间,但实际上很多嵌入式系统并不需要这么大的内存。 这就给具体的MCU实现留下了很大的自定义空间。
Cortex-M4支持大端和小端两种存储方式,但是我常用的STM32系列的处理器只支持小端存储。
1. 内存映射
处理器的内存系统通常会涉及到两种甚至更多的内存形式:用于烧录程序代码的闪存(flash memory), 用于数据处理的SRAM,有时也会有EEPROM。在大多数情况下,这些不同种类的内存都是共存在处理器芯片上的。 因此,在对处理器编程时需要知道程序空间和SRAM空间的起始地址和大小。
事实上,内核的4GB地址空间被划分为了很多小的区间映射到了各种不同的外设和内存中。 图1中记录了Cotex-M4的内存划分情况,这种划分对于所有的Cortex-M4处理器都适用,总体上可以分为以下几类:
- 用于存储程序代码的CODE空间;
- 用于数据存储的静态内存SRAM;
- 外设空间Peripherals;
- 可扩展的外部存储空间(RAM)和外设空间(Devices);
- 处理器的内置中断处理器(NVIC)和调试工具的System/Vector空间。
图1 Cortex-M4的内存映射(其中的阴影部分主要用于DEBUG) |
实际上,不同的芯片只用了其中的一部分, 具体各个区间的使用情况和外设的地址映射可以参考芯片厂商提供的数据手册(Datasheet)和参考手册(reference manual)。
处理器内核通过不同的总线访问,这些区域,如图2所示。前0.5GB的CODE空间由I-CODE和D-CODE两个总线访问, 它们是哈佛架构的体现可以同时获取指令和数据,因此在CODE空间中代码的执行速度要比其它空间中的代码要快。 此外I-CODE还用于触发中断时查询中断向量,这一操作可以和上下文寄存器压栈操作一起进行,提高了中断的响应速率。 处于以上两点的考虑,一般我们把代码放在CODE段中执行。当然,我们也可以在SRAM和RAM中装载代码, 只是由System单总线的访问形式要相对慢一些。此外,在Peripheral、Device、System/Vector空间中是不能执行代码的。
图2 连接内核与各个内存区域的各种总线接口 |
2. 栈空间
在介绍Cortex-M4的寄存器时, 我们提到M4有一个栈空间寄存器R13,专门用来指示系统的栈空间。 栈空间是中断处理,函数调用等上下文切换机制所必需的内存空间。 所谓的栈是一种特殊的内存访问机制,新的数据总是存放在栈指针标志的栈顶位置,称之为入栈; 而从栈中取数据也总是从栈顶开始,称之为出栈。 表现出来的效果就是,最后入栈的数据先被取出,即Last In First Out(LIFO)。
对于ARM处理器,提供了PUSH指令把数据入栈,POP指令出栈。 Cortex-M系列的处理器的栈空间模型被称为"full-descending stack"。 处理器复位后,栈指针SP指向保留作栈空间的最高地址。 每次执行PUSH指令,先减小SP中的地址,再把数据存放在SP指示的位置。 这样将始终保证SP指向栈空间中的第一个空内存,数据总是存放在栈顶。 而执行POP指令时,则先读出栈顶的数据,然后增加地址指针SP。这一过程如图3所示。
图3 PUSH和POP的操作过程 |
一般情况下,PUSH和POP指令是成对使用的,而且两者对SP的影响应当是对称的, 即PUSH指令入栈了多少数据,POP指令就应当出栈相同大小的数据。 Cortex-M4中,栈空间总是4字节对齐的,也就是说SP指针的低两位数据总是0。