最小構成のMBD事例 第2章 バックナンバー

最小構成のMBD事例 第2章 バックナンバー 事例
最小構成のMBD事例 第2章 バックナンバー

はじめに

第1章のバックナンバーはこちら

Modelicaによるプラント設計。
それをFMUにして他のプラットフォーム(Python等)での再利用。
さらに制御器との様々な接続方法(ASAM XCP、MDF等)の事例を紹介していく話。
Modelica用ツールとしてはOpenModelicaを使用する。

OpenModelica

ASAM XCP

ASAM MDF

参考書籍等

Modelica関連の書籍はそこそこあるが、どうしても情報不足は否めない。
また、それを実際に利用する場合、CANやXCPなどの通信側の知識を必要とされることは少なくない。

導入編

  • 恒例の太郎くんの悩み事からスタート。
  • プラントモデルを作成する必要がある。
    • それっぽいものを作るだけならば過去の記事を参照すればOK。
      • 一次遅れ系。
      • モーター伝達関数。
  • 本シリーズはModelicaを使用した話に踏み込む予定。
    • Modelica用のツールは雰囲気的にはSimulinkに似ていることが多い。

Modelica

  • Modelicaの説明。
    • オブジェクト指向のマルチドメイン・モデリング言語。
    • 機械、電気、電子、油圧、熱、制御などの領域を横断して記述できる。
  • Modelicaはプログラミング言語だが、一般的な言語とは性質が異なる。
    • シミュレーション特化言語。
    • モデル定義としてパラメータと方程式を内包。
  • Modelicaのプログラミング言語として性質を知るためモデル定義確認。
    • Massモデルを参照。
    • ある質量とある長さをもった完全剛体を1Dとして両端に力を加えられるモデル。
  • 一般的なプログラムと異なり代入式ではなく方程式を定義していく。
    • シーケンシャル且つ代入になる書き方もあるが一般的なプログラムのそれとは異なる。
  • Massモデルの中にあるextendsは継承を示すキーワード。
  • PartialキーワードはModelの不完全性を示しており、継承以外の利用ができないよう制約を掛けている。
    • OOPの抽象class的なもの。
  • PartialRigidモデルは剛体モデル。
    • 直線運動する剛体モデルを作る際はこのモデルを継承すると吉。
  • Flangeの説明。
    • 様々な領域で使われる用語ということもあり「円筒形の物体」程度の定義。
  • ModelicaのFlange。
    • connectorタイプでModel間の接続用インターフェース。
    • flow接頭辞は運動の第3法則(作用・反作用)を実現するのに便利。
      • 力(Force)の宣言で利用されてる。

Modelicaツール

  • Modelicaを扱えるツールは様々。
    • Amesim、Simplorer、Dymola、MapleSim、SimulationX、OpenModelica。
  • 自動車業界ではMapleSim、SimulationXが多め。
    • HILS、Simulinkの相性の都合。
  • OpenModelicaはオープンソースなツール。

OpenModelica

  • OpenModelicaの説明。
    • Wikiepdia英語ページから引用。
    • 自動車、水処理、発電所の領域で使われている。
    • 自動車業界でも開発フェーズだと相互運用性都合でプロプライエタリ品を使うことが多い。
  • 複数のツールで構成されている。
    • コンパイラ、エディタ、インターフェス、プラグインなど。
  • OpenModelica Compiler (OMC)はコンパイラ。
    • C言語を生成する。
    • インタプリタ用言語を生成してデバッグ動作を実現。
  • OpenModelica Connection Editor (OMEdit)
    • グラフィックエディタ。
    • C++/Qtで作成されているためマルチプラットフォーム。
  • OpenModelica Shell (OMShell)について説明。
    • その名の通りシェル。
    • MATLABのコマンドウィンドウに近い。
  • OpenModelica Notebook (OMNotebook)の説明。
    • コマンドの実行とその結果を含めてドキュメント化できる機能。
    • Jupyter Notebookに近い。
  • OpenModelica Python Interface (OMPython)
    • Python自動化インターフェース
  • OpenModelica Matlab Interface (OMMatlab)
    • 上記のMATLAB版
  • Modelica Development Tooling (MDT)
    • Eclipseインターフェース
  • OpenModelicaのダウンロード。
    • Windows、Linux、Mac版がある。
    • 32bit、64bit用に分かれている。
  • OpenModelicaのインストール。
    • 基本はウィザードに従って「次」へ進んでいくだけ。
    • トータルで10Gbyteほどのサイズになるので、15Gbyteくらいの空き容量があった方が良い。

massモデル

  • 使用するモデルはMassとconstantForce。
  • 直線運動をふんわり知って置いた方が良い。
    • 加速度、速度、距離、力、運動量、仕事、仕事率。
  • OpenModelica Connection Editorで各モデルを配置&接続
    • Modelica→Mechanics→Translationalに目的のモデルがある。
  • 前回作成したモデルが何を示しているか確認。
    • massを引っ張り合ってるモデル。
    • グラフィックエディタだと分かりにくいがマイナス符号を付けないと逆向きの力にはならない。
  • シミュレーション結果を事前に予測してみる。
    • 運動方程式を使用する。
    • 質量と力が分かっているので、加速度が求められる。
    • 加速度から速度、速度から距離。
  • シミュレーションするモデルと前回の予測を再掲。
    • constantForce、massの組み合わせ。
    • \(1[m/s^2]\)の加速度。
  • シミュレーション開始方法。
    • OpenModelica Connection Editor上部の矢印アイコンをクリックするだけ。
  • シミュレーション結果確認。
    • 予測通りの結果が得られた。
  • 他のシミュレーション結果のパラメータを確認
    • 加速度に加えて、速度と移動距離。
    • OpenModelica Connection Editorのプロットの画面の変数ブラウザでチェックを入れるだけで確認可能。
  • それぞれのパラメータの関係性を確認。
    • 加速度を積分して速度。
    • 速度を積分して移動距離。
  • OpenModelicaはローコード、ノーコードの性質がある。
    • しかし、コードの読み書きも出来ていた方が良い。
  • massモデルのソースコードを確認。
    • equationではconnectキーワードで接続と定義。
    • annotationキーワードでグラフィカルな情報が追記されソースコード内で情報が完結している。
  • Modelicaコードを弄ることでいろいろと効率化される可能性がある。
  • 実際にparameterキーワードを使って変数を定義。そして、それをconstantForceに設定。
    • 今のままでは動作は何も変わらない。
    • ソースコード上でパラメータ調整をし易くなったくらいの効能しかない。
    • 即値で調整するのでは労力に差はさなそう。
  • 「Modelicaのソースコードに変数を設置」の効能はモデル編集ではなくシミュ―ション時。
  • シミュレーションをするためには毎回モデル検査、Cコード生成、コンパイルが入る。
    • 規模が大きくなれと結構待たされる。
  • parameterキーワードで宣言した変数はコンパイル後にも修正可。
    • コンパイルせずに再シミュレーション可能。
  • Modelicaコードで変数追加後のOpenModelica Connecter Editor上の見え方確認。
  • まずは普通にシミュレーション。
  • 変数ブラウザで変数を書き換えたあとに再シミュレーション。
  • モデルチェック、コンパイル無しで即シミュレーション結果が得られた。
  • ちょっとしたテクニックをしってるだけで効率化可能。
  • Modelicaコードに追加したパラメータが増えると管理が大変。
    • 注釈が欲しい。
  • 他のモデルもModelicaコードで書かれたものであるが変数ブラウザで注釈が確認できる。
    • つまり、注釈が入れられるはず。
  • 変数の隣にダブルクォーテーションでくくった文字を入れれば注釈。
    • 変数ブラウザでも確認可能。
  • ソースブロックによっては様々な信号を入力できる。
    • ソースブロックは信号を生成してくれるブロック。
  • 信号を生成するソースブロックと物理量に変換するソースブロックがある。
    • 今回はtrapezoidが信号生成、forceが物理量変換。
  • Modelicaライブラリは大量にあるがライブラリブラウザで検索ができるようになってる。
  • trapezoidブロックとForceブロックを配置。
  • ForceブロックはModelica→Mechanics→Translational→Sourcesにある。
  • trapezoidブロックはModelica→Blocks→Sources→Trapezoidにある。
  • 本来はtrapezoidを修正する必要があるが今回は不要。
  • trapezoidの設定もせずにおもむろにシミュレーション。
    • 矩形波的な出力になった。
  • 変数ブラウザでtrapezoidの設定を編集。
    • 台形波的な出力に変化。
  • このように、変数ブラウザからパラメータ変更が可能なソースブロックはそこそこある。
    • これらを知っているといろいろとサボれて楽できる。

DCモータ

  • ModelicaのDCモータモデルのサンプルの位置をライブラリブラウザで確認。
  • DCモータモデルをとりあえずシミュレーション。
    • 制御電圧、制御電流、角速度の結果を確認。
  • 今回は電圧をランプ関数で制御したシンプルなもの。
    • ランプ関数は0を起点に徐々に上がっていく関数。
  • ModelicaのDCモータモデルをちょっと掘り下げ。
    • 以下が絡んでくる。
      • 電気/電子領域。
      • 古典力学領域。
      • 幾何学(材料力学)領域。
  • 物理モデリングは伝達経路、伝達関数、微分方程式解決、シミュレーションの4つの工程がある。
    • Modelicaは伝達関数、微分方程式解決をサボれるツール。
  • DCモータモデルのModelicaコードを確認。
    • 半分くらいはannotationなのでそれほど規模は大きくない。
  • 見るべきポイントを列挙。
    • 先頭のparameter部。
    • 中間のモデル宣言部。
    • 真ん中DcPermanentMagnetData。
      • これが今回のサボりポイントの目玉となる。
  • Modelicaコードのparameter部を確認。
    • parameterに関しては以前やった。
    • しかし、今回はReal型ではない。
    • 厳密には、Real型に単位の定義を付加したもの。
      • 電圧だったら”V”。
      • トルクなら”N.m”。
  • SI単位系で存在するものはModelica.SIunitsの中にすでに定義済み。
  • Modelicaコード モデル宣言部を確認した。
    • 以下が存在しており、OpenModelica Connection Editor上にもある。
      • DC_PermanentMagnet、Ramp、SignalVoltage、Inertia、TorqueStep。
  • DcPermanentMagnetDataが特殊な位置づけ。
  • DcPermanentMagnetDataをOpenModelica Connection Editorで確認。
    • UI上で様々なパラメータを設定可能。
    • さらにそのパラメータをDC_PermanentMagnetに渡すことでモデル初期化している。
  • 初期化するモデルが一個だとあまり意味がないかもだが、同特性モデルが複数あるとサボれる。

FMI(Fumction Mockup Interface)

  • Modelicaモデルを外部から利用する手段は一応ある。
    • OpenModelicaからFMIをもったFMUを出力可能。
  • FMIは物理モデルをモジュール化したものの標準インターフェース。
    • MODELISARプロジェクトで策定。
    • その後、Modelica Association Project(MAP)で管理。
  • FMI仕様の公開場所確認。
    • FMI-Standardにて公開されている。
  • FMI/FMUはMATLAB/Simulinkで言うところのS-Functionみたいなもの。
    • コンセプトとしてはほぼ一緒。
    • FMI/FMU側は標準仕様と言うことでもうマルチプラットフォームを意識したものとなっている。
  • FMU/FMIの存在価値について確認。
    • S-Functionと同等とすると存在価値が薄くなる。
  • 自動車業界なりの狙いはある。
    • サプライヤから納入される部品と同等の振る舞いするモデルモジュールをもらい、完成車メーカ側で統合する。
  • FMU/FMIはSimulink、LabViewをプラットフォームとして入出力を繋げられる。
  • 知ってる範囲でFMU/FMIに対応しているツールを調べてきた。
    • 自動車業界限定且つメジャー所だと5社ほど。
      対応Versionやアドオン追加などのの制約はある。
      対応ツールは多いので結構使えそう。
      • ただし、Vector社製品のようにCANoeは対応しているが、CANapeは対応していない。などはある。
  • 各社ツールでFMU/FMIの利用で追加費用は発生しない。
    • 非競争領域と考えて広めることを重要視している可能性が高い。
    • 2016年くらいから流行り始めている。
      • SDKのリリースが2014年なのが理由かも。
  • FMU/FMIのVersionは1.0と2.0がある。
    • ただし、互換性はない点に注意が必要。
  • FMU/FMIのシミュレーション方式は2種類ある。
    • Model Exchange(通称ME)。
      • 外部にSolver。
    • Co-Simulation(通称CS)。
      • 内部にSolver。
  • SolverはODE Solverのこと。
    • 常微分方程式を解決する機能。
      • オイラー法、ホルン法などが有名
  • FMU/FMIのシミュレーション方式とSolverの位置づけを図解した。
  • MEは近似精度を調整したい場合に有利。
    • ECUの粗い制度を再現したい。
    • プラントの演算負荷を下げてシミュレーションを高速化したい。
  • CSは内部にSolverがあり、繋ぐだけで動くので設定が簡単。
    • CSのみのサポートしかしていないツールもある。
  • FMU/FMIはあまり一般的に知られているものではないので利用方法の情報が皆無。
    • よって、仕様に踏み込まないと利用方法も見えない。
  • 仕様書を読み込むのも大変なのでFMU自体の中身を見て行った方が理解としては楽そう。
    • 実はFMUは特定のファイルとフォルダ構成をzip圧縮したもの。
      • つまり解凍して中身を参照できる。
  • FMUとzipとして解凍してみた。
    • 何個かのフォルダとxmlファイルがあった。
  • binariesにプラットフォーム別のライブラリが格納。
  • resourcesフォルダに依存関係があるファイル群を格納。
  • modelDescription.xmlにinput。output、内部パラメータが定義されている。
  • FMU/FMIのプラットフォーム上での位置づけを再確認。
  • FMUのユーザ視点に於いての位置づけを確認。
    • modelDescription.xmlとMotor.DLLの位置づけなど。
    • これを元に仕様の性質から予測される利用手順を列挙。
  • やはりmodelDescription.xmlの中身が気になるので、簡単に説明予定。
  • modelDescription.xmlの中身を確認。
    • name、valueReference、description、variability、causality、Real unitが存在。
  • valueReferenceについて仕様確認。
    • 変数ハンドル用で数値の衝突は禁止。
    • ただし、エイリアスはその限りではない。
  • modelDescription.xmlのvariabilityの仕様について。
    • constant(定数)、fixed(固定値)、tunable(調整可能値)、discrete(離散)、continuous(連続)。
      • fixedはシミュレーション前であれば変更可能。
      • tunableは変更時にODE演算のイベントが発生。
  • modelDescription.xmlのcausalityの内容を確認。
    • parameter、calculatedParameter、input、output、local、independentの6種が存在。
    • parameterはModelicaのparameter相当。
    • calculatedParameterは初期値関連。
  • FMU/FMIを読めそうなツールを確認。
    • 以前、太郎くんが調査した情報をベースに確認。
      • CANoeがすぐ使えそうだった。
  • CANoeでFMU/FMIの変数確認。
    • 問題無く確認できた。
    • FMU/FMIの変数をCANoeのシステム変数に割り付ける形。
      • よって、CAPLからでも簡単に制御できそう。

FMILibrary

  • C言語からFMU/FMIを制御するライブラリはFMILibrary
  • Windows版のFMILibraryはあるにはあるが…。
    • win32版のみ。
    • 64bit版は自分でビルドする必要がある。
  • OpenModelica64bit版のFMUは恐らく64bitビルド。
    • FMILibrary 64bit版を用意することに。
  • FMILibraryのビルド環境準備。
    • Visual Studio 2017 express。
    • cmake。
      • コンパイラ非依存なビルド自動化ツール。
  • FMILibraryのソースコード。
    • zipアーカイブかGithubで入手できる。
    • 今回はFMILibrary-2.0.3-src.zipを使用。
  • FMILibraryのビルド手順を確認。
    • 開発者コマンドプロンプト起動、cmakeでビルド、ライブラリインストール。
  • cmakeへのオプションがちょっとややこしい。
    • FMILIB_INSTALL_PREFIXでインストール先。
    • FMILIB_FMI_PLATFORMでプラットフォーム。
    • -Gでビルドする環境指定。
  • cmakeの前回のコマンドを使ってコンフィグレーション開始。
    • 問題無く完了。
  • そのままcmakeでビルド開始。
    • ビルドの途中でエラーが発生。
    • xmlparse.cの中でBLOCKとsという構造体とメンバ変数の絡みでエラーが起きたようだが・・・。
    • とりあえず、頑張って調べてみる。
  • FMILibraryのビルドエラーの原因究明。
    • stddef.hにoffsetofが定義されていなかった。
    • 厳密にはC++向けには定義されていたが、C言語向けには定義されていなかった。
    • Visual Studio自体がC++を想定した環境であるためC言語のケアが薄いためなのかもしれない。
  • 再びFMILibraryのビルドにチャレンジ。
    • 今回は無事ビルドが通った。
      • ライブラリが生成されたのも確認。
    • その後、指定したインストール先にライブラリ及びヘッダファイルが配置された。
  • FMILibraryビルド時にサンプルプロジェクトが生成されているので、今後はこれをベースに話を進める予定。
  • FMILibraryのサンプルプロジェクトを確認。
    • 大量にある。
    • 今回はfmi2_import_cs_testを使用。
    • すでにビルド済みのものが存在。
  • fmi2_import_cs_testは引数を要求するタイプの実行プログラム。
    • よって、ただ起動しただけでは何もしてくれない。
    • 引数については次回説明予定。
  • fmi2_import_cs_testの引数について確認。
    • fmu_fileとtemporary_dir。
  • fmu_fileはその名の通りFMUを指定。
    • 今回はFMI2.0且つCSのFMUであるBouncingBall2_cs.fmuが該当。
  • temporary_dirはFMUを展開するためのワーク用。
  • fmi2_import_cs_testの起動時パラメータ確認。
    • FMUとテンポラリディレクトリのPathを設定。
  • 実行と結果を取得。
    • 「log level = VERBOSE」ってのはFMILibraryの内部のデバッグログ。
    • Ball height、Ball speedとその次に続いている数値がシミュレーション上重要。
  • fmi2_import_cs_testの実行結果のうちシミュレーション部分のところだけ抜き出し。
    • Ball heightとBall speedのパラメータがある。
  • 本シミュレーションはボールを投げたあとのバウンドに伴うボールの高さと縦方向の速度を示したもの。
    • Excelでグラフ表示してみたところ確かにそんな感じ。
  • シミュレーション時間とシミュレーションステップはFMUの外側の制御の仕方次第で確定する。
    • FMU処理自体はfmi2_import_do_stepという関数の中で指定時間分実施する動き。
  • サンプルプロジェクトfmi2_import_cs_testの場合はhstepとtendを調整すればOK。
    • 時間は「秒」である点に注意。
  • fmi2_import_cs_testのシミュレーションステップとシミュレーション時間を変えてみた。
  • 上記のシミュレーションを実行。
    • 精度を細かくしたのと、シミュレーション時間を延ばしたことでデータ量は増えた。
  • グラフで確認。
    • 前回の100msサンプリングと同じ特性で精度、時間が変わっていた。
  • FMUのシミュレーションパラメータを変更することができる。
    • ただし、イニシャルモード中。
    • イニシャルモードを指定するAPIが存在。
  • fmi2_import_set_realというAPIでパラメータ変更が可能。
    • 型に応じたAPIになっており、他にinteger、boolean、string用のAPIが存在する。
  • fmi2_import_set_realのAPI仕様確認。
  • FMI statusの定義確認。
  • モデル記述オブジェクトはfmi2_import_parse_xmlで取得できるfmi制御用のハンドル。
    • FMUを展開した後に出てくるのmodelDescription.xmlを指定する必要がある。
  • modelDescription.xmlでインターフェース定義を確認。
  • fmi2_import_set_realに渡すvalueReferenceはmodelDescription.xmlに定義されてるvalueReferenceを渡せばOK。
  • これを踏まえた上で最もシンプルと思われるコードサンプル提示。
  • 修正コードができたので確認。
  • 内容の詳細説明。
    • 読み出すvalueReference群の定義。
    • fmi2_import_get_realで一気に読み出し。
    • ボールの初期の高さだけ変更。
    • fmi2_import_set_realで一気に書き戻し。
  • 数値解析ツール由来のベクトルで一気に制御する方式になっている。
  • 修正済みfmi2_import_cs_testを実行。
    • パッと見変化がわからないので以前の実行結果と比較。
      • 明らかに初期のボールの高さは変わっている。
    • グラフにして確認。
      • 初期のボールの高さが変わっているので、跳ね方も変わる。
  • このように初期パラメータもFMILibrary経由で変更可能。
  • FMUはパラメータ名とvalueReferenceの紐づけが出来た方が運用し易い。
  • FMILibraryはmodelDescription.xmlの内部情報を構造的に抱えている。
    • よって、APIで各種情報を取得可能。
  • 「modelDescription.xmlの内容を列挙」までの流れを確認。
    • 手順は多いが、流れはシンプル。
  • fmi2_import_parse_xmlについては以前やったのでスルー。
  • fmi2_import_get_variable_listはmodelDescription.xmlの情報取得の起点。
    • ソートルールを切り替えられる。
      • 型/valueReferenceでソートがちょっと特殊。
        • ベクトル的アクセスで使えそう。
  • 「fmi2_import_get_variable_list_sizeによる変数リスト数の取得」の仕様確認。
    • やってることはそのままでリストの要素数を取得。
  • 「fmi2_import_get_variableによる変数オブジェクトの取得」の仕様確認。
    • 変数オブジェクトは変数関連の情報にアクセスするハンドルのようなもの。
  • valueReferenceの取得方法確認。
    • 変数オブジェクトを渡すと取得できる。
  • 変数名の取得。
    • これも変数オブジェクトを渡すと取得できる。
  • その他のdescription、variability、causality、initial。
    • これも一緒で変数オブジェクトを渡して取得。
  • fmi2_import_get_variability、fmi2_import_get_causality、fmi2_import_get_initialと併用して使う便利APIが存在。
    • 上記関数戻り値のenumに準じて文字列を返してくれるAPI。
    • 中身はswich分で実現してるだけ。
  • 次回は実際にソースコード作成。
  • modelDescription.xml内部変数列挙の処理手順確認。
  • コード追加箇所説明。
    • fmi2_import_enter_initialization_modeとfmi2_import_exit_initialization_modeの間。
  • コード提示。
    • 前回までに説明したAPI(文字列変換含む)を全部使用した。
  • 「modelDescription.xml内の変数情報を列挙」を実施。
    • 問題無く動作。
    • modelDescription.xmlに記載されてる変数がすべて列挙されていることを確認。
  • ソートルールは「XMLファイルに記載されているオリジナルの順序」
    • こちらも想定通りの動作になっていることを確認。
  • 変数リストのソートルールが複数あることを思い出した。
  • よって、他のソートルールも試した。
  • それぞれ想定通りの動作になっていることを確認。
  • ソートの変更は現実的にはあまり出番は無さそう。
    • HILS、RAPIDコントローラで使うかもしれないが、それらもそこそこの性能があるのでやはり使わない?
  • 変数リストを取得する以外のvalueReference取得方法がある。
  • パラメータ名文字列を指定してvalueReference取得したいが、直接それができるAPIは無い。
    • パラメータ名文字列を元に変数オブジェクトを取得するAPIはある。
  • 変数オブジェクトが取得できれば、そこからvalueReferenceは取得できる
  • 「パラメータ名文字列から変数オブジェクト取得」のAPI確認。
    • fmi2_import_get_variable_by_nameというAPI。
    • パラメータ名文字列を渡せば、変数オブジェクトが返ってくる。
  • 修正箇所は恒例のイニシャルモード中。
    • 今後のことも考え複数のvalueReferenceを取得する予定。
  • パラメータ名文字列を元にFMU内パラメータ変更の実験コード提示。
    • 変数オブジェクトを取得し、そこからvalueReference取得。
    • valueReferenceが分かれば、FMU内パラメータは好き勝手できる。
  • これにより今後、他のFMUを使う場合になっても比較的楽にパラメータアクセスができそう。
  • パラメータ名文字列からFMUの制御までを動作確認。
    • valueReferenceの取得OK。
    • その後のvalueReferenceを使用したパラメータ変更も当然OK。
  • 今後の予定としては、OpenModelicaがexportしたFMUを使っていろいろやっていこうと画策している。
    • ぶっちゃけると手探り状態。

FMIライブラリ(DCモータ)

  • 「OpenModelicaで作ったFMUをFMILibraryで制御する」のプランを提示。
  • モデルは以前扱ったDCモータモデルとする。
    • Rampをそのまま電圧としてDCモータに印加するモデル。
  • ただし、そのまま使わずPID制御を追加してみる。
    • オープンループ制御からクローズループ制御のモデルに変更。
  • 以前使ったDCモータモデルにPID制御器を付けた。
    • PID制御器はModelicaライブラリに最初から存在。
    • 実際にはLimPID。
    • パラメータはKp、Ki、KdではなくKp、Ti、Tdな点に注意。
  • とりあえず、クローズループ(PID)制御のDCモータモデルが出来た。(つもり)
  • クローズループ制御にしたDCモータモデルの動作確認を実施。
    • ちゃんと動いてるっぽい。
  • Outputブロック設置
    • 各種信号にエイリアスを振るために設置。
      • 目標値をtarget。
      • 指令電圧値をvoltage。
      • 電流センサで取れる電流をcurrent。
      • 角速度センサで取れる角速度をspeed。
  • OpenModelicaからFMUをexportするための設定確認が必要。
    • FMIオプション内を確認&修正。
      • バージョン:2.0。
      • タイプ:Co-Simulation。
      • Platforms:Static。
        • Linux環境だとDynamicでもOKだろうが、Windows環境だとStatic推奨。
  • OpenModelicaから無事FMUをexport。
  • FMU内部のmodelDescription.xmlを参照。
    • Outputブロック名のパラメータの存在を確認。
    • 上記のvalueReferenceと同値のパラメータも確認。
      • モデル上、同一の信号線上のパラメータが該当。
      • 利用するのはOutputブロック側。
  • OpenModelicaからexportしたFMUをFMILibraryで読み込んでみた。
    • 無事読み込み成功。
    • 変数リストによる列挙もできた。
      • 必要なパラメータの情報は問題無く取得出来ている。
  • シミュレーションをするために若干の改造が必要。
    • シミュレーションループにvalueReferenceが渡るように修正。
  • シミュレーションループにvalueReferenceを渡すためのfmi2_import_cs_testのソースコード修正を確認。
  • 流れは以下。
    • 欲しいパラメータ名文字列列挙。
    • 変数オブジェクト取得。
    • valueReference取得。
    • valueReferenceをシミュレーションループで利用。
  • 改造版fmi2_import_cs_testの実行してみた。
    • 問題無く動作している様子。(目標値、制御電圧、モータ電流、モータ角速度)
    • 試しにグラフで表示。
      • 期待通りの波形が得られた。
  • これに伴い、OpenModelicaからexportしたFMUもFMILibraryで制御可能と言える。
  • FMU内部の固定値パラメータの変更ができるか?
    • 以前やった方法で実現可能。
  • これのソースコード改造実施。
    • Rampの開始タイミング、\(0→1\)の期間を設定できるように改造。
      • “ramp.duration”が\(0.8[s]→1.5[s]\)。
      • “ramp.startTime”が\(0.2[s]→0.3[s]\)。
  • 固定値パラメータの変更の挙動確認。
    • Rampの挙動を変えた。
  • 目標値(target)の挙動を変えたため、それに合わせて全体の挙動が変化。
    • 狙った挙動になっている。
  • パラメータになっていれば、おおよそ変更可能。
    • 変えられないのはアルゴリズムそのものや信号線の繋ぎぐらい。
  • FMILibraryについての感想。
    • 標準仕様であるが故の恩恵であるが、他ツールで出力したものを再利用できるのは助かる。
    • CAN、A/D、D/Aなどと繋げるとさらに強力な使い方ができるかも?
  • 今後はPythonベースの環境を構築してみる。
    • ググっても情報少ないので手探り状態の失敗覚悟状態で進める。

PyFMI

  • PyhthonからFMUを制御するPyFMIの紹介。
    • 内部でFMILibraryを使用している。
  • JModelicaの一部。
    • JModelicaはmodelon AB社のModelicaPlatform。
      • 2019年にClosed Sourceに移行。
      • FMILibraryも開発元はmodelon AB社。
  • PyFMIのインスト―ルについてあれこれ。
  • 依存関係がヤバイ。
    • FMILibraryの64bitが必要。
    • Assimuloが依存したsundials、GLIMDAのsolverの64bit品が必要。
  • condaだと依存関係を一撃で解決してくれる。
  • python-canなどはconda管理になっていないなど万能では無い。
  • PyFMIの動作確認方法を列挙。
    • 実験用のFMUを作って、それをPyFMIで制御しつつmatplotlibで波形表示する。って流れ。
  • DCモータモデル改造。
    • 改造と言ってもInputブロックを追加した程度。
    • InputブロックもOutputブロックと同様にエイリアスは生成される。
    • このエイリアスにアクセスする予定。
  • FMUをPythonで使用する上で必要ライブラリのimport。
    • PyFMIのload_fmu。
    • numpy。
    • matplotlib。
  • load_fmuの戻りのオブジェクトはFMU次第。
    • FMUModelCS1。
    • FMUModelCS2。
    • FMUModelME1。
    • FMUModelME2。
  • 時間軸作った。
    • とりあえず、0秒から2秒の等差数列で作った。
  • Ramp作った。
    • 等差数列で斜めにプロットした後にmax,minでサチらせた。
  • 時間軸とRamp入力と統合&縦方向に。
    • vstackとtransposeを使用。
  • 入力オブジェクト作った。
    • voltageに入力行列を紐づけた。
  • シミュレーション実施。
    • 開始時刻、終了時刻、入力オブジェクトを渡すことで実施可能。
  • シミュレーション結果取得。
    • simulate関数の戻り値が連想配列になっている。
    • voltage = res[‘voltage’]のような指定方法。
  • シミュレーション結果のグラフ表示。
    • matplotlibでプロットするのみ。
  • PyFMIでFMU制御するPythonコードを開示。
    • 割とあっさり実現。
    • Pythonなのでmatpotlibでそのままグラフ表示。
  • FMILibraryと比べるとvalueReferenceに振り回されることが無い点がとても良い。
    • PyFMIによるFMU制御の有用性がなんとなく見えてきた。

マルチFMU

  • 「完璧に把握したかもしれん」は幻。
    • ダニング=クルーガー効果。
  • FMUの本体の目的は「完成車メーカがサプライヤからの提供されたFMUを統合する」
    • よって、複数のFMUを作成。
      • DCモータモデルを分解して複数のFMUを作ってみる方針。
  • とりあえず上記をやってみて課題が出たら、それを次のネタにする。
  • 元にするDCモータモデルは使いまわし。
    • Ramp、PID制御器、DCモータの構成が一番部品が多い。
  • モデルの分解もRamp、PID制御器、DCモータの単位でやってく予定。
  • 念のため、OpenModelicaで現状の動作結果を取得しておく。
    • PyFMIで統合したときの成功/失敗の判定用。
  • 次はモデル分解。
  • DCモータモデル分解とFMU exportを実施。
    • 前回までのやり方で簡単にできるはず。
    • (よって詳細説明は端折った)
  • PyFMIからのFMU呼び出しをする際のおおまかな構成を提示。
    • 各FMUの信号はPython側で接続するイメージ。
      • マルチFMU制御ならではの処理。
      • (それだけでは解決しない話もあったり)
  • PyFMIでマルチFMU制御する際にある程度の前提知識が必要になる。
  • FMUロード。
    • 以前はload_fmuを使ったが、今回はFMUModelCS2を使用。
    • 引数が増えている。
      • PATHの指定がファイル名のPATHに分けることが可能。
      • DLLロード有無の引数がある。
        • つまりロードしない使い方が・・・。
  • Masterが土台になるが必要な情報がある。
    • モデルセット。
      • FMUをロードしたモデルをリスト化。
    • モデル間接続セット。
      • モデルの出力、入力を1セットとしたリスト。
  • モデルセットとモデル間接続セットをMasterに渡せばシミュレーションをする準備はおおよそ完了。
    • まだ、若干の調整はある。
  • Masterにオプション設定が可能。
    • 今回はstep_sizeを調整。
      • デフォルト値が0,01秒なので0,001秒に変更。
  • step_size以外にも大量のオプションがある。
    • 補間の仕方、並列処理の有無、結果出力、ログ出力などなど。
    • どういうものがあるかだけ把握し、必要になった際に再度確認すればOK。
  • マルチFMU制御用のコード提示。
    • 前半は前回まで説明した内容。
      • ロード、モデルセット、モデル間接続セット、Master定義
    • 後半はシミュレーション結果取得と波形表示。
  • FMILibraryの時とは異なり、結果取得と波形表示が楽なのは本当に有難い。
    • 結果は連想配列で取得。
    • 波形表示はmatplotlib使用。
  • マルチFMU制御用のコード実行結果を提示。
    • 事前に取っていたOpenModelicaのシミュレーション結果も一緒に提示。
  • 今回の方法だと目的のHILS環境としては課題がある。
    • シミュレーションが1関数の中で閉じてるのでリアルタイムに外部とのやり取りができない。
    • ググっても情報出て来ないのでいろいろ模索するしかない。

ダミーFMU

  • HILSに於いてのシミュレーション時間と実時間の合わせこみ方法は大きく2種類。
    • HILS自体が時間保証。
    • モデルの一部で時間の辻褄合わせ。
  • 今回はモデルの一部で辻褄合わせの方針。
    • PyFMIでダミーFMUを定義できそう。
      • 「Undocumented specification」なので当たって砕けろ方式。

めだよ。

  • ダミーFMU定義に向けての方針を提示。
  • FMUのロードの仕方を再確認。
    • _connect_dllという引数があった。
  • _connect_dllをFalseにするとFMUがロードされずインターフェースだけが定義される。
    • インターフェースを利用してでPython側からFMU出力制御可。
    • しかし、時間の制御はできない。
  • FMUロード関数を一旦整理。
    • Dummy_*という謎関数が各FMUタイプ別に存在。
  • Dummy_FMUModelCS2を題材として掘り下げ。
    • FMUModelCS2を継承している。
    • オーバーライドしているメソッド多数。
      • 重要なのはdo_step。
      • cpdef定義なので外部から上書きできないのを回避している。
  • do_stepをオーバーライドしている理由の説明。
    • Cythonによる静的関数でそのままではPython側からの上書きができない。
  • do_stepの重要性の説明。
    • masterモジュールからシミュレーションステップ毎に呼ばれるメソッドだから。
    • これを自由に書き換えられれば時間制御ができる。
    • 実時間に追いつくまで待たせる。
  • Dummy_FMUModelCS2を使用する実験構成を提示。
    • 以前作ったマルチFMUの構成をベースにちょい修正の方針。
    • ついでにPythonからstep毎の出力も制御してみる。
  • 各FMUのロードのコード。
    • Dummy_FMUModelCS2で既存のFMUを指定しておくとインターフェース仕様だけは取り込める。
  • do_stepの実装方針を決めた。
    • Ramp UpとRamp Down。
  • 出力信号のソースコード。
    • シミュレーション時間を見ながら出力信号を決定する方式。
  • 時刻同期のソースコード。
    • 単にタイマーを使ってシミュレーション時間が実時間に追いつくのを待つ。
  • do_stepメソッドの上書きはそのまま作成関数で上書きすればOK。
  • ダミーFMU実験用ソースコードは以前のマルチFMUの時の物を流用。
    • do_step周りの追加が主な修正部分。
  • ソースコード開示。
    • FMUModelCS2の一部をDummy_FMUModelCS2。
    • do_stepをdo_dummyで上書き。
    • 時刻同期ができてるかを確認できるよう一部printを入れている。
  • ダミーFMU実験の動作確認。
    • タイミングによっては若干ズレるがシミュレーション時間と実時間の同期はOK。
  • シミュレーション波形確認。
    • Ramp Up、Ramp Downの台形状の波形が出ており、期待通りの指令値になっている。
  • 上記によりダミーFMUによる実験がし易くなる。
    • 簡単なアルゴリズムであればPythonで実施

リアルタイム描画

  • 前回までやってたシミュレーション時間と実時間の同期には課題がある。
    • シミュレーション時間の方が長いと破綻。
      • Python側でグラフ表示等すると破綻し易い。
  • よって、別案。
    • 前回まではシミュレーション時間に実時間を追いつかせる。
    • 対して、新方式は実時間に対してシミュレーション時間を追いつかせる。
  • 実験環境は「シミュレーションしながらリアルタイムで波形表示」。
  • 上記以外にも以下を組み込む。
    • スライダー等で入力を手動操作。
    • sin波、のこぎり波などを入力。
    • リアルタイム表示の一時停止。
  • tkinterを使用する予定。
    • Tk GUIツールキットをPythonから呼ぶライブラリ。
  • 実験構成はGUIから決めていく。
    • GUIでおおよそのインターフェースが確定しそうだから。
  • GUIの概要図を描いた。
    • 各種チェックボックス、Scale、plot画面。
    • 上記の機能、目的を定義。
      • 処理負荷が分かるようにする。
      • pauseに関してはリアルタイム描画を抑制した際の負荷を見る為に設置。
  • tkinterはPython標準ライブラリのためインストール済み。
  • 動作確認。
    • 「python -m tkinter」でウィンドウが出てくればとりあえずOK。
  • メインウィンドウをrootとして定義し、そこに各ウィジットを生成&紐づけしていく。
    • matplotlib関連もtkinter連携用モジュールが存在する。
  • tkinterのimportはそのまんま。
    • 「import tkinter」
  • VisualStudioのリソースエディタのようなものは無い。
  • メインウィンドウ生成方法解説。
    • titleも指定可能。
  • メインウィンドウの大きさと位置指定。
    • 文字列で以下のような感じで指定。
      • ‘960×540+960+0’
  • matplotlibをtkinterに埋め込む際には以下のモジュールが必要。
    • matplotlib.pyplot。
      • プロット用。
    • FigureCanvasTkAgg。
      • プロット画面埋め込み用。
    • NavigationToolbar2Tk。
      • メニューバー。
  • 埋め込む前に図(figure)を用意しておく。
  • FigureCanvasTkAggを使用してtkinterへmatplotlibを埋め込み。
    • pack(side = tkinter.RIGHT)でメインウィンドウの右端に接するよう配置。
  • NavigationToolbar2Tkを使用してメニューバー配置。
    • place(x=0, y=h/2-40)を使用して座標指定で配置。
  • チェックボックスの配置。
    • チェック状態を確認するための変数を用意し、ウィジット生成時に渡しておく。
      • この変数を見ればチェック状態が分かる。
    • チェック状態をPython側から変更することも可能。
    • placeで座標指定して配置。
  • 同様の処理を必要なチェックボックス分実施。
    • GUI上でも設置されていることを確認。
  • 最後のウィジットとしてScaleを配置。
    • いままでのウィジットの中では最もパラメータが多い。
    • 縦置き、横置きの指定ができる。
    • commandという引数で値変更時のコールバックを設定できる。
    • to、from_で値の範囲を指定できるが大きさが逆転しても良い。
      • 0値の配置を逆にしたい場合は意図的に逆転させる。(公式もOK情報あり)
  • Scaleの配置確認。
    • 問題無く配置されているのを確認。
  • Scaleの挙動確認。
    • 値をprintしているのでそれを確認。
      • Scaleの値が出力されているの挙動もOK。
  • tkinterはmainloopに入ると、戻ってこない。
    • よって、タイマ処理を使ってうFMU関連、描画関連をうまくハンドリングする必要あり。
  • まずはtkinterのタイマ処理から確認。
    • タイマ処理に描画処理、FMU処理を組み込むためまずはタイマ。
  • タイマ処理はafterメソッドに指定時間とタイマハンドラを渡すことで実施できる。
    • しかし、一回コールバックするとそれで終了。
    • よって、周期的に処理したい場合はタイマハンドラ内で再度afterメソッドを呼び出す必要あり。
  • タイマ処理にmatplotlibの描画更新処理を入れてみる。
    • 現時刻をベースに過去8秒分の時間軸を生成し、それをsin関数に入れてsin波生成。
    • relimメソッドを使うと縦軸が自動調整される。
  • pauseのチェックボックスを設置していたので、チェック状態を確認して更新するかしないかの判定に使う。
  • いままでごちゃごちゃと追加してきたコードを再確認。
  • tkinterによるリアルタイム描画の動作確認。
    • sin波が流れるように描画されていることを確認。
    • pauseで一時停止もできることを確認。
  • FMU関連処理はまだ未実装だが、tkinterによるリアルタイム描画はできそう。
  • FMU関連処理追加がそこそこいろいろあるので情報整理。
    • 基本はいままでのFMU処理ではある。
  • 追加でdequeが必要そう。
    • シミュレーションデータ全てを保持すると、記憶する配列が膨大になる可能性が高い。
    • よって、描画に必要なデータだけを格納するためにdequeを使用。
    • dequeの使用方法を追々説明予定。
  • FMU関連処理前準備を一気に説明。
    • いままでやってきたものなので細かい説明は省略。
  • deque関連の初期化。
    • dequeはcollectionsライブラリの一部。
    • maxlenで最大要素数を指定できる。
      • 指定なしの場合は無限に入れられる。
        • 実際には物理的な限界はあるだろうが・・・。
  • 必要なタイマハンドラは2つ
    • FMU処理用とmatplotlib描画用。
    • 描画用側の負荷が高いので、こっちは長めの周期で実施。
  • FMU処理用タイマハンドラ内で時間関連の演算が入るため、タイマハンドラ外で下準備が必要。
  • Masterの初回呼び出し時のオプションと2回目以降で変える必要がある。
    • initializeオプション。
  • FMU_handlerことFMUシミュレーション用処理のコードを提示。
    • 外部の変数を使用するためにglobal定義している変数あり。
  • 実時間の経過を元にFMUシミュレーション時間を決定している。
    • 実時間にシミュレーション時間を追いつかせる方式。
    • ただし、開始と終了のstepが重複したり欠落したりするので微小時間の調整が必要。
  • plot_handlerことmatplotlibリアルタイム更新処理のコード提示。
    • 以前のリアルタイム描画実験との差は以下。
      • deque使用。
      • plot時にnp.arrayに変換しているが無くてもOK。
      • CPU負荷描画チェックボックス確認。
  • dequeのように要素数が分からない場合は-1による末尾指定が便利。
  • 現状のコードで動作確認をしてみた。
    • FMUの波形表示OK。
    • Scaleで指令値変更OK。
  • まだ以下の課題が残っている。
    • ソースコードがちょっとヤッツケ。
    • 負荷関連の評価が出来ていない。
      • 実際の負荷状態。
      • 描画処理の影響有無の評価。
      • なんとなく現状でも負荷の影響が見え隠れはしている。
  • 現状のソースコード確認。
    • 暗黙的なものも含めてグローバル変数が点在している。
      • GUIイベントとかタイマハンドラで関数が分離しているので仕方ない面もある。
  • 折角なのでクラスとしてまとめてみようと試みる。
    • 基本的なロジックは出来ているのでそれほど修正時間はかからない見込み。
  • 実験用Pythonコードのクラス化。
    • 基本、関数横断変数をメンバ変数にした。
    • つまり、メンバ変数になっているものが暗黙的なグローバル変数だった。
    • 「実はグローバル変数だったのかー!」ってのはあるある。
  • 前回と同様の動きはしていそうなので、このコードをベースに実験を継続する。
    • 特に負荷関連が残っている。
  • 修正コードでとりあえず動作させてみた。
    • 問題無く動作した。
  • 負荷確認実施。
    • カクついているが人間の操作のせいの可能性もある。
  • sin波の自動入力で確認。
    • やはりカクついている。
      • よって、操作の問題ではない。
  • CPU負荷を見て、さらにmatpotlibの波形拡大で詳細確認。
    • FMU処理以外の処理負荷大きそう。
  • 前回の負荷を安定化する方法を検討。
    • 描画に処理時間を持っていかれているっぽからpauseで描画だけ停止していた。
      • 案の定、負荷安定化。
  • 上記より、フェーズを分けた利用法が考えられる。
    • デバッグフェーズは波形を見ながら。
    • ある程度デバッグが完了したら波形無しの高精度状態で検証する。
  • 描画を止める以外で負荷低減方法を検討してみた。
  • スレッドの利用案が出たが以下の理由で不可。
    • GIL(Global Interpreter Lock)仕様の影響で期待するスレッドのコンテキストスイッチにならない。
    • よって、負荷はほぼ減らない。
      • 下手したら増える可能性もある。
      • というわけで対策としては見送り。
  • スレッド以外で処理分散を検討。
    • マルチプロセスでやれば分散できるかもしれない。
  • マルチプロセスを実現するにはプロセス間通信が必要。
    • Queue、Pipe、共有メモリとあるが、今回はPipeを使用してみる。
      • ただし、通常の文字列送信方式ではなく、バイナリ方式。
      • 文字列方式は型を維持できて便利だがオーバーヘッドが大きい。
  • Pythonでマルチプロセスをする上での手順確認。
    • Pipeを使ったプロセス間通信が面倒そう。
  • 追加コードについては今回は触り程度。
    • 要importのライブラリ。
      • multiprocessingのProcessとPipe。
    • Pipeの生成。
      • 送受信の2つが取得できる。
      • 引数自体で双方向、片方向が切り替わる
  • Pythonでサブプロセスの生成と開始について説明。
    • Process関数を呼ぶだけ。
    • 引数にサブプロセス開始時に呼び出す関数とそれに対する引数を渡せる。
      • 今回はPipeを渡しておく。
  • Pipeによる送信について説明。
    • send_bytesを呼ぶだけ。
    • dequeのままでは渡せないのでdouble型のarrayにする。
  • Pipeの受信処理のコードを提示。
    • 受信自体は一関数だが、byte配列からdoubleに直すのに手間がかかってる感じ。
  • Pipeの受信確認。
    • 受信関数自体は受信が来るまで戻ってこないので、無条件では呼びたくない。
    • よって、poll関数で受信有無を確認した後に実際の受信処理をする。
  • 次回から実際のコードと動作確認。
  • マルチプロセス化した実験コードを提示。
    • 基本的な流れは一緒だが、Pipe関連のコードが増えた。
    • deque毎にsend_bytesで送信。
    • Scaleの入力やsin波生成指示を描画のメインプロセスからFMUのサブプロセスに送ってる。
      • sin波をサブプロセス側にしないと、結局描画負荷でsin波が崩れる。
  • マルチプロセス化実験コードを実際に動作させてみた。
  • 一応負荷は下がったが、それでも50ms間の負荷がかかるポイントが散見される。
  • 結果としてはPipeのプロセス間通信のオーバーヘッド。
    • 他のプロセス間通信でもこのオーバーヘッドはそれほど変わらない。
  • 今後の実験は通常のシングルプロセス版のコードをベースとする。

CAN連携

  • さらなるHILSっぽさを求めて外部入力を検討。
    • ECUのインターフェースを想定すると分かり易い。
  • インターフェースは様々ではあるが、とりあえずCANであれば融通が利きそう。
    • CANからADC、DAC、PWMへの変更はそれほど大変ではない。
    • よって、今後はCANをインターフェースの前提として話を進める。
  • CANをHILSのインターフェースにするには本来では専用のインターフェース装置が必要。
    • しかし、今回はVector社のXL Driver Library付属のVirtual CAN Busを使用。(無償)
  • 実験構成は指令値をCANで指定。
  • 前回までのtkinterに対して追加する。
    • 入力パターンが増えるイメージ。
  • CAN経由で渡したい情報として指令値を仮定義。
    • Ramp Up、Ramp Down、Stepなどの組み合わせ
  • Python-CANの復習。
    • インストールの話。
      • pip使えばOK。
    • importの話。
      • canをimportすればOK。
  • 以降はバス初期化、送受信などの話が続く予定。
  • Python-CAN関連は基本的には過去記事参照でOK。
  • バス初期化時に使用するデバイスやボーレートを設定。
    • 回線が複数ある場合は、回線毎にバス初期化をする。
    • そして戻り値のbusをもって通信先を識別する。
  • CANのメッセージはcan.Messageで作成。
    • CANID、標準or拡張フレームの指定ができる。
  • Python-CANで指令値送信のコードを提示。
    • CAN関連の前回までの復習の話のまんま。
  • 利便性を上げるためにコマンドライン引数を取り込む機能追加。
    • 送信周期変更と繰り返し処理切替を追加。
  • 動作確認はBusMasterでモニタすることで実施予定。
    • 実際のCANだと対抗機が必要だがVirtual CAN Busでは不要。
  • Python-CANで送信確認。
    • 送信は成功している。
  • BusMasterで確認。
    • 送信状況はモニタできている。
    • 100ms周期より若干上振れしている。
      • Sleepで周期を作っているため、どうしても上振れ方向になる。
        • よって、今回は気にせず指令器としてはOKとする。
  • CAN受信用にモード切替のUIが必要。
    • チェックボックスで実施予定。
  • CAN受信のimport、バス初期化はCAN送信側といっしょ。
  • 実際の想定受信コードも記載。
    • スレッドを使うことも可能だが、タイムアウトを0秒にするポーリング型を想定。
      • とりあえず、負荷に影響を与えない&シンプルな実装にする。
  • GUI(tkinter)にチェックボックス追加。
    • 「can rcv」というチェックボックス。
    • チェック時に受信有効になる想定。
  • CAN受信にチェックボックス判定追加。
    • if文を追加したのみ。
  • バス初期化、チェックボックス生成は__init__メソッドに追加予定。
  • CAN受信はFMU処理のタイマハンドラ内に追加予定。
  • CAN連携の受信側(FMU処理、グラフ描画)のコードを開示。
    • __init__にバズ初期化とチェックボックス配置。
    • FMU_handlerでCAN受信。
      • ただし、タイムアウト0秒のポーリング方式。
  • 一応起動することだけは確認済み。
    • あとは指令器側のCAN送信との連携を試すのみ。
  • 「指令器」と「FMU処理&グラフ描画処理」の結合実験実施。
    • 共に問題無く動作。
    • CANの送信周期が100msというのもあって、波形もキレイ。
  • 上記の状況を動画で確認。
  • 「HILSもどき」というおおよそ目的を達成した気もするが、他のアプローチも試したいのでこのシリーズはまだまだ続く。

XCP Basic

  • 「HILSもどき」を利用した試したい事はXCPを利用することらしい。
  • XCPはBypass関連の記事で一年以上前にやったことはある。
  • BypassはECUの一部の機能をXCPを使って疑似化するもの。
  • 今回やろうとしていることはECUの外側をXCPを使って疑似化するもの。
    • 詳細は次回説明予定。
  • ECUの外側の疑似化にXCPを使うってのはインターフェースをXCPにするってことだった。
    • 以前やったBypassとはデータの流れは逆向きになっている。
  • XCPで直接RAMの読み書きを行うことで物理的なインターフェースの制約を無視できる。
    • 逆に物理的な制約に紐づいた検証はできないという欠点はある。
  • XCPのシミュレーション環境が欲しい。
    • 候補としてはVector社で無償で公開されているXCP basicがある。
  • XCP basicはEULAとしてはVector社の責が無いことを前提に自由にして良い。
    • サポートが必要な場合は有償。
    • XCP ProfessionalやMICROSAR XCPというプロプライエタリ品もある。
  • プロプライエタリ品との差別化のためXCP Basicには機能制限がある。
    • XcpBasic.cに機能制限について記載されている。
    • 特に大きいのがSTIM使用不可。
  • STIM使用不可に関してはDOWNLOADで代替可能。
    • ただし、スループットはどうしても落ちる。
      • とりあえずの落としどころとしてやむを得ず。
  • XCP Basic動作までの方針を決めた。
    • 数点、要否が確定できない作業があるが、実際に確認してみてから決める。
  • 実験構成を提示。
    • CANの制御はPython-CANを使用。
      • 単純な送受信であればお手軽。
    • 恒例のVirtual CAN Busを使用。
      • 物理的な制約を一旦無視して論理的な正しさを求める場合はこれが一番楽。
  • XCP Basicを入手方法。
    • Vector社のサイトから入手。
  • インストール。
    • 最初にEULAが出てくる。
    • インストール先にはXCP Basicのソースコードが展開されるので好きなところでOK。
  • ビルド環境は恒例のVisual Studio 2017 express。
  • Virtual CAN Busのインストール。
  • XCP Basicのフォルダ構成を確認。
    • ドキュメント、EULA、Sample、XCP Basicハード非依存コードなどが配置されている。
  • Sampleに各物理層のポーティングコードが入っている。
    • XCPsimがPCシミュレーションを想定。
      • Ethernet(TCP/UDP)かCAN(VN16xx)を物理層と想定。
  • XCP BasicのVisual Studio 2015から2017へのアップグレードは特に何もなかった。
  • ビルドを実施。
    • warningは出ているが、sscanfをsscanf_sにする話。
  • 起動オプションがある。
    • XCPsim -hでヘルプを参照可能。
    • デフォルトはCANでオプションでEthernetへの切り替えもできる。
  • とりあえずXCP Basicのテスト用にメモリアクセスに絡まない以下のコマンドを送ってみる。
    • CONNECT。
    • GET_STATUS。
    • SYNC。
  • コマンドの送信はPythonの対話モードかJupyter Notebookで実施。
  • まずはCONNECTコマンドを送信。
    • 無事送信&受信できた。
    • 他のコマンドも同様に試してみる。
  • XCP BasicにGET_STATUSコマンドを送ってみた。
    • 問題無く応答あり。
  • 次にSYNCコマンドを送ってみた。
    • 応答があったがエラー応答?
      • 実は「コマンドプロセッサの同期。」を示す0x00というエラーコードで通常応答になる。
  • メモリ非依存のコマンド確認は取れた状態。
  • PCシミュレーションの場合、XCP Basicでメモリアクセス系コマンドを実施には課題がある。
    • 各変数のアドレスが起動毎に変わる可能性あり。
  • よって、以下のどちらかが必要。(後者を採用)
    • 変数のアドレスをファイル出力して、XCPマスタからアクセス毎にファイル参照。
    • 配列定義して配列先頭を0アドレスとする。
  • xcp_cfg.hの修正は以下のdefine定義を追加するだけ。
    • XCP_ENABLE_CALIBRATION。
    • XCP_ENABLE_MEM_ACCESS_BY_APPL。
  • XCP_ENABLE_CALIBRATIONはDOWNLOADとDOWNLOAD_MAXが有効になる。
    • SHORT_DOWNLOAD等は使用不可。
  • xcp_cfg.hについかしたXCP_ENABLE_MEM_ACCESS_BY_APPLのdefine定義について説明。
    • XCP経由のメモリアクセスが直接アクセスからフック関数を経由する状態に切り替わる。
  • ecu.cの修正。
    • アクセス用の配列を定義。
    • メモリアクセス用フック関数の定義と実装。
  • XCP Basicがサポートしているメモリアクセス系コマンドを列挙。
    • SET_MTA。
    • DOWNLOAD。
    • DOWNLOAD_MAX。
    • SHORT_UPLOAD。
    • UPLOAD。
  • Python-CAN初期化とCONNECTコマンドを発行してからSET_MTAコマンド発行。
    正常応答あり。
  • DOWNLOADコマンドを実施。
    • 正常応答あり。
    • 実際に書き込まれたかはUPLOAD系コマンド実験時に確認予定。
  • DOWNLOAD_MAXコマンド実施
    • MTAは読み書き時にそのサイズ分参照アドレスが後方にズレる。
      • C言語のポインタ的な仕様。
    • MAX_CTO(今回の場合は8)-1分のサイズが無条件に書き込みサイズになる。
  • UPLOAD系コマンドの実験開始。
    • UPLOADコマンドが基本形のコマンドでその派生形としてSHORT_UPLOADがあるが、SHORT_UPLOADのとある挙動を確認するためにSHORT_UPLOADから実施。
  • SHORT_UPLOADでDOWNLOADで書き込んだ値が読めた。
    • よって、両コマンド共に動作OKとなる。
  • なんとSHORT_UPLOAD時にMTAも更新される仕様になっていた。
    • よって、続けて読み出す場合はSET_MTA無しでUPLOADを実施すればOK。
      • この仕様を利用してSHROT_UPLOAD→UPLOAD×n回とすると、効率的なメモリダンプが実現できる。
  • メモリアクセス系コマンドはこれで動作確認OKとなる。
  • メモリアクセス系コマンド以外のメモリアクセスとしてDAQ(Data AcQuisition)がある。
  • DAQは過去記事で数回にわたって説明しているのでそちらを参照。
  • 必要なコマンドをそこそこあるので、一個ずつ試していく。
    • 尚、XCP BasicはDynamic DAQが実装されている。
      • Static DAQは未実装。
  • XCPパケット送受信処理を関数化してみた。
  • CAN送信は従来通り。
  • CAN受信でいろいろ判定を追加
    • タイムアウト追加。
      • 1秒タイムアウト。
    • CANID判定追加
    • PID判定追加。
      • 0xFE(エラー)か0xFF(正常)以外はレスポンスとして扱わない。
      • レスポンスのタイミングでDAQパケットが来ても無視できる。
  • FREE_DAQコマンドを送信。
    • ECU内部のDAQ list構造を真っ新にするコマンド。
    • “とりあえず”一番最初に投げらえる。
  • ALLOC_DAQコマンドを送信。
    • 生成するDAQ listの数を指定するコマンド。
    • XCP Basicの場合、DAQ list毎に6byte消費。
  • ALLOC_ODTを送信してみた。
    • ODT1個あたり4byteの管理領域のリソース割り当てがされる。
  • 試しに5個ずつODTを生成してみた。
    • 20個のODT生成時にエラー。
      • エラー理由はメモリ不足。
      • 1ODTあたり8byteの送信バッファも必要なのでそれも含めてメモリ不足。
      • この点含めてリソース管理が必要。
  • ALLOC_ODT_ENTRYは物理層の都合やODTのフォーマットによる制限がある。
    • CANの場合だとデータフィールド8byteが制限。
    • タイムスタンプ等が入ることで計測データを格納する範囲が少なくなる。
  • ALLOC_ODT_ENTRYを複数回投げてみた。
    • リソースが枯渇するまで生成は可能。
  • DAQ listの構造的な準備はできたので、実際にODT_ENTRYを更新していく。
  • 更新するODT_ENTRYを参照するにはSET_DAQ_PTRを使用する。
  • 実際にODT_ENTRYを更新するにはWRITE_DAQを使用する。
    • WRITE_DAQ実行後は参照するODT_ENTRYは自動的に一個後ろになる。
  • SET_DAQ_LIST_MODEコマンドでDAQの計測データ送信ポリシーを設定できる。
    • TIMESTAMP有無、使用するEvent channel、分周比など。
  • Event channelと実際の周期はECU側の実装依存。
    • 今回は1:10ms、2:100ms、3:1msとなっている。
  • DAQの開始方法は大きく2パターン。
    • DAQ list毎に個別開始。
    • 開始可能状態にして複数のDAQ listを同時開始。
      • 今回は後者で実施。
  • START_STOP_DAQ_LISTコマンドで個別開始/停止と開始可能状態の設定できる。
    • これでDAQの開始条件は揃った状態となる。
  • START_STOP_SYNCHコマンドを投げて見た。
  • 並行してcan.loggerでCAN回線モニタを実施。
    • DAQパケットの計測ができた。
  • タイムスタンプ有効設定をしていたため先頭の方にタイムスタンプが埋まっている。
  • タイムスタンプの後ろから計測データが続く。
    • 今回は0値が取得されている。
  • 計測RAM値の変更方法はSET_MTAとDOWNLAOD。
  • SET_MTAとDOWNLOADの電文確認。
    • 折角なので連続で投げて見る。
      • ただ、同じ値だと変化が分からない無いのでちょっと書き込みデータを変える。
      • さらにDAQパケット送出前に書き換えてしまう可能性もあるのでsleep関数を挟む。
  • 実際にSET_MTAとDOWNLOADを使ってRAM値書き換えを実施。
  • RAM値書き換え前にDAQを起動しておき、RAM値書き換えとDAQパケットの状況をCAN回線モニタして確認。
    • RAM値書き換えに合わせてDAQパケットの計測データが変化。
  • おおよそXCP Basicの挙動はOKだが、まだちょっと問題がある。
  • XCP Basic PCシミュレーションの問題点は送信周期。
  • 前回のCAN回線ログのDAQパケットだけを抽出し、それのタイムスタンプと元に現状の送信周期を算出。
    • 10[ms]周期で送信されるべきところが15[ms]になっていた。
      • 一応、原因は分かってはいるので、そこらへんを次回確認予定。
  • DAQパケット送信周期の精度が悪い理由を確認。
    • イベントチャンネルが15[ms]になってる。
      • Sleep(10)で10[ms]周期を作ろうとしているが、実際は15[ms]になってる。
        • この部分はPC依存な面はある。
  • 上記により小手先の回収ではどうにもならなそう。
  • 一応対策も考えてるので次回確認予定。
  • DAQパケット送信周期精度改善の方針を決めた。
    • 時間精度を引き上げるためマルチメディアタイマを使用。
      • 1[ms]精度の過去実績あり。
  • Sleep(10)を挟んだWhileループをマルチメディアタイマによる1[ms]コールバックに変更。
    • これに伴い10[ms]周期だけでなく、イベントチャンネル3も1[ms]保証ができる。
  • 再度DAQパケットを確認。
    • XCPの一連のコマンド含めてCAN回線ログとして取得。
  • DAQパケットだけ抽出し、タイムスタンプを確認。
    • 狙い通り10[ms]周期の送信周期になっていた。
  • ソースコード等はGithubに上げておいた。
    • Python側はJupyterNotebook形式。

PyXCP

  • Python-CANにXCPの上位プロトコルを載せたPythonLibraryが存在。
    • その名はそのまんまでPyXCP。
  • PyXCPのインストールはpip使えばOK。
    • pip install pyxcp。
  • PyXCPだけだと疎通確認も取れないからXCP BasicによるPCシミュレーション環境はかなり重要。
  • PyXCPを利用する上で必要なimportを列挙。
  • 設定ファイルがJSONファイルを想定しており、Python内の文字列をファイル
    • 認識されるためにio.StringIOもimport。
  • StringIOに引き渡すJSONを提示。
    • 比較的自明なパラメータが多いが一部分かり難いものもあるので次回説明。
  • JSON文字列を再度確認。
  • それぞれのパラメータについて一気に説明。
  • 使用できるCANインターフェースデバイスは多い。
    • 有名どころは網羅されている。
  • トランポート層にSxIを指定できる。
    • SPI、SCI(UART)のことでPyXCPに於いてはCOMポートになる。
    • と言ってもそれほど利用シーンは無い。
  • コンフィグレーションパラメータ(JSON文字列)の作成と読み込みのコードを提示。
    • readConfigurationにStringIOを渡しているが、拡張子がjsonのファイル名を設定しておく必要あり。
  • コンフィグレーションを元にXCPマスターの生成。
    • コンフィグレーションを渡すことでXCPマスターが生成される。
  • 早速PyXCPでCONNECTとGET_STATUSのコマンドを投げて見た。
    • 共に問題無く動作
  • 該当メソッドの戻り値でレスポンスの詳細が取得できる。
    • かなり見やすい構造になっている。
  • GET_STATUSはレスポンス内容の性質上、DAQ起動中やCAL_PAGEのROM書き戻し完了待ちなどの確認使用されることが多い。
  • SYNCHコマンドを投げた。
    • 想定通りERR_CMD_SYNCHを受け取れた。
  • GET_COMM_MODE_INFOコマンドを投げた。
    • 今回使用しないがinterleavedMode、masterBlockModeの有無や関連パラメータが返ってくる。
    • 上記以外に適用しているXCP仕様Versionも取得可能。
  • メモリアクセス系コマンドということでSET_MTA、DOWNLOAD、UPLOADを実施。
  • DOWNLOADの結果がUPLOADするまで分からないということと、一個ずつ試すがめんどいので一気に流した。
  • UPLOAD→DOWNLOAD→UPLOADの流れでverifyすることで読み書きが正常に行われていることが確認できた。
  • DAQ listの構築を一気に実施した。
    • FREE_DAQ、ALLOC_DAQ、ALLOC_ODT、ALLOC_ODT_ENTRYを一気に実施。
    • 以前XCP BasicでやったDAQ list構築に合わせた構成にしてある。
    • PyXCPのメソッド単位で隠蔽されていることもあり、かなり楽ちん。
  • WRITE_DAQの前にODT_ENTRYのデータ管理の便利な方法について説明。
    • namedtupleと使う。
  • tupleは異なるデータを一組に管理する手法や構造。
  • namedtupleはtupleの各要素に明示的に名前を付けられるようにしたもの。
    • 管理のし易さを見やすさの両方が得られる。
  • ODT_ENTRYをnamedtupleを使ってデータ管理してみた。
    • 今回は一個しかないのでほぼ効能はないが、ODT_ENTRYが増えてきた際には大きな効能が見込めそう。
  • 実際にSET_DAQ_PTR、WRITE_DAQ実施。
    • 問題なく動作。
    • 毎回SET_DAQ_PTRを投げるようなコードになってるが、とりあえずOK。
  • DAQ起動すべく以下のコマンドに相当するメソッドを実行。
    • SET_DAQ_LIST_MODE。
    • START_STOP_DAQ_LIST。
    • START_STOP_SYNCH。
  • 狙い通りDAQ起動はした。
    • CAN回線上でDAQパケットが流れていることが確認できた。
    • しかし、PyXCP上でDAQパケットを確認する術が不明。
  • DAQパケットの取得方法の概要説明。
    • transport層に相当するクラスでdaqQueueが定義されている。
    • このdaqQueueに自動的にDAQパケットがキューイングされる仕組み。
  • 上記仕組みはJSONコンフィグレーションのCAN_USE_DEFAULT_LISTENER
    • trueでないと使えない点に注意。
  • PyXCPでDAQパケットを受信しながらSET_MTA、DOWNLOADのコマンド送信を行う実験。
    • XCP Basic側は問題なくできることは分かってるのでPyXCP側メインの実験。
    • 一応PyXCP内コード的には大丈夫そう。
  • 実験コードはDAPパケット受信ループ内で0.05秒周期でSET_MTA、DOWNLOAD発行。
  • DAQパケットを受信しながらSET_MTA、DOWNLOADのコマンド送信してみた。
    • 一応動いた。
    • が、DAQパケットの吸い上げのリアルタイム性が若干悪い。
      • 20~30[ms]程度の遅れがある。
  • Pythonでやる以上、やむを得ない問題ではあるが、もう少し手が無いか考える必要はありそう。
  • XCP BasicとPyXCPは微妙だった?ってことはない。
    • 計測やちょっとしたキャリブレーションであれば問題無く使える。
    • HILSもどきと相性が悪いだけ。
  • オーバーヘッドを検討。
    • 15msはSET_MTAとDOWNLOADの2回のコマンド発行分。
    • ここを何とかすればもう少しマシになりそう。
  • SET_MTAとDOWNLOAD以外のRAM書き換え方法はSTIM
    • しかし、XCP BasicはSTIMは未対応。
  • XCP Basicに対してSTIM拡張をするかXCP Basic以外のXCPスレーブIPを探すか。
    • とりあえず後者の線でやってみる。
    • 見つからなかったら諦めてSET_MTAとDOWNLOADで頑張る方向で。

AUTOSAR-XCP

  • XCP Basicとは別のXCPスレーブIPを見つけてきた。
    • その名はAUTOSAR-XCP。
    • 以前やったCanTp、DCMと同じくAUTOSAR-BSWのXCP仕様に準拠したもの。
  • ライセンスがLGPL。
    • よって、条件によってはコード開示が必要になり、商用利用としては難しい場合がある。
  • LGPLはライブラリ化すればライセンス汚染をある程度食い止められる。
  • しかし、完全に食い止められるわけではなくどうしてもリバースエンジニアリングを許容する条件は付く。
    • これは動的リンクであっても変わらない。
  • AUTOSARのXCP実装、STIMの実装を学べるという利点があるので、使ってみる方針で行く。
  • AUTOSAR-XCPの機能範囲について説明。
    • 純粋にXCP BWSを実現しているのみ。
    • よって、下位BSWのCanIfを実装する必要がある。
  • CanIfは以前、診断通信関連BSWであるCanTpのシミュレーションをしたときに作成してる。
    • 微調整はあるかもしれないが、これを使いまわす予定。
  • AUTOSAR-XCP PCシミュレーションに向けてのロードマップを提示。
    • 恒例の実験構成、環境、実験内容を提示。
  • 実験構成の提示。
    • こちらもおなじみのVirtual CAN Busを使用した構成。
  • AUTOSAR-XCPのPCシミュレーション環境も恒例のVisual Studio 2017 express。
  • AUTOSAR-XCPをGithubからCloneしてきてソースコードを確認。
    • とりあえず、全部使う。
  • AUTOSAR仕様として必要そうなソースとヘッダを洗い出し。
    • TOPPERSプロジェクトのA-ComStackとATK2から頂戴する。
    • 足りないかもしれないが、まずはこれでビルドを通してみる。
  • AUTOSAR-XCPをビルドする上で排他同期等でWindowsAPIが必要となる。
    • しかしwindows.hが他の定義を競合することがある。
    • よってstub.cでラップ関数を定義して避けている。
  • マルチメディアタイマで1msコールバックを生成。
    • ECUの実装が1ms周期を起点に処理されることが多く、それを模擬してる。
  • CanIfとかAUTOSAR-XCPから呼び出される関数群の辻褄合わせをmain.cで実施。
    • 送信関数と送信完了割り込み、受信割り込み、エラー割り込み。
    • 排他制御関数。
    • エラー通知関数。
  • main関数で初期化処理関連を実施。
  • これでやっとビルドが通った状態となる。
  • AUTOSAR-XCPのコンフィグレーションはREADME.txtに説明あり。
    • Xcp_Cfg.hとXcp_Cfg.cを作成する必要あり。
  • Xcp_Cfg.hを作成。
    • README.txtに記載が無かった項目として以下がある。
      • XCP_PROTOCOL。
      • XCP_STANDALONE。
      • BYTE_ORDER。
  • Xcp_Cfg.cの作成したコードを確認。
    • DAQ、STIMで使うイベントチャンネルの定義がほとんど。
  • Online Calibrationで使用されるSegmentについても記載するパラメータがある。
    • アクセスするメモリ空間を疑似的に切り替える概念を実現。
    • 今回は使用しないので、そういう概念があるのを認識程度に留める。
  • AUTOSAR-XCPの動作確認開始。
    • XCPコマンドはPyXCPを使って実施。
    • XCP Basic、PyXCPで散々やったところなので巻き気味で推進。
  • 一気にCONNECT、GET_STATUS、SYNCH、GET_COMM_MODE_INFOを実施。
    • 当然ながら問題無く動作。
    • CAN回線ログも一緒に確認。
  • AUTOSAR-XCPのメモリアクセス系コマンドの動作確認を実施。
  • 今回も一気にSET_MTA、DOWNLOAD、UPLOADを確認。
    • 最後UPLAODでverifyして想定される値が書き込まれていたのでOK。
    • 念のためCAN回線ログも確認。
  • 次回からDAQ関連に突入。
  • AUTOSAR-XCPのDAQ設定系コマンドの動作確認。
    • FREE_DAQ、ALLOC_DAQ、ALLOC_ODT、ALLOC_ODT_ENTRYを一気に確認。
  • ODT_ENTRYへの書き込み実施。
    • SET_DAQ_PTR、WRITE_DAQを確認。
  • この段階のDAQ listはDAQかSTIMかの利用方法の指定はしていない。
  • AUTOSAR-XCPのDAQ listモード設定コマンドの動作確認実施。
    • SET_DAQ_LIST_MODEを実施。
      • STIM側はTimeStampFieldは不要なので削除指定。
      • さらにRAM値への更新速度を上げるため1ms周期。
    • START_STOP_DAQ_LISTを実施。
  • ついにAUTOSAR-XCPでSTIMを実現。
    • SET_MTA,DOWNLOADで実現していたコードを改修。
    • 若干、遅れがあるように見えたが、CAN回線ログと比較した感じだと、全に正しい結果となっている。
      • 思った以上の成果と言える。
  • フクさんが一点気になることがあるらしい。
    • これは次回説明予定。
  • AUTOSAR-XCPのSTIMの気になる点がある。
    • STIMパケットに対してRESパケットが発行されている点。
    • 仕様書上は、RESパケット応答に関する振る舞いは明記されていなかった。
      • 恐らくデファクトスタンダード的な仕様が紛れている。
  • 上記は何が正しいというのは恐らくない。
    • こういうものがあるということに気を付ける。

仮想ECU/PID制御

  • 仮想ECUを作るべくAUTOSAR-XCPに制御器を組み込む必要がある。
    • PID制御器は過去記事で作ったものを流用予定。
  • 制御周期はとりあえず、10msあたりにしておく。
  • PID制御器の動作確認はXCP経由で入出力を制御することで実施する予定。
    • PID制御器自体のソースコードは以前、SILS等で確認済み。
  • PID制御器のコードを用意した。
    • 過去に使用したものを今回向けに修正したもの。
  • ecu_t10ms_job関数で10ms周期で処理。
  • ecu_init関数で初期化処理。
    • 変数の初期化を実施。
  • コンパイルが通ることのみ確認済み。
    • 動作確認にPyXCPを使用するが具体手なコードは次回以降に説明。
  • PID制御器の動作確認方法としてPyXCPを使う。
    • HILSもどきから使用することを想定してクラス化。
    • クラス化は目的次第で隠蔽する度合いが変化する。
      • つまり何をサボりたいかが動機になり易い。
  • エントリーポイントとして呼び出すかライブラリで呼び出すかで振る舞いを変えると便利。
    • 単体テストをする場合はエントリーポイント。
  • PID制御器動作確認用Pythonコードを開示。
    • コンフィグレーション、XCPマスター生成、DAQ起動、計測、書き換えを隠蔽している。
  • エントリポイントとして起動した際は簡単なXCP通信をしてXCPスレーブの挙動が見れる。
  • ライブラリとして呼び出すことも可能。
    • こんな感じのちょっとずづの積み上げが割と重要。
  • 仮想ECU側のPID制御器の入出力する信号の種類を確認。
  • 実際にXCPのDAQ、STIMを実施して制御状態を計測。
    • 想定通りの挙動になっていた。
  • CAN回線モニタを確認。
    • DAQとSTIMの目標値、実値のレイアウトを合わせていたので、縦に慣れべることで挙動が推測できる。
  • HILSもどきの前にDAQパケットを取得して波形表示する機能が必要。
    • 仮想ECU側の内部変数を表示することが目的。
    • DAQパケットはECU側の計測値なので、内部変数と解釈しても差し支えない。
  • 上記機能はDAQリスナと命名。
    • DAQリスナの実験構成を図示した。
      • 横からCANを覗き見るだけのシンプルな機能。
  • DAQリスナーのPythonコードを開示。
    • 基本的にはPython-canで受信。
    • DAQパケットならば取り込む。
    • 上記を元にmatplotlibで描画。
  • 波形表示はリアルタイムではあるが負荷低減のため0.2秒周期。
    • ここらへんの描画負荷の事情はHILSもどきの時と一緒
  • DAQリスナーの動作確認実施。
    • 問題無く動作した。
    • やや表示範囲が有ってないが次に作る「HILSもどき」向けの設定になってる。
  • 「HILSもどき」改め「仮想HILS」と命名変更。
  • 仮想HILS作成に向けてのロードマップ確認。
    • 下位レイヤの動作が先に保証済みになっていると計画が立てやすい。

仮想HILS

  • 再びPyFMIを使うということでPyFMI関連の復習。
    • 過去記事のリンクを貼っておいた。
  • PIDのFMUをDummy_FMUModel化実施。
    • FMUModelCS2でロードしていたものをDummy_FMUModelCS2でロードするに変えるだけ。
      • 当然、これに伴う変更もあるが。
  • DummyFMU化したPID制御の辻褄合わせを実施。
    • Dummy_FMUModelCS2のdo_stepメソッドをオーバーライドすればOK。
  • do_stepをdp_pidとして実装。
    • do_pid関数内部でPID.fmuの変わりに出力を決定する仕掛け。
    • このように入出力の辻褄合わせをすればOK。
  • XCP関連の処理を追加。
    • 基本xcp_canクラスを呼び出すだけ。
  • DAQ受信とSTIM送信はFMU処理が実装してあるFMU_handler内で呼び出す。
    • DAQ受信はFMU処理直前。
    • STIM送信はFMU処理直後。
    • これにより、仮想ECU側の変数と仮想HILS側のシグナルの同期が取れる。
  • ついに仮想HILSのPythonコード完成。
    • 基本的には前回までの修正内容を盛り込んだだけ。
  • 指令値取得用のCANバス初期化の位置を先頭から実処理の前に移動。
    • XCP関連のCANフレームがFIFOに詰まれてしまうため、それを避ける目的で移動。
    • 使用する前にFIFOクリアしてもOK。
  • 構成がややこしいことになってきたので仮想ECUと仮想HILSの実験構成を再確認。
    • ネットワーク構成と論理的構成を確認。
  • 論理的構成はModelicaモデルベースでシンプルだが、ネットワーク構成は各信号をCANやXCPに変えてるため複雑化。
    • このようにノードを分割しておくと部分的に実物に差し替えるなどがし易くなる。
  • ついに仮想ECUと仮想HILSの連携動作。
    • とりあえず動いたんで録画してYoutubeに上げた。
  • 想定よりもキレイだはあるが、やはりカクついている。
    • カクついている原因は変数の精度も含まれているかもしれない。
    • 変数を32bit長にすることで精度を上げられるがODTを増やす必要がある。
      • CAN-FDを使うと・・・。

XCPonCANFD

  • XCPonCANとしての課題を明確化。
    • CANのデータフィールド8byteの仕様がODTの限界値を決めていた。
    • よって、データフィールドが長くなれば解決と言える。
  • CANのデータフィールドの上限が8byteに対し、CAN-FDは64byte。
    • よって、4byte、3変数が載っても十分な長さ。
  • XCPonCAN-FDに対応すべく現状の仮想ECU、仮想HILS構成を再確認。
    • CANのところをCAN-FD化。
    • XCP経由で変数のやり取りをしている部分を32bit化。
  • 改造方針と同時に動作確認手段も模索する必要あり。
    • python-can、PyXCPを駆使すればなんとかなりそうという当たりだけ付けた。
  • AUTOSAR-XCPのCAN-FD対応に向けての方針を決める。
    • CanIf相当の部分とXCPのパケット長のところを修正する必要あり。
    • といってもCanIf相当の部分は実はすでにCAN-FD対応済み
  • CAN-FD対応の際はDLCの仕様の特殊さに注意する必要がある。
    • 8以下の時と8を超えた時で雰囲気が変わる。
  • AUTOSAR-XCPのパケット長変更を実施。
    • MAX_CTOとMAX_DTOを8から64に変更すればOK。
    • CTO、DTOについては過去記事で復習。
  • 念のためCANFD_SUPPORTで#ifdefで切り分けられるようにしておく。
  • 仮想ECU側のPID制御の入出力定義はポインタで実施。
    • 元々が符号付き16bit変数へのポインタだったのでこれを符号付き32bit長変数に差し替え。
    • 上記に伴い、参照先のアドレス境界を16bitから32bitに切替。
  • PID制御器の入力はfloat64で、参照変数が16bitか32bitかは気にしなくてもOKなコード。
  • 指令器の修正範囲を確認。
    • CANがCAN-FDに変えることから逆算して特定。
      • バス初期化とメッセージ構築の部分。
  • バス初期化はCAN-FD有効化とDataRateの設定。
  • メッセージ構築はCAN-FD化とbitrate_switch有効。
    • bitrate_switch無効でもOKだがbitrateは切り替わらない。
  • xcp_canクラスのCAN-FDに関係する部分を確認。
    • 要はPyXCPをCAN-FD対応にする部分。
      • コンフィグレーションのJSON記述を修正すればOKっぽい。
      • ソースコードからのリバースエンジニアリングによる調査結果。
  • 本当合ってるか不安なのでJupyterNoteBookで動作確認を先に実施予定。
  • PyXCPのCAN-FD対応はコンフィグレーションのJSON記述部分だけではあるが、動作確認用に修正する部分もある。
    • UPLOAD、DOWNLOADのサイズ。
    • DAQ listのODTのサイズ。
  • 上記の方針に合わてコード修正実施。
    • DAQ listはODT_ENTRYの数を増やした。
  • PyXCPのCAN-FD対応の動作確認の結果を確認。
    • 一見するとうまく動いているように見える。
    • が、どうやらPyXCP側からの送信フレームがCAN-FDになっていない。
  • 少なくともCAN-FDフレームの受信はできている。
    • PyXCPもVersionがあるので、Version別の挙動を確認してみる。
  • PyXCPのVersion別CAN-FD対応状況を確認。
    • かなり、いろいろ変化している。
    • SERIALパラメータがあると、CAN-FDフレーム送信でエラーが発生する。
  • よって、現時点では0.16.5のVersionを使用する。
    • CAN-FDフレーム送信可能、DLC可変。
  • PyXCP Version0.16.5にてCAN-FDの動作確認再開。
  • まずはコンフィグレーション用JSON記述を修正。
    • SERIALパラメータを削除。
  • SERIALパラメータはデバイスの製品シリアルを指定するもの。
    • CAN-FD対応有無を判断しているようで、この判定でCAN-FD不可側になってるように見える。
  • PyXCP Version0.16.5にてCAN-FDの動作確認。
    • UPLOAD、DOWNLOAD、DAQ、STIMを確認。
  • 基本的にOK。
    • PyXCP側からのCAN-FDはBitrate_switchが無効になっているが、現状のPyXCPの仕様上やむを得ない。
  • この結果を元にxcp_canクラスを改造予定。
  • xcp_canクラス改造箇所を整理。
    • 予想以上に多いが、仮想HILSにXCPの都合を見せないための部分なので修正内容が集中し易い。
  • コンフィグレーション用のJSON記述の修正。
    • 事前のPyXCP CAN-FD実験を元に修正。
      • SERIALを消して、FDとDATA_BITRATEと追加。
  • DAQ listのODT_ETNRYのデータ長変更(16bit→32bit)。
    • namedtupleを使用していたので修正は楽。
  • DAQの受信レイアウト調整。
    • int.from_bytesを使用してるので16bitから32bitへの変更は楽。
  • DAQ受信データの物理値変換(1/65536→浮動小数点)。
  • STIMの送信レイアウト調整した。
    • int.to_bytesを使用指定していたので一撃で修正完了。
  • STIM送信時のrawデータ変換(浮動小数点→1/65536)を実施。
    • xcp_canのテストコードとしては送信データをリスト管理しているだけなので、そのリスト修正するのみ。
      • 仮想HILS修正時に要注意。
  • xcp_canクラス改めxcp_canfdクラスを作成。
  • xcp_canfdクラスのソースコードを開示。
    • 前回までの修正分を盛り込んだのみ。
      • コンフィグレーション、ODT_ENTRY構成、STIMレイアウト、DAQレイアウト、STIM送信用データ。
  • 次回は、これを実際に動作させてみる。
  • xcp_canfdクラス動作確認を実施。
    • 問題無く動作。
    • 念のためxcp_canの時の動作確認結果とも比較。
      • 同じ動きをしていることが確認できる。
  • CAN-FD回線モニタも実施し、CAN-FDフレームのレベルでも確認。
    • こちらも想定通りの動作をしていることを確認。
    • PID制御器もおおよそ狙い通り動いてそう。
  • DAQリスナーのCAN-FD対応の要否ついて説明。
    • CANのみ対応のインターフェースはCAN-FDフレームを検知すると「異常フレーム」と認識しエラーフレームをもってフレーム破壊を行う。
      • よって、CAN-FDフレームが流れるネットワークにCANのみ対応インターフェースは接続禁止。
  • DAQリスナーの修正は一撃。
  • DAQリスナーCAN-FD対応版の動作確認を実施。
    • can.loggerのCAN-FDモードを並走さえて回線モニタを実施。
      • 問題無くCAN-FDフレームが送出されていることを確認。
        • Bitrate_switchも有効になっている。
  • python-canの範疇では問題無しと判断できる。
  • 仮想HILSのCAN-FD対応方針整理。
    • 数としてはそこそこあるが、一個一個は1行修正のレベル。
  • importしているxcp_canをxcp_canfdに変更。
  • 上記のクラス変更に伴い、XCPインスタンスの生成部分変更。
    • importのところでエイリアスを使うのもあり。
  • 指令値受取用バスの初期化変更。
    • バス初期化の引数をFD用に変更するのみ。
  • XCP DAQ受信部のレイアウト変更対応とLSB変更対応
    • xcp_canfdクラス作成時のテストコードと同じ対応。
  • XCP STIM送信部のレイアウト変更対応とLSB変更対応
    • レイアウトはxcp_canfdクラス内で決めているのでLSB対応のみ。
  • 仮想HILS側の修正後のコード開示。
    • 前回までの修正範囲と内容を反映したのみ。
  • tkinter、matplotlibのコードもあり、肥大化しているが、基本一直線のコード。
    • タイマハンドラで周期的に呼ばれるくらい。
  • いろいろな要素が絡んでるので、次回は全体構成の再確認。
  • XCPonCANFD対応に於ける当初想定していた仮想HILS、仮想ECUの全体構成と実際の全体構成。
    • 基本的には想定通りの修正。
      • PyXCPのCAN-FD対応が難航したのが想定外ってくらい。
  • 論理構成としては変わらず。つまり基本的な動作は変わらないはず。
    • 変数のサイズと精度が変わっているのでそれの効能を期待。
  • 仮想HILSと仮想ECUのXCPonCANFD対応の動作確認は結果としては失敗に終わった。
    • 変数の精度向上はあまり性能向上にはつながらなかった。
  • しかしXCPonCANFDを直に見るのも珍しい体験なので、これはこれで将来に生かすって発想が大事。
    • 失敗したからこそ意地でも糧になるものを拾うべし。

CANoeでFMU/FMI

  • 仮想HILSと仮想ECUの精度が上がらなかった原因を予測。
    • ほぼ間違いなく応答性が原因。
      • Pythonではこれ以上の応答性は得られそうもない。
  • Pythonに変わって仮想HILS側をVector社のCANoeにしたらどうかという意見あり。
    • 本物のHILSと比べ、コスパも良さそう。
  • CANoeで仮想HILSの実現が可能かを検討する間に現行の仮想HILSの機能を列挙した。
  • FMU import&実行。
    • たぶんOK。でも要確認
  • 各種信号のグラフ表示&CAN受信。
    • 間違い無くできる。
  • XCPマスタ
    • 本当のXCPをするなら追加ライセンスが必要。
    • しかし、CAPLを駆使すれば今回の目的は達成できそう。
  • CANoeの仮想HILS化への実験ロードマップ提示。
    • 大雑把にはFMU importとXCPマスタの2つ。
  • FMU importの実験をやってからXCPマスタの実験の流れ。
    • 最初はXCPを使用せずにCANoeのシミュレーションバスを使用したFMU間連携をさせてみる。
  • CANoeでFMU import&動作実験の全体構成提示。
    • 恒例のネットワーク構成と論理構成。
    • FMU間の各信号の接続はCAN経由で行う。
  • 実は以前SimulinkDLLをCANoeで駆動させた時と近似の構成。
    • SimulinkDLLの代わりにFMUになっただけ。
  • dbcファイルについて簡単に説明。
    • CANメッセージとそれに載せるシグナルだけでなく、ネットワークノードの定義もできる。
      • ネットワークノードを定義しておくと、CANoeのインポートウィザードでノードの自動生成をしてくれる。
  • dbcファイルを作成開始。
    • CANeb++エディターを使用。
      • プロトコルの設定まで実施。
  • CANdb++エディターで各種定義を実施。
    • シグナル、メッセージ、ノードの順番で定義していく。
  • メッセージの周期時間は送信周期を示す。
    • 単位は[ms]。
    • 属性の「GenMsgCycleTime」パラメータを修正することで変更可能。
      • CANoe.IL機能を使用する時に生きてくるパラメータ。