0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Arduino UNO】タイマーレジスタ―を使いこなす

Last updated at Posted at 2025-12-02

fiord Advent Calendar 2025 2 日目の内容となります。

前日の記事 でタイマーレジスターを含めた複数の方法で L チカを実装しました。タイマーレジスタ―がどのように動作しているかについては説明を省いたため、この記事で解説を行います。

この方法では、Arduino が搭載しているマイコンの種類によって操作方法が変わります。今回は Arduino UNO R3 を対象に、ATmega328P を前提とします。

割り込みの種類

下記の割り込みが ATmega328P にはあります(データシートより抜粋)

Vector No. プログラムアドレス 割り込みベクタ名(AVR記号) 割り込み要因
1 0x0000 RESET リセット(外部ピン、電源ON、ブラウンアウトリセット、ウォッチドッグ)
2 0x0002 INT0 外部割り込みリクエスト 0
3 0x0004 INT1 外部割り込みリクエスト 1
4 0x0006 PCINT0 ピンチェンジ割り込みリクエスト 0(PBポート)
5 0x0008 PCINT1 ピンチェンジ割り込みリクエスト 1(PCポート)
6 0x000A PCINT2 ピンチェンジ割り込みリクエスト 2(PDポート)
7 0x000C WDT ウォッチドッグ・タイムアウト割り込み
8 0x000E TIMER2 COMPA Timer/Counter2 比較一致A
9 0x0010 TIMER2 COMPB Timer/Counter2 比較一致B
10 0x0012 TIMER2 OVF Timer/Counter2 オーバーフロー
11 0x0014 TIMER1 CAPT Timer/Counter1 キャプチャイベント
12 0x0016 TIMER1 COMPA Timer/Counter1 比較一致A
13 0x0018 TIMER1 COMPB Timer/Counter1 比較一致B
14 0x001A TIMER1 OVF Timer/Counter1 オーバーフロー
15 0x001C TIMER0 COMPA Timer/Counter0 比較一致A
16 0x001E TIMER0 COMPB Timer/Counter0 比較一致B
17 0x0020 TIMER0 OVF Timer/Counter0 オーバーフロー
18 0x0022 SPI, STC SPI 転送完了
19 0x0024 USART, RX USART 受信完了
20 0x0026 USART, UDRE USART データレジスタ空(送信可能)
21 0x0028 USART, TX USART 送信完了
22 0x002A ADC ADC 変換完了
23 0x002C EE READY EEPROM 書き込み/読み込み準備完了
24 0x002E ANALOG COMP アナログコンパレータ出力変化
25 0x0030 TWI 2線式シリアルインターフェース(I²C)
26 0x0032 SPM READY ストアプログラムメモリ準備完了(ブートローダ用)

このうち、今回は 0x000E から 0x0020 までの範囲がタイマー割り込みとして利用可能な種類となります。Timer/Counter に種類があるので、それぞれどのように利用されているかも見てみましょう。

タイマーの種類

Timer bit 幅 PWM で対応するピン デフォルトの用途
Timer0 8bit 5, 6 millis()delay() などで利用
Timer1 16bit 9, 10 Servo ライブラリで利用
Timer2 8bit 3, 11 tone() が利用

ATmega328P は 16MHz で動作します。「分周」という概念について後述しますが、8bit カウンターだと最大でも約 16ms に 1 度は割り込みを起こす必要があります。一方で、16bit では非常に多様なタイミングで割り込みを起こすことが出来ることから、基本的には Timer1 の利用をすることが望ましいと個人的には考えています。

以降は Timer1 を利用することを前提に話を進めます。

Timer1 に関連するレジスタについて

レジスタ ビット 役割
TCCR1A COM1A1:0, COM1B1:0 PWM出力ピンの動作(割り込みだけなら 00 でOK)
WGM11:10 波形生成モードの下位2bit
TCCR1B ICNC1 入力キャプチャノイズキャンセラ
ICES1 入力キャプチャエッジ選択
WGM13:12 波形生成モードの上位2bit
CS12:10 クロック選択(プリスケーラ設定)
TCCR1C FOC1A/B 強制出力比較(通常不要)
TCNT1 (16bit) 現在のカウンタ値(自動でインクリメント)
OCR1A / OCR1B 比較一致値(ここに値を入れる)
TIMSK1 OCIE1A/B 比較一致割り込みA/B 有効化
ICIE1 入力キャプチャ割り込み
TOIE1 オーバーフロー割り込み
TIFR1 割り込みフラグ(自動or手動でクリア)

「ビット」の欄の : の前後の数値は配列の添え字の範囲で、例えば CS12:10 には CS12CS11CS10 が内包されています。

この表から、例えば TCCR1BICNC1ICES1WGM13:12CS12:10 の 4 つのパラメータを持っていることが分かります。WGM12 を有効化(1 に変更)するには、下記のようにすればよいです。

TCCR1B |= (1 << WGM12);

一方で、 CS12 を無効化(0 に変更)するには、下記のように変更すればよいです。

TCCR1B &= ~(1 << CS12);

波形生成モード

WGM13:10 の値の内容により、どのように割り込みを起こすかの処理方法に違いがあります。
原則として、マイコンのクロックと共に内部でカウンターの数値が上がっていき、特定のタイミングで割り込みが発生します。

WGM13 WGM12 WGM11 WGM10 モード名 カウント範囲 割り込み発生タイミング 特徴
0 0 0 0 Normal 0 ~ 65,535 65,535 → 0 の時 Timer0 はこれで動作
0 0 0 1 PWM, Phase Correct, 8-bit 0→255→0 0/255 到達時
0 0 1 0 PWM, Phase Correct, 9-bit 0→511→0 0/511 到達時
0 0 1 1 PWM, Phase Correct, 10-bit 0→1,023→0 0/1,023 到達時
0 1 0 0 CTC 0 ~ OCR1A OCR1A 到達時 非常に汎用性が高い
0 1 0 1 Fast PWM, 8-bit 0 ~ 255 255 到達時
0 1 1 0 Fast PWM, 9-bit 0 ~ 511 511 到達時
0 1 1 1 Fast PWM, 10-bit 0 ~ 1,023 1,023 到達時
1 0 0 0 PWM, Phase and Frequency Correct 0→ICR1→0 0 モーターで利用されることがある
1 0 0 1 PWM, Phase and Frequency Correct 0→OCR1A→0 0 モーターで利用されることがある
1 0 1 0 PWM, Phase Correct 0→ICR1→0 0/ICR1 到達時 ` モーターで利用されることがある
1 0 1 1 PWM, Phase Correct 0→OCR1A→0 0/OCR1A 到達時 ` モーターで利用されることがある
1 1 0 0 CTC 0 ~ ICR1 ICR 到達時 OCR1A をパルス幅に利用可能な点が魅力
1 1 0 1 (Reserved)
1 1 1 0 Fast PWM 0 ~ ICR1 ICR1 到達時 analogWrite で利用
1 1 1 1 Fast PWM 0 ~ OCR1A OCR1A 到達時 1,000Hz 以上の高速 PWM

基本的には wGM13:100b0100 の CTCモードを利用することが一般的です。

TCCR1A = 0; // COM1A/COM1B は 0で良いです。そうでない場合 9/10 ピンへの出力が行われます。
TCCR1B = (1 << WGM12); // 他の設定は後述

分周比について

CTC で利用する OCR1A は 16-bit、つまり 65,535 までカウントが出来ます。

しかし、16MHz でカウントされるので、約 4ms でカウント上限に到達してしまいます。4ms より大きな周期で割り込みが行いたい際に 分周比(Prescaler) というものを利用します。

分周比は特定のクロック数毎にカウントを 1 上昇させる、というものです。

CS12 CS11 CS10 分周比
0 0 1 1
0 1 0 8
0 1 1 64
1 0 0 256
1 0 1 1,024

例えば分周比が 64 の場合、64 クロック毎、つまり 16MHz/64 = 250kHz でカウンターが上昇します。OCR1A の値の範囲とこの分周比により、非常に多様な周期での割り込みが出来るようになります。

実践編

では、実際に 50ms 毎(20 Hz)に割り込みを発生させてみましょう。各分周比での必要カウント数は下記のようになります。

分周比 計算式 必要カウント数
1 16MHz/(Prescaler: 1)/20Hz 800,000
8 16MHz/(Prescaler: 8)/20Hz 100,000
64 16MHz/(Prescaler: 64)/20Hz 12,500
256 16MHz/(Prescaler: 256)/20Hz 3,125
1024 16MHz/(Prescaler: 1024)/20Hz 781.25

ここで、分周比 1/8 は OCR1A の上限を超えてしまうため利用できません。一方で、1,024 も必要カウント数が小数になってしまいました。
782 カウントにすると、782*1024/16MHz=50.048ms 秒単位での割り込みとなります。これは正確な割り込みにならないため避けるべきでしょう。

従って 64 もしくは 256 を利用することが望ましいです。

実装

実装に入っていくのですが、ここでは分周比 256 を利用します。
重要な点として、「必要カウント数」には OCR1A → 0となるステップ数を含むため、値を 1 減らしておきましょう。

void setup() {
  noInterrupts(); // 割り込み出来ないように
  // 初期化
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1  = 0;

  // prescaler: 256, 必要カウント数: 3125
  OCR1A = 3124; // 16MHz / 256 / (3124 + 1) = 20 Hz
  TCCR1B |= (1 << WGM12); // CTC mode
  TCCR1B |= (1 << CS12);  // prescaler 256
  TIMSK1 |= (1 << OCIE1A); // OCR1A との比較一致割り込みを有効化

  interrupts(); // 割り込みスタート
}

まとめ

タイマー割り込みを元に、Arduino の裏側にあるマイコンの機能を直接利用する、という感覚を少し身に着けてもらえたら嬉しいです。

また、書いていて感じた本末転倒なことではございますが、結局のところデータシートを読みながら理解、実装を行う形は避けられないと感じています。この記事でも記載はしたものの、「ここまで触れると長すぎる記事になってしまう」として避けた内容が多いです。故に一部中途半端に感じられる箇所が多いと思います。
現代は生成 AI を用いてデータシート上から必要な内容をピックアップすることも出来ますので、是非データシートを読み込んでみることをオススメします。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?