STのモータ制御開発キットで『自力で』モータを回す　Vol.1 強制転流

# 1. はじめに
STマイクロの永久磁石同期モータ制御開発キット [P-NUCLEO-IHM001](https://www.st.com/ja/evaluation-tools/p-nucleo-ihm001.html) は約5000円で買えて、誰でも<b>即座に</b>モータをぶん回すことが出来る逸品です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/302506/b2b55f44-47e0-7c0f-250f-f6f214813e46.png)



<b>即座に</b>モータをぶん回すにはどうするかというと、STより提供されているソフトウェア開発環境を用います。詳細については下記。
https://qiita.com/usashirou/items/9aa311654e2aa859a9a2

上記開発環境を用いることで、モータ制御の難しい部分をぶっ飛ばした上でユーザーフレンドリーなGUIでもってモータを好きなようにぶん回すことが出来ます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/302506/ccd55427-feab-5598-d304-76534a905902.png)

「やったね、これで僕もモータ制御を習得したぞ！」

<b>なんて思うでしょうか、私はそうは思えません。</b>
これはまるで、買ってきた扇風機のリモコンを操作して気持ちよくなっているのと同じです。
それが楽しめるならそれはそれで幸せなのかも知れません


じゃあどうすればモータ制御を習得したことになるのかと言えば、<b>便利なツールに頼らず、自力で</b>モータをぶん回せることが出来なければならないのではないでしょうか。

本記事では自力でモータをぶん回すための第一歩、<b>強制転流</b>(同期モータの回転角度や電流は観測せず３相交流電圧をぶっかけることで乱暴に回す)を実現するまでの道のりを示します。
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">これまでモータ制御マンと名乗っている癖にモータ制御の動画全然なくてすいませんでした。P-NUCLEO-IHM001で強制転流できましたのでお収め下さい。<br>Dutyと周波数は一定、始動は手助けの必要あり（摩擦およびコギング大きいため） <a href="https://t.co/fyzqMLWRfn">pic.twitter.com/fyzqMLWRfn</a></p>&mdash; モータ制御マン (@motorcontrolman) <a href="https://twitter.com/motorcontrolman/status/1258573866294632448?ref_src=twsrc%5Etfw">May 8, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

なおVol.1と書いているのはあくまで気概であって、Vol.2がいつになるかは未定です。
➡<b>3年かかりました。</b>
寝太郎かよ

[STのモータ制御開発キットで『自力で』モータを回す　Vol.2 電流検出](https://qiita.com/motorcontrolman/items/c24666e3d0fb258a080f)

また、「どうしても！」という方向けにソースをGitに置きましたので迷子の方は参考にして下さい。
https://github.com/motorcontrolman/F302-ihm7-simple/tree/master/Core/Src

## 1.1 必要なもの
Must
・[P-NUCLEO-IHM001](https://www.st.com/ja/evaluation-tools/p-nucleo-ihm001.html)
・直流電源(できれば安定化電源、なければ9V電池)
・CubeIDEのインストールされたPC

Wantと言いつつほぼMust
・2ch以上のオシロスコープ

なくてもなんとかなった
・デバッガ([J-Link edu](https://www.segger-pocjapan.com/segger-jlink-top)買ったけど接続うまくいかず…)

## 1.2 躓きどころ
たぶん、ここだけ知りたい人も多いと思うので先に書いておきます。

### ①割り込みは`TIM16`を使う
`TIM16`はPWM同期で発生する割り込みで、設定方法および利用方法は下記を参照。個人的に一番躓いたポイントなので強調しておきたい。
[2.4 割り込み設定を行う](https://qiita.com/motorcontrolman/items/5a9c17008314f0224ef9#24-%E5%89%B2%E3%82%8A%E8%BE%BC%E3%81%BF%E8%A8%AD%E5%AE%9A%E3%82%92%E8%A1%8C%E3%81%86)
[3.2.1 割り込みの開始](https://qiita.com/motorcontrolman/items/5a9c17008314f0224ef9#321-%E5%89%B2%E3%82%8A%E8%BE%BC%E3%81%BF%E3%81%AE%E9%96%8B%E5%A7%8B)
[3.3.1 割り込み処理を追記する場所](https://qiita.com/motorcontrolman/items/5a9c17008314f0224ef9#331-%E5%89%B2%E3%82%8A%E8%BE%BC%E3%81%BF%E5%87%A6%E7%90%86%E3%82%92%E8%BF%BD%E8%A8%98%E3%81%99%E3%82%8B%E5%A0%B4%E6%89%80)


### ②PWM出力時の`OCMode`は`TIM_OCMODE_PWM1`であることを確認
詳しくは [3.1 main.cの内容確認](https://qiita.com/motorcontrolman/items/5a9c17008314f0224ef9#31-mainc%E3%81%AE%E5%86%85%E5%AE%B9%E7%A2%BA%E8%AA%8D)に記述。
TIM1のモード設定ミス、またはParameter Settingのミスがあった場合は正常なPWM出力が得られませんが、その場合はここをチェックしましょう。

### 
③付属のモータはコギングトルクが大きく、低速で回しにくい＆始動が難しい

これは見当違いで、本当の躓きどころは下記であることが後から分かりました。

### ④P-NUCLEO-IHM001 はキャリア周波数が概ね20kHzでないと正常に動作しない

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">STのモータ制御キット P-NUCLEO-IHM001を強制転流した場合の電流波形がどうにも怪しくて、何が悪いのかずっと悩んでいたのだけど今日解決した。理由は「キャリア周波数が低すぎたため」だった…。ブートストラップコンデンサの容量が20kHz程度狙いなんかなぁ。 <a href="https://t.co/qeNoIRZsHC">pic.twitter.com/qeNoIRZsHC</a></p>&mdash; モータ制御マン (@motorcontrolman) <a href="https://twitter.com/motorcontrolman/status/1350781159060299776?ref_src=twsrc%5Etfw">January 17, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

キャリア周波数の設定は、[2.3 タイマーの設定を行う](https://qiita.com/motorcontrolman/items/5a9c17008314f0224ef9#23-%E3%82%BF%E3%82%A4%E3%83%9E%E3%83%BC%E3%81%AE%E8%A8%AD%E5%AE%9A%E3%82%92%E8%A1%8C%E3%81%86) における `Counter Period` の値によって決まります。`Counter Period = 2000-1`に設定することでキャリア周波数を18kHzに設定できます。`Counter Period`をこれより大きい値に設定した場合は、正常動作しない可能性があること注意下さい。


# 2. ソフトウェア作成の前準備
## 2.1CubeIDEでプロジェクト作成
P-NUCLEO-IHM001の制御ボードは[NUCLEO-F302R8](https://www.st.com/ja/evaluation-tools/nucleo-f302r8.html)なので、Board Selector タブで検索→選択してプロジェクト作成しましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/302506/11622071-b785-8d2a-d43b-7361bfdc9de4.png)

もっと詳しく！という方は下記を参照。
「STM32入門 環境構築「CubeIDEのインストールとSTM32F446でLチカ」
https://qiita.com/yosihisa/items/136bcc09c466227303a2

## 2.2 PWMとGPIOのピン割り当てを実施する
強制転流のためには最低限、下記赤枠部分の割り当てが必要となります。
・PA8 → TIM1_CH1
・PA9 → TIM1_CH2
・PA10 → TIM1_CH3
・PC10 → GPIO_Output に設定した上で適宜名前入力(画像ではEN1)
・PC11 → GPIO_Output に設定した上で適宜名前入力(画像ではEN2)
・PC12 → GPIO_Output に設定した上で適宜名前入力(画像ではEN3)
・PB13 → LD2[Green Led]（デフォルトで設定されているはず）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/302506/0381b414-ea3f-d3c2-12c6-f7c535495527.png)


上記画像ではADCを将来のために有効化していますが、今回使う事はありません。

## 2.3 タイマーの設定を行う
上記にてPA8~10にタイマーをピン割り当てしましたが、これだけでは設定が完了していません。（この場合ピンが黄色になっているはず）ピン割り当て画面の左側にあるタブ「Timers」→「TIM1」からさらに設定を行います。

設定の必要な個所は下記２点です。
・Clock SourceをInternal Clockに設定
・Channel1~3を PWM Generation CH1~3に設定
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/302506/9025cc15-72be-9990-478a-fdbc0e30cc06.png)


さらにConfigurationの設定を行います。
・Prescalerは0のままで変えない
・Counter Mode を Center Aligned mode1(三角波キャリア)に設定
・Counter Period を <b>2000-1</b>に設定
・PWM Generation Channel 1~3 をPWM mode 1(正論理)に設定
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/302506/238dc58d-4933-2686-fd14-771b31fa8a9f.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/302506/92fd5c21-a521-c154-7090-b0c8c79ee9b7.png)

<b>上記設定を行った場合のスイッチング周波数は18kHzです。</b>考え方は下記。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/302506/4ec79a35-0930-6721-e7ec-e3237b117b58.png)

なおPrescalerおよびCounter Periodは後からでも簡単に設定できます。色々いじってみると良いと思います。

## 2.4 割り込み設定を行う
正しくモータ制御を行うには３相交流の生成をPWMに同期して行う必要があり、このためPWM同期の割り込みを使用する必要があります。そのための設定は下記。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/302506/8eff421c-a778-c9bc-eb74-07ab69b6c736.png)
TIM1 update and TIM16 interrupts を 有効化します。

この設定を行うことで、[2.3](https://qiita.com/motorcontrolman/items/5a9c17008314f0224ef9#23-%E3%82%BF%E3%82%A4%E3%83%9E%E3%83%BC%E3%81%AE%E8%A8%AD%E5%AE%9A%E3%82%92%E8%A1%8C%E3%81%86) にてCounter Modeを三角波キャリアに設定しているため三角波の<b>山谷</b>でTIM16割り込みが発生します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/302506/57cba840-0056-5d24-27e2-3b583bf403ac.png)


# 3. ソフトウェア作成
プロジェクト作成時に生成されたソースファイルのうち、最低限変更の必要があるのは下記の２か所です。
・main.c：割り込みの開始、およびドライバ回路の動作ONを追記
・stm32f3xx_it.c：割り込み時に３相交流を生成するよう追記
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/302506/94fc336c-920c-39c1-55a0-813e920e82b7.png)

## 3.1 main.cの内容確認

2章にて設定が問題なく行えていれば本節はスキップしてよいですが、<b>所望の動きが得られなかった場合</b>は下記を確認下さい。

最も陥りがちなのは、<b>PWM Generation</b>の設定ミスです。
2.3節にて正しく設定できていれば、初期化関数`MX_TIM1_Init`内は下記の表記となっているはずです。

```c
static void MX_TIM1_Init(void)
{
  //中略
  sConfigOC.OCMode = TIM_OCMODE_PWM1;
  //中略
}
```

上記のようになっていなかった場合は、コピペで構いませんので修正しましょう。

## 3.2 main.cへの追記

whileループの直前に下記を追記します。

```main.c
  HAL_TIM_Base_Start_IT(&htim1);
  HAL_GPIO_WritePin(EN1_GPIO_Port, EN1_Pin, GPIO_PIN_SET);
  HAL_GPIO_WritePin(EN2_GPIO_Port, EN2_Pin, GPIO_PIN_SET);
  HAL_GPIO_WritePin(EN3_GPIO_Port, EN3_Pin, GPIO_PIN_SET);
  HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
  HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_2);
  HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_3);

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1) 
 {
    /* USER CODE END WHILE */
    /* USER CODE BEGIN 3 */
  }
```

### 3.2.1 割り込みの開始

`HAL_TIM_Base_Start_IT(&htim1);`でTIM16割り込みを開始させます。タイマの開始は`HAL_TIM_PWM_Start_IT`というのもあって、最初はこれが正しいと思い込んでいたのですが違いました。正直ここの設定で半日ほど溶かしました。

### 3.2.2 ドライバ回路の動作ON

`HAL_GPIO_WritePin(EN1_GPIO_Port, EN1_Pin, GPIO_PIN_SET);`でドライバ回路の動作ONを行います、これがないとモータへの電圧印可が行われません。

動作ON／OFFは３相それぞれで管理されているため、３相全てに対し動作ON命令を行う必要があります。これはドライバ回路の真ん中に乗っているSTのIC [L6230](https://www.st.com/resource/en/datasheet/l6230.pdf)の都合です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/302506/a5838d44-584c-4715-f298-b29bfa8496bc.png)
上記画像のEN1~EN3と、[2.2](https://qiita.com/drafts/5a9c17008314f0224ef9/edit#22-pwm%E3%81%A8gpio%E3%81%AE%E3%83%94%E3%83%B3%E5%89%B2%E3%82%8A%E5%BD%93%E3%81%A6%E3%82%92%E5%AE%9F%E6%96%BD%E3%81%99%E3%82%8B) でGPIO_Output設定を行ったピン PC10~PC12が回路上で繋がっています。このため上記コードではPC10~PC12に対しON命令をかけています。

なお上記コードは[2.2](https://qiita.com/drafts/5a9c17008314f0224ef9/edit#22-pwm%E3%81%A8gpio%E3%81%AE%E3%83%94%E3%83%B3%E5%89%B2%E3%82%8A%E5%BD%93%E3%81%A6%E3%82%92%E5%AE%9F%E6%96%BD%E3%81%99%E3%82%8B) でピン名をEN1~EN3にした場合であり、他の名前に設定した場合は`EN1_GPIO_Port, EN1_Pin`の部分を適宜設定する必要がある点注意下さい。

### 3.2.3 PWMの開始
`HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1)` でCH1のPWMが開始されます。
`HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_2)` でCH2の、`HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_3)` でCH3のPWM開始されるのは言うまでもなく。

割り込みの開始とPWMの開始の両方を行う必要がある点注意。

## 3.3 stm32f3xx_it.cへの追記
### 3.3.1 割り込み処理を追記する場所 

`stm32f3xx_it.c`にはデフォルトで下記の関数が記述されています。この関数はTIM16割り込みにて呼び出されるものであり、ここに追記を行うことでPWMに同期した制御を行うことが可能になります。

```stm32f3xx_it.c
void TIM1_UP_TIM16_IRQHandler(void)
{
  /* USER CODE BEGIN TIM1_UP_TIM16_IRQn 0 */

  /* USER CODE END TIM1_UP_TIM16_IRQn 0 */
  HAL_TIM_IRQHandler(&htim1);
  /* USER CODE BEGIN TIM1_UP_TIM16_IRQn 1 */

  /* USER CODE END TIM1_UP_TIM16_IRQn 1 */
}
```

最終的には3相交流のためのPWM生成をここで行いますが、まずは動作検証のためLチカとPWM 50%出力を行います。

```stm32f3xx_it.c
void TIM1_UP_TIM16_IRQHandler(void)
{
  /* USER CODE BEGIN TIM1_UP_TIM16_IRQn 0 */

  /* USER CODE END TIM1_UP_TIM16_IRQn 0 */
  HAL_TIM_IRQHandler(&htim1);
  /* USER CODE BEGIN TIM1_UP_TIM16_IRQn 1 */
  HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin);
  TIM1 -> CCR1 = 1000;
  TIM1 -> CCR2 = 1000;
  TIM1 -> CCR3 = 1000;
  /* USER CODE END TIM1_UP_TIM16_IRQn 1 */
}
```

### 3.3.2 レジスタ直叩きによるDuty設定

`TIM1 -> CCR1 = 1000;`は、STMの関数を介すことなくDutyの設定を行います。'CCR1'はCH1、`CCR2`はCH2、`CCR3`はCH3のレジスタとなります。

レジスタ直叩きにすることのメリットについては下記を参照下さい。
[モータ制御のためのTIM/ADC設定(STM32・コード記述編)](https://ioloa.com/?p=365)

引数1000は、2.3にてCounter Periodに設定した値の50%です。厳密には1000-1な気がしますが、あくまで動作検証なのでとりあえずよしとします。

上記を書いた状態でプログラムを実行、PA8(PWM出力)とPB13(Lチカ)をオシロにて観測した時の波形は下記になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/302506/26ee3454-7465-3e33-e6fe-a068c3cc9684.png)
![P_20200508_224136.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/302506/adb4f278-5872-1152-36dd-6ed0569f8c4a.jpeg)
黄色がPWM、青がLチカのピン出力です。この波形が出たなら「<b>勝った！</b>」とガッツポーズを取っていいでしょう、モータが回るまではもう一歩です。

なおこのとき、観測は白の制御回路だけで観測を行いましょう。<b>青の駆動回路が乗った状態かつ直流電源OFF状態</b>だとPWM出力が乱れます。恐らくですがL6230が何かをしているせい。
![P_20200508_224417.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/302506/ac5bf5a1-a864-3eb7-33a3-104c92f36d51.jpeg)


## 3.3.3 モータを回すためのソフト最終形
再び`stm32f3xx_it.c`に戻って、下記のように修正します。sinの生成周りは下記URLを参考にしました。
https://arm-stm.blogspot.com/2015/12/stm32f4-generating-sine-wave.html

なお、sinはSTMのライブラリにあるのでこちらを使っても構いません。
[STM32でCMSIS DSPを使ってみる その１](https://gsmcustomeffects.hatenablog.com/entry/2017/04/05/172210)
私がこれを今回使っていないのは、<b>実装時に存在を知らなかった</b>だけです。無知って恐ろしい！

```stm32f3xx_it.c
void TIM1_UP_TIM16_IRQHandler(void)
{
	uint8_t angle_u;
	uint8_t angle_v;
	uint8_t angle_w;
	int16_t pulse_u;
	int16_t pulse_v;
	int16_t pulse_w;
  /* USER CODE BEGIN TIM1_UP_TIM16_IRQn 0 */

  /* USER CODE END TIM1_UP_TIM16_IRQn 0 */
  HAL_TIM_IRQHandler(&htim1);
  /* USER CODE BEGIN TIM1_UP_TIM16_IRQn 1 */
  HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin);

  phase_accumulator+=100;

  angle_u = (uint8_t)(phase_accumulator>>8);
  angle_v = angle_u + 171;
  angle_w = angle_u + 85;

  pulse_u = sinetable[angle_u] - 127;
  pulse_v = sinetable[angle_v] - 127;
  pulse_w = sinetable[angle_w] - 127;

  TIM1 -> CCR1 = 1000 + 2 * pulse_u;
  TIM1 -> CCR2 = 1000 + 2 * pulse_v;
  TIM1 -> CCR3 = 1000 + 2 * pulse_w;
  /* USER CODE END TIM1_UP_TIM16_IRQn 1 */
}

```

なぜ上記コードでモータが回るかの詳細説明は、それだけで単独記事を書けてしまうのでここでは省略しざっくり説明だけします。

`phase_accumulator`はグローバル変数です。`+=100`の部分を変えることで周波数を変化させることが可能。

`TIM1 -> CCR1 = 1000 + 2 * pulse_u;`の`2`の部分を変えることでPWM出力を変えることが可能。

上記コードにて実行した結果が冒頭の動画です。それなりに苦労した分、感動もひとしおです。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">これまでモータ制御マンと名乗っている癖にモータ制御の動画全然なくてすいませんでした。P-NUCLEO-IHM001で強制転流できましたのでお収め下さい。<br>Dutyと周波数は一定、始動は手助けの必要あり（摩擦およびコギング大きいため） <a href="https://t.co/fyzqMLWRfn">pic.twitter.com/fyzqMLWRfn</a></p>&mdash; モータ制御マン (@motorcontrolman) <a href="https://twitter.com/motorcontrolman/status/1258573866294632448?ref_src=twsrc%5Etfw">May 8, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# 4. おわりに
最近、自宅ではMATLABをいじってばっかりで久々にがっつりマイコンと戦ったのですが<b>正直疲れました</b>、が自力でモータが回る瞬間は最高に嬉しかったです。

あとは持っててよかったオシロスコープ。モータ制御開発をこれ無しでやるのはやはり考えられません。オシロあると開発が捗るだけでなく、写真映えするなどの効果もあるのでモータ制御やりたい人はぜひ導入を検討しましょう。