第9回

割り込み

割り込みとは,内外の何らかのイベントに応じてその時点で実行している処理を中断し,別の処理へと移すことです.
イベントはハードウェアによって検知されるのでユーザがやるべきことは検知するかどうかの設定のみです.
これにより,「何かが起きたら」という形の処理を行うことができます.

例えば,今まで一定時間後に何らかの動作をさせたいとしたら_delay_ms()を用いて待つということで実現してきました.
しかし,待っている間は何もしません,何かを平行して処理したいとか,ある時間がたった後に動作を切り替えるとか,
そういったことを単に待つだけで実行することは困難です.
そういったときに便利なのがタイマー割り込みです.割り込みの元にタイマーを用います.
前回はタイマーにピンの出力を連動させてPWM波形を生成するというタイマーの使い方として応用的なことをしましたが,
今回は単純にカウントダウンタイマー(より正確にはカウントアップ)として使用します.
例として下のプログラムを挙げます.これは一定時間ごとにPORTDの出力を反転するというものです.

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

// コンペアマッチA割込が発生したときの処理を行います。
ISR(TIMER1_COMPA_vect){
    PORTD = PORTD^0b11111111;       // PD7~PD0を反転した値をPORTDに出力します。
}

int main(void){

    // ポートD初期設定
    // bit  : 76543210  
    DDRD  = 0b11111111;              // PD7~PD4は出力、PD3~PD0は入力に設定します。
    PORTD = 0b00000000;             // PD7~PD0に'L'を出力します。

    // タイマーの初期化
    TCCR1A = 0b00000000;         // タイマ1をCTCモードに設定
    TCCR1B = 0b00001010;         // タイマ1をCTCモードに設定、8分周
    TIMSK1 = 0b00000010;         // コンペアマッチA一致で割込を許可
    OCR1A = 62499;                  // コンペアマッチAレジスタに62499(0xf423)を設定

    sei();                  // 全体の割込を許可
        
    while(1);				// 何もせずに止まる
}

main文中には各種の初期設定が記述されているだけですね.
具体的にPORTDの出力を反転するという文はISR(...){...}の中にあります.
これが割り込みです.ISRとはInterrupt Service Routine,まさしく割り込みのことです.
ISRに続く()の中にベクタ(割り込みが発動するイベント)を指定し,{}に実行する処理を記述します.
タイマー割り込みでは,タイマーが
  • オーバーフロー,OVF_vect
  • コンペアマッチA(比較一致A),COMPA_vect
  • コンペアマッチB(比較一致B),COMPB_vect
  • キャプチャ(捕獲),CAPT_vect
したタイミングをそれぞれ検知することができます.
また,このほかにレジスタにどの割り込みを許可するかの設定を書き込まなければいけません.
タイマー割り込みはTIMSKレジスタにより管理されます.
7(bit) 6 5 4 3 2 1 0
TIMSK 0 0 0 0
①…キャプチャ割り込みを許可します.CAPTピンでの論理変化を読み取ったり,カウントがICRレジスタ値に達したりした時に実行されます.
②…コンペアマッチB割り込みを許可します.カウント値がOCRnBレジスタ値に達した時に実行されます.
③…コンペアマッチA割り込みを許可します.カウント値がOCRnAレジスタ値に達した時に実行されます.
④…オーバーフロー割り込みを許可します.カウント値が上限に達した時に実行されます.
また,文頭に#include <avr/interrupt.h>という見慣れぬヘッダファイルがインクルードされています.
この中では割り込み関連の各種マクロが定義されており,割り込みを使用するためにはこれが必要です.
main文内にsei()という関数も見られます.これは全割り込みを許可するマクロです.
一方で全割り込みを禁止するcli()マクロも用意されています.

具体的に割り込みをするためには
1.#include <avr/interrupt.h> を記述します.これにより割り込み関連のマクロが使用可能になります.
2.必要に応じてレジスタの設定をします.
3.sei()を記述し割り込みを許可します.
といった手順を踏むことになります.

下のプログラムはタイマー割り込みを応用してPWM機能のないピンPC5でPWM動作をしつつ,
PC0に接続されたスイッチの状態を読みそれに応じてPB0に接続されたLEDを光らせるというものです.

 #include <avr/io.h>
 #define F_CPU 1000000
 #include <avr/interrupt.h>//割り込み用(初登場!)
 #include <util/delay.h>
 
 //LED
 #define ON_LED  (PORTC |= (1 << 5))//PC5点灯
 #define OFF_LED (PORTC &= ~(1 << 5))//PC5消灯
 
 //スイッチ
 #define SWITCH  !(PINC&(1 << 0))//PC0の状態
 
 //関数
 void port_init(void);
 void timer1_init(void);
 
 //割り込み(初登場!)
 ISR(TIMER1_OVF_vect)
 {
 	ON_LED;
 }
 ISR(TIMER1_COMPA_vect)
 {
 	OFF_LED;
 }
   
 int main(void)
 {
 	port_init();
 	timer1_init();
 	
 	sei();//全体の割り込み許可(初登場!)
 	
     while(1)
     {
         if (SWITCH)//スイッチが押されたら(PINC0が0になったら)
         {
 			PORTB |= (1 << 0);//PB0点灯
 			_delay_ms(20);
         }
 		else
 		{
 			PORTB &= ~(1 << 0);//PB0消灯
 			_delay_ms(20);
 		}
     }
 }
 
 void port_init(void)
 {
 	DDRB  = 0b11111111;//全て出力
 	PORTB = 0b00000000;//全てlow
 	DDRC   = 0b00100000;//PC5出力,他入力
 	PORTC  = 0b01011111;//内部プルアップ有効,PC5Low
 }
 void timer1_init(void)
 {
 	TCCR1A = 0b00000011;//標準ポート動作,高速PWM動作
 	TCCR1B = 0b00010011;//64分周
 	TIMSK1 = 0b00000011;//比較一致A,溢れ割り込み許可(初登場!)
 	ICR1   = 59999;//1周期60000*64/1000000=3.84s
 	OCR1A  = 29999;//Duty50%
 }

タイマー以外にも各種の割り込みが用意されており,時に受信完了割り込みやピン変化割り込みなどがよくつかわれます.
書式は同様ですのでデータシートを参考に使ってみてください.

課題

任意です.暇つぶしや腕試しにどうぞ.
1.PC0,1,2にそれぞれLEDをつなぎ,それぞれ異なった周期1秒,2秒,3秒で点滅させる.各LEDの同期は問わない.周期は大体でよい.
ヒント:atmega88にはタイマーが3つ内蔵されており,またそれぞれ独立して割り込みを持つことを利用する.
2.PB0にLEDを,PC0にスイッチをつなぐ.通常LEDは2秒周期で点滅しているが,スイッチを押したら即時点灯し,また押している間は点灯し続ける.
離したらまた点滅動作を再開する.
最終更新:2017年09月19日 07:13