如何优化代码和RAM大小
如果供应商为我自己的项目提供了一个起点,那就太好了。工作'眨眼'始终是一个伟大的首发。方便总是有代价,而且“眨眼”就是夸大“切换GPIO引脚”的代码大小。对于具有少量RAM和FLASH的设备,这可能会引起关注:如果'blinky'占用那么多,我的应用程序是否适合该设备?不要担心:可以轻松地修剪掉(或任何其他项目)。
恩智浦LPC845-BRK主板上的Binky
我在这里使用一个'blinky'项目作为一个例子:修剪技巧也适用于任何其他类型的项目。
在本教程中,我在BRK(突破)板上使用NXP LPC845:
恩智浦LPC845-BRK板
1、Blinky示例
我所使用的是基于Eclipse的NXP MCUXpresso IDE:
选择SDK板
我使用供应商默认设置创建了'blinky'项目:
Blinky项目
一个'blinky'应该闪烁一个LED,对任何项目来说都是一个好的开手机。构建相当小的项目,代码大小如下:
Memory region |
Used Size |
Region Size |
%age Used |
PROGRAM_FLASH: |
10536 B |
64 KB |
16.08% |
SRAM: |
2424 B |
16 KB |
14.79% |
text |
data |
bss |
dec |
hex |
filename |
10532 |
4 |
2420 |
12956 |
329c |
lpc845breakout_led_blinky.axf |
该信息也在控制台中显示,分为文本,数据和bss:
10K的'blinky'看起来有点夸张。但是我们现在将在接下来的步骤中修改它。
2、大小信息
有关大小信息的含义,请阅读“ text,data和bss:Code and Data Size Explained ”。查看我的设备上使用空间的正常方法是检查链接器映射文件(* .map):
链接器映射文件
但是这个map文件很难阅读,而且对于专家来说更是如此:它列出了具有地址和大小的部分:
链接器映射文件内容
使用MCUXpresso IDE V11,有一个很好的“图像信息”视图,它基本上是一个更好的ma'p文件信息查看器:
图像信息查看
我可以过滤和排序数据,这让我知道代码和数据使用了多少空间:
图像信息存储器内容
当然,它需要一些关于应用程序应该做什么的知识。我总是浏览视图中的项目列表,看看是否有任何我不希望的东西:也许应用程序正在使用可以删除的东西。
3、源代码
对于一个简单的眨眼,这是相当小的。首先要检查程序正在做什么。main.c有这个:
1 /* 2 * Copyright 2017 NXP 3 * All rights reserved. 4 * 5 * SPDX-License-Identifier: BSD-3-Clause 6 */ 7 8 #include "board.h" 9 #include "fsl_gpio.h" 10 11 #include "pin_mux.h" 12 /******************************************************************************* 13 * Definitions 14 ******************************************************************************/ 15 #define BOARD_LED_PORT 1U 16 #define BOARD_LED_PIN 2U 17 18 /******************************************************************************* 19 * Prototypes 20 ******************************************************************************/ 21 22 /******************************************************************************* 23 * Variables 24 ******************************************************************************/ 25 volatile uint32_t g_systickCounter; 26 27 /******************************************************************************* 28 * Code 29 ******************************************************************************/ 30 void SysTick_Handler(void) 31 { 32 if (g_systickCounter != 0U) 33 { 34 g_systickCounter--; 35 } 36 } 37 38 void SysTick_DelayTicks(uint32_t n) 39 { 40 g_systickCounter = n; 41 while (g_systickCounter != 0U) 42 { 43 } 44 } 45 46 /*! 47 * @brief Main function 48 */ 49 int main(void) 50 { 51 /* Define the init structure for the output LED pin*/ 52 gpio_pin_config_t led_config = { 53 kGPIO_DigitalOutput, 54 0, 55 }; 56 57 /* Board pin init */ 58 BOARD_InitPins(); 59 BOARD_InitBootClocks(); 60 BOARD_InitDebugConsole(); 61 62 /* Init output LED GPIO. */ 63 GPIO_PortInit(GPIO, BOARD_LED_PORT); 64 GPIO_PinInit(GPIO, BOARD_LED_PORT, BOARD_LED_PIN, &led_config); 65 66 /* Set systick reload value to generate 1ms interrupt */ 67 if (SysTick_Config(SystemCoreClock / 1000U)) 68 { 69 while (1) 70 { 71 } 72 } 73 74 while (1) 75 { 76 /* Delay 1000 ms */ 77 SysTick_DelayTicks(1000U); 78 GPIO_PortToggle(GPIO, BOARD_LED_PORT, 1u << BOARD_LED_PIN); 79 } 80 }
基本上,代码正在初始化引脚,时钟,设置SysTick定时器,然后在循环中执行'blinky',使用Systick计数器延迟闪烁周期。
4、调试控制台
但我可以看到它初始化一个调试控制台(以及它的UART硬件):
BOARD_InitDebugConsole();
去掉这些,我们就可以得到:
Memory region |
Used Size |
Region Size |
%age Used |
PROGRAM_FLASH: |
5616 B |
64 KB |
8.57% |
SRAM: |
2400 B |
16 KB |
14.65% |
在许多情况下,演示应用程序会设置一些通信通道,但之后就不会使用它们。链接器可以很好地删除未使用的对象(函数/变量),但前提是它们没有被引用。
5、半主控和printf()
接下来要看的是是否存在任何半主机或printf()。该项目正在使用'Redlib',这是一个优化的库,与'标准'newlib或较小标准的newlib-nano相比:
Redlib
尽管如此,该库可能会增加代码大小,因为它使用半主机(通过调试器发送消息)。查看Memory视图,我可以直接或间接地看到所需的所有这些标准I / O函数:
stdio功能
拥有该功能的所有钩子只有在使用它时才有意义,并且“blinky”不会使用它。因此,摆脱半主机和所有未使用的标准I / O意味着使用'none'变体:
没有标准I / O的库
这让我们了解到这一点:
Memory region |
Used Size |
Region Size |
%age Used |
PROGRAM_FLASH: |
3372 B |
64 KB |
5.15% |
SRAM: |
2208 B |
16 KB |
13.48% |
或者使用较小的变体或实现。有关此问题的更多背景信息,请参阅本文末尾的链接。
6、DEBUG和NDEBUG
接下来要检查编译器是否定义了列出的DEBUG。事实上,情况就是这样:
DEBUG定义
使用该定义集,SDK和示例驱动程序中有许多额外的代码,它们使用'assert()'宏检查好的值:
SDK代码中断言的用法
在这里,图像信息视图再次有用:它向我展示了使用assert()的所有地方:
断言用法
实际上,在代码中使用断言来尽早捕获编程错误是一种很好的做法。但是所有的assert()代码确实加起来了。要关闭额外的代码(和安全带!),我将宏更改为NDEBUG:
NDEBUG
这让我们了解到一点:
Memory region |
Used Size |
Region Size |
%age Used |
PROGRAM_FLASH: |
3144 B |
64 KB |
4.80% |
SRAM: |
2208 B |
16 KB |
13.48% |
7、中断和向量
图像信息视图再次是一个很好的起点。我正在检查使用过的中断。Blinky正在使用预期的SysTick中断。但是仍然使用UART中断?
使用中断
大多数中断都实现为“weak”:实现为默认/空,可以被应用程序覆盖。但UART没有意义,因为”blinky”没有使用任何UART通信?
事实证明,NXP SDK默认启用了UART事务API:
UART Transactional API设置
事务API允许在通信组块/事务中发送/接收UART数据。但我们不需要在我们的眨眼中,所以让我们把它关掉:
关闭UART Transactional API
这样一来,内存情况为:
Memory region |
Used Size |
Region Size |
%age Used |
PROGRAM_FLASH: |
2964 B |
64 KB |
4.52% |
SRAM: |
2184 B |
16 KB |
13.33% |
但我认为CMSIS(设置中断优先级,通用时钟设置)非常有用,所以我不在这里触摸它。应用程序中最大的功能是SysTick代码用来将定时器的优先级设置为最低优先级,以节省另外220个字节:
CMSIS作为最大的单一功能代码大小贡献者
8、优化
到目前为止,我已经删除了不需要的或未使用的功能。接下来我可以打开编译器优化。默认情况下,项目设置为-O0:
编译器优化
-O0表示无优化:代码直观且易于调试。
-O1主要优化函数进入/退出代码,并且能够在不影响调试的情况下减少代码大小。在这个例子中,它将代码大小减少了一半!
Memory region |
Used Size |
Region Size |
%age Used |
PROGRAM_FLASH: |
1540 B |
64 KB |
2.35% |
SRAM: |
2184 B |
16 KB |
13.33% |
-O2优化更多并尽可能地将事物保存在寄存器中。因为应用程序中的功能相当小,所以改进并不大:
Memory region |
Used Size |
Region Size |
%age Used |
PROGRAM_FLASH: |
1516 B |
64 KB |
2.31% |
SRAM: |
2184 B |
16 KB |
13.33% |
-O3通过额外的内联优化最佳。-O3的目标是速度,所以难怪代码大小再次增加:
Memory region |
Used Size |
Region Size |
%age Used |
PROGRAM_FLASH: |
1792 B |
64 KB |
2.73% |
SRAM: |
2184 B |
16 KB |
13.33% |
代码大小优化的最佳选择是-Os(针对大小进行优化):
Memory region |
Used Size |
Region Size |
%age Used |
PROGRAM_FLASH: |
1456 B |
64 KB |
2.22% |
SRAM: |
2184 B |
16 KB |
13.33% |
现在看起来很合理!当然现在有一些方法可以为“裸露的裸眼”切断更多,但是现有的一切(启动代码,时钟和GPIO初始化)对于真正的应用程序是有意义的,所以我现在停在这里。
9、RAM:堆和堆栈
看起来不正确的是SRAM的使用。'heap'使用了一大块:
堆内存使用情况
该堆用于动态内存分配(malloc())。嵌入式编程的一般规则是避免它。但它默认在这里。它可以在链接器设置中关闭:演示使用1K用于堆和堆栈。由于我没有使用malloc(),我可以将堆大小设置为0x0。对于真正依赖于应用程序的保留堆栈。在ARM Cortex上,MSP用于启动/主控和中断(参见“ ARM Cortex-M中断和FreeRTOS ”)。0x100(256字节)应该足够我的眨眼。
堆和堆栈大小
这让我了解到一点:
Memory region |
Used Size |
Region Size |
%age Used |
PROGRAM_FLASH: |
1456 B |
64 KB |
2.22% |
SRAM: |
392 B |
16 KB |
2.39% |
如果它是关于进一步减小堆栈大小,我可以查看调用图信息,它给出了有关使用多少堆栈空间的信息:
堆栈大小的图形显示
有一些项目的大小信息未知(标有“?”)因为它们在库中。验证实际堆栈使用情况的方法是编写模式(例如0xffff'ffff),然后运行应用程序一段时间:
使用的堆栈
这表明实际使用了72个字节。有一点余地,在这种情况下将堆栈大小设置为128字节看起来是合理的。这给出了:
Memory region |
Used Size |
Region Size |
%age Used |
PROGRAM_FLASH: |
1456 B |
64 KB |
2.22% |
SRAM: |
264 B |
16 KB |
1.61% |
堆栈溢出可能是嵌入式应用程序中最常见的问题。如果可以的话,可以为堆栈提供尽可能多的RAM。如果缩小尺寸,请确保进行了足够的分析以证明堆叠尺寸合理。
10、MTB
剩下的一件事就是使用RAM空间:MTB缓冲区。微跟踪缓冲区用于跟踪,这非常有用(请参阅“ 使用MTB跟踪调试ARM Cortex-M0 +硬故障 ”)。可以使用宏禁用缓冲区:
mtb.c
__MTB_DISABLE
这让我对此:
Memory region |
Used Size |
Region Size |
%age Used |
PROGRAM_FLASH: |
1456 B |
64 KB |
2.22% |
SRAM: |
136 B |
16 KB |
0.83% |
我想在这里我们可以很开心
11、摘要
供应商的例子很棒:它们给了我一个很好的起点。它们没有经过优化,这是故意的。但它们可能带有我不需要的功能和功能。了解使用切断功能或调整设置来优化应用程序的不同方法对于优化RAM和FLASH使用非常有用。在本教程中,我展示了如何将'blinky'降低到大约1KB闪存和大约136字节的SRAM。当然这一切都取决于功能和用法,但我认为现在为我的应用程序添加额外的功能是一个非常合理的状态。
我希望这些提示可能对您的项目有用。
12、链接
- 文本,数据和bss:代码和数据大小说明
- 拆箱恩智浦LPC845-BRK板
- 教程:使用恩智浦LPC845-BRK主板闪烁
- 使用恩智浦Kinetis SDK V2.0进行半主机(再次!)
- 为什么我不喜欢printf()
- XFormat,轻量级printf()和sprintf()替代品
- 优化Kinetis gcc启动
- 新的恩智浦MCUXpresso Eclipse IDE v11.0
声明: 此篇由 Erich Styger的《Tutorial: How to Optimize Code and RAM Size》翻译。原文地址为:https://mcuoneclipse.com/2019/08/17/tutorial-how-to-optimize-code-and-ram-size/。权属归原作者所有。
欢迎关注:
如果阅读这篇文章让您略有所得,还请点击下方的【好文要顶】按钮。
当然,如果您想及时了解我的博客更新,不妨点击下方的【关注我】按钮。
如果您希望更方便且及时的阅读相关文章,也可以扫描上方二维码关注我的微信公众号【木南创智】