thkanaのアイコン画像
thkana 2022年04月21日作成 (2022年04月29日更新) © CC BY 4+
セットアップや使用方法 セットアップや使用方法 Lチカ Lチカ 閲覧数 2507
thkana 2022年04月21日作成 (2022年04月29日更新) © CC BY 4+ セットアップや使用方法 セットアップや使用方法 Lチカ Lチカ 閲覧数 2507

[Arduino] マルチタスクのまねごと(2:LED点滅中にスイッチ操作をしたい)

前回:まずはLED2個の点滅から

スイッチが効かない!

前回の前フリで、「スイッチの操作ができない」と言っている場合の一つの例として

// Program 1.1
const int SW_OFF = 7;
const int LED = 13;

void setup() {
    pinMode(SW_OFF, INPUT_PULLUP);
    pinMode(LED, OUTPUT);
}

int run = 1; //点滅動作しているかどうかを示す変数

void loop() {
    if (run != 0) {
        digitalWrite(LED, HIGH);
        delay(1000);
        digitalWrite(LED, LOW);
        delay(1000);
    }
    if (digitalRead(SW_OFF) == LOW) {
        run = 0;
    }
}

接続は図のようとして。
接続図
(説明を簡略化するため、点滅を停止する方だけの話とします)
というのを挙げてみました。一切スイッチは効かないわけじゃないです。押し続けていれば(スイッチはタクトスイッチ/押し釦スイッチとして)いつか効く、という感じ。でも、チョンと押すだけの場合、よほどタイミングを狙わないと効いてくれません。LEDが消灯から点灯に移る一瞬だけしかスイッチを調べていないからです。

切り刻もう

さて、ここで前回やったことを思い出しましょう。
2つのLEDで、一つをN秒周期で点滅、もう一つをM秒周期で点滅したわけです。では、一つのLEDを2秒周期で点滅するとして、もう一つのLEDをスイッチに置き換えて、点滅操作の代わりにスイッチの検査を100ミリ秒(1/10秒)毎に行ったら? スイッチは多分すぐに反応してくれるはず。では、前回のProgram 2.5を書き換えてみましょう。

// program 2.1
const int SW_OFF = 7;
const int LED = 13;

void setup() {
    pinMode(SW_OFF, INPUT_PULLUP);
    pinMode(LED, OUTPUT);
}

int count = 0;
int run = 1;

void loop() {
    // LEDの制御
    if (run != 0) {
        if (count % 20 == 0) {        // 2秒毎
            digitalWrite(LED, HIGH);  // LEDを点灯する
        }
        if (count % 20 == 10) {      // 2秒毎+1秒
            digitalWrite(LED, LOW);  // LEDを消灯する
        }
    }
    // スイッチの読み取り
    // 100ミリ秒毎、つまり毎回なので回数制御は不要
    if (digitalRead(SW_OFF) == LOW) {
        digitalWrite(LED, LOW);
        run = 0;
    }
    // 全体周期の管理
    count++;
    if (count == 20) {  // 全体のワクは2秒
        count = 0;
    }
    delay(100);
}

本当に素早く押したときは止まらないかも知れませんが、「しっかり押す」ぐらいの感じで点滅が止まるようになったと思います。loop()の最後のdelay()の引数を小さくして、その分点滅周期のcountの値を調整すればもっと「感度」を高くすることもできるでしょう。

脱線1(プログラムの整理)

今回のプログラム、確かに問題なく動くのですが、ちょっと気になるんです。digitalWrite(LED, xx)というのが3回登場していますね。経験上、複雑なプログラムのあちこちで一つのペリフェラル(GPIOなど外部につながる装置)を操作していると、あっちの操作とこっちの操作が矛盾や妙な干渉を起こして、例えば意図せず一瞬LEDが光るとか、点灯のはずなのに光らないとか、そういうことが起こってくるんですよ。
なので、LEDの状態を代表する変数を一つ設定して、実際にLEDを操作するのは一箇所にしてみます。
マルチタスクっぽいことと直接には関係ありませんが、でも気をつけておくとハマらずに済むことがあるかも。

// Program 3.1
// setup()まではprogram 2.1から変更ないので省略

int count = 0;
int run = 1;
int ledStat; //LEDの状態を示す変数を追加

void loop() {
    // LEDの制御
    if (count % 20 == 0) {  // 2秒毎
        ledStat = HIGH;
    }
    if (count % 20 == 10) {  // 2秒毎+1秒
        ledStat = LOW;
    }
    // スイッチの読み取り
    // 100ミリ秒毎、つまり毎回なので回数制御は不要
    if (digitalRead(SW_OFF) == LOW) {
        run = 0;
    }
    // 全体周期の管理
    count++;
    if (count == 20) {  // 全体のワクは2秒
        count = 0;
    }
    if (run == 0) {
        ledStat = LOW;  //強制消灯
    }
    digitalWrite(LED, ledStat);
    delay(100);
}

脱線2(止めたらやっぱり再開したいでしょ)

点滅の再開、したくなりますよね、普通。では、やりましょう。
まず簡単な方法。再開ボタンを設けます。

// program 4.1
const int SW_OFF = 7;
const int SW_ON = 8;
const int LED = 13;
void setup() {
    pinMode(SW_OFF, INPUT_PULLUP);
    pinMode(SW_ON, INPUT_PULLUP);
    pinMode(LED, OUTPUT);
}

int count = 0;
int run = 1;
int ledStat;
void loop() {
    // LEDの制御
    if (count % 20 == 0) {  // 2秒毎
        ledStat = HIGH;
    }
    if (count % 20 == 10) {  // 2秒毎+1秒
        ledStat = LOW;
    }
    // スイッチの読み取り
    // 100ミリ秒毎、つまり毎回なので回数制御は不要
    if (digitalRead(SW_OFF) == LOW) {
        run = 0;
    }
    if (digitalRead(SW_ON) == LOW) {
        run = 1;
        count = 20; //次回点灯から始まるように
    }

    // 全体周期の管理
    count++;
    if (count >= 20) {  // 全体のワクは2秒
        count = 0;
    }
    if (run == 0) {
        ledStat = LOW;  //強制消灯
    }
    digitalWrite(LED, ledStat);
    delay(100);
}

全体周期の管理のところのif文カッコ内の式をちょっと変えました。この方が「再開」のときにcountをいくつにするか頭を使わずに、とにかく大きな値を放り込むと次回が点灯になるので。

でも...やっぱりアレですよね、スイッチを一回押すと消灯、同じスイッチをまた押すと点灯にしたいですよね。このとき問題になるのが、「スイッチを押したまま」にしたときの動作。検出を現状のような「スイッチが押されている」ことでやっていると、一つのスイッチが押されたままだと毎回動作が反転することになってしまいます。そういうときには、「スイッチが押された」ことを検出しなければいけません。
スイッチが「押されている」と「押された」ってなにが違うのでしょう? 「押された」は、その前までスイッチが押されていなかった、いまは押されている、ということです。
プログラム上で考えると、「スイッチが押されていなかった」ことは「直前のスイッチの状態」を変数に取っておいて、「押された」条件の判断に組み入れてやることになります。

// program 4.2
const int SW = 7;
const int LED = 13;
int lastSw = HIGH;  //直前のスイッチ状態を保存する変数
void setup() {
    pinMode(SW, INPUT_PULLUP);
    pinMode(LED, OUTPUT);
    lastSw = digitalRead(SW);  //起動時のスイッチ状態を保存
}

int count = 0;
int run = 1;
int ledStat;
void loop() {
    // LEDの制御
    if (count % 20 == 0) {  // 2秒毎
        ledStat = HIGH;
    }
    if (count % 20 == 10) {  // 2秒毎+1秒
        ledStat = LOW;
    }
    // スイッチの読み取り
    int nowSw = digitalRead(SW); //いまのスイッチ状態
    if (lastSw == HIGH && nowSw == LOW) {  //直前は押されていなかった、いまは押されている
        if (run != 0) {
            run = 0;
        } else {
            run = 1;
          	count = 20;
        }
    }
  	lastSw=nowSw; //「いまのスイッチ状態」は、次回には「直前の状態」になる

    // 全体周期の管理
    count++;
    if (count >= 20) {  // 全体のワクは2秒
        count = 0;
    }
    if (run == 0) {
        ledStat = LOW;  //強制消灯
    }
    digitalWrite(LED, ledStat);
    delay(100);
}

なんだか脱線の方がボリュームがあったような...
次回は、時間の測り方のバリエーションなどやってみましょうか。

thkanaのアイコン画像
一応組み込みエンジニアのつもり... でも最近製品になるようなコード書いてないなぁ。
  • thkana さんが 2022/04/21 に 編集 をしました。 (メッセージ: 初版)
  • thkana さんが 2022/04/29 に 編集 をしました。 (メッセージ: 次回へのリンク追加)
ログインしてコメントを投稿する