記事一覧

MATLAB/Simulinkのカスタムブロックの作成

●この記事は MCC Advent Calendar 2017 - Adventar の15日目の記事です。

今回は,MATLABのSimulinkでのカスタムブロックの作成方法を書きます.ネット上を見てもなかなかいい情報が見当たらないので,この機会に公開します.

MATLAB


数値計算をはじめ,機械学習やシミュレーションが行える数値計算ツールです.このうち,Simukinkは,回路を組み立ててシミュレーションするパッケージになります.しかし,所望のブロックが無いときは,s-functionを使ってカスタムブロックを作成します.今回はカスタムブロックとして,2次元ベクトルの回転を実装します.

s-function名:
「Rot2」という名前にします.

実装する回路(Model.mdl):
Simulink2dRotCirc.png
Simulinkライブラリブラウザから,クロック,ゲイン,定数値,スコープ,出力端子を持ってきます.加えて,UserDefoned FunctionsライブラリにあるS-functionブロックを持ってきて作っておきます.

実行環境:
MATLAB Version 6.1.0.450 (R12.1) on PCWIN
(化石みたいな古さですが)

コード


まず,次のコードを書きます.

// Rot2.c
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */

#define MDL_START
#define S_FUNCTION_NAME Rot2
#define S_FUNCTION_LEVEL 2

#include "simstruc.h"
#include "Sub.h"

// 初期化処理
void mdlStart(SimStruct *S) {
Construct();
}

// 終了処理
static void mdlTerminate(SimStruct *S) {
Destruct();
}

static void mdlInitializeSizes(SimStruct *S) {
ssSetNumSFcnParams(S, 0);
if (ssGetNumSFcnParams(S) != ssGetSFcnParamsCount(S)) return;

// 入力ポートの設定
if (!ssSetNumInputPorts(S, 2)) return;
ssSetInputPortWidth(S, 0, 1); // 入力ポート0の線数=1
ssSetInputPortWidth(S, 1, 2); // 入力ポート1の線数=2
ssSetInputPortDirectFeedThrough(S, 0, 1);

// 出力ポートの設定
if (!ssSetNumOutputPorts(S,1)) return;
ssSetOutputPortWidth(S, 0, 2); // 出力ポート0の線数=2

ssSetNumSampleTimes(S, 1);
ssSetOptions(S, SS_OPTION_EXCEPTION_FREE_CODE | SS_OPTION_USE_TLC_WITH_ACCELERATOR);

}

static void mdlInitializeSampleTimes(SimStruct *S) {
ssSetSampleTime(S, 0, INHERITED_SAMPLE_TIME);
ssSetOffsetTime(S, 0, 0.0);
}

// 出力動作
static void mdlOutputs(SimStruct *S, int_T tid) {
InputRealPtrsType ppu = ssGetInputPortRealSignalPtrs(S,0); // 入力ポートのダブルポインタ
real_T a = *ppu[0];
real_T *pu = (real_T *)ppu[1];
real_T *py = ssGetOutputPortRealSignal(S,0); // 出力ポートのポインタ
Rot(py, pu, a); // 回転
}

#ifdef MATLAB_MEX_FILE /* Is this file being compiled as a MEX-file? */
#include "simulink.c" /* MEX-file interface mechanism */
#else
#include "cg_sfun.h" /* Code generation registration function */
#endif

#ifdef __cplusplus
}
#endif /* __cplusplus */



次にヘッダを用意します.

// Sub.h
#pragma once

#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */

extern void Construct();
extern void Rot(double *y, const double *u, double ang);
extern void Destruct();

#ifdef __cplusplus
}
#endif /* __cplusplus */



そして,回転変換の処理のcファイルです.

// Sub.c
#include "Sub.h"
#include

/*
* Construction
*/
void Construct() {
}

/*
* Destruction
*/
void Destruct() {
}

/*
* Rotate 2D vector
* @param y Output pointer to 2D vector
* @param u Input pointer to 2D vector
* @param ang Rotation angle
*/
void Rot(double *y, const double *u, double ang) {
double a = fmod(ang, 2*M_PI);
y[0] = cos(a)*u[0] - sin(a)*u[1];
y[1] = sin(a)*u[0] + cos(a)*u[1];
}


コンパイルのためには,Makefileが便利です.これは,mingwのg++の使用を想定しています.また,MATLABが"C:/MATLAB6p1"にインストールされているものとします(違ったら適宜変えてください).

# Makefile
MEXTGT = Rot2.dll

MATLAB_ROOT="C:/MATLAB6p1"
MEXFLAGS = -DMATLAB_MEX_FILE

CC = g++
OBJS = Sub.o Rot2.o

LDFLAGS = -Wl,--export-all-symbols -shared
CFLAGS = -O3
INCLUDE = -I$(MATLAB_ROOT)/simulink/include -I$(MATLAB_ROOT)/extern/include
LIBS = -L$(MATLAB_ROOT)/bin/win32 -L$(MATLAB_ROOT)/extern/lib/win32/microsoft/msvc60
LIBS += -lmex -lmx

.PHONY: all
all: $(MEXTGT)

$(MEXTGT): $(OBJS)
$(CC) $(MEXFLAGS) $(LDFLAGS) $(OBJS) $(LIBS) -o $@
@echo Done.

.c.o:
$(CC) $(CFLAGS) $(MEXFLAGS) $(INCLUDE) -c $<

.cpp.o:
$(CC) $(CFLAGS) $(MEXFLAGS) $(INCLUDE) -c $<

.PHONY: clean
clean:
@rm -f *.o*
@echo Cleaned.

makeすると,Rot2.dllが出来上がります.
Rot2Folder.png

sfunctionをクリックし,s-function名である「Rot2」を入れます.
SfuncName.png

▶を押すとモデルを実行できます.入力として与えた(1, 0)がScope1に映ります.これが回転変換を受け,Scope2のような正弦波になっています.よって,カスタムブロックを正しく作成できたことがわかります.
SimRot2Result.png


Comments

Post a comment

Private comment