1 Simulink代碼生成的基本概念(續)
1.1 上一期補充內容
上一篇文章中提到,生成嵌入式代碼,必須選擇定步長求解器。實際中,生成嵌入式代碼幾乎不會使用Simulink模型庫中的連續模型,往往需要通過最簡單的離散模塊來實現算法模型。
所以要強調一點:生成嵌入式代碼,要學會 使用和適應離散模型的搭建方式 。在建模的一開始就要充分考慮離散模型的特點和需求。
一個模型是允許多個離散步長的,這涉及到多任務相關的內容,后續再作詳細介紹。建議讀者在建模的時候,將離散步長(其倒數即為采樣頻率)顯示出來,方便辨別不同模塊的實際步長。顯示離散步長的方法如下:
顯示離散步長 - From autoMBD
同時也建議讀者將端口數據類型顯示出來,因為不同模塊之間的數據傳遞,要保證數據類型相同(后續將介紹數據類型的相關內容)。顯示端口數據類型的方法如下所示:
顯示端口數據類型 - From autoMBD
1.2 Embedded Coder的使用
Embedded Coder工具專門為嵌入式軟件生成代碼而設計,集成了MATLAB Coder和Simulink Coder,可以將m腳本和模型生成C代碼。Embedded Coder可以在下圖位置找到:
Embedded Coder位置 - From autoMBD
單擊“Embedded Coder”便可以進入到Code Perspective窗口。在這個窗口下可以看到四個主要功能區域:
- 工具欄
- 模型區域
- 代碼面板
- 代碼映射面板
如下圖所示:
Code Perspective**四個功能區域 - From autoMBD
Tips :MATLAB/Simulink的版本不同,上述界面可能會有差異,截圖為版本為2020b。
在工具欄中,可以找到大部分關于代碼生成的選項工具或配置入口。模型區域用于編輯和設計模型,如果設置了定步長求解器和ert系統目標文件,單擊工具欄中的“Generate Code”按鈕便可以生成代碼了。
代碼面板用于查看生成的代碼。在代碼面板中,點擊生成的代碼,代碼對應的模型會高亮顯示。這樣方便用戶追蹤模型生成的代碼。
代碼映射面板有很多功能,涉及到的概念和知識點很多,在后續的內容中會逐步講解。
讀者可以自行探索和體驗這四個功能區域的作用和使用方法。
2 詳解模型與代碼之間的聯系
為了便于展示此部分內容,我制作了一個簡易的PI控制模型作為示例。讀者可以在autoMBD資源庫的“臨時資源分享”文件夾中找到該模型(資源序號 tA22 )。資源庫鏈接的獲取可以在《autoMBD原創技術文章合集》中找到(見文章開頭)。
2.1 模型生成的代碼
打開PI控制器示例模型,可以看到模型如下圖所示:
PI控制器示例模型 - From autoMBD
該示例模型已經配置好,點擊“Generate Code”生成代碼。可以發現一共生成了六個文件:
PI控制器示例模型生成的代碼 - From autoMBD
生成的文件位于模型同級目錄下“** 模型名 _ert_rtw**”文件夾內,這六個文件的作用分別是:
- ert_main.c :該文件是一個樣例文件,向用戶展示主程序如何調用模型代碼,在代碼集成時可以參考該文件;
- 模型名.c:該文件包含模型的全部實現方法,包含變量的聲明和定義,Step函數、初始化函數、終止函數等的定義和實現,即“模型的本身”;
- 模型名.h:該文件包含模型所依賴的數據結構、數據類型的定義和聲明,以及模型變量、模型算法函數的外部聲明;
- 模型名_private.h:包含模型本地的宏和數據類型定義,有定義才會生成相關內容;
- 模型名_types.h:該文件包含模塊參數(Parameters)和模型數據(Model Data)的數據結構的向前聲明,在一些可重用函數中可能會被使用;
- rtwtypes.h :定義了基本的數據類型和宏,大部分的生成代碼可能會依賴該文件;
- 模型名_data.c:上圖中沒有生成,但在某些情況下會生成該文件,其中包含模型中的模塊參數(Parameters)、常數模塊和I/O的數據結構的定義和聲明。
對于代碼集成來說,用戶只需要在主函數代碼中,添加下面這個語句,即可使用模型生成的代碼,實現相關的算法和功能:
#inlcude "模型名.h"
讀者可以自行閱讀生成的代碼,初步接觸生成代碼的風格和特點。接下來會詳細介紹模型和代碼之間的對應關系。
2.2 代碼之母——模型
作為MBD的核心,怎么強調對****模型的理解的重要性都不為過。
在嵌入式代碼中,數據是代碼的重要組成部分。 代碼中的數據和模型中的數據是怎么聯系起來的 ,便是今天討論的重點。對于模型,有四個與生成代碼緊密關聯的 模型數據形式 :
- 信號(Signals)
- 參數(Parameters)
- 狀態(States)
- 模型數據(Model Data)
模型中的這四種數據形式,生成的代碼各不相同。通過控制這些模型數據的配置,即可控制生成代碼的數據類型、存儲位置和數據形式。
在開始介紹前,給出模型數據形式的思維導圖:
模型數據形式的思維導圖 - From autoMBD
2.2.1 信號
信號(Signals), 即模型中不同模塊之間的數據傳遞線,有兩種:外部信號和 內部信號 。其中外部信號又分為外部輸入信號和外部 輸出信號 。
以我構建的PI控制器模型為例,模型包含的信號如下圖所示:
PI控制器模型的信號 - From autoMBD
上圖中,Req_Ctrl和Feedback與輸入端口連接,屬于外部輸入信號;PI_Ctrl與輸出端口連接,屬于外部輸出信號;Err、P_value和I_value沒有與任何端口連接,屬于內部信號。
Tips :上圖并沒有標注全部的內部信號,一般只標注有重要、意義的內部信號即可。
保持默認設置情況下生成代碼, 外部信號會生成全局變量 ,其中輸入變量為“ ** 模型名 _U** ”,而輸出變量為“ ** 模型名 _Y** ”。
以本文的示例工程為例,輸入輸出信號生成的全局變量以及相關的數據類型聲明和定義如下所示:
/* 外部輸入數據類型定義 代碼生成于"模型名.h" */
/* External inputs (root inport signals with default storage) */
typedef struct {
real_T Req_Ctrl; /* '< Root >/Req_Ctrl' */
real_T Feedback; /* '< Root >/Feedback' */
} ExtU_autoMBD_example_PI_noSub_T;
/* 外部輸出數據類型定義 代碼生成于"模型名.h" */
/* External outputs (root outports fed by signals with default storage) */
typedef struct {
real_T PI_Ctrl; /* '< Root >/PI_Ctrl' */
} ExtY_autoMBD_example_PI_noSub_T;
/* 外部輸入變量定義 代碼生成于"模型名.c" */
/* External inputs (root inport signals with default storage) */
ExtU_autoMBD_example_PI_noSub_T autoMBD_example_PI_noSubs_U;
/* 外部輸出變量定義 代碼生成于"模型名.c" */
/* External outputs (root outports fed by signals with default storage) */
ExtY_autoMBD_example_PI_noSub_T autoMBD_example_PI_noSubs_Y;
保持默認設置生成代碼時,對于內部信號, 只有具有分叉點的信號線會生成局部變量 ,變量名為“ rtb_信號名 ”。由于是局部變量,它會隨著Step函數的出棧而被釋放。
其他不具備分叉點的內部信號,不會生成任何變量,而是 隱含在算法的運算過程當中 。
以示例工程的內部信號Err為例,模型中僅該信號具有分叉點,它生成的局部變量如下所示:
/* 代碼生成于"模型名.c" */
/* Model step function */
void autoMBD_example_PI_noSubs_step(void)
{
real_T rtb_Err; /* 內部信號Err 變量定義*/
/* Sum: '< Root >/Sum2' incorporates:
* Inport: '< Root >/Feedback'
* Inport: '< Root >/Req_Ctrl'
*/
rtb_Err = autoMBD_example_PI_noSubs_U.Req_Ctrl -
autoMBD_example_PI_noSubs_U.Feedback; /* 內部信號Err 變量計算*/
/* Outport: '< Root >/PI_Ctrl' incorporates:
* DiscreteIntegrator: '< Root >/Discrete-Time Integrator'
* Gain: '< Root >/Kp'
* Sum: '< Root >/Sum1'
*/
autoMBD_example_PI_noSubs_Y.PI_Ctrl = 2.0 * rtb_Err +
autoMBD_example_PI_noSubs_DW.DiscreteTimeIntegrator_DSTATE; /* 內部信號Err 變量使用*/
/* Update for DiscreteIntegrator: '< Root >/Discrete-Time Integrator' incorporates:
* Gain: '< Root >/Ki'
*/
autoMBD_example_PI_noSubs_DW.DiscreteTimeIntegrator_DSTATE += 3.0 * rtb_Err *
0.001; /* 內部信號Err 變量使用*/
}
關于如何修改信號的代碼生成模塊信號(對應生成變量為“ 模型名_B ”),以及其他關于信號的配置選項,在后續的文章中進行介紹。
2.2.2 參數
參數 (Parameters)指的是模塊的參數,例如:本文PI控制器模型中的PI增益模塊,它們的參數分別實現Kp和Ki,模型中的Discrete-Time Integrator模塊也有兩個慘數,具體為采樣時間參數、初始值參數。
保持默認設置情況下, 模塊的參數會作為數值常數,直接用于算法的運算過程 。如下所示PI控制器生成的代碼中,Kp、Ki和采樣時間直接使用其數值2、3和0.001參與算法的運算。
/* 代碼生成于"模型名.c" */
/* Model step function */
void autoMBD_example_PI_noSubs_step(void)
{
real_T rtb_Err;
/* Sum: '< Root >/Sum2' incorporates:
* Inport: '< Root >/Feedback'
* Inport: '< Root >/Req_Ctrl'
*/
rtb_Err = autoMBD_example_PI_noSubs_U.Req_Ctrl -
autoMBD_example_PI_noSubs_U.Feedback;
/* Outport: '< Root >/PI_Ctrl' incorporates:
* DiscreteIntegrator: '< Root >/Discrete-Time Integrator'
* Gain: '< Root >/Kp'
* Sum: '< Root >/Sum1'
*/
/* 直接使用比例增益的數值常量2.0參與計算 */
autoMBD_example_PI_noSubs_Y.PI_Ctrl = 2.0 * rtb_Err +
autoMBD_example_PI_noSubs_DW.DiscreteTimeIntegrator_DSTATE;
/* Update for DiscreteIntegrator: '< Root >/Discrete-Time Integrator' incorporates:
* Gain: '< Root >/Ki'
*/
/* 直接使用積分增益的數值常量3.0和離散步長數值常量0.001參與計算 */
autoMBD_example_PI_noSubs_DW.DiscreteTimeIntegrator_DSTATE += 3.0 * rtb_Err *
0.001;
}
有的時候,我們不希望模塊參數是一個數值常量,而是一個可以修改的變量。例如對Kp和Ki參數進行在線標定時,需要有兩個變量來保存Kp和Ki參數。
關于如何修改模塊參數的代碼生成方式,使其成為一個變量而不是數值常量,可以按照下圖所示步驟進行:
修改模塊參數的生成方式 - From autoMBD
簡單來說,就是讓模型參數是可調(Tunable)的,這樣便會生成一個新的變量來保存模型中所有模塊的參數。
修改模塊參數的生成方式后,重新生成代碼。可以看到,在“ 模型名 .h”中會生成一個新的數據結構體,包含了所有模塊的全部可調參數:
/* 模塊參數數據結構體定義 代碼生成于"模型名.h" */
/* Parameters (default storage) */
struct P_autoMBD_example_PI_noSubs_T_ {
real_T Kp_Gain; /* Expression: 2
* Referenced by: '< Root >/Kp'
*/
real_T DiscreteTimeIntegrator_gainval;
/* Computed Parameter: DiscreteTimeIntegrator_gainval
* Referenced by: '< Root >/Discrete-Time Integrator'
*/
real_T DiscreteTimeIntegrator_IC; /* Expression: 0
* Referenced by: '< Root >/Discrete-Time Integrator'
*/
real_T Ki_Gain; /* Expression: 3
* Referenced by: '< Root >/Ki'
*/
};
在“ 模型名 *types.h”中會對參數結構體聲明新的數據類型,并在“ 模型名 * data.c”(新生成的文件,默認配置無該文件)中定義一個該數據類型的變量,同時幅初值,如下所示:
/* 模塊參數數據類型定義 代碼生成于"模型名_types.h" */
/* Parameters (default storage) */
typedef struct P_autoMBD_example_PI_noSubs_T_ P_autoMBD_example_PI_noSubs_T;
/* 模塊參數變量定義和賦初值 代碼生成于"模型名_data.c "*/
/* Block parameters (default storage) */
P_autoMBD_example_PI_noSubs_T autoMBD_example_PI_noSubs_P = {
/* Expression: 2
* Referenced by: ' Root >/Kp'
*/
2.0,
/* Computed Parameter: DiscreteTimeIntegrator_gainval
* Referenced by: ' Root >/Discrete-Time Integrator'
*/
0.001,
/* Expression: 0
* Referenced by: ' Root >/Discrete-Time Integrator'
*/
0.0,
/* Expression: 3
* Referenced by: ' Root >/Ki'
*/
3.0
};
可以看到,用于存儲模塊參數的變量名為“ 模型名_P ”。重新查看生成的Step函數,可以發現PI控制器算法的計算,不再直接使用數值常量,而是使用變量“ 模型名 _P”進行的運算。
/* 代碼生成于"模型名.c" */
/* Model step function */
void autoMBD_example_PI_noSubs_step(void)
{
real_T rtb_Err;
/* Sum: '< Root >/Sum2' incorporates:
* Inport: '< Root >/Feedback'
* Inport: '< Root >/Req_Ctrl'
*/
rtb_Err = autoMBD_example_PI_noSubs_U.Req_Ctrl -
autoMBD_example_PI_noSubs_U.Feedback;
/* Outport: '< Root >/PI_Ctrl' incorporates:
* DiscreteIntegrator: '< Root >/Discrete-Time Integrator'
* Gain: '< Root >/Kp'
* Sum: '< Root >/Sum1'
*/
/* 使用"模型名_P.Kp_Gain"參與計算 */
autoMBD_example_PI_noSubs_Y.PI_Ctrl = autoMBD_example_PI_noSubs_P.Kp_Gain *
rtb_Err + autoMBD_example_PI_noSubs_DW.DiscreteTimeIntegrator_DSTATE;
/* Update for DiscreteIntegrator: '< Root >/Discrete-Time Integrator' incorporates:
* Gain: '< Root >/Ki'
*/
/* 使用"模型名_P.Ki_Gain"和"模型名_P.DiscreteTimeIntegrator_gainval"參與計算 */
autoMBD_example_PI_noSubs_DW.DiscreteTimeIntegrator_DSTATE +=
autoMBD_example_PI_noSubs_P.Ki_Gain * rtb_Err *
autoMBD_example_PI_noSubs_P.DiscreteTimeIntegrator_gainval;
}
更多關于模塊參數的代碼生成配置方法,將在后續的文章中介紹。
2.2.3 狀態
狀態 (States)是離散系統運算過程中必不可少的元素。
我們知道,離散系統是在每一個離散的時間點上, 運行一次Step函數。某一時刻運行一次Step函數,除了需要輸入數據(通過外部輸入信號輸入)以外, 往往還需要上一個時刻的運算結果 ,甚至之前連續幾個時刻的運算結果。
在嵌入式系統中,這些結果需要一個變量來存儲,這個變量即為 狀態變量 。
在Simulink模型庫中,凡是包含離散因子“ z ”的模塊,全部具有狀態變量。這些模塊在生成代碼時,都會生成一個名為“ 模型名_DW ”的變量來保存狀態變量。
具體而已,在本文PI控制器示例工程中,包含一個離散積分模塊,該模塊具有狀態變量,生成的代碼如下:
/* 數據類型定義 位于"模型名.h"中*/
/* Block states (default storage) for system '< Root >' */
typedef struct {
real_T DiscreteTimeIntegrator_DSTATE;/* '< Root >/Discrete-Time Integrator' */
} DW_autoMBD_example_PI_noSubs_T;
/* 狀態變量定義 位于"模型名.c"中 */
/* Block states (default storage) */
DW_autoMBD_example_PI_noSubs_T autoMBD_example_PI_noSubs_DW;
用戶還可以定義自己的“狀態變量”,通過Data Store Memory模塊即可實現。 Data Store Memory模塊位于Simulink庫中的下圖這個位置:
Data Store Memory - From autoMBD
Data Store Memory模塊與離散模塊一樣,被當作狀態變量,生成在變量“ 模型名 _DW”當中。
雖然我們稱它為狀態變量,但對于Data Store Memory模塊,把它當作普通的變量來使用也是可以的。
更多關于狀態變量的代碼生成,將在后續的文章中介紹。
2.2.4 模型數據
模型數據是Simulink為模型定義的一個數據類型,它保存了模型的部分信息。在代碼生成中,它的數據類型和定義如下圖所示:
/* 模型數據結構體定義 位于"模型名.h"中 */
/* Real-time Model Data Structure */
struct tag_RTM_autoMBD_example_PI_no_T {
const char_T * volatile errorStatus;
};
/* 模型數據類型聲明 位于"模型名.h"中 */
/* Forward declaration for rtModel */
typedef struct tag_RTM_autoMBD_example_PI_no_T RT_MODEL_autoMBD_example_PI_n_T;
/* 模型數據變量外部聲明 位于"模型名.h"中 */
/* Real-time Model object */
extern RT_MODEL_autoMBD_example_PI_n_T *const autoMBD_example_PI_noSubs_M;
/* 模型數據變量定義 位于"模型名.c"中 */
/* Real-time model */
static RT_MODEL_autoMBD_example_PI_n_T autoMBD_example_PI_noSubs_M_;
RT_MODEL_autoMBD_example_PI_n_T *const autoMBD_example_PI_noSubs_M = &autoMBD_example_PI_noSubs_M_;
可以看到,默認情況下,模型數據只包含了一個表示錯誤狀態的字符串,他的變量名為“ 模型名_M ”。在實際中,很少會使用Simulink默認的模型數據定義。
更多關于模型數據的代碼生成,將在后續的文章中介紹。
3 規范建模過程
從上文可以看到,模型的數據形式直接關系到代碼的生成,所以模型的好壞直接影響代碼的可讀性。
為了生成好的代碼,規范建模過程是非常重要的。MathWorks官方發布了建模指南《MathWorks? Advisory Board Control Algorithm Modeling Guidelines》(可以在autoMBD資源庫“臨時資源分享”文件夾中找到該指南的PDF文檔,資源序號 tA23 ,資源庫鏈接可以在文章合集中找到,文章合集的獲取見文章開頭)。
該建模指南在模型的命名、模塊基本配置、模型的框架層級涉及、模型的復用等等多個方面,指導用戶建模。但該文檔內容太多,五百多頁,不是職業工作要求,一般人也很難看完并堅持執行。
但我依然建議讀者保持一個良好的建模習慣,我認為可以從以下幾個方面來做:
- 為端口、信號線、子系統、模塊等命有意義的名字,而不是空著,或命名無意義;
- 建模時,遵循信號從上到下、從左到右的傳遞順序,而不是雜亂無章的到處飛線,盡量避免信號逆向傳遞,無法避免時盡量少而清晰。
- 合理使用子系統模塊、參考子系統模塊、參考模型,構建合理的模型框架和層級;
- 一個功能的模型不要太復雜,復雜的模型可以考慮分層簡化,子系統模型不要嵌套太多層;
- 建模的過程,要考慮模型的可重用性、模型的獨立性;
- 可以使用腳本來定義模型數據,這樣可以擴展模型的功能。
以上是我的一些建模經驗,規范建模不是一朝一夕的事情,是平時的積累和習慣養成。雖然這會花一些精力,但好處是明顯的。為了更好的生成可讀性高的代碼,特別是模型復雜到一定程度時,我提倡保持一個好的建模習慣。
-
嵌入式系統
+關注
關注
41文章
3566瀏覽量
129225 -
狀態機
+關注
關注
2文章
492瀏覽量
27478 -
MBD
+關注
關注
0文章
24瀏覽量
8940 -
PI控制器
+關注
關注
1文章
25瀏覽量
11371 -
simulink仿真
+關注
關注
0文章
75瀏覽量
8560
發布評論請先 登錄
相關推薦
評論