STのモータ制御開発キットで『自力で』モータを回す　Vol.2 電流検出

# 1.はじめに
本記事は[STのモータ制御開発キットで『自力で』モータを回す　Vol.1 強制転流](https://qiita.com/motorcontrolman/items/5a9c17008314f0224ef9)の続きになります。

あれから3年の月日が過ぎ去った、光陰矢の如し。


3年経ってもまだモータ制御してるなんて変わり映えしないですね。


モータ制御(というかベクトル制御)にとって電流検出は必要不可欠な要素であるため、Vol.1記事の時点で含めて書いたほうが良いのでは？　と過去にも悩みましたが、こうして実際に記事を書いてみると説明しなければならないことがまあ多いこと多いこと。Vol.1記事を強制転流だけにしておいて良かったなーとか改めて思ったりします。

なお、Arduino Dueを使ったモータ制御記事もPWM出力と電流検出とで２つの記事で分けて書いたりしていました、そういえば。

①[Arduino Dueに３相同期・三角波・相補PWMを打たせる](https://qiita.com/motorcontrolman/items/ef6670f9dff0f63cdc50)
②[Arduino DueでPWM山同期AD変換を行う](https://qiita.com/motorcontrolman/items/ba2cd0fb200501186194)

なので本記事は、上記②記事のSTマイコンverとも言えます。
また本記事は、 [モータ制御のためのTIM/ADC設定(STM32・CubeMX編)](https://ioloa.com/blog/archives/339)を大いに参考にしているので、行間が読み取れない箇所があったら参照してみて下さい。

# 2. ソフトウェア作成の前準備
## 2.1 CubeIDEでプロジェクト作成
[記事Vol.1](https://qiita.com/motorcontrolman/items/5a9c17008314f0224ef9)参照。

## 2.2 ピン割り当てを実施する

記事Vol.1 のGPIO、PWMに加えてADのピン割り当てを実施します。
①~⑦はVol.1記事と同じです。

①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]（デフォルトで設定されているはず）
⑧PA0 → ADC1_IN1
⑨PC0 → ADC1_IN6
⑩PC1 → ADC1_IN7
⑪PB1 → ADC1_IN12

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/302506/9dadb639-51d7-db6a-87b6-53b57abf3cf4.png)

なお、P-NUCLEO-IHM001においては、各ADCポートと回路との接続関係は下記になります。

・ADC1_IN1：U相電流
・ADC1_IN6：W相電流
・ADC1_IN7：V相電流
・ADC1_IN12：ボリューム

相電流の検出方法としてはローサイド３シャント電流検出となります。詳しくは[Arduino DueでPWM山同期AD変換を行う](https://qiita.com/motorcontrolman/items/ba2cd0fb200501186194)を参照。

## 2.3 ADCの設定を行う
ここが一番重要です。ここの設定を間違えると電流検出がうまくできないので要注意。

### ポイント①入力モードはSingle-endedを選択
特に気にしなくてもデフォルトでSingle-endedになっているかもですが、念のため確認しましょう。Differentialになっていたら直す。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/302506/d8d0ee5d-fd13-58ca-7da9-d7b673a6188c.png)

Single-endedって何？　という方は下記を参照。
[シングルエンド伝送と差動伝送におけるコネクタ性能差の理由](https://www.irisoele.com/jp/technology/column/single-ended-and-differential/)

### ポイント②変換モードはInjectedを使用する
F302R8のAD変換はRegularとInjectedの２種が準備されていますが、後者を使います。
設定は下記を参照。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/302506/a4b7f96d-43ef-f824-46e0-d081cce75d58.png)
Number Of Conversions：
AD変換を実施するポート数。今回はUVW電流+ボリュームのため４に設定。

External Trigger Source：
AD変換を実施するトリガの発生源。後述するタイマー1のトリガイベントを使いたいので、Timer1 Trigger Out eventを選択。
(間違ってTimer1 Trigger Out 2 eventを選択しないよう注意)

Channel：
UVW電流、ボリュームのADポート番号を設定。
V相(ポート番号7)とW相(ポート番号6)を間違えないよう注意。

### ポイント③AD変換割り込みを有効化する
Vol1記事ではPWM同期割り込みを使いましたが、今回はAD変換を行うため割り込みもAD変換割り込みを使います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/302506/55b52afb-ea4d-bb3d-0d00-d69c39d99fe9.png)

後述しますが、AD変換自体をPWMの山タイミングと同期させているため上記設定によって結果的にPWM山割り込みとほとんど同じ結果を得る事が出来ます。(厳密にはちょっと違うけど。)




## 2.4 タイマーの設定を行う
[記事Vol.1](https://qiita.com/motorcontrolman/items/5a9c17008314f0224ef9)に対し、少し込み入った設定が必要になります。

### ポイント①PWM Channel4の設定が必要
Channel1~3をUVW相電圧の出力用に使うのに加え、AD変換のトリガ信号用にChannel4を使います。

TIM1のモード設定
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/302506/567a18ab-c32f-e351-4467-5318f562d11c.png)

Parameter Setting
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/302506/e72b539f-0389-48f5-cdbe-f8f4a8889ba0.png)
PWM Generation Channel1~3：
ModeはPWM mode1を選択します。

PWM Generation Channel4：
ModeはPWM mode2を選択します。
mode1とmode2の関係は下記です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/302506/f33fdbaa-85ac-8dae-4056-271f39ba328a.png)

後述しますが、別にPWM Channel4がmode1でも同等の動作が得られます。本記事の最後のほうでおまけとして試しますのでお楽しみに。

### ポイント②トリガイベントを設定
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/302506/c9738de7-b30b-8506-25b4-4f38c91988bc.png)

・Prescaler：0のままで変えない
・Counter Mode： Center Aligned mode1(三角波キャリア)に設定
・Counter Period：2000-1に設定
・<b>Trigger Event Selection TRGO：Output Compare(OC4REF)に設定</b>
(<b>Trigger Event Selection TRGO2に誤って設定しないよう注意)

説明コストが高すぎてゲロが出そうですが、図にすると下記です。
要するに、Channel4がONするタイミングでトリガイベントTRGOが呼ばれるように設定しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/302506/719a6ef3-9e09-6947-31a7-11d648cbe833.png)


ここの設定とADC行った「External Trigger Source：Timer1 Trigger Out event」設定によって、Channel4がONするタイミングでAD変換が行われる設定を得ることが出来ます。前述した、AD変換割り込みがPWM山割り込みとほぼ同じになるのはここの設定によって実現されています。

なお、上図に示す通りTRGOをPWM山に同期して発生させるにはDuty指令(Ch4)をキャリアカウンタの上限値付近に設定する必要があります。具体的にはソフトウェア作成の内容を確認のこと。


# 3. ソフトウェア作成
Vol.1記事同様、下記２つのソースファイルのみを操作します。

main.c：割り込みの開始、およびドライバ回路の動作ONを追記
stm32f3xx_it.c：割り込み時に３相交流電圧を生成 & 相電流取得するよう追記

## 3.1 main.cへの追記

whileループの直前に下記を追記します。
```c
	  // HAL_TIM_Base_Start_IT(&htim1);
	  HAL_ADCEx_InjectedStart_IT(&hadc);
	  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);

	  TIM1 -> CCR4 = 2000 - 10;
```

### 3.2.1 割り込みの開始
前回記事では、`HAL_TIM_Base_Start_IT(&htim1)` で割り込みを開始したが今回は`HAL_ADCEx_InjectedStart_IT`でAD変換同期割り込みを開始します。

### 3.2.2 山割り込みの設定
今回記事における勘所となります。
設定を行っているのは、一番最後の　`TIM1 -> CCR4 = 2000 - 10` です。
`TIM1 -> CCR4` は前回記事にて解説した通り、レジスタ直叩きによるDuty設定を実施しており、山割り込みの設定に使っているのが PWM Channel4 であるため `CCR4` への設定を行っています。

Duty値としては、山割り込みを行いたいのでキャリアカウンタの上限値から少し下げた値を設定する必要があります。キャリアカウンタの上限値はPWMの設定にて「Counter Period：2000-1」として設定したので、適当に10引いた値を設定しています。

<b>この設定を行うことで、前述した「TRGOがPWM山に同期して発生」、TRGOをトリガとしてAD変換が開始され、完了時にAD変換割り込み関数がコールされます。</b>

重要なことなので、Duty指令とPWM、TRGOの関係図をもう一度示しておきましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/302506/719a6ef3-9e09-6947-31a7-11d648cbe833.png)

キャリアカウンタから引く値を大きくするほど、TRGOの発生タイミングがPWM山タイミングから離れていきます。STモータ制御開発キットは下アーム3シャント電流検出なので、Duty指令が0近傍では電流検出が不可能となります。

### 3.2.3 ドライバ回路の動作ON~PWMの開始
前回記事参照。
そういえばですが、Duty出力の開始させるコード`HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);`はChannel1~3までしか呼んでおらずChannel4は呼んでないですね…。物理的に出力させていないので不要という事でしょうか。

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

AD変換割り込みを使用するため、割り込み関数は `ADC1_IRQHandler` を使います。本関数は`stm32f3xx_it.c`にあらかじめ下記のように定義されているものと思います。
```c
void ADC1_IRQHandler(void)
{
  /* USER CODE BEGIN ADC1_IRQn 0 */

  /* USER CODE END ADC1_IRQn 0 */
  HAL_ADC_IRQHandler(&hadc1);
  /* USER CODE BEGIN ADC1_IRQn 1 */

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


検証のためだけに新しくコードを書くのが面倒なので
説明コストを下げる目的で、前回記事にて記載したコードを流用した強制転流をAD変換割り込みにて実施、電流検出が狙い通り出来ているか確認してみましょう。

```c
void ADC1_IRQHandler(void)
{
  /* USER CODE BEGIN ADC1_IRQn 0 */
	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 END ADC1_IRQn 0 */
  HAL_ADC_IRQHandler(&hadc1);
  /* USER CODE BEGIN ADC1_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 + 1.0f * (float)pulse_u;
  TIM1 -> CCR2 = 1000 + 1.0f * (float)pulse_v;
  TIM1 -> CCR3 = 1000 + 1.0f * (float)pulse_w;

  // AD Read
  Iu_AD = ADC1 -> JDR1;
  Iv_AD = ADC1 -> JDR2;
  Iw_AD = ADC1 -> JDR3;

  HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin);
  /* USER CODE END ADC1_IRQn 1 */
}
```

# 4. 相電流検出結果の確認
[【STM32】無償のRAMモニターJ-Scopeを使おう](https://qiita.com/motorcontrolman/items/c1910c5a51fb29a700fd)を参考に、今回作成したソフトにて検出した相電流をJ-Scopeに表示させてみましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/302506/0365ccbd-ade3-0b3a-bed3-007ffe95b3e0.png)
強制転流時にて3相交流電圧の作成に使っている角度 `phase_accumulator` と、相電流検出値の生値 `Iu_AD` `Iv_AD` `Iw_AD` を選択してモニタします。結果は下記。
 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/302506/e7ba2f18-ceb1-360f-2fe9-a25e12b8d64f.png)
<b>今日はぐっすり眠れそうです。</b>
なお、vw相電流と比べてu相電流検出値が上にずれていますが、これはAD検出値のオフセットがずれているせいなので補正すれば治ります。オフセット除去した上で、マイコンが検出した12bitの値を物理値に変換するゲインをかけることで電流[A]を得る事ができますがそれはまた別の機会に。

# おまけ PWM Channel4にて、mode1の場合とmode2の場合とで割り込みタイミングがどう変わるか

mode1でもmode2でも結果はほとんど同じと書きましたが、実際に試してみましょう。

<details><summary>思った以上のボリュームなので折り畳み</summary>

PWM Channel4のmode1とmode2の設定変更は、`main.c`にて下記コード

```c
  sConfigOC.OCMode = TIM_OCMODE_PWM1;
  sConfigOC.Pulse = 0;
  sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
  sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;
  sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
  sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
  sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
  if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_2) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_3) != HAL_OK)
  {
    Error_Handler();
  }
  sConfigOC.OCMode = TIM_OCMODE_PWM2;
  if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_4) != HAL_OK)
  {
    Error_Handler();
  }
```

の下から5行目 `sConfigOC.OCMode = TIM_OCMODE_PWM2;`を書き換えることで容易に可能です。

それでは、実際にmode1とmode2での割り込みタイミングの違いをオシロで確認してみましょう。下記では、Channel1~3のDutyを50%固定した上で各Dutyとの相対関係から確認を行っています。

mode1、Channel4 Duty = 2000-10
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/302506/b61f4282-11b6-864b-c25f-d4050587e03a.png)
青、赤、緑がChannel1~3のDuty、黄色がChannel4 DutyにてAD変換を実施した後にコールされるAD変換割り込みにてGPIOをトグルさせた結果になります。AD変換時間が存在するため、AD変換割り込みの発生は完全に山タイミングとはならず、山から5usほど遅れています。

では、mode2だとどうなるでしょうか。
mode2、Channel4 Duty = 2000-10
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/302506/2c3a6aa1-1aca-87da-12ec-de1d091e7905.png)
<b>見た目では分からん差異ですね。</b>
これは、Channel4 Dutyを`2000-10`に設定しているために、Duty出力が負論理だろうが正論理だろうが立ち上がりトリガのタイミングがクロック72MHzの10カウント×2=0.28usしか変化しないため、このような結果となっています。

では、Channel4 Dutyをいじってみるとどうなるでしょうか。試しに`2000-200`に設定してみた結果が下記です。

PMW1、Channel4 Duty = 2000-200
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/302506/b390fb59-a2b1-d7d9-0ffa-a992d59f9d14.png)

PMW2、Channel4 Duty = 2000-200
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/302506/024e8386-a8af-55ec-658b-9113d58cd778.png)

クロック72MHzの200カウント×2=5.6usだけ、mode1がmode2に対し割り込みタイミングが遅れる結果が得られました。

上記をまとめると
・AD変換割り込み用トリガ信号を発生させるためのDuty設定を100%近傍に設定している場合は、mode1でもmode2でも割り込み発生タイミングに大差はない
・Duty設定が100%を下回るほど、mode1とmode2とで割り込み発生タイミングに差異が生じる。
・ただし、通常の設計においてAD変換割り込み用トリガ信号を発生させるためのDuty設定を100%近傍から離して設計する必要はないため、mode1かmode2かを深く考える必要はない。(とはいえ、自分がどちらの設計にしたかはちゃんと把握しておくべき)


お後がよろしいようで。

</details>

# おわりに
モータ制御の説明コストが高すぎて泣きそうです。