0
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 のビルドプロセスをカスタマイズする (#5)

Last updated at Posted at 2025-09-08

はじめに...

第5回は、カスタムブロックの作成とSTF_make_rtw_hook.m ( 第1回 参照) について記した後、構築した環境でコード生成を行います。

対象ハードウェア向けカスタム ブロック

対象ハードウェア向けカスタム ブロック (以降、デバイスブロックと記します) は、MEX用Cソースファイルと tlc ファイルで異なる動作を記述した S-Function ブロックです。

デバイスブロックは通常、シミュレーション中に何の動作もしません。これは Simulink Support Package for Arduino Hardware で提供されるブロックをモデルへ配置して、シミュレーションを行っている状況を思い浮かべれば合点がいくと思います。そして、シミュレーション中のブロックの振る舞いは MEX 用 C ソースファイル内にある mdlOutputs() や mdlUpdate() で決まります。ですから、これらを「何もしない」ように編集します。次は「何もしない」mdlOutputs() の例です。

static void mdlOutputs(SimStruct *S, int_T tid)
{
  /* 何もしない */
}

他方、デバイスブロックを配置したモデルからコードを生成する場合は、デバイスブロックに対応する C 関数を生成コード内で呼び出さなくてはなりません。例えば、モデル内にDigital Output ブロックが在れば、model_step() 内で digitalWrite() を呼ぶ、というようにです。モデルからコードを生成する際の、ブロックに関するコード展開を担うのが 「ブロックターゲットファイル」と呼ばれる tlc ファイルです。
デバイスブロックを model_step() 内にコード展開する記述は tlc ファイルの Outputs() にあり、次はその例です。

%function Outputs (block, system) Output
    %if IsModelReferenceSimTarget() || CodeFormat=="S-Function" || ::isRAccel
    %else
        %assign p1_val = LibBlockParameter(p1, "", "", 0)
        %assign u1_val = LibBlockInputSignal(0, "", "", 0)
        digitalWrite(%<p1_val>, %<u1_val>);
    %endif
%endfunction

デバイスブロックの作成

それではデバイスブロックを作ります。作成するブロックは、連載最後で L チカを行うので digitalWrite() 用とします。
C MEX S-Function ブロックの作成は、ご存知のように次の何れかで行います。

1)Legacy Code Tool
2)S-Function Builder
3)MEX 用 C ファイルと tlc ファイルを手書きする

1→2→3の順に難易度と自由度が上がります。おのおのの詳細は、以下のMATLAB ドキュメンテーション、ならびにそのリンクをご覧ください。


1) MEX 用 C ファイルと tlc ファイルの生成

ここで作るデバイスブロックは、Arduino ライブラリ関数の pinMode() と digitalWrite() を呼び出すだけの簡単なものですから、Legacy Code Tool を用います。

MATLAB コマンドウィンドウ
lct = legacy_code('initialize');
lct.SFunctionName = 'sfn_digitalWrite';

Legacy Code オブジェクトを新規作成し、S-Function 名を指定します。

MATLAB コマンドウィンドウ
lct.StartFcnSpec = 'pinMode(uint8 p1)';

ブロックが必要とする初期化処理は StartFcnSpec で指定します。ここに記述したステートメントは、model_initialize() 内へ反映されます。

ここで pinMode() の引数が足りない点に気付かれたと思います。Legacy Code オブジェクトのプロパティ ○○○FcnSpec では、引数に定数値を記述できないため、このようにして一旦 MEX 用 C ソースファイルと tlc ファイルを生成し、その後で編集するという手順を踏みます。

MATLAB コマンドウィンドウ
lct.OutputFcnSpec = 'digitalWrite(uint8 p1, uint8 u1)';

model_step() 内へ反映させたい処理は OutputFcnSpec プロパティに指定します。

MATLAB コマンドウィンドウ
legacy_code('sfcn_cmex_generate', lct);
legacy_code('sfcn_tlc_generate', lct);

MEX用Cソースファイルと tlc ファイルを生成します。カレント作業フォルダに sfn_digitalWrite.c と sfn_digitalWrite.tlc が作られます。


2) sfn_digitalWrite.c を編集

2-1) mdlStart()

mdlStart() 【編集前】
/* Function: mdlStart ================================================
 * Abstract: ...
 */
static void mdlStart(SimStruct *S)
{
    /* Get access to Parameter/Input/Output/DWork data */
    uint8_T* p1 = (uint8_T*) ssGetRunTimeParamInfo(S, 0)->data;
    /* Call the legacy code function */
    pinMode(*p1);
}
mdlStart() 【編集後】
static void mdlStart(SimStruct *S)
{
}

{} で囲まれたコードを全て削除します。


2-2) mdlOutputs()

mdlOutputs() 【編集前】
/* Function: mdlOutputs ===============================================
 * Abstract: ...
 */
static void mdlOutputs(SimStruct *S, int_T tid)
{
    /* Get access to Parameter/Input/Output/DWork data */
    uint8_T* p1 = (uint8_T*) ssGetRunTimeParamInfo(S, 0)->data;
    uint8_T* u1 = (uint8_T*) ssGetInputPortSignal(S, 0);
    /* Call the legacy code function */
    digitalWrite(*p1, *u1);
}

mdlOutputs() 【編集後】
static void mdlOutputs(SimStruct *S, int_T tid)
{
}

{} で囲まれたコードを全て削除します。


3) sfn_digitalWrite.tlc を編集

3-1) BlockTypeSetup()

BlockTypeSetup() 【編集前】
%% Function: BlockTypeSetup ===========================================
%function BlockTypeSetup(block, system) Output
    %if ::GenCPP==1 && !IsModelReferenceSimTarget()
        ...
    %endif
    %if IsModelReferenceSimTarget() || CodeFormat=="S-Function" || ::isRAccel
        ... 
    %else
    %endif
%endfunction
BlockTypeSetup() 【編集後】
%function BlockTypeSetup(block, system) Output
    %if ::GenCPP==1 && !IsModelReferenceSimTarget()
        ...
    %endif
    %if IsModelReferenceSimTarget() || CodeFormat=="S-Function" || ::isRAccel
        %% 全て削除
    %else
        %if EXISTS(::_DONE_ARDUINO_H_SETUP_) == 0
            %assign ::_DONE_ARDUINO_H_SETUP_ = 1
            %assign srcFile = LibGetModelDotCFile()
            %openfile tmpBuf
            #include "Arduino.h"
            %closefile tmpBuf
            %<LibSetSourceFileSection(srcFile,"Includes",tmpBuf)>
        %endif
    %endif
%endfunction

%if%else で挟まれた箇所

%if IsModelReferenceSimTarget() || CodeFormat=="S-Function" || ::isRAccel
    ...
%endif

は、アクセラレータ モード シミュレーションに関するコードです。全て削除します。sfn_digitalWrite.tlc の他のパートも同様です。

編集後の

%if EXISTS(::_DONE_ARDUINO_H_SETUP_) == 0
    %assign ::_DONE_ARDUINO_H_SETUP_ = 1
    %assign srcFile = LibGetModelDotCFile()
    %openfile tmpBuf
    #include "Arduino.h"
    %closefile tmpBuf
    %<LibSetSourceFileSection(srcFile,"Includes",tmpBuf)>
%endif

は、model.c ファイルに「#include "Arduino.h"」を書き出す記述です。

BlockTypeSetup() はコード生成時、ブロックのタイプごとに一度だけ実行されます。モデルに同じブロックが複数存在していても、この関数が実行されるのは1回です。しかし、ブロックの種類が異なれば、おのおのの BlockTypeSetup() が実行されるため、「_DONE_ARDUINO_H_SETUP_」の有無を確かめ、インクルード ステートメントの多重な記述を防ぎます。
「_DONE_ARDUINO_H_SETUP_」はユーザ定義の変数 ーつまり、変数名は任意ー で、二連コロン「::」はグローバルスコープを意味します。

%assign srcFile = LibGetModelDotCFile()

LibGetModelDotCFile() で model.c ファイルへの参照 (ハンドル) を取得して、

%<LibSetSourceFileSection(srcFile,"Includes",tmpBuf)>

そのインクルード セクションへ tmfBuf の内容をセットします。

3-2) BlockInstanceSetup()

BlockInstanceSetup() 【編集前】
%% Function: BlockInstanceSetup =========================================
%function BlockInstanceSetup(block, system) Output
%assign uint8Type = LibGetDataTypeNameFromId(::CompiledModel.tSS_UINT8)
    %if IsModelReferenceSimTarget() || CodeFormat=="S-Function" || ::isRAccel
        ...
    %else
        %<LibBlockSetIsExpressionCompliant(block)>
    %endif
%endfunction
BlockInstanceSetup() 【編集後】
%function BlockInstanceSetup(block, system) Output
    %assign uint8Type = LibGetDataTypeNameFromId(::CompiledModel.tSS_UINT8)
    %if IsModelReferenceSimTarget() || CodeFormat=="S-Function" || ::isRAccel
        %% 全て削除
    %else
        %<LibBlockSetIsExpressionCompliant(block)>
    %endif
%endfunction

%if%else で挟まれた箇所を削除します。編集はこれのみです。

3-3) Start()

Start() 【編集前】
%% Function: Start =================================================
%function Start(block, system) Output
    %if IsModelReferenceSimTarget() || CodeFormat=="S-Function" || ::isRAccel
        ... 
    %else
        %assign p1_val = LibBlockParameter(p1, "", "", 0)
        pinMode(%<p1_val>);
    %endif
%endfunction
Start() 【編集後】
%function Start(block, system) Output
    %if IsModelReferenceSimTarget() || CodeFormat=="S-Function" || ::isRAccel
        %% 全て削除
    %else
        %assign p1_val = LibBlockParameter(p1, "", "", 0)
        pinMode(%<p1_val>,OUTPUT);
    %endif
%endfunction

%if%else で挟まれた箇所を削除し、pinMode() の第2引数を補います。

3-4) Outputs()

Outputs() 【編集前】
%% Function: Outputs ===============================================
%function Outputs(block, system) Output
    %if IsModelReferenceSimTarget() || CodeFormat=="S-Function" || ::isRAccel
        ... 
    %else
        %assign p1_val = LibBlockParameter(p1, "", "", 0)
        %assign u1_val = LibBlockInputSignal(0, "", "", 0)
        digitalWrite(%<p1_val>, %<u1_val>);
    %endif
%endfunction
Outputs() 【編集後】
%function Outputs(block, system) Output
    %if IsModelReferenceSimTarget() || CodeFormat=="S-Function" || ::isRAccel
        %% 全て削除
    %else
        %assign p1_val = LibBlockParameter(p1, "", "", 0)
        %assign u1_val = LibBlockInputSignal(0, "", "", 0)
        digitalWrite(%<p1_val>, %<u1_val>);
    %endif
%endfunction

%if%else で挟まれた箇所を削除します。編集はこれのみです。


4) MEX ファイルの生成

MATLAB コマンドウィンドウ
legacy_code('compile', lct);

MEX 用 C ソースファイルから mex ファイルを生成します。カレント作業フォルダに sfn_digitalWrite.mex64 が作られます。


5) デバイスブロックの生成

MATLAB コマンドウィンドウ
legacy_code('slblock_generate', lct);

マスク化 S-Function ブロックを生成します。新規のモデルウィンドウが立ち上がり、その中に作成したデバイスブロックが置かれます。
モデルを任意のファイル名で保存します。ここでは untitled.slx としました。

画像5-3.png

見栄えを良くする場合は、MATLAB ドキュメンテーション「ブロック マスクの作成」のリンクを参考ください。


6) カスタム ライブラリの作成

MATLAB コマンドウィンドウ
copyfile sfn_digitalWrite.* z:\qiita\mytarget\blocks
copyfile untitled.slx z:\qiita\mytarget\blocks

出来上がった以下のファイルを mytarget¥blocks フォルダへコピーします。

  • sfn_digitalWrite.c
  • sfn_digitalWrite.tlc
  • sfn_digitalWrite.mex64
  • untitled.slx (sfn_digitalWrite ブロックが置かれたモデル)
MATLAB コマンドウィンドウ
cd z:\qiita\mytarget\blocks
if ~contains(path,pwd,IgnoreCase=true)
addpath(pwd);
savepath;
end

mytarget¥blocks フォルダへ移動し、blocks フォルダが検索パスに含まれているかを確認します。含まれていなければ、検索パスへ追加します。

MATLAB コマンドウィンドウ
hs = load_system('untitled');
hd = new_system('mytarget_lib','Library');
add_block('untitled/sfn_digitalWrite','mytarget_lib/sfn_digitalWrite');

set_param("mytarget_lib","EnableLBRepository","on");
set_param("mytarget_lib","Lock","on")
save_system(hd);

close_system(hd);
close_system(hs);

空のライブラリ「mytarget_lib」を作成し、untitled.slx の sfn_digitalWrite ブロックをコピーして貼り付けます。
ライブラリ モデルのプロパティ EnableLBRepository と Lock を「on」にして、保存します。
mytarget_lib.slx と untitled.slx を閉じます。

次のスクリプト m ファイルを「slblocks.m」として mytarget¥blocks フォルダに保存します。

slblocks.m
function blkStruct = slblocks
    Browser.Library = "mytarget_lib";
    Browser.Name = "mytarget for Arduino";
    blkStruct.Browser = Browser;
end

Simulink エディタを立ち上げて、ライブラリ ブラウザを開きます。ライブラリ リストに slblocks.m の [Browser.Name] で指定した名前が載っているかを確認します。


mytarget_make_rtw_hook.m

第1回 で紹介した STF_make_rtw_hook.m です。定型なのでテンプレートを利用します。組み込み向けのテンプレートファイルは [matlabroot]¥toolbox¥coder¥embeddedcoder¥ert_make_rtw_hook.m です。これをコピーして、Target フォルダ (mytarget¥mytarget) へ「mytarget_make_rtw_hook.m」というファイル名で保存します。

ファイルを開き、関数名を編集します。編集箇所はこれのみです。

  • 編集前:ert_make_rtw_hook
  • 編集後:mytarget_make_rtw_hook
mytarget_make_rtw_hook.m 【編集後】
function mytarget_make_rtw_hook(hookMethod, ...
                           modelName, ...
                           rtwroot, ...
                           templateMakefile, ...
                           buildOpts, ...
                           buildArgs, ...
                           buildInfo) %#ok<INUSL,INUSD> 

% Copyright 1996-2021 The MathWorks, Inc.
  
  switch hookMethod
   case 'error'
    msg = DAStudio.message('RTW:makertw:buildAborted', modelName);
    disp(msg);
    
   case 'entry'
    msg = DAStudio.message('RTW:makertw:enterRTWBuild', modelName);
    disp(msg);
    
    option = LocalParseArgList(buildArgs);
    
    if ~strcmp(option,'none')
        ert_unspecified_hardware(modelName);
        cs = getActiveConfigSet(modelName);
        cscopy = cs.copy;
        ert_auto_configuration(modelName,option);
        locReportDifference(cscopy, cs);
    end
    
   case 'before_tlc'
    
   case 'after_tlc'
    
   case 'before_make'

   case 'after_make'
    
   case 'exit'
    if strcmp(get_param(modelName,'GenCodeOnly'),'off')
        msgID = 'RTW:makertw:exitRTWBuild';
    else
        msgID = 'RTW:makertw:exitRTWGenCodeOnly';
    end
    msg = DAStudio.message(msgID,modelName);
    disp(msg);
  end
end
...

ここでは省略していますが、引数についてはこのファイルで詳しく書かれていますので、そちらをお読みください。

コードを生成する

これでカスタムラピッドプロトタイピング環境によるコード生成の準備が整いました。簡単なモデルでコード生成を行います。

まずは検索パスを設定します。

MATLAB コマンドウィンドウ
addpath('z:\qiita\mytarget');
addpath('z:\qiita\mytarget\mytarget');
addpath('z:\qiita\mytarget\blocks');	
savepath;

(※) mytarget¥blocks フォルダは既に検索パスが通っていますから、行わなくても良いです。

モデルを入れる適当なフォルダを作成します。ここでは次としました。

次のモデルを作成します。ファイル名は blink.slx としました。

ブロック:Repeating Sequence Stair (Simulink ⇒ Sources)
画像5-7.png

ブロック:sfn_digitalWrite (Arduino for Mytarget)
画像5-8.png

コンフィギュレーションパラメータ
画像5-9.png
[ソルバー] ペイン
[ソルバーの選択] カテゴリ: [タイプ] ...「固定ステップ」
[ソルバーの選択] カテゴリ: [ソルバー] ...「離散 (連続状態なし)」
[ソルバーの詳細] カテゴリ: [固定ステップ時間 (基本サンプル時間)] ... 適宜

画像5-10.png
[コード生成] ペイン
[ターゲットの選択] カテゴリ: [システムターゲットファイル] ...「mytarget.tlc」
[ビルドプロセス] カテゴリ: [コード生成のみ] ... 「チェックあり」
[詳細設定パラメータ] カテゴリ: [詳細なビルド (コマンド表示)] ... 「チェックあり」

画像5-11.png
[コード生成]-[インターフェイス] ペイン
[ソフトウェア環境 サポート] カテゴリ:
[浮動小数点数]、[非有理数]、[複素数]、[絶対時間]、[連続時間]、[可変サイズの信号] ... 「チェックなし」


次をタイプしてコードを生成します。

MATLAB コマンドウィンドウ
slbuild('blink')

ビルドメッセージがコマンドウィンドウに表示されます。当方ビルドメッセージを参考までに次に載せます。

ビルドメッセージ出力【例】
>> slbuild('blink')
### ビルド プロセスを開始中: blink
### 'モデル固有' フォルダー構造へコードとアーティファクトを生成中
### コードをビルド フォルダーに生成しています: Z:\qiita\model\blink_mytarget_ert
### Invoking Target Language Compiler on blink.rtw
### Using System Target File: Z:\qiita\mytarget\mytarget\mytarget.tlc
  Converting 'Z:\qiita\mytarget\mytarget\mytarget.tlc' to UTF-8
### Loading TLC function libraries
.......
### Generating TLC interface API for custom data
### Initial pass through model to cache user defined code
.
### Caching model source code
...........................  Converting 'Z:\qiita\mytarget\mytarget\mytarget_srmain.tlc' to UTF-8
.
### Writing header file blink_types.h
### Writing source file blink.c
### Writing header file blink.h
.
### Writing header file rtwtypes.h
### Writing header file blink_private.h
### Writing source file blink_data.c
### Writing source file ert_main.c
### TLC code generation complete (took 1.779s).
.### バイナリ情報のキャッシュを保存しています。
.
### テンプレート makefile Z:\qiita\mytarget\mytarget\mytarget.tmf を処理しています
### makefile Z:\qiita\model\blink_mytarget_ert\blink.mk を作成しました
### コード生成が正常に完了: blink
### 'Z:\qiita\model\blink.slxc' に 'blink' の Simulink キャッシュ アーティファクトが作成されました。

ビルド概要

最上位モデルターゲット:

モデル    ビルドの理由                                ステータス         ビルド期間        
========================================================================
blink  情報キャッシュ フォルダーまたはアーティファクトが見つかりませんでした。  コードが生成されました。  0h 0m 6.3603s

1/1 ビルドされたモデル (既に最新のモデル 0)
ビルド期間: 0h 0m 7.0028s

model フォルダ下に「blink_mytarget_ert」というフォルダが作られているので、その中の blink.c を開き、「#include "Arduino.h"」、「pinMode(...)」、「digitalWrite(...)」のステートメントを確認します。

blink.c 【例】
#include "Arduino.h"
#include "blink.h"
#include "rtwtypes.h"

DW_blink_T blink_DW;
static RT_MODEL_blink_T blink_M_;
RT_MODEL_blink_T *const blink_M = &blink_M_;
void blink_step(void)
{
  uint8_T rtb_FixPtSum1;
  rtb_FixPtSum1 = blink_ConstP.Vector_Value[blink_DW.Output_DSTATE];
  digitalWrite(((uint8_T)13U), rtb_FixPtSum1);
  rtb_FixPtSum1 = (uint8_T)(blink_DW.Output_DSTATE + 1);
  if (rtb_FixPtSum1 > 3) {
    blink_DW.Output_DSTATE = 0U;
  } else {
    blink_DW.Output_DSTATE = rtb_FixPtSum1;
  }
}

void blink_initialize(void)
{
  pinMode(((uint8_T)13U),OUTPUT);
}

void blink_terminate(void)
{
}

終わりに...

第5回は以上です。コードは無事に生成できたでしょうか。
blink.c のコードは、コンフィギュレーションパラメータの設定で異なることもあります。Arduino.h、pinMode()、digitalWrite() がこのように記述されていれば十分です。
これまでに登場したファイル (今回登場したファイルは薄黄色) とその格納先フォルダを示して、本回の締めくくりとします。最後までお読みくださり、ありがとうございました。

0
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
0
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?