ATmega88以降は全てのI/Oピンに「外部入力割り込み」が付いている。これは、それぞれのピンに電流がONになったり、OFFになったりするとイベントが起こるという事だ。

今まではmain()の中のループで各ピンをずっと監視し続けなければならなかったが、この外部入力ピンを使うと完全にイベントドリブンでプログラムを書ける様になる。ちなみにArduinoからは使えない機能です。

ATmega168 SIG_PIN_CHANGE0ATmega168 SIG_PIN_CHANGE0

■作った

PB1につながったスイッチを押した時に割り込み(SIG_PIN_CHANGE0)が発生し、UARTでPCに通知する。PB0のLEDも光らせる。スイッチを離すとLEDを消し、UARTで通知する。

SourceCode, hex, Makefile(avr-gcc 3.4.6)

■外部入力割り込みの仕組み

2箇所設定する必要がある。

1.「ピン変化割り込みレジスタ」でピン変化割り込みを許可する

2.入力割り込みを受け取るピンを「ピン変化割り込みマスクレジスタ」で指定する

ピン変化割り込み0,1,2(PCIE0,1,2)の3つがあり、それぞれPORTB,PORTC,PORTDに対応している。

つまり、割り込み1つで7~8個のピンを受け持つという所がINT0,INT1の外部入力割り込みと違う。

ATmega88のデータシートの41ページを見るとPCICRというレジスタのPCIE0ビットに1をセットしてやるとピン変化割り込み0が許可される事がわかる。

sbi(PCICR,PCIE0); // ピン変化割り込み0



さらに、ピン変化割り込みマスクレジスタで、PCINT1(PB1)だけに反応する様に指定する。

PCMSK0 = (1<<PCINT1); // PCINT1のみ許可

PCINT0,1,2,3を許可する時はこうする

PCMSK0 = (1<<PCINT0)|(1<<PCINT1)|(1<<PCINT2)|(1<<PCINT3);

あとはmain()の中でsei()して、main()の後ろに外部割り込み0を受け取る関数 SIGNAL(SIG_PIN_CHANGE0) を宣言すれば割り込みが受け取れる。

SIG_PIN_CHANGEの中で実際どのピンが変化したのか判定しないとならないのがめんどいな…まあ変数で覚えておけばいいんだが

/**外部割り込み0**/
SIGNAL(SIG_PIN_CHANGE0){
  if(bit_is_set(PINB, PB1)){ // PB1立ち上がりの時
    LED_SET(); // LED点灯
    usart_sendStr("PB1_HIGH¥r¥n");
  }
  else{
    LED_CLR(); // LED消灯
    usart_sendStr("PB1_LOW¥r¥n");
  }
}

ピン変化をシリアル通信で受信したスクリーンショット

ATmega168 SIG_PIN_CHANGE0

ちなみにSIGNAL()の中で指定する割り込みベクタ名は、データシートの33ページ「ATmega48/88/168の割り込みベクタ」の表のベクタ番号と、C:\WinAVR\avr\include\avr\iomx8.hの中で_VECTOR(番号)で#defineされている名前の組み合わせを見るとわかる。

今回のプログラムですよ


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

#define TRUE 1
#define FALSE 0
#define NULL ‘¥0’
#define sbi(BYTE,BIT) BYTE|=_BV(BIT) // BYTEの指定BITに1をセット
#define cbi(BYTE,BIT) BYTE&=~_BV(BIT) // BYTEの指定BITをクリア

/** 動作設定 **/
#define FOSC 8000000 // 8MHz

/** UART設定 **/
#define BAUD 9600 // 9600bps
#define MYUBRR FOSC/16/BAUD-1 // UART分周率
// #define UCSR0A_U2X0 1 // 倍速フラグ 等速ならコメントアウト
#ifdef UCSR0A_U2X0 // 倍速が定義されているならば
 #define MYUBRR FOSC/16/(BAUD/2)-1 // UART分周率(倍速)
#endif
volatile char usart_recvData; // USARTで受信したデータ

#define LED_SET() sbi(PORTB, PB0) // 基盤上の動作確認LED
#define LED_CLR() cbi(PORTB, PB0)


/* PORT設定 */
void port_init(void){
  sbi(DDRB, PB0);
}


/* USART設定 */
void usart_init(unsigned int ubrr){
  UBRR0H = (unsigned char)(ubrr>>8); // ボーレート上位8bit
  UBRR0L = (unsigned char)ubrr; // ボーレート下位8bit
  UCSR0A = (0<<U2X0); // 等速
  UCSR0B = (1<<RXEN0)|(1<<TXEN0)|(0<<RXCIE0); // 送受信許可、受信完了割り込み不可
  UCSR0C = (0<<UMSEL00)|(3<<UCSZ00)|(1<<USBS0)|(0<<UPM00);
  // フレーム設定 非同期通信 8ビット 1ストップビット パリティ無し
}


/* UARTで文字列送信 */
void usart_sendStr(char *str){
  while(*str != NULL){
    loop_until_bit_is_set(UCSR0A,UDRE0); // 送信データレジスタ空きまで待機
    UDR0 = *str++; // 1文字送信、1文字進む
  }
}

/* ピン変化割り込み設定 */
void pinchange_init(void){
  sbi(PCICR, PCIE0); // ピン変化割り込み0許可
  PCMSK0 = (1<<PCINT1); // PCINT1のみ許可
  
  // sbi(PCICR, PCIE1); // ピン変化割り込み1許可
  // PCMSK1 = (1<<PCINT8)|(1<<PCINT14);
  
  // sbi(PCICR, PCIE2); // ピン変化割り込み2許可
  // PCMSK2 = (1<<PCINT16)|(PCINT23);
}


int main(void){
  port_init(); // PORT設定
  usart_init(MYUBRR); // USART設定
  pinchange_init(); // ピン変化割り込み設定
  sei(); // 全割り込み許可
  
  LED_SET(); // 起動確認LED
    
  for(;;){
  }
}


/** 外部割り込み0 **/
SIGNAL(SIG_PIN_CHANGE0){
  if(bit_is_set(PINB, PB1)){ // PB1立ち上がりの時
    LED_SET(); // LED点灯
    usart_sendStr("PB1_HIGH¥r¥n");
  }
  else{
    LED_CLR(); // LED消灯
    usart_sendStr("PB1_LOW¥r¥n");
  }
}


ロータリーエンコーダもタイマじゃなくピン変化割り込みでできそうだな。