※なにか気になる点がありましたらコメント欄からお伝えください。
目次
1.はじめに
もりしーさん(@kotaMorishita :twitter)の懸賞でXmas tree のプリント基板が当たりました。今回はその基板にLEDをはんだ付けしたものを、ラズベリーパイ3にて制御したので、そのときのコードを張り付けておくことにしました。
他人の書いたコード、もしくは自分のコードでも日数のおいたものは、難解を極めるとのことなので、あえて詳しい解説は少なめにしました。パズルの解読が好きな方には、リアルのなぞ解きに挑戦するつもりで、読んでいただくのも楽しいかもしれないです。
(単に詳しく解説するのが面倒なことと、それでもコードをそれなりに公開したい気持ちがあることから、このような形を取ったまでです。平にご容赦願います)
動画1 Xmas ツリー
youtu.be
参考にしたコード:もりしーさんによるXmas ツリー用のコード
(oled.py のクラスは全面的にもりしーさんによるものです)
github.com
2.イルミネーションのコード
以下にコードの中身のあらましを記述します。
- 前処理
- メインとなる処理 ステージ0からステージ8まである(動作は動画と下のコード内のコメントを見てください)
- 締めくくり
- 後処理
リスト1 Xmas ツリー用のコード
import RPi.GPIO as GPIO import time import random from luma.core.render import canvas # 以下の1行はもりしーさんがコーディングしたモジュールをインポートしています # (今回のプログラムのため、私が少し手を加えました) from oled import oled # LEDが接続している端子(GPIO) leds = [6, 5, 13, 11, 16, 19, 9, 12, 26] # 奇数の段のLED ledOdds = [6, 13, 16, 9, 26] # 偶数の段のLED ledEven = [5, 11, 19, 12] # 回転して表示するときに使う rotate = [6, 13, 19, 26, 12, 9, 16, 11, 5] # 複数の段をオンオフするときに使う SW_OnOff = [[1, 0, 0, 0], [1, 1, 0, 0], [1, 1, 1, 0]] # もりしーさんのコードの名残 # LEDを段ごとに表示する # You can reverse the array in the program too! ledGroup = [[6], # top [5, 13], # 2nd [11, 16, 19], # 3rd [9, 12, 26]] # 4th # 列ごとに表示するとき用(下から上) ledColumUp = [[9, 11, 5, 6], [12, 16, 6], [26, 19, 13, 6]] # 列ごとに表示するとき用(上から下) ledColumDown = [[6, 5, 11, 9], [6, 16, 12], [6, 13, 19, 26]] # LEDをランダムに表示するとき用 ledRandom = [0, 0, 0, 0, 0] # GPIOの設定 GPIO.setmode(GPIO.BCM) # LEDを表示させるためのインスタンスを生成 for pins in ledGroup: for pin in pins: GPIO.setup(pin, GPIO.OUT) GPIO.output(pin, GPIO.LOW) # OLED表示に使うインスタンスを生成 # (もりしーさんオリジナル) monitor = oled.ssd1306_oled() device ,font32 = monitor.setup() # OLEDに”BEGIN”と表示 # (うまい表示のさせ方が分からず、 # 表示するごとにcanvas(device)を呼び出している) with canvas(device) as drawUpdate: drawUpdate.text((5, 0), "BEGIN" , font=font32, fill=100) # 0.8秒待つ(随所で使用している) time.sleep(0.8) try: # STAGE0と表示(ステージ0開始) with canvas(device) as drawUpdate: drawUpdate.text((5, 0), "STAGE0" , font=font32, fill=100) time.sleep(0.5) # ステージを切り替えるための変数 stage = 0 # ステージ0のループ while(stage == 0): # すべてのLED、3回点滅 for _ in range(3): for pin in rotate: GPIO.output(pin, GPIO.HIGH) time.sleep(0.5) for pin in rotate: GPIO.output(pin, GPIO.LOW) time.sleep(0.5) # 半分ずつ点滅 for _ in range(3): for pin in ledOdds: GPIO.output(pin, GPIO.HIGH) time.sleep(0.5) for pin in ledOdds: GPIO.output(pin, GPIO.LOW) time.sleep(0.01) for pin in ledEven: GPIO.output(pin, GPIO.HIGH) time.sleep(0.5) for pin in ledEven: GPIO.output(pin, GPIO.LOW) time.sleep(0.01) stage = 1 tm = 180.0 count = 0 # ステージ1と表示(ステージ1開始) with canvas(device) as drawUpdate: drawUpdate.text((5, 0), "STAGE1" , font=font32, fill=100) time.sleep(0.5) while(stage == 1): # tmは待ち時間。tmを用いて回転の速さを調節 # 3周期でステージ2へ if tm <= 1.0: tm = 25.0 count += 1 if count >= 3: stage = 2 j = 0 tm = 100.0 count = 0 row = 0 # LEDを回転して表示 for pin in rotate: GPIO.output(pin, GPIO.HIGH) time.sleep(tm/1000.0) GPIO.output(pin, GPIO.LOW) time.sleep(tm/1000.0) tm -= 0.5 * tm # ステージ2開始(OLEDに表示) with canvas(device) as drawUpdate: drawUpdate.text((5, 0), "STAGE2" , font=font32, fill=100) time.sleep(0.5) # tmで速さを調節 # jで表示させる段の先頭の位置を決定 # rowで段の数を変化させている # 2次元リストSW_OnOff で小細工 while(stage == 2): if j >= 4: j = 0 if tm <= 1.0: tm = 50.0 if count >=50: row += 1 count = 0 # メインの処理 for i in range(4): if SW_OnOff[row][ (j - i) % 4 ]: for pin in ledGroup[i]: GPIO.output(pin, GPIO.HIGH) time.sleep(tm / 1000.0) else: for pin in ledGroup[i]: GPIO.output(pin, GPIO.LOW) time.sleep(tm / 1000.0) j += 1 tm -= 1.0 count += 1 if row >= 2: stage = 3 tm = 80.0 count = 0 # すべてのLEDをオフ for i in leds: GPIO.output(i, GPIO.LOW) # ステージ3突入 with canvas(device) as drawUpdate: drawUpdate.text((5, 0), "STAGE3" , font=font32, fill=100) time.sleep(0.5) # 列ごとに上昇しながらLEDオン while(stage == 3): for i in range(3): for pin in ledColumUp[i]: GPIO.output(pin, GPIO.HIGH) time.sleep(tm / 1000.0) for pin in ledColumUp[i]: GPIO.output(pin, GPIO.LOW) time.sleep(tm / 1000.0) # 周期の調整 tm = 0.8 * tm if tm <= 0.1: stage = 4 tm = 80.0 # ステージ4突入 with canvas(device) as drawUpdate: drawUpdate.text((5, 0), "STAGE4" , font=font32, fill=100) time.sleep(0.5) # 列ごとに下降しながらLEDオン while(stage == 4): for i in range(3): for pin in ledColumDown[i]: GPIO.output(pin, GPIO.HIGH) time.sleep(tm / 1000.0) for pin in ledColumDown[i]: GPIO.output(pin, GPIO.LOW) time.sleep(tm / 1000.0) tm = 0.6 * tm if tm <= 0.1: stage = 5 tm = 80.0 # ステージ5突入 with canvas(device) as drawUpdate: drawUpdate.text((5, 0), "STAGE5" , font=font32, fill=100) time.sleep(0.5) # 乱数を導入。列ごとに乱数の段数まで点滅 while(stage == 5): for i in range(3): n = random.randint(0, 2) + 1 # 1, 2, 3 if i == 1: n = random.randint(0, 1) + 1 # 1, 2 for j in range(n+1): GPIO.output(ledColumUp[i][j], GPIO.HIGH) time.sleep(tm / 1000.0) for j in range(n+1): GPIO.output(ledColumUp[i][j], GPIO.LOW) time.sleep(tm / 1000.0) tm -= 5 if tm <= 30: stage = 6 tm = 80.0 # ステージ6突入 with canvas(device) as drawUpdate: drawUpdate.text((5, 0), "STAGE6" , font=font32, fill=100) time.sleep(0.5) # メイン処理。ランダムにLEDを5個表示させている while(stage == 6): for i in range(len(ledRandom)): ledRandom[i] = random.randint(0, 8) for i in ledRandom: GPIO.output(leds[i], GPIO.HIGH) time.sleep(tm / 1000.0) for i in ledRandom: GPIO.output(leds[i], GPIO.LOW) time.sleep(tm / 1000.0) tm -= 2 if tm <= 30: stage = 7 # ステージ7ではあるが、LEDの表示に切れ目が出てしまうので、 # OLEDはステージ6のまま # with canvas(device) as drawUpdate: # drawUpdate.text((5, 0), "STAGE7" , font=font32, fill=100) # time.sleep(0.5) # メイン処理 while(stage == 7): for i in range(len(ledRandom)): ledRandom[i] = random.randint(0, 8) for i in ledRandom: GPIO.output(leds[i], GPIO.HIGH) time.sleep(tm / 1000.0) for i in ledRandom: GPIO.output(leds[i], GPIO.LOW) time.sleep(tm / 1000.0) # テンポを遅らせている tm += 10 if tm >= 150: stage = 8 for i in ledRandom: GPIO.output(leds[i], GPIO.HIGH) time.sleep(tm / 1000.0) # ステージ8だが表示は7(別にどっちでもいい) with canvas(device) as drawUpdate: drawUpdate.text((5, 0), "STAGE7" , font=font32, fill=100) time.sleep(0.5) # メイン処理(ステージ0と同じ) # すべてのLEDを点滅 while(stage == 8): for _ in range(3): for pin in leds: GPIO.output(pin, GPIO.HIGH) time.sleep(0.5) for pin in leds: GPIO.output(pin, GPIO.LOW) time.sleep(0.5) # 半分ずつ点滅 for _ in range(3): for pin in ledOdds: GPIO.output(pin, GPIO.HIGH) time.sleep(0.5) for pin in ledOdds: GPIO.output(pin, GPIO.LOW) time.sleep(0.01) for pin in ledEven: GPIO.output(pin, GPIO.HIGH) time.sleep(0.5) for pin in ledEven: GPIO.output(pin, GPIO.LOW) time.sleep(0.01) stage = 9 # 締めくくりに突入 for pin in leds: GPIO.output(pin, GPIO.HIGH) time.sleep(0.5) # 感謝の意を表す for _ in range(3): with canvas(device) as drawUpdate: drawUpdate.text((5, 0), "THANK" , font=font32, fill=100) time.sleep(0.4) with canvas(device) as drawUpdate: drawUpdate.text((5, 0), "YOU!!" , font=font32, fill=100) time.sleep(0.4) # 後処理(フィナーレ) finally: GPIO.cleanup() for _ in range(2): with canvas(device) as drawUpdate: drawUpdate.text((5, 0), "GOOD" , font=font32, fill=100) time.sleep(0.8) with canvas(device) as drawUpdate: drawUpdate.text((5, 0), "BYE !" , font=font32, fill=100) time.sleep(0.8) # 変化を付けている(遊んでいる) with canvas(device) as drawUpdate: drawUpdate.text((5, 0), "GOOD" , font=font32, fill=100) time.sleep(0.8) with canvas(device) as drawUpdate: drawUpdate.text((5, 0), "LUCK!!" , font=font32, fill=100) time.sleep(2) #OLEDの表示を消している with canvas(device) as drawUpdate: time.sleep(0.1)
3.もりしーさんのモジュール(oled.py)
OLEDを制御するコードです。もりしーさんのコードを使用させていただきました。出展は「1.はじめに」のリンク先を参照してください。
メインのファイルから、呼び出すことができるように、自分用に少し手を加えました。(28~31行、35,6行をコメントアウトし、33行を付け加えている)
リスト2 oled.py の中身
# -*- coding: utf-8 -*- # sudo -H pip3 install --upgrade luma.oled # sudo -H pip3 install serial # 128 x 64 from luma.core.interface.serial import i2c from luma.core.render import canvas from luma.oled.device import ssd1306 from PIL import Image, ImageFont, ImageDraw, ImageOps import time import os class ssd1306_oled(object): def __init__(self): print("oled initialize") def setup(self, i2c_address=0x3C): self.serial = i2c(port=1, address=i2c_address) self.device = ssd1306(self.serial) # font configuration self.ttf = '/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf' basedir = os.path.dirname(os.path.realpath(__file__)) self.imagedir = os.path.join(basedir, 'images') self.font8 = ImageFont.truetype(self.ttf, 8) self.font16 = ImageFont.truetype(self.ttf, 16) self.font32 = ImageFont.truetype(self.ttf, 32) # with canvas(self.device) as drawUpdate: # drawUpdate.text((5, 0), "Hello" , font=self.font32, fill=100) # time.sleep(10) return self.device, self.font32 # monitor = ssd1306_oled() # monitor.setup()
4.まとめ
もりしーさんの懸賞でXmas ツリーが当選したので、それのLEDを点滅させるプログラムの解説を行いました。
- 繰り返しの処理があまりなかったので、関数にまとめることなく、延々と書き連ねていくスタイルを取りました。(読みづらかったかと思います)
- OLEDの表示では、1度表示した文字を消すのに、何度も canvas() という関数を呼び出しました。非推奨です。
気分としては、スクラッチなどで小学生ぐらいの子供がが、アニメーションをコーディングしているのとあまり変わらないのかな、といった印象を抱きました。思いつくままに、つけ足していって、少しくどい演出だったかもしれないです。
しかし、これで、懸賞に当たったときからの宿題にこたえることができた、といった思いです。
改めまして、もりしーさんには感謝の意を表したいと思います。ありがとうございました。