第11回 変数の記憶域期間

スイッチを検知してLEDを短い時間点灯させるプログラムをうまく動くようにします。

第10回から第12回までの内容は、少し難しくなります。第13回以降は別の内容になりますので、すべて理解できなくても大丈夫です。ご自身で理解できるところまでチャレンジしてみましょう!

目次

プログラムが動かない原因

前回の記事で作成したプログラムは、アルゴリズムとしては問題ないはずなのに、、、うまく動作しませんでした。何が原因でうまく動かなかったのでしょうか。

今回の記事では、先に原因を説明します。その原因を言葉で説明するとわかりづらいので、まずは以下のプログラムの動作がどうなるか予想してみましょう。

以下のプログラムは、何度も繰り返すloop()関数の中でcountという変数を毎回1プラスし、その内容をシリアルモニタに表示する、というものです。

シリアルモニタを使用しますので、以下の手順でプログラムの動作を確認してみてください。

  1. Arduino IDEで新規ファイルを作成
  2. 以下のプログラムをコピペ
  3. Arduino MicroをUSBケーブルで接続
  4. シリアルモニタを表示
  5. Arduino Microにプログラムを送信
/*
 *   変数の振る舞いについて確認するプログラム
 */

void setup() {
  
  // シリアルモニタ設定
  Serial.begin(9600);

  // シリアルモニタ待ち
  while(!Serial){
  }

}


void loop() {

  // 「count」という変数を宣言する
  uint8_t count;

  // countの数字を1プラスする
  count++;

  // countの内容をシリアルモニタに表示する
  Serial.println(count);

  // 1秒待つ
  delay(1000);
  
}

loop()関数の中では、変数countを++演算子で1ずつ数が増えるように書いたつもりです。でも結果は以下のようになったのではないでしょうか。数字が1でないケースがあるかもしれませんが、その数字は増えないことに注目してください。

1
1
1
1
1

なぜこのようになるのか、次にその理由を説明します。

変数の寿命

話は基礎編パート1の第27回までさかのぼります。その記事の内容は「変数のスコープ」というものでした。

かなり前の話ですし、この話以降は特に変数のスコープについてそれほど意識してきませんでしたので、内容はすっかり忘れてしまっているかもしれません。そこで、もう一度「変数のスコープ」の意味を確認しましょう。

変数は宣言する位置によって使える範囲が決まっていました。

例えば、以下のようにloop()関数の始めの方で変数を宣言した場合は「変数を宣言した直後から、その変数が属している『 { 』と『 } 』の範囲内の『 } 』まで」でした。。。ってさっぱりわからん!状態ですよね。

実際のプログラムを見て確認しましょう。以下のプログラムで変数countはloop()関数の始めで宣言しています。このcountを利用できるのは、この変数が属する「 { 」〜「 } 」の最後の『 } 』までです。つまり、loop()関数の最後の「 } 」までです。このように変数には使用できる範囲(スコープ)があるので、その範囲のことを「変数のスコープ」と呼ばれることを習得しました。

変数のスコープ1

ところで、この「変数のスコープ」に関して、もう一つ重要な性質があります。

このシリーズでは変数をメモ用紙に例えて説明してきました。例えばuint8_t count;と宣言すると、countという0〜255まで書くことのできるメモ帳が用意される、というイメージでしたよね。

変数のイメージ

ところで、このメモ帳には寿命があるんです。

このメモ帳はuint8_t count;と宣言したときに用意されました。でもせっかく用意されたこのメモ帳、なんと変数のスコープ最後の「 } 」までくると捨てられてしまうんです!

変数のスコープ2

つまり最初のプログラムでcountをどんどん1増やしていくはずだったのに、ずっと1が続いたのは、以下のプログラムの4行目で用意された変数が、7行目でプラス1したにもかかわらず、最後の「 } 」で捨てられてしまっていたためなんです。

せっかくcountを1増やしても、最後の「 } 」で変数が捨てられてしまったので、シリアルモニタには「1」ばかりが表示されていたわけです。

void loop() {

  // 「count」という変数を宣言する
  uint8_t count;

  // countの数字を1プラスする
  count++;

  // countの内容をシリアルモニタに表示する
  Serial.println(count);

  // 1秒待つ
  delay(1000);
  
}

このように変数には、スコープに対応した寿命があるんです。この変数の寿命のことを正式には「変数の記憶域期間」と呼びます。なんだかすごく難しい専門用語って感じですね。さすがに難しすぎる印象があるせいか、私の周りではこの言葉を聞くことがありません。単に「変数のスコープ」と呼ばれることが多いです。

グローバル変数の寿命

今まで説明したように「 { 」と「 } 」で囲まれた中で宣言した変数は、いずれ「 } 」に出会うので寿命があることがわかりました。

ところでグローバル変数はどのぐらいの寿命があるのでしょうか。グローバル変数は「 { 」と「 } 」の範囲外で宣言されますので、どこのブロックにも属していません。

このグローバル変数の寿命は、プログラム開始からプログラム終了までです。Arduino Microの場合、電源を入れるとプログラムが動作を開始して、電源を切るまでずっとプログラムが動作します(loop関数が繰り返されます)。つまり、グローバル変数は、Arduino Microの電源を入れてから電源を切るまでになります。

変数のスコープ3

前回のプログラムがうまく動作しなかったのは、次回のスイッチ状態チェック用に保存しておいたzenkaiの変数が、そのスコープの最後、つまりloop()関数の最後で捨てられてしまっていたためです。

変数のスコープ4

正しく動作するプログラムにする

それではどのように解決すればよいのでしょうか。

原因は変数zenkaiが捨てられてしまうことにありましたので、loop()関数の処理が終わっても捨てられない変数を用意する必要があります。つまり変数zenkaiをグローバル変数にすればよいわけです。

ということで、変数zenkaiをグローバル変数に変更しましょう。

ただ、一点注意があります。基礎編パート1の第24回「変数の型と宣言」で説明しましたが、例えばuint8_t zenkai;と変数を宣言すると、適当な数字が書かれたメモ帳が用意されてしまいます。変数zenkaiをグローバル変数として宣言すると、以下のように適当な数字の状態で最初の条件判断が行われてしまいます。

変数のスコープ5

そこで、変数を宣言するときに数字を指定することができるようになっています。数字を指定して変数を宣言するには、以下のよう「=」記号で数字を指定すればOKです。

uint8_t count = 10;

このように書くと、10という数字が代入された変数が用意されます。つまり10と書かれたメモ帳が用意されるわけです。

それでは、変数zenkaiはどのような数字の状態で用意してもらえばよいでしょうか。スイッチ状態はArduino Microの電源投入時、OFFですよね。ということで、以下のように変数zenkaiをグローバル変数で宣言することにします。

uint8_t zenkai = OFF;

「OFF」は#defineで1に定義していますので、実際には1が代入された変数zenkaiが用意されることになります。

これまでのことをまとめると、スイッチを押したときにLEDがピカっと光るプログラムは以下のようになります。

/*
 *  スイッチ制御プログラム改良版
 *  内容: スイッチを押すとピカッとLEDを光らせる
 */

// 青色LEDのピン接続番号
#define LED_BLUE  12

// 左側スイッチのピン接続番号
#define SWITCH_HIDARI  23

// スイッチの状態
#define OFF 1
#define ON  0

// 点灯時間(単位:ms)
#define TENTOU_JIKAN 50

// 前回のスイッチの状態を保存しておく変数
uint8_t zenkai = OFF;

void setup() {
  // LEDのピンを出力に設定
  pinMode(LED_BLUE, OUTPUT);

  // スイッチのピンを入力に設定
  pinMode(SWITCH_HIDARI, INPUT_PULLUP);

  // 最初はLEDをOFFに設定しておく
  digitalWrite(LED_BLUE, LOW);
}


void loop() {

  uint8_t konkai;

  // 左スイッチ状態を読み取る
  konkai = digitalRead(SWITCH_HIDARI);

  // スイッチ状態が、前回OFF、今回ONの時、スイッチが押されたと判断する
  if( (zenkai == OFF) && (konkai == ON) ) {
    // LEDを点灯
    digitalWrite(LED_BLUE, HIGH);
    // 一定時間待つ
    delay(TENTOU_JIKAN);
    // LEDを消す
    digitalWrite(LED_BLUE, LOW);
  }

  // 次回の処理のために、今回の値を前回の値として保存
  zenkai = konkai;
  
}

動作確認

それでは、上のプログラムをArduino Microに送って動作確認してみてください。

スイッチを押した瞬間、LEDがピカっと光ると思います。このようにスイッチが押されたことを検知できれば、うまくキーボードが作れそうです。

今回はスイッチが押されたら、LEDがピカっと1回光るプログラムにしました。キーボードの場合、スイッチが押されたら特定の文字データを1回だけPCに送れば正しい動作ができそうです。

ミニチャレンジ課題

今回もミニチャレンジ課題に挑戦してみましょう。

ミニチャレンジ課題

作成したプログラムは、スイッチを押した瞬間にLEDが50ms短く1回だけ点灯します。
スイッチを押したタイミングではなく、スイッチを離したタイミングでLEDが50msピカッと光るプログラムを作ってみてください。

更新履歴

日付 内容
2021.9.11 新規投稿
通知の設定
通知タイミング
guest
0 コメント
本文中にフィードバック
全てのコメントを見る
目次