STM32(F407)—— 堆栈

目录

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),每个block512Mbyte,如下图所示。

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 (CodeRO-data) = 12876 (  12.57kB)

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

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

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

不知道大家注意到 HEAP 和 STACK 的起始地址分别 0x2000 55380x2000 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:3Reserved
2

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 指针的值,例如,在多任务系统的上下文切换。


参考资料

  1. STM32F407数据手册 —— https://www.st.com/resource/en/datasheet/stm32f405rg.pdf
  2. STM32F407参考手册
  3. Cortex‐M3 权威指南(中文) 
对于在STM32F407上实现流水灯,你可以按照以下步骤进行操作: 1. 首先,需要配置GPIO引脚为输出模式,并设置相应的引脚电平。可以使用STM32CubeMX工具来生成初始化代码,或者手动编写代码进行配置。 2. 创建一个循环,在循环中依次将各个引脚置高电平,然后延时一段时间,再依次将引脚置低电平,再延时一段时间。 3. 重复上述步骤,就可以实现流水灯效果。 下面是一个简单的示例代码,演示了如何在STM32F407上实现流水灯效果: ```c #include "stm32f4xx.h" #include "stm32f4xx_hal.h" // 定义LED引脚 #define LED_PIN_1 GPIO_PIN_12 #define LED_PIN_2 GPIO_PIN_13 #define LED_PIN_3 GPIO_PIN_14 #define LED_PIN_4 GPIO_PIN_15 // 定义延时时间 #define DELAY_MS 500 int main(void) { // 初始化HAL库 HAL_Init(); // 初始化GPIO库 __HAL_RCC_GPIOA_CLK_ENABLE(); GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.Pin = LED_PIN_1 | LED_PIN_2 | LED_PIN_3 | LED_PIN_4; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); // 循环流水灯 while (1) { // 依次点亮LED HAL_GPIO_WritePin(GPIOA, LED_PIN_1, GPIO_PIN_SET); HAL_Delay(DELAY_MS); HAL_GPIO_WritePin(GPIOA, LED_PIN_2, GPIO_PIN_SET); HAL_Delay(DELAY_MS); HAL_GPIO_WritePin(GPIOA, LED_PIN_3, GPIO_PIN_SET); HAL_Delay(DELAY_MS); HAL_GPIO_WritePin(GPIOA, LED_PIN_4, GPIO_PIN_SET); HAL_Delay(DELAY_MS); // 依次熄灭LED HAL_GPIO_WritePin(GPIOA, LED_PIN_1 | LED_PIN_2 | LED_PIN_3 | LED_PIN_4, GPIO_PIN_RESET); HAL_Delay(DELAY_MS); } } ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值