使用Keil 5编写程序应对单片机flash内存不足的方法

290 阅读4分钟

一、前言

        在我们开发编程时,随着对一个项目进行的功能扩展,我们编写的程序占用大小会越来越大,这个时候会出现程序体积过大引发的问题,如下举例,这篇文章将记录我总结的优化的一些方法。

  • Flash 空间吃紧(无法烧录)
  • 编译出错(代码超出 ROM)
  • RAM 使用不当时引发堆栈冲突(数组值混乱,申请内存不成功等等)
  • 更新困难,功能扩展受限

二、优化程序代码

        这个时候可以优化程序代码来减少程序内存的占用,这里强烈推荐、重复代码封装函数和静态替代 malloc,具体优化方法如下:

  1. 封装重复逻辑为函数

  2. 使用 const 常量数组代替硬编码数据,示例

usart_send(0x31);
usart_send(0x32);
usart_send(0x33);
usart_send(0x34);
usart_send(0x35);
//改写为
const uint8_t msg[] = "12345";
for (int i = 0; i < sizeof(msg)-1; i++) {
    usart_send(msg[i]);
}
  1. 用宏/函数统一初始化逻辑
//例如GPIO
#define INIT_GPIO_OUTPUT(port, pin) \
    gpio_mode_set(port, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, pin); \
    gpio_output_options_set(port, GPIO_OTYPE_PP, GPIO_OSPEED_10MHZ, pin)

  1. 减少 floatdouble 的使用(特别是 printf)
//使用 int 或 fixed-point 运算替代浮点运算
//避免在 printf() 中使用 %f,会引入 大量浮点格式支持代码

printf("value = %f\n", my_float);
//改写为
printf("value = %d.%03d\n", (int)val, (int)((val - (int)val) * 1000));

  1. 删除未用函数、变量、printf 日志等调试代码(这个自己看着来)

  2. 合理使用 static inline(避免小函数调用开销),让编译器展开代码(权衡空间与效率)

三、Keil 5编译器优化选项设置

        在代码上没有什么可以优化的地方就可以使用编译器的优化功能,设置不同的优化等级可以节省不同大小的内存,各个等级有自己的特点,针对现阶段选择就行。

1、编译器优化选项设置在哪里选择?打开设置然后选择C/C++如图:

fd20c5a265aaab1851b8048290f6643.png

c947654ef1aa53d6f0bdb385674f867.png

2、编译等级分类:我的编译器版本是6,如上图,编译优化选项有默认、O0、O1、O2、O3、Ofast、Os balanced、Oz image size、Omax

3、编译优化等级说明:

等级含义特点
默认(Default)通常相当于 O0 或 O1,取决于项目设置稳定但没有特别优化性能或大小
O0无优化(No optimization)编译速度最快、调试体验最好。代码最慢、体积最大
O1轻优化(Some optimization)适度优化性能,保持较好调试能力
O2中等优化(More optimization)平衡性能和代码大小,是常用等级
O3高优化(Full optimization)最大性能优化,可能会打乱函数结构,不适合调试
Ofast极致性能优化(Aggressive)启用所有 O3 优化并加入违反 IEEE 或标准行为的优化(如去除 NaN 检查等)
Os balanced平衡优化和代码体积(Size and Speed)介于 O2 与 Oz 之间,适用于资源受限但又有性能要求的系统
Oz image size最小体积优化最大限度压缩代码体积,适合 ROM 空间紧张的嵌入式系统
OMax厂商预定义最大优化级别(Keil 特有)实际等价于 Ofast 或 O3+Os,根据目标 CPU 平台特化优化策略

四、优化等级实测 

1、怎么看程序大小?如下,圈出的一行:

35893be878dd1516f4b8652f3880f85.png 含义如下:

字段名全称含义存储区域是否占Flash?是否占RAM?
CodeCode Size代码段:函数、指令等编译后的机器码Flash✅ 是❌ 否
RO-dataRead-Only Data只读数据段:如 const 常量、字符串常量等Flash✅ 是❌ 否
RW-dataRead-Write Data已初始化全局/静态变量(如 int a = 5;Flash(初始化值) & RAM(实际运行)✅ 是✅ 是
ZI-dataZero-Initialized Data未初始化全局/静态变量(如 int b;RAM(运行时置 0)❌ 否✅ 是

Flash 总占用 ≈ Code + RO-data + RW-data
RAM 总占用 ≈ RW-data + ZI-data 

 2、再多说明不如来一次实际测试,数据如下(Os和Oz很容易跑飞,谨慎选择):

等级程序大小
默认(Default)Program Size: Code=39036 RO-data=3860 RW-data=76 ZI-data=2580  
O0Program Size: Code=39036 RO-data=3860 RW-data=76 ZI-data=2580  
O1Program Size: Code=33556 RO-data=2004 RW-data=76 ZI-data=2580  
O2Program Size: Code=38484 RO-data=2080 RW-data=76 ZI-data=2580  
O3Program Size: Code=41140 RO-data=2128 RW-data=76 ZI-data=2580  
OfastProgram Size: Code=41052 RO-data=2128 RW-data=76 ZI-data=2580  
Os balancedProgram Size: Code=31124 RO-data=2004 RW-data=76 ZI-data=2580  
Oz image sizeProgram Size: Code=30372 RO-data=1996 RW-data=76 ZI-data=2580  
OMaxProgram Size: Code=51140 RO-data=2496 RW-data=20 ZI-data=2580