查看: 4342|回复: 0

如何优化生成文件中的Code和RAM大小

[复制链接]
  • TA的每日心情
    开心
    2025-7-11 08:53
  • 签到天数: 301 天

    连续签到: 2 天

    [LV.8]以坛为家I

    3937

    主题

    7558

    帖子

    0

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    40214
    最后登录
    2025-9-8
    发表于 2020-2-25 11:24:13 | 显示全部楼层 |阅读模式
    如何优化生成文件中的Code和RAM大小

    如果产品的生产商能为初学者提供一些入门,那就太好了。blinky示例程序是一个很好的起始点。便利总要付出代价的,blinky示例的代码就是切换GPIO引脚的代码大小被夸大了。对于只有少量RAM和存储器的器件,代码可能可能会需要关注:如果blinky代码占据的空间那么大,我的应用程序是否会适合该器件呢?不用担心:blinky代码(以及其他任何项目)都可以轻松裁剪。

    在本篇文章中,我将以blinky项目为例展示裁剪技巧,也可以应用于任何其他类型的项目。本文使用的是NXP LPC845开发板:
    50.png
    恩智浦LPC845-BRK开发板


    Blinky项目


    本文使用基于Eclipse的NXP MCUXpresso IDE。首先使用供应商默认设置新建了一个blinky项目:
    51.png
    blinky项目




    blinky项目可以实现LED闪烁,这对于任何项目都是一个很好的入门。制作这个相当小的项目的代码大小如下所示:
    1. <font size="3" face="微软雅黑">Memory region         Used Size  Region Size  %age Used
    2.    PROGRAM_FLASH:       10536 B        64 KB     16.08%
    3.             SRAM:        2424 B        16 KB     14.79%</font>
    复制代码
    这些信息也以这种方式显示在控制台中,分为text、data和bss:
    1. <font size="3" face="微软雅黑">   text           data            bss            dec            hex        filename
    2.   10532              4           2420          12956           329c        lpc845breakout_led_blinky.axf</font>
    复制代码
    10K的代码量看起来有点夸张。我们将在下一步中对此进行调整。


    代码大小信息


    查看器件上正在使用空间的正常方式是检查链接器映射文件(* .map):
    52.png
    链接器映射文件


    但是该map文件相当难以阅读,对于专家而言则信息更多:它列出了带有地址和大小的部分:
    53.png
    链接器映射文件内容


    使用MCUXpresso IDE V11,有一个不错的“Image Info(映像信息)”视图,从本质上来说,它是一个更好的查看器,用于查看map文件信息:
    54.png
    Image Info视图


    我可以对数据进行过滤和排序,这使我了解了代码和数据使用了多少空间:
    55.png
    Image Info Memory Content




    当然,它需要有关应用程序应该做什么的一些知识。我总是查看视图中的项目列表,以查看是否有我所不希望的内容:也许应用程序使用的是可以删除的内容。


    源代码


    对于简单的blinky来说,这相当小。第一件事是检查程序在做什么。 main.c如下:
    1. <font size="3" face="微软雅黑">/*
    2. * Copyright 2017 NXP
    3. * All rights reserved.
    4. *
    5. * SPDX-License-Identifier: BSD-3-Clause
    6. */

    7. #include "board.h"
    8. #include "fsl_gpio.h"

    9. #include "pin_mux.h"
    10. /*******************************************************************************
    11. * Definitions
    12. ******************************************************************************/
    13. #define BOARD_LED_PORT 1U
    14. #define BOARD_LED_PIN 2U

    15. /*******************************************************************************
    16. * Prototypes
    17. ******************************************************************************/

    18. /*******************************************************************************
    19. * Variables
    20. ******************************************************************************/
    21. volatile uint32_t g_systickCounter;

    22. /*******************************************************************************
    23. * Code
    24. ******************************************************************************/
    25. void SysTick_Handler(void)
    26. {
    27.     if (g_systickCounter != 0U)
    28.     {
    29.         g_systickCounter--;
    30.     }
    31. }

    32. void SysTick_DelayTicks(uint32_t n)
    33. {
    34.     g_systickCounter = n;
    35.     while (g_systickCounter != 0U)
    36.     {
    37.     }
    38. }

    39. /*!
    40. * @brief Main function
    41. */
    42. int main(void)
    43. {
    44.     /* Define the init structure for the output LED pin*/
    45.     gpio_pin_config_t led_config = {
    46.         kGPIO_DigitalOutput,
    47.         0,
    48.     };

    49.     /* Board pin init */
    50.     BOARD_InitPins();
    51.     BOARD_InitBootClocks();
    52.     BOARD_InitDebugConsole();

    53.     /* Init output LED GPIO. */
    54.     GPIO_PortInit(GPIO, BOARD_LED_PORT);
    55.     GPIO_PinInit(GPIO, BOARD_LED_PORT, BOARD_LED_PIN, &led_config);

    56.     /* Set systick reload value to generate 1ms interrupt */
    57.     if (SysTick_Config(SystemCoreClock / 1000U))
    58.     {
    59.         while (1)
    60.         {
    61.         }
    62.     }

    63.     while (1)
    64.     {
    65.         /* Delay 1000 ms */
    66.         SysTick_DelayTicks(1000U);
    67.         GPIO_PortToggle(GPIO, BOARD_LED_PORT, 1u << BOARD_LED_PIN);
    68.     }
    69. }</font>
    复制代码
    代码是初始化引脚、时钟、设置SysTick计时器,然后使用Systick计数器延迟闪烁周期来循环执行“闪烁”。


    半主机Semihosting和printf


    接下来要看的是是否有任何半主机或printf()。与“标准” newlib或较小标准的newlib-nano相比,该项目使用的是“优化库”“ Redlib”:
    56.png
    Redlib




    不过,该库可能会增加代码大小,因为它使用的是半主机(通过调试器发送消息)。查看“内存”视图,可以看到直接或间接需要的所有这些标准I / O函数:
    57.png
    stdio函数




    拥有该功能的所有钩子仅在使用时才有意义,而blinky则不使用。因此,不使用这种半主机和所有未使用的标准I / O意味着要使用“none”变体:
    58.png
    没有标准I / O的库



    这样我们就可以得到以下:
    1. <font size="3" face="微软雅黑">Memory region         Used Size  Region Size  %age Used
    2.    PROGRAM_FLASH:        3372 B        64 KB      5.15%
    3.             SRAM:        2208 B        16 KB     13.48%</font>
    复制代码
    调试和NDEBUG


    下一步是检查编译器定义是否列出了DEBUG。以下是示例:
    59.png
    调试定义


    有了该定义集,SDK和示例驱动程序中会包含许多额外的代码,这些代码可通过“ assert()”宏检查是否有合适的值:

    同样,这里的图像信息视图很有用:它向我显示了使用assert()的所有位置:
    60.png
    assert用法


    在代码中断言以及早发现编程错误实际上是一个好习惯。但是所有的assert()代码确实加起来了。要关闭多余的代码(和安全带!),我将宏更改为NDEBUG:
    61.png
    NDEBUG


    这样我们可以实现:
    1. <font size="3" face="微软雅黑">Memory region         Used Size  Region Size  %age Used
    2.    PROGRAM_FLASH:        3144 B        64 KB      4.80%
    3.             SRAM:        2208 B        16 KB     13.48%</font>
    复制代码
    中断(Interrupt)和向量(Vector)


    同样,“图像信息”视图是一个很好的起点。我正在检查使用的中断。 Blinky正在使用预期的SysTick中断。但是仍然使用UART中断吗?
    62.png
    使用的中断


    大多数中断都实现为“弱”:实现为默认/空,可以被应用程序覆盖。但是UART没有意义,因为blinky不使用任何UART通信?


    事实证明,NXP SDK默认情况下启用了UART事务API:
    63.png
    UART事务API设置


    事务性API允许在通信块/事务中发送/接收UART数据。但是,我们不需要在blinky之间就关闭它,所以我们将其关闭:
    64.png
    关闭UART事务性API


    这使:
    1. <font size="3" face="微软雅黑">Memory region         Used Size  Region Size  %age Used
    2.    PROGRAM_FLASH:        2964 B        64 KB      4.52%
    3.             SRAM:        2184 B        16 KB     13.33%</font>
    复制代码
    优化

    到目前为止,我已经剥离了不需要或未使用的函数。接下来,我可以打开编译器优化。默认情况下,项目设置为-O0:
    65.png
    编译器优化


    -O0表示没有优化:代码简单明了且易于调试。


    -O1主要优化函数的进入/退出代码,并且能够在不真正影响调试的情况下减小代码大小。在此示例中,它将代码大小减少了一半!
    1. <font size="3" face="微软雅黑">Memory region         Used Size  Region Size  %age Used
    2.    PROGRAM_FLASH:        1540 B        64 KB      2.35%
    3.             SRAM:        2184 B        16 KB     13.33%</font>
    复制代码
    -O2优化更多,并尝试将其尽可能多地保留在寄存器中。由于应用程序中的功能很小,因此改进并不大:
    1. <font size="3" face="微软雅黑">Memory region         Used Size  Region Size  %age Used
    2.    PROGRAM_FLASH:        1516 B        64 KB      2.31%
    3.             SRAM:        2184 B        16 KB     13.33%</font>
    复制代码
    -O3通过额外的内联来最优化。 -O3以速度为目标,因此难怪代码大小会再次增加:
    1. <font size="3" face="微软雅黑">Memory region         Used Size  Region Size  %age Used
    2.    PROGRAM_FLASH:        1792 B        64 KB      2.73%
    3.             SRAM:        2184 B        16 KB     13.33%</font>
    复制代码
    代码大小优化的最佳选择是-Os(针对大小进行优化):
    1. <font size="3" face="微软雅黑">Memory region         Used Size  Region Size  %age Used
    2.    PROGRAM_FLASH:        1456 B        64 KB      2.22%
    3.             SRAM:        2184 B        16 KB     13.33%</font>
    复制代码


    现在看起来很合理!当然,现在有很多方法可以消除“blinky”的问题,但是对于实际应用而言,所有适当的地方(启动代码、时钟和GPIO初始化)都是有意义的,所以我现在就在这里结束。


    RAM:堆和堆栈


    看起来不正确的是SRAM的使用情况。 “堆”使用了大量块:
    66.png
    堆内存使用情况


    该堆用于动态内存分配(malloc())。嵌入式编程的一般规则是避免使用它。但这是默认设置。可以在链接器设置中将其关闭:该演示使用1K进行堆和栈。由于我没有使用malloc(),因此可以将堆大小设置为0x0。对于真正取决于应用程序的保留堆栈。在ARM Cortex上,MSP用于启动/主程序和中断。 0x100(256字节)应该足够满足blinky项目。
    67.png
    堆和堆栈大小




    这样可以实现:
    1. <font size="3" face="微软雅黑">Memory region         Used Size  Region Size  %age Used
    2.    PROGRAM_FLASH:        1456 B        64 KB      2.22%
    3.             SRAM:         392 B        16 KB      2.39%</font>
    复制代码
    如果要进一步减小堆栈大小,我可以查看“调用图”信息,该信息为我提供了有关已使用多少堆栈空间的信息:
    68.png
    具有堆栈大小的调用图


    MTB


    使用RAM空间的还有一件事:MTB缓冲区。Micro Trace Buffer(微型跟踪缓冲区)用于跟踪,这可能非常有用。可以使用宏禁用缓冲区:
    69.png
    这样可以实现:
    1. <font size="3" face="微软雅黑">Memory region         Used Size  Region Size  %age Used
    2.    PROGRAM_FLASH:        1456 B        64 KB      2.22%
    3.             SRAM:         136 B        16 KB      0.83%</font>
    复制代码
    总结


    供应商提供示例很好:它们给了我一个很好的起点。它们没有优化,这是有意的。但是它们可能带有我不需要的功能。知道通过切断功能或调整设置来优化应用程序的不同方法对于优化RAM和FLASH的使用非常有用。在本文中,我展示了如何将blinky降低到大约1KB闪存和大约136个字节的SRAM。当然,这全部取决于功能和用法,但是我认为这是为应用程序添加额外功能的一种相当合理的状态。




    我希望这些技巧可能对您的项目有用。




    作者:阿哲       文章出处:点击

    qiandao qiandao
    回复

    使用道具 举报

    您需要登录后才可以回帖 注册/登录

    本版积分规则

    关闭

    站长推荐上一条 /3 下一条

    Archiver|手机版|小黑屋|恩智浦技术社区

    GMT+8, 2025-9-8 20:25 , Processed in 0.084006 second(s), 21 queries , MemCache On.

    Powered by Discuz! X3.4

    Copyright © 2001-2024, Tencent Cloud.

    快速回复 返回顶部 返回列表