ArduinoのTimerに関する情報がまとまっていなかったのと、自分には複雑だったためかいつまんで重要そうなところだけまとめてみます。今後も加筆・修正していく予定です。後学のためにも間違いがありましたらご指摘いただけると幸いです。またこの記事において"arduino"は全てArduino Unoを指します。
なぜTimerが必要か
個人的にはATtiny85(Arduinoの超小型化版のようなもの。詳しくは->ArduinoユーザーのためのATTiny入門)で(擬似的な)サイン波を出すために必要でした。Arduinoではtone()やfor文を使って矩形波で音を出すことは可能ですが、サイン波など矩形波以外の信号を手軽に出すにはMozziなど外部のライブラリが必要でした。
Timerは基本的には正確なタイミングでなんらかのシグナルを送りたい時に必要になるものです。arduinoではdelay()を使って時間の操作が行えますが、delayを使っているとそれが実行されている間に他の操作を行えないという弊害があり、そんな時にTimerを利用することができます。Timerの大まかなイメージとしてはTimerのカウンターが数字を0から「自動的に」カウントアップしていって、ある数値に到達した時に何かをしてもらい、そしてそのカウントしていた数値をまたリセットして(あるいはカウントダウンして)その後もこの動作が繰り返される、というようなものです。
Arduino UnoのTimerは3つ
Arduino UnoにはTimer0・Timer1・Timer2の3つのTimerがあります。
bit数 | 対応pin | 対応しているfunction | |
---|---|---|---|
Timer0 | 8bit | 5,6pin | delay(), millis(), micros()など |
Timer1 | 16bit | 9,10pin | - |
Timer2 | 8bit | 3,11pin | tone() |
Arduino Unoは電源を繋ぐと16MHz(1610^6Hz)の電気信号を中央の"T16.000"と書いてあるクリスタルから発振します。millis()などの関数も実際にはこの信号を元に作り出されています。Timerのbit数の違いによって、この16MHzの周期を分解する解像度が変わってきます。つまり、8bitの時は16MHzの信号をmax255(8bit = 2^8 = 256->0から255)のステップにして処理し、16bitの時には同じ考え方でmax65535のステップにします。1秒につき1610^6回の波を送り出しているのを8bitと16bitで制御するので、1秒を1000ミリセカンド(ms)だとすると、それぞれのTimerが一つの周期にかかる時間はそれぞれ以下のようになります。
1000ms\div16000000(16MHz)\times256(8bit) = 0.016ms\\
1000ms\div16000000(16MHz)\times65536(16bit) = 4.096ms
カウンターの描くグラフはノコギリ波のような形状になります。(ただしこれはカウンターの数値が描くグラフであって、何かこのような形状の電気信号が送られているわけではありません。自分は最初ここら辺のところがごっちゃになっていたので一応書いておきます。。。)
レジスタについて
それぞれのTimerはレジスタというものをもっています。
Register | 合計 | |
---|---|---|
Timer0 | TCCR0A, TCCR0B, TCNT0, OCR0A, OCR0B, TIMSK0, TIFR0 | 7 |
Timer1 | TCCR1A, TCCR1B, TCCR1C, TCNT1H, TCNT1L, OCR1AH, OCR1AL, OCR1BH, OCR1BL, ICR1H, ICR1L, TIMSK1, TIFR1 | 13 |
Timer2 | TCCR2A, TCCR2B, TCNT2, OCR2A, OCR2B, TIMSK2, TIFR2 | 7 |
これをみるとTimer0とTimer2に関しては構成は一緒でTimer1は比較的多数のレジスタがあることがわかります。ちなみにTCCR0AであればTimer0、TCCR1AであればTimer1、TCCR2AであればTimer2といったように、レジスタの名前にある数字でどのTimerに属するかを知ることができます。ちなみにTimer0とTimer2は8bit、Timer1は16bitのタイマーになります(違いについては上のノコギリのようなグラフを参照してください)。
これらのレジスタを制御することによってTimerの動きを決めていきます。なので、Timerをいじるとは具体的にはこれらのレジスタを操作していくことになります。
レジスタ TCCR0A / TCCR0Bについて
ここではTimer0のTCCR0A / TCCR0Bレジスタについて話していきます。3つのTimer全てがこのレジスタ(TCCRxA / TCCRxB)を共通して持っており、Timerの要となるようなレジスタです。Timer0のTCCR0AとTCCR0Bはそれぞれ8bitのレジスタですが、その2つのレジスタを組み合わせることによってなんらかの機能を持ちます。機能と一口に言いましたが、これだけでもかなり色々な機能をもちえます。下はTCCR0AとTCCR0Bの構成表です。
TCCR0A
bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|
名称 | COM0A1 | COM0A0 | COM0B1 | COM0B0 | - | - | WGM01 | WGM00 |
TCCR0B
bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|
名称 | FOC0A | FOC0B | - | - | WGM02 | CS02 | CS01 | CS00 |
これらの表を初めて見たときイマイチどういうことか理解できなかったのですが、
例えばTCCR0Aという一つのレジスタは8bitの数値で構成されているので、
TCCR0A = 0b00000001; //アタマの"0b"は以下は二進数ですよ、という合図のようなものです
という風に書いたとすると、1桁目の"WGM00"が"1"になっている状態となります。このレジスタの書き込み方については色々なやり方があるので後ほどまた説明します(実は上の書き方はベストではないのですが、わかりやすいためこう書いています。これでも機能します)。ちなみにこのようなTCCR0Aといったレジスタはデフォルトで定義されているものなので、当然のことながら変数宣言などいらず、上のようにarduinoのIDEに直接書けます。では組み合わせによってどのような機能を持ち得るか、代表的なものとしてModeというものがあります。
Modeについて
Modeを変更することによってカウンターを制御する方法を変えます。Timerは大まかにいうと4つのモードをもっています。
モード名 | 特徴 | カウンターの描く形状 |
---|---|---|
Normal | 基本的なタイミングの制御が可能。複数の割り込みが可能 | ノコギリ波 |
CTC | カウンターの上限を自由に設定。複数の割り込みが可能 | ノコギリ波 |
Fast PWM | カウンターを基準にPWMを生成 | ノコギリ波 |
PWM, Phase Correct | カウンターを基準にPWMを生成 | 三角波 |
Normal modeとCTC(Clear Timer on Compare Match) modeはInterruput(割り込み処理)するためのもので、ある周期でなんらかの動作をさせたい時に使用し、Fast PWMとPWM Phase CorrectはPWMを生成するためのもので、カウンター+自分で指定した値の組み合わせからデューティー比、周波数を定義して電気信号を取り出したい時に使用します。割り込み処理をするタイミングはFlagを送るタイミングであり、これはISR()というすでにデフォルトで定義された関数を使って、そのフラグがきたタイミングにやってほしいことを自由に指定することができます。ちなみにarduinoはデフォルトでは割り込み処理が有効になっていますが、noInterrupts()を使って割り込み処理をプログラムの一部分で無効化することも可能です。それではそれらを踏まえた上で下がModeの構成表になります。
まずはWGM02とWGM01、WGM00に着目してください。これらの組み合わせがモードを決定していることがわかります。それぞれ0か1かしか数値を取らないので、2^3で8パターンのModeを選ぶことができます(とはいえmode4と6は"Reserved"になってるので実質は6パターンですが)。Timer/Counter Mode of OperationがModeの名前になり、Topとはカウンターの頂点がどのような数値になるかです。例えば"0xFF"であれば16進数で256なので、255がMax値になり、"OCRA(Output Compare Register A)"であればOCR0Aの値を定義してその値にカウンターが到達した時をTOPにして数値がリセットしますよ、ということです。例えばOCR0Aが200で"TOP"がOCR0Aなら、255に到達する前に200でカウンターは0にリセットされます(そしてまたカウントが繰り返されます)。Update of OCR0x atとありますが、これはOCR0AとOCR0Bの値をアップデートするタイミングを"Immediate"、つまりそのOCR0xの値にカウンターが到達した時にリセットするか、あるいは"TOP"とするとそのカウンターの頂点の時に、"BOTTOM"とすると信号の0の時にOCR0xがリセットされます。TOPとBOTTOMってタイミングほぼ一緒じゃないかと思われるかもしれませんが(というか自分は最初思いました)、ノコギリのように右上がりのカウントだけでなく、上がってまた下がっていく(カウントアップしてカウントダウンしていく)ような山のようなカウントの仕方も"PWM, Phase Correct Mode"ならできるので、TOPとBOTTOMでは意味が異なります。ちなみにOCR0xについては図を交えて後ほどまた説明します。TOV Flag Set onは、あるタイミングの時にフラグを送る、つまり今行動をおこしなさいという信号を送ります(Interrupt, いわゆる割り込み処理です)。"MAX"、"BOTTOM"、"TOP"はそのフラグを送るタイミングです。"MAX"は255にカウンターが到達した時で、"BOTTOM"はカウンターが0になった時、"TOP"はそのカウンターの周期の中で一番高い数値に到達した時です。これはOCR0Aに定義した値に依存します。
ここまでTimer0を例にとって話を進めてきましたが、実はTimer0が使われることはほとんどありません。。というのもTimer0はdelay()やmillis()、micros()などの機能を担っており、もっといってしまうとTimer0を使おうとするとエラーが出て書き込めません。。Timer0は先ほど挙げた時間を制御する機能を担っているので使えないということなのだと思いますが、ちょっとこの点については謎です。。ちなみにTimer1とTimer2では書き込めないといったことは起こりません(Timer2を使うとtone()は使えなくなりますが)。ただしTimer0とTimer2の構成はほぼ一緒なのと、Timer1でもビット数が16bitに変わるだけで基本的な考え方は全く同じです。
上の図はTimer1のModeの構成表になります。WGMx0, WGMx1, WGMx2だけでなく、WGM13というものが増え、モードの数が増えたことがわかります。新しい情報もあるのですが、基本的なことはTimer0のモードと同じなので、ここではそこには触れずに具体例をあげてそれぞれのモードについて解説していきます。
Normal モード
まず以下のようなプログラムをarduinoに書き込んでください。
Timer1を使用しています。
void setup() {
Serial.begin(115200);
TCCR1A = 0; // 初期化
TCCR1B = 0; // 初期化
TCCR1B |= (1 << CS10); // CS10 -> 1 prescaler = 1
TIMSK1 |= (1 << TOIE1); //TOIE -> 1 enable overflow
}
ISR(TIMER1_OVF_vect) {
Serial.println(millis());
}
void loop() {
}
これは最初にTCCR1AとTCCR1Bを初期化して、TCCR1BのCS10とTIMSK1のTOIE1を1に変えています。TOIE1を1にするとカウンターがTOPにきた時(この例では16bitのTimer1を使っているので65535になった時)に割り込み処理を行います。この処理の中身はISR内に記入してあって、TOPになった時に何ミリ秒たったかをシリアルモニターに表示します。Normal modeはデフォルトのモードなのでレジスタを変更しなくても自動的にNormal modeになります。一番最初の方で紹介しましたが、16bitのTimer1では一回の周期が4.096msだったのでほぼ4ずつ数値が上がっていくのがわかるかと思います。そして、CS10についてですが、これは分周波(prescaler)を指定するものです。("<<"はC言語のビットシフトを表します。この計算方法については後ほど解説します。)
CS10を1にする操作とは分周波を1にすることです(No prescaling)。この分周波とは16MHzの信号をその数値で割ってカウンターのTopに至るタイミングを遅くしたりできるのですが、これは実際に数値を変えてみるとよくわかります。例えば、CS10の行を消して以下のように書き直すと、
TCCR1B |= (1 << CS12); // CS12 -> 1 prescaler = 256
これはCS12を1にして分周波が256になったということです。数値のアップデートが遅くなっているのがわかります。1047という数値が出てきたかと思いますが、これは以下のような計算式で求めることができます。
\frac{65536(16bit)\times256(prescaler)}{16000000(16MHz)} = カウンターの周期
計算式としては1048.576ですがほぼ近似値が出ているのがわかります。では、カウンターの周期を1秒ちょうどで割り込みさせてみたいと思います。上の計算式から16bitの部分が65536ではなく62500となった時に1秒ちょうどになることが導けます。カウンターの値はTCNT1に指定することができるので、65536-62500=3036をカウンターの初期値として入れ、割り込むと同時にまたカウンターの初期値を毎回3036に変えています。
void setup() {
Serial.begin(115200);
TCCR1A = 0; // 初期化
TCCR1B = 0; // 初期化
TCNT1 = 3036;
TCCR1B |= (1 << CS12); // CS12 -> 1 prescaler = 256
TIMSK1 |= (1 << TOIE1); //TOIE -> 1
}
ISR(TIMER1_OVF_vect) {
Serial.println(millis());
TCNT1 = 3036;
}
void loop() {
}
これは以下のようなイメージです。
CTC(Clear Timer on Compare) モード
ではCTCモードをみていきます。Modeの表にある通り、CTCモードの時TOPはOCR1Aの値になります。このOCR1Aは先ほどのTCNT1のように直接数値を書き込むことができます。また同時にCTCモードではOCR1Bというcompare matchをもう一つ使ってOCR1AとOCR1B2つの割り込みを与えることができます。カウンターTCNT1がOCR1AあるいはOCR1Bの値とマッチした時に割り込みます(OCR -> Output Compare Register)。TIMSK1のOCE1AとOCIE1Bを1にすることでそれらの割り込みの許可を与えることができます。以下は1秒周期(OCR1A)とそこから0.5秒ずれた1秒周期(OCR1B)とで割り込み処理を行なっています。
void setup() {
Serial.begin(115200);
TCCR1A = 0; // 初期化
TCCR1B = 0; // 初期化
OCR1A = 62500;
OCR1B = 31250;
TCCR1B |= (1 << CS12) | (1 << WGM12); // CS12 -> 1(prescaler -> 256) CTC mode on
//OCIEA -> 1 (enable OCR1A Interrupt) OCIEB -> 1 (enable OCR1B Interript)
TIMSK1 |= (1 << OCIE1A) | (1 << OCIE1B);
}
ISR(TIMER1_COMPA_vect) {
Serial.print("from OCR1A : ");
Serial.println(millis());
}
ISR(TIMER1_COMPB_vect) {
Serial.print("from OCR1B : ");
Serial.println(millis());
}
void loop() {
}
Normal modeとCTC modeの違い
Normal modeでもOCR1AとOCR1Bで割り込むことができますが、Normal Modeの場合TOPの値はTimerのbit数に依存し、Timer1なら65535、Timer0と2なら255でこの値は変えられません。一方でCTCの場合はTOPはOCR1Aで指定した値になり、そこでカウンターは0にリセットされます。つまり、Normal modeでは割り込みの頻度(周波数と表現していのかわからないので…)は変えられませんが、CTCでは変えることができます。イメージとして言うと、ノコギリみたいなカウンターの描く三角形の大きさがCTCだといくらでも小さくできて、リセットのタイミングを速められます。
Fast PWM
Fast PWMでduty比を調整してPWMを送ることができます。また名前からもわかる通り、通常のdigitalWriteでは出せないような高い周波数のPWMを生成することも可能です。ここでレジスタTCCR1AのCOM1A1, COM1A0, COM1B1, COM1B0を操作して"non-inverting mode"か"inverting mode"かを選びます。non-invertingの時はOCR1A(OCR1B)とカウンターの値を比べた時に、カウンターの値の方が数値が小さい時にOC1A(OC1B)をHIGHに(データシートではHIGHになることをset、LOWになることをclearと表現しています)、大きい時にはLOWにします。"inverting mode"ではこれの真逆です。これは図を見ると一目瞭然だと思います。
イメージはこのサイトからです。
イメージ内にも書いてありますが、PWMの周波数は以下のように求められます。bit数やprescalerはModeによって変更可能なので自分の好きな周波数を設定することができます。ちなみに以下の公式はnone-inverting, invertingどちらのモードでも同じ公式です。
\frac{16MHz}{256(8bit)\times prescaler} = PWMの周波数
そしてこれはnone-inverting modeのデューティー比の求め方です。
\frac{OCR1A/B + 1}{256(8bit)}\times100 = デューティー比(none-inverting\ mode)
これはinverting modeの時です。
\frac{255 - OCR1A/B}{256(8bit)}\times100 = デューティー比(inverting\ mode)
OC1AのPWMは9pin、OC1BのPWMは10pinから出てきます。
!!周波数計算についての追記!!
実際に使ってみて思ったことですが、例えば特定の周波数のPWMを出したい、と思った時に16MHzは動かしようがないので、計算式の256(8bit)の部分を512(9bit)や1024(10bit)などモードを変えて変更する、あるいは分周波(prescaler)を1から8、256などに変更できますが、これではあまりに正確さに欠け、自分の望みの周波数はほぼ逆算できません。。256(8bit)のところを完全に数値指定できるモードはないか、、と思っていたところありました。Fast PWMのICR1をTOPにするMode14です。(Mode 15のFast PWM modeでも可能です!!!この場合はOCR1Aの値がTOPになるので、この値を調節することでPWMの周波数を変えられます)
これを使うと、例えば
ISR1 = 200;
とすると、仮に分周波を64として、16MHz/(200*64) = 1250Hzというようにより自由に周波数を出力することができます。以下のような計算式が成り立ちます。
\frac{16MHz}{ICR1(0 to 255)\times prescaler} = PWMの周波数
この動画の最後の方で全く同じ方法を試していますが、最高で8MHzのPWMを出力できるようです。自分はオシロスコープを持っていないので確かめられませんが。。
Phase Correct PWM
Phase Correct PWMも基本的な考え方はFast PWMと同じです。がここではカウンターがTOPに行ってから0にリセットされるのではなく、0に向かってカウントダウンしていきます。やはりnone-inverting modeとinverting modeがあり、カウンターの数値とOC1RA/OC1RBを比較してカウンターが小さい時にHIGHなのがnone-inverting modeで、その逆がinverting modeです。
以下の510は256(8bit)を2倍にしてひとまず512、山なりに数値がおりていくので頂点で1bit分(255から255ではなく254になるので)、一番降りきったところで1bit分(0から0ではなく1になるので)、合わせて2bit分なくなっているからだと思います。
\frac{16MHz}{510(8bitの場合)\times prescaler} = PWMの周波数
そしてこれはnone-inverting modeのデューティー比の求め方です。
\frac{OCR1A/B}{255}\times100 = デューティー比(none-inverting\ mode)
これはinverting modeの時です。
\frac{255 - OCR1A/B}{255}\times100 = デューティー比(inverting\ mode)
OC2Aは11pin、OC2Bは3pinから出力されます。
PWMをどうやって止めるか?
PWMの止め方ですが、
TCCR1B = 0b00000000;
あるいは
TCCR1B = 0x00;
とすると、止めることができます。データシートによるとTCCR1BのCS12、CS11、CS10が0の時"No clock source (Timer/Counter stopped)."となると書かれていますが、
TCCR1B |= (CS12 << 0)|(CS11 << 0)|(CS10 << 0);
と書いても止まってくれません。
レジスタへの代入方法
ビット演算の基本はここ
ビット演算のANDとOR
&(AND)は -> 両方のビットが 1 のときのみ結果が 1 になるビット演算。
|(OR)は -> いずれかのビットが 1 なら結果が 1 になるビット演算
これをふまえて以下のように書くと、レジスターTCCR1Bには0bxxxxx001が入リます。
TCCR1B = TCCR1B & 0b11111000 | 0b00000001; // 0bxxxxx001
これは最初TCCR1Bが"0bxxxxxxxx"の初期状態であり、ここに"b11111000"をANDで演算すると、"0bxxxxxx000"が出てきて、そこに"0b00000001"をORで演算すると、1の部分だけ取り出され、"0bxxxxxx001"が出てきます。
ちなみに
TCCR1B = 0b00000001; // 0b00000001
すでにこの書き方は最初のほうであげましたが、このようにビット演算を使わずに直接0か1で書くこともできますが、この"0b00000001"と"0bxxxxxx001"は違う意味で、xの部分はなんらの情報も入っていない状態で、0は状態を記入しており、思わぬ動作を避けるためにも、ビット演算で必要な部分のみを書き込むべきです。
ただし、この書き方よりもビットシフトを使った以下の書き方のほうが明示的で個人的にはわかりやすいです。以下のCS12はarduino側で2の数値が当てられており、0を2桁分左にシフトしています。すると、当然の事ながらCS12に0がセットされます。CS11の場合は0を1桁分左にシフトしてCS11に0をセット、CS10の時は1を0桁分左にシフトするので、0桁目に1が入る、つまりCS10に1がセットされるということになります。
// Timer1 Clock Prescaler to : 1
TCCR1B |= (0 << CS12) | (0 << CS11) | (1 << CS10);
**_BV()と~_BV()**を使う書き方もネット上で散見されます。
_BV()は中身を1に、~_BV()は0にします。
// Timer1 Clock Prescaler to : 1
TCCR1B |= ~_BV(CS12) | ~_BV(CS11)| _BV(CS10);
sbi(), **cbi()**というのもあります。
1個目の引数でレジスタを指定して、2個目に変えたい項目を書きます。
sbi()は1をセット(sはset)、cbi()は0を入れる(cはclear)。
//関数を定義するために以下の2行必要です
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
// Timer2 Clock Prescaler to : 1
sbi (TCCR2B, CS20);
cbi (TCCR2B, CS21);
cbi (TCCR2B, CS22);
bitSet()というarduino側で定義されてる関数も利用できます。というわけでレジスタの書き方はかなりたくさんの方法があります。これがTimerをわかりにくくしている理由のようにも思えますが。。
ちなみに以下のようにするとTimer関連情報をシリアルモニタから確認できます。
この方法のソースはこちらになります。
void setup() {
Serial.begin(9600);
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);
}
void loop() {
}
追加情報
ココに割り込みの優先度が載っています。
参考
website
(日本語ページ)
- Timerに関する詳細情報。
Arduinoで遊ぶページ - PINの図解 + PORT + レジスタへの基本的な代入方法の説明があります。
Arduinoプログラムの高速化( Qiita ) - ArduinoのTimerライブラリの紹介があります。Arduinoのタイマーライブラリ
(英語ページ)
- ArduinoのTimerに関する網羅的な記事です。初心者向けの優しい解説です。(英語)
「Skill Builder: Advanced Arduino Sound Synthesis」 - Arduinoでsine波を出す方法。コードもありますが、中〜上級者向けで、初歩的な解説はありません。(英語)
「Arduino DDS Sinewave Generator」
youtube
- ArduinoのTimerに関する導入的な解説動画です。電子工作に関する親切な解説動画をよくあげているドイツのyoutuberさんです。(英語)
「Electronic Basics #30: Microcontroller (Arduino) Timers」 - 同じ人の動画ですが、レジスターの扱いに関する説明が中盤以降にあります。(英語)
Arduino Basics 103: Library, Port Manipulation, Bit Math, Faster PWM/ADC - ArduinoのTimerに関する網羅的な解説動画です。長いですが詳細に解説してくれています。(英語)
「8. Arduino Timers and Counters」」 - 図解もあってコンパクトにまとめられた説明です。(英語)
Lecture 8: Timer PWM Output