【說在前面的話】
然而,既然你點(diǎn)開了這篇文章,無論是否真的有這樣的需求,至少說明你對這樣的搭配還是“頗有些好奇”的。我就不去擔(dān)心背后的真正原因了,就讓我們速速切入正題,進(jìn)入實(shí)操環(huán)節(jié)吧。
我們知道MDK是一個集成開發(fā)環(huán)境(Integrated Development Environment),它默認(rèn)原生支持Arm Compiler 5(armcc)、Arm Compiler 6(armclang)和 arm gcc。雖然這三個編譯器都是由Arm所維護(hù)和提供的,但前兩者算是彼此兼容的編譯器:
實(shí)際上可以認(rèn)為,armcc和armclang是一對連體兄弟,身子是armlink,而兩個腦袋分別是 armcc 和 armclang。大約是這種感覺,你體會下。 與親生的兩兄弟不同,牛頭人arm gcc是Arm公司從GCC開源社區(qū)“抱回來的孩子”。它雖然語法上與armclang(clang)基本相同,但卻擁有自己獨(dú)立的編譯和連接環(huán)節(jié),用來描述地址空間布局的方式也完全不同——采用 linker script(*.ld)來進(jìn)行。
arm gcc 獲取并不困難,可以訪問arm的官方頁面直接下載: 下載后一路無腦安裝即可,這里就不再贅述。接下來,我們打開MDK,通過菜單 project->New uVision Project... 新建一個工程: 如果一切順利,你會看到如下的界面: 在新打開的對話框中選擇 '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 窗口,我們會看到編譯器配置界面變成了一個陌生的樣子: 【實(shí)現(xiàn)“無匯編化”的啟動】 很多人可能都有錯覺——以為使用gcc開發(fā)項(xiàng)目一定要用匯編的方式來處理啟動文件——過去也許是這樣,但是,“大人時代變了”!。 單擊CMSIS-CORE后面的注釋文字: 會打開一個瀏覽器頁面,忽略其中的內(nèi)容,我們需要的是頁面網(wǎng)址中的路徑信息: 這里,我們找到了當(dāng)前CMSIS Pack在本地的路徑,利用這一路徑信息在瀏覽器中打開對應(yīng)文件夾,找到 Device目錄: 在MDK工程中,將startup_ARMCM7.c和system_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 00008000 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: 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)容:
即: 接下來,為了初步檢驗(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(); } }
此外,如果我們不做任何的設(shè)置,MDK會將所有生成的中間文件(比如 .o、.d之類)直接保存到工程文件夾下,產(chǎn)生“垃圾遍布”的感覺: 完成基礎(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)的修改方式為:
同理,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)于Stack和Heap大小的設(shè)置可以借助__STACK_SIZE和__HEAP_SIZE來設(shè)置:
【如何配置中斷向量表】 可以看到,這一向量表完全采用的是C語言函數(shù)指針數(shù)組初始化的形式定義的。它不僅提供了默認(rèn)的各類系統(tǒng)異常的定義,還以Interruptn_Handler的形式為我們提供了定義的范例。 /** * @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_IRQn到SPDIF_RX_IRQn之間的每一項(xiàng)都對應(yīng)一個外設(shè)中斷,可以將它們拷貝出來,添加到我們的startup_ARMCM7.c的向量表中,并依樣畫葫蘆,修改成對應(yīng)的形式:
接下來,我們需要更新 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ǔ)充即可:
對于STM32芯片的用戶來說,其實(shí)官方的CMSIS Pack已經(jīng)為arm gcc提供了對應(yīng)的啟動文件,我們可以在RTE中將其打開: 隨后在工程管理器中就可以在Device選項(xiàng)卡下看到它們: 遺憾的是,這里的啟動文件使用的是匯編,如果你不喜歡它們,則仍然可以使用本文介紹的方法。
加入工程管理器中參與編譯,并將原本的system_ARMCM7.c從編譯中剔除: 最終呈現(xiàn)的效果如下: 在我們著急開始編譯以驗(yàn)證效果之前,這里有一個細(xì)節(jié)需要分情況討論:
完成了上述步驟,基本上就完成了對新的目標(biāo)芯片的最基礎(chǔ)支持。 【如何設(shè)置開啟編譯優(yōu)化】 MDK在“Option for Target”的'CC'選項(xiàng)卡中提供了簡化的優(yōu)化選項(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也會被傳染。 此外,還有一些更高階的優(yōu)化選項(xiàng)并未提供在Optimisation列表中,例如,最高的性能優(yōu)化'-Ofast',以及更聰明的鏈接優(yōu)化“Link Time Optimisation”,詳細(xì)的使用效果請參考gcc的官方文檔,這里就不再贅述。要想使用它們:
注意,對應(yīng)的編譯選項(xiàng)也要在 'Linker'的Misc Controls中添加: 【如何在編譯成功后打印尺寸信息】 MDK使用GCC進(jìn)行編譯時,默認(rèn)情況下就沒有這么方便了。為了達(dá)到同樣的效果,我們可以在'Options for Target'的“User” 選項(xiàng)卡中增加 After Build/Rebuild命令行:
注意,如果你修改了輸出文件的名稱,千萬要記得同步更新這里的命令行呀! 最終實(shí)現(xiàn)了類似的效果: 【如何優(yōu)雅的測量系統(tǒng)的性能】
具體步驟如下: 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'))); 修改為
注意,千萬不要在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ù)的地方添加對頭文件的包含:
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)行初始化:
編譯后一切正常: 【說在后面的話】
|
|