プログラムの説明
まず、レジスタの設定を変えるために、<avr/io.h>をインクルードします。
#include <avr/io.h>
次に、delay()と一緒に使って、10 Hzの出力を5秒ごとに繰り返すという関数を作るため、HzWriteという関数を指定します。カッコ内には周波数、Duty比を指定できるようにします。
void HzWrite(int frq, float duty) {
}
次にモードの指定です、。 今回使うレジスタはTCCR1A/TCCR1Bです。(TCCR: Timer/Counter Control Register) ここで1というのはTImer1を示しており、Timer2を使いたければTCCR2A/TCCR2Bを使います。
PWM周波数を〇〇Hzとして設定するには、TOP値を自分で指定する必要があります。 TOP値が大きければ大きいほど出力される周波数は低くなります。 今回は10 Hzで例を取っているので、16MHzというシステムクロックと比べると非常に遅いので、大きなTOP値と分周を指定する必要がある。よって、なるべく大きいTimer1を使用します。
Timer0, 2の8 bitではTOP値の最大値は255(2^8 – 1)であり、Timer1の16 bitでは65535(2^16 – 1)となる (最大値が-1となるのは、0~255もしくは0~65535となるため、数字の数が2^xになるが、最大値は-1しなければならない。)
システム的には、TOP値 までカウンタをインクリメント(0, 1, 2, と数えていく)し、OCRxA/OCRxB(xはカウンタ番号、カウンタについて2つの出力ピンA, Bが割り当てられている。)と一致した時にピンの出力を変化させる(LOW→HIGHのように)。そして、カウンタがTOP値 まで到達すると今度は0までデクリメント(65535, 65534, 65533と上から数えていく)し、インクリメントの時と同様にOCRxA/OCRxBと一致した時にピンの出力を変化させます。
Arduino UNOではこのカウンタのインクリメントするスピードは分周比の設定を変えることによって、ある程度変えることができます。(ある程度とは、1/8/64/256/1024 の中から選択できるということ) 分周比は周波数を分周(周波数を1/n倍すること)するときの比(n)です。 つまり、1000 Hzを分周比10で分周すると100 Hzになります。
ちなみに、分周比1ではArduino UNO(ATmega328)のシステム動作クロックである16MHzで操作します。
つまり、TOP値とOCRxA/OCRxBの値、分周比を変えることによって、自由に出力の変換点を変えることができます。
今回は10Hzを指定したいので余裕を持って分周比256で行います。 1カウントする時間は、Unoなら16MHzなので1 / 16MHz = 62.5ns(分周1) 分周比を256にすると、TOPまでカウントした際には62.5ns x 256 x 65535 = 1.04856 secなので、1 Hzを指定できるようになる。
今回このプログラムでは1 Hz ~ 31250 Hzまでの周波数を作り出すことができる。 しかし、31250に近づくにつれてDuty比の指定ができなくなっていきます。 Duty比を細かく設定したい際には300 Hz程度までしか作成することができません。
このプログラムでは分周比の設定を変更することで、自分の使用用途にあった周波数帯を作れるようなプログラムを作ることができます。
TCCRaA/TCCR1Bには何を指定するのかを書きます。 ここら辺の細かいところはなんか難しいので、データシートを使って、なんとなく指定していきましょう。
今回は2進数で示しているので、最初に0bがついています。TCCR1AではCOM1A1, COM1A0, COM1B, 無指定, 無指定, WGM11, WGM10 を 1 か 0 で設定します。TCCR1B には それぞれ、無指定(ICNC1), 無指定(ICES1), 無指定, WGM13, WGM12, CS12, CS11, CS10 を設定します。
TCCR1Aの指定(ATmega328データシートより)
TCCR1Bの指定(ATmega328データシートより)
まず、今回はModeはPWMモードがPhase Correct and Frequency Correct であるMode9を指定する。 今回TOP値 はOCR1A に指定する。
モードの指定(ATmega328データシートより)
よって、WGM13 / WGM12 / WGM11 / WGM10 はそれぞれ、1,0,0,1
出力の指定(ATmega328データシートより)
COM1B0 / COM1B0 は 0, 0 が 無出力、0, 1 が トグル動作 (一致時の出力を反転)、 1, 0 は カウンタ がOCR1A/B – TOP間 にある場合 LOW、0 – OCR1A/B間 は HIGH となるよう出力、 1, 1 は1, 0 の逆です。
今回は周波数をOUTPUTのLEDをLOWとHIGHで変えるため、1B0 / COM1B0 は1, 0の値を指定します。
スケッチを書く際にわかりやすいので 1, 0 を選択します。
分周比の指定(ATmega328データシートより)
今回分周比は256を指定するため、CS12/CS11/CS10は1, 0, 0を指定する。
まとめると、以下のようになる。
TCCR1A = 0b00100001;
TCCR1B = 0b00010010;
次にOCR1AとOCR1Bを設定し、周波数とDuty比を指定したもので出力してもらうように指定します。
// TOP値指定
OCR1A = (unsigned int)(31250 / frq);
// Duty比指定
OCR1B = (unsigned int)(31250 / frq * duty);
今回Phase and Frequency Correct PWMを用いる際には、インクリメントしディクリメントすることから、出力される周波数は以下のように示すことができます。
周波数frq = IC の動作周波数 / (分周比 * TOP値 * 2)
逆にTOP値を指定するときは
TOP値 = IC の動作周波数 / (分周比 x frq x 2)
今回Arduino UNOで分周比256とした際には
TOP値 = OCR1A = 16,000,000 / (256 x frq x 2) = 31250 / frq
LOWとHIGHが入れ替わるタイミングはOCR1A/OCR1B = Duty比としたいため、
OCR1B = 31250 / frq x duty
オーバーフローを防ぐため、Unsigned intを指定している。
出力のメインはこちら
void setup() {
pinMode(PWMPin, OUTPUT);
}
void loop() {
HzWrite(10, 0.5);
delay(5000);
digitalWrite(PWMPin, LOW);
delay(5000);
}
PWMPinつまり10ピンをOUTPUTで指定し、HzWrite()で周波数とDuty比を指定する。 delay()でまった後に、digitalWrite(PWMPin, LOW)で出力をなくし、delay()する。
以上がプログラムの全貌と解説です。