打印
[其他ST产品]

【转载】学STM32单片机,要理解它的堆栈

[复制链接]
287|16
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
学习 STM32 单片机的时候,总是能遇到 “堆栈” 这个概念。本文来分析一下堆栈相关的知识内容,希望对你的理解有帮助。
对于了解一点汇编编程的人,就可以知道,堆栈是内存中一段连续的存储区域,用来保存一些临时数据。堆栈操作由 PUSH、POP 两条指令来完成。而程序内存可以分为几个区:
  • 栈区(stack)
  • 堆区(Heap)
  • 全局区(static)
  • 文字常亮区
  • 程序代码区

程序编译之后,全局变量,静态变量已经分配好内存空间。在函数运行时,程序需要为局部变量分配栈空间,当中断来时,也需要将函数指针入栈,保护现场,以便于中断处理完之后再回到之前执行的函数。


栈是从高到低分配,堆是从低到高分配。普通单片机与STM32单片机中堆栈的区别

普通单片机启动时,不需要用 bootloader 将代码从 ROM 搬移到 RAM。


但是STM32单片机需要,可以参考相关文章:STM32启动详细流程分析。这里我们可以先看看单片机程序执行的过程,单片机执行分三个步骤:
  • 取指令
  • 分析指令
  • 执行指令

根据 PC 的值从程序存储器读出指令,送到指令寄存器。然后分析、执行。这样单片机就从内部程序存储器取代码指令,从RAM 存取相关数据。

RAM 取数据的速度是远高于 ROM 的,但是普通单片机因为本身运行频率不高,所以从 ROM 取指令慢并不影响。


而 STM32 的 CPU 运行的频率高,远大于从 ROM 读写的速度。所以需要用 bootloader 将代码从 ROM 搬移到 RAM(请注意,这部分表述存在问题)。使用就象我们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,他的好处是快捷,但是自由度小。使用就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且自由度大。其实堆栈就是单片机中的一些存储单元,这些存储单元被指定保存一些特殊信息,比如地址(保护断点)和数据(保护现场)。如果非要给他加几个特点的话那就是:
  • 这些存储单元中的内容都是程序执行过程中被中断打断时,事故现场的一些相关参数。如果不保存这些参数,单片机执行完中断函数后就无法回到主程序继续执行了。
  • 这些存储单元的地址被记在了一个叫做堆栈指针(SP)的地方。

使用特权

评论回复
沙发
麻花油条|  楼主 | 2023-2-3 10:50 | 只看该作者
结合STM32的开发讲述堆栈从上面的描述可以看得出来,在代码中是如何占用堆和栈的。可能很多人还是无法理解,这里再结合 STM32 的开发过程中与堆栈相关的内容来进行讲述。如何设置STM32的堆栈大小?在基于MDK的启动文件开始,有一段汇编代码是分配堆栈大小的。

这里重点知道堆栈数值大小就行。还有一段 AREA(区域),表示分配一段堆栈数据段。数值大小可以自己修改,也可以使用 STM32CubeMX 数值大小配置,如下图所示。



STM32F1 默认设置值 0x400,也就是 1K 大小。

view plaincopy to clipboardprint?

  • Stack_Size  EQU  0x400

函数体内局部变量:

view plaincopy to clipboardprint?

  • void Fun(void)
  • {
  •    char i;
  •    int Tmp[256]; //...
  • }

局部变量总共占用了 256*4 + 1 字节的栈空间。所以,在函数内有较多局部变量时,就需要注意是否超过我们配置的堆栈大小。

函数参数:

view plaincopy to clipboardprint?

  • void HAL_GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_Init)

这里要强调一点:传递指针只占 4 字节,如果传递的是结构体,就会占用结构大小空间。提示:在函数嵌套,递归时,系统仍会占用栈空间。

堆(Heap)的默认设置 0x200(512)字节。

view plaincopy to clipboardprint?

  • Heap_Size  EQU  0x200

大部分人应该很少使用 malloc 来分配堆空间。虽然堆上的数据只要程序员不释放空间就可以一直访问,但是,如果忘记了释放堆内存,那么将会造成内存泄漏,甚至致命的潜在错误。

MDK中RAM占用大小分析经常在线调试的人,可能会分析一些底层的内容。这里结合 MDK-ARM 来分析一下RAM 占用大小的问题。在 MDK 编译之后,会有一段 RAM 大小信息:


这里 4+1636=1640,转换成16进制就是 0x668,在线进行调试时,会出现:

这个 MSP 就是主堆栈指针,一般我们复位之后指向的位置,复位指向的其实是栈顶:

而MSP指向地址 0x20000668 是 0x20000000 偏移 0x668 而得来。具体哪些地方占用了 RAM,可以参看 map 文件中【Image Symbol Table】处的内容:

转载自网络,如有侵权,联系删除。


使用特权

评论回复
板凳
zljiu| | 2023-3-4 14:59 | 只看该作者
st的这种启动时需要用 bootloader 将代码从 ROM 搬移到 RAM的方式 有哪些优势

使用特权

评论回复
地板
tpgf| | 2023-3-4 15:25 | 只看该作者
zljiu 发表于 2023-3-4 14:59
st的这种启动时需要用 bootloader 将代码从 ROM 搬移到 RAM的方式 有哪些优势

我觉得这样做保证代码不会容易被更改 是吗

使用特权

评论回复
5
nawu| | 2023-3-4 15:41 | 只看该作者
他的这种特殊的方式会限制代码的大小吗

使用特权

评论回复
6
coshi| | 2023-3-4 16:07 | 只看该作者
弱弱的问一下 可以使用外挂的只读存储器吗

使用特权

评论回复
7
磨砂| | 2023-3-4 16:21 | 只看该作者
coshi 发表于 2023-3-4 16:07
弱弱的问一下 可以使用外挂的只读存储器吗

我觉得外挂的只读存储器就不能叫rom了吧?

使用特权

评论回复
8
晓伍| | 2023-3-4 16:33 | 只看该作者
这种不同 会影响我们正常的编程吗  还是说体现在代码的执行效率上啊

使用特权

评论回复
9
Clyde011| | 2024-5-1 07:08 | 只看该作者

电源电压处于1.6V到5.5V之间

使用特权

评论回复
10
公羊子丹| | 2024-5-1 08:01 | 只看该作者

电压范围称为工作电源电压

使用特权

评论回复
11
万图| | 2024-5-1 09:04 | 只看该作者

内部电路工作电圧是通过内部电压调节器调节电源电压得到的

使用特权

评论回复
12
Uriah| | 2024-5-1 10:07 | 只看该作者

单片机的外部都连接有象电池等电源部分

使用特权

评论回复
13
Bblythe| | 2024-5-1 13:06 | 只看该作者

主时钟振荡器主要用作CPU的工作时钟

使用特权

评论回复
14
周半梅| | 2024-5-1 15:02 | 只看该作者

防止因瞬间大电流引起的电源电压下降

使用特权

评论回复
15
Pulitzer| | 2024-5-1 16:05 | 只看该作者

要在外部连接一个振荡电路提供时钟信号

使用特权

评论回复
16
童雨竹| | 2024-5-1 18:01 | 只看该作者

与15号引脚连接的C1称为旁路电容

使用特权

评论回复
17
Wordsworth| | 2024-5-1 19:04 | 只看该作者

时序电路是按时钟信号(CK)的上升沿(信号从L→H的变化)或下降沿(信号从H→L的变化)同步工作的

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

277

主题

1438

帖子

2

粉丝