Arduino NANO EveryのPWM周波数を変更する

# Arduino Nano EveryのPWM周波数を変更してみる

# はじめに

この記事は、ロボコンなどでモータドライバをマイコンから制御するときに使用するPWM制御についてです。
Arduinoでは通常、*490Hz*や*980Hz*のPWMが出力されていますが、設定を少し変更することで分解能を維持したまま最大*62.5kHz*の周波数のPWMを生成できます。
その上で日本語の情報が少ないなと個人的に思った**Arduino Nano Every (ATmega4808/4809)** での情報を整理しました。

そして、今回初めてQiitaに記事を投稿します。
内容の説明がいまいちなところもあると思いますが、指摘していただけるととてもありがたいです。

# どうして高周波数PWMを生成するのか

結論から言うと様々なメリットがあります。

プログラム班の自分が考える理由としては以下の2つで、
- モータドライバでのスイッチング損失は大きくなるが、モータが触れなくなるほどの発熱を抑えられる
- モータの電気ノイズが人間に可聴域を超えて聞こえなくなる

詳しい回路事情については自分はよくわかりませんが、これらの理由についてはここでは無く、別の記事に書こうと思います。

# Arduino Nano Everyについて

少し前にArudino公式から**Arduino Nano Every**というつよつよなマイコンが販売されました。

PWMの設定に影響する部分のスペックを見ていきたいと思います。

|      動作電圧      |                          5V                          |
| :----------------: | :--------------------------------------------------: |
|      マイコン      |                      ATMega4809                      |
|     動作周波数     |                   16MHz(20MHz)[^1]                   |
|     PWMピン数      |               D3, D5, D6, D9, D10[^2]                |
|    ペリフェラル    |          16-bit Timer/Counter type A (TCA)           |
|                    |    Up to four 16-bit Timer/Counters type B (TCB)     |
|                    |                       I2C x 1                        |
| クロックオプション |       16/20 MHz low-power internal oscillator        |
|                    | 32.768 kHz Ultra Low-Power (ULP) internal oscillator |
|                    |        32.768 kHz external crystal oscillator        |

まず搭載されているチップは**ATMega4809**です。これは**megaAVR 0**シリーズの一つで大きな特徴としては**HardwareSerialが4つ**使用できることだと思います。
その他にもデバッグ用の端子の**UPDI**が搭載されたり**すべてのデジタルピン**で割り込みが可能であるのもこのシリーズの特徴だと言えます。
ただ、ブレイクアウトボードを作る際には内部にクリスタルを持っていて、部品、はんだ付けの手間が省けていいのですが、UPDIによる書き込みでプログラマーを別途購入、自作しなければいけないのがかなりのマイナスポイントかなと思います。

# ATMega4809のペリフェラルの割り当て
ATMega4809には2つの16ビットタイマ**TCA**と**TCB**があります。チャンネル数としてはTCAが1つ、TCBが4つになっていています。
I/OピンのMultiplexed Signals割り当ては以下の表の通りです。

<img width="800" alt="ScreenShot1.png" src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/925192/e3b33770-8896-c9a0-4e99-21bb5ef70a71.png">
<img width="800" alt="ScreenShot2.png" src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/925192/9bc2ae55-90e1-cad0-c2dc-f95153c12dfa.png">

| 対象ビット - レジスタ名 | レジスタ値 |   デフォルト   | オルタネイティブ |
| :---------------------: | :--------: | :------------: | :--------------: |
|  Bits 2:0 – TCAROUTEA   |    0x0     | PORTA(PA[5:0]) | PORTB (PB[5:0])  |
|                         |    0x1     |                | PORTC (PC[5:0])  |
|                         |    0x2     |                | PORTD (PD[5:0])  |
|                         |    0x3     |                | PORTE (PE[5:0])  |
|                         |    0x4     |                | PORTF (PF[5:0])  |
|                         |   Other    |       -        |     Reserved     |
(ATmega4808/4809 Data Sheet page_139)


| 対象ビット - レジスタ名 |    ビット    |             レジスタ値              | デフォルト  | オルタネイティブ |
| :---------------------: | :----------: | :---------------------------------: | :---------: | :--------------: |
|  Bits 2:0 – TCBROUTEA   | Bit 3 – TCB3 | write 0x1 to select alternative out |  PB5(RX0)   |       PC1        |
|                         | Bit 2 – TCB2 |                                     |     PC0     |     PB4(TX0)     |
|                         | Bit 1 – TCB1 |                                     | PA3(A5/SCL) |     PF5(D3)      |
|                         | Bit 0 – TCB0 |                                     | PA2(A4/SDA) |     PF4(D6)      |
(ATmega4808/4809 Data Sheet page_140-141)

<img width="800" alt="ScreenShot3.png" src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/925192/ad97c218-29b1-c529-4335-203372ee220a.png">
<img width="800" alt="ScreenShot4.png" src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/925192/25dabb23-0d05-51e7-76ec-1636955b4a75.png">

Arduino Nano Everyでは、PWMピンは D3(*PF5*), D5(*PB2*), D6(*PF4*), D9(*PB0*), D10(*PB1*)に設定されています。つまりArduinoフレームワークではデフォルトの設定値として`TCAROUTEA`レジスタには`0x01`を`TCBROUTEA`レジスタのビット0には`0x01`,ビット1には`0x01`,ビット2には`0x00`,ビット3には`0x01`を設定していることがわかります。次の項目では今調べたポートに対応するタイマーのカウンタに対しての設定変更を行っていきます。

<img width="800" alt="ScreenShot5.png" src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/925192/9e1f6367-5f8b-a145-2b7d-369f510422fe.png">
参考：https://content.arduino.cc/assets/NANOEveryV3.0_sch.pdf

# タイマーレジスタの変更
レジスタの書き換えを行ってきたいのですが、ここで一つ注意点があります。
Arduino Nano Everyは`delay()` `millis()` `micros()`などの処理にTCAのタイマーを使用しています。なので、このタイマーの設定を変更するとこれらの処理に影響を及ぼします。

ここではArduino Nano Everyの評価ボードを使うことを前提として、TCA0レジスタのスプリットモードの有効化は行いません。（スプリットモードでは16Bitタイマーを2つの8ビットタイマーとして使用できます。共通のクロック源からのチャンネルが6つ必要な場合はこのモードが有効です。）

PWM高周波化の方針としては**タイマーのプリスケーラーを小さく調整する**事と**Waveform Generationモードをシングルスロープモードに設定**します。
最初にプリスケーラーの設定がどうなっているかを確認します。
読み出しの対象は**TCAのControl AレジスタのBits 3:1(CLKSEL[2:0])**です。
このレジスタでは使用するプリスケーラーを変更します。
<img width="800" alt="ScreenShot6.png" src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/925192/cea03c1f-5591-3b66-ac07-6b7894216ba0.png">
このようにして設定値を取得しました。

```c++:TCA0_Clock_Select確認

  Serial.println(TCA0_SINGLE_CTRLA, BIN); //p198 Bits 3:1 – CLKSEL[2:0] Clock Select確認
```

結果としては`0b00001011`が設定されており、インデックスで1~3ビットをデータシートの表と比較するとプリスケーラーは**64**に設定されているみたいです。ちなみに最下位ビットはこのペリフェラルを有効化するかの設定で、デフォルトでは有効です。


次にWaveform Generationの設定を確認していきます。
読み出しの対象は**TCAのControl BレジスタのBits 2:0(WGMODE[2:0])**です。
このレジスタでは波形生成のモードとカウンティング時のTOP値、更新タイミングなどのPWM波形を生成するうえで必要な情報を指定します。
<img width="800" alt="ScreenShot7.png" src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/925192/bcb0724e-825b-8433-0650-f83992c26959.png">

このようにして設定値を取得しました。（Control Bレジスタではスピリットモードとシングルモードで設定項目が大きく変化しているので注意します。）

```c++:Waveform_Generation_Mode確認

  Serial.println(TCA0_SINGLE_CTRLB, BIN); //p199 Bits 2:0 – WGMODE[2:0] Waveform Generation Mode 確認
```

結果としては`0b00000011`が設定されており、インデックスで0~2ビットをデータシートの表と比較するとWaveform Generation Modeは**SINGLESLOPE**に設定されているようです。

つまり今行うべき設定はプリスケーラーの再設定のみです。
`TCA0_SINGLE_CTRLA`レジスタを`0x00`に設定すればよいので、`TCA0_SINGLE_CTRLA &= ~0b00001110;`を実行して設定を適応して再度適応されているか確認してみましょう。

ここで、実際に計算で求めたPWM周波数とオシロスコープで計測した周波数を比較してみます。
Single-SlopeモードでのPWM周波数の計算方法は

```math
 f_{PWM＿SS} = \frac{f_{CLK＿PER}}{N(PER+1)}
```
適応前が

```math
 f_{CLK＿PER} = 16MHz\\
 N = 64（プリスケーラー）\\
 PER = 255（分解能）
```

適応後が

```math
 f_{CLK＿PER} = 16MHz\\
 N = 1（プリスケーラー）\\
 PER = 255（分解能）
```
ですので、変更前と後のPWM周波数はこのようになるはずです。

```math
 f_{PWM＿SS} = \frac{f_{CLK＿PER}}{N(PER+1)} = \frac{16000000}{64(255+1)} = 978.6Hz\\
 f_{PWM＿SS} = \frac{f_{CLK＿PER}}{N(PER+1)} = \frac{16000000}{1(255+1)} = 62500Hz = 62.5kHz
```

実際にオシロスコープで計測した結果はこのようになっています。
<img width="800" alt="ScreenShot8.png" src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/925192/3bb72b0c-47a2-9107-7006-540f6d67ec9c.png">
<img width="800" alt="ScreenShot9.png" src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/925192/8a9bc987-b908-f103-7c5c-0569e3085193.png">

出力されているPWM波形が約*978.6Hz*・*62.5kHz*であることが確認できます。
変更点、検証結果を表にまとめるとこのようになりました。

|           TCA            |   設定前   |   設定後   |
| :----------------------: | :--------: | :--------: |
|   Control Aレジスタ値    | 0b00001011 | 0b00000001 |
| Waveform Generation Mode | 0b00000011 | 0b00000011 |
|        PWM周波数         |  978.6Hz   |  62.5kHz   |


以上でTCAタイマーの検証・設定は完了しました。

次にTCBタイマーの設定を行っていきます。
TCBタイマーは4つのカウンターを保持していてそれぞれでPWMチャンネルを持つことができますが、Arduino Nano Everyで使用されているカウンタは0,1の二つのみなのでそれらの設定を行っていきます。
こちらのタイマーでの方針としては**タイマーのプリスケーラーを小さく調整する**事と**Timer Modeを8-Bit PWM modeに設定**します。
最初にプリスケーラーの設定がどうなっているかを確認します。
読み出しの対象は**TCBのControl BレジスタのBits 2:0(CNTMODE[2:0])**です。
このレジスタではTCBタイマーのクロックのソースを指定します。

<img width="800" alt="ScreenShot11.png" src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/925192/8936c761-dfca-e45d-ad6c-594088d28b91.png">

このようにして設定値を取得しました。

```c++:TCB0,TCB1_Clock_Select確認
  Serial.println(TCB0_CTRLA, BIN); //p244 Bits 2:1 – CLKSEL[1:0] Clock Select 確認
  Serial.println(TCB1_CTRLA, BIN);
```

結果としては`0b00000101`が設定されており、インデックスで1~2ビットをデータシートの表と比較するとクロックソースは**TCA0からのCLK_TCA**に設定されているみたいです。ここでも最下位ビットはこのペリフェラルを有効化するかの設定で、デフォルトでは有効です。



次にWaveform Generationの設定を確認していきます。
読み出しの対象は**TCBのControl BレジスタのBits 2:0(CNTMODE[2:0])**です。
このレジスタではタイマーのモードを設定します。

<img width="800" alt="ScreenShot10.png" src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/925192/18f7b8e8-30ef-df09-b498-4f4cd6b5751d.png">

このようにして設定値を取得しました。

```c++:TCB0,_TCB1_Timer_Mode_確認
  Serial.println(TCB0_CTRLB, BIN); //p245 Bits 2:0 – CNTMODE[2:0] Timer Mode 確認
  Serial.println(TCB1_CTRLB, BIN);
```

結果としては`0b00000111`が設定されており、インデックスで0~2ビットをデータシートの表と比較するとTimer Modeは**8-Bit PWM mode**に設定されているようです。

今回このタイマーに対して行うべき処理は**クロックのソース元の変更**のみです。
このままのTCAタイマーからの設定を持ってくる状態だと、TCAタイマーは時間系関数との関係でよっぽどのことがない限りプリスケーラを変更しないと思うのでTCBタイマー側でクロック周波数を指定したほうが懸命だと思うからです。
具体的には`TCB0_CTRLA`レジスタを`0x00`に設定すればよいので、`TCB0_CTRLA &= ~0b00000110;`を実行します。
レジスタ更新後、再度表示させて適切か確認してみましょう。

周波数の計算はTCAタイマーで行ったものと同様です。実際に計測した波形はこのようなものになっています。

<img width="800" alt="ScreenShot12.png" src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/925192/69227b56-dc89-222d-1226-794f30c2a6e5.png">
<img width="800" alt="ScreenShot13.png" src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/925192/14e79d1d-96d9-b36c-db04-95444dd6e06d.png">

出力されているPWM波形が約*978.6Hz*・*62.5kHz*であることが確認できます。
変更点、検証結果を表にまとめるとこのようになりました。

|         TCB         |   設定前   |   設定後   |
| :-----------------: | :--------: | :--------: |
| Control Aレジスタ値 | 0b00000101 | 0b00000001 |
|     Timer Mode      | 0b00000111 | 0b00000111 |
|      PWM周波数      |  978.6Hz   |  62.5kHz   |


以上でTCBタイマーの検証・設定も完了です。

# 調べようと思った経緯
部活動で新たにマイコン搭載のモータドライバを制作しようと構想を練っていたところ、UARTが4つもあって値段もチップ単体だと150円というMEGA2560に比べると圧倒的な安さのマイコンがあると同期から聞いたのが最初でした。モータードライバに乗せるうえでPWM周波数の変更は必須だったので、調べてみたのですが、ネット上に情報が多いArduino UNOなどのATmega328系列と違い想像以上にATMega4809の情報が全体的（プログラム面＆回路面）に少ない状態でした。もし、PWMの周波数の変更方法がわからずこのマイコンでのブレイクアウトの作成を断念してしまったという人の少しでも参考になればいいかなと思います。最後まで読んでいただきありがとうございます。


### [ATmega4808/4809 Data Sheet](https://ww1.microchip.com/downloads/en/DeviceDoc/ATmega4808-09-DataSheet-DS40002173B.pdf)

[^1]: Arduino Nano EveryはArduino Nanoとの互換性を保つためにデフォルトでは16MHzで駆動するように設定されているようです。
[^2]: NanoでPWMピンであったD11はNano EveryではPWM対応ピンではないみたいです。