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, 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ビットタイマTCAとTCBがあります。チャンネル数としては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
レジスタには0x01
をTCBROUTEA
レジスタのビット0には0x01
,ビット1には0x01
,ビット2には0x00
,ビット3には0x01
を設定していることがわかります。次の項目では今調べたポートに対応するタイマーのカウンタに対しての設定変更を行っていきます。
タイマーレジスタの変更
レジスタの書き換えを行ってきたいのですが、ここで一つ注意点があります。
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])**です。
このレジスタでは使用するプリスケーラーを変更します。
このようにして設定値を取得しました。
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レジスタではスピリットモードとシングルモードで設定項目が大きく変化しているので注意します。)
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.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タイマーのクロックのソースを指定します。
このようにして設定値を取得しました。
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])**です。
このレジスタではタイマーのモードを設定します。
このようにして設定値を取得しました。
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.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の周波数の変更方法がわからずこのマイコンでのブレイクアウトの作成を断念してしまったという人の少しでも参考になればいいかなと思います。最後まで読んでいただきありがとうございます。