はじめに
Simulinkは既存のブロックを組み合わせることでプログラミングすることが可能な、いわゆるビジュアルプログラミングツールであり、通常の使用方法においては組み込み御用達言語C言語やC++を使う必要がありません。やったー!
しかしながら、諸般の事情によりSimulink内でC言語を使わざるを得ないケース*というのが大人にはあります。その際に準備された拡張機能がS-Functionで、これはSimulink上でC言語やC++を扱う事が可能です。
(※主に過去の資産をそのまま流用したい場合やシミュレーションを高速化したい場合)
このように書くと別にS-Function良いじゃん、という雰囲気になりますが、サンプルコードがどう見ても呪文にしか見えなかったりと迷路に迷い込むこと請け合いです。
(S-Function Examples C S-Function Examples を参照)
そんな貴方に救いの手を! というのが表題のS-Function Builderとなります。
これは何かというと、美しいGUIを入力していけばS-Functionのコードを半自動生成してくれる便利ツールとなります。
ただしドキュメントが英語しかなかったり(Build S-Functions Automatically Using S-Function Builder)何を設定すればよいのか分からない欄が多かったりとそれなりにトラップがあります。このため本記事ではS-Function Builderの使い方をサクっと説明します。
なお本記事はMATLAB Expo2021 LTでの発表「MATLAB Homeでのモータ制御MBD開発に向けた“脱獄指南書”」の解説をシリーズ化したものの記事の番外編を兼ねます。このため、3章以降にてArduino連携も交えた解説を行うものとします。
①Arduino Dueに3相同期・三角波・相補PWMを打たせる
②Arduino DueでPWM山同期AD変換を行う
番外編 実質無料! Tiの3相GaNインバータの使い方
③Arduino Dueの2ch DACを利用する
番外編 便利!S-Function Builderの使い方-Arduino連携ライブラリの自作
④MATLAB/SimulinkでArduinoのレジスタを叩く
⑤MATLAB/SimulinkとArduino DueでPWM同期の制御を実現する
1. 基本的な使い方
1.1 初期設定
S-Function BuilderをSimulink画面上に配置、ダブルクリックすることで編集画面が現れます。
詳細設定は公式ドキュメントに任せるとして、サクっと使う場合は下記3つの設定だけ行えばよいです。
①S-Function名:任意の名前を付けましょう。ここではtestと付けます。
②離散時間の数:とりあえず「1」を設定しましょう。
③直達:デフォルトでチェックが入っているのでそのままにします。
その他の設定についてはひとまず不要です。
2. コーディングの実施
上記設定を行った場合のコーディング箇所としては下記4か所あります。
①Start_wrapper 内
②Outputs_wrapper 内
③Update_wrapper 内(離散時間の数を非ゼロに設定した場合のみ利用可能)
④Terminate_wrapper 内
①は起動時の処理を、④は終了時の処理を書く箇所というのはお分かりの通りかと思います。
②と③はいずれも通常時の処理を書く箇所ですが、その違いがちょっと分かりにくいので下記実例で示します。
2.1 Outputs_wrapper内に記載(オススメ)
入力信号に対し出力信号を遅れなく得ることが出来ます。
入力信号を2倍して出力する処理をOutputs_wrapperに記載した場合の結果は下記です。
Simulinkのゲインブロックと同等の結果が得られていることが分かるかと思います。
なお基本設定における「直達」はOutputs_wrapper内における入力信号の利用可否を意味しており、チェックを外した場合はS-Function Builderブロックへの入力信号を利用できなくなります。
このため「直達」のチェックは基本的に入れておく必要があります。
2.2 Update_wrapper内に記載(違いの分かる人向き)
Outputs_wrapper とは異なる下記の結果が得られます。
なお、ソルバは固定ステップサイズ0.1sに設定してあります。
Update_wrapper内に記載した場合との差異としては下記2点です。
・初期値として0が出力される
・入力信号の変化に対し出力信号が1シミュレーション周期遅れる
ちなみに初期値として0が出力さていますが、特段どこかで設定を行ったわけではなくデフォルトで初期値0が出力されるようです。
(このへんのいいかげんさがMATLAB/Simulinkの良いところというか何というか…)
ちなみに②③の両方にコードを書いた場合は②が優先されるようです。
2.3 ビルド
コーディングが完了したら、右上の「ビルド」ボタンを押します。
ビルドログにて、S-Function Builderから生成されたコードが問題なくビルドされたかどうかを確認したのちに、S-Function Builder編集ウィンドゥを閉じましょう。
ビルドボタンを押さずに閉じた場合、もしくはビルド失敗した場合はコーディングの内容が結果に反映されないので注意下さい。
S-Function Builderを通常使用する場合のフローとしては上記となります。ね、簡単でしょう?
3. Arduino連携ライブラリの自作
これ以降ではS-Function Builderを用いたArduino連携ライブラリの作成方法について説明します。
3.1 基本的なコーディング方法
ポイント:#ifndef MATLAB_MEX_FILE を駆使しろ
タイトルだけ見ても意味不明と思いますので、まずは下記のS-Function Builderサンプルコード(S-Function Builderへの入力信号に応じたLチカ)を確認頂きましょう。#ifndef MATLAB_MEX_FILE
は3度登場します。
/* Includes_BEGIN */
#include <math.h>
#ifndef MATLAB_MEX_FILE
#include <arduino.h>
#endif
/* Includes_END */
/* Externs_BEGIN */
/* extern double func(double a); */
/* Externs_END */
void test_Start_wrapper(real_T *xD)
{
/* Start_BEGIN */
#ifndef MATLAB_MEX_FILE
pinMode(13, OUTPUT);
#endif
/* Start_END */
}
void test_Outputs_wrapper(const real_T *inputSignal,
real_T *outputSignal,
const real_T *xD)
{
/* Output_BEGIN */
#ifndef MATLAB_MEX_FILE
if(inputSignal[0] > 0)
digitalWrite(13, HIGH);
else
digitalWrite(13, LOW);
#endif
/* Output_END */
}
void test_Update_wrapper(const real_T *inputSignal,
real_T *outputSignal,
real_T *xD)
{
/* Update_BEGIN */
/* Update_END */
}
void test_Terminate_wrapper(real_T *xD)
{
/* Terminate_BEGIN */
/* Terminate_END */
}
魔法の言葉である#ifndef MATLAB_MEX_FILE
から #endif
までの間に記載したコードは下記の扱いとなります。
①Simulinkを通常実行する場合:コードに含まないものとして扱われる
②arduino配布用コード生成を行う場合:コードに含むもとのして扱われる
逆説的に、digitalWrite
のようなarduino固有の関数やヘッダのインクルード #include <arduino.h>
をS-Function Builderにそのまま記述した場合、ビルドエラーとなります。
もしarduino連携ライブラリを作成しようとしてS-Function Builderがビルドエラーになった場合は#ifndef MATLAB_MEX_FILE
にて除外処理が正しく出来ているかどうかを確認しましょう。
[備考] Arduino向けコードが誤っていたらどうなるか
Arduino向けコード、すなわち#ifndef MATLAB_MEX_FILE
内のコード内容に問題があった場合にはどうなるかと言うと、この場合 S-Function Builderではビルドエラーとなりません。例えば...
void test_Start_wrapper(void)
{
/* Start_BEGIN */
#ifndef MATLAB_MEX_FILE
pinMode(13, OUTPUT) あああ
#endif
/* Start_END */
}
のように、あからさまなエラー要因があってもS-Function Builderのビルドは通ります。
最終的にどうなるかと言うと、Arduinoに書き込もうと「ビルド、配布および起動」を押した際にエラーとなります。(少し面食らうと思います)
キャプチャ画像省略しますが、上記のコードをビルドした場合のエラーメッセージは100行ぐらい出てくるため、何がまずかったかの対処がしにくい問題があります。
エラーメッセージ内を「error」で検索かけるなりして対処しましょう。
3.2 上級者向け 自作ライブラリの作成・使用
3.1に示したような、Arduino向けコードをS-Function Builderに直書きした場合について下記2点の問題があります。
①エラーメッセージが長く、どの箇所が原因なのかの特定がしにくい。
②ビルド時間が長くトライ&エラーがしにくい。
このような場合の対処方法として、Arduino向けコードをS-Function Builder内ではなくライブラリに記載、S-Function Builder内にてインクルードして用いる方法が挙げられます。
具体的には、arduino自作ライブラリを作成するとき同様に.h
ファイルや.cpp
ファイルを別途作成しここにコードの記載を行います。
なお、ここで記載するS-Function Builderにおける自作ライブラリの使用方法は過去開発済み資産を流用する場合にも有効ですのでご参考として下さい。
3.2.1 自作ライブラリを作成する
ライブラリ自体の作成方法としては外部記事を参照頂くものとして、ここでは以降で使用するサンプルコードのみを示すものとします。
内容としては、ライブラリ内部でカウンタを加算してき閾値を超えたらONするものとなります。(サンプルにするには少しだけ複雑ですが、こんなことも出来ますよーというアピールのため。)
ヘッダファイル
extern int16_t Counter;
int16_t CountUP(void);
void LED_SetUP(void);
void LED_ON(void);
ソースファイル
#include <Arduino.h>
#include "LED_ON_CNT.h"
int16_t Counter = 1;
int16_t CountUP(void){
Counter ++;
if(Counter > 1000)
Counter = 0;
return Counter;
}
void LED_SetUP(void){
pinMode(13, OUTPUT);
}
void LED_ON(void){
if(Counter > 500)
digitalWrite(13, HIGH);
else
digitalWrite(13, LOW);
}
3.2.2 ライブラリパスを通す
作成した.h
、.cpp
を適当なフォルダに格納し、S-Function Builderにてパスを通します。
パスの設定画面はS-Function Builderの入出力信号設定ウィンドウの横タブ「ライブラリ」にありますので、まずはクリックして有効化します。
その上で、「タグ」あたりを右クリックして「Add PATH」からライブラリパスを設定できます。
「タグ」はいくつか設定がありますが、「INC_PATH」を使っておけば余程問題ないです。
3.2.3 ライブラリを使用する
最大のポイント:.cppもincludeしなければならない
個人的に最も躓いたポイントはここになります。
通常のプログラミングであれば、ライブラリ作成時に.h
と.cpp
に分けて作成した場合、ライブラリを使用する場合は.h
のみインクルードすればOKと思います。
しかしながら、S-Function Builder の場合.cpp
をインクルードしないとビルドエラーとなります。
ライブラリ使用例
上記にて作成したLED_ON_CNT.h
およびLED_ON_CNT.cpp
をライブラリとして使用した場合のS-Function Builderコードを下記に記載します。
/* Includes_BEGIN */
#include <math.h>
#ifndef MATLAB_MEX_FILE
#include <arduino.h>
#include "LED_ON_CNT.h"
#include "LED_ON_CNT.cpp"
#endif
/* Includes_END */
/* Externs_BEGIN */
/* extern double func(double a); */
/* Externs_END */
void test_Start_wrapper(void)
{
/* Start_BEGIN */
#ifndef MATLAB_MEX_FILE
LED_SetUP();
#endif
/* Start_END */
}
void test_Outputs_wrapper(void)
{
/* Output_BEGIN */
#ifndef MATLAB_MEX_FILE
CountUP();
LED_ON();
#endif
/* Output_END */
}
void test_Terminate_wrapper(void)
{
/* Terminate_BEGIN */
/*
* カスタム終了コードをここに配置します。
*/
/* Terminate_END */
}
上記S-Function Builderコードを使用したSimulinkモデルは下記です。
Triggerd Subsystemを使用せず、S-Function Builderブロックをダイレクトに設置、カウントアップのための関数CountUP
を test_Outputs_wrapper
内に記載していますが、特に問題なくカウントアップが行われてArduinoをLチカさせることが出来ました。
(なお、固定ステップサイズ1e-3
を設定した場合において1秒ごとのLチカとなります。アンチョコ集参照。)
おわりに
ここまで読んで頂いた方はなんとなく分かると思いますが、2章まではお手軽に使えそうな空気が漂っていますが3章以降、だんだんと雲行きが怪しくなってきます。(特に3.2以降)
とはいえ、誰もが躓きそうなところはしっかりとケアしたつもりではいますのでArduinoライブラリを自作してみたい!という方は是非トライしてみて下さい。そしてSimulink+Arduino芸の沼へようこそ。
アンチョコ集(随時更新)
本文に書くまでもない細かなノウハウなどはここに記載。
S-Function Builderのコール周期について
上記で少し匂わせましたが、S-Function BuilderブロックをSimulink画面にダイレクトに設置した場合は固定ステップサイズがコール周期となります。
LED_ON_CNT.cpp
にてカウンタが500を上回ったらLEDオン、カウンタが1000を上回ったらクリア...というように記載しているため固定ステップサイズ1e-3
で1秒ごとのL地下になります。
.cppの関数内部を微修正した場合、S-Function Builderはどうすべき?
例えば、LEDオンする閾値を500
から100
に変えたような場合にS-Function Builderを開きなおしてビルドする必要があるか? ということですがこれに対する解はNoとなります。
S-Function Builder内のコードと.cppのコードとの接続部が変更となった場合(例えば、関数の型を変更した場合)などはS-Function Builderを開き修正を施す必要があります、当たり前ですが。
個人的には、わざわざS-Function Builder側のビルド必要か? を考えるのが面倒なので.cpp
ないし.h
を修正するたびにS-Function Builderを開いてビルドしなおしています。
エクスターナルモードの使用について
うまくいかない。
extern宣言した変数の扱い
S-Function Builder内で呼ぶことはできるが、外部に引き回せないかも。
ビルダ内で+4した場合→OK
simulink外部を経由する場合