【MATLAB】真理値表で直接回る！ブラシレスモータ

# はじめに
待ちに待ったクリスマス、街ゆく人たちは嬉しそうな声色で「メリークリスマス！」と挨拶を交わしあいます。

一方で私はと言うと、毎年クリスマスを迎える時期に下記のような挨拶をするのでした。

<b>『MATLAB/SimulinkはarduinoやRaspberryPiといった、ハードウェアと連携することが可能です。連携方法としてはMATLABと連携する方法、Simulinkと連携する方法が準備されています。』</b>（[2019年版](https://qiita.com/motorcontrolman/items/291955cbc3fb1c7e4d43)）（[2022年版](https://qiita.com/motorcontrolman/items/19cd1f935b2addd79a43)）

ということで、毎度おなじみのSimulink Support Packageを用いたMATLAB芸です。
https://jp.mathworks.com/matlabcentral/fileexchange/40312-simulink-support-package-for-arduino-hardware

そして、タイトルを見ただけで中身が大筋分かったアナタ！ MATLAB通ですね~。そうです、
私が変な芸人です
お察しの通りStateflow芸の披露となります。
（Stateflowって何？な人は[Stateflow 入門](https://jp.mathworks.com/help/stateflow/gs/stateflowonramp.html)へどうぞ。Onrampは神コンテンツ）

Stateflow単体でもMATLAB芸として十分に成立することは、[Stateflow テトリスのモデルを頑張って解読する](https://qiita.com/macht/items/5445d0d930b1e20b4ee5)を一読することでご理解頂けると思いますが、何せこの芸は競技者人口が少ない。そこにSimulink Support Packageを組み合わせた芸は、なんかもう絶滅危惧種かってぐらいの希少さになりますので珍しがって読んで頂ければと思います。
珍しがってくれ、頼む


前置きはさておきとして、Simulink Support PackageとStateflowの組み合わせで、下記のようにモータをぎゅんぎゅん回す方法について本記事では解説していきます。
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">Simulinkのオートコード機能を使ってブラシレスモータを120度通電で回している。真理値表（Stateflow）を使ってモータを回している点がミソ。 <a href="https://t.co/ycAPDTbvF2">pic.twitter.com/ycAPDTbvF2</a></p>&mdash; モータ制御マン (@motorcontrolman) <a href="https://twitter.com/motorcontrolman/status/1713569284696498444?ref_src=twsrc%5Etfw">October 15, 2023</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# 1. ブラシレスモータのハードウェア特性
以降、ほぼほぼ[東芝の120度通電解説資料](https://toshiba.semicon-storage.com/info/application_note_ja_20180803_AKX00154.pdf?did=61175
)を使っての説明になります。サンキュー東芝！ ブラシレスモータ、120度通電についてご存じの方は飛ばして頂いて構いません。

## 1.2 何を/どう操作してモータが回るか
所謂「ブラシレスモータ」は3相インバータと永久磁石同期モータの組み合わせによって構成されます。3相インバータはU相、V相、W相がそれぞれ上下2つのスイッチング素子から構成されます。(下記図にてU相であれば$Q_{U-H}$と$Q_{U-L}$から構成される)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/302506/833ad580-b7b4-8bac-fb96-242600f3164c.png)
120度通電における上下2つのスイッチング素子の動作方法はいくつかありますが、本記事では下記３つの動作状態のみを用いるものとします。

|上側SW|下側SW|本記事における出力状態の呼称|
|---|---|---|
|PWM|上側反転|<font color="blue">PWM</font>|
|OFF|ON|<font color="red">GND</font>|
|OFF|OFF|OFF|

なお、上記3状態を用いた120度通電は[東芝資料](https://toshiba.semicon-storage.com/info/application_note_ja_20180803_AKX00154.pdf?did=61175
)にて片アームPWM・上下相補スイッチングに分類されます。(さらに厳密に言うと片アームPWM・上下相補スイッチング・下ON。)PWM、OFF、GNDの具体的なイメージは下記ピンク枠内を参照。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/302506/ce250606-b58a-a156-9ac0-e9af18bc76bc.png)

PWMでは、上側SWのオンDutyを0~100%の間にて調整することでインバータ出力電圧を0VからDCリンク電圧の間に操作することが出来ますが、本記事では詳細説明は省略します。なんか調整できる要素があるんだなぐらいに思って下さい。(乱暴)

3相インバータを構成するU、V、Wの3相それぞれが上記3つの出力状態を取る事が出来ますが、実際の120度通電では各動作状態ごと1相にしか適用されません。このため組み合わせの総数としては3×2×1=6通りとなります。

|U相SW出力|V相SW出力|W相SW出力|本記事における出力状態の呼称|
|---|---|---|---|
|<font color="blue">PWM</font>|<font color="red">GND</font>|OFF|１|
|<font color="blue">PWM</font>|OFF|<font color="red">GND</font>|２|
|OFF|<font color="blue">PWM</font>|<font color="red">GND</font>|３|
|<font color="red">GND</font>|<font color="blue">PWM</font>|OFF|４|
|<font color="red">GND</font>|OFF|<font color="blue">PWM</font>|５|
|OFF|<font color="red">GND</font>|<font color="blue">PWM</font>|６|

動作状態１ではU相-V相間に電圧が印可されることで電流が流れます。動作状態２ではU相-W相間、動作状態３ではV-W相間に…というように、状態１~６を随時切り替えていくことで電流が流れる相が変化、回転磁界が発生することでモータが回転します。

## 1.3 動作状態をどうやって決定するか
状態１~６の随時切り替えにはホールセンサが用いられます。ホールセンサの詳しい説明は[東芝の120度通電解説資料](https://toshiba.semicon-storage.com/info/application_note_ja_20180803_AKX00154.pdf?did=61175
)を参照して頂きたいですが、重要なポイントとしては下記です。

:::
・120度ごとに配置される
・モータ磁極位置に応じてON/OFFを出力する
:::

下記図は、U相ホールセンサのON/OFF境界を赤線、V相を青線、W相を緑線として表した図になりますが、3本の境界線によってモータ磁極位置は60度ごとに切り替わる6領域に分割されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/302506/a65fe5f3-5c10-fef3-6691-83b0ed3ebaa4.png)

上記①~⑥領域におけるU,V,W相ホールセンサ状態は下記となります。

|本記事における領域の呼称|U相センサ状態|V相センサ状態|W相センサ状態|
|---|---|---|---|
|①|<font color="green">ON</font>|OFF|<font color="green">ON</font>|
|②|<font color="green">ON</font>|OFF|OFF|
|③|<font color="green">ON</font>|<font color="green">ON</font>|OFF|
|④|OFF|<font color="green">ON</font>|OFF|
|⑤|OFF|<font color="green">ON</font>|<font color="green">ON</font>|
|⑥|OFF|OFF|<font color="green">ON</font>|

天下り的に答えを言ってしまうと、領域①にて出力状態１を、領域②にて出力状態２を出力することを決定することで120度通電によるブラシレスモータ駆動が実現されます。


# 2. 真理値表で表現する120度通電
1.3に記載したU,V,W相センサ状態表と、1.2に記載したU,V,W相SW出力表を突き合わせることで120度通電に必要な真理値表が完成します。なお、ここでセンサ状態はON=True、OFF=Falseとして書き換えるものとします。
|領域|U相センサ状態|V相センサ状態|W相センサ状態|　|U相SW出力|V相SW出力|W相SW出力|
|---|---|---|---|---|---|---|---|
|①|<font color="green">True</font>|False|<font color="green">True</font>||<font color="blue">PWM</font>|<font color="red">GND</font>|OFF|
|②|<font color="green">True</font>|False|False||<font color="blue">PWM</font>|OFF|<font color="red">GND</font>|
|③|<font color="green">True</font>|<font color="green">True</font>|False||OFF|<font color="blue">PWM</font>|<font color="red">GND</font>|
|④|False|<font color="green">True</font>|False||<font color="red">GND</font>|<font color="blue">PWM</font>|OFF|
|⑤|False|<font color="green">True</font>|<font color="green">True</font>||<font color="red">GND</font>|OFF|<font color="blue">PWM</font>|
|⑥|False|False|<font color="green">True</font>||OFF|<font color="red">GND</font>|<font color="blue">PWM</font>|

[東芝の120度通電解説資料](https://toshiba.semicon-storage.com/info/application_note_ja_20180803_AKX00154.pdf?did=61175
)だと下記表にて表現されています。センサ状態とSW状態が左右逆に書かれている点、SW出力が上側と下側で別々に記載されている点などに相違がありますが、やっている事は同じです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/302506/700bbe12-3dad-a632-d55c-2fffff7aad3a.png)

# 3. MATLAB/Simulinkにおける真理値表の実装
皆様お待たせしました、やっと本論に入ります。と言ったのも束の間、真理値表の実装はStateflowの真理値表ブロックを使うだけなので本論が一瞬で終わります。

Mathworks公式の[真理値表のプログラミング](https://jp.mathworks.com/help/stateflow/ug/programming-a-truth-table.html)になんかいっぱい書いてありますが、要するにやることは…

Simulinkライブラリブラウザを開いてTruth Tableをドラッグ&ドロップしーの
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/302506/a87b2ac4-da85-2546-4d1c-1cb97e891341.png)

2章で作成した真理値表を入力しーの
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/302506/4d3d6605-489f-f96e-39a2-f589f9e75e84.png)

Ardino ハードウェア連携の入出力ブロックをいい感じに繋げーの
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/302506/11aee7ac-b753-fe9d-3b20-e2f6b9cba5d3.png)

するだけです。
ね、　簡単でしょう？
<BR>
<BR>
<BR><BR>

<b>…と言われても困りますよね。</b>
ということで、要点だけに絞って下記解説します。

## 3.1 真理値表とTruth Tableとの対応
下記色分けされた領域のように対応しています。なお、真理値表をTruth Tableに実装する際に行と列を入れ替えている点注意下さい。(TFの組み合わせを行ごとに書くほうが一般的な気がするが…Truth Tableは列ごとに記載する仕様のためこうなる)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/302506/16381d00-33b9-685b-b589-a8aa90dadbc7.png)
オレンジ：入力条件の説明部分。単に説明のため、記載内容は振る舞いへ影響しない。

青：True/Falseの組み合わせ。なお真理値表にない組み合わせとしてTruth Table７つめに、3入力とも「-」となる条件を追加しているがこれはデフォルトを指す。今回の場合、3入力ともTrue、および3入力ともFalseの場合にデフォルトが選択される。

緑：所定のTrue/Falseの組み合わせにおいて実行されるアクションの番号、およびその具体的内容。

## 3.2 Simulinkモデルにおける入出力設定
入出力設定の前段としてハードウェア構成、入出力を説明します。

### 3.2.1 ハードウェア構成、入出力
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/302506/200f0536-6f7c-4247-3f40-bcbb5dae9dd4.png)


滅茶苦茶ざっくり説明ですが、
①ブラシレスモータ(ホールセンサ付き)
②3相インバータ
③マイコン
④ボリューム

にて構成されます。

②3相インバータは[X-NUCLEO-IHM07M1](https://www.st.com/ja/ecosystems/x-nucleo-ihm07m1.html)を使用しています。このインバータにて使用されているドライバIC [L6230](https://www.st.com/ja/motor-drivers/l6230.html)は、U,V,W各相の上下SWを1つのPWM端子(下記IN1~IN3端子)のみで相補PWMにて操作する特徴を有すると共に、イネーブル端子(下記EN1~EN3端子)によって上下SWを両OFFとする特徴を有しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/302506/6cae2fe4-6894-36f4-41ef-f4927e6a7d34.png)

このため③マイコンから②3相インバータへの出力としては、ＰWM出力(U,V,W)と出力イネーブル(U,V,W)の6出力が必要となります。

①ブラシレスモータは②3相インバータより供給される3相電圧によって回転し、回転角度に応じてホールセンサのON/OFFが変化、これが③マイコンへと取り込まれ、Simulinkモデル上のTruth Tableへと入力されます。

最後に④ボリュームはPWMの調整用で、ボリュームをねじることで最終的にブラシレスモータへと出力される電圧が調整されるようモデルの作成を行っています。

### 3.2.2 モデル入出力設定
上記より
入力：ホールセンサ値(U,V,W)、ボリューム
出力：イネーブル(U,V,W)、PWM(U,V,W)
として定義されるため、モデルにて下記のように配置・配線します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/302506/aeedbeb7-497c-bfa7-58d6-022aa69f5b28.png)

なお、状態遷移図における出力状態とイネーブル、PWM出力との関係は下記になります。
|出力状態|イネーブル|PWM出力|
|---|---|---|
|<font color="blue">PWM</font>|ON|ボリュームと連携|
|<font color="red">GND</font>|ON|0%|
|OFF|OFF|0%|

上記表に基づき、Truth Tableのアクションテーブル欄を記入しています。再掲になりますが、領域①のU,V,W相SW出力は下記にて定義しました。
|領域|U相SW出力|V相SW出力|W相SW出力|
|---|---|---|---|
|①|<font color="blue">PWM</font>|<font color="red">GND</font>|OFF|

Truth Tableのアクションテーブル欄の記入結果は下記です。ここで、EN_xが各相のイネーブル端子、PWM_xが各相のPWM端子へと繋がっています。また、dutyはTruth Table入力端子にてボリュームと繋がっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/302506/26a2fbad-12d8-baf1-b681-99bf9f170057.png)

領域②~⑥についても同様に記入することで、120度通電に必要な出力を得ることが出来ます。
StateflowおよびTruth Tableの入力に慣れていないと面倒なようにも感じますが、状態遷移図そのものを作成することでプログラミングが完了する恩恵は非常に大きいと感じました。

# 4.改めて、実装結果
冒頭の動画とやっている事は基本同じですが、途中で紹介したハードウェア構成全体が見える動画を撮り直したので。
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">Stateflowでモータ制御するやつの拡張版。出力電圧をsimulinkモデル上からも基盤ボリュームからも調整できるようにしています。 <a href="https://t.co/YIW3N1WLmk">pic.twitter.com/YIW3N1WLmk</a></p>&mdash; モータ制御マン (@motorcontrolman) <a href="https://twitter.com/motorcontrolman/status/1734222100427370516?ref_src=twsrc%5Etfw">December 11, 2023</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# おまけ C言語における120度通電の実装
完全に趣味の領域の話ですが(記事全般を通して趣味の話しかしてないので何を今更だが)本記事のネタを作成する数か月前に120度通電をCコードで実装していたので、その際のコードを引き合いにすることで真理値表を直接書けてしまうことのメリットを強調してみます。

<BR>
<details><summary>コードが滅茶苦茶長いので、折り畳み。</summary><div>

当然ながらCコードでは真理値表を直接書けないので、switch caseを駆使することになりますが下記のような構成によって実装する必要があります。(なお、Cコードの実装に当たってはCQ出版の [高トルク&高速応答！センサレス・モータ制御技術](https://shop.cqpub.co.jp/detail/1979/)をおおいに参照しています。 )

A. ホールセンサ入力を領域①~⑥に変換するコード
B. 領域①~⑥に対応してU,V,W相出力がPWMか、GNDか、OFFかを決定するコード
C. U,V,W相出力に応じてイネーブルを決定するコード
D. 出力がPWMの場合、ボリュームに応じて出力Dutyを決定するコード

実際のコードを下記に記載します。内容を追う必要は全くありません、あくまで実際にコードを書くと滅茶苦茶大変だというのが伝われば良いので。
（ただしこのコードの長さは可読性重視によるところが大きく、可読性を無視すればもっと短くできる余地はあります。Truth Tableは可読性が高く、ブロックが１つで済む点が秀逸。）

「A. ホールセンサ入力を領域①~⑥に変換するコード」の実際
```c
static uint8_t calcVoltageMode(uint8_t* Hall){
	uint8_t hallInput;
	uint8_t voltageMode = 0;

	// Convert hall input to digital signal
	hallInput = (Hall[2] << 2) + (Hall[1] << 1) + Hall[0];

	// Decode digital signal to voltage mode
	switch(hallInput){
	  case 3:
		voltageMode = 3;
		break;
	  case 2:
		voltageMode = 4;
		break;
	  case 6:
		voltageMode = 5;
		break;
	  case 4:
		voltageMode = 6;
		break;
	  case 5:
		voltageMode = 1;
		break;
	  case 1:
		voltageMode = 2;
		break;
	  default :
		voltageMode = 0;
	  break;
	}
	return voltageMode;
}
```

「B. 領域①~⑥に対応してU,V,W相出力がPWMか、GNDか、OFFかを決定するコード」の実際
```c
static void calcOutputMode(uint8_t voltageMode, int8_t* outputMode){
		// Decide output mode
		// OUTPUTMODE_OPEN = 0
  		// OUTPUTMODE_POSITIVE = 1
  		// OUTPUTMODE_NEGATIVE = -1
  
		switch(voltageMode){
		  case 3:
			outputMode[0] = OUTPUTMODE_OPEN;
			outputMode[1] = OUTPUTMODE_POSITIVE;
			outputMode[2] = OUTPUTMODE_NEGATIVE;
			break;
		  case 4:
			outputMode[0] = OUTPUTMODE_NEGATIVE;
			outputMode[1] = OUTPUTMODE_POSITIVE;
			outputMode[2] = OUTPUTMODE_OPEN;
			break;
		  case 5:
			outputMode[0] = OUTPUTMODE_NEGATIVE;
			outputMode[1] = OUTPUTMODE_OPEN;
			outputMode[2] = OUTPUTMODE_POSITIVE;
			break;
		  case 6:
			outputMode[0] = OUTPUTMODE_OPEN;
			outputMode[1] = OUTPUTMODE_NEGATIVE;
			outputMode[2] = OUTPUTMODE_POSITIVE;
			break;
		  case 1:
			outputMode[0] = OUTPUTMODE_POSITIVE;
			outputMode[1] = OUTPUTMODE_NEGATIVE;
			outputMode[2] = OUTPUTMODE_OPEN;
			break;
		  case 2:
			outputMode[0] = OUTPUTMODE_POSITIVE;
			outputMode[1] = OUTPUTMODE_OPEN;
			outputMode[2] = OUTPUTMODE_NEGATIVE;
			break;
		  default :
			outputMode[0] = OUTPUTMODE_OPEN;
			outputMode[1] = OUTPUTMODE_OPEN;
			outputMode[2] = OUTPUTMODE_OPEN;
		  break;
		}
}
```

「C. U,V,W相出力に応じてイネーブルを決定するコード」の実際
```c
void writeOutputMode(int8_t* outputMode){

	// if the outputMode is OPEN, set Enable Pin to RESET.
	if(outputMode[0] == OUTPUTMODE_OPEN )
		HAL_GPIO_WritePin(EN1_GPIO_Port, EN1_Pin, GPIO_PIN_RESET);
	else
		HAL_GPIO_WritePin(EN1_GPIO_Port, EN1_Pin, GPIO_PIN_SET);

	if(outputMode[1] == OUTPUTMODE_OPEN )
		HAL_GPIO_WritePin(EN2_GPIO_Port, EN2_Pin, GPIO_PIN_RESET);
	else
		HAL_GPIO_WritePin(EN2_GPIO_Port, EN2_Pin, GPIO_PIN_SET);

	if(outputMode[2] == OUTPUTMODE_OPEN )
		HAL_GPIO_WritePin(EN3_GPIO_Port, EN3_Pin, GPIO_PIN_RESET);
	else
		HAL_GPIO_WritePin(EN3_GPIO_Port, EN3_Pin, GPIO_PIN_SET);
}

```

「D. 出力がPWMの場合、ボリュームに応じて出力Dutyを決定するコード」の実際
```c
static void calcDuty(int8_t* outputMode, float DutyRef, float* Duty){

	if( outputMode[0] > 0)
		Duty[0] = (float)(outputMode[0] * DutyRef;
    else
    	Duty[0] = 0;
	if( outputMode[1] > 0)
		Duty[1] = (float)(outputMode[1] * DutyRef;
    else
    	Duty[1] = 0;
	if( outputMode[2] > 0)
		Duty[2] = (float)(outputMode[2] * DutyRef;
    else
    	Duty[2] = 0;

}
```

</div></details>


# おわりに
ちなみに、上記動画と同じ内容の実機デモを会社でやったのですが反応がイマイチでした。
やはりモータ制御は人類には早すぎたか…。