スイッチを使ってタイマーを制御――「運動しなさい! 電光掲示板」がついに完成!!アイデア・ハック!! Arduinoで遊ぼう(9)(2/2 ページ)

» 2014年03月06日 10時00分 公開
[三月兎MONOist]
前のページへ 1|2       

統合開発環境「Arduino IDE」でスケッチ(プログラム)を作成

 では、Arduinoの統合開発環境「Arduino IDE」で、スケッチ(プログラム)を作成していきましょう(スケッチ1スケッチ2スケッチ3)。


#include "pitches.h"
 
//-----------------------
// 定数の定義
//-----------------------
const int BZ = 13;                // ブザー(TMP36)の出力ピン
 
const int SW_COUNT = 3;           // スイッチの数
const int SW[SW_COUNT] = {3,4,5}; // 各スイッチの入力ピン番号
// スイッチ0〜2の定義
const int SW_UP = 0;
const int SW_START = 1;
const int SW_DOWN = 2;
 
// スイッチの回路は負論理なので、ONがLOWになる
const int SW_ON = LOW;
const int SW_OFF = HIGH;
 
= LCDの定義 略 =
 
// プログラムのモード
const int MODE_SW_INPUT = 0;                // スイッチ入力モード
const int MODE_TIMER_COUNT = 1;             // タイマーカウントモード
// タイマーの最大/最小
const int TIME_MAX = 60;
const int TIME_MIN = 1;
// 1分間(ミリ秒)
const unsigned long ONE_MINUITE = 60L * 1000L;
// 終了メッセージ
const int MESSAGE_COUNT = 3;               // メッセージの数
char MESSAGE[3][16] = {"Stretch!",
                       "Walking!",
                       "Stepper!"};
// LCDブリンクON/OFF
const int BLINK_ON = 1;
const int BLINK_OFF = 0;
// LCDブリンク時間
const unsigned long BLINK_ON_TIME = 700L;
const unsigned long BLINK_OFF_TIME = 300L;
//-----------------------
// グローバル変数の定義
//-----------------------
int G_mode = MODE_SW_INPUT;                          // プログラムのモード
int G_old_val[SW_COUNT] = {SW_OFF, SW_OFF, SW_OFF};  // 前回のスイッチの値
int G_blink_stat = BLINK_ON;                         // ブリンク状態
unsigned long G_time = TIME_MIN;                     // タイマーの時間
unsigned long G_sw_on_time[SW_COUNT];                // スイッチを押し続けている時間
unsigned long G_start_time;                          // スタートボタンを押してからの時間
unsigned long G_blink_time;                          // ブリンク時間
 
スケッチ1 「運動しなさい! 電光掲示板 ver.1.1」のスケッチ。定数の定義

 1〜17行目までは、ブザーとスイッチの設定です。スイッチ制御の基本については、連載第2回「親子で楽しむ夏休み自由研究――2つのLEDで『アニメシアター』を作ろう!」を参照してください。

 今回、新しいのは22、23行目です。Arduinoのプログラムは、setup()を1回実行した後は、loop()の実行を繰り返します。今回のプログラムでは、

  • ユーザーがタイマーをセット中
  • カウンダウンを実行中

の2つの状態があります。loop()内でどちらの状態にあるのかを判断するためにプログラムのモードを用意しました。

 いつも同じメッセージが流れては飽きるので、仕様をバージョンアップして、終了メッセージをランダム表示できるようにしました(29〜33行目)。

 タイマーの点滅は、点灯と消灯が同じ長さではキレイに見えませんでした。そこで点滅時間を“7:3”の割合にしています(34〜39行目)。

 新しいモジュールは、スイッチで時間を入力するためのinput_time()です。

int input_time() {
    int int_swNumber = -1;          // 何も押してないときの番号を設定
    int sw_val;
    // スイッチ1〜3の状態を取得
    for(int i = 0; i < SW_COUNT; i++) {
        sw_val = digitalRead(SW[i]);      // スイッチの状態を読み取る
		// スイッチを押した瞬間
        if(sw_val == SW_ON && G_old_val[i] == SW_OFF) {
            int_swNumber = i;             // スイッチの番号を取得
            G_sw_on_time[i] = millis();	  // 押した瞬間の時間を取得
        } 
		// スイッチを押し続けているとき
        if(sw_val == SW_ON && G_old_val[i] == SW_ON) {
            if(millis() - G_sw_on_time[i] > 500L) {
                int_swNumber = i;
            }
        } 
        G_old_val[i] = sw_val;            // スイッチの値を保存 
    }
    // スイッチの入力があったとき
    switch(int_swNumber) {
      case SW_UP:
          if(G_time < TIME_MAX) {
              G_time++;
          }
          break;
      case SW_DOWN:
          if(G_time > TIME_MIN) {
              G_time--;
          }
          break;
      case SW_START:
          break;
    }
    return int_swNumber;
}
スケッチ2 input_time()は、スイッチで時間を入力するモジュール

 5行目からのfor文で、どのスイッチが押されているのか、または何も押されていないのかを判定します。このときに“押された瞬間なのか(7行目)”、または“押され続けているのか(12行目)”を判定しています。0.5秒以上押され続けているときは“長押し”していると判断します。

 スイッチの入力があった際、「SW_UP」が押されていたらタイマーをインクリメント、「SW_DOWN」が押下されていたらデクリメントします(20行目〜)。

 前回作ったdisp_time()を改造し、タイマーの数字を点滅させるようにしました。ブザーのスケッチは連載第4回「ハラハラドキドキ『うさぎさん危機一髪ゲーム』を作ろう!」のモジュールを流用しています。本稿末尾にスケッチのダウンロードリンクを掲載しています。変更箇所を確認してみてください。

void loop() {
    int sw_no;
    unsigned long current_time;
    // プログラムのモードによって処理を振り分ける
    switch(G_mode) {
        case MODE_SW_INPUT:
            // スイッチ入力モード
            sw_no = input_time();
            switch(sw_no) {
                case SW_UP:
                case SW_DOWN:
                    // スタートメッセージ領域をクリア
                    disp_start_message(" ");
                    // メッセージ領域をクリア
                    disp_message(" ");
                    break;
                case SW_START:
                    if(G_time < TIME_MIN) {
                        G_time = TIME_MIN;
                    }
                    G_start_time = millis();
                    G_blink_time = G_start_time;
                    // スタートメッセージを表示
                    disp_start_message("Start");
                    // メッセージ領域をクリア
                    disp_message(" ");
                    // タイマーカウントモードにする
                    G_mode = MODE_TIMER_COUNT;
                    break;
            }
            G_blink_stat = BLINK_ON;
            break;
        case MODE_TIMER_COUNT:
            // タイマーカウントモード
            current_time = millis();
            if(current_time - G_start_time >= ONE_MINUITE) {
                // 1分経過
                G_time--;
                if(G_time <= 0) {
                    // 終了メッセージを表示
                    disp_start_message("End");
                    // メッセージをランダムに表示
                    int int_rand = random(MESSAGE_COUNT);
                    disp_message(MESSAGE[int_rand]);
                    // ブリンクを停止
                    G_blink_stat = BLINK_OFF;
                    // ブザーを鳴らす
                    bz_sounding();
                    // スイッチ入力モードにする
                    G_mode = MODE_SW_INPUT;
                }
                G_start_time = current_time;
                G_blink_time = G_start_time;
                G_blink_stat = BLINK_ON;
            }
            // 実行中と分かるように時間をブリンクさせる
            if(G_blink_stat == BLINK_ON) {
                if(current_time - G_blink_time >= BLINK_ON_TIME) {
                    G_blink_stat = BLINK_OFF;
                    G_blink_time = current_time;
                }
            }
            else {
                if(current_time - G_blink_time >= BLINK_OFF_TIME) {
                    G_blink_stat = BLINK_ON;
                    G_blink_time = current_time;
                }
            }
            break;
    }
    // 時間を表示する(ブリンクあり)
    disp_time(G_time, G_blink_stat);
}
スケッチ3 スイッチ入力モードと、タイマーカウントモードに分けて処理している

 loop()は、前述のように「スイッチ入力モード(5〜32行目)」と「タイマーカウントモード(33〜69行目)」の処理に別れます。

 「G_mode」の初期値は「MODE_SW_INPUT」なので、最初は必ずスイッチ入力モードから開始します。先に用意したinput_time()で、どのスイッチが押されているのかを判断します。

 押されたスイッチが「SW_UP」か「SW_DOWN」のときは、ディスプレイのスタートメッセージ領域とメッセージ領域に表示されている「End」や「Walking!」の文字列を削除します。これはシステムを連続使用する際、前回のメッセージが残ったままになっているのがイヤだからです。

 「SW_START」が押されたときは、millis()でプログラムがスタートしてからの時間を取得し、「G_start_time」に格納します(21行目)。この開始時刻が、カウントダウンや点滅の制御に使用されます。スタートメッセージを表示してから、「G_mode」をタイマーカウントモードに変更します。

 タイマーカウントモードに入ったら、millis()でプログラムがスタートしてからの時間を取得して、「current_time」に保存します(35行目)。「current_time」と「G_start_time」を比較し、1分経過ごとにタイマー表示をデクリメントしていきます(38行目)。

 残り時間が「0」になったときの処理に、メッセージのランダム表示機能(43行目)、タイマーの点滅終了(46行目)、ブザー鳴動(48行目)を追加しました。次のスタートに備えて、「G_mode」をスイッチ入力モードに置き換えます(50行目)。

 次の1分をカウントするために、グローバル変数を書き換えておきます(52〜54行目)。

 カウントモードのときは、実行中であることを示すためにタイマーを点滅しています(57〜68行目)。

 Arduinoを初めて扱ったとき、LEDを点滅するプログラムを触って「Arduinoってカンタンなんだ!」と感動しました。ところが、タイマー点滅は、見た目は同じ点滅なのですが、LED点滅のサンプルプログラムのようにはいきませんでした……。LED点滅サンプルのように、delay()を使ってディスプレイの表示状態を保持していたら、その間はタイマーも止まってしまいます。それでは困ります。そこで、「G_start_time」と「G_blink_time」の差で点滅時間を制御しています(57〜68行目)。

 今回のスケッチは、(1)ユーザーがスイッチで時間設定している間の処理とカウントダウン処理を分けること、(2)millis()を使ってタイマーカウントしながらLEDを点滅させることの2つがポイントになりました。

 本稿では、前回のスケッチと連載第4回で作ったスケッチを流用しています。本記事と併せて読んでいただくと、より理解が深まります。

>>スケッチ(mono09.zip)のダウンロードはこちら



 スイッチもブザーも単体で扱うのはカンタンだったのに、組み合わせて使うときは処理の流れを理解してプログラムを組まなければならないので、途端に難易度がアップしました。悩みながらプログラミングすると、思い通りに動いたときの喜びがとても大きいです。

 さて次回は、「感圧センサー」を使って遊んでみたいと思います。お楽しみに!! (次回に続く

⇒連載バックナンバーはこちら

前のページへ 1|2       

Copyright © ITmedia, Inc. All Rights Reserved.