大家好,我是痞子衡,是正經(jīng)搞技術(shù)的痞子。今天痞子衡給大家講的是嵌入式開發(fā)里的project文件。
前面兩節(jié)課里,痞子衡分別給大家介紹了嵌入式開發(fā)中的兩種典型input文件:源文件(.c/.h/.s)、鏈接文件(.icf)。痞子衡要再次提問了,還有沒有input文件呢?答案確實(shí)是有,但這次真的是有且僅有了,本文要介紹的主角project文件也屬于半個(gè)input文件。為什么說是半個(gè)?因?yàn)閜roject文件不僅包含開發(fā)者指定的input信息,還包含很多其他輔助調(diào)試的input/output信息,算是嵌入式開發(fā)中承前啟后的文件。而本文側(cè)重點(diǎn)在于project文件中與開發(fā)者應(yīng)用相關(guān)的input信息,僅當(dāng)?shù)玫搅诉@些input信息,再加上前面介紹的source和linker文件,那么你就已經(jīng)得到了application所有的信息,你可以用它們來可以生成無歧義的可執(zhí)行image binary。
隨著嵌入式軟件工程的發(fā)展,為了應(yīng)對日益復(fù)雜的需求,現(xiàn)代IDE的功能也越來越強(qiáng)大了,IDE版本更迭讓人應(yīng)接不暇,Keil MDK已然踏入5.0時(shí)代,IAR EWARM更是進(jìn)入了8.0時(shí)代,IDE各有千秋,但本文要講的內(nèi)容卻是每個(gè)IDE必須具有的基本功能,還是繼續(xù)以IAR EWARM為例開始今天的內(nèi)容:
一、標(biāo)準(zhǔn)IDE功能
在開始今天的主題之前,痞子衡覺得有必要先簡要給大家科普一下標(biāo)準(zhǔn)IDE應(yīng)該具有的功能?,F(xiàn)代IDE基本都是由組件構(gòu)成,嵌入式開發(fā)中的每個(gè)階段都對應(yīng)著相應(yīng)的組件,由這些組件去實(shí)現(xiàn)各階段的需求。
1.1 IDE組件
標(biāo)準(zhǔn)嵌入式開發(fā)應(yīng)該至少包括以下6個(gè)階段,而IAR里對于每個(gè)階段都有1個(gè)或多個(gè)組件:
輸入(IAR Editor):編輯源文件代碼。
編譯(ICCARM、IASMARM):編譯源文件代碼生成可執(zhí)行二進(jìn)制機(jī)器碼。
分析(C-STAT、MISRA-C):編譯過程中檢查代碼中潛在的問題。
鏈接(ILINK):鏈接可執(zhí)行二進(jìn)制機(jī)器碼到指定ARM存儲空間地址。
下載(I-jet、flashloader):將鏈接好的可執(zhí)行二進(jìn)制機(jī)器碼下載進(jìn)芯片內(nèi)部非易失性存儲器。
調(diào)試(C-SPY、C-RUN):在線調(diào)試代碼在芯片中執(zhí)行情況。
project文件主要用來記錄整合上述6個(gè)階段的所有開發(fā)需求。
1.2 IDE文件類型
既然IDE有很多組件,那么同時(shí)也會存在不同類型的文件以存儲這些組件的所需要的信息。IAR里支持的文件擴(kuò)展類型非常多,痞子衡在這里僅列舉你所創(chuàng)建的工程根目錄下的與工程同名的擴(kuò)展文件,相信你一定會覺得眼熟。
.eww // Workspace file
.ewp // IAR Embedded Workbench project
.ewd // Project settings for C-SPY
.ewt // Project settings for C-STAT and C-RUN</td>
.dep // Dependency information
本文要講的內(nèi)容都包含在.ewp文件里,ewp文件記錄了開發(fā)者為應(yīng)用指定的不可缺少的input信息,沒有這些信息,application工程是不完整的。換句話說,如果你得到了application的所有source文件和linker文件,但沒有ewp文件的話,可能導(dǎo)致最終生成的image binary文件是不同的。
Note:更多IAR支持的擴(kuò)展文件類型請查閱IAR軟件安裝目錄下\IAR Systems\Embedded Workbench xxx\arm\doc\EWARM_IDEGuide.ENU.pdf文檔里的File types一節(jié)。
二、解析project(ewp)文件
前面痞子衡鋪墊了很多IDE/project基礎(chǔ)概念,該是直奔主題的時(shí)候了,本文主角ewp工程文件到底包含哪些開發(fā)者指定的input信息?痞子衡從下面3個(gè)方面為大家揭秘:
2.1 源文件組織
一個(gè)稍微復(fù)雜一點(diǎn)的嵌入式工程,應(yīng)用代碼行數(shù)應(yīng)該是以百行/千行為單位計(jì)算的(此處僅指的是由開發(fā)者自己創(chuàng)建的文件與代碼),我們在組織代碼的時(shí)候肯定不會只創(chuàng)建一個(gè).c文件,單文件會導(dǎo)致代碼功能模塊結(jié)構(gòu)不清晰,不方便工程的管理與維護(hù)。
當(dāng)我們?yōu)楣こ虅?chuàng)建多個(gè)文件時(shí),就會涉及到一個(gè)必然問題:引用路徑問題(所以路徑信息就是本文要說的第一個(gè)input信息)。當(dāng)源文件數(shù)目較多時(shí),通常我們會創(chuàng)建不同文件夾把相同功能的源文件都放在一起,當(dāng)編譯器開始編譯.c源文件時(shí)會搜索include語句所包含的頭文件。熟悉C語言的朋友肯定清楚下面兩種不同include語句的用法:
#include <file.h> // 引用編譯器類庫下的頭文件(IDE安裝路徑)
#include "file.h" // 引用當(dāng)前工程下的頭文件(project路徑)
所以在ewp文件里會包含路徑信息,所有路徑都應(yīng)該列在Options->C/C++ Compiler->Preprocessor下有Additional include directories里,這個(gè)路徑既可以是當(dāng)前PC的絕對路徑,也可以是以ewp文件為基準(zhǔn)的相對路徑,為了保證工程可以在任意PC任意位置下正常編譯,推薦使用如下相對路徑方式列出所有路徑:
ewp當(dāng)前路徑:$PROJ_DIR$/
ewp下級路徑:$PROJ_DIR$/xxFolder/
ewp上級路徑:$PROJ_DIR$/../
說到路徑問題,痞子衡在這里順便給大家介紹一種經(jīng)典的嵌入式工程文件目錄組織方式:
\projectDir
\doc --放置工程文檔
\bsp --放置bsp(板級)相關(guān)的source file
\linker --工程linker文件
\src --板級相關(guān)的源文件(比如pinout,clock等)
\builds\xxBuild\.ewp --工程ewp文件
.eww --工程workspace文件
\src --放置bsp無關(guān)的source file
\platform --芯片頭文件及CMSIS文件
\drivers --芯片片內(nèi)外設(shè)driver
\include --要被所有source引用的頭文件
\startup --標(biāo)準(zhǔn)的startup code
\utilities --標(biāo)準(zhǔn)的通用函數(shù)
\middleware --獨(dú)立的中間件
\components --板級外設(shè)組件driver
\application --當(dāng)前應(yīng)用主邏輯代碼
2.2 全局宏定義
經(jīng)常使用條件編譯的朋友肯定知道workspace文件與project文件的關(guān)系,一個(gè)項(xiàng)目通常只會有一個(gè)eww文件,但卻可能會有多個(gè)ewp文件,這是因?yàn)樵创a里常常會有條件編譯,我們有時(shí)候會給項(xiàng)目不同的配置從而編譯出不同的結(jié)果(速度優(yōu)先/面積優(yōu)先,特性控制...),這些配置就是由全局宏定義來實(shí)現(xiàn)的,打開Options->C/C++ Compiler->Preprocessor下的Defined symbols,在框內(nèi)寫入你需要定義的全局宏:
MACRO1 // 等價(jià)于源文件里的#define MACRO1 (1)
MACRO2=2 // 等價(jià)于源文件里的#define MACRO2 (2)
全局宏信息就是本文要說的第二個(gè)input信息,如果全局宏信息丟失,有時(shí)候工程編譯并不會報(bào)錯(cuò),因?yàn)榫幾g器在處理如下普遍用法里的條件編譯語句時(shí)會默認(rèn)未定義的宏為0,而在處理普遍用法里的條件編譯語句則會報(bào)錯(cuò),所以推薦大家使用第二種條件編譯用法來規(guī)避全局宏問題。
// 普遍用法
#if MACRO
// your code block 1
#else
// your code block 2
#endif
// 推薦用法
#if !defined(MACRO)
#error "No valid MACRO defined!"
#elif (MACRO == 1)
// your code block 1
#else
// your code block 2
#endif
2.3 編譯選項(xiàng)
編譯選項(xiàng)包含了編譯器所需要的所有信息,代碼需經(jīng)過編譯器編譯才能生成二進(jìn)制機(jī)器碼,不同的編譯器選項(xiàng)配置會生成不同的機(jī)器碼,那么需要指定哪些選項(xiàng)呢?打開project的Options選項(xiàng)卡,分別設(shè)置下表item:
Note:更多ewp文件中option解釋請查閱IAR軟件安裝目錄下\IAR Systems\Embedded Workbench xxx\arm\doc\EWARM_IDEGuide.ENU.pdf文檔里的General Options和Compiler Options倆小節(jié)。
編譯設(shè)置信息就是本文要說的第三個(gè)input信息,當(dāng)在project中組織好源文件并設(shè)置好正確的全局宏定義和編譯選項(xiàng),那么恭喜你,你的application設(shè)計(jì)工作已經(jīng)基本完成了。
三、創(chuàng)建demo工程
為方便后續(xù)課程的進(jìn)行,本節(jié)課在最后順便創(chuàng)建一個(gè)demo工程,以下是demo工程的信息:
IDE: IAR EWARM v8.11.2
Device: NXP MKL25Z128VLH4
project layout:
\D\myProject\bsp\builds\demo\demo.ewp
\D\myProject\bsp\linker\iar\KL25Z128xxx4_flash.icf
\D\myProject\bsp\src\startup_MKL25Z4.s (僅保留前16個(gè)系統(tǒng)中斷)
\D\myProject\bsp\src\system_MKL25Z4.c (僅做關(guān)閉WDOG操作)
\D\myProject\bsp\src\system_MKL25Z4.h
\D\myProject\bsp\helloArm.eww
\D\myProject\src\platfrom\CMSIS
\D\myProject\src\platfrom\devices\MKL25Z4
\D\myProject\src\startup\reset.s
\D\myProject\src\startup\startup.c
\D\myProject\src\startup\startup.h
\D\myProject\src\application\main.c
\D\myProject\src\application\task.c
\D\myProject\src\application\task.h
// main.c
//////////////////////////////////////////////////////////
#include "task.h"
const uint32_t s_constant = 0x7f;
int main(void)
{
uint32_t l_variable = 0x7f;
if (s_constant == l_variable)
{
normal_task();
ram_task();
heap_task();
}
while (1);
}
// task.c
//////////////////////////////////////////////////////////
#include "task.h"
static uint32_t s_variable0;
__no_init uint32_t n_variable1;
static uint32_t s_variable2 = 0x5a;
static uint8_t s_array[16];
void normal_task(void)
{
s_variable0 *= 2;
}
__ramfunc void ram_task(void)
{
n_variable1++;
}
void heap_task(void)
{
uint8_t *heap = (uint8_t *)malloc(16 * sizeof(uint8_t));
if (heap != NULL)
{
memset(heap, 0xa5+s_variable2, 16);
memcpy(s_array, heap, 16);
s_variable0 = (uint32_t)heap;
free(heap);
}
}
番外一、幾個(gè)小技巧
又來到痞子衡番外時(shí)間了,細(xì)心的朋友看到上表有兩處標(biāo)藍(lán),是的沒錯(cuò),今天的番外內(nèi)容就是標(biāo)藍(lán)的項(xiàng)目有關(guān)。
技巧1:運(yùn)行于異構(gòu)雙核
目前嵌入式產(chǎn)品越來越復(fù)雜,對MCU的性能要求也越來越高,各大ARM廠商也在不斷推出性能越來越強(qiáng)勁的ARM MCU產(chǎn)品,超高主頻,雙核,四核MCU已經(jīng)不鮮見了。對于其中的一些異構(gòu)雙核MCU產(chǎn)品,有時(shí)在開發(fā)中會有這樣的需求:你有一份的middleware會被異構(gòu)雙核同時(shí)調(diào)用,而兩個(gè)不同內(nèi)核的指令集有可能是不一致的,怎么解決這個(gè)問題?有朋友會想到分別在每個(gè)核下面都編譯一份binary放置于存儲器不同位置,運(yùn)行時(shí)各自指向?qū)?yīng)的binary,這是一個(gè)辦法,但比較浪費(fèi)存儲空間,且有可能會搞混淆導(dǎo)致誤調(diào)用。有沒有更好的方法?
為了能做到Cortex-M軟件重用,ARM公司在設(shè)計(jì)Cortex-M處理器時(shí)為其賦予了處理器向下兼容、軟件二進(jìn)制向上兼容特性。通俗的話來說就是在較低版本處理器上編譯的代碼可以在較高版本處理器上執(zhí)行。所以解決方法就是選用異構(gòu)雙核里較低版本的內(nèi)核在編譯middleware,這樣這份middleware可以同時(shí)被兩個(gè)核調(diào)用。
技巧2:生成PIC代碼
經(jīng)常和bootloader打交道的朋友肯定知道,代碼在經(jīng)過鏈接階段生成binary文件后,這個(gè)binary并不是可以放在任意位置的,必須放到linker文件指定的位置,如果位置沒有放正確,可能會導(dǎo)致執(zhí)行出錯(cuò)。究其原因,是因?yàn)榫幾g器在匯編源代碼時(shí)因?yàn)橐恍┎呗圆⒉豢偸菍⑺衒unction都匯編成位置無關(guān)代碼。如果我們借助于IDE編譯選項(xiàng)將middleware匯編成PIC代碼,那么我們可以在工程中直接加入middleware的binary,然后借助linker的自定義section功能將其放置于任意某個(gè)位置,最后只要為這個(gè)middleware binary建立一個(gè)以binary首地址為基準(zhǔn)的函數(shù)指針地址列表即可無障礙調(diào)用這個(gè)middleware。
技巧3:引用.c文件
在項(xiàng)目開發(fā)中,我們在一個(gè)workspace下會創(chuàng)建多個(gè)project,常常是因?yàn)椴煌琾roject需要包含不同的.c文件以完成不同的功能。那么能不能只創(chuàng)建一個(gè)project呢能實(shí)現(xiàn)不同功能呢?當(dāng)然可以!通常情況下我們在.c文件中只會用#include "xx.h"語句來引用.h頭文件,其實(shí)我們也同樣可以引用.c文件,比如這樣#include "xx.c",只是需要注意盡量不要在.h文件中引用.c文件(除非該.h只會被一個(gè).c文件include)??吹竭@里的朋友如果腦洞再大一點(diǎn),你甚至可以做到工程里只需要添加一個(gè).c文件,而其他.c文件全部由添加進(jìn)工程的那個(gè).c文件逐級(僅能單級)引用進(jìn)工程。
至此,嵌入式開發(fā)里的project文件痞子衡便介紹完畢了,掌聲在哪里~~~