Arduinoで外部割り込み(ピン変化割り込み)をライブラリを使わないで行う方法です。以前Timer割込みを使うためにいろいろ調べていたついでにピン変化割込みについても調べたのでその内容をまとめておきます。
概要
Timerによる時間割込みはその名の通り、ある決められた時間間隔でプログラムに割込み処理を発生させます。
対して、外部割込み(ピン変化割込み)とは割り当てられたピンがHIGH⇒LOWもしくはLOW⇒HIGHなどと変化したときに割込み処理を発生させるというものです。
例えばボタンが押されたらLEDを点灯するといったソースを考えてみます。
ボタンのON/OFFをメインループ内で確認する場合、いつボタンが押されるのか分からない状態を定期的(周期的)にチェックするため、どうしてもボタンが押されてから反応するまでにタイムラグが発生してしまいます。はやい周期でチェックできれば問題ありませんが、いろいろ処理が増えてくるとだんだん無理が生じてきます。
そこでピン変化割込みを利用します。ボタンが押されたことをピン変化で感知し、変化した瞬間に割込み処理が発生するため、押された瞬間に処理を行うことができます。
でArduinoIDEにはやはり便利な関数 attachInterrupt()が準備されていて、この関数使用すれば簡単に外部割込みを使用することができます。しかしこの関数、ピン割り当てが2pin/3pinの2つしかできません(UNOの場合)。
レジスタを直接操作すればもう少し柔軟にピン割り当てができるので今回はIDE関数(ライブラリ)を使わない方法で外部割込みを行います。
外部割込みの設定
外部割込みの方法と、ピン変化割込みの大きく二つがあります。
外部割込みは attachInterrupt()関数 を使った方法で使用されてます。また割込みタイミング(変化、立上がり、立下り)を指定して割込み処理を発生させることができます。のでこちらの方法で行うならば attachInterrupt()関数 使えば良いかと思います。ただArduinoでいうD2、D3の2ピンにしか割り当てられません。
ピン変化割込みはほとんどのピンに割り当てることが可能です。ですが割込みのタイミングを設定するこはできず、ピンロジックが変化したら割込みが発生します。 レジスタを直接いじる必要がありますが、IDE関数では通常設定できないピンにも割り当てることができるので少し幅が広がります。
外部割込み(INT0、INT1)
割込みを発生させるピンを許可にして、割込み発生のタイミングを指定すれば完了です。複数ピンの割込みに比べて高速に処理できるようです。
外部割込み許可(有効化)レジスタ
有効化はEIMSKレジスタ(External Interrupt Mask Register)で行います。
bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|
EIMSK | - | - | - | - | - | - | INT1 | INT0 |
arduino対応pin | - | - | - | - | - | - | D3 | D2 |
有効化は下位2bitで行います。対応するbitを「1」にすると各々のピン割込みが有効になります。
割込み制御レジスタ
割込みのタイミングの設定はEICRAレジスタ(External Interrupt Control Register A)で行います。割込みタイミングをピン変化、立上がり、立下りなどで発生するよう設定できます。
bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|
EICRA | - | - | - | - | ISC11 | ISC10 | ISC01 | ISC00 |
ISC11/10でINT1(D3)をISC01/00でINT0(D2)を設定します。
ISC*1 | ISC*0 | 割込み発生条件(タイミング) |
0 | 0 | Lowレベルで発生 |
0 | 1 | 論理変化で発生 |
1 | 0 | ピンレベルの下降端で発生 |
1 | 1 | ピンレベルの上昇端で発生 |
それぞれのピンで発生条件を指定できます。
必要最低限の設定は以上です。ただ先ほども少し触れましたが単独ピンの割込みはIDE関数 attachInterrupt() が準備されているため特に拘りがなければそちら使えば良いかと思います。
ピン変化割込み(PCINT)
もう一つの割込みは複数ピンをグループ毎にして割込みを処理するものです。Arduinoのほとんどのピンに割り当てることができますが、割込み処理の細かいタイミングは指定できません。ピン変化にのみ対応します。IDE関数では通常設定できないピンにも割り当てることができるので少し幅が広がります。
複数ピンの割込み許可レジスタ
8ピン毎に3つのグループに分けられていてグループ毎に割込み許可の設定を行います。まずグループ毎(8ピン)をまとめて許可、その後さらにピン毎に個別に許可する感じです。
各グループはそのままポート毎に分けられてます。
PCIE0
ポート | PB7 | PB6 | PB5 | PB4 | PB3 | PB2 | PB1 | PB0 |
Arduinoピン | – | – | D13 | D12 | D11 | D10 | D9 | D8 |
PCIE1
ポート | PC7 | PC6 | PC5 | PC4 | PC3 | PC2 | PC1 | PC0 |
Arduinoピン | – | RESET | A5 | A4 | A3 | A2 | A1 | A0 |
PCIE2
ポート | PD7 | PD6 | PD5 | PD4 | PD3 | PD2 | PD1 | PD0 |
Arduinoピン | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
でまずこのグループ毎に割込みを許可します。割込み許可はPCICRレジスタ(Pin Change Interrupt Control Register)で行います。
bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|
PCICR | - | - | - | - | - | PCIE2 | PCIE1 | PCIE0 |
許可は下位3bitで行います。対応するbitを「1」にすると各々のピングループの割込みが有効になります。 例えばPCIE0を許可する場合、0bit目を「1」にします。
PCICR |= B00000001;
とかもしくは、
PCICR |= (1 << PCIE0);
こんな感じで記述します。後者をちょくちょく見かけますが、わからなければ前者の記述でも問題ありません。これで許可した複数ピンの何れかが変化したときに割込み処理が発生します。
さらに個別ピン毎に割込み許可を行うにはPCMSKレジスタ(Pin Change Enable Mask)で行います。
bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|
PCMSK2 | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
PCMSK1 | – | RESET | A5 | A4 | A3 | A2 | A1 | A0 |
PCMSK0 | – | – | D13 | D12 | D11 | D10 | D9 | D8 |
対応するI/OピンをArduinoピンで表してます。レジスタは先ほどと同じでポート毎になってます。
例えばPCIE0で「D8」ピンを個別に許可する場合は、
PCMSK0 |= B00000001;
こんな感じで対応するレジスタの対応するbitを「1」にします。もしくは
PCMSK0 |= (1 << PCINT0);
こんな記述でもO.Kです。対応するbit名など詳細はデータシートを確認して下さい。
最低限必要な事前設定は以上です。これで許可したピンが変化したときに割込みが発生するようになります。
割込み時に呼ばれる関数
割込みが発生したときに呼ばれる関数(ISR)です。
シンボル | 発生元 |
---|---|
INT0_vect | D2ピンの外部割込みが発生 |
INT1_vect | D3ピンの外部割込みが発生 |
PCINT0_vect | PCIE0のピン変化が発生 |
PCINT1_vect | PCIE1のピン変化が発生 |
PCINT2_vect | PCIE2のピン変化が発生 |
例えばD8ピン(PCINT0群)のピン変化割込みが発生したときは
ISR(PCINT0_vect) { //ここに処理を記述 }
こんな感じの関数が呼び出されます。
基本的にはIDEの関数使用していれば特に問題ないと思いますが、D2、D3ピン以外で外部割込み使いたい場合とかはこんな感じでレジスタ操作する必要があります。
コメント
大変わかりやすかったです。ありがとうございます!