如果供應商為我自己的項目提供了一個起點,那就太好了。工作'眨眼'始終是一個偉大的首發。方便總是有代價,而且“眨眼”就是誇大“切換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/。權屬歸原作者所有。
歡迎關注: