💡

ArduinoでTimer割込みを使う

2024/05/06に公開

Arduinoとは

Arduinoとはワンボードマイコンの一種です。
マイコンとGPIOピンやUARTのインターフェイスが搭載されています。
電子工作に使うボードです。
なぜ、Arduinoに興味を持ったかというと仕事で使おうと思ったからです。日系メーカのいいところは購買が優秀なことです。三菱みたいな大手FA企業からPLCを安く買えるんですよね。外資だとそうはいかないので、Arduinioに安価なPLCとしての役割を持たせようと思いました。

有名な電子工作ボードにラズベリーパイがありますが、ラズパイとの違いはOSの有無です。
ラズパイはLinuxが入ってます。なぜラズパイにしなかったかというとOSのリアルタイム性を信用してないからです。1ms単位でIO制御したかったのでOSなんかが入ってたらダメですね。
組み込み用のLinuxとかもありますが、ハードウェア分野のエンジニアなのでOSはあんまりわかりません。ということで、Arduinoにしました。

タイマー割込みとは

Arduinoに搭載されているマイコンにはタイマー割込み機能があります。
Arduinoのマイコンは16MHzのクロックが入力されています。
クロックをカウントしているレジスターがあり、レジスターがある一定の値になると割込み信号を出力します。

以下の画像が例です。のこぎり波の頂点で割込み信号が出力されます。
タイマー1は16bitカウンタなので65535になった地点です。

なぜタイマー割込みを使うのか

よくあるLチカのコードを以下に示します。

void loop(){
    digitalWrite(LED_BUILTIN, LOW);
    delay(1000);
    digitalWrite(LED_BUILTIN, HIGH);
    delay(1000);
}

単にLEDを光らせるだけなら問題ないのですが、組み込み機器でこのようなコードは書きません。
なぜならdelayの間、CPUは単に待っているだけで他の処理ができないからです。

実際の組み込み機器では決められた時間になったときに割込み処理を実行し、割込みがない時は他の処理を実行します。

100us毎にタイマー割込みを発生させる方法

Arduinoマイコンのタイマーレジスターを設定します。
割込みの出し方もいろいろありますが、今回はCTCモードを使います。
CTCモードはOCRAレジスタに設定された値に達したら割込み信号を出力するというものです。

Arduinoはタイマーが3つありますが、今回はタイマー1を使います。
タイマー0はDelay()とかの時間管理にも使われているので、タイマー0の設定を変えると、ほかの関数に影響が出ます。他の関数に影響がないタイマー1を使うのが無難です。

bit数 対応している関数
Timer0 8 delay(), millis(), micros()など
Timer1 16
Timer2 8 tone()

以下のコードをsetup()内に記述します。

TCCR1A = 0; // タイマー1動作モードを初期化
TCCR1B = (1 << WGM12) | (1 << CS10); // CTCモード 分周比1
OCR1A = 1600 - 1; // 100μsとなるように設定
TIMSK1 = (1 << OCIE1A); // タイマー1のAマッチ割り込みを許可

割込み処理は以下のように記述します。
今回は13ピンの信号をON/OFFしたいと思います。変数led_stsは関数外部で定義したグローバル変数です。

ISR(TIMER1_COMPA_vect) {
    if (led_sts) {
      digitalWrite(LED_BUILTIN, LOW);
      led_sts = 0;
    } 
    else {
      digitalWrite(LED_BUILTIN, HIGH);
      led_sts = 1;
    }
}

細かい解説

レジスタの細かい解説をします。
TCCR1Aレジスタの各bitは以下のような意味を持っています。

TCCR1Bレジスタの各bitは以下のような意味を持っています。



今回はWGM12を1、CS10が1、他が0なのでCTCモードで分周比は1になります。

OCRA1で先ほどののこぎり波の頂点を変更します。
今回は100usなので1600 - 1です。クロック周波数が16MHzなので。

TIMSK1は割込み許可レジスタになります。

今回はOCIE1Aだけを1にします。

ちなみにWGM12,CS10はコード内で宣言しなくてもそのまま使えます。
普通のC言語は定義しないとエラーが出ますが、ArduinoIDEのいいところですね。

ISRは割込みサービスルーチン(Interrupt Service Routine)の略です。

ISR(割込みイベントを書く){
    // 割込み処理を書く
}

といった構造になります。上の例ではTIMER1_COMPA_vectが割込みイベントでLチカ制御部分が割込み処理内容になります。

応用例

ソフト的に疑似タイマーを用意し、割込みルーチンが呼び出されるためにデクリメントします。
疑似タイマーが0になったらLチカ処理を実行します。疑似タイマーを複数用意すれば、ハード割込みは一つでソフト的にタイマー割込みをいくつも使えるようになります。タイマーの精度は落ちますが、IO操作だけなら実用的には十分です。
ハード割込みを一つにするのは多重割込みを防ぐためです。
優先順位を考えたりいろいろ面倒だからです。

1ms間隔でLチカさせる例を示します。

void setup() {
  pinMode(13, OUTPUT);
  digitalWrite(LED_BUILTIN, LOW);

  TCCR1A = 0; // タイマー1の動作モードを正常モードに設定
  TCCR1B = (1 << WGM12) | (1 << CS10); // CTCモードで1周を設定
  OCR1A = 1600 - 1; // 100μsとなるように設定
  TIMSK1 = (1 << OCIE1A); // タイマー1のAマッチ割り込みを許可
 timer = 0;
  led_sts = 0;
}

ISR(TIMER1_COMPA_vect) {
  if (timer > 0) {
    timer--;
    } 
}

Void loop(){
  if (timer == 0) {
    cycle_time = os_time - cycle_start;
    LED();
    timer = 9;
  }
}

void LED(){
  if (led_sts) {
    digitalWrite(LED_BUILTIN, LOW);
    led_sts = 0;
  } 
  else {
    digitalWrite(LED_BUILTIN, HIGH);
    led_sts = 1;
  }
}

timer = 9のところを変えれば、Lチカ制御周期を変更できます。
変数にしておけば、シリアル通信で変数を書き換えて、パソコンから制御周期を変更可能とかもできます。

Arduino A000066 Uno Rev3 ATmega328 アルドゥイノ マイコンボード 送料無料
価格:4,680円
楽天で購入

Discussion