ArduinoのPWMの最高周波数は?1MHzを出せるか?

ArduinoではanalogWrite()でPWMを出力できる。このときの周波数は、490Hz、または、980Hzだそうだ(ピンによる)。

この周波数を変更しようと思うとArduino IDEで用意されている関数では無理で、AVRのレジスタを直接いじる必要がある、というか、直接いじれば変更できる。そのあたりは以下の記事などで。

また、AVRのドキュメントも。

AVR131 : AVRの高速PWMの使用法 ⇒ https://avr.jp/user/AN/PDF/AVR131.pdf

ATmega328PB ⇒ https://avr.jp/user/DS/PDF/mega328PB.pdf

これらをジーッと見ていると、どうやらPWMのデューティを256段階(8ビット: 0~255)確保するには、クロックの1/256が上限周波数になるみたい。Arduino UNOだと16MHzクロックなので、16MHz/256=62.5kHz。これより上は出しようがない。なお、analogWrite()のPWM周波数が62.5kHzではなくて490Hzや980Hzなのは、クロックをそのまま使っているのではなくて、分周したものを使用しているため。

では、デューティの可変範囲を減らす、極端に1/2(50%)に固定すれば、もっと高い周波数を出力できそう(PWMではなくなるけど)。最大で、16MHzの半分の8MHzが出せる?

ということで、やってみる。コードはこれ。

#include <avr/io.h>

void setup() {
  unsigned int top_val;

  pinMode(5, OUTPUT); // pin 5 -> OC0B

  TCCR0A = 0x33;  // COM0B = 0x3, WGM0 = 0x7
  TCCR0B = 0x09;  // CS0 = 0x7

  top_val = 255;
  OCR0A = top_val; // TOP
  OCR0B = (unsigned int)(top_val >> 1); // duty
}

void loop() {
}

OCR0Aに設定するのがカウンタの最大値(変数top_val)。この半分の値をOCR0Bに設定してデューティを1/2にする。OC0Bを使うので、信号はデジタル出力の5番。やることはレジスタの設定だけだから、loop()の中は空。

まずは、top_valを255(8ビットの最大値)に設定。計算通り、62.5kHz。

続いて、半分の127。想定通り、125kHz。

飛ばして、15。計算通り、1MHz。波形が鈍っているのは、プローブのつなぎ方がいい加減ってことにもよるだろう。とりあえず、それは置いておく。デューティが崩れているのが気になる。カウンタの最大値が15で、上の計算だと7がセットされることになるので、これで合っているか。「 (unsigned int)(top_val >> 1) + 1」とすべきだったか?ま、いいや。

さらに半分の7。ちゃんと2MHzが出た。

さらに半分の3で4MHz。1:3になってしまうので、こうなるか。

さらに半分。と思ったけど、それだとカウンタの最大値が1になってしまう。この半分は整数では作れないので設定は無理。

ということで、カウンタを2に。5.35MHz。デューティは1:2になる。これが上限ってことか。


ということで、デューティの刻みが少なくなってしまうことを受け入れられるなら、1MHz程度は出せるようだ(1MHzの場合でデューティの設定範囲は16段階)。

Arduino UNO互換機。 上は、公式ショップ(AliExpress内)。 子供向けみたいだけど、スイッチ、ブザー...

【追記】

上のようなレジスタ設定などは何もしないで、単に「analogWrite(5, 127);」とだけ書いた場合。マニュアル通り、約980Hzが出力される(16MHz÷64÷256)。