MATLAB Coder, Simulink Coder (旧名 Real-Time Workshop)

□ Simulink のコード生成

モデルファイル *.mdl は, 最初に, Simulink Coder で, モデル記述ファイル *.rtw に変換される。*.rtw はオリジナルのモデルを高レベル言語で記述していて, Target Language Compiler に渡される。次に, *.tlc ファイルに記述されたルールに従って, Cコードが割り当てられ, Target Language Compiler によってターゲットに向けたCコードが作られる。

 

□ サンプル

NewSimModel.zip

コード生成

①モデル

f:id:sato-7411:20211102233917p:plain

②ソルバー

f:id:sato-7411:20211102234037p:plain

③ターゲットの選択

f:id:sato-7411:20211102234129p:plain

④モデルのビルド

f:id:sato-7411:20211102234208p:plain

⑤結果

f:id:sato-7411:20211102234328p:plain

コンパイル・ビルドのために必要なフォルダ
simulink/r2018b/rtw/c/src (前もってコンパイルしてライブラリを作る)
simulink/r2018b/include

 

 

□ MATLAB ドキュメント

rtw_gs_ja_JP
rtw_ug_ja_JP
rtw_ref_ja_JP
rtw_tlc
rn
coder_gs_ja_JP
coder_ug_ja_JP
coder_ref_ja_JP
rn (1)
rtw_getting_started 

 

□ 説明

作成されるファイル

NewSimModel.rtw
  コンパイル済みモデルの ASCII ファイル
  デフォルトでは Real-Time Workshop はビルド完了後に本ファイルを削除する

NewSimModel.c
  NewSimModel.h と NewSimModel_private.h をインクルードする
  NewSimModel_data.c 以外のデータを定義
  GRT ラッパー関数 (MdlStart, MdlOutputs, MdlUpdate, MdlInitializeSizes, MdlInitializeSampleTimes)
  モデル登録コード
  アルゴリズムコード

NewSimModel.h
  モデルについて次を定義する
    データ構成
    入出力インターフェイス
    リアルタイムモデルデータ構成 (NewSimModel_rtM) のアクセスマクロを経由したインターフェイス
  モデルに次をインクルードする
    Simulink データシンボルの出力
    Stateflow マシン親データの出力
    rtM を含んだモデルデータ構成
    モデル入出力関数

NewSimModel_private.h
  モデルに必要なローカル定数とローカルデータの定義
  生成されたコードに自動的にインクルードされる
  次を含む
    Simulink データシンボルの入力
    Stateflow マシン親データの入力
    Stateflow 入出力
    Real-Time Workshop 詳細 (様々なマクロ, 列挙型, 等)

NewSimModel_types.h
  リアルタイムモデルデータ構成とパラメータデータ構成の事前定義

NewSimModel_data.c
  条件に応じて, 生成されたコードは, パラメータデータ構成と定数ブロックI/Oデータ構成を含む
  これらデータ構成が使われていなければ, NewSimModel_data.c は生成されない
  これらデータ構成は NewSimModel.h で extern 宣言される
  次を含む
    定数ブロックI/Oパラメータ
    NewSimModel.h と NewSimModel_private.h のインクルード
    定数パラメータ

subsystem.c
  サブシステムが別ファイルで書かれたときの, 各, 非インライン, 非仮想, のサブシステムのコード

subsystem.h
  非インライン, 非仮想, のサブシステムの外部シンボル

NewSimModel.exe (PC上) 又は NewSimModel (UNIX上) ※ build フォルダでなく現フォルダに作られたもの
  make ユーティリティを使って開発システムが作った実行ファイル

NewSimModel.mk
  Real-Time Workshop が生成した makefile。本ファイルで実行ファイルをビルドする

rtmodel.h
  grt_main.c や grt_malloc_main.c などの静的メインプログラムに必要な #include 文を含む
  これらモジュールはコード生成時に作られるものではないため, モデル固有のデータ構成や入出力にアクセスするために rtmodel.h をインクルードする
  自分のメインプログラムモジュールを作る場合は rtmodel.h をインクルードすること

rtwtypes.h
  GRT ターゲットでは tmwtypes.h と simstruc_types.h をインクルードする
  Real-Time Workshop Embedded Coder ERT ターゲットでは, ビルド設定に応じて rtwtypes.h は最適化され, 定義, 列挙型, などは tmwtypes.h や simstruc_types.h に直接含まれる

rt_nonfinite.c
  inf や minus inf や nan のためのグローバル非定型値の宣言と初期化

rt_nonfinite.h
  非定型の値や関数の外部参照を定義

rtw_proj.tmw
  現在の Real-Time Workshop プロジェクトの名前を変更したとき, いつオブジェクトをリビルドするかを決めるために, make ユーティリティによって使われるファイル

 
使用するフォルダ

作業フォルダ
  「実行ファイルを生成する」を選択した場合に, Real-Time Workshop は NewSimModel.exe (PC上) 又は NewSimModel (UNIX上) を作業フォルダに作成する

ビルドフォルダ NewSimModel_grt_rtw (model_target_rtw)
  生成されたコードと他のすべてのファイル (実行ファイル以外) を保存する

プロジェクトフォルダ slprj
  Model ブロック経由で参照するモデルをビルドするとき, ファイルが本フォルダに保存される
  サブフォルダには次が保存される
    シミュレーションコード
    いくつかの Real-Time Workshop コード
    モデル間で共有するユーティリティコード
    その他のファイル
  Real-Time Workshop のユーザーは特に次を使用する
    Model 参照 RTW ターゲットファイル: slprj/target/modelname/
    Model 参照 RTW ターゲットとスタンドアロンのコード生成で使われるMATファイル: slprj/target/modelname/tmwinternal
    共有 (固定小数) ユーティリティ: slprj/target/_sharedutils

 
ビルドフォルダには次のファイルが保存される
  model.c 生成コード
  model.h 生成コードヘッダ
  model.mk 生成 makefile

オプションに応じて次のファイルが保存される
  model.rtw
  オブジェクトファイル (.obj 又は .o)
  サブシステムの生成コード
  HTML 要約報告
  TLC プロファイラ報告
  ブロックI/Oやパラメータ調整情報 (model_capi.c)
  パラメータや信号の C-API コード
  Real-Time Workshop プロジェクト (model.tmw)

 
 

□ チュートリアル1 (一般 Real-Time プログラムのビルド)

作業フォルダを準備する

1. フォルダ作成 (D:\f14example)

2. そのフォルダに移動

3. Simulink モデルを開く (f14)

4. 別名で保存 (f14rtw.mdl)
  コード生成が正しく行われるように, シミュレーションパラメータを変更する
  特に, 一般 Real-Time (GRT) や他のターゲットでは, 固定ステップのソルバーを使う必要がある

 
コンフィグレーションパラメータを設定する

1. シミュレーション − コンフィグレーションパラメータ, を選択

2. ソルバーで以下を設定
  Start Time: 0.0
  Stop Time: 60
  Solver options: Type=Fixed-step, ode5
  Fixed step size: 0.05
  Mode: SingleTasking

3. Apply ボタン押下

4. モデルの保存

 
ターゲットコンフィグレーションを選択する

システムターゲットファイル, テンプレート makefile, make コマンドを選択する
ここでは 一般 Real-Time ターゲット (GRT) を使用する
GRT ターゲットはワークステーションで実行可能なスタンドアロンのプログラムをビルドする

1. コード生成 − ターゲットの選択

2. システム ターゲット ファイル

3. 参照ボタンを押下
  ターゲットコンフィグレーションを選択すると, Real-Time Workshop は自動的に, システムターゲットファイル, テンプレート makefile, make コマンドを選択する

4. Generic Real-Time Target を選択し, OK ボタンを押下する

画面にはシステムターゲットファイル (grt.tlc), テンプレート makefile (grt_default_tmf), make コマンド (make_rtw) が表示される

5. (Debug タブ)

6. (Symbols タブ)

7. (Comments タブ)

8. 「コード生成のみ」のオプションがチェックされていないことを確認する

 
Build フォルダの構成

本サンプルでは f14rtw_grt_rtw フォルダが作成される。次のファイルが格納される
f14rtw.c: スタンドアロン Cコード
f14rtw_data.c: パラメータ初期値
rt_nonfinite.c: 非有限型 (Inf, NaN, -Inf) を初期化する関数
f14rtw.h: パラメータと状態変数の定義を含むインクルードヘッダーファイル
f14rtw_types.h: コードで使われるデータ型の事前定義
f14rtw_private.h: 共通インクルード定義を含むヘッダーファイル
rt_nonfinite.h: 非有限型の入力定義
rtwtypes.h: Simulink simstruct データ型の静的インクルードファイル
rtmodel.h: 静的メインプログラムで生成コードをインクルードするためのマスターヘッダーファイル (このファイル名はいつも同じで, f14rtw.h を単にインクルードしている)
f14rtw.mk: GRT ターゲットのテンプレートから作られた makefile

 

 

□ チュートリアル2 (データロギング)

Real-Time Workshop は, 各モデルの実行時間ステップで, システム状態, 出力, シミュレーション時間, を MAT ファイルデータ (デフォルトでは model.mat) として保存する機能を持っている

コンフィグレーションパラメータの Data Import/Export で設定する。Simulink モデルの出力を MATLAB ワークスペースに保存する設定と同じである。Real-Time Workshop では次の点が異なる。例えば, シミュレーション時間 tout は rt_tout に保存される

チュートリアルでは, シミュレーション時間とシステム出力を f14rtw.mat に保存する。よって, MATLAB ワークスペースにそれをロードして, シミュレーション時間とそれに対する出力を図示することができる

Part1: シミュレーション中のデータロギング

Part2: 生成したコードでのデータロギング

 

 

□ チュートリアル4 (生成コードの確認)

本サンプルでは「コード生成のみ」を選択する

ブロックI/O最適化機能を有効にしたとき, Real-Time Workshop はブロック出力に可能な限りローカル変数を使用する

バッファ最適化なしでのコード生成
コンフィグレーションパラメータ − 最適化
Signal storage reuse のチェックを外す

バッファ最適化ありでのコード生成
コンフィグレーションパラメータ − 最適化
Signal storage reuse をチェックする
Enable local block outputs はチェック
Reuse block outputs はチェック
Eliminate superfluous temporary variables (Expression folding) は未チェック

 

 

□ makefile の変更

MATLAB_ROOT     = /home/chbstyle/simulink/r2018b
START_DIR       = /home/chbstyle/Src/Work211022_gcc
PRODUCT         = /home/chbstyle/Src/Work211022_gcc/NewSimModel
CMD_FILE        = NewSimModel.rsp

OBJS            = rt_logging.obj NewSimModel.obj rtGetInf.obj rtGetNaN.obj rt_nonfinite.obj
MAIN_OBJ        = rt_main.obj

SRCS = $(MATLAB_ROOT)/rtw/c/src/rt_logging.c $(START_DIR)/NewSimModel_grt_rtw/NewSimModel.c $(START_DIR)/NewSimModel_grt_rtw/rtGetInf.c $(START_DIR)/NewSimModel_grt_rtw/rtGetNaN.c $(START_DIR)/NewSimModel_grt_rtw/rt_nonfinite.c
xx MAIN_SRC = $(MATLAB_ROOT)/rtw/c/src/common/rt_main.c
MAIN_SRC = /home/chbstyle/Src/Work211022_gcc/rt_main.c

DEFINES = -DMODEL=NewSimModel -DNUMST=2 -DNCSTATES=1 -DHAVESTDIO -DRT -DUSE_RTMODEL -DONESTEPFCN -DTERMFCN

CC     = /usr/bin/gcc
CFLAGS               = -c -I. -I.. -I/user/include -I$(MATLAB_ROOT)/include $(DEFINES)

LD     = /usr/bin/g++
LDFLAGS              = -s -L/usr/lib

 
# SOURCE-TO-OBJECT
%.obj : %.c
        $(CC) $(CFLAGS) -Fo"$@" $(subst /,\,"$<")
※NewSimModel.rsp の中身は *.obj なので -Fo"$@" を -o "$@" にする

 
# Create a standalone executable
$(PRODUCT) : $(OBJS) $(PREBUILT_OBJS) $(MAIN_OBJ)
        $(LD) $(LDFLAGS) -o $(PRODUCT) @$(CMD_FILE) $(subst /,\,$(subst /,\,$(SYSTEM_LIBS))) $(subst /,\,$(subst /,\,$(TOOLCHAIN_LIBS)))
※ここで SYSTEM_LIBS と TOOLCHAIN_LIBS は空

 
ファイル数が少なければ, gcc と g++(ld) を使って, 上記を参考に, 手動で obj と実行ファイルを作成できる
/usr/bin/gcc -c -I. 〜 -o rt_main.obj 〜 rt_main.c
/usr/bin/g++ -s 〜 -o NewSimModel @NewSimModel.rsp
./NewSimModel 

 

 

□ rt_main.c の変更

rt_OneStep() の前に
NewSimModel_U.In1 = val1;
NewSimModel_U.In2 = val2;

// The resolution of this integer timer is 0.001, which is the step size of the task.
// Timer of this task consists of two 32 bit unsigned integers.(0 to 4,294,967,295)
// When the low bit overflows to 0, the high bits increment.
NewSimModel_M->Timing.clockTick1
NewSimModel_M->Timing.clockTickH1

rt_OneStep() の後に
val3 = NewSimModel_Y.Out1;

 
/* 時間制御 */
#include <time.h>

struct timespec tstart, tcurrent;
timespec_get(&tstart, TIME_UTC);

timespec_get(&tcurrent, TIME_UTC);

struct timespec telapsed;
telapsed.tv_sec = tcurrent.tv_sec - tstart.tv_sec - (tstart.tv_nsec > tcurrent.tv_nsec ? 1 : 0);
telapsed.tv_nsec = tcurrent.tv_nsec - tstart.tv_nsec + (tstart.tv_nsec > tcurrent.tv_nsec ? 1000000000 : 0);

struct timespec tsim;
tsim.tv_sec = ( (long)NewSimModel_M->Timing.clockTick1) / 1000;
tsim.tv_nsec = ( ( (long)NewSimModel_M->Timing.clockTick1) % 1000) * 1000000;

truct timespec treq, trem;
treq.tv_sec = tsim.tv_sec - telapsed.tv_sec - (telapsed.tv_nsec > tsim.tv_nsec ? 1 : 0);
treq.tv_nsec = tsim.tv_nsec - telapsed.tv_nsec + (telapsed.tv_nsec > tsim.tv_nsec ? 1000000000 : 0);
if (treq.tv_sec >= 0 && treq.tv_nsec > 0) nanosleep(&treq, &trem);

 
/* 再生パターン読み込み */
#include <stdlib.h>

int process_step = 0;
int row_cnt = 0;
int row_num_max = 0;
char* ptr_excel_sheet_str = 0;
struct timespec start_time_ns, t_ns;

double rcv_test_start = 1;
double snd_sequence_time = 0;

timespec_get(&t_ns, TIME_UTC);

if ( (process_step == 0) && (rcv_test_start == 1))
{
    FILE *fp;
    char str[4096];
    int row_num;

    row_num = 0;
    if ( (fp = fopen("/home/chbstyle/Src/Work211022_gcc/Test_Pattern.csv", "rt")) == NULL)
    {
        (void)printf("rt_main : fopen error Test_Pattern.csv" );
        process_step = 0;
        rcv_test_start = 0;
    }
    while ( fgets(str, 4096, fp) != NULL )
    {
        row_num++;
    }
    fclose(fp);

    row_num_max = row_num;
    ptr_excel_sheet_str = (char *)malloc(row_num_max*3*2048); // col_max=3,len_max=2048

    row_num = 0;
    if ( (fp = fopen("/home/chbstyle/Src/Work211022_gcc/Test_Pattern.csv", "rt")) == NULL)
    {
        (void)printf("rt_main : fopen error Test_Pattern.csv" );
        process_step = 0;
        rcv_test_start = 0;
    }
    while ( fgets(str, 4096, fp) != NULL )
    {
        char *tp;
        tp = strtok( str, ",\r\n" ); //",¥r¥n"
        sprintf(ptr_excel_sheet_str + row_num*3*2048 + 0*2048, "%s", tp); // Time
        tp = strtok( NULL, ",\r\n" ); //",¥r¥n"
        sprintf(ptr_excel_sheet_str + row_num*3*2048 + 1*2048, "%s", tp); // AP
        tp = strtok( NULL, ",\r\n" ); //",¥r¥n"
        sprintf(ptr_excel_sheet_str + row_num*3*2048 + 2*2048, "%s", tp); // BK
        row_num++;
    }
    fclose(fp);

    row_cnt = 2; // row_cnt=0 is 'Title', row_cnt=1 is 'STEP/SLOPE'
    start_time_ns = t_ns;
    snd_sequence_time = 0;

    char value_str[4096];
    sprintf(value_str, "%s", (ptr_excel_sheet_str + row_cnt*3*2048 + 1*2048 ));
    NewSimModel_U.In1 = atof(value_str) / 100.0; // Driver_Accelerator
    sprintf(value_str, "%s", (ptr_excel_sheet_str + row_cnt*3*2048 + 2*2048 ));
    NewSimModel_U.In2 = atof(value_str) / 100.0; // Driver_Brake

    process_step++;
}
else if ( (process_step > 0) && (rcv_test_start == 1))
{
    struct timespec t_diff;
    t_diff.tv_sec = t_ns.tv_sec - start_time_ns.tv_sec - (start_time_ns.tv_nsec > t_ns.tv_nsec ? 1 : 0);
    t_diff.tv_nsec = t_ns.tv_nsec - start_time_ns.tv_nsec + (start_time_ns.tv_nsec > t_ns.tv_nsec ? 1000000000 : 0);
    if ( ( ( (double)t_diff.tv_sec) + ( ( (double)t_diff.tv_nsec)/1000000000)) >= atof( ptr_excel_sheet_str + (row_cnt+1)*3*2048 + 0*2048 ) ) // sec
    {
        row_cnt++;
    }
    else
    {
    }

    snd_sequence_time = ( (double)t_diff.tv_sec) + ( ( (double)t_diff.tv_nsec)/1000000000); // sec

    char value_str[4096], mode_str[4096];
    double val;

    sprintf(value_str, "%s", (ptr_excel_sheet_str + row_cnt*3*2048 + 1*2048 ));
    sprintf(mode_str, "%s", (ptr_excel_sheet_str + 1*3*2048 + 1*2048 ));
    if ( (strcmp(mode_str, "SLOPE") == 0) && (row_cnt <= (row_num_max - 2)) )
    {
        char value2_str[4096];
        sprintf(value2_str, "%s", (ptr_excel_sheet_str + (row_cnt+1)*3*2048 + 1*2048 ));
        double a, b, c, d;
        a = atof(value_str);
        b = atof(value2_str);
        c = atof( ptr_excel_sheet_str + row_cnt*3*2048 + 0*2048 );
        d = atof( ptr_excel_sheet_str + (row_cnt+1)*3*2048 + 0*2048 );
        val = (b - a) / (d - c) * (snd_sequence_time - c) + a ;
    }
    else
    {
        val = atof(value_str);
    }
    NewSimModel_U.In1 = val / 100.0; // Driver_Accelerator

    sprintf(value_str, "%s", (ptr_excel_sheet_str + row_cnt*3*2048 + 2*2048 ));
    sprintf(mode_str, "%s", (ptr_excel_sheet_str + 1*3*2048 + 2*2048 ));
    if ( (strcmp(mode_str, "SLOPE") == 0) && (row_cnt <= (row_num_max - 2)) )
    {
        char value2_str[4096];
        sprintf(value2_str, "%s", (ptr_excel_sheet_str + (row_cnt+1)*3*2048 + 2*2048 ));
        double a, b, c, d;
        a = atof(value_str);
        b = atof(value2_str);
        c = atof( ptr_excel_sheet_str + row_cnt*3*2048 + 0*2048 );
        d = atof( ptr_excel_sheet_str + (row_cnt+1)*3*2048 + 0*2048 );
        val = (b - a) / (d - c) * (snd_sequence_time - c) + a ;
    }
    else
    {
        val = atof(value_str);
    }
    NewSimModel_U.In2 = val / 100.0; // Driver_Brake

    //(void)printf("%lf %lf %lf\n", snd_sequence_time, NewSimModel_U.In1, NewSimModel_U.In2);

    if ( row_cnt == (row_num_max - 1) )
    {
        process_step = 0;
        rcv_test_start = 0;
        free(ptr_excel_sheet_str);
    }
    else
    {
    }
}
else
{
}

 
/* 結果書き込み */
(void)printf("%lf %lf\n", snd_sequence_time, NewSimModel_Y.Out1);

 
/* 画面表示 */
int prev_cnt = 0;

if (prev_cnt != row_cnt)
{
    prev_cnt = row_cnt;
    (void)printf("%lf %lf %lf %lf\n", snd_sequence_time, NewSimModel_U.In1, NewSimModel_U.In2, NewSimModel_Y.Out1);
}
else
{
}


rt_main.zip

Test_Pattern.zip

NewSimModel.zip (再掲)

 
/* 実行結果 */

f:id:sato-7411:20211102234441p:plain

f:id:sato-7411:20211102234505p:plain

 

□ rt_main.c の書き換え, NewSimModel_main.c の作成
外部から Start, OnStep, Terminate 等で呼び出せるように, NewSimModel_main.c を作成
それで実行するように rt_main.c を書き換え (例えば, 複数のモデルを実行する)

NewSimModel_main.zip

rt_main.zip

Test_Pattern.zip (再掲)

NewSimModel.zip (再掲)

( rt_main_win.zipWindows テスト用 )

( NewSimModel_dll.zipWindowsFormsApplication1.zipWindowsFormsApplication2.zipWindows テスト用、DLL形式、GUI C# )

 

 

※ 「((」が, はてな記法で脚注になるので, 間にスペースを入れました