Arduinoで割り込み処理

Arduinoで割り込み処理

Arduinoだって割り込み処理、できるもん。

今回は、Arduinoを使った割り込み処理についてです。
さて、今回もArduino Microを使ってのコードです。


 目次:          

 ・ スイッチ状態を読み取る

 ・ スイッチの状態変化を割り込みで受け取る 



スイッチ状態を読み取る

まずは、外部にタクトスイッチを取り付けてArduinoで受け取るプログラムを作成してみましょう。

回路図は、下記のようになります。




この回路では、電源電圧5Vを220Ωの抵抗を挟んでデジタル入力の12とスイッチに接続しています。スイッチのもう1方はGNDに接続としています。
このような接続方法を”プルアップ接続”といいます。

この接続方法では、ボタンを押さない状態ではデジタル入力ピンに電圧がかかっているため、HIGHとして認識され、ボタンを押すとGND側に電流が流れるためLOWなることでボタンが押されたことを検出できます。

実際に使うスイッチはタクトスイッチです。


ブレッドボード上ではこんな感じになります。


まずは、簡単なプログラムとしてスイッチが押された状態ならば、ボード上のLEDを点灯し、離された状態ならばLEDを消灯するプログラムを作成してみましょう。

プログラムは、下記のようになります。

スイッチの状態を検出するプログラム

const byte LED_PIN = 13;
const byte INPUT_PIN = 12;

void setup() {
  // Digital Pin設定
  pinMode(LED_PIN, OUTPUT);      // On-Board LED
  pinMode(INPUT_PIN, INPUT);

}

void loop() {

  if(digitalRead(INPUT_PIN) == LOW) {
    digitalWrite(LED_PIN, HIGH);
  }
  else {
    digitalWrite(LED_PIN, LOW);
  }
}


デジタル入出力12番を入力に設定し、ボード上のLEDを点灯するためデジタル入出力13番を出力に設定します。
  // Digital Pin設定
  pinMode(LED_PIN, OUTPUT);      // On-Board LED
  pinMode(INPUT_PIN, INPUT);

デジタル入力12番の状態は、digitalRead()で、知ることができます。
    if(digitalRead(INPUT_PIN) == LOW) {

今回の回路はプルアップなので、読んだ結果がLOWならば押されているので、digitalWrite()を使ってLEDを点灯し、そうでなければ消灯しています。

それでは、このプログラムを改造して、LED動作をスイッチでトグル操作するようにしてみましょう。
トグル動作ととは、LEDをが消えているときにスイッチを押すと点灯し、LEDが点灯しているときに押すと消える動作です。
LEDに出力する状態を ledState と言う変数にして、押されたときに状態を反転しています。

const byte LED_PIN = 13;
const byte INPUT_PIN = 12;

byte ledState = LOW;

void setup() {
  // Digital Pin設定
  pinMode(LED_PIN, OUTPUT);      // On-Board LED
  pinMode(INPUT_PIN, INPUT);

  digitalWrite(LED_PIN, ledState);
}

void loop() {

  if(digitalRead(INPUT_PIN) == LOW) {
    ledState = !ledState;
    digitalWrite(LED_PIN, ledState);
  }
}


ちょっと、動きが変だと思いませんか?
押したときに、ちゃんと動作するときもあれば、そうでないときもあります。

実は、このようなスイッチはチャタリングという現象を発生させます。
チャタリングとは、このようなスイッチのように電気的な接点を持ったデバイスなどで発生します。
 → チャタリング(Wikipediaへ)

ボタンが完全に押し込まれ、電気的な接点が物理的に安定するまでの僅かな時間に接点の振動でON/OFFを繰り返してしまい、マイコンはこのON/OFFを検出してしまうのです。

対策として、ハード的なフィルタを入れてやることで取り除くこともできますが、ココではソフトウェアでチャタリングの除去を行ってみましょう。

下記の例ではチャタリングとして、スイッチ状態が変化したとき、10ミリ秒待ってからスイッチの状態を5回確認して連続的に同じ状態だった場合にスイッチ状態が変化したとみなし、その状態がLOWだったときに、LEDのトグル動作としています。

完成したプログラム

/*!
 * Copyright <2020> <LightningBrains>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
 * associated documentation files (the "Software"), to deal in the Software without restriction,
 * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do
 * so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or substantial
 * portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
 * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
 * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

const byte LED_PIN = 13;
const byte INPUT_PIN = 12;

byte ledState = LOW;
byte swCount = 0;
byte swState;

void setup() {
  // Digital Pin設定
  pinMode(LED_PIN, OUTPUT);      // On-Board LED
  pinMode(INPUT_PIN, INPUT);

  digitalWrite(LED_PIN, ledState);   // LED初期表示はOFF
  swState = digitalRead(INPUT_PIN);  // 初期スイッチ状態読み込み
}

void loop() {
  byte pinInput = digitalRead(INPUT_PIN);    // スイッチ状態読み込み

  // スイッチ状態が変化した?
  if(pinInput != swState) {
    swCount++;                   // チャタリング防止カウントを加算
    if(swCount > 5) {            // 5回連続で状態維持
      swState = pinInput;        // スイッチ状態更新
      if(pinInput == LOW) {      // LOWならばLEDをトグル動作
        ledState = !ledState;
        digitalWrite(LED_PIN, ledState);
      }
      return;
    }
    delay(10);                   // 5回に満たない時は10ミリ待つ
  }
  else {
    swCount = 0;                 // チャタリング防止カウントリセット

  }
}



スイッチの状態変化を割り込みで受け取る

さてさて~
いよいよ、外部にスイッチの変化を割り込みで受け取るプログラムを作成してみましょう。

今回も、スイッチONの動作でLEDをトグル動作させるようにします。

割り込みでスイッチの状態変化を受け取るプログラム

/*!
 * Copyright <2020> <LightningBrains>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
 * associated documentation files (the "Software"), to deal in the Software without restriction,
 * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do
 * so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or substantial
 * portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
 * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
 * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

const byte LED_PIN = 13;
const byte INPUT_PIN = 7;

byte ledState = LOW;
byte interruptState = 0;
unsigned long msTime;

void setup() {
  // Digital Pin設定
  pinMode(LED_PIN, OUTPUT);      // On-Board LED
  pinMode(INPUT_PIN, INPUT);

  digitalWrite(LED_PIN, ledState);   // LED初期表示はOFF

  // 割り込み設定:入力ピン、割り込みハンドラ、割り込み条件はHIGH→LOW
  attachInterrupt(digitalPinToInterrupt(INPUT_PIN), switchON, FALLING);
  msTime = millis();
}

void loop() {

  if(interruptState) {              // 割り込み発生
    if((millis() - msTime) > 50) {  // 50ミリ秒以下の割り込みは無視する
      ledState = !ledState;         // LEDをトグル動作
      digitalWrite(LED_PIN, ledState);
      msTime = millis();            // 割り込み時刻更新

    }
    interruptState = 0;    // 割り込みフラグOFF
  }
}

// 割り込みハンドラ
//   IMPUT_PIN HIGH→LOW
//
void switchON() {

  // 割り込みフラグ設定
  interruptState = 1;
}


割り込みの設定は、

  // 割り込み設定:入力ピン、割り込みハンドラ、割り込み条件はHIGH→LOW
  attachInterrupt(digitalPinToInterrupt(INPUT_PIN), switchON, FALLING);

で、行っています。
割り込みを行うピン、割り込みハンドラ、割り込み条件をそれぞれ指定します。
気をつけるのは、割り込みを行うピンが限られている、という事です。
Arduino Microの場合は、0、1、2、3、7です。ボードによっても異なるので注意してください。
割り込み条件として、今回指定しているのは"FALLING"です。これは、ピンのレベルがHIGH→LOWに変化したときに割り込みを発生させます。割り込み条件として、以下の設定が可能です。

 LOW: ピンがLOWの状態のとき割り込みが発生
 RISING: ピンの状態がLOW→HIGHに変化したときに割り込みが発生
 FALLING: ピンの状態がHIGH→LOWに変化したときに割り込みが発生
 CHANGE: ピンの状態がLOW→HIGH、HIGH→LOWどちらでも、割り込みが発生

今回のプログラムでは、スイッチを押した瞬間の割り込みを捉えたいのでFALLINGとしました。

割り込みを使わないプログラムではチャタリング対策をしました。割り込みでも同様で、スイッチが安定するまでの間に発生するHIGH→LOWの変化をすべて割り込みとして捉えてしまいます。

このため、下記のような処理となっています。

  if(interruptState) {              // 割り込み発生
    if((millis() - msTime) > 50) {  // 50ミリ秒以下の割り込みは無視する
      ledState = !ledState;         // LEDをトグル動作
      digitalWrite(LED_PIN, ledState);
      msTime = millis();            // 割り込み時刻更新
    }
    interruptState = 0;    // 割り込みフラグOFF
  }

短時間に連続して発生する割り込みはチャタリングとみなして無視しています。
割り込みを使わないプログラムでは、10ミリ秒のディレイで5回連続スイッチ状態が同じならばOKとして、でうまく行っていたので前回の割り込みから50ミリ秒以内の割り込みを無視するようにしています。

この割り込みを使った方のプログラムの方がスッキリしています。
その理由は、

・レベルの変化をFALLINGだけを捉えるので、信号の変化に対する処理を簡略化できる。
・チャタリングを単純に50ミリ以内を無視とした
・ディレイなどの処理も行わない

などの理由です。

割り込みと、ループ処理
この組み合わせをどのように扱うか?
そのあたりは設計者の力量です。
うまく割り込み処理を使って、スッキリしたプログラミングを目指しましょう!

余談ですが、チャタリングが発生しているような状況をエンジニアは、
「チャタる」とか「チャタってる」とか言います。
同様に、無視することを
「ネグる」(ネグレクトから)とか言います。





Have a Happy Hucking!!
Lightning Brains

コメント

このブログの人気の投稿

Linuxシステムコール、共有メモリの使い方

Linuxシステムコール、メッセージキューの使い方

Linuxシステムコール、セマフォの使い方