はじめに...
第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 で設定を行っています。次はその例です。
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
%%%% 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)
%% ----------------------------------------------------------------
%% #include
%% ----------------------------------------------------------------
%openfile tmpBuf
#include <Arduino.h>
#include "%<LibGetMdlPubHdrBaseName()>.h"
%closefile tmpBuf
%<LibSetSourceFileSection(cFile,"Includes",tmpBuf)>
#include 文を生成するパートです。次は生成された C ソースファイルの該当ステートメントです。
#include <Arduino.h>
#include "mymodel.h"
「%openfile」で後続する文字列を蓄えるバッファを開き、「%closefile」でこれを閉じ、LibSetSourceFileSection() でバッファの内容を出力先参照 cFile の「Includes」セクションへセットする、という流れです。
この %openfile ⇨ %closefile ⇨ LibSetSourceFileSection() は tlc ファイルの基本パターンです。
mytarget_file_process.tlc (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)
%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 で次となっています。
%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」というファイル名で保存し、編集します。
まずはファイル全体を俯瞰します。
%selectfile NULL_FILE
%function FcnSingleTaskingMain() void
...
%endfunction
見てわかるように関数が定義されているだけのファイルです。インクルード元のmytarget_file_process.tlc から追った実行順序は次となります。
それでは、関数 FcnSingleTaskingMain() 内に書かれているコードへ移ります。
mytarget_srmain.tlc (1)
%if GenerateSampleERTMain
%assign ::CompiledModel.OverrideSampleERTMain = TLC_TRUE
%endif
このコードは、main() 関数生成の際に、「コード生成」処理のデフォルトとして用意されている「メインプログラムモジュール」の使用を無効化します。何言っているのか分からないですよね...
なので生成されたファイルを見てください。まずはシステムターゲットファイルが「ert.tlc (Embedded Coder)」である場合の、簡素化した 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」とした場合です。
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」とした場合です。
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)
%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)
%openfile tmpBuf
/* Overrun counter */
static %<uint8Type> uOverrunCounter = 0;
%closefile tmpBuf
%<SLibCacheCodeToFile("mainSrc_data_defn", tmpBuf)>
ローカル変数を定義するステートメントを生成します。次は生成されたCソースファイルの該当ステートメントです。
static uint8_T uOverrunCounter = 0;
SLibCacheCodeToFile()
この関数は TLC ライブラリ関数に載っておらず、MATLAB ドキュメンテーションで検索してもヒットしないため、詳しくは分かりません。ですが、その挙動から mytarget_file_process.tlc (2) に登場した LibSetSourceFileSection() と同様、tmpBuf の内容を指定したセクションへ溜めている、と推察します。
mytarget_srmain.tlc (4)
%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ソースファイルの該当ステートメントです。
uint8_T getOverrunCounter(void)
{
return uOverrunCounter;
}
mytarget_srmain.tlc (5)
mytarget_srmain.tlc の仕上げです。少し長いですが、main() の定義を生成するコードを始めに、生成された C ソースコードを次に示します。双方を見比べると 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("")>
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回を締めくくります。最後までお読みくださり、ありがとうございました。

