目录
一、关于单片机堆栈的基础知识
1、STM32程序数据分类
- Code:程序代码
- RO-data:const常量和指令
- RW-data:初始化值不为0的全局变量
- ZI-data:未初始化的全局变量 或 初始化值为0的全局变量
RO Size = Code + RO Data 表示程序运行时占用的FLASH大小
RW Size = RW Data + ZI Data 表示占用RAM大小
ROM Size = Code + RO Data + RW Data 表示烧写程序后占用的FLASH大小
2、STM32内存(RAM)分配
一个由C/C ++编译的程序占用的内存可分为以下几个部分:
- 栈(stack):由编译器自动分配释放,存放函数的参数值,局部变量的值等。
- 堆(heap):存放程序运行中被动态分配的内存,一般由程序员分配释放,若程序员不释
放,程序结束时可能由OS回收。
- bss段:通常是指用来存放程序中未初始化的全局变量的一块内存区域,存放ZI-data数据
- data段:通常是指用来存放程序中已初始化的全局变量的一块内存区域,存放RW-data数据
FLASH占用大致以下两个部分:
- 文字常量区(const) :常量字符串就是放在这里的。
- 程序代码区 (code): 存放函数体的二进制代码
3、经典例子分析:
int a = 0; //全局初始化区
char *p1; //全局未初始化区
main()
{
int b; //栈
char s[] = "abc"; //栈
char *p2; //栈
char *p3 = "123456"; //123456\0在常量区,p3在栈上。
static int c =0;//全局(静态)初始化区
p1 = (char *)malloc(10);
p2 = (char *)malloc(20); //分配得来得10和20字节的区域就在堆区。
strcpy(p1, "123456"); //123456\0放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。
}
在MDK编译环境下,可在map文件的"Memory Map of the image"-->"Execution Region RW_IRAM1"内容中查看程序的RAM占用及分配情况,如下【图一】【图二】
【图一】
【图二】
4、STM32堆栈位置
首先看一下STM32的地址空间映射【图三】,STM32的堆栈就是存放在片上静态SRAM中的,地址分配可以见Keil的编译map文件的"Memory Map of the image"【图四】;可见堆的地址为0x20000a08,大小为0x200,栈的地址为 0x20000c08,大小为0x400,可推算栈顶地址为:0x20000c08 + 0x400 = 0x20001008。而程序在刚运行的时候,主堆栈指针MSP指向的是程序所占用内存的最高地址【图五】,也就是栈的栈顶地址。
【图三】
【图四】
【图五】
5、STM32栈增长方式
STM32的栈增长方式是向下增长的,也就是程序运行后,SP指针从栈顶地址开始往下给函数的局部变量分配地址 。可以通过如下代码来测试STM32单片机的栈增长方式。
//保存栈增长方向 0,向下增长;1,向上增长.
static unsigned char stack_dir;
//查找栈增长方向,结果保存在stack_dir里面.
void find_stack_direction(void)
{
static u8 *addr = NULL; //用于存放第一个dummy的地址。
u8 dummy; //用于获取栈地址
if(addr == NULL) //第一次进入
{
addr = &dummy; //保存dummy的地址
find_stack_direction (); //递归
}
else //第二次进入
{
if(&dummy > addr)stack_dir=1; //第二次dummy的地址大于第一次dummy,那么说明栈增长方向是向上的.
else stack_dir=0; //第二次dummy的地址小于第一次dummy,那么说明栈增长方向是向下的.
}
}
三、如何设置STM32的堆栈大小
1、MDK编译环境下
通过修改.s启动文件中的 Stack_Size 和 Heap_Size大小
2、IAR 编译环境下
通过修改Linker-->Config中的CSTACK和HEAP大小
四、STM32单片机程序内存占用大小分析
map文件:通过编译器编译之后,集程序、数据及IO空间的一种映射文件。通过map文件可以知道函数大小,入口地址等一些重要信息。
1、MDK编译环境下
编译后的map文件大致有如下几个部分:在分析内存占用时主要看d、e段就行
a、Section Cross References:模块、段(入口)交叉引用
b、Removing Unused input sections from the image:移除未调用模块
c、Image Symbol Table:映射符号表
d、Memory Map of the image:内存(映射)分布,包括存储地址和运行地址分布
e、Image component sizes:存储组成大小,列出了各个文件中各种数据占用的内存大小
2、IAR编译环境下
编译后的map文件大致有如下几个部分:
a、MAIN INIF:关于map文件的主要信息。包含:IAR版本、日期、输出文件路径等
b、RUNTIME MODEL ATTRIBUTES:运行时MODEL属性
c、PLACEMENT SUMMARY:概述位置,即各Section(段)存储的位置
大致意思为:
"A0"段位于0x08000000,类型为intvec(初始化向量);
"P1"段位于0x08000000 至 0x0807ffff区域,类型为 ro;
"P2"段位于0x20000000 至0x2000ffff区域,类型包含:rw, block CSTACK;
段与类型一般有对应关系:
Section Kind
"A1":
.intvec ro code(ro代码)
"P1":
.text ro code(ro代码)
.rodata const (常量)
CODE ro code(ro代码)
.iar.init_table const(常量)
Initializer bytes ro data(ro数据)
"P2":
.data inited(已初始化数据)
.bss zero(未初始化数据 零)
d、INIT TABLE:初始化表
e、MODULE SUMMARY:概述模块,文件所占ro代码大小、rw数据大小
f、ENTRY LIST:入口列表,包含函数、变量等入口地址
h、最后就是程序所占用的code、RO-data、RW-data