🐥

【電子工作】割り込み処理(ハードウェア割り込み)

2022/12/18に公開

割り込み処理

大きく分けて、ハードウェア割込みとソフトウェア割込みがあります。

ハードウェア割り込みとは、GPIOピンの信号値が変化したときに割込みサービスルーチン(ISR)を実行するような割り込みで、簡単に言えばスイッチが押された時に特定の動作を実行します。

ソフトウェア割り込みとは、タイマー割込みとウォッチドックタイマ割込みのことで、一定時間毎に実行するような動作のことです

ハードウェア割り込み

micropython(pymakr + RP2で動作確認)

コールバック関数にあたるirq_callback(関数名は何でも良いです)は引数にPinオブジェクトを取ります。
※ 引数を指定しないとエラーが出るので、必ず渡しましょう

from machine import Pin

irq_count = 0

# 割り込み発生時にコールされる。
def irq_callback(pin):
    global irq_count
    print(pin) # Pin(27, mode=IN, pull=PULL_UP)
    irq_count += 1

# pin27をinput設定
btn = Pin(27, Pin.IN, Pin.PULL_UP)

# pin27に対し、立ち上がりに検出時irq_callback()がコールされる
btn.irq(trigger=Pin.IRQ_RISING, handler=irq_callback)

C/C++ ① Arduino UNO、ESP32

attachInterrupt(int,func,mode)を使います。
※ RP2はattachInterruptが使えないため、書き方が全く異なります!RP2の書き方は②を参照してください

サンプルコード
const int led = 25;
const int speakerPin = 9;
 
void setup() {
  pinMode(2, INPUT_PULLUP);
  pinMode(7, OUTPUT);
  pinMode(9, OUTPUT);
  // 割り込み関数の設定
  attachInterrupt(0, speaker, FALLING);
}
 
void loop() {
  digitalWrite(ledPin, HIGH);
  delay(500);
  digitalWrite(ledPin, LOW);
  delay(500);
}
 
void speaker() {
  tone(speakerPin, 255, 1000);
}

解説

attachInterrupt(int,func,mode)
  • int:割り込みさせるピン番号。
     UNOは0(D2ピン)or1(D3ピン)、esp32はどのピンも割り込み設定できます。

  • func:実行させる関数。ISR(割り込みサービスルーチン)とも呼ばれる

  • mode:割り込みが発生するトリガーを指定。
      LOW:LOWのとき発生
      CHANGE:状態が変化したときに発生
      RISING:LOWからHIGHに変わったときに発生
      FALLING:HIGHからLOWに変わったときに発生

参考
http://7ujm.net/micro/arduino_int.html
https://novicengineering.com/arduinoの割り込み機能を使ってみる/
https://lastminuteengineers.com/handling-esp32-gpio-interrupts-tutorial/

C/C++ ② platformio + RP2

gpio_set_irq_enabled_with_callback(15, 0x4u, false, callback_button);という関数で割り込みさせるコールバック関数を指定します。

指定したピンの入力で割り込みを起こさせるには、gpio_set_irq_enabled(15, 0x4u, true)で割り込みを有効化させます。

ここでは「トグルボタンを押している間はLEDを点灯させる関数を実行する」という割り込みを発生させましょう。

サンプルコード
#include <Arduino.h>

#define LED25 25
#define SWITCH 1
int count=0;

void gpio_callback(uint gpio, uint32_t events) {
    count++;
    digitalWrite(LED25,HIGH);
    Serial.print(count);
}

void setup(){
    pinMode(SWITCH,INPUT_PULLUP);
    pinMode(LED25,OUTPUT);
    Serial.begin(9600);
    gpio_set_irq_enabled_with_callback(SWITCH, GPIO_IRQ_LEVEL_LOW, true, &gpio_callback);
}

void loop(){
    digitalWrite(LED25,LOW);
}
  • GPIO_IRQ_LEVEL_LOW INPUTがLOWになっている間実行されます。プルアップ抵抗をオンにしている間は実行するようにするにはこれを使いましょう
  • GPIO_IRQ_LEVEL_HIGH
  • GPIO_IRQ_EDGE_FALL INPUTがHIGHからLOWに変化した瞬間に発火します。

複数の入力割り込み

gpio_set_irq_enabled_with_callback(15, 0x4u, false, callback_button);という書き方では、一見15番ピンの入力だけに反応してコールバック関数が呼ばれているようですが、実は15番ピンでなくてもどのピンでも反応します(なんというクソ仕様)
https://qiita.com/tom_tom_tom/items/77e2f378f9f3cb7d8449

じゃあボタンが複数あった場合の入力はどう区別するかですが、いったんボタン番号を判別するためだけのコールバック関数を作ってやり、その関数の中でボタンの判定と、どのコールバック関数を呼ぶかを指定します。

サンプルコード
#include <Arduino.h>
#include <stdio.h>

#define LED25 25
#define LED26 26
#define LED28 28
#define SWITCH1 1
#define SWITCH2 2
#define SWITCH3 3

void switch1_on_callback() {
    gpio_set_irq_enabled(SWITCH1, GPIO_IRQ_EDGE_FALL, false);
    gpio_set_irq_enabled(SWITCH2, GPIO_IRQ_EDGE_FALL, false);
    digitalWrite(LED25, LOW);
    digitalWrite(LED25, HIGH);
    for (int i=0; i<128; ++i){
      printf("a");
    }
    printf("\n");
    digitalWrite(LED25, LOW);
    gpio_set_irq_enabled(SWITCH1, GPIO_IRQ_EDGE_FALL, true);
    gpio_set_irq_enabled(SWITCH2, GPIO_IRQ_EDGE_FALL, true);
}

void switch2_on_callback() {
    gpio_set_irq_enabled(SWITCH1, GPIO_IRQ_EDGE_FALL, false);
    gpio_set_irq_enabled(SWITCH2, GPIO_IRQ_EDGE_FALL, false);
    digitalWrite(LED26, HIGH);
    for (int i=0; i<128; ++i){
      printf("b");
    }
    printf("\n");
    digitalWrite(LED26, LOW);
    gpio_set_irq_enabled(SWITCH1, GPIO_IRQ_EDGE_FALL, true);
    gpio_set_irq_enabled(SWITCH2, GPIO_IRQ_EDGE_FALL, true);
}

void callback_button(uint gpio, uint32_t events){
    if(gpio == SWITCH1 && events == GPIO_IRQ_EDGE_FALL){
      switch1_on_callback();
    }
    if(gpio == SWITCH2 && events == GPIO_IRQ_EDGE_FALL){
      switch2_on_callback();
    }
}

int main() {
    Serial.begin(9600);
    Serial.println("begin・・・");
    printf("start・・・");

    pinMode(SWITCH1,INPUT_PULLUP);
    pinMode(SWITCH2,INPUT_PULLUP);
    pinMode(SWITCH3,INPUT_PULLUP);
    pinMode(LED25,OUTPUT);
    pinMode(LED26,OUTPUT);
    pinMode(LED28,OUTPUT);

    gpio_set_irq_enabled_with_callback(SWITCH1, GPIO_IRQ_EDGE_FALL, false, callback_button);// 実はSWITCH1でなくてもあらゆるピンの入力に反応する
    gpio_set_irq_enabled(SWITCH1, GPIO_IRQ_EDGE_FALL, true);
    gpio_set_irq_enabled(SWITCH2, GPIO_IRQ_EDGE_FALL, true);

    while(true){
    }
}

上記のサンプルコードではstdlibライブラリを使っていますが、通常platformioでラズピコを立ち上げるとframeworkはArduinoとなるため、stdlibライブラリは使えません
https://github.com/Wiz-IO/wizio-pico

そのため、platformio.iniを以下のように設定し、stdlibライブラリを使えるようにします

[env:raspberry-pi-pico]
platform = https://github.com/Wiz-IO/wizio-pico.git
board = raspberry-pi-pico
framework = baremetal

board_build.bynary_type = copy_to_ram   
build_flags =                           
        -D PICO_STDIO_USB              ; enable stdio over USB  
build_unflags  = -D PICO_STDIO_UART 

upload_port = /Volumes/RPI-RP2/
monitor_speed = 9600

Discussion