PMWの周波数を変更する(タイマ/カウンタ レジスタ値の確認と変更)
作成 : 2013/02/11
PMWの周波数を変更する(タイマ/カウンタ レジスタ値の確認と変更)
Arduinoのアナログ出力では、電圧をパルス出力で表現します(PWM)。 1周期の中でHIGHとLOWの比率を変化させ、平均値を出力電圧としています。
クロック周波数が16MHzのArduino UNOのPWM周波数は、3番・9番・10番・11番のピンでは約500Hzです。 クロック周波数が8MHzのJapaninoだと、PWM周波数は1/2の約250Hzになります。
5番と6番のピンでは約1kHzです。 Japaninoでは約500Hzです。
鉄道模型(Nゲージ)の列車制御にもPWMが使われています。 手持ちのTOMIX N-1000-CLという電源ユニットは、PWM周波周が約20KHzでした。
ArduinoのPWM周波数をTOMIX N-1000-CL並みに上げることができるのか知りたかったというのが、調べ始めたきっかけでした。 ATmega328のデータシートとGarretLabのanalogWrite()のページがとても参考になりました。
以下、PWMに関連するTimer/Counterのレジスタ値をシリアルに出力し、5番・6番ピンの分周比をデフォルトの「64」から「1」に変更するスケッチです。 スケッチの中で10番ピンを使用していないのは、Ethernetシールドを搭載していたためです。
// sbi(), cbi()を利用するため、wiring_private.hをインクルード。 #include <wiring_private.h> void setup() { Serial.begin(9600); pinMode(3,OUTPUT); pinMode(5,OUTPUT); pinMode(6,OUTPUT); pinMode(9,OUTPUT); pinMode(11,OUTPUT); } void loop() { // 指定したピンに対応するタイマを確認。 // 1 : TIMER0A // 2 : TIMER0B // 3 : TIMER1A // 4 : TIMER1B // 6 : TIMER2A // 7 : TIMER2B Serial.print("PIN#3 TIMER : "); Serial.println(digitalPinToTimer(3)); Serial.print("PIN#5 TIMER : "); Serial.println(digitalPinToTimer(5)); Serial.print("PIN#6 TIMER : "); Serial.println(digitalPinToTimer(6)); Serial.print("PIN#9 TIMER : "); Serial.println(digitalPinToTimer(9)); Serial.print("PIN#11 TIMER : "); Serial.println(digitalPinToTimer(11)); // 5番・6番ピンに対応するTimer/Counter0が使用するレジスタの値を確認。 Serial.println(""); Serial.println("PIN#5/PIN#6"); Serial.print("TCCR0A : "); Serial.println(_SFR_BYTE(TCCR0A),BIN); Serial.print("TCCR0B : "); Serial.println(_SFR_BYTE(TCCR0B),BIN); Serial.print("TCNT0 : "); Serial.println(_SFR_BYTE(TCNT0),BIN); Serial.print("OCR0A : "); Serial.println(_SFR_BYTE(OCR0A),BIN); Serial.print("OCR0B : "); Serial.println(_SFR_BYTE(OCR0B),BIN); Serial.print("TIMSK0 : "); Serial.println(_SFR_BYTE(TIMSK0),BIN); Serial.print("TIFR0 : "); Serial.println(_SFR_BYTE(TIFR0),BIN); // 9番・10番ピンに対応するTimer/Counter1が使用するレジスタの値を確認。 Serial.println(""); Serial.println("PIN#9/PIN#10"); Serial.print("TCCR1A : "); Serial.println(_SFR_BYTE(TCCR1A),BIN); Serial.print("TCCR1B : "); Serial.println(_SFR_BYTE(TCCR1B),BIN); Serial.print("TCCR1C : "); Serial.println(_SFR_BYTE(TCCR1C),BIN); Serial.print("TCNT1H : "); Serial.println(_SFR_BYTE(TCNT1H),BIN); Serial.print("TCNT1L : "); Serial.println(_SFR_BYTE(TCNT1L),BIN); Serial.print("OCR1AH : "); Serial.println(_SFR_BYTE(OCR1AH),BIN); Serial.print("OCR1AL : "); Serial.println(_SFR_BYTE(OCR1AL),BIN); Serial.print("OCR1BH : "); Serial.println(_SFR_BYTE(OCR1BH),BIN); Serial.print("OCR1BL : "); Serial.println(_SFR_BYTE(OCR1BL),BIN); Serial.print("ICR1H : "); Serial.println(_SFR_BYTE(ICR1H),BIN); Serial.print("ICR1L : "); Serial.println(_SFR_BYTE(ICR1L),BIN); Serial.print("TIMSK1 : "); Serial.println(_SFR_BYTE(TIMSK1),BIN); Serial.print("TIFR1 : "); Serial.println(_SFR_BYTE(TIFR1),BIN); // 3番・11番ピンに対応するTimer/Counter2が使用するレジスタの値を確認。 Serial.println(""); Serial.println("PIN#3/PIN#11"); Serial.print("TCCR2A : "); Serial.println(_SFR_BYTE(TCCR2A),BIN); Serial.print("TCCR2B : "); Serial.println(_SFR_BYTE(TCCR2B),BIN); Serial.print("TCNT2 : "); Serial.println(_SFR_BYTE(TCNT2),BIN); Serial.print("OCR2A : "); Serial.println(_SFR_BYTE(OCR2A),BIN); Serial.print("OCR2B : "); Serial.println(_SFR_BYTE(OCR2B),BIN); Serial.print("TIMSK2 : "); Serial.println(_SFR_BYTE(TIMSK2),BIN); Serial.print("TIFR2 : "); Serial.println(_SFR_BYTE(TIFR2),BIN); Serial.print("ASSR : "); Serial.println(_SFR_BYTE(ASSR),BIN); Serial.print("GTCCR : "); Serial.println(_SFR_BYTE(GTCCR),BIN); // タイマ/カウンタ0が使用するTCCR0Bレジスタの値を変更して分周比を1にする。 // cbiはレジスタの対応するビットを'0'に、sbiは'1'にする。 cbi(TCCR0B, CS02); cbi(TCCR0B, CS01); sbi(TCCR0B, CS00); // PWM出力開始。 analogWrite(3,127); analogWrite(5,127); analogWrite(6,127); analogWrite(9,127); analogWrite(11,127); // タイマ/カウンタ0が使用するTCCR0Bレジスタの値を確認。 // "00000011"が"00000001"になるはず。 Serial.println(""); Serial.print("TCCR0B : "); Serial.println(_SFR_BYTE(TCCR0B),BIN); for (;;); }
スケッチを実行したときのシリアル出力です。
PIN#3 TIMER : 7 PIN#5 TIMER : 2 PIN#6 TIMER : 1 PIN#9 TIMER : 3 PIN#11 TIMER : 6 PIN#5/PIN#6 TCCR0A : 11 TCCR0B : 11 変更前のTCCR0Bレジスタ値 TCNT0 : 110100 OCR0A : 0 OCR0B : 0 TIMSK0 : 1 TIFR0 : 110 PIN#9/PIN#10 TCCR1A : 1 TCCR1B : 11 TCCR1C : 0 TCNT1H : 0 TCNT1L : 11111010 OCR1AH : 0 OCR1AL : 0 OCR1BH : 0 OCR1BL : 0 ICR1H : 0 ICR1L : 0 TIMSK1 : 0 TIFR1 : 111 PIN#3/PIN#11 TCCR2A : 1 TCCR2B : 100 TCNT2 : 11110010 OCR2A : 0 OCR2B : 0 TIMSK2 : 0 TIFR2 : 111 ASSR : 0 GTCCR : 0 TCCR0B : 1 変更後のTCCR0Bレジスタ値
オシロスコープで見ると、5番ピンのPWM周波数は約60kHzになりました。
タイマ/カウンタ0の分周比とdelay(), millis(), micros()
タイマ/カウンタ0の分周比を変更すると、タイマ/カウンタ0を利用している関数に影響があります。 以下のようなスケッチを作成して、分周比を変更しながらdelay(100)ごとにmillis()、micros()の実行結果をみてみます。
// sbi(), cbi()を利用するため、wiring_private.hをインクルード。 #include <wiring_private.h> void setup() { Serial.begin(9600); } void loop() { // タイマ/カウンタ0の分周比を変更にする。 // 001:1, 010:8, 011:64 //cbi(TCCR0B, CS02); //cbi(TCCR0B, CS01); //sbi(TCCR0B, CS00); // タイマ/カウンタ1の分周比を変更にする。 // 001:1, 010:8, 011:64 //cbi(TCCR1B, CS12); //cbi(TCCR1B, CS11); //sbi(TCCR1B, CS10); // タイマ/カウンタ2の分周比を変更にする。 // 001:1, 010:8, 011:64 //cbi(TCCR2B, CS22); //cbi(TCCR2B, CS21); //sbi(TCCR2B, CS20); Serial.print("millis() : "); Serial.print(millis()); Serial.print(", micros() : "); Serial.println(micros()); delay(100); }
分周比がデフォルトの64のときは想定通りの挙動です。
millis() : 1208, micros() : 1209632 millis() : 1309, micros() : 1310492 millis() : 1410, micros() : 1411364 millis() : 1511, micros() : 1512228 millis() : 1611, micros() : 1613100 millis() : 1713, micros() : 1713956 millis() : 1813, micros() : 1814832 millis() : 1914, micros() : 1915696 millis() : 2015, micros() : 2016568 millis() : 2116, micros() : 2117428
分周比を8にすると出力間隔が短くなり、
millis() : 4227, micros() : 4351768 millis() : 4527, micros() : 4651288 millis() : 4826, micros() : 4950808 millis() : 5126, micros() : 5250328 millis() : 5425, micros() : 5549848 millis() : 5725, micros() : 5849368 millis() : 6024, micros() : 6148888 millis() : 6324, micros() : 6448412 millis() : 6623, micros() : 6747928 millis() : 6923, micros() : 7047452
分周比を1にすると出力間隔がもっと短くなります。
millis() : 33083, micros() : 34215492 millis() : 35612, micros() : 36744768 millis() : 38141, micros() : 39274024 millis() : 40671, micros() : 41803336 millis() : 43200, micros() : 44332596 millis() : 45729, micros() : 46861892 millis() : 48259, micros() : 49391156 millis() : 50788, micros() : 51920456 millis() : 53317, micros() : 54449736 millis() : 55846, micros() : 56978984
タイマ/カウンタ0の分周比を変更すると、Serialライブラリ、tone()、analogRead()、micros()、millis()、delayMicroseconds()、delay()に影響があるようです。 タイマ/カウンタ1とタイマ/カウンタ2の分周比を変更しても、これらの関数の挙動に影響はなさそう。