如题
//hal_systick_config_t cfg =
//{
// .clk_source = SYSTICK_CLK_HCLK_DIV8,
// .reload_value = 21000,
// .sys_clk_frequency = 168,
// .tick_callback = timer_ticks_count
//};
static void systick_init(void)
{
hal_systick_config_t cfg =
{
.clk_source = SYSTICK_CLK_HCLK_DIV8,
.reload_value = 21000,
.sys_clk_frequency = 168,
.tick_callback = timer_ticks_count
};
hal_systick_init(&cfg);
}
这个代码中,只要cfg结构体变量放到函数外面一切正常,只要放在函数内部则进入debug模式后需要三次run之后程序才会执行,把栈空间调大依然会出现这个问题
如何解决?
涉及到的知识点
一、堆栈初始值计算公式
STM32 的堆栈起始地址(即 SP 初始值)由以下公式决定:
SP_初始值=SRAM起始地址+RW_Data大小+ZI_Data大小+Stack_Size
SRAM起始地址:STM32F407 的 SRAM 起始地址为 0x20000000
RW_Data(可读写数据):已初始化的全局变量和静态变量
ZI_Data(零初始化数据):未初始化的全局变量和静态变量(默认填充为0)
Stack_Size:启动文件中定义的栈大小(您设置为 0x400,即 1KB)
二、现象解释
在您的案例中,SP 初始值为 0x20000900 的具体计算逻辑如下:
默认内存布局:
SRAM 起始地址:0x20000000
程序中的全局变量(RW+ZI)总大小:假设为 0x900 字节
栈大小(Stack_Size):0x400 字节(1KB)
则 SP 初始值 = 0x20000000 + 0x900 + 0x400 = 0x20000D00
但实际观察到的 SP 值为 0x20000900,这表明 RW+ZI 实际仅占用 0x500 字节(0x20000900 - 0x20000000 - 0x400 = 0x500)。
堆栈生长方向:
STM32 的栈是 向下生长 的,SP 初始值指向栈顶(高地址),栈底为 SP - Stack_Size。因此:
栈顶地址:0x20000900
栈底地址:0x20000900 - 0x400 = 0x20000500
栈空间范围:0x20000500 ~ 0x20000900。
根据.map文件分析,可以得出以下关于栈溢出问题的结论:
一、栈空间配置分析
栈大小设置
在startup_stm32f40_41xxx.o中明确显示:
STACK 0x20000500 0x00000400 (1KB)
这与用户设置的0x400(1024字节)完全一致。
内存布局验证
栈的地址范围:0x20000500 ~ 0x20000900
堆的地址范围:0x20000300 ~ 0x20000500(512字节)
全局变量(ZI + RW Data):总占用2304字节(RW_IRAM1区域)。
二、栈溢出风险判断
栈使用量估算
根据Image component sizes,ZI Data(零初始化数据)为2268字节,RW Data为36字节,总占用2304字节。
关键点:ZI Data包含全局变量和静态变量,而栈和堆的地址范围与ZI/RW区域相邻。如果函数调用链中的局部变量过多,可能导致栈指针(SP)超出0x20000900,覆盖其他内存区域。
调试现象关联
用户提到“变量放在函数内部时需要多次运行才成功”,这与栈溢出的典型现象(随机性崩溃、HardFault)高度吻合。局部变量存储在栈中,若超过0x400限制,会破坏中断向量表或关键数据,导致程序异常。
溢出检测方法
静态分析:检查函数调用层级和局部变量总大小。例如,若某函数定义了char buffer[1024],直接占满栈空间,必然溢出。
动态验证:在Keil调试器中观察SP寄存器值是否超出0x20000900,或通过Memory窗口查看栈内存是否被意外改写。
记录遇到的坑
1、函数指针一定不能跑飞,加上保护,防止程序跑飞
2、局部变量或者局部数据,尤其局部大数组一定要static化,或者直接全局化,防止堆栈溢出
3、利用三元运算符替代if逻辑,程序看起来简洁优雅
4、巧用#ifndef HAL_STATUS_T_DEFINED
#define HAL_STATUS_T_DEFINED
命令来解决重复定义