MATLAB ユーザーコミュニティー

MATLAB & Simulink ユーザーコミュニティー向け日本語ブログ

初心者のための Embedded Coder : 設定Tipsとワークフローをまとめて解説

本日は、X (Twitter) でもおなじみ、Simulinkの中の人からの投稿です。


今回は、Embedded Coder の活用方法について解説したいと思います。

初心者にはとっつきにくいツールではありますが、モデルベースデザインの中核をなす重要なツールです。使いこなせると、開発工数を大幅に削減できますので、是非使いこなせるようになりましょう!

Note: 本記事の MATLAB, Simulink のバージョンは R2024a を用いています。

目次:

1. コード生成とは
  1.1. コード生成のありがたみ
  1.2. Embedded Coder の特徴
2. 今回の目標
  2.1. 振子の振れ止め制御
  2.2. Arduino 開発環境
  2.3. タイマー割込みを設計する
3. Simulink モデル構築
  3.1. コード生成のための Simulink モデリング
  3.2. コンフィギュレーションパラメーター設定方法
  3.3. Simulink モデルから生成されたコードをライブラリ的に使いたい場合
  3.4. Embedded Coder 設定 Tips
4. 実装と実機試験
  4.1. Arduino 開発環境へ統合
  4.2. 実機試験
5. まとめ

1. コード生成とは

このセクションでは、コード生成とは何かについて説明します。

1.1. コード生成のありがたみ

モデルベースデザイン(モデルベース開発、MBD)は、制御開発の上流工程において、コントローラーに実装する制御モデルと、制御対象となる物理システムモデルを組み合わせた全体システムをシミュレーションで表現し、ふるまいを検証する手法です。詳細については、こちらのページをご確認ください。

モデルベースデザインの開発ワークフローは、以下の「V字モデル」で表現されます。

V字モデル

 

V字の左側では、物理システムと制御システムをシミュレーションモデル化し、実機を使わずに機能の検証を行います。その後、V字の右側で、最初は部分的に実機で、最後は全て実機で試験を行っていきます。

シミュレーションを活用して早期に機能を検証することで、実機検証の工数を削減できることから、モデルベースデザインは多くの企業に取り入れられてきました。

このV字の左側から右側へ移行する段階で、制御システムや物理システムを実機コントローラーやリアルタイムシミュレーターに実装する必要があります。このとき活躍するのが「自動コード生成」です。

1.2. Embedded Coder の特徴

Embedded Coder は、MATLAB コードや Simulink モデルで作った機能を C/C++ 言語へ自動変換するツールボックスです。効率的なコードを生成でき、またコードのカスタマイズも柔軟に行えることから、組み込みマイコンに実装する目的でよく使われています。

Embedded Coderの特徴

 

ちなみに、物理システムモデル、例えば「Simscape」を使って作られたモデルも、コード生成することができます。ただし、Simulink で作られたものと比べると、最適化はされない点に注意してください。(それでも、シンプルな物理モデルであればマイコンにも実装できます。)一方で「Stateflow」は、Embedded Coder で最適なコードを生成できます。

もし、コード生成機能が無かったら、モデルを作成した後、そのモデルと同じ動きをするコードを書かなければなりません。もう一度同じものを作るという無駄な作業が生じてしまいます。コード生成があれば、その工程を省略できます。

Embedded Coder を用いたソフト開発のメリット​

 

では、最初から C/C++ で機能を作ればよい、という反論もあろうかと思います。しかし MATLAB, Simulink は、モデリング、デバッグ、結果の可視化機能に優れています。分かりやすくグラフィカルにモデル化できることにより、他者と共有、共同開発もしやすいです。

2. 今回の目標

今回は、Quanser 社の QUBE – Servo 2 という倒立振子装置を用いて、振れ止め制御を設計してみたいと思います。制御アルゴリズムの中身については、以前に行いましたこちらのセミナーで用いたものをそのまま流用します。本記事では、制御アルゴリズムの「コード生成」にフォーカスして説明していきます。

2.1. 振子の振れ止め制御

実機の構成は以下の通りです。

実機構成図

 

QUBE – Servo 2 は、SPI通信で電圧指令値を受け取り、その電圧をPWMで制御し、DCモーターを動かします。それによって根元と振り子がそれぞれ回転します。根元部分と振り子部分の回転角度がエンコーダーで計測されており、その値をSPI通信で外部に出力しています。

今回は、そのSPI通信の相手として「Arduino Due」を用います。振れ止め制御のアルゴリズムは、Arduino Due に実装します。

2.2. Arduino 開発環境

Arduinoは「Simulink Support Package for Arduino Hardware」を使えば、全くC/C++コードに触れることなく制御アルゴリズムを実装できます。

しかし、開発の現場では、量産製品に Arduino を用いることはありません。通常、量産製品に使われるマイコンにはそれぞれ固有の開発環境があり、その中でC/C++コードを書いていくことになります。

今回は、Arduino を始めとして、多くのマイコンに対して一つの環境で開発ができる「PlatformIO」を用います。PlatformIO は、Visual Sudio Code の拡張機能として用意されていまして、Visual Sudio Code をベースに開発ができるのでとても有難いツールです。

PlatformIO

 

2.3. タイマー割込みを設計する

通常、PID制御などの制御アルゴリズムは、一定時間間隔で繰り返し実行される形で、コントローラーに実装されます。そのため、まずは「タイマー割込み」をマイコンに実装する必要があります。なぜタイマー割り込みかというと、通常は一定間隔で時間を刻むということが、Z変換の前提条件ですので、それを実現するためです。詳しくは制御工学の教科書をご参照ください。

タイマー割込みの実装方法はマイコンによって変わります。Arduino Due では、以下のように書くことで実現できました。

タイマー割込みの実装

 

3. Simulink モデル構築

今回のデモにおいて、実装しなければならないソフトウェア機能は以下の通りです。

  1. SPI通信で Quanser から信号値を受け取る
  2. 受け取った信号値を角度と角速度の値に変換する
  3. 角度と角速度の値から制御アルゴリズムを計算し、次の指令値電圧を得る
  4. 指令値電圧をSPI通信の信号値に変換する
  5. SPI通信で Quanser へ信号値を送る

これら全てを、Simulink でモデル化する必要はありません。モデル化すべきなのは、2, 3, 4 になります。

なぜか、分かりますでしょうか。SPI通信の関数などは、マイコンに依存する処理です。Simulink は基本的には、ハードウェアに固有の処理を実装することを意図していません。やろうと思えばできるのですが、そこまでするメリットがありません。

皆さんには、是非「機能の抽象化レイヤー」の考え方を理解してほしいと思っています。決まった形は無いですが、今回の例に沿ってざっくりした概念図を以下に描きました。

機能の抽象化レイヤー

 

Simulink でモデリングすべきなのは、「アプリケーション層」の処理です。アプリケーション層は、ハードウェアに依存しない機能です。例えば将来「別のマイコンにこの機能を実装したい」と思った時、今回作成したアプリケーション層のモデルはそのままコード生成して実装できます。別のマイコンに実装するさいに必要になるのは、機能の抽象化レイヤー図における「入出力層」や「OS, プロセッサ層」を、別のマイコンに対応したものに差し替える作業です。

従って、機能検証の効率化や資産の流用がとてもやりやすくなります。

2.3.節で説明した通り、制御アプリはタイマー割込みにより、一定周期で実行されます。つまり、Simulinkモデルもその前提でモデリングされていなければなりません。モデルのサンプル時間を確認し、タイマー割込みの時間間隔と一致していることを確認しましょう。

サンプリングタイムステップの確認

 

ちなみに、複数のサンプル時間を持つモデルをコード生成し、それぞれのサンプル時間のブロックごとに別々のタイマー割込みに渡すこともできます。詳しくはこちらをご参照ください。

次に、モデルの最上位階層に入力ポートと出力ポートを用意しましょう。手書きのコードと結合する時に、データを受け渡す処理を行います。この時、その受け渡しは Inport ブロックと Outport ブロックで行います。

InportとOutport

 

3.2. コンフィギュレーションパラメーター設定方法

Embedded Coder の設定は、コンフィギュレーションパラメーターで行います。ここでは、基本的な設定方法を紹介します。

まず、「ソルバー」で、固定ステップソルバーに設定し、固定ステップサイズにサンプル時間を明示的に指定します。

コンフィギュレーションパラメーター:ソルバー設定

 

次に「コード生成」で、システムターゲットファイルを「ert.tlc」に設定します。この設定により、Embedded Coderを使ってコード生成する、ということを明示します。

コンフィギュレーションパラメーター:コード生成

 

任意ですが、「レポート」にて「コード生成レポートを作成」「静的コードメトリクスの生成」にチェックを入れることをおすすめします。生成されたコードがどのように構成されているか、グローバル変数やスタックのメモリ消費量の見積もりができます。

コンフィギュレーションパラメーター:レポート

 

以上で、基本的なコード生成の設定は完了しました。「アプリ」タブから「Embedded Coder」をクリックし、新しく表れたタブから「ビルド」をクリックしてください。コードが生成され、Simulinkのウィンドウでは、モデルと生成コードの比較ができるようになります。

Simulinkモデル、コードの比較

 

一般的に、Simulink は時間的なシミュレーションを行うための環境です。そのため、Simulink モデルから生成するコードは、連続的に時間的な進展を行うことを前提としたコードとなります。つまり、モデル一つがひとまとまりとなって、step関数を実行するごとに時間進展するコードになります。

しかし一方で、Simulink はデータフロー図のモデリングツールです。単純に入力と処理と出力の関係をモデリングし、その機能だけをコード生成したい場合もあります。つまり、モデル内のサブシステム一つ一つを関数やライブラリとしてコード生成したい場合があります。

その場合は、モデルのプロパティインスペクターから「実行領域の設定」「領域」を「エクスポート関数」に設定します。これにより、Simulink モデルは時間に依存しないモデルとなります。

エクスポート関数に設定する

 

この状態のモデルでは、連続時間のブロックを使用することはできません。モデルは、時間に依存せず、あらゆるタイミングで呼び出される、という前提でモデリングされていなければなりません。

ちなみに、Simulink Functionというサブシステムがエクスポート関数モデルと相性がよいのでお勧めです。

エクスポート関数のSimulink Function

 

今回の例では、SPI通信の信号を変換する部分を、このエクスポート関数のモデルで作成してみましょう。Simulink Function は、MATLABの関数のように、入力と出力を記述することができます。この入力と出力が、「Argument Inport」「Argument Outport」ブロックとしてサブシステム内に現れます。

Argumentブロック

 

内部の処理を作り、コード生成を行うと、ブロックごとにC言語の関数が生成されます。

エクスポート関数のコード生成C

 

ちなみに、C++でコード生成すると、モデルが一つのクラスとなり、Simulink Functionはそのクラスのメソッドになるので、管理がしやすくなるのでお勧めです。C++でコード生成する方法については、この後の3.4.3節をご確認ください。

エクスポート関数のコード生成C++

 

3.4. Embedded Coder 設定 Tips

この節では、初心者がつまづくポイントや、現場のコントローラーに実装するために知っておくべきおすすめの設定などについて紹介します。以下に折りたたんでいますので、興味のある方はクリックしてみてください。

 

 ▽こちらをクリック

 

3.4.1. 「ビルド」ボタンをクリックした時に発生するエラーの対処法

「ビルド」ボタンをクリックした時、エラーが発生する場合があります。典型例として、コンパイラがインストールされていないから、という場合があります。

もし MATLAB にコンパイラをインストールしていない方は、コンパイラをインストールしてください。Windows 向けの無料コンパイラとしては MATLAB Support for MinGW-w64 C/C++/Fortran Compiler が用意されています。

なぜコンパイラが必要なのかというと、「ビルドを実行する」とは、そのPCで実行できる実行ファイルを作成するということだからです。つまり、Simulink モデルを解析、C/C++コードを生成、生成したコードをコンパイル、リンク、実行ファイル化までを一気に行うのが「ビルド」ボタンの機能です。

一応、実行ファイル化までやってしまった方が、「生成したコードがちゃんとエラーなくビルドできている」と確認できるので、良い面もあります。ただ、そこまでしなくてもいい場合もあるので、お好みでそれをしないこともできます。

「ビルド」ボタンのプルダウンメニューから「コード生成」をクリックします。この機能を使うと、コードを生成する所まででストップするようになります。

コード生成のボタン

 

この時、コンフィギュレーションパラメーターの「コード生成」の設定で「コード生成のみ」に自動的にチェックが入ります。ここにチェックを入れていると、ビルドは行わない設定になります。

コンフィギュレーションパラメーター:コード生成のみの設定

 

3.4.2. 入出力の渡し方を変えたい

デフォルト設定では、生成コードの入出力はグローバル変数による受け渡しとなります。

デフォルトコード生成の入出力受け渡し

 

グローバル変数ではなくて、例えば一般的なやり方であるGet, Set関数を使う方法にしたい場合もあります。その場合は、「Cコード」タブの「コードインターフェイス」をクリックし、「規定のコードマッピング」をクリックし、Inports, Outportsの設定を「GetSet」に変更します。

規定のコードマッピングからIOの設定をGetSetに変更

 

この時、ビルドをするとエラーになります。なぜかというと、GetSetの関数自体が、生成コードの中で定義されていないからです。実は、GetSet以外にもこういうことが起きる設定項目がちらほら存在しているので、気をつけてください。(前述したコード生成のみの設定を施すとエラーは出なくなります)

GetSetにした場合のエラー

 

GetSetの関数を外部ファイルで定義した場合、生成されたコードの中に、そのファイルを参照するために「#include~~」を書かないといけませんよね。それをする場合は、コンフィギュレーションパラメーターの「コード生成」「カスタムコード」のインクルードヘッダーの所に、「#include~~」を記入します。

そうすると、以下のように生成コードの中にインクルードを追加できます。

カスタムコードのインクルードヘッダー

 

3.4.3. C++のコードを生成したい

Cではなくて、C++のコードにしたい場合も多いかと思います。その場合は、「Cコード」タブの「組み込みコード – C」のプルダウンメニューから「組み込みコード – C++」をクリックします。これにより、C++のコードを生成する設定に変わります。

組み込みコード – C++設定

 

C++設定にすると良いことがあります。モデルファイルを一つのクラスとしてコード生成できるので、より明確なカプセル化がなされます。「コードマッピング」から、Inports, Outportsのデータの可視性をprivateに設定し、メンバーアクセスメソッドをMethodに設定することをお勧めします。

C++メンバーアクセスメソッドをMethodに

 

3.4.4. 余計なヘッダーファイルを減らしたい

デフォルトでは、モデルファイルごとに「モデル名.c」「モデル名.h」「モデル名_private.h」「モデル名_types.h」の四つが生成されます。モデルが複雑になってくると、それ以外にもいろいろ共通のヘッダーファイルが増えてくるので、ややこしいのでヘッダーファイルを減らしたい、と思う時があります。

この場合は、コンフィギュレーションパラメーターの「コード生成」「コード配置」のファイルパッケージ化形式を「コンパクト」に設定します。これにより、モデルファイルごとの生成ヘッダーファイルは「モデル名.h」のみになります。

コンフィギュレーションパラメーター:コンパクト設定

 

3.4.5. 「rtwtypes.h」を使いたくない

Simulinkのコード生成では、どれだけシンプルなモデルであっても「rtwtypes.h」が必ず生成されるようになっています。このヘッダーファイルでは主に、double や int32 などの基本のデータ型を、real_T や int32_T などの、MATLAB 固有のデータ型に定義し直しています。

以前から、お客様から「rtwtypes.h」を無くしてほしい、というご要望を頂いておりました。R2023a にて、ついにこのヘッダーファイルを無くすことができるようになりました。コンフィギュレーションパラメーターの「コード生成」「データ型置換」のデータ型の置換で「固定幅の整数のCデータ型を使用」に設定します。

コンフィギュレーションパラメーター:固定幅の整数のCデータ型を使用

 

3.4.6. 各フォルダに散らばっているコードを一つのフォルダにまとめたい

複数のモデルを参照しているモデルからコード生成すると、生成されたC/C++ファイルがいろんなフォルダに散らばります。カテゴリーごとに分けられている、という良い面もありますが、その後に外部の開発環境へ持っていくことを考えると、必要なコードだけ、一つのフォルダにまとまっている方が良いです。

この場合は「packNGo」というコマンドで、必要なコードだけ、一つのフォルダにまとめることができます。まず、buildInfo.matというファイルをloadコマンドで読み込みます。buildInfo.matは、「モデル名_ert_rtw」というフォルダに入っています。

buildInfoを読み込み

 

次に、「packNGo(buildInfo)」を実行します。これにより、必要なコードがzipファイルにまとめられます。それでも、まだ一部コードと関係ないファイルがzipの中に入っていますが、お手数ですが、別途除去していただく必要があります。

packNGoを実行

 

 

4. 実装と実機試験

コード生成が完了しましたので、最後にマイコンの開発環境に統合する流れを紹介します。

4.1. Arduino 開発環境へ統合

生成されたコードを Arduino 開発環境側へ持っていきます。PlatformIOでは、platformio.iniで「build_flags」というオプションで、プロジェクトフォルダのパスを指定できます。そのパスの内に生成コードを持っていきます。

コードをArduino 開発環境側へコピー

 

タイマー割り込み関数の中で、生成したコードを呼び出すコマンドを記述します。

タイマー割り込み関数に生成コードの関数を記述

 

また、初期化を行うコマンドも忘れずに記述します。初期化のコマンド(initialize()関数)は、最初に1度だけ実行すればよいです。

Arduinoでは、setup()という関数で最初に1度だけ実行する処理を書けますので、その関数の中に記述すればよいです。

初期化コマンドを記述

 

Buildをクリックし、正常にビルドができることを確認します。問題なければ、Arduino DueをPCに接続し、Uploadをクリックします。これにより、実行ファイルがArduino Dueに書き込まれます。

BuildとUpload

 

4.2. 実機試験

最後に、Arduino Due と Quanser を接続し、実行してみます。

Arduino Due で制御した動画

 

過去に行った Raspberry Pi の制御と同じ動きが実現できました!

5. まとめ

本記事では、Embedded Coder を使って Simulink モデルからC/C++コードを生成し、外部の開発環境に統合するワークフローを紹介しました。

Embedded Coder のTipsもたくさん紹介しましたが、実はまだまだ他にも機能がたくさんありまして、量産開発の現場では、それらを駆使して最適なC/C++コードを生成しています。

Embedded Coderを本格的に使いたい場合は、こちらのトレーニングコースもおすすめです。量産開発で必要なノウハウを学べます。

皆様の参考になりましたら幸いです。

 

|
  • print

コメント

コメントを残すには、ここ をクリックして MathWorks アカウントにサインインするか新しい MathWorks アカウントを作成します。