1. 程式人生 > >(控制)把SIMULINK轉成C語言並在VS中呼叫

(控制)把SIMULINK轉成C語言並在VS中呼叫

背景

最近發現SIMULINK其實是可以轉成C語言的(再次感嘆MATLAB的神奇),這個功能大大地減少了我的工作量。由於時間不允許,在轉換過程中我沒有對這裡的一些細節進行研究,而且網路上關於這個方法把這個方法的文章不多(主要是本人懶得看英文資料……),所以把它寫成部落格,既方便以後回來看也希望能和大家交流。

本人程式設計基礎差且沒有對這個方法進行深究,這篇文章僅作為本人筆記和交流知識的作用,文中很可能有大量錯誤,也希望大家能指出

平臺

  1. VS 2013
  2. MATLAB 2015a
  3. Windows 10

方法

下面以建立一個PID控制器為例說明這個方法的過程。

首先在SIMULINK中搭建一個模型,注意給模型新增起碼一個輸入埠和一個輸出埠。在這裡,因為我需要搭一個PID控制器,所以另外分別加入P、I、D三個輸入埠,最終得到的模型如下圖所示,注意這裡埠的命名,轉成C程式碼後輸入輸出點的命名將和他們相對應。因為我最後是用這個控制器來控制雲臺攝像頭的轉動的,其實這裡應該設計成離散控制系統,由於本人偷懶,隨便做了個連續的PID控制器就用了起來,最後調整好PID後效果也還行,就不管了。
PID控制器

然後在當前的模型視窗中依次開啟Simulation->Model Configuration Parametersta彈出以下視窗。
設定

一定要把Stop time設為inf,把Solver options下的Type設為Fixed-step編譯才能正常進行,否則會報錯。這裡設定的好像是系統狀態更新的方式, 我沒有仔細研究,如果大家希望詳細瞭解這些選項的意思可以把滑鼠放在相關選項上然後右擊,會出來一個What’s this選項,點進去就可以(感嘆MATLAB的友好度)。

點選右側視窗的Code Generation按鍵可以選擇生成C程式碼(預設)或者C++程式碼,以及選定IDE。

另外對於要在微控制器中實現控制的同學可以在右側視窗點選Hardware Implementation,會出來一個選擇裝置的下拉選框,這個可能需要設定一下(沒有經過測試),但是因為本文只要在VS中實現控制,所以就不需要選了。

設定好之後關閉Configuration視窗,依次點選Code->C/C++ Code->Build Model。此時SIMULINK已經開始編譯了,等到MATLAB的Command Window視窗進入就緒狀態,這時就已經編譯成功了。開啟MATLAB當前的工作目錄你會發現多了一個資料夾,資料夾名稱為“你的模型名稱”+_grt_rtw。例如我這裡的模型為PIDController.slx,那麼生成的資料夾名稱為PIDController_grt_rtw。為了便於說明,MATLAB自動生成的檔案和函式等的命名一併以這個例子為例,具體情況請根據相應的模型名替換。在資源管理器中開啟這個資料夾你會發現裡面有一個批處理檔案,雖然不知道有什麼用,我一般都會執行一下(-.- !)。

下一步就是把這些檔案匯入到VS裡面去了。先建立一個VS的專案。可以選擇把這一整個資料夾的檔案拷進VS的工程檔案裡,或者在VS工程裡新增這個資料夾作為標頭檔案目錄和原始碼目錄。在標頭檔案中右擊,點選新增->現有項…,然後把PIDController_grt_rtw裡面所有的標頭檔案和原始檔新增進工程。

在這些檔案中我們只要關心PIDController.h這個檔案就行了。在工程的入口函式所在的原始檔處加上

include “PIDController.h”

這一行。接下來Build這個工程,你會發現有很多找不到檔案的錯誤,這是因為PIDController_grt_rtw這個資料夾中的原始碼都引用了MATLAB安裝目錄底下的一些原始碼。因為找不到更好的方法,我只能按照報錯的源頭一個一個檔案地從MATLAB安裝目錄中找出來並且新增進工程。我這個應該是很笨的方法,如果有簡單的方法請告訴我。

把所有應該新增的檔案都新增進來之後就可以在程式中呼叫這個PID控制器了。閱讀PIDController.h這個檔案你會發現有以下程式碼


//...
/* External inputs (root inport signals with auto storage) */
extern ExtU_PIDController_T PIDController_U;

/* External outputs (root outports fed by signals with auto storage) */
extern ExtY_PIDController_T PIDController_Y;
/* Model entry point functions */
extern void PIDController_initialize(void);
extern void PIDController_step(void);
extern void PIDController_terminate(void);

從註釋我們可以看出這PIDController_U和PIDController_Y分別是模型的輸入和輸出;PIDController_initialize(void)、PIDController_step(void)和PIDController_terminate(void)三個函式是模型的入口函式。

進一步研究PIDController_U和PIDController_Y的定義,我們可以發現它們其實是兩個結構體,分別對應著模型中的輸入埠和輸出埠。

/* External inputs (root inport signals with auto storage) */
typedef struct {
  real_T In1;                          /* '<Root>/In1' */
  real_T I;                            /* '<Root>/I' */
  real_T D;                            /* '<Root>/D' */
  real_T P;                            /* '<Root>/P' */
} ExtU_PIDController_T;
/* External outputs (root outports fed by signals with auto storage) */
typedef struct {
  real_T Out1;                         /* '<Root>/Out1' */
} ExtY_PIDController_T;

PIDController_initialize(void)、PIDController_step(void)和PIDController_terminate(void)三個函式的功能分別如下:
- PIDController_initialize(void): 初始化模型
- PIDController_step(void):對模型的輸出埠進行一次更新,有點像PLC中的一個週期
- PIDController_terminate(void):結束模型

實際使用如下

PIDController_initialize();
PIDController_U.I = 0.0193;
PIDController_U.P = 1.625;
PIDController_U.D = 2.67;
while (!quit_flag){

    //TODO 檢測被控量,更新模型的輸入newInput

    PIDController_U.In1 = newInput;
    PIDController_step();
    newOutput = PIDController_Y.Out1;

    //TODO 把新的輸出newOutput作為被控物件的輸入

}
PIDController_terminate(void);