小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

【教程】如何用GCC“零匯編”白嫖MDK

 袁先森lemon 2021-07-25

圖片

【說在前面的話】


其實(shí)我很久之前就想寫這篇文章了,但彼時總覺得這是一個偽命題:

  • 既然已經(jīng)用了MDK,編譯出來的代碼,無論是體積還是性能都甩下arm gcc好幾條街,誰還會想用gcc來進(jìn)行Cortex-M開發(fā)呢?

  • 對那些只能使用arm gcc、或者對gcc情有獨(dú)鐘的小伙伴來說,無論是配合eclipse、vscode、Embedded Studio還是其它什么開發(fā)環(huán)境,哪個不比MDK香呢?

然而,既然你點(diǎn)開了這篇文章,無論是否真的有這樣的需求,至少說明你對這樣的搭配還是“頗有些好奇”的。我就不去擔(dān)心背后的真正原因了,就讓我們速速切入正題,進(jìn)入實(shí)操環(huán)節(jié)吧。


先說結(jié)論:
  • MDK原生支持GCC開發(fā),且不受License限制

  • MDK使用GCC開發(fā)時“可以做到”不寫一句匯編的程度

  • MDK使用GCC開發(fā)時可以享受來自Runtime Environment配置機(jī)制的福利——也就是你可以輕松的享用來自Pack Installer所引入的各類軟件包的支持——這同樣也是免費(fèi)的

  • MDK使用GCC開發(fā)時支持調(diào)試(所能調(diào)試的代碼尺寸受到License限制)


我們知道MDK是一個集成開發(fā)環(huán)境(Integrated Development Environment),它默認(rèn)原生支持Arm Compiler 5(armcc)、Arm Compiler 6(armclang)arm gcc。雖然這三個編譯器都是由Arm所維護(hù)和提供的,但前兩者算是彼此兼容的編譯器:

  • 使用共同的 armlink

  • 使用相同的方式來描述地址空間布局(分散加載腳本 scatter script)

  • 從Arm Compiler 6.14開始,armclang甚至開始支持armasm的匯編語法了

實(shí)際上可以認(rèn)為,armccarmclang是一對連體兄弟,身子是armlink,而兩個腦袋分別是 armccarmclang。大約是這種感覺,你體會下。

圖片

與親生的兩兄弟不同,牛頭人arm gccArm公司從GCC開源社區(qū)“抱回來的孩子”。它雖然語法上與armclang(clang)基本相同,但卻擁有自己獨(dú)立的編譯和連接環(huán)節(jié),用來描述地址空間布局的方式也完全不同——采用 linker script(*.ld)來進(jìn)行。

那么這些差異對我們在MDK中使用gcc進(jìn)行開發(fā)有什么意義呢?我們需要做哪些工作準(zhǔn)備工作呢?總的來說,問題集中在以下幾個方面:
  1. 編譯器的獲取和集成

  2. 如何芯片的啟動

  3. 如何描述目標(biāo)軟件的地址空間布局

  4. 如何對編譯選項(xiàng)進(jìn)行配置

  5. 如何進(jìn)行代碼的優(yōu)化


接下來,我們就有針對性的為您解答這些問題。

【如何在將arm gcc集成到MDK環(huán)境中】

圖片

arm gcc 獲取并不困難,可以訪問arm的官方頁面直接下載:

https://developer./tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm

圖片

下載后一路無腦安裝即可,這里就不再贅述。接下來,我們打開MDK,通過菜單 project->New uVision Project... 新建一個工程:

圖片

為了方便,工程文件名不妨就叫 gcc_template好了:

圖片

單擊 'Save' 后,MDK會彈出窗口讓我們選擇工程的目標(biāo)芯片,實(shí)際上很多芯片公司都為MDK提供了面向gcc的工程模板,因此在這里直接選擇實(shí)際芯片型號往往就可以省略后面大部分步驟,但考慮到讓本教程擁有更強(qiáng)的通用性,這里我們選擇目標(biāo)芯片所使用的處理器

圖片


假設(shè),我們要使用的芯片是STM32F746,我們知道它的內(nèi)核是Cortex-M7,因此這里就選擇 Arm->ARM Cortex-M7->ARMCM7_SP(假設(shè)是單精度浮點(diǎn)運(yùn)算單元),單擊OK。


對這里選什么芯片比較糾結(jié)的小伙伴大可不必,因?yàn)楹竺骐S時可以回來改,不會存在那種“買定離手”而“無法反悔”的問題。


接下來,MDK會彈出RTE的配置界面。RTE的配置我們將在后面介紹,此時直接單擊OK進(jìn)行跳過即可。

圖片

如果一切順利,你會看到如下的界面:

圖片

以上步驟只能算是準(zhǔn)備工作,接下來才是將arm gcc集成到MDK中的正題。依次通過菜單 Project -> Manage -> Project Items 打開配置窗體:

圖片

在新打開的對話框中選擇 'Folders/Extensions' 選項(xiàng)卡,并勾選“Use GCC Compiler (GNU)for ARM projects”(如下圖所示):

圖片

單擊 “...” 按鈕,選擇arm gcc工具鏈所在的安裝目錄。以最新的的arm gcc 2020-q4-major 版本為例,默認(rèn)情況下它會被安裝在 

“C:\Program Files (x86)\GNU Arm Embedded Toolchain”

目錄下。我們選中這里的 '10 2020-q4-major' 目錄,單擊 Select Folder 按鈕。

圖片

在回到上一級窗口時,我們注意到,此時arm gcc的路徑已經(jīng)被正確配置了:

圖片

單擊“OK”就完成了 arm gcc 的添加工作。此時,如果打開 Project -> Options for Target 窗口,我們會看到編譯器配置界面變成了一個陌生的樣子:

圖片

如果你看到類似這樣的界面,恭喜您,您的MDK已經(jīng)和arm gcc“喜結(jié)連理”了

【實(shí)現(xiàn)“無匯編化”的啟動】


很多人可能都有錯覺——以為使用gcc開發(fā)項(xiàng)目一定要用匯編的方式來處理啟動文件——過去也許是這樣,但是,“大人時代變了”!。

圖片

借助 CMSIS的幫助,我們現(xiàn)在也可以優(yōu)雅的完全使用C語言來實(shí)現(xiàn)芯片的啟動過程。首先,我們需要獲得最新的CMSIS,具體方法可以在這篇文章《CMSIS玩家的“陰間成就”指南》中獲得,這里就不在贅述。

無論是通過Pack安裝還是github導(dǎo)入,在確保最新的CMSIS被成功的安裝到MDK中以后,我們首先需要在工程中通過RTE窗口引入最新的CMSIS支持:在工具欄中,單擊下面的按鈕:

 圖片

打開 Runtime Environment 配置窗口:

圖片


這里,我們展開CMSIS,并勾選 CORE(這里,請確保CORE的版本不低于 5.4.0),單擊OK確認(rèn)配置。
圖片

如果你對CMSIS的版本有所疑問,可以單擊 “Select Packs” 按鈕,確保窗體頂端的 “Use latest versions of all installed Software Packs” 被勾選,如果這樣做以后,CMSIS-CORE的版本仍然低于 5.4.0,請務(wù)必參考這篇文章CMSIS玩家的“陰間成就”指南來獲取最新的CMSIS。


單擊CMSIS-CORE后面的注釋文字:

圖片

會打開一個瀏覽器頁面,忽略其中的內(nèi)容,我們需要的是頁面網(wǎng)址中的路徑信息:

圖片

這里,我們找到了當(dāng)前CMSIS Pack在本地的路徑,利用這一路徑信息在瀏覽器中打開對應(yīng)文件夾,找到 Device目錄:

圖片


依次進(jìn)入目錄 “Device\ARM\ARMCM7\Source”:

圖片

將上圖選中的文件拷貝到我們的工程中來:

圖片


MDK工程中,將startup_ARMCM7.csystem_ARMCM7.c加入到工程中參與編譯(這里我們新建了一個分組叫做 low_level):

圖片

先別著急去編譯,注意到這里的小鑰匙圖標(biāo)了么?這說明這兩個文件自帶了“只讀屬性”。由于我們后面要修改這兩個文件,因此必須要通過Windows的文件屬性管理將只讀屬性去除(把下圖的勾選去掉后單擊OK):

圖片

此時再看MDK的工程管理器,小鑰匙標(biāo)志就已經(jīng)消失了:

圖片

接下來,打開 “Option for Target...” 窗體,進(jìn)入Linker選項(xiàng)卡:

圖片

將這里的 'Do not use Standard System Startup Files' 選項(xiàng)去除。


注意,這一步驟非常重要,不可以省略,否則你會看到如下的編譯錯誤:

linking...c:/program files (x86)/gnu arm embedded toolchain/10 2020-q4-major/bin/../lib/gcc/arm-none-eabi/10.2.1/../../../../arm-none-eabi/bin/ld.exe: warning: cannot find entry symbol _start; defaulting to 00008000c:/program files (x86)/gnu arm embedded toolchain/10 2020-q4-major/bin/../lib/gcc/arm-none-eabi/10.2.1/../../../../arm-none-eabi/bin/ld.exe: ./startup_armcm7.o: in function `__cmsis_start':C:/Users/gabriel/AppData/Local/Arm/Packs/ARM/CMSIS/5.8.0/CMSIS/Core/Include/cmsis_gcc.h:163: undefined reference to `_start'c:/program files (x86)/gnu arm embedded toolchain/10 2020-q4-major/bin/../lib/gcc/arm-none-eabi/10.2.1/../../../../arm-none-eabi/bin/ld.exe: C:/Users/gabriel/AppData/Local/Arm/Packs/ARM/CMSIS/5.8.0/CMSIS/Core/Include/cmsis_gcc.h:163: undefined reference to `__copy_table_start__'c:/program files (x86)/gnu arm embedded toolchain/10 2020-q4-major/bin/../lib/gcc/arm-none-eabi/10.2.1/../../../../arm-none-eabi/bin/ld.exe: C:/Users/gabriel/AppData/Local/Arm/Packs/ARM/CMSIS/5.8.0/CMSIS/Core/Include/cmsis_gcc.h:163: undefined reference to `__copy_table_end__'c:/program files (x86)/gnu arm embedded toolchain/10 2020-q4-major/bin/../lib/gcc/arm-none-eabi/10.2.1/../../../../arm-none-eabi/bin/ld.exe: C:/Users/gabriel/AppData/Local/Arm/Packs/ARM/CMSIS/5.8.0/CMSIS/Core/Include/cmsis_gcc.h:163: undefined reference to `__zero_table_start__'c:/program files (x86)/gnu arm embedded toolchain/10 2020-q4-major/bin/../lib/gcc/arm-none-eabi/10.2.1/../../../../arm-none-eabi/bin/ld.exe: C:/Users/gabriel/AppData/Local/Arm/Packs/ARM/CMSIS/5.8.0/CMSIS/Core/Include/cmsis_gcc.h:163: undefined reference to `__zero_table_end__'c:/program files (x86)/gnu arm embedded toolchain/10 2020-q4-major/bin/../lib/gcc/arm-none-eabi/10.2.1/../../../../arm-none-eabi/bin/ld.exe: ./startup_armcm7.o:E:\Temp Project\gcc_template/startup_ARMCM7.c:84: undefined reference to `__StackTop'collect2.exe: error: ld returned 1 exit status'.\gcc_template.elf' - 1 Error(s), 0 Warning(s).

正如錯誤提示中指出的那樣,CMSIS會在一個叫做 __cmsis_start的函數(shù)中,調(diào)用 '_start' 函數(shù),而這一函數(shù)正是gcc標(biāo)準(zhǔn)啟動文件的入口,當(dāng)你在MDK中選擇'Do not use Standard System Startup Files時,linker自然就找不到這個“不存在”的入口函數(shù)啦。


接下來,單擊如下圖所示的按鈕:

圖片

打開我們剛剛一起拷貝過來的GCC目錄,選中其中的連接腳本 gcc_arm.ld后,單擊Open:

圖片

最后的結(jié)果如下圖所示,單擊OK確認(rèn)我們的配置:

圖片

雖然不是必須,但推薦在Misc controls中添加如下的內(nèi)容:

--specs=nosys.specs -Wl,--gc-sections-fshort-enums -fshort-wchar

即:

圖片

接下來,為了初步檢驗(yàn)一下我們的成果,在工程中添加一個main.c(實(shí)現(xiàn)一個簡單的main() 函數(shù)):

圖片

懷著忐忑的心理,按下編譯按鈕:

圖片

圖片

不用懷疑,我們已經(jīng)成功的實(shí)現(xiàn)了“零匯編”gcc工程建立。簡單不?你可以把這個工程連同文件夾一起保存好,這就是未來的工程模板了。此外,關(guān)于main.c中的代碼,需要做一些簡單的說明:

#include <stdint.h>#include <stdbool.h>#include <stdio.h>#include 'cmsis_compiler.h'
int main(void){ while(1) { }
return 0;}
__attribute__((noreturn))void exit(int err_code) { while(1) { __NOP(); }}
  • GCC要求main函數(shù)的返回值是 int 類型,而這里的返回值會被作為 exit() 函數(shù)的傳入?yún)?shù)——一般負(fù)數(shù)表示出錯,0表示平安。

  • 如果不實(shí)現(xiàn)一個 exit() 函數(shù),鏈接器會報錯。

  • __attribute__((noreturn)) 就是字面意思,告訴編譯器這個這個函數(shù)是有去無回的。

  • 為了使用類似 __NOP() 這樣的“固有函數(shù)(intrinsics)”,我們需要直接或者間接的包含頭文件  'cmsis_compiler.h'

此外,如果我們不做任何的設(shè)置,MDK會將所有生成的中間文件(比如 .o、.d之類)直接保存到工程文件夾下,產(chǎn)生“垃圾遍布”的感覺:

圖片

為了解決這一問題,我們可以在'Options for Target'窗口的Target選項(xiàng)卡中通過“Select Folder for Objects” 來選擇一個專門的文件夾放置這些中間文件:

圖片

完成基礎(chǔ)模板的制作后,接下來我們來一一介紹一些模板在使用過程中所需要處理的細(xì)節(jié)問題:

【簡單的地址空間布局、Stack和Heap的配置】


在去掉 GCC/gcc_arm.ld 文件的只讀屬性后,我們就可以借助它根據(jù)目標(biāo)芯片的實(shí)際情況描述地址空間布局,打開gcc_arm.ld,可以看到如下的內(nèi)容:

圖片

如果你的目標(biāo)芯片較為簡單,比如,F(xiàn)LASH是一片完整的地址區(qū)間,則可以通過修改__ROM_BASE的方式來設(shè)置目標(biāo)鏡像中FLASH的起始地址,通過修改修改__ROM_SIZE來設(shè)置FLASH的實(shí)際大小,比如,起始地址為0x0800-0000,大小為256K的Flash對應(yīng)的修改方式為:

/*---------------------- Flash Configuration ----------------------------------  <h> Flash Configuration    <o0> Flash Base Address <0x0-0xFFFFFFFF:8>    <o1> Flash Size (in Bytes) <0x0-0xFFFFFFFF:8>  </h>  -----------------------------------------------------------------------------*/__ROM_BASE = 0x08000000;__ROM_SIZE = 0x00040000;

同理,SRAM的起始地址和大小可以通過__RAM_BASE__RAM_SIZE來設(shè)置,這里就不再贅述:

/*--------------------- Embedded RAM Configuration ---------------------------- <h> RAM Configuration <o0> RAM Base Address <0x0-0xFFFFFFFF:8> <o1> RAM Size (in Bytes) <0x0-0xFFFFFFFF:8> </h> -----------------------------------------------------------------------------*/__RAM_BASE = 0x20000000;__RAM_SIZE = 0x00020000;

最后,關(guān)于StackHeap大小的設(shè)置可以借助__STACK_SIZE__HEAP_SIZE來設(shè)置:

/*--------------------- Stack / Heap Configuration ----------------------------  <h> Stack / Heap Configuration    <o0> Stack Size (in Bytes) <0x0-0xFFFFFFFF:8>    <o1> Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>  </h>  -----------------------------------------------------------------------------*/__STACK_SIZE = 0x00000800;   /* 2K Byte */__HEAP_SIZE  = 0x00000200;   /* 256 Byte */

【如何配置中斷向量表】


不同的芯片擁有不同的中斷向量表,而此前我們所建立的gcc工程模板中,startup_ARMCM7.c 里定義的其實(shí)是一個默認(rèn)的中斷向量表:

圖片

可以看到,這一向量表完全采用的是C語言函數(shù)指針數(shù)組初始化的形式定義的。它不僅提供了默認(rèn)的各類系統(tǒng)異常的定義,還以Interruptn_Handler的形式為我們提供了定義的范例。

更新這一文件的步驟并不復(fù)雜。實(shí)際上一般芯片公司都會提供符合CMSIS規(guī)范的芯片頭文件,這一頭文件中會提供對應(yīng)的中斷向量定義,比如STM32F746就有一個對應(yīng)的頭文件 STM32F746xx.h。將其打開會看到專門的向量表定義:
/** * @brief STM32F7xx Interrupt Number Definition, according to the selected device * in @ref Library_configuration_section */typedef enum{/****** Cortex-M7 Processor Exceptions Numbers ****************************************************************/ NonMaskableInt_IRQn = -14, /*!< 2 Non Maskable Interrupt */ MemoryManagement_IRQn = -12, /*!< 4 Cortex-M7 Memory Management Interrupt */ BusFault_IRQn = -11, /*!< 5 Cortex-M7 Bus Fault Interrupt */ UsageFault_IRQn = -10, /*!< 6 Cortex-M7 Usage Fault Interrupt */ SVCall_IRQn = -5, /*!< 11 Cortex-M7 SV Call Interrupt */ DebugMonitor_IRQn = -4, /*!< 12 Cortex-M7 Debug Monitor Interrupt */ PendSV_IRQn = -2, /*!< 14 Cortex-M7 Pend SV Interrupt */ SysTick_IRQn = -1, /*!< 15 Cortex-M7 System Tick Interrupt *//****** STM32 specific Interrupt Numbers **********************************************************************/ WWDG_IRQn = 0, /*!< Window WatchDog Interrupt */  ... SPDIF_RX_IRQn = 97, /*!< SPDIF-RX global Interrupt */} IRQn_Type;

這里,WWDG_IRQnSPDIF_RX_IRQn之間的每一項(xiàng)都對應(yīng)一個外設(shè)中斷,可以將它們拷貝出來,添加到我們的startup_ARMCM7.c的向量表中,并依樣畫葫蘆,修改成對應(yīng)的形式:

.../*----------------------------------------------------------------------------  Exception / Interrupt Handler *----------------------------------------------------------------------------*//* Exceptions */void NMI_Handler            (void) __attribute__ ((weak, alias('Default_Handler')));void HardFault_Handler      (void) __attribute__ ((weak));void MemManage_Handler      (void) __attribute__ ((weak, alias('Default_Handler')));void BusFault_Handler       (void) __attribute__ ((weak, alias('Default_Handler')));void UsageFault_Handler     (void) __attribute__ ((weak, alias('Default_Handler')));void SVC_Handler            (void) __attribute__ ((weak, alias('Default_Handler')));void DebugMon_Handler       (void) __attribute__ ((weak, alias('Default_Handler')));void PendSV_Handler         (void) __attribute__ ((weak, alias('Default_Handler')));void SysTick_Handler        (void) __attribute__ ((weak, alias('Default_Handler')));/*void Interrupt0_Handler     (void) __attribute__ ((weak, alias('Default_Handler')));...void Interrupt9_Handler     (void) __attribute__ ((weak, alias('Default_Handler')));*/void WWDG_IRQn_Handler      (void) __attribute__ ((weak, alias('Default_Handler')));...void SPDIF_RX_IRQn_Handler  (void) __attribute__ ((weak, alias('Default_Handler')));
...
extern const VECTOR_TABLE_Type __VECTOR_TABLE[240]; const VECTOR_TABLE_Type __VECTOR_TABLE[240] __VECTOR_TABLE_ATTRIBUTE = { (VECTOR_TABLE_Type)(&__INITIAL_SP), /* Initial Stack Pointer */ Reset_Handler, /* Reset Handler */ NMI_Handler, /* -14 NMI Handler */ HardFault_Handler, /* -13 Hard Fault Handler */ MemManage_Handler, /* -12 MPU Fault Handler */ BusFault_Handler, /* -11 Bus Fault Handler */ UsageFault_Handler, /* -10 Usage Fault Handler */ 0, /* Reserved */ 0, /* Reserved */ 0, /* Reserved */ 0, /* Reserved */ SVC_Handler, /* -5 SVC Handler */ DebugMon_Handler, /* -4 Debug Monitor Handler */ 0, /* Reserved */ PendSV_Handler, /* -2 PendSV Handler */ SysTick_Handler, /* -1 SysTick Handler */
/* Interrupts */  [WWDG_IRQn+16]= WWDG_IRQn_Handler,        /*   0 WWDG_IRQn */  ...  [SPDIF_RX_IRQn+16]= SPDIF_RX_IRQn_Handler,/*   97 SPDIF_RX_IRQn */
};

接下來,我們需要更新 startup_ARMCM7.c system_ARMCM7.c中的頭文件,同樣以STM32F746為例,將原本的ARMCM7相關(guān)的包含注釋掉,加入#include 'stm32f746xx.h'

#if 0#if defined (ARMCM7) #include 'ARMCM7.h'#elif defined (ARMCM7_SP) #include 'ARMCM7_SP.h'#elif defined (ARMCM7_DP) #include 'ARMCM7_DP.h'#else #error device not specified!#endif#endif
#include 'stm32f746xx.h'

有時候,客戶芯片的頭文件會缺少一些必要的定義,比如函數(shù)指針VECTOR_TABLE_Type,則直接在這兩個.c文件中補(bǔ)充即可:

  /**  \brief Exception / Interrupt Handler Function Prototype*/typedef void(*VECTOR_TABLE_Type)(void);

對于STM32芯片的用戶來說,其實(shí)官方的CMSIS Pack已經(jīng)為arm gcc提供了對應(yīng)的啟動文件,我們可以在RTE中將其打開:

圖片

隨后在工程管理器中就可以在Device選項(xiàng)卡下看到它們:

圖片

遺憾的是,這里的啟動文件使用的是匯編,如果你不喜歡它們,則仍然可以使用本文介紹的方法。

再次重申下,本文使用STM32作為例子僅僅是因?yàn)槲沂稚嫌羞@塊板子,并不是說對于STM32來說,使用GCC一定要用我說的方法而不使用官方提供好的gcc啟動文件。本文講解的目標(biāo)時提供最大的通用性,因此會涉及到很多具體的細(xì)節(jié)。


值得注意的是:有時候,某些芯片會提供面向Arm Compiler 5或者Arm Compiler 6system_xxxx.c,其實(shí)我們完全可以拷貝出來直接替換掉這里的 system_ARMCM7.c。在STM32F746的例子中,我們看中了廠家提供的system_stm32f7xx.c——因?yàn)槠渲邪吮匾男酒跏蓟a(時鐘、外設(shè)等等),因此,我們將其單獨(dú)拷貝到工程目錄下:

圖片

加入工程管理器中參與編譯,并將原本的system_ARMCM7.c從編譯中剔除:

圖片

最終呈現(xiàn)的效果如下:

圖片

在我們著急開始編譯以驗(yàn)證效果之前,這里有一個細(xì)節(jié)需要分情況討論:

  • 目標(biāo)芯片原本就與針對MDK的 CMSIS-Pack支持

    對于這種情況,我們需要在“Options for Target”的Device選項(xiàng)卡中選擇對應(yīng)芯片,這樣MDK會自動將目標(biāo)芯片頭文件的路徑加入編譯器的頭文件搜索列表中。

    圖片

  • 目標(biāo)芯片沒有針對MDK的CMSIS-Pack,而只提供了目標(biāo)芯片的頭文件(包含了寄存器定義等等)

    此時,我們需要將目標(biāo)芯片的頭文件拷貝到工程目錄下,并收工將對應(yīng)路徑添加到編譯器的頭文件搜索列表中。這里因?yàn)槲覀兗僭O(shè)你直接將頭文件保存在了工程目錄下,因此這里的搜索路徑就是'工程所在當(dāng)前目錄'——直接用'.'就可以了:

    圖片

完成了上述步驟,基本上就完成了對新的目標(biāo)芯片的最基礎(chǔ)支持。

【如何設(shè)置開啟編譯優(yōu)化】


MDK在“Option for Target”的'CC'選項(xiàng)卡中提供了簡化的優(yōu)化選項(xiàng)支持:

圖片


看似滿足要求,其實(shí)遠(yuǎn)遠(yuǎn)不夠——哪怕你選擇了'Level 2 (Size)'優(yōu)化,可能最終代碼的尺寸依然大的嚇人。要解決這一問題,需要在 'Misc Controlls' 文本框中追加以下的編譯選項(xiàng):

-ffunction-sections -fdata-sections

由于linker進(jìn)行尺寸優(yōu)化的基本單位是section,而section是變量和代碼的容器。默認(rèn)情況下,每個c源文件中所有函數(shù)生成的代碼都會放在一個叫做“.text”的容器中;而所有靜態(tài)分配的變量也會被類似的放在名為.data或者.bss的section中——這樣的缺點(diǎn)是,整個section中只有一個函數(shù)或者變量被用到了,整個section中的內(nèi)容都會被判定為是需要保留的。更糟糕的是,這種判定是具有“傳染”性的,這意味著哪怕某一個section中存在沒用到函數(shù),只要該section被判定為要保留,則這些沒有用到的函數(shù)所調(diào)用的函數(shù),其所在section也會被傳染。

要解決這一問題的方式很簡單,直接給每一個函數(shù)或者靜態(tài)變量都分配一個獨(dú)立的section即可。
關(guān)于這方面的詳細(xì)討論,請參考文章《真刀真槍模塊化(3.5)——騷操作?不!這才是正統(tǒng)》,這里就不再贅述。

圖片

此外,還有一些更高階的優(yōu)化選項(xiàng)并未提供在Optimisation列表中,例如,最高的性能優(yōu)化'-Ofast',以及更聰明的鏈接優(yōu)化“Link Time Optimisation”,詳細(xì)的使用效果請參考gcc的官方文檔,這里就不再贅述。要想使用它們:

  1. 可以將 Optimisation列表設(shè)置為<Default>;

  2. Misc Controls文本框中添加對應(yīng)的選項(xiàng)'-Ofast'已開啟最高性能優(yōu)化;

  3. Misc Controls文本框中添加對應(yīng)的選項(xiàng)'-flto'已開啟Link Time Optimisation;

圖片

注意,對應(yīng)的編譯選項(xiàng)也要在 'Linker'的Misc Controls中添加:

圖片

【如何在編譯成功后打印尺寸信息】


當(dāng)MDK使用Arm Compiler 5或者Arm Compiler 6進(jìn)行編譯,成功后會在Build Output窗口中打印一些尺寸信息,比如:

圖片

MDK使用GCC進(jìn)行編譯時,默認(rèn)情況下就沒有這么方便了。為了達(dá)到同樣的效果,我們可以在'Options for Target'的“User” 選項(xiàng)卡中增加 After Build/Rebuild命令行:

arm-none-eabi-size.exe ./Objects/gcc_template.elf

注意,如果你修改了輸出文件的名稱,千萬要記得同步更新這里的命令行呀!

圖片

最終實(shí)現(xiàn)了類似的效果:

圖片

【如何優(yōu)雅的測量系統(tǒng)的性能】


熟悉我公眾號的朋友一定注意到我有一個開源項(xiàng)目 perf_counter,可以幫助用戶在不額外占用SysTick的情況下提供一系列服務(wù),包括但不限于:
  • 為裸機(jī)或者RTOS提供Cycle級別的性能測量;

  • 評估代碼片段的CPU占用;

  • 算法精細(xì)優(yōu)化時用于測量和觀察優(yōu)化的效果;

  • 測量中斷的響應(yīng)時間;

  • 測量中斷的發(fā)生間隔(查找最短時間間隔);

  • 評估GUI的幀率或者刷新率;

  • 與SystemCoreClock計(jì)算后,獲得一個系統(tǒng)時間戳(Timestamp);

  • 當(dāng)做Realtime Clock的基準(zhǔn);

  • 作為隨機(jī)數(shù)種子

  • ……

其詳細(xì)使用方法在文章《如何“優(yōu)雅”的測量系統(tǒng)性能》中有詳細(xì)介紹,這里就不再贅述。在Github上的最新版本中,優(yōu)化了gcc的部署體驗(yàn)——也能像Arm Compiler 5以及Arm Compiler 6那樣簡單拖放lib即可完成部署

具體步驟如下:

1、通過下面連接獲取最新版本的 perf_counter

https://github.com/GorgonMeducer/perf_counter/releases/tag/v1.5.0

圖片

2、解壓縮后拷貝到gcc工程所在目錄下,改名為perf_counter

圖片

3、添加perf_counter.h所在路徑到編譯器頭文件搜索路徑中:

圖片

4、修改startup_ARMCM7.c文件,將原本的:

void SysTick_Handler (void) __attribute__ ((weak, alias('Default_Handler')));

修改為

void SysTick_Handler(void) __attribute__ ((weak));

注意,千萬不要在startup_ARMCM7.c中為 SysTick_Handler提供默認(rèn)的weak實(shí)現(xiàn)函數(shù)!切記,切記!

5、更新'Options for Target'的'Linker'配置,在Misc Controls文本框中追加:

-Wl,--wrap=SysTick_Handler'./perf_counter/lib/libperf_counter_gcc.a'

圖片

6、在要用到 perf_counter 服務(wù)的地方添加對頭文件的包含:

#include 'perf_counter.h'...

7、編譯工程,如果報告如下的錯誤:

libperf_counter_gcc.a(systick_wrapper_gcc.o): in function `__ensure_systick_wrapper':(.text+0x18): undefined reference to `SysTick_Handler'

則說明我們的工程中并沒有實(shí)現(xiàn)SysTick_Handler,此時,應(yīng)該在任意c文件中增加一個SysTick_Handler,并在 main() 函數(shù)中對 perf_counter.c進(jìn)行初始化:

#include 'perf_counter.h'
void SysTick_Handler(void){ }
int main(void){ init_cycle_counter(false); while(1) { } return 0;}

編譯后一切正常:

圖片

圖片

詳細(xì)的使用方法,還請參考《如何“優(yōu)雅”的測量系統(tǒng)性能》。

【說在后面的話】


MDK中使用GCC具有很多實(shí)際意義,比如:
  • 編譯不受License限制

  • 可以進(jìn)行調(diào)試(需要License)

  • 可以借助RTE實(shí)現(xiàn)各類CMSIS Pack的快速部署(比如很多操作系統(tǒng):cmsis-rtos2等等)

  • 不必編寫makefile

  • 很多大廠芯片(比如STM32)其實(shí)都在CMSIS-Pack的軟件包里提供了arm gcc的支持(提供了arm gcc下的啟動文件和 *.ld 文件),直接通過RTE就可以加入對應(yīng)的文件

不管你是否喜歡MDK,總的來說是多了一種選擇把。

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多