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