本文在于記錄使用Simulink建模仿真,到最終部署到微控制器上的過程。從Simulink算法模型到目標硬件平臺可以支持的可執行文件,大體經過Matlab的各種模型描述語言,編譯成通用C語言,再在目標硬件平臺的編譯環境中編譯生成最終的可執行文件。市面上的相關的學術研究和實踐經驗,大多是使用了MATLAB已經官方認證集成的BSP,例如:Arduino、TI DSP、STM32、樹莓派等,在這些工具包的加持下,MATLAB配合具體的目標平臺的集成開發環境,可以一通到底。但實際上,MATLAB只要在將Simulink工具描述的算法模型轉化成C語言,完全可以由開發者自行進一步適配的具體平臺,而不需要依賴于具體的官方認證的設備支持包。更進一步,適用于Arm兼容的微控制器,MATLAB已經集成了一個通用的模板,在將MATLAB算法語言轉化成C語言的過程中特別考慮在嵌入式平臺上執行的代碼優化。
本文詳細演繹了從Simulink創建模型,仿真驗證,之后再生成C源碼部署到plus-f5270開發板的全過程。其中描述的方法和操作步驟,為后續部署更多仿真模型奠定了基礎。
Overview
從Simulink中以符號圖形和算法框圖表示的仿真模型,轉化成可編譯的C源碼,再到可以運行在嵌入式平臺的可執行程序,經歷了多個階段的處理,在每個階段需要對應的工具,將上一階段的輸出文件作為本階段的輸入,經過處理后再輸出給后續的環節。
圖x 從Matlab模型到目標平臺可執行文件
圖x完整展示了MATLAB從算法模型到可執行程序的各個環節:
?使用MATLAB、Simulink和Stateflow工具可以在MATLAB中創建模型,此時的模型可能是以邏輯圖符號的方式表示,保存成model.mdl文件
?RealTime Workshop(RTW)是MATLAB中的一個工具,是用來做仿真的軟件開發環境,可以使用描述模型符號及其邏輯關系的model.mdl文件作為輸入,經過本地編譯,形成描述模型的model.rtw文件。RTW文件是以ASCII碼的形式存放,開發者可以直接讀其中的內容。
–TLC目標語言編譯器(Target Language Compiler)是生成C源碼的關鍵環節。TLC目標語言編譯器也是RTW的一部分,可以讀取model.rtw文件
–中的信息,將模型轉化成源代碼的描述。TLC目標語言編譯器可以輸出成多種語言,例如M語言、VHDL語言等,C語言是其中的一種選項。生成C語言也可以使用不同的模板,以約束生成代碼的組織方式和風格,例如,一般實時目標使用grt.tlc模板,嵌入式實時目標使用ert.tlc模板。
–使用RTW生成源碼,還有更多靈活的操作,例如,使用配置宏觀代碼框架的系統TLC文件和配置單個模塊的模塊TLC文件,直接用函數源碼定義算法模塊的S-Function等。RTW不能轉換S-Function的代碼,需要開發者手寫代碼嵌入到生成代碼中。
–RTW還能根據makefile的模板文件,生成可適用于目標硬件平臺編譯環境的makefile文件model.mk。但這個不是必須的,如果某些集成開發環境沒有適配到MATLAB中,就需要手動添加生成的源文件到對應的集成開發環境當中。
?通過TLC目標語言編譯器生成的C源碼文件model.c文件,可以直接加入到嵌入式系統的集成開發環境當中(例如Keil)參與編譯。但實際上,這里生成的C源碼文件主要是描述算法的實現,是應用程序的框架,仍需要開發者在具體嵌入式硬件平臺上,結合實際應用,手動向其中嵌入具體的輸入輸出函數。
?MATLAB的工具生成算法執行的源碼框架,配合目標嵌入式硬件平臺的適配移植,最終在嵌入式硬件平臺的編譯環境中完成編譯鏈接,最終生成可以在目標嵌入式硬件平臺上運行的可執行文件。后續可以使用目標嵌入式硬件平臺的一般操作過程完成下載和調試。
MATLAB、Simulink、StateFlow、Real-Time Workshop之間的關系
?MATLAB 是一個集成開發環境,包含M語言的解析器,同時還提供多種特定應用的工具箱。
?Simulink 是集成在 MATLAB 中的眾多工具箱的一個,作為建模、分析和仿真的交互環境。
?Stateflow 是Simulink中的一個功能組件,通過狀態圖執行選項拓展了 Simulink 的功能,使得可編程的狀態圖也能作為Simulink仿真系統中的一個組件。
?Real-Time Workshop 在早期版本的MATLAB中曾經作為一個獨立的組件,但后來并入Simulink工具箱中,可用于從Simulink模型生成優化的、可移植的和可定制的ANSIC代碼。利用它可以針對某種目標平臺(例如嵌入式平臺)或是部分子系統可下載執行的C代碼,以展開硬件在回路仿真。
?Simulink Coder即是之前的 Real-Time Workshop 和 StateflowCoder,可以從 Simulink 框圖和 Stateflow 系統以及 TargetLink 模型中自動生成 C 代碼。
MATLAB Coder、Simulink Coder、Embedded Coder之間的關系
Mathworks公司的MATLAB軟件環境中有3個代碼生成工具:Matlab Coder、Simulink Coder和Embedded Coder。MATLAB可以將M語言轉化成目標代碼,Simulink Coder可以將Simulink中的仿真模型轉化成目標代碼,而Embedded Coder 依賴于MATLAB Coder以及Simulink Coder的,通過進一步優化,主要生成用于部署在嵌入式平臺上的源碼。
圖x MATLAB中的3中Coder生成器的關系
考慮最終將生成的C代碼部署到嵌入式平臺上,從MATLAB程序和從Simulink模型生成源碼的工具流有一些區別,如圖x所示。
圖x MATLAB中多種Coder生成器的工作流
從MATLAB算法模型到C語言
此處,以實現一個流水燈的模型為例,說明基于模型設計的全過程。
在Simulink中配置模型的開發環境
在MATLAB中啟動Simulink后,可通過工具欄的“建模”->“模型設置”,激活“配置參數”對話框。如圖x所示。
圖x 在Simulink中打開配置參數對話框
在“配置參數”對話框中,首先配置“求解器”。求解器是Simulink用數值方法結算模型切片狀態的引擎,對應于Simulink的仿真過程的行為。這里考慮將來是在嵌入式系統中作為周期任務更新求解過程,設定在Simulink的仿真行為同嵌入式系統類似,為“離散”的“固定步長”,計算步長的時間為10ms,對應于配置對話框中的0.01s。如圖x所示。在實際的嵌入式系統平臺運行求解器時,將使用一個硬件定時器,以10ms的周期觸發中斷,執行求解器的程序。
圖x 在配置參數對話框中配置求解器
在“配置參數”對話框的“硬件實現”標簽中,選擇即將生成代碼的目標平臺。考慮到未來將會在ARM微控制器的平臺上部署(但不在Simulink的支持設備清單中),這里將“設備供應商”選擇為“ARM Compatible”,選擇“設備”類型為“ARM Cortex-M”。特別注意將“Code Generation system target file”選擇成為“ert.tlc”,這是最終生成C語言的配置文件。
圖x 在配置參數對話框中配置硬件實現
實際上,在配置TLC時,配置參數對話框會自動切換到“代碼生成”頁面,在這里選擇使用etc.tlc文件。如圖x所示。
圖x 在配置參數對話框中配置代碼生成
從圖x中可以看到,Simulink提供了多種TLC配置文件可選。這里選擇的ert.tlc,是專門適用于Embedded Coder生成嵌入式系統C代碼的。
配置好項目之后,保存模型文件到文件系統中。從圖x中可以看到,Simulink保存的模型文件可以有兩種后綴名,一種是“.slx”,另一種是“.mdl”,這里,我們使用默認的“.slx”文件后綴名,保存成“leds_model.slx”文件。
圖x 在Simulink中保存模型文件
在MATLAB中創建符號和框圖描述的算法模型
在plus-f5270開發板上設計了4個可編程的LED燈,有原理圖如圖x所示。
圖x plus-f5270開發板上的可編程LED燈
這里要設計一個邏輯,用一個計數器驅動小燈的亮暗狀態,當計數器輸出0時,控制僅LED1亮,當計數器輸出1時,控制僅LED2亮,以此類推。計數器從0到3周期計數,小燈也依次閃爍。
在“庫瀏覽器”中選擇需要的符號元件,拖放到繪圖工作區中,例如,常量“Constant”、等于號“Equal”、有限計數器“Counter Limited”、輸出端點“Out”等,并可用示波器“Scope”和顯示器“Display”查看仿真輸出結果。使用連線,將符號的輸入輸出信號連接在一起,形成一個有輸入輸出的完整系統的功能框圖。如圖x所示。
圖x leds_model模型的功能框圖
從圖x中可以看出,計數器的輸出值驅動4個LED燈的亮暗狀態。但默認情況下,計數器的計數節奏是同求解器的計算步長保持一致的,如之前設定求解器的計算步長是10ms,這就意味著這些小燈會閃爍得非常快,難以被人眼觀察到。這里希望調整計數器的計數節奏為1m,需要調整計數器的計數節奏。雙擊計數器的圖標,可以激活計數器模塊的參數配置對話框,配置其中的“采樣時間”值為1。如圖x所示
圖x 配置計數器的計時步長
此時,使用計數器控制小燈閃爍的流水燈模型就已經搭建完成。
在仿真的工具欄中,點擊“步進”即可逐步運行,點擊“運行”可以全速仿真運行。如圖x所示。
圖x Simulink的仿真工具欄
啟動仿真后,可以通過系統框圖中的示波器模塊查看仿真結果,如圖x所示。
圖x leds_model模型中的示波器模塊顯示仿真結果
從圖x中顯示的示波器輸出波形可以看到,4個輸出通道以1s為控制節奏,依次輸出高電平。驗證仿真結果達到預期設計。
使用TLC目標語言編譯器生成描述算法的C源碼
經過仿真驗證的系統設計框圖,可以直接轉化成嵌入式系統可以可以執行的C程序源碼。在調用RTW編譯模型生成C源碼之前,必須再次確認幾個配置要點:
?配置模型使用ert.tlc,這是專用于嵌入式系統平臺的目標語言編譯器配置選項。
?配置求解器的計算步長,0.001s或者0.01s均可,可根據實際需要的計算時間精度設定,這個時間長度對應于嵌入式系統中部署模型時,周期調用求解器程序的中斷服務程序的周期。
?在MATLAB的主窗口切換當前工作目錄到指定的項目錄下。如圖x所示。即將生成的C代碼工程將位于這里指定的目錄下。
圖x 在MATLAB中設定當前工作目錄
然后,試著生成C源碼。在Simulink的界面中,在工具欄“APP”頁面中選擇“Embedded Coder”激活“C代碼”標簽頁,然后在“C代碼”標簽頁中其中選擇“生成待代碼”。如圖x所示。
圖x 在Simulink中使用Embedded Coder生成C代碼
此時,Simulink的目標語言編譯器TLC將啟動編譯,并生成C源碼程序文件。如圖x所示。
圖x Embedded Coder生成的C源碼
至此,在Simulink中調用Embedded Coder,已經將創建的leds_model模型轉化成了C源碼,保存在一系列源碼文件中。此時,可以在預先設定的當前工作目錄中,找到新建保存生成源碼文件的文件夾“leds_model_ert_rtw”,顧名思義:leds_model是模型的名字;ert是Embedded Real-Time Target,對應源碼生成器的配置選項;rtw是Real-Time Workshop,這是源碼生成器的工具包,在MATLAB的早期版本中獨立作為一個工具包,現在已經被集成在源碼生成器內部。
將模型C源碼集成到嵌入式平臺工程
集成模型C源碼碼到Keil工程中
這里準備了一個plus-f5270開發板上可以運行的工程,plus-f5270_hello_world_mdk,作為部署模型的基礎。
首先,將工程的目錄名改為“plus-f5270_leds_model_mdk” ,便于標識該工程專用于部署leds_model模型。在該工程根目錄下創建“model”目錄,將由Embedded Coder生成的模型C源碼文件全部復制到其中。此時,工程目錄的源碼結構如圖x所示。
圖x 將生成的模型源碼文件復制到Keil工程目錄下
然后,在Keil工程中添加模型源碼,先試著編譯一下,驗證模型文件本身源碼的完整性。如圖x所示。
圖x 在Keil中編譯包含模型源碼的工程
經編譯驗證可知,模型源碼本身是閉包無誤的,不會為基礎工程引入額外的錯誤,也不需要專門針對嵌入式編譯環境調整任何源碼。在Keil中編譯模型文件需要注意:
?需要在Keil工程中為新加入的模型源碼添加源文件搜索路徑。
?不要添加模型的ert_main.c文件。Embedded Coder為模型生成的ert_main.c文件中,包含了在嵌入式環境中調用模型的參考用例,其中包含兩個重要的函數:一個是頂級調用main()函數,另一個是需要基于硬件定時器中實現的中斷回調函數rt_OneStep()。在ert_main.c文件中,有注釋對main()函數和rt_oneStep()函數的用法進行了詳細的解釋。見代碼x所示。
代碼x Embedded Coder為leds_model生成的ert_main.c文件
/*
* File: ert_main.c
*
* Code generated for Simulink model 'leds'.
*
* Model version : 1.4
* Simulink Coder version : 9.8 (R2022b) 13-May-2022
* C/C++ source code generated on : Mon Dec 19 18:09:56 2022
*
* Target selection: ert.tlc
* Embedded hardware selection: ARM Compatible- >ARM Cortex-M
* Code generation objectives: Unspecified
* Validation result: Not run
*/
#include
#include /* This example main program uses printf/fflush */
#include "leds_model.h" /* Model header file */
/*
* Associating rt_OneStep with a real-time clock or interrupt service routine
* is what makes the generated code "real-time". The function rt_OneStep is
* always associated with the base rate of the model. Subrates are managed
* by the base rate from inside the generated code. Enabling/disabling
* interrupts and floating point context switches are target specific. This
* example code indicates where these should take place relative to executing
* the generated code step function. Overrun behavior should be tailored to
* your application needs. This example simply sets an error status in the
* real-time model and returns from rt_OneStep.
*/
void rt_OneStep(void);
void rt_OneStep(void)
{
static boolean_T OverrunFlag = false;
/* Disable interrupts here */
/* Check for overrun */
if (OverrunFlag) {
rtmSetErrorStatus(leds_model_M, "Overrun");
return;
}
OverrunFlag = true;
/* Save FPU context here (if necessary) */
/* Re-enable timer or interrupt here */
/* Set model inputs here */
/* Step the model */
leds_model_step();
/* Get model outputs here */
/* Indicate task complete */
OverrunFlag = false;
/* Disable interrupts here */
/* Restore FPU context here (if necessary) */
/* Enable interrupts here */
}
/*
* The example main function illustrates what is required by your
* application code to initialize, execute, and terminate the generated code.
* Attaching rt_OneStep to a real-time clock is target specific. This example
* illustrates how you do this relative to initializing the model.
*/
int_T main(int_T argc, const char *argv[])
{
/* Unused arguments */
(void)(argc);
(void)(argv);
/* Initialize model */
leds_model_initialize();
/* Attach rt_OneStep to a timer or interrupt service routine with
* period 0.001 seconds (base rate of the model) here.
* The call syntax for rt_OneStep is
*
* rt_OneStep();
*/
printf("Warning: The simulation will run forever. "
"Generated ERT main won't simulate model step behavior. "
"To change this behavior select the 'MAT-file logging' option.\\n");
fflush((NULL));
while (rtmGetErrorStatus(leds_model_M) == (NULL)) {
/* Perform application tasks here */
}
/* Terminate model */
leds_model_terminate();
return 0;
}
/*
* File trailer for generated code.
*
* [EOF]
*/