[Simulink] MBD開發嵌入式軟件的C語言基礎

本文轉載並改編自董淑成 基於模型的設計 微信公衆號,侵刪,原文鏈接 https://mp.weixin.qq.com/s/c4G907PBnyHMI_uMrCmSlw

工作過程中,隨着使用Simulink建模的深入,越發覺得C語言的重要性,尤其是將自動生成的代碼與底層代碼做集成的時,如果不瞭解C語言,在開發過程中很不方便。

|-引言 —— C語言中.h文件的作用

當前技術背景下,工程化的項目已經沒有小到一個文件就可以搞定的了,但凡有點規模的項目,基本上都是模塊化開發的。模塊化開發的時候,對應每個模塊,通常會寫兩個文件,一個.c,另一個.h,比如module1.c,module1.h
.c文件通常用於定義變量和實現函數,而.h文件,除了定義一些公用的宏和類型之外,還會有下面這樣的代碼:

      extern uint8 var1;
      extern uint8 var2;
      extern void myfunction(void);

這裏 var1、var2是全局變量,而myfunction()是全局函數。

.h文件的主要作用是接口,不同模塊之間的接口
舉個例子:在module2模塊中如果要用到module1裏面定義的全局變量或者全局函數,只要在module2.h文件中加這樣一樣代碼:#include “module1.h”。負責module1模塊的工程師,如果想調用module2模塊提供的全局函數,也只要看一下module2.h即可。

所以老胡經常跟用戶強調不要去讀自動生成的代碼,如果一定要讀,也只要讀.h文件即可。

我們在.h文件中經常會看到這樣一段代碼:

#ifndef _MODULE1_H_
#define _MODULE1_H_

然後在.h文件的最末尾有:

#endif

這樣的代碼在.c文件中沒有,請問,這又是什麼意思?下面給出答案

|-條件編譯的作用

舉個例子:如果一個項目有10個模塊,module1爲其中的一個模塊,其他模塊在用到module1中定義的全局變量或者全局函數的時候,通常會在自己模塊的.h文件中加這樣一行代碼#include “module1.h”

問題來了:如果10個模塊中的9個模塊都有這樣一行代碼,在預編譯的時候,就會重複加載很多次module1.h文件。怎麼解決這個問題?

可以通過宏_MODULE1_H_來確認是否已經加載過module1.h裏的代碼,第一次加載的時候,發現沒有定義過宏_MODULE1_H_,也就是#ifndef _MODULE1_H_爲真,於是定義這個宏,並加載後面的代碼,在第二次再遇到#include “module1.h”的時候,已經有_MODULE1_H_這個宏了,#ifndef _MODULE1_H_後面的內容就不再被考慮,直接到#endif。

條件編譯,顧名思義,也就是條件成立的時候給後面的內容做編譯,條件不成立,則直接跳過。

|-從C到hex/exe

編譯,通常我們會說把C編譯成hex文件。其實,這種說法是不準確的,當然,你也可以認爲這裏的編譯是廣義上的編譯,也就是我們經常在各種編譯工具中的IDE界面下看到的Build,沒錯,Build更準確一些(Build是否應該翻譯成“構建”?)。
從C到hex的整個Build過程會經歷編譯鏈接兩個階段。

編譯

編譯,就是把C代碼轉換成目標代碼,通常擴展名爲.obj或者.o。
目標代碼不是最終的二進制碼,目標代碼中沒有代碼和變量的地址信息,編譯過程中不會提示你某個變量沒有定義或者某個變量被多次定義,只要你合理的使用extern關鍵字。

鏈接

地址信息是在鏈接(Link)過程中加上去的,鏈接器會把編譯好的目標代碼根據鏈接文件,把代碼和數據分配到合適的存儲區域。

|-extern關鍵字

編譯階段

這個關鍵字是給編譯器看的,編譯器遇到這個關鍵字,就知道這個關鍵字修飾的那個全局變量或者全局函數不在本文件中定義,而是在其他文件中有定義,所以編譯器在編譯本文件的時候不會因爲沒有定義這個全局變量或者全局函數而提示“未定義”之類的錯誤。

鏈接階段

當然,如果到了鏈接階段,鏈接器找遍了所有的目標文件,都找不到你所謂的extern變量的話,那個時候,鏈接器就會提示undefined reference 了。

|-static變量和全局變量

C語言中static變量和全局變量一樣,編譯、鏈接之後會分配固定的RAM地址,不同的是,static變量的作用域僅限於文件級別,你沒法讓module1中定義的staitc變量被module2模塊使用,自然,加了static關鍵字的變量,也不會在module1.h文件中做extern聲明。

另外,static除了可以修飾變量之外,還可以修飾函數。

換言之,static和extern兩個關鍵字不能同時出現。

|- #pragma的作用

翻開C標準,你會發現,#pragma在標準裏並沒有明確的定義,而是預留給編譯器擴展使用,通常,大多數編譯器使用如下格式使用:

#pragma mysection begin
Uint8 var1;
Uint16 var2;
#pragma mysection end

需要強調的是這種格式並非標準化的格式,不同的編譯器之間可能有所不同,使用前需要查看編譯器手冊,使用正確的格式

上面的4行代碼是什麼作用呢?中間兩行顯然是定義了兩個變量var1和var2,前後的兩行#pragma是把這兩個變量放到了mysection區域,而這個mysection的地址範圍是在link文件中定義的。
當然,也可以使用@將變量分配到某一地址下,不過這種做法在Simulink模型中實現起來非常麻煩,不建議使用。

|-宏定義 #define

關於#define,我們在代碼中可能會看到這樣一些使用方式

	#define MY_MACRO
    #define  K      30
    #define MAX(a,b)  (a>b)?a:b

先說第一種,就是定義了MY_MACRO,MY_MACRO就是一個宏,這個宏就像一個標記,後續代碼可以判斷是否有這個宏,有的話怎麼樣,沒有的話又怎麼樣,.h的文件最開始的那段,就是這種用法。
第二種,就是把K定義爲30,後續代碼中凡是遇到K的地方,編譯器編譯的時候,就直接替換成30。
第三種,相當於用宏的方式定義了一個函數,跟普通的函數定義不同,在宏定義函數的時候無需定義形參的數據類型,而普通函數的形參是需要定義數據類型的,爲什麼呢?

在函數中,形參和實參是兩個不同的變量,都有自己的作用域,調用時要把實參的值傳遞給形參;而在帶參數的宏中,只是符號的替換,不存在值傳遞的問題,所以不需要定義形參數據類型

|- 定點數

定點數就是用整數來表達小數,計算機裏並沒有定點數這種東西,所以我們在定義數據類型的時候,Simulink模型中可能會使用fixdt,而到C代碼中,只能看到uint16,uint32等等。定點數中的小數點,是我們假設的,我們會在整數的某個位置假設有一個小數點,那麼小數點左邊的是整數部分,小數點右邊的是小數部分,當然這些計算機並不知道,它依然當成整數運算處理,所以對於編程者來說,需要在運算前後做一些移位對齊小數點的工作,如果你用Simulink模型開發軟件,移位對齊工作就不需要你去折騰了,代碼生成的時候會被考慮進去。

結個尾

原文作者老胡給MBD開發者的建議是 simulink、stateflow要學,S函數和TLC不要碰,之前不理解,現在很有道理。
舉個例子: waijung早期推出基於STM32的MBD開發平臺,就是利用一大堆TLC實現底層驅動。之前還覺得有用,現在發現只需要使用STM32CubeMX做底層配置,再加一點自己需要的底層代碼,Simulink推出的C Caller完美解決上述問題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章