親子IoTワークショップ

マイコンとクラウドを使ってIoTとプログラミングを学ぼう

[ トップ | 開催予定・概要 | 2024-12開催 | お知らせ | Facebook ]

複数のLEDを点滅させる(複数のGPIOピンを使う)

第1回のワークショップでは、LEDをひとつ点滅させました。ここでは、複数のLEDをいろいろな方法で点滅させます。

典型的なLEDを使っている限り、抵抗の抵抗値は1 KΩ(1,000 Ω)で問題ありません。330 Ωでも問題ないはずです。使うLEDの機種や色によっては220 Ωや100 Ωも使えます。抵抗値が小さいほどLEDは明るく光ります。

1 KΩ未満の抵抗を使う場合には、事前に計算をしてから使うようにしてください。抵抗値が小さすぎたり、抵抗を使うのを忘れるとLEDが溶けたり、爆発しますのでご注意を。計算方法はこちら。計算が面倒な場合は1 KΩの抵抗を使ってください。

2個のLEDを同時に点滅

LEDをひとつ点滅させるときに作った回路と同じものを、もうひとつ作ります。ラズパイに回路が2個つながり、各回路にLEDがひとつ付いているイメージです。以下の図では、赤いLEDを2個使っていますが、異なる色でも構いません。

ひとつめの回路は、ラズパイ(GPIO 21)から60-aの穴へ電流が流れ込み、49-aの穴がグラウンドにつながっています。これはワークショップで作った回路そのままです。新規に追加した回路は、ラズパイ(GPIO 14)から38-aの穴へ電流が流れこみ、48-aの穴がグラウンドにつながっています。それぞれの回路が別々のGPIOピンとグラウンドを使うので、合計でGPIOピンを2本、グラウンドを2本使います。

Pythonのプログラムはled2-on-off.pyを使います。ワークショップでやったように、ブラウザ上でプログラムをコピーして、ラズパイの中のThonnyへペーストします。ラズパイの中でプログラムのファイルを保存するとき、ファイル名は何でも構いませんが、同じ名前(led2-on-off.py)を付けておくと分かりやすいかと。

プログラムをThonnyを使って実行すると、以下のように2つのLEDが同時に点滅します。この例では赤と青のLEDを使っています。

ここで使っているプログラムでは、「ループ」を使っています。ワークショップで説明したように、同じ作業を繰り返すときにはループを使います。LEDの点滅を5回繰り返したいとき、5回分をひとつひとつ全部書くのではなく、LEDを1回だけ点滅させるプログラムを書いて、「これを5回繰り返してくれ」という命令方法にするのです。こうするとプログラムが短くなって読みやすくなります。

LEDの点滅を5回繰り返すには、以下のようなループを書きます。

for x in range(5):
  LEDを1回点滅させるプログラムをここに書く

このループは「for」という特別な記号(命令)を使ったものなので「forループ」と呼ばれています。forループを書く時には、forの後に変数(variable)を定義する決まりになっています(変数の名前は何でもよい)。ここでは「x」という名前の変数になっています。今のところ、この変数は使わないので無視してください。

2個のLEDを同時に点滅(グラウンドを1本にまとめる)

次に、上記の回路を少し変形して、グラウンドにつながっているワイヤの数を2本から1本へ減らします。変形する回路は左側の回路だけで、右側の回路はそのままにします。

まずは、左側の回路の電源側のワイヤをラズパイから取り外します(GPIO 14から引き抜く)。このように、回路を変更・修正するときには、まずワイヤのラズパイ側を外すことが重要です(理由はこちら)。次に、左側の回路のグラウンドのワイヤをラズパイから取り外し(グラウンドのピンから引き抜く)、その後ブレッドボード(の48-a)から取り外します。そして、左側のLEDの位置をひとつ右の穴へ移動します。つまり、プラス端子(長い足)が48番、マイナス端子(短い足)が49番の穴に刺さるようにします。これにより、2つのLEDのマイナス端子が49番を通してグラウンドを共有するようになります。このように、複数の回路のグラウンドをひとつにまとめることは全く問題なく、回路作りが少し単純化するのでおすすめです。

なお、左の回路の中のLEDを右へずらしたので、抵抗の右側の端子が入る穴を48番にします(47番から48番へずらす)。抵抗の左側の端子はそのままでかまいません。最後に、左の回路の電源側ワイヤをGPIO 14に戻します。

Pythonのプログラムはled2-on-off.pyを流用できます(使うGPIOピンを変えていないので)。

2個のLEDを交互に点滅

これまでは、2個のLEDを同時に点滅させました(2つ同時にON、2つ同時にOFF)。今度は、2個のLEDを交互に点滅させましょう。回路はそのままで、Pythonのプログラムをled2-on-off-alternate.pyに変えます。

2個のLEDを交互に無限点滅

次に、LEDの点滅を無限に繰り返すようにします。これまでは、「5回」や「10回」など、特定の「繰り返し回数」を事前に指定していました。例えば、5回の繰り返しの場合には、forループの先頭行を以下のように書きます。

for x in range(5):

forループはとても便利なのですが、繰り返し回数を事前に指定せずに「以下の作業をひたすらずっと繰り返してくれ」という命令方法は実現できません。そういう繰り返し(無限回の繰り返し)を命令したい場合は、「無限ループ(infinite loop)」という種類のループを書きます。このループの先頭行は以下のようになります。

while True:

このように「while」という特別な記号(命令)を使ったループを「whileループ」と呼びます。whileループで実現できる繰り返し作業にはいろいろなものがありますが、そのひとつが無限回の繰り返し(無限ループ)です。

無限ループを使って、2個のLEDを無限回点滅させるプログラムが、led2-on-off-alternate-infinite-loop.pyです。これを実行すると、ラズパイはずっと2個のLEDを交互に点滅させ続けます。

ずっとLEDを点滅させ続けるにせよ、いつかはそれを止めたくなります。あるいは止める必要があります。寝る前に止めるとか、食事の前に止めるとか、見飽きたら止めたくなるとか、そういうことです。実行中のプログラムを強制終了するには、Thonnyの上で(開いているThonnyのウインドウの中で)キーボードの「Control」と「c」を一緒に押します。キーボードによっては、「Control」ではなく「Ctrl」と書いてあるかもしれません。Mac用のキーボードの場合、Control(Ctrl)とCommand(Cmd)は違うキーですので、Control(Ctrl)を使ってください。

ちなみに、この強制終了の方法はとても有名(よく使う)ので、「Ctrl-c」と書いたり、「コントロール・シーする」と言います(英語でも日本語でも)。「ググる」みたいな言い方ですね。

無限ループを実行中にCtrl-cで強制終了する場合、プログラムは以下のような構造で書くきまりになっています。

while True:
    try:
        繰り返す作業
    except KeyboardInterrupt:
        break

今までのループと違うのは、「try」と「except KeyboardInterrupt」が入っていることです。ここで詳細に立ち入ることは避けたいので(これを説明するのはあまりに時期尚早なので)、とりあえずは以下のような理解でお願いします。

3個以上のLEDを点滅

これまで、LEDをひとつ持った回路を2つ使うことで、全部で2つのLEDを点滅させました。ここからLEDの総数を増やします。LEDをひとつ持った回路を3つ使うとLEDの総数は3になり、LEDをひとつ持った回路を4つ使うとLEDの総数は4になります。この要領でLEDの総数を増やすことができます。

ラズパイに最大何個のLEDを接続できるかは、使うLEDの機種や色、そして使う抵抗の抵抗値によります。典型的なLEDを使っていて、抵抗の抵抗値が1 KΩなら、10個は接続できます。正確な最大個数を知るには自分で計算する必要があります。計算方法はこちら。忘れてはいけないのは、GPIOのピン全てから流れる電流の合計が100 mAを超えてはいけないということです。この電流量を超えるとラズパイが壊れます。

ここでは、全部で8個のLEDを使うことにして、以下のような回路を作ります。

LEDをひとつ持った回路が8個あります。一番右の回路は、GPIO 21のピンから55-aの穴へワイヤがつながり、ブレッドボード内の垂直方向の配線を伝わって抵抗につながり、さらにLEDにつながっています。LEDは、55-jの穴にプラス端子(長い足)、その上の穴にマイナス端子(短い足)が刺さっています。LEDのマイナス端子は、ブレッドボード内の水平方向の配線を伝わって(一番左の)グラウンドにつながっています。

右から2番目の回路は、GPIO 20のピンから53-aの穴へワイヤがつながり、抵抗を介してLEDとつながっています。LEDのプラス端子(長い足)は53-jの穴に、マイナス端子(短い足)はその上の穴に刺さっています。LEDのマイナス端子は、ブレッドボード内の水平方向の配線を伝わって(一番左の)グラウンドにつながっています。

残りの回路の配線も、上記と同じ方法で解読できると思います。回路が8個あるのでGPIOのピンを全部で8本使い、8個の回路がグラウンドを共有しています。配線方法が、これまでのものとは少し違いますが、その理由はLEDをずらっと一列に並べたいからです(点滅させたときの見栄えがよいから)。

Pythonのプログラムはled8-on-off-alternate-infinite-loop.pyを使います。これを実行すると、次のように点滅します。

3個以上のLEDを点滅(プログラムを改良する)

LEDが期待通りに点滅するのを確認したら、プログラムを改良します。現在のプログラム(led8-on-off-alternate-infinite-loop.py)は、まともに動作するものの、要領が悪いというか、冗長というか、ダラダラしているというか、冴えないのです。問題点は、以下のように、本質的に同じことを何度も繰り返し書かなければならないことです。

GPIO.output(redPin1, GPIO.HIGH)
time.sleep(interval)
GPIO.output(redPin1, GPIO.LOW)
time.sleep(interval)

GPIO.output(redPin2, GPIO.HIGH)
time.sleep(interval)
GPIO.output(redPin2, GPIO.LOW)
time.sleep(interval)

GPIO.output(redPin3, GPIO.HIGH)
time.sleep(interval)
GPIO.output(redPin3, GPIO.LOW)
time.sleep(interval)

GPIO.output(redPin4, GPIO.HIGH)
time.sleep(interval)
GPIO.output(redPin4, GPIO.LOW)
time.sleep(interval)

ここでやろうとしていることは、LEDの点滅を4つのGPIOピンを使って行うことで、使うピンの番号(redPin1、redPin2、redPin3、redPin4)を変えること以外は同じことの繰り返しです。このように、同じようなプログラムを何度も書くのが(コピぺするのが)面倒だなあと思ったらループを使います。

ここでは、完全に同じこと(一字一句同じこと)を毎回繰り返すわけではなく、毎回GPIOピンの番号を変えながら同じこと(LEDの点滅)を繰り返します。こういう場合、従来のようなforループの使い方ではなく、新しい使い方をします。そのためにまず、使うGPIOピンの番号を以下のようにひとまとめにします。

redPins = [21, 20, 16, 12]

ここでは、4つのピンの番号をカンマで区切って(21, 20, 16, 12)、カッコでくくっています([21, 20, 16, 12])。このようにカンマとカッコで複数データを並べたものを「リスト」といいます。そして、リスト内の各データのことを要素(element)といいます。上記のプログラムでは、[21, 20, 16, 12]というリストを作って、そのリストを「redPins」という変数に格納しています。変数には、数や文字だけでなくリストも格納可能です。

このリスト「redPins」を使ってforループを書くと次のようになります。

for pin in redPins:
  GPIO.output(pin, GPIO.HIGH)
  time.sleep(interval)
  GPIO.output(pin, GPIO.LOW)
  time.sleep(interval)

このループの先頭行は「リストredPinsの各要素を変数pinで表す」と理解します。そしてループの実行過程は次のようになります。

同様に、ループの3回目、4回目の実行時には、リストredPinsの3番目、4番目の要素(16と12)が使われます。リストredPinsの要素は4つなので、ループは4回実行されます。

ここでやろうとしていることを、算数の計算の例を使って以下説明します。例えば、次のように同じ整数の足し算を繰り返したいとします。

1 + 1
2 + 2
3 + 3
4 + 4

具体的な足し算の式を繰り返すのではなく、使う整数をリストにして、次のようにループを実行すると、同じことを簡潔に記述できます。

for x in [1, 2, 3, 4]
  x + x

改良版のプログラムはled8-on-off-alternate-infinite-loop-lists.pyです。このプログラムでは、リストを使ったループ実行が4回あります。

点滅間隔をランダムにする

ここまでのプログラムでは、各LEDの光っている時間と消えている時間が常に一定で、点滅させるLEDの順番も常に一定です。結果として、光が流れているような印象の(ネオンサインのような)光り方をします。これはこれで綺麗ですが、少し趣の異なる点滅パターンを考えましょう。

以下では、警察・消防車両の(屋根の上についている)緊急警光灯のような光り方をプログラムしてみます。ひと昔(ふた昔?)前まで、あのライトは一定の時間間隔で左右交互に点滅していましたが、昨今のものはもっと「けたたましく」光ります。たくさんのライトがずらっと並んでいて、個々のライトは一瞬だけ点灯し、点灯するライトはランダムのように見えます。そこで、以下では、点滅間隔(各LEDの光っている時間と消えている時間)をランダムにして、光らせるLEDもランダムにしてみます。まずは、点滅間隔をランダムにします。

これまでのプログラムでは、以下のように冒頭部分で点滅間隔を設定し、

interval = 0.5

それを以下のように使ってLEDを点滅させてきました。

time.sleep(interval)

このように、あらかじめ決めた点滅間隔を使い回すのではなく、点灯・消灯の際にその間隔をランダムに決めることにします。Pythonにはランダムに数を生成する機能があるので、それを使います。ランダムに作られた数のことを乱数(random number)といいます。プログラム中で乱数を使うには、プログラムの最初の部分に以下のような行を追加して、乱数を生成する機能を使えるようにします。

import random

この「import」と「random」が何を意味しているかの説明は避けたいので(これを説明するのはあまりに時期尚早なので)、とりあえずは以下のような理解でお願いします。

random.random()

LEDの点滅間隔として、最大1秒というのはやや長すぎなので、以下のようにして乱数の範囲を0以上0.1未満に縮めます。「/」は割り算の記号です(100/10は100÷10のことです)。

random.random()/10

そして、その縮めた乱数を以下のようにLEDの点滅間隔として使います。

time.sleep(random.random()/10)

これにより、LEDの点灯時間、消灯時間は、0秒以上0.1秒未満の乱数によって決定されるようになります。Pythonのプログラムはこちらです:led8-on-off-alternate-random-intervals.py

点灯するLEDとその点滅間隔の両方をランダムにする

最後に、LEDをその並び順に点滅させるのではなく、ランダムにLEDを選ぶようにします。LEDの色にも関係なく、バラバラにLEDを点滅させます(警察・消防の緊急警光灯はそうなっていると思います)。

ここまでのプログラムでは、GPIOのピン番号をLEDの色別に分けて保存・管理していました。以下のように、redPinsには赤いLED用のGIOPのピン番号を(リストとして)入れて、bluePinsには青いLED用のピン番号を(リストとして)入れています。

redPins = [21, 20, 16, 12]
bluePins = [17, 27, 22, 10]

これに問題はないのでそのままにしますが、これからLEDの色に関係なく、バラバラにLEDを選択、点滅させたいので、2種類のピン番号をひとつのリストにまとめます(合体させます)。

allPins = redPins + bluePins

このように、「あるリスト + 別のリスト」として「リストの足し算」(集合の足し算)をすると、最初のリストの要素と次のリストの要素をひとつにまとめたリストを作れます。ここでは、ひとつにまとめたリストを変数allPinsに格納しています。

次に、このallPinsに入っているピン番号8個の中からひとつをランダムに選んで点滅させます。リストの中から要素をひとつランダムに選ぶには、以下のようにします。

pin = random.choice(allPins)

random.choice(リスト)というプログラムは、与えられたリストから要素をランダムに選びます。ここでは、allPinsの中から要素(ピン番号)を選んで、それを変数pinに格納しています。そして、選ばれたピン番号(pin)を使ってLEDの点滅を行います。

完成したプログラムはこちら:led8-on-off-random.py。これを実行すると以下のように点滅します。

このプログラムでは、点滅間隔として使う乱数の範囲を0以上0.1未満にしていますが、この範囲を変えると点滅の「けたたましさ」が変わると思います。例えば、

time.sleep(random.random()/10)

time.sleep(random.random()/20)

のようにするなど。

自習プロジェクトの目次に戻る