PMWの周波数を変更する(タイマ/カウンタ レジスタ値の確認と変更)

[Arduinoで遊ぶ]

作成 : 2013/02/11

"サーバの実験室"の検索


PMWの周波数を変更する(タイマ/カウンタ レジスタ値の確認と変更)

Arduinoのアナログ出力では、電圧をパルス出力で表現します(PWM)。 1周期の中でHIGHとLOWの比率を変化させ、平均値を出力電圧としています。

クロック周波数が16MHzのArduino UNOのPWM周波数は、3番・9番・10番・11番のピンでは約500Hzです。 クロック周波数が8MHzのJapaninoだと、PWM周波数は1/2の約250Hzになります。

PWM 3番ピン オシロスコープ

5番と6番のピンでは約1kHzです。 Japaninoでは約500Hzです。

PWM 5番ピン オシロスコープ

鉄道模型(Nゲージ)の列車制御にもPWMが使われています。 手持ちのTOMIX N-1000-CLという電源ユニットは、PWM周波周が約20KHzでした。

TOMIX N-1000-CL オシロスコープ

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になりました。

PWM 5番ピン 分周比1 オシロスコープ

タイマ/カウンタ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の分周比を変更しても、これらの関数の挙動に影響はなさそう。


[Arduinoで遊ぶ]