阿哲
发表于: 2020-1-30 12:01:59 | 显示全部楼层

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


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

nxplpc845-brkboard.png

恩智浦LPC845-BRK开发板


Blinky项目

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

blinky.png

blinky项目


blinky项目可以实现LED闪烁,这对于任何项目都是一个很好的入门。制作这个相当小的项目的代码大小如下所示:

  1. 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%
复制代码

这些信息也以这种方式显示在控制台中,分为text、data和bss:

  1.    text           data            bss            dec            hex        filename
  2.   10532              4           2420          12956           329c        lpc845breakout_led_blinky.axf
复制代码

10K的代码量看起来有点夸张。我们将在下一步中对此进行调整。


代码大小信息

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

linker-map-file.png

链接器映射文件


但是该map文件相当难以阅读,对于专家而言则信息更多:它列出了带有地址和大小的部分:

linker-map-file-content.png

链接器映射文件内容


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

image-info-view.png

Image Info视图


我可以对数据进行过滤和排序,这使我了解了代码和数据使用了多少空间:

image-info-memory-content.png

Image Info Memory Content


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


源代码

对于简单的blinky来说,这相当小。第一件事是检查程序在做什么。 main.c如下:

  1. /*
  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. }
复制代码

代码是初始化引脚、时钟、设置SysTick计时器,然后使用Systick计数器延迟闪烁周期来循环执行“闪烁”。


半主机Semihosting和printf

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

redlib.png

Redlib


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

stdio-functions.png

stdio函数


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

library-without-standard-i-o.png

没有标准I / O的库


这样我们就可以得到以下:

  1. 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%
复制代码

调试和NDEBUG

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

debug-define.png

调试定义


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


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

assert-usage.png

assert用法


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

ndebug.png

NDEBUG


这样我们可以实现:

  1. 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%
复制代码

中断(Interrupt)和向量(Vector)

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

interrupts-used.png

使用的中断


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


事实证明,NXP SDK默认情况下启用了UART事务API:

uart-transactional-api-setting.png

UART事务API设置


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

turning-off-uart-transactional-api.png

关闭UART事务性API


这使:

  1. 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%
复制代码

优化

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

compiler-optimizations.png

编译器优化


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

-O1主要优化函数的进入/退出代码,并且能够在不真正影响调试的情况下减小代码大小。在此示例中,它将代码大小减少了一半!

  1. 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%
复制代码

-O2优化更多,并尝试将其尽可能多地保留在寄存器中。由于应用程序中的功能很小,因此改进并不大:

  1. 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%
复制代码

-O3通过额外的内联来最优化。 -O3以速度为目标,因此难怪代码大小会再次增加:

  1. 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%
复制代码

代码大小优化的最佳选择是-Os(针对大小进行优化):

  1. 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%
复制代码

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


RAM:堆和堆栈

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

heap-memory-usage.png

堆内存使用情况


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

heap-and-stack-size.png

堆和堆栈大小


这样可以实现:

  1. 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%
复制代码

如果要进一步减小堆栈大小,我可以查看“调用图”信息,该信息为我提供了有关已使用多少堆栈空间的信息:

call-graph-with-stack-size.png

具有堆栈大小的调用图


MTB

使用RAM空间的还有一件事:MTB缓冲区。Micro Trace Buffer(微型跟踪缓冲区)用于跟踪,这可能非常有用。可以使用宏禁用缓冲区:

mtb_disable.png


这样可以实现:

  1. 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%
复制代码

总结

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


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

跳转到指定楼层
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

主题 53 | 回复: 76



手机版|

GMT+8, 2024-4-19 13:25 , Processed in 0.136021 second(s), 6 queries , Gzip On, MemCache On. Powered by Discuz! X3.5

YiBoard一板网 © 2015-2022 地址:河北省石家庄市长安区高营大街 ( 冀ICP备18020117号 )

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