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


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

はじめに

この記事は、ロボコンなどでモータドライバをマイコンから制御するときに使用するPWM制御についてです。
Arduinoでは通常、490Hz980Hzの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, D102
ペリフェラル 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ビットタイマTCATCBがあります。チャンネル数としてはTCAが1つ、TCBが4つになっていています。
I/OピンのMultiplexed Signals割り当ては以下の表の通りです。


対象ビット - レジスタ名 レジスタ値 デフォルト オルタネイティブ
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)


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


参考: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])です。
このレジスタでは使用するプリスケーラーを変更します。

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

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波形を生成するうえで必要な情報を指定します。

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

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周波数の計算方法は

 f_{PWM_SS} = \frac{f_{CLK_PER}}{N(PER+1)}

適応前が

 f_{CLK_PER} = 16MHz\\
 N = 64(プリスケーラー)\\
 PER = 255(分解能)

適応後が

 f_{CLK_PER} = 16MHz\\
 N = 1(プリスケーラー)\\
 PER = 255(分解能)

ですので、変更前と後のPWM周波数はこのようになるはずです。

 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

実際にオシロスコープで計測した結果はこのようになっています。

出力されているPWM波形が約978.6Hz62.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タイマーのクロックのソースを指定します。

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

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])です。
このレジスタではタイマーのモードを設定します。

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

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タイマーで行ったものと同様です。実際に計測した波形はこのようなものになっています。


出力されているPWM波形が約978.6Hz62.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


  1. Arduino Nano EveryはArduino Nanoとの互換性を保つためにデフォルトでは16MHzで駆動するように設定されているようです。 

  2. NanoでPWMピンであったD11はNano EveryではPWM対応ピンではないみたいです。