痞子衡嵌入式:ARM Cortex-M開發檔案詳解(3)- 工程檔案(.ewp)

無痕幽雨發表於2018-03-23

出處:https://www.cnblogs.com/henjay724/p/8232585.html

大家好,我是痞子衡,是正經搞技術的痞子。今天痞子衡給大家講的是嵌入式開發裡的project檔案

  前面兩節課裡,痞子衡分別給大家介紹了嵌入式開發中的兩種典型input檔案:source檔案linker檔案。痞子衡要再次提問了,還有沒有input檔案呢?答案確實是有,但這次真的是有且僅有了,本文要介紹的主角project檔案也屬於半個input檔案。為什麼說是半個?因為project檔案不僅包含開發者指定的input資訊,還包含很多其他輔助除錯的input/output資訊,算是嵌入式開發中承前啟後的檔案。而本文側重點在於project檔案中與開發者應用相關的input資訊,僅當得到了這些input資訊,再加上前面介紹的source和linker檔案,那麼你就已經得到了application所有的資訊,你可以用它們來可以生成無歧義的可執行image binary。
  隨著嵌入式軟體工程的發展,為了應對日益複雜的需求,現代IDE的功能也越來越強大了,IDE版本更迭讓人應接不暇,Keil MDK已然踏入5.0時代,IAR EWARM更是進入了8.0時代,IDE各有千秋,但本文要講的內容卻是每個IDE必須具有的基本功能,還是繼續以IAR EWARM為例開始今天的內容:

一、標準IDE功能

  在開始今天的主題之前,痞子衡覺得有必要先簡要給大家科普一下標準IDE應該具有的功能。現代IDE基本都是由元件構成,嵌入式開發中的每個階段都對應著相應的元件,由這些元件去實現各階段的需求。

1.1 IDE元件

  標準嵌入式開發應該至少包括以下6個階段,而IAR裡對於每個階段都有1個或多個元件:

  • 輸入(IAR Editor):編輯原始檔程式碼。
  • 編譯(ICCARM、IASMARM):編譯原始檔程式碼生成可執行二進位制機器碼。
  • 分析(C-STAT、MISRA-C):編譯過程中檢查程式碼中潛在的問題。
  • 連結(ILINK):連結可執行二進位制機器碼到指定ARM儲存空間地址。
  • 下載(I-jet、flashloader):將連結好的可執行二進位制機器碼下載進晶片內部非易失性儲存器。
  • 除錯(C-SPY、C-RUN):線上除錯程式碼在晶片中執行情況。

  project檔案主要用來記錄整合上述6個階段的所有開發需求。

1.2 IDE檔案型別

  既然IDE有很多元件,那麼同時也會存在不同型別的檔案以儲存這些元件的所需要的資訊。IAR裡支援的檔案擴充套件型別非常多,痞子衡在這裡僅列舉你所建立的工程根目錄下的與工程同名的擴充套件檔案,相信你一定會覺得眼熟。

.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

  本文要講的內容都包含在.ewp檔案裡,ewp檔案記錄了開發者為應用指定的不可缺少的input資訊,沒有這些資訊,application工程是不完整的。換句話說,如果你得到了application的所有source檔案和linker檔案,但沒有ewp檔案的話,可能導致最終生成的image binary檔案是不同的。

Note:更多IAR支援的擴充套件檔案型別請查閱IAR軟體安裝目錄下\IAR Systems\Embedded Workbench xxx\arm\doc\EWARM_IDEGuide.ENU.pdf文件裡的File types一節。

二、解析project(ewp)檔案

  前面痞子衡鋪墊了很多IDE/project基礎概念,該是直奔主題的時候了,本文主角ewp工程檔案到底包含哪些開發者指定的input資訊?痞子衡從下面3個方面為大家揭祕:

2.1 原始檔組織

  一個稍微複雜一點的嵌入式工程,應用程式碼行數應該是以百行/千行為單位計算的(此處僅指的是由開發者自己建立的檔案與程式碼),我們在組織程式碼的時候肯定不會只建立一個.c檔案,單檔案會導致程式碼功能模組結構不清晰,不方便工程的管理與維護。
  當我們為工程建立多個檔案時,就會涉及到一個必然問題:引用路徑問題(所以路徑資訊就是本文要說的第一個input資訊)。當原始檔數目較多時,通常我們會建立不同資料夾把相同功能的原始檔都放在一起,當編譯器開始編譯.c原始檔時會搜尋include語句所包含的標頭檔案。熟悉C語言的朋友肯定清楚下面兩種不同include語句的用法:

#include <file.h>           // 引用編譯器類庫下的標頭檔案(IDE安裝路徑)
#include "file.h"           // 引用當前工程下的標頭檔案(project路徑)

  所以在ewp檔案裡會包含路徑資訊,所有路徑都應該列在Options->C/C++ Compiler->Preprocessor下有Additional include directories裡,這個路徑既可以是當前PC的絕對路徑,也可以是以ewp檔案為基準的相對路徑,為了保證工程可以在任意PC任意位置下正常編譯,推薦使用如下相對路徑方式列出所有路徑:

ewp當前路徑:$PROJ_DIR$/
ewp下級路徑:$PROJ_DIR$/xxFolder/
ewp上級路徑:$PROJ_DIR$/../

  說到路徑問題,痞子衡在這裡順便給大家介紹一種經典的嵌入式工程檔案目錄組織方式:

\projectDir
           \doc                            --放置工程文件

           \bsp                            --放置bsp(板級)相關的source file
                  \linker                    --工程linker檔案
                  \src                       --板級相關的原始檔(比如pinout,clock等)
                  \builds\xxBuild\.ewp       --工程ewp檔案
                  .eww                       --工程workspace檔案

           \src                            --放置bsp無關的source file
                  \platform                  --晶片標頭檔案及CMSIS檔案
                  \drivers                   --晶片片內外設driver
                  \include                   --要被所有source引用的標頭檔案
                  \startup                   --標準的startup code
                  \utilities                 --標準的通用函式
                  \middleware                --獨立的中介軟體
                  \components                --板級外設元件driver
                  \application               --當前應用主邏輯程式碼

2.2 全域性巨集定義

  經常使用條件編譯的朋友肯定知道workspace檔案與project檔案的關係,一個專案通常只會有一個eww檔案,但卻可能會有多個ewp檔案,這是因為原始碼裡常常會有條件編譯,我們有時候會給專案不同的配置從而編譯出不同的結果(速度優先/面積優先,特性控制...),這些配置就是由全域性巨集定義來實現的,開啟Options->C/C++ Compiler->Preprocessor下的Defined symbols,在框內寫入你需要定義的全域性巨集:

MACRO1            // 等價於原始檔裡的#define MACRO1 (1)
MACRO2=2          // 等價於原始檔裡的#define MACRO2 (2)

  全域性巨集資訊就是本文要說的第二個input資訊,如果全域性巨集資訊丟失,有時候工程編譯並不會報錯,因為編譯器在處理如下普遍用法裡的條件編譯語句時會預設未定義的巨集為0,而在處理普遍用法裡的條件編譯語句則會報錯,所以推薦大家使用第二種條件編譯用法來規避全域性巨集問題。

// 普遍用法
#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 編譯選項

  編譯選項包含了編譯器所需要的所有資訊,程式碼需經過編譯器編譯才能生成二進位制機器碼,不同的編譯器選項配置會生成不同的機器碼,那麼需要指定哪些選項呢?開啟project的Options選項卡,分別設定下表item:

PositionItemDescription
General Options->Target->Processor variant->Core指定ARM核心版本
Endian mode指定核心大小端模式
Floating point settings->FPU指定核心支援的FPU版本
General Options->Library Configuration->Library選擇C/C++動態連結庫版本
General Options->Library Option 2->Heap selection選擇HEAP實現版本
C/C++ Compiler->Language 1->Language指定程式語言型別
Language 1->C dialect指定C語言標準
Language 1->Language conformance選擇對標準C/C++的遵循程度
Language 2->Plain 'char' is選擇對char的符號性預設處理方法
Language 2->Floating-point semantics選擇對浮點數的處理遵循C標準的程度
Code->Process mode指定核心指令集模式
Code->Position-independence選擇要生成位置無關程式碼的物件
Optimizations->Level選擇優化等級

Note:更多ewp檔案中option解釋請查閱IAR軟體安裝目錄下\IAR Systems\Embedded Workbench xxx\arm\doc\EWARM_IDEGuide.ENU.pdf文件裡的General Options和Compiler Options倆小節。

  編譯設定資訊就是本文要說的第三個input資訊,當在project中組織好原始檔並設定好正確的全域性巨集定義和編譯選項,那麼恭喜你,你的application設計工作已經基本完成了。

三、建立demo工程

  為方便後續課程的進行,本節課在最後順便建立一個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個系統中斷)
    \D\myProject\bsp\src\system_MKL25Z4.c   (僅做關閉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);
    }
}

番外一、幾個小技巧

  又來到痞子衡番外時間了,細心的朋友看到上表有兩處標藍,是的沒錯,今天的番外內容就是標藍的專案有關。

技巧1:執行於異構雙核

  目前嵌入式產品越來越複雜,對MCU的效能要求也越來越高,各大ARM廠商也在不斷推出效能越來越強勁的ARM MCU產品,超高主頻,雙核,四核MCU已經不鮮見了。對於其中的一些異構雙核MCU產品,有時在開發中會有這樣的需求:你有一份的middleware會被異構雙核同時呼叫,而兩個不同核心的指令集有可能是不一致的,怎麼解決這個問題?有朋友會想到分別在每個核下面都編譯一份binary放置於儲存器不同位置,執行時各自指向對應的binary,這是一個辦法,但比較浪費儲存空間,且有可能會搞混淆導致誤呼叫。有沒有更好的方法?
  為了能做到Cortex-M軟體重用,ARM公司在設計Cortex-M處理器時為其賦予了處理器向下相容軟體二進位制向上相容特性。通俗的話來說就是在較低版本處理器上編譯的程式碼可以在較高版本處理器上執行。所以解決方法就是選用異構雙核裡較低版本的核心在編譯middleware,這樣這份middleware可以同時被兩個核呼叫。

技巧2:生成PIC程式碼

  經常和bootloader打交道的朋友肯定知道,程式碼在經過連結階段生成binary檔案後,這個binary並不是可以放在任意位置的,必須放到linker檔案指定的位置,如果位置沒有放正確,可能會導致執行出錯。究其原因,是因為編譯器在彙編原始碼時因為一些策略並不總是將所有function都彙編成位置無關程式碼。如果我們藉助於IDE編譯選項將middleware彙編成PIC程式碼,那麼我們可以在工程中直接加入middleware的binary,然後藉助linker的自定義section功能將其放置於任意某個位置,最後只要為這個middleware binary建立一個以binary首地址為基準的函式指標地址列表即可無障礙呼叫這個middleware。

技巧3:引用.c檔案

  在專案開發中,我們在一個workspace下會建立多個project,常常是因為不同project需要包含不同的.c檔案以完成不同的功能。那麼能不能只建立一個project呢能實現不同功能呢?當然可以!通常情況下我們在.c檔案中只會用#include "xx.h"語句來引用.h標頭檔案,其實我們也同樣可以引用.c檔案,比如這樣#include "xx.c",只是需要注意儘量不要在.h檔案中引用.c檔案(除非該.h只會被一個.c檔案include)。看到這裡的朋友如果腦洞再大一點,你甚至可以做到工程裡只需要新增一個.c檔案,而其他.c檔案全部由新增進工程的那個.c檔案逐級(僅能單級)引用進工程。

  至此,嵌入式開發裡的project檔案痞子衡便介紹完畢了,掌聲在哪裡~~~

來自網友劉浩的私信問題:
經典嵌入式工程檔案目錄組織方式中,你提到分三個目錄,關於分目錄的原則是與晶片或平臺相關的都應分離開來,但是你跟的BSP目錄下有個\src目錄,我不太清楚次目錄裡要存放什麼內容,能否舉個例子;

痞子衡解答:
經典工程目錄有兩個src資料夾,統一解釋這兩個資料夾作用:
\projectDir\src 這個資料夾主要存放與硬體無關的抽象出來的high-level程式碼,工程的主業務邏輯都在這裡。
\projectDir\bsp\src 這個資料夾就是存放所有硬體/板級相關的low-level程式碼,比如時鐘初始化,引腳配置等。

簡單總結就是,當你的應用工程從某硬體平臺(比如Freescale Kinetis KL25Z4)移植到另一個硬體平臺(ST STM32F103),原則上 要做到只需要改動\projectDir\bsp\src裡面的原始檔就可以了。

來自網友劉浩的私信問題:
IAR工程中是否可以同時管理多個專案,其中專案共用的.c/.h不動,每次都只是新增和編譯與我專案相關的.c\.h,然後進行編譯,這樣便於同個架構下多個專案的管理;

痞子衡解答:
IAR支援多個專案的管理,這在文章中已有提及,一個工程folder下一般只有一個eww檔案(工作空間,用於管理project),但可以建立多個ewp檔案(工程檔案),ewp檔案共享當前目錄下的所有source檔案,source檔案按工程需要新增進具體工程。




相關文章