キッチンタイマーの続きデース。設定した時間が経過したら、ゲンガーの目が光るキッチンタイマーを作りますョ。
今回は、実際にプログラムを作ってみたのだ。まずは、プログラムの動きをお見せしよう。
動画で動きをお見せします!
Aボタン、Bボタンで時間を設定して、追加したボタンを押すと、カウントダウンするのだ。
カウントダウンの数字の表示がスクロールするのが、いまいちそれらしくないですね。
やはり「display.scroll()」によるスクロール表示では、無理があったのだ。
今後の改良が必要ですね!
ウム。そして、AボタンとBボタンを同時に押すと、設定した時間を0にクリアすることもできるのだ。
それらしくなってきましたね!ところで、もしかして今回使っているマイクロビットは・・・
使用したマイクロビットのバージョン
実は、今回作ったプログラムでは、マイクロビットバージョン2を使用します。
【プログラミング】マイクロビット・バージョン2を買ってみた。
え、バージョン2って、入手困難になってるやつじゃないですか!(2022年1月現在)
申し訳ない。最初のバージョンのマイクロビットで動くプログラムも、近いうちに作るので、ユルシテ~
どんなプログラムを作ったのか?
実際にプログラムの説明を行う前に、以前考えたキッチンタイマーの動きをおさらいしておこう。(先ほどのリンクをご参照ください。)
- 「分」ボタンで分を設定する。
- 「秒」ボタンで秒を設定する。
- 「スタート/ストップ」ボタンでカウントダウンを開始、中止する。
- 設定した時間(分:秒)が「00:00」になったら音が鳴る。
- 「分」ボタンと「秒」ボタンを同時に押すと、時間(分:秒)が「00:00」にクリアされる。
よく売られているキッチンタイマーを参考にしたのでしたね。
で、これを実現するために、次のような状態の動きを考えた。
飛行機のプロペラの時も同じような図が出てきましたね。
ボタンの操作で状態が変化するのは飛行機のプロペラと同じ原理なのだ。今回新しいのは、カウントダウンの状態がある、ということなのだ。
もしかして、「カウントダウンの状態」でマイクロビットの「utime」を使うのですか?
その通り!よく分かったね!
そりゃぁ、今回のお題が「utimeを使ってキッチンタイマーを作る」ということですからね(エッヘン)
追加した回路について
上の説明で、「分」ボタン、「秒」ボタンおよび「スタート/ストップ」ボタンの三つのボタンが出てきた。
でも、マイクロビットにはAボタンとBボタンの二つしかついていないですね?
そこで、マイクロビットにボタンを一つ追加したのだ。また、アラームの時に光らせるため、LEDを二つ追加したのだ。
0ピンに押しボタンスイッチ、1ピンと2ピンにLEDが追加されていましたね。
追加した押しボタンスイッチを「Cボタン」として、マイクロビットのAボタン、BボタンおよびCボタンに、以下のように役割を割り当ててプログラムを作成したのだ。
マイクロビットのボタン | 役割 |
Aボタン | 分ボタン |
Bボタン | 秒ボタン |
Cボタン(0ピンに追加した押しボタンスイッチ) | スタート/ストップボタン |
今回のプログラムは、この回路を前提としています。詳しくは前回の内容をご覧ください。(新しいタブで開きます。)
【マイクロビット応用】[8] キッチンタイマー(2)押しボタンスイッチとLEDを追加、プログラムの動きを考える、ポケプラ「ゲンガー」の製作
プログラムの内容
先ほどの図に出てきた三つの状態がボタン操作などで切り替わるのは、以前作成したプロペラのプログラムと同じだ。
マイクロビットのボタン操作でプロペラの回り方が変わったり止まったりするやつでしたね。(以下をご覧ください。新しいタブで開きます。)
【マイクロビット応用】[2] プラモの飛行機のプロペラ(1)
プログラムの全文は以下に掲載しておいたのだ。(新しいタブで開きます。)
【マイクロビット応用】[9.1]キッチンタイマーのプログラム:最初のバージョンのプログラム
いよいよプログラムの説明ですね!
ウム!ちょっと長いけど、頑張ってついてきてくれたまえ!
どのボタンが操作されたかを調べるための関数について
マイクロビット本体のAボタン、Bボタンと、追加したCボタンのうち、どれが押されたかを調べるため、「UserInput()」という関数を作成したのだ。
- _25行目 # どのボタンが押されたか調べる。
- _26行目 def UserInput():
- _27行目 if button_a.was_pressed():
- _28行目 return UserInput_CheckB()
- _29行目 if button_b.was_pressed():
- _30行目 return UserInput_CheckA()
- _31行目 if pin0.read_digital() == 0:
- _32行目 return ‘c’
- _33行目 return ‘ ‘
飛行機のプロペラのプログラムの時にも同じような関数を作りましたね。・・・あれ、Aボタン、Bボタンがされたときに、「UserInput_CheckA()」、「UserInput_CheckB()」などの関数が呼び出されますね?(28行目、30行目)
キッチンタイマーのプログラムでは、設定時間を0秒にクリアするため、AボタンとBボタンが同時に押されたかどうかを判定しないといけない。
そういえば、そういう機能もありましたね。
関数「UserInput_CheckA()」および「UserInput_CheckB()」でこの判定を行っているのだ。
- _13行目 # Aボタンが押されたとき、Bボタンが押されているかどうかを調べる。
- _14行目 def UserInput_CheckB():
- _15行目 if button_b.is_pressed():
- _16行目 return ‘w’
- _17行目 return ‘a’
- _18行目
- _19行目 # Bボタンが押されたとき、Aボタンが押されているかどうかを調べる。
- _20行目 def UserInput_CheckA():
- _21行目 if button_a.is_pressed():
- _22行目 return ‘w’
- _23行目 return ‘b’
Aボタンが押されたときは「UserInput_CheckB()」が呼び出される。そして15行目でBボタンが押されているかどうかを調べる。押されている場合は「’w’」が、押されていない場合は「’a’」が返される。
フムフム。では、Bボタンが押されたときは「UserInput_CheckA()」で、同時にAボタンが押されたかどうかを調べるのですね。
その通り!そして、AボタンとBボタンが同時に押されていれば「’w’」、押されていなければ「’b’」が返されるのだ。
ということは、AボタンとBボタンが同時に押されている場合には「’w’」が返される、というとなのですね。
ィエース。ちなみに、Cボタンの場合は「’c’」が返される。(Cボタンについては、以下をご覧ください。新しいタブで開きます。)
【マイクロビット応用】[8.1]キッチンタイマーのプログラム:追加した回路の確認用プログラム
と、いうことは、関数「UserInput()」は、押されているボタンに応じて次のような値を返すように作られているのですね。
押されたボタン | 役割 | 戻り値 |
Aボタン | 分ボタン | ’a’ |
Bボタン | 秒ボタン | ’b’ |
AボタンとBボタンが同時に押されている | 分ボタンと秒ボタンが同時に押されている。 | ’w’ |
Cボタン | スタート/ストップボタン | ’c’ |
どのボタンも押されていない | どのボタンも押されていない | ’ ’ |
設定の状態の動きをする関数について
設定の状態では、上で説明したボタンの操作に従って、カウントダウンする秒数を設定する。
何秒後にアラームが鳴るのかを設定するのですね。
そうなのだ。そして、その「何秒後」というのをマイクロビットに覚えていてもらうため、「s」というグローバル変数を用意した。
- _10行目 # s : キッチンタイマーの設定時間(秒)
- _11行目 s = 0
「s」は「sec(秒)」の「s」ですか?
イエ~ス。この「s」はグローバル変数なので、プログラム全体で有効、すなわち、プログラムのどこからでも値を見たり、変えたりすることができるのだ。
グローバル変数については、以下を見ればよいですね。(新しいタブで開きます。)
そして、グローバル変数「s」は、プログラムの動作が「設定の状態」の時に、Aボタン、Bボタンの操作によって設定される。
- _41行目 # 「設定の状態」の動き
- _42行目 def StateSetting():
- _43行目 global s
- _44行目 while True:
- _45行目 display.scroll(s)
- _46行目 data = UserInput()
- _47行目 if data == ‘a’:
- _48行目 s = s + 60
- _49行目 elif data == ‘b’:
- _50行目 s = s + 1
- _51行目 elif data == ‘w’:
- _52行目 s = 0
- _53行目 display.scroll(s)
- _54行目 ClearButtonInfo()
- _55行目 elif data == ‘c’:
- _56行目 break
- _57行目 return STATE_COUNT_DOWN
45行目で現在の設定値を表示した後、46行目でどのボタンが押されたかを調べる。押されたボタンに応じて、以下のようにグローバル変数「s」の値を設定するのだ。
ボタン | 役割 | UserInput()の戻り値 | グローバル変数「s」の値 |
Aボタン | 分ボタン | ’a’ | 60増やす。(1分増やす) |
Bボタン | 秒ボタン | ’b’ | 1増やす。(1秒増やす) |
AボタンとBボタン同時 | 分ボタンと秒ボタン同時 | ’w’ | 0とする。 |
「’w’」の場合には、54行目で「ClearButtonInfo()」という関数が呼び出されますね。これは何を行っているのですか?
- _35行目 # マイクロビットに記録されたボタンの情報をクリアする。
- _36行目 def ClearButtonInfo():
- _37行目 button_a.was_pressed()
- _38行目 button_b.was_pressed()
これを説明するため、上で説明した関数「UserInput_CheckA()」および「UserInput_CheckB()」をもう一度見てみよう。
AボタンとBボタンが同時に押されたかどうかを調べるやつでしたね。
- _13行目 # Aボタンが押されたとき、Bボタンが押されているかどうかを調べる。
- _14行目 def UserInput_CheckB():
- _15行目 if button_b.is_pressed():
- _16行目 return ‘w’
- _17行目 return ‘a’
この中で、「is_pressed()」を使っているのだが、「is_pressed()」では、「was_pressed()」の戻り値に関係する「以前ボタンが押されたかどうか」の記録がFalseに戻らないようなのだ。(「is_pressed()」と「was_pressed()」の違いについては、以下をご覧ください。新しいタブで開きます。)
すると、どうなるのですか?
AボタンとBボタンの同時押しで時間が0となった後、「ボタンが押された」と判定されて設定値が0から増えてしまったのだ。
なんてこった!
これを回避するため、AボタンとBボタンが同時に押された後、改めて「ボタンが押された」と判定されないようにするため、「was_pressed()」を呼び出して、ボタンが押されたかどうかの記録を「押されていない」に戻しておくようにしたのだ。
それで、AボタンとBボタンの両方で「was_pressed()」を呼び出すのですね。
- _35行目 # マイクロビットに記録されたボタンの情報をクリアする。
- _36行目 def ClearButtonInfo():
- _37行目 button_a.was_pressed()
- _38行目 button_b.was_pressed()
Cボタンの説明がないのですが・・・
Cボタンが押された場合は、「カウントダウンの状態」に状態が変化する。グローバル変数「s」の値はそのまま変化しない。
ですよね~
カウントダウンの状態
次はカウントダウンの状態について説明しよう。ここで「utime」を使用しているのだ。
ようやく「utime」が出てきましたね!どんなプログラムかな・・・
- _58行目 # 「カウントダウンの状態」の動き
- _59行目 def StateCountDown():
- _60行目 global s
- _61行目 nextState = STATE_ALARM
- _62行目 # 終了時刻を設定する。
- _63行目 deadLine = utime.ticks_add(utime.ticks_ms(), s * 1000)
- _64行目 # HAPPYのイメージを表示して開始音を鳴らす。
- _65行目 display.show(Image.HAPPY)
- _66行目 audio.play(Sound.HELLO)
- _67行目 # 現在時刻が終了時刻より前の間はループする。
- _68行目 while utime.ticks_diff(deadLine, utime.ticks_ms()) > 0:
- _69行目 display.scroll(utime.ticks_diff(deadLine, utime.ticks_ms() / 1000)
- _70行目 # ボタンが押されたかどうかを調べる。
- _71行目 data = UserInput()
- _72行目 # Cボタンが押された場合は中止する。
- _73行目 if data == ‘c’:
- _74行目 nextState = STATE_SETTING
- _75行目 break
- _76行目 return nextState
63行目に「utime」が出てきますね。これは、何を行っているのですか?
現在の時刻に、「設定の状態」で設定した秒数を追加して、アラームをならす時刻を求めているのだ。
ほほぅ。ええと、「utime」で用意されている関数は以前調べましたね。
関数名 | 機能 |
utime.sleep(seconds) | secondsで指定された秒の間、何もしないで待つ。 |
utime.sleep_ms(ms) | msで指定されたミリ秒の間、何もしないで待つ。 |
utime.sleep_us(us) | usで指定されたマイクロ秒の間、何もしないで待つ。 |
utime.ticks_ms() | 現在をミリ秒であらわす値(ticks)を得る。 |
utime.ticks_us() | 現在をマイクロ秒であらわす値(ticks)を得る。 |
utime.ticks_add(ticks, delta) | ticksにdeltaを足す。 |
utime.ticks_diff(ticks1, ticks2) | 2つのticksの差を計算する。 |
今回は「utime.ticks_ms()」と「utime.ticks_add()」を使っているようですが・・・
現在の時刻を「utime.ticks_ms()」で取得して、「utime.ticks_add()」「s」秒を足すことで、アラームを鳴らす時刻を求めている。これをローカル変数「deadLine」に格納しているのだ。
- _62行目 # 終了時刻を設定する。
- _63行目 deadLine = utime.ticks_add(utime.ticks_ms(), s * 1000)
「s」は秒単位なので、1000倍してミリ秒として使用しているですね?
ウム、その通りだ。「deadLine」は一度設定したら変化しない。そのため、68行目の「while」のループ文より前に設定しておく必要があるのだ。
「while」文では何を行っているのですか?
- _67行目 # 現在時刻が終了時刻より前の間はループする。
- _68行目 while utime.ticks_diff(deadLine, utime.ticks_ms()) > 0:
- _69行目 display.scroll(utime.ticks_diff(deadLine, utime.ticks_ms() / 1000)
- _70行目 # ボタンが押されたかどうかを調べる。
- _71行目 data = UserInput()
- _72行目 # Cボタンが押された場合は中止する。
- _73行目 if data == ‘c’:
- _74行目 nextState = STATE_SETTING
- _75行目 break
- _76行目 return nextState
「utime.ticks_diff()」により、現在の時刻とアラームを鳴らす時刻の差を求めている。この差が0以下になるまでループするようになっているのだ。
差が0以下になるとどうなるのですか?
ループが終了して、「カウントダウンの状態」の関数が終了し、次の状態(スタート/ストッブボタンが押されていなければ「アラームの状態」、押されていれば「設定の状態」)の関数が呼び出されるのだ。
「while」文のループの中(69行目から75行目までのブロック)にもプログラムが書かれていますが・・・
69行目は、残り時間をマイクロビットのLED表示部分にスクロール表示している。
- _69行目 display.scroll(utime.ticks_diff(deadLine, utime.ticks_ms() / 1000)
そして、71行目以降は、Cボタンが押されたかどうかを調べて、押された場合はカウントダウンを中止する機能が実現されているのだ。
- _70行目 # ボタンが押されたかどうかを調べる。
- _71行目 data = UserInput()
- _72行目 # Cボタンが押された場合は中止する。
- _73行目 if data == ‘c’:
- _74行目 nextState = STATE_SETTING
- _75行目 break
- _76行目 return nextState
そうか、カウントダウンの時に「スタート/ストッブボタン:Cボタン」が押されたら、「設定の状態」に変化するのでしたね。
アラームの状態
アラームの状態では、どのような動きをするのですか?
サウンドを三回ならして、同時に追加した回路のLEDも三回光らせているのだ。そして、マイクロビットのLED表示部分にはイメージを表示している。
回 | サウンド | マイクロビットのLED表示部 | 追加した回路のLED |
1 | Sound.SPRINGを再生 | Image.HAPPYを表示 | 1ピンにつないだLEDを点灯 |
2 | Sound.SPRINGを再生 | Image.SNAKEを表示 | 2ピンにつないだLEDを点灯 |
3 | Sound.SPRINGを再生 | Image.HEARTを表示 | LEDを両方とも点灯 |
サウンドは三回とも「Sound.SPRING」なのですね?
まぁ、最初のバージョンなので、すでに用意されているサウンドを利用したのだ。今後の改良で、オリジナルなサウンドも作ってみたいネ。
どんなサウンドができるのか、楽しみですね!
今回のまとめ
今回は、キッチンタイマーのプログラムの最初のバージョンを作ったのですね。
ウム。時間の設定と、カウントダウン動作、そして、設定した時間をリセットする機能を実現したのだ。時間の表示方法を改良しないといけないのだ。
オリジナルのサウンドも作らないといけないですね。
次回をお楽しみになのだ~
コメント