attachInterrupt()

概要

attachInterrupt()は、外部割り込みが発生したときに呼び出す関数を設定します。attachInterrupt()のリファレンスはこちらを参照してください。

ATmega328Pの外部割り込みは、INT0とINT1、PCINT0~PCINT23のどれかで起動されます。Arduinoでは、INT0(デジタルピン2番)とINT1(デジタルピン3番)の外部割り込みを利用できます。

割り込みの制御

INT0とINT1の外部割り込みは、外部割り込み関連のレジスタ(EICRAとEIMSK)を操作することで実現します。

EICRA

EICRA(External Interrupt Control Register A、外部割り込み制御レジスタA)は、割り込みの検出方法を制御します。

EICRA
ビット76543210
名称----ISC11ISC10ISC01ISC00

ISC11とISC10の組み合わせで、INT1(デジタルピン3)の割り込み条件を、ISC01とISC00の組み合わせでINT0(デジタルピン2)の割り込み条件を設定します。

ISC11/ISC01ISC10/ISC00割り込み発生条件
00LOWのとき
01値が変化したとき
10HIGHからLOWになるとき
11LOWからHIGHになるとき

EIMSK

EIMSK(External Interrupt Mask Register)は、外部割り込みの許可・禁止を制御します。

EIMSK
ビット76543210
名称------INT1INT0

それぞれ1のときに、INT1/INT0の割り込みを許可します(かつ状態レジスタのIビットが1になっている必要があります)。

ソースコード

attachInterrupt()は、hardware/arduino/avr/cores/arduino/WInterrupts.c に定義されています。以下に全ソースコードを示します。Arduino Unoの場合に適用される部分だけを抜粋しています。実際には#if によりさまざまなチップに対応しています。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
static void nothing(void) {
}
 
static volatile voidFuncPtr intFunc[EXTERNAL_NUM_INTERRUPTS] = {
   nothing,
   nothing,
};
 
void attachInterrupt(uint8_t interruptNum, void (*userFunc)(void), int mode) {
  if(interruptNum < EXTERNAL_NUM_INTERRUPTS) {
    intFunc[interruptNum] = userFunc;
     
    // Configure the interrupt mode (trigger on low input, any change, rising
    // edge, or falling edge).  The mode constants were chosen to correspond
    // to the configuration bits in the hardware register, so we simply shift
    // the mode into place.
       
    // Enable the interrupt.
       
    switch (interruptNum) {
    case 0:
      EICRA = (EICRA & ~((1 << ISC00) | (1 << ISC01))) | (mode << ISC00);
      EIMSK |= (1 << INT0);
       break;
    case 1:
      EICRA = (EICRA & ~((1 << ISC10) | (1 << ISC11))) | (mode << ISC10);
      EIMSK |= (1 << INT1);
      break;   
    case 2:
      break;
    }
  }
}

intFunc[]は、外部割り込みが発生したときに起動する関数を代入しておく配列です。intFuc[]の型のvoidFuncPtrとEXTERNAL_NUM_INTERRUPTSは、hardware/arduino/cores/arduino/wiring_private.hに以下のように定義されています。

1
2
#define EXTERNAL_NUM_INTERRUPTS 2
typedef void (*voidFuncPtr)(void);

voidFuncPtrは、voidを返す、引数のない関数へのポインタ型です。EXTERNAL_NUM_INTERRUPTSが2なので、関数を2個定義することができます。初期値として、何もしないnothing()という関数が代入されています。

入力は、interruptNumとuserFuc、modeで、それぞれ、uint8_t、void(*)(void)、int型です。出力はありません。

 9
10
void attachInterrupt(uint8_t interruptNum, void (*userFunc)(void), int mode) {
  if(interruptNum < EXTERNAL_NUM_INTERRUPTS) {

割り込み番号(interruptNum)が、EXTERNAL_NUM_INTERRUPTS未満のとき、それ以降を実行します。

引数で指定した関数をintFunc[]に格納します。割り込みハンドラの中から指定した関数が呼び出されます。

11
intFunc[interruptNum] = userFunc;

割り込み番号に対応したレジスタの値を設定します。

11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
    // Configure the interrupt mode (trigger on low input, any change, rising
    // edge, or falling edge).  The mode constants were chosen to correspond
    // to the configuration bits in the hardware register, so we simply shift
    // the mode into place.
       
    // Enable the interrupt.
       
    switch (interruptNum) {
    case 0:
      EICRA = (EICRA & ~((1 << ISC00) | (1 << ISC01))) | (mode << ISC00);
      EIMSK |= (1 << INT0);
       break;
    case 1:
      EICRA = (EICRA & ~((1 << ISC10) | (1 << ISC11))) | (mode << ISC10);
      EIMSK |= (1 << INT1);
      break;   
    case 2:
      break;
    }
  }
}

論理演算が見づらいので、少しずつ解剖していきます。

22
(1 << ISC00) | (1 << ISC01)

ISC00とISC01は、それぞれ、0と1です。よって、1 « ISC00は1(0b00000001)、1«ISC01は2(0b00000010)です。

両者の論理和をとると、0b00000011(=3)です。

22
(EICRA & ~((1 << ISC00) | (1 << ISC01)))

次に、EICRAと~((1 « ISC00) | (1 « ISC01)))の論理積をとっています。((1 « ISC00) | (1 « ISC01)))は、0b00000011なので、その否定である、~((1 « ISC00) | (1 « ISC01)))は、0b11111100となります。

EICRAとこの0b11111100の論理積をとっているので、EICRAの下位2ビットをクリアするということになります。

mode « ISC00との論理和をとります。ISC00は0なので、modeとの論理和と同値です。

22
(EICRA & ~((1 << ISC00) | (1 << ISC01))) | (mode << ISC00)

modeとして指定するのは、LOWあるいはCHANGE、RISING、FALLINGのどれかで、それぞれ、0、1、2、3です。

これにより、EICRAを設定します。

modeの値は検査していないので、変な値を指定すると、INT1の設定を変えてしまう可能性があります。

INT0_vect、INT1_vectは、それぞれ、INT0、INT1が発生したときに起動される割り込みハンドラです。

1
2
3
4
5
6
7
#define IMPLEMENT_ISR(vect, interrupt) \
  ISR(vect) { \
    intFunc[interrupt](); \
  }
 
IMPLEMENT_ISR(INT0_vect, EXTERNAL_INT_0)
IMPLEMENT_ISR(INT1_vect, EXTERNAL_INT_1)

ISR()は、割り込みハンドラを定義するためのマクロです。割り込みハンドラでは、intFunc[]に登録した関数を呼び出します。

バージョン

Arduino AVR Boards 1.8.6

最終更新日

March 21, 2023

inserted by FC2 system