1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Simulink/Embedded Coder のビルドプロセスをカスタマイズする (#3)

Posted at

はじめに...

第3回は、ビルドプロセス「コード生成」段階の main.c ファイル生成についてです。

ビルドプロセスのカスタマイズにおいて、TLC 言語で記述された「tlc ファイル」を避けて通ることはできません。システムターゲットファイルでも「%include」や「%assign」といった TLC ディレクティブは登場しましたが、その動作を予想できるものばかりであったかと思います。
今回、TLC言語のディレクティブや関数が多く登場し、予想がつくものもあれば、そうでないものあります。後者の場合に備えて、ここにリンクを記します。

TLC ディレクティブ
Target Language Compiler Directives
TLC ライブラリ関数
Target Language Compiler のライブラリ関数の概要 のトピックに載っている「入力信号関数」~「高度な関数」

コンフィギュレーションパラメータ ウィンドウでの設定

main.c ファイルを生成する、コンフィギュレーションパラメータ ウィンドウでの設定は次になります。

コンフィギュレーションパラメータ ウィンドウでの設定

  • [メイン プログラム例の生成]
    チェックを付ける
  • [ファイルカスタマイズテンプレート]
    main.c ファイル生成用の tlc ファイルを指定します。ここでは後述する「mytarget_file_process.tlc」です

これらはカスタム ラピッドプロトタイピング環境において固有かつ必須なため、第2回で紹介した mytarget_select_callback_handler.m で設定を行っています。次はその例です。

mytarget_select_callback_handler.m
function mytarget_select_callback_handler(hDlg, hSrc)

% メイン プログラム例の生成
slConfigUISetVal(hDlg, hSrc, 'GenerateSampleERTMain', 'on');
% ファイル カスタマイズ テンプレート
slConfigUISetVal(hDlg, hSrc, 'ERTCustomFileTemplate', 'mytarget_file_process.tlc');

end

mytarget_file_process.tlc

main.c ファイル生成はこのファイルから始まります。システムターゲットファイル同様、テンプレートが用意されています。そのコピーを Target フォルダ (mytarget¥mytarget) へ「mytarget_file_process.tlc」というファイル名で保存し、編集します。
組み込み向けラピッド プロトタイピング環境のテンプレートファイルは [matlabroot]¥toolbox¥rtw¥targets¥ecoder¥example_file_process.tlc です。

mytarget_file_process.tlc は4つのパートで成ります。1つづつ見ていきます。

mytarget_file_process.tlc (1)

mytarget_file_process.tlc --Part 1--
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%  mytarget_file_process.tlc
%%%% Copyright 1994-2021 The MathWorks, Inc.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%selectfile NULL_FILE
%assign cFile = LibCreateSourceFile("Source", "Custom", "mytarget_main")

tlc ファイルのコード先頭行は、大抵、この「%selectfile NULL_FILE」で始まります。とりあえず、お約束と捉えてください。

LibCreateSourceFile() は C 言語の fopen() のようなもので、引数に応じた出力先領域を作成して、その参照を返します。この出力先参照は fopen() で例えるとファイルポインタに当たります。

mytarget_file_process.tlc (2)

mytarget_file_process.tlc --Part 2--
%% ----------------------------------------------------------------
%%  #include
%% ----------------------------------------------------------------
%openfile tmpBuf
    #include <Arduino.h>
    #include "%<LibGetMdlPubHdrBaseName()>.h"
%closefile tmpBuf
%<LibSetSourceFileSection(cFile,"Includes",tmpBuf)>

#include 文を生成するパートです。次は生成された C ソースファイルの該当ステートメントです。

ert_main.c
#include <Arduino.h>
#include "mymodel.h"

「%openfile」で後続する文字列を蓄えるバッファを開き、「%closefile」でこれを閉じ、LibSetSourceFileSection() でバッファの内容を出力先参照 cFile の「Includes」セクションへセットする、という流れです。
この %openfile ⇨ %closefile ⇨ LibSetSourceFileSection() は tlc ファイルの基本パターンです。

mytarget_file_process.tlc (3)

mytarget_file_process.tlc --Part 3--
%% ----------------------------------------------------------------
%%  #define
%% ----------------------------------------------------------------
%assign SAMPLETIME = CompiledModel.FundamentalStepSize
%if SAMPLETIME < 0.001
    %<LibReportError("The minimum supported sample time is 1 msec. Change the Sample time parameter in blocks that use incorrect sample times.")>
%endif
%assign SampleRate  = CAST("Number", %<SAMPLETIME> * 1000000)
%openfile tmpBuf
    #define STEP_SIZE %<SampleRate>UL  /* 単位 [usec] */
%closefile tmpBuf
%<LibSetSourceFileSection(cFile,"Defines",tmpBuf)>

#define 文を生成するパートです。次は生成された C ソースファイルの該当ステートメントです。

#define STEP_SIZE    100000UL  /* 単位 [usec] */

STEP_SIZE は、コンフィギュレーションパラメータ ウィンドウ [ソルバー] ペイン ⇨ [ソルバーの詳細] ⇨ [固定ステップサイズ (基本サンプル時間)] で設定した値 (ここでは 0.1 秒) のマイクロ秒表現です。

固定ステップサイズ (基本サンプル時間)

コンフィギュレーションパラメータで設定されたパラメータ値を読み取るのは、このパート冒頭のコード

%assign SAMPLETIME = CompiledModel.FundamentalStepSize

です。「CompiledModel」については「model.rtw」で記します。

mytarget_file_process.tlc (4)

mytarget_file_process.tlc --Part 4--
%if LibIsSingleRateModel() || LibIsSingleTasking()
    %include "mytarget_srmain.tlc"
    %<FcnSingleTaskingMain()>
%else
    %<LibReportError("The MutiTaskMain() is not supported.")>
%endif

シングルタスク動作とマルチタスク動作で、main() 関数を生成する tlc ファイルを変えます。ここではシングルタスクのみを対象とし、マルチタスクが指定された場合は、「コード生成」段階でエラー終了させています。
「mytarget_srmain.tlc」に main() 関数を生成するコードが記述されています (後述) 。

マルチタスク動作もサポートする例は、テンプレートファイル example_file_process.tlc で次となっています。

example_file_process.tlc
%if LibIsSingleRateModel() || LibIsSingleTasking()
  %include "bareboard_srmain.tlc"
  %<FcnSingleTaskingMain()>
%else
  %include "bareboard_mrmain.tlc"
  %<FcnMultiTaskingMain()>
%endif

mytarget_srmain.tlc

シングルタスク用 main() 関数を生成する tlc ファイルです。テンプレートファイルが用意されています。
テンプレートファイルは [matlabroot]¥rtw¥c¥tlc¥mw¥bareboard_srmain.tlc です。ちなみにマルチタスク用テンプレートファイル bareboard_mrmain.tlc も同じフォルダにあります。
そのコピーを Target フォルダ (mytarget¥mytarget) へ「mytarget_srmain.tlc」というファイル名で保存し、編集します。

まずはファイル全体を俯瞰します。

mytarget_srmain.tlc
%selectfile NULL_FILE
%function FcnSingleTaskingMain() void
  ...
%endfunction

見てわかるように関数が定義されているだけのファイルです。インクルード元のmytarget_file_process.tlc から追った実行順序は次となります。


それでは、関数 FcnSingleTaskingMain() 内に書かれているコードへ移ります。

mytarget_srmain.tlc (1)

mytarget_srmain.tlc --Part 1--
%if GenerateSampleERTMain
  %assign ::CompiledModel.OverrideSampleERTMain = TLC_TRUE
%endif

このコードは、main() 関数生成の際に、「コード生成」処理のデフォルトとして用意されている「メインプログラムモジュール」の使用を無効化します。何言っているのか分からないですよね...

なので生成されたファイルを見てください。まずはシステムターゲットファイルが「ert.tlc (Embedded Coder)」である場合の、簡素化した ert_main.c ファイル (以下同様) です。

デフォルトのメインプログラムモジュールで生成された ert_main.c
void rt_OneStep(void) {  }
int_T main(int_T argc, const char *argv[])
{
  mymodel_initialize();
  /* rt_OneStep(); */
  mymodel_terminate();
  return 0;
}

次は、システムターゲットファイルが「mytarget.tlc」で、「OverrideSampleERTMain」を「TLC_TRUE」とした場合です。

mytarget.tlcで生成された ert_main.c
OverrideSampleERTMain = TLC_TRUE
uint8_T getOverrunCounter(void) {  }
int_T main(int_T argc, const char *argv[])
{
  init();
  mymodel_initialize();
  while (true) {  }
  mymodel_terminate();
  return 0;
}

最後に、システムターゲットファイルが「mytarget.tlc」で、「OverrideSampleERTMain」を「TLC_FALSE」とした場合です。

mytarget.tlcで生成された ert_main.c
OverrideSampleERTMain = TLC_FALSE
uint8_T getOverrunCounter(void) {  }    /* mytarget_srmain.tlc が生成 */
int_T main(int_T argc, const char *argv[])  /* mytarget_srmain.tlc が生成 */
{
  init();
  mymodel_initialize();
  while (true) {  }
  mymodel_terminate();
  return 0;
}

void rt_OneStep(void) {  }              /*「デフォルト メインプログラムモジュール」が生成 */
int_T main(int_T argc, const char *argv[])  /* 「デフォルト メインプログラムモジュール」が生成 */
{
  mymodel_initialize();
  /* rt_OneStep(); */
  mymodel_terminate();
  return 0;
}

「OverrideSampleERTMain」を「TLC_FALSE」にすると、「mytarget_srmain.tlc」と「デフォルト メインプログラムモジュール」の両方でコードが生成されてしまっています。
カスタム ラピッドプロトタイピング環境では「デフォルト メインプログラムモジュール」のコード生成を抑制するため、先の設定が必要になります。

mytarget_srmain.tlc (2)

mytarget_srmain.tlc -- Part2--
%assign intType   = LibGetDataTypeNameFromId(::CompiledModel.tSS_INTEGER)
%assign uint8Type = LibGetDataTypeNameFromId(::CompiledModel.tSS_UINT8)
%<SetCurrentUtilsIncludesIdx("main_util_incl")>
%assign intType, %assign uint8Type

モデルで使われるデータ型を変数へ設定します。ここでは intType に「int_T」が、uint8Type に「uint8_T」が設定されます。

SetCurrentUtilsIncludesIdx()

この関数は TLC ライブラリ関数に載っておらず、MATLAB ドキュメンテーションで検索してもヒットしないため、詳しくは分かりません。テンプレートファイルの流用です。

mytarget_srmain.tlc (3)

mytarget_srmain.tlc -- Part3--
%openfile tmpBuf
/* Overrun counter */
static %<uint8Type> uOverrunCounter = 0;
%closefile tmpBuf
%<SLibCacheCodeToFile("mainSrc_data_defn", tmpBuf)>

ローカル変数を定義するステートメントを生成します。次は生成されたCソースファイルの該当ステートメントです。

ert_main.c
static uint8_T uOverrunCounter = 0;
SLibCacheCodeToFile()

この関数は TLC ライブラリ関数に載っておらず、MATLAB ドキュメンテーションで検索してもヒットしないため、詳しくは分かりません。ですが、その挙動から mytarget_file_process.tlc (2) に登場した LibSetSourceFileSection() と同様、tmpBuf の内容を指定したセクションへ溜めている、と推察します。

mytarget_srmain.tlc (4)

mytarget_srmain.tlc -- Part4--
%assign fcnReturns = uint8Type
%assign fcnName = "getOverrunCounter"
%assign fcnParams = "void"
%assign fcnCategory = "main"

%openfile tmpBuf
%<fcnReturns> %<fcnName>(%<fcnParams>)
{
  return uOverrunCounter;
}
%closefile tmpBuf
%<SLibCacheCodeToFile("mainSrc_fcn_defn", tmpBuf)>

main() 内で用いる関数 getOverrunCounter() の定義を生成するコードです。次は生成されたCソースファイルの該当ステートメントです。

ert_main.c
uint8_T getOverrunCounter(void)
{
  return uOverrunCounter;
}

mytarget_srmain.tlc (5)

mytarget_srmain.tlc の仕上げです。少し長いですが、main() の定義を生成するコードを始めに、生成された C ソースコードを次に示します。双方を見比べると TLC ライブライ関数の意味を把握できると思います。

mytarget_srmain.tlc
%assign fcnReturns = intType
%assign fcnName = "main"
%assign fcnParams = "%<intType> argc, const char *argv[]"
%assign fcnCategory = "main"
%openfile tmpBuf

%<fcnReturns> %<fcnName>(%<fcnParams>)
{
  /* Unused arguments */
  (void)(argc);
  (void)(argv);
  
  /* STEP 時刻用の変数 */
  unsigned long prevTime;
  unsigned long currTime;
  
  /* Arduino 初期化関数 */
  init();
  
  /* モデル初期化関数 */
  %<LibCallModelInitialize()>
  
  prevTime = micros();
  
  while (true) {
    %<LibCallModelStep(0)>
    
    currTime = micros();
    
    /* オーバーラン検査 */
    if ((currTime - prevTime) >= STEP_SIZE) {
      if(uOverrunCounter < 255U){
        uOverrunCounter++;  /* オーバーラン計数 */
      }
      prevTime = currTime;  /* STEP 開始時刻を更新 */
      continue;             /* オーバーランでも続行 */
    }
    
    /* STEP 時間待ち */
    while((currTime - prevTime) < STEP_SIZE){
      currTime = micros();  /* 現在時刻を取得 */
    }
    prevTime = currTime;      /* STEP 開始時刻を更新 */
  }
  
  %<LibCallModelTerminate()>
  return 0;
}

%closefile tmpBuf
%<SLibCacheCodeToFile("mainSrc_fcn_defn", tmpBuf)>
%<SetCurrentUtilsIncludesIdx("")>
ert_main.c
int_T main(int_T argc, const char *argv[])
{
  /* Unused arguments */
  (void)(argc);
  (void)(argv);
  
  /* STEP 時刻用の変数 */
  unsigned long prevTime;
  unsigned long currTime;
  
  /* Arduino 初期化関数 */
  init();
  
  /* モデル初期化関数 */
  mymodel_initialize();
  
  prevTime = micros();
  
  while (true) {
    mymodel_step();
    
    currTime = micros();
    /* オーバーラン検査 */
    if ((currTime - prevTime) >= STEP_SIZE) {
      if (uOverrunCounter < 255U) {
        uOverrunCounter++;             /* オーバーラン計数 */
      }
      prevTime = currTime;             /* STEP 開始時刻を更新 */
      continue;                        /* オーバーランでも続行 */
    }
    /* STEP 時間待ち */
    while ((currTime - prevTime) < STEP_SIZE) {
      currTime = micros();             /* 現在時刻を取得 */
    }
    prevTime = currTime;               /* STEP 開始時刻を更新 */
  }
  mymodel_terminate();
  return 0;
}

終わりに...

第3回はここまでとします。「model.rtw」についても記す予定でしたが、尺が長くなりましたので次回へ繰り越します。
ここまでに登場したファイルとその格納先フォルダを示して、第3回を締めくくります。最後までお読みくださり、ありがとうございました。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?