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になってしまう。この半分は整数では作れないので設定は無理。 ← これは勘違い。「この半分は整数では作れない」のはそのとおりだけど、カウンタとしては0と1の2つの状態が取れるので、波形の出力はできる。下の「追々記」を参照。
ということで、カウンタを2に。5.35MHz。デューティは1:2になる。これが上限ってことか。
ということで、デューティの刻みが少なくなってしまうことを受け入れられるなら、1MHz程度は出せるようだ(1MHzの場合でデューティの設定範囲は16段階)。
【追記】
上のようなレジスタ設定などは何もしないで、単に「analogWrite(5, 127);」とだけ書いた場合。マニュアル通り、約980Hzが出力される(16MHz÷64÷256)。
【追々記】
top_valの値を1にすれば8MHzが出せる。
波形がきれいじゃないのはこのオシロの限界のせいもある。
それともう一点。この実験では、ATmega328Pのタイマ/カウンタ0を使ったが、このタイマ/カウンタはArduinoが使用しているので設定を自らない方が無難。delay()の周期が変ったり、シリアルモニタが動かなかったりといった副作用が出る。正弦波の記事のようにタイマ/カウンタ1や2を使う方が良い。
コメント