STM32(F407)

您所在的位置:网站首页 堆栈是如何操作的呢 STM32(F407)

STM32(F407)

2024-07-12 11:32| 来源: 网络整理| 查看: 265

目录

1. SRAM

2. 堆栈的作用

3. 堆栈的设置

4. 堆栈的实现 

5. 双堆栈机制

堆栈是一种数据结构。堆栈都是一种数据项按序排列的数据结构,只能在一端(称为栈顶(top))对数据项进行插入和删除,相应地,另一端为成为栈底(bottom),不含元素的空表称为空栈。

其实堆栈是由栈(Stack)和堆(Heap)组成的,汇编中应用的 PUSH 和 POP 就是对 栈(Stack)的操作,其按照后进先出(LIFO-Last In First Out)的原理运作。​

笼统地讲,堆栈操作就是对内存的读写操作,但是其地址由 SP 给出。

1. SRAM

STM32的内存(存储器)的地址空间大小为4G(0x0000 0000 ~ 0xFFFF FFFF),被分为8个block(block0~block7),每个block为512Mbyte,如下图所示。

SRAM 被分配到 block 1,内有SARM1(112KB,0x2000 00000 ~ 0x2001 BFFF)和SRAM2(16KB, 0x2001 C000 ~ 0x2001 FFFF)两块连续的SRAM,可供所有的AHB 主控总线访问。

不同系类单片机的 SRAM 是不一样的,但它们的起始地址都是0x2000 0000。为什么是两块SRAM?这是因为主总线支持并发SRAM访问,提高执行效率。例如当 CPU 对 112 KB SRAM 进行读/写操作时,以太网MAC 可以同时对 16 KB SRAM 进行读/写操作;或者CPU和DMA可以同时访问不同的SRAM。

STM32F40xxx 存储器映射

2. 堆栈的作用

栈(Stack):由编译器自动分配和释放。栈存放局部变量,函数调用时的返回地址和现场保护,函数的参数(形参)等。

堆(Heap):有程序员主动分配和释放。主要用于动态内存分配,malloc 申请,free 释放。

在函数中定义的局变量占用栈空间,当数据较多,栈空间使用完,则会占用堆空间,甚至其他全局变量空间,造成程序崩溃或数据错误(内存溢出错误)。

在不断申请、释放的过程中,容易产生碎片化内存;当然如果不是在短时间内频繁的使用 malloc 申请和 free 释放内存,系统是有足够的时间来回收碎片内存。

堆栈指针的最低两位永远是 0,这意味着堆栈总是 4 字节对齐的。

3. 堆栈的设置

这里我们以 STM32F407 的程序代码为例进行说明,找到启动文件 startup_stm32f407xx.s,设置栈大小为 2048(0x800),堆大小为 1024(0x400)。

 编译代码后,在 Build output 窗口中显示编译结果信息,如下图所示:

这里只是针对当前测试实例程序进行说明,但万变不离其宗,一样可以对照理解。

 Code:存储到Flash(ROM)中的程序代码,显示占用了 12372  个字节。RO-data:只读数据(Read Only data),被存储到ROM中的数据,也就是常量等不能被程序修改的数据;例如 const 修饰的变量。RW-data:可读写数据(Read Write data),在程序编写时已经初始化初始值。ZI-data:(Zero Initialized data),没有初始化的变量,被编译器初始化为0;(使用了RTOS)

Total RO  Size (Code + RO-data) = 12876 (  12.57kB)

Total RW  Size (RW-data + ZI-data)  = 24888 (  24.30kB)

Total ROM Size (Code + RO-data + RW-data)  = 12984 (  12.68kB)

编译成功后会在工程文件下生成一个 xxxx.map 文件,这个文件的详细讲解网上很多,请百度一下即可;如图红框所示,STACK 和 HEAP 的大小和我们所设置的一样,

不知道大家注意到 HEAP 和 STACK 的起始地址分别 0x2000 5538 和 0x2000 5938,这个其实是有编译器决定的,是不固定的;起始地址后面紧跟的就是HEAP 和 STACK 的大小(size)。

若程序中完全没有使用 malloc 动态申请堆(HEAP)空间,编译器会优化,不把堆空间计算在内。

最后,这里再看下上面实例的MSP(R13,主堆栈指针)和PC(R15,程序计数器)初始化流程图:

​​​​​​​

4. 堆栈的实现 

STM32使用的是“向下生长的满栈”模型,堆栈指针 SP 指向最后一个被压入堆栈的 32位数值。

堆栈指针指向最后压入的堆栈的有效数据项,称为满栈;堆栈指针指向下一个要放入的空位置,称为空栈。

为什么说“向上生长(向高地址方向生长)”和“向下生长(向低地址方向生长)”呢?那是因为,一般画堆栈示意图都是把低地址画在下面,高地址画在上面。如下图。

压栈(PUSH): SP 先自减 4,再存入新的数值。

出栈(POP):先从 SP 指针处读取上一次被压入的值,再把 SP 指针自增 4。

 虽然 POP 后被压入的数值还保存在栈中,但它已经无效了,因为为下次的 PUSH 将覆盖它的值!

5. 双堆栈机制

堆栈分为主堆栈(MSP)和进程堆栈(PSP),选择当前使用哪个堆栈指针由CONTROL寄存器(特殊功能寄存器)决定。

图5-1 特殊功能寄存器

当处理器处于线程模式时,控制寄存器控制所使用的堆栈和软件执行的特权级别,并指示 FPU 状态是否为活动的。

表5-1 Cortex-M4 的控制寄存器位定义 位(bits)功能(Function)31:3Reserved2

FPCA:指示当前浮点上下文是否活动:

0:不激活浮点上下文

1:表示浮点上下文活动。

Cortex-M4在处理异常时使用该位来决定是否保留浮点状态。

1

SPSEL:激活选择的堆栈指针。选择当前堆栈:

0:MSP是当前的堆栈指针

1:PSP是当前的堆栈指针。

在 Handle 模式下,该位读为零并忽略写。在异常返回时,Cortex-M4会自动更新此位。

0

nPRIV:线程模式权限级别。定义线程模式权限级别。

0:特权

1:无特权的。

在 Cortex‐M4 的 handler 模式中, CONTROL[1](CONTROL寄存器的bit[1])总是 0(Handle 模式下总是使用 MSP)。

当 CONTROL[1]=0 时,只使用 MSP,此时用户程序和异常 handler 共享同一个堆栈。这也是复位后的缺省使用方式。  

CONTROL[1]=0 时的堆栈使用情况

当 CONTROL[1]=1 时,线程模式将不再使用 PSP,而改用 MSP( handler 模式永远使用 MSP)。  

CONTROL[1]=1 时的堆栈使用情况

注意,在这种情况下,进入异常时的自动压栈使用的是进程堆栈,进入异常 handler 后才自动改为 MSP,退出异常时切换回 PSP,并且从进程堆栈上弹出数据。

在特权级下,可以直接对 MSP 和 PSP 执行读/写操作,而不会混淆你所引用的R13,示例代码如下:

MRS R0, MSP ; 读取主堆栈指针到 R0

MSR MSP, R0 ; 写入 R0 的值到主堆栈中

MRS R0, PSP ; 读取进程堆栈指针到 R0

MSR PSP, R0 ; 写入 R0 的值到进程堆栈中

通过MRS指令读取 PSP 的值,OS(操作系统) 可以读取用户应用程序堆积的数据(比如在系统服务调用前的寄存器内容,SVC)。此外,OS 可以改变 PSP 指针的值,例如,在多任务系统的上下文切换。

参考资料

STM32F407数据手册 —— https://www.st.com/resource/en/datasheet/stm32f405rg.pdfSTM32F407参考手册Cortex‐M3 权威指南(中文) 


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3