読者です 読者をやめる 読者になる 読者になる

外部割り込み

 今回は外部割り込みについて。外部割り込みは、タイマ割り込みなどと違ってピンの状態によってかかる割り込みです。これを使うことにより、例えばスイッチの状態変化から、あるいは接続したモジュールから割り込みを書ける事が出来るようになります。

対象マイコン

 全部(のはず) ただし、今回確認しているのはATTiny2313だけです。

外部割り込み概要

 外部割り込みの種類は大まかにINTとPCINTの2種類があります。INTはレジスタによって、割り込みの起こる条件を4種類設定できるのに対し、PCINTでは1種類しかありません。またINTピンは二つくらいしか用意されれませんが、PCINTはたくさん*1用意されているようです。ピンの配置はデータシートを確認しましょう。他にINTピンだとスタンバイモードのAVRを復帰させることが出来ます。


 外部割り込みに登場するレジスタを紹介しましょう。


使用しそうなレジスタ一覧

レジスタ description
MCUCR INTの割り込みの方法を設定(ATTiny2313等)
GIMSK 外部割り込みマスクレジスタ(ATTiny2313等)
GIFR 外部割り込みフラグレジスタ(ATTiny2313等)
EICRA INT割り込みコントロールレジスタ(ATMega168等)
EIMSK INT割り込みマスクレジスタ(ATMega168等)
EIFR ITN割り込みフラグレジスタ(ATMega168等)
PCICR PCINT割り込みコントロールレジスタ(ATMega168等)
PCIFR PCINTフラグレジスタ(ATMega168等)
PCMSK PCINTピンのマスクレジスタ


 今回登場するレジスタは一見多いように見えますが、Tiny系とMega系でレジスタが若干変わってるので合わせて列挙しただけです。Mega系ではINTとPCINTのレジスタ自体が分かれてます。


 INT、PCINTの割り込みの使い方はほとんど一緒で

  • 割り込み方法の設定(INTの場合)
  • 割り込みに使うピンのマスク(PCINTの場合)
  • 割り込みコントロールレジスタから割り込み許可
  • 割り込みハンドラ登録

などが必要でしょう。もちろんsei()も必要となります。

INT

 INTピンは大抵はINT0、INT1の二つ用意されています。機能は一緒で、それぞれ設定するbitが異なってるくらいです。割り込みの起こる条件はMCUCRあるいはEICRAから設定します。


MCUCR(ATTiny2313等)

bit 7 6 5 4 3 2 1 0
MCUCR PUD SM1 SE SM0 ISC11 ICS10 ISC01 ISC00


EICRA(ATMega168等)

bit 7 6 5 4 3 2 1 0
EICRA - - - - ISC11 ICS10 ISC01 ISC00


ここで設定するのはISCビットで、INT1はISC11:0から、INT0はISC01:0からそれぞれ設定することになります。


Interrupt Sence Control(ISC)

ISCn1 ISCn0 description
0 0 INTピンのLレベルで割り込み
0 1 INTピンの論理変化で割り込み
1 0 INTピンの立ち下がりで割り込み
1 1 INTピンの立ち上がりで割り込み


 AVRのスタンバイモードからの復帰で使いたい場合はLレベル割り込み、つまりISCn1:0 = 0とする必要があります。


 次にマスクレジスタです。


GIMSK(ATTiny2313等)

bit 7 6 5 4 3 2 1 0
GIMSK INT1 INT0 PCIE0 PCIE2 PCIE1 - - -


EIMSK(ATMega168等)

bit 7 6 5 4 3 2 1 0
EIMSK - - - - - - INT1 INT0


 ここで使うビットはINT1とINT0のみです。それぞれセットするとせっとした名前のピンの割り込み有効になります。


 最後にピンと割り込みハンドラ名の対応関係です。


割り込みハンドラ名

ピン ハンドラ名
INT0 INT0_vect
INT1 INT1_vect

PCINT

 PCINTはピンの論理変化割り込みしかサポートしていませんが、数が多いのが特徴です。割り込みの方法を設定するレジスタはありません。


PCINT位置(ATTiny2313等)

ピン ポート PCIE PCMSK
PCINT7..0 PORTB PCIE0 PCMSK0
PCINT10..8 PORTA PCIE2 PCMSK1
PCINT17..11 PORTD PCIE1 PCMSK2


PCINT位置(ATTiny2313等)

ピン ポート PCIE PCMSK
PCINT7..0 PORTB PCIE0 PCMSK0
PCINT14..8 PORTC PCIE1 PCMSK1
PCINT23..16 PORTD PCIE2 PCMSK2


 ポート別にレジスタが割り振られてるみたいなので、ビットの操作はある程度しやすくはなってそうです。
 問題はavr-libcのバージョンによっては?PCINT7..0しかビット名が割り振られてない場合があることです。その場合は使えるピンはPCINT7..0だけになるか、あるいは頑張って使えるようにするかです。ビットの名前も最後の数字がとれてるので注意です。



割り込みを許可するレジスタはGIMSKあるいはPCICRです。


GIMSK(ATTiny2313等)

bit 7 6 5 4 3 2 1 0
GIMSK INT1 INT0 PCIE0 PCIE2 PCIE1 - - -


PCICR(ATMega168等)

bit 7 6 5 4 3 2 1 0
PCICR - - - - - PCIE2 PCIE1 PCIE0


 使用するビットはPCIE2:0の3つです。PCMSKはそれぞれ使いたいピンに対応するビットをセットします。

 PCINTでは割り込みハンドラ名はPCINT_vectになっています。

サンプルコード

 スイッチ関係を扱うとどうしてもチャタリングが問題になってくるのでチャタリングの心配がいらないようなプログラムにしておきました。


INT(Dフリップフロップっぽいの)

/* ATTiny2313 */                                                                                                                
#include <avr/io.h>
#include <avr/interrupt.h>

ISR(INT0_vect)
{
  /* PD5の状態をPD6に読み込む */
  if((~PIND & (1<<PD5)) == (1<<PD5)) {
    PORTD |=  (1<<PD6);
  } else {
    PORTD &= ~(1<<PD6);
  }
}

int main(void)
{
  /* PD6をL出力、その他をプルアップ有り入力設定 */
  DDRD  =  (1<<PD6);
  PORTD = ~(1<<PD6);


  MCUCR = (1<<ISC01); // INT0立ち下がり
  GIMSK = (1<<INT0);  // INT0の割り込み許可

  sei();
  while(1);
  return 0;
}


PCINT(リセット優先RSフリップフロップっぽいの)

/* ATTiny2313 */
#include <avr/io.h>
#include <avr/interrupt.h>

ISR(PCINT_vect)
{
  /*  
   * PCINT0 でセット、PNINT1でリセット
   * PB0 <=> PCINT0;  PB1 <=> PCINT1
   */

  if((~PINB & (1<<PB0)) == (1<<PB0)) PORTD |=  (1<<PD6);
  if((~PINB & (1<<PB1)) == (1<<PB1)) PORTD &= ~(1<<PD6);
}

int main(void)
{
  /* PD6をL出力設定 */
  DDRD  =  (1<<PD6);
  PORTD = ~(1<<PD6);

  /* PORTBをプルアップ有り入力に設定 */
  DDRB  = 0x00;
  PORTB = 0xFF;

  GIMSK = (1<<PCIE); // PCINT7:0の割り込み許可
  PCMSK = (1<<PCINT0)|(1<<PCINT1); // PCINT0, PCINT1をマスク

  sei();
  while(1);
  return 0;
}

詳しくは

ATTiny2313データシート http://www.atmel.com/dyn/resources/prod_documents/doc8246.pdf

*1:電源用のピン以外すべて?