细说BSS段SRAM清零与否
在大多数项目里面,我们并不需要关心变量在SRAM中的状态。当MCU每次上电复位后,我们编写的程序也都会给全局变量进行初始化,防止SRAM中的脏数据污染了我们的变量,导致程序异常跑飞等。不是这个技术知识点不重要,而是Keil等编译工具链已经为我们准备好了一套常用、稳定的SRAM内存初始化策略。不过,也有一些时候,我们更希望SRAM的使用更贴近项目,依项目背景而定制!这也是本帖子的分享目的。
Keil中的BSS段
在Keil编译环境下,BSS段在编译后记录着”未初始化或显式初始化为0的全局变量与静态变量“。这些内容需要在MCU上电复位后由MCU进行清零操作,以保证获得确定的0值。
Keil启动流程
我们以我平时常用的STM32F407VET6芯片为例。它在上电复位后,第一步的操作是初始化堆栈指针,然后就开始跑SystemInit(),紧接着就是跳转到__main()函数。而在__main()函数里面会把所有RAM清空,再加载全局变量和静态变量,之后MCU才会跑到我们自已编写的main()函数里面。我们查看startup_stm32f407xx.s文件就可以在复位回调函数里面看到:
; Reset handler Reset_Handler PROC EXPORT Reset_Handler [WEAK] IMPORT SystemInit IMPORT __main LDR R0, =SystemInit BLX R0 LDR R0, =__main BX R0 ENDP
Keil中的BSS段不被清零的方法
在默认情况下,BSS段在上电复位的时候会被MCU清零,但项目需求要求在上电复位时无需清零,这时该怎么办呢?Keil编译工具提供了两种方法来定制化解决这个问题。
编译器GUI设置
打开Keil编译器Option设置,将IRAM区域中的NoInit勾选,即表示在__main函数中此块IRAM区域不会被强制初始化为零。

我这里为示意,把全部的IRAM空间都列为了”非初始化“,大家按自已的项目需求而设定。
特别注意:
用此种方法需要勾选Use Memory Layout from Target Dialog选项,否则无效。后面我们也会讲到。
RAM1-3是片外RAM,下方的IRAM1-2才是片上RAM。不要设置错误与混淆。
sct分散加载文件
刚刚的GUI配置方法设置非常简单,但它有它的局限性——它只能配置两块IRAM区域。这在我们实际项目应用中,很难满足定制化与复杂的项目需求。这时就需要依靠sct分散加载文件了。
对于修改sct文件来关闭RAM初始化清零的操作也非常简单,只需要在RAM的定义声明上面添加UNINIT关键字即可。
; *************************************************************
; *** Scatter-Loading Description File generated by uVision ***
; *************************************************************
LR_IROM1 0x08005000 0x0001B000 { ; load region size_region
ER_IROM1 0x08005000 0x0001B000 { ; load address = execution address
*.o (RESET, +First)
*(InRoot$$Sections)
.ANY (+RO)
.ANY (+XO)
}
RW_IRAM1 0x20002000 UNINIT 0x00020000 { ; RW data
.ANY (+RW +ZI)
}
}特别注意:
如果使用sct分散文件加载的方式,一定要把Use Memory Layout from Target Dialog选项勾选掉

实验验证
有了理论基础,又有硬件环境,所以一定要上手验证一下。于是我编写了下述代码:
uint8_t txbuf[16] = {0}; /* 全局变量数组 */
int main(void)
{
uint32_t i;
hw_init();
NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_3);
SysTick_Config(SystemCoreClock / 1000);
debug_out(txbuf, 16);
time_tick = 1000;
while(time_tick > 0)
{
;
}
for (i = 0; i < 16; i++)
{
txbuf[i] = i;
}
debug_out(txbuf, 16);
while (1)
{
}
}上述代码的执行逻辑如下:
先打印全局变量数组txbuf[16]的16字节数据内容
等待1秒时间间隔
再将全局变量数组txbuf[16]依次写入0-15
再打印全局变量数组txbuf[16]的16字节数据内容
根据上面的理论学习,如果我未勾选NoInit的话,全局数组txbuf[16]应该会在上电复位后由编译器对其清零操作。而勾选NoInit后,全局数组txbuf[16]在上电复位后极可能是脏数据。实验结果会与结论保持一致吗?一起来看看我的串口打印内容

串口打印内容一共分为6行:
第1行为未勾选NoInit时的上电打印数据,可以看到全部为0,即编译器已经将其做了清零操作;
第2行为手动初始化后打印的结果,也符合预期;
第3行为勾选NoInit时的上电打印数据,果然没有初始化清零操作了,看上去就是脏数据;
第4行为手动初始化后打印的结果,符合预期;
第5行为按reset按键后程序重新运行的打印数据。可以看到是上一次初始化的结果,再次验证了编译器未清零操作;
第6行为手动初始化后打印的结果,符合预期;
总结
在一些无须清零RAM的应用场景,或者需要保留RAM的应用场景下,Keil提供给了程序员控制BSS段的RAM清零与否的有效手段。大家根据自已的项目需求而参与设计实现吧!
我要赚赏金
