🔈

ラズパイ-ラジコン+αを自作する - その4(LED/ブザー/表示)

2021/09/25に公開

🏁デモ

📚記事一覧

📍本記事の範囲

  • LEDのON/OFF
  • ブザーのON/OFF
  • 液晶の表示

について説明します。

🔧パーツ一覧

部品名 個数 内容 備考
RaspberryPiZeroWH 1 ラズベリーパイゼロ本体 秋月電子
カムロボット 1 カムロボット本体 Amazon
SG90 2 SG90本体 秋月電子
LED 2 LED本体 -
LED用抵抗 2 抵抗 -
圧電ブザー(PKM13EPYH4000) 1 BEEP音用 秋月電子
OLED 1 表示機128x64dot 秋月電子

※全パーツは、その1-コンセプトを参照のこと

接続図

LED

Pin LED 備考
GND GND LED1/2
BCM16 LED1-アノード -
BCM20 LED2-アノード -

ブザー

Pin ブザー 備考
GND ブザー接続先 極性なし
BCM21 ブザー接続先 極性なし

OLED

Pin OLED 備考
BCM2 SDA -
BCM3 SCL -
GND GND -
3.3V VDD -

💻環境

開発環境

  • ラズベリーパイ
    • Linux rpi 5.10.17-v7l+ #1403 SMP Mon Feb 22 11:33:35 GMT 2021 armv7l GNU/Linux
  • Python
    • Python 3.7.3 (default, Jan 22 2021, 20:04:44)

ラズベリーパイの設定

OLED - I2Cの有効化

本デバイスは、I2C通信を使って制御します。
I2C通信を使用できるように設定する必要があります。
I2Cの有効化は、以下のコマンドから実施できます。

$ sudo raspi-config
  • Interface Optionsを選択
  • I2Cを選択
  • "はい"(or "Yes")を選択
  • これで有効化されます(1回行えばOKです)

モジュールのインストール

apt

pigpioライブラリは、Raspberry PiのGPIOを制御するためのライブラリです。
以下のコマンドは、はじめてインストールする場合のみ必要です。

$ sudo apt install pigpio
$ sudo service pigpiod start
$ sudo systemctl enable pigpiod.service

また、日本語を表示するためにフォントをインストールします。

$ sudo apt-get install fonts-ipafont

pip

Pythonに関するモジュールをインストールします。

LED, ブザー

$ python3 -m venv env
$ source env/bin/activate
(env) $ pip install pigpio
(env) $ pip install gpiozero
(env) $ pip install icecream

OLED

(env) $ pip install adafruit-circuitpython-ssd1306
(env) $ pip install smbus2
(env) $ pip install pillow

📝手順

最終的に、すべてのモジュールが連携するため、キューを使用しています。

LED制御

コード - LED

led.py
from queue import Queue
import threading
import time
from gpiozero import LED
from gpiozero.pins.pigpio import PiGPIOFactory
import sys
from icecream import ic

# LEDのピン設定
PIN_LED_NO1 = 16
PIN_LED_NO2 = 20

LED_DICT = {
    "no1" : PIN_LED_NO1,
    "no2" : PIN_LED_NO2,
}


class LedThread(threading.Thread):
    """
    LED管理
    例:
    queue経由で、{"name":"no1", "action":"on"}
    を取得すると、LED1を点灯
    """
    def __init__(self):
        threading.Thread.__init__(self)
        self.stop_event = threading.Event()
        self.setDaemon(True)

        self._rcv_que = Queue()
        self._leds = {}

        # 各ピンをLED設定
        factory = PiGPIOFactory()
        for key, pin in LED_DICT.items():
            self._leds[key] = LED(pin, pin_factory=PiGPIOFactory())
        return

    def stop(self):
        self.stop_event.set()
        return


    def run(self):
        while True:
            value = self.rcv_que.get()
            ic("[led_th]", value)
            
            if "led" not in value["type"]:
                ic("[led_th]", "error!!!")
                continue
            
            if value["name"] in self._leds:
                name = value["name"]
                on_off = True if ("on" in value["action"]) else False
                self._write_leds(name, on_off)
        return

    @property
    def rcv_que(self):
        return self._rcv_que

    def _write_leds(self, name, on_off):
        if on_off:
            self._leds[name].on()
        else:
            self._leds[name].off()
        return


def main():
    import time

    led_th = LedThread()
    led_th.start()
    q = led_th.rcv_que

    q.put({"type": "led", "name": "no1", "action": "on"})
    time.sleep(3)
    q.put({"type": "led", "name": "no1", "action": "off"})
    time.sleep(1)
    q.put({"type": "led", "name": "no2", "action": "on"})
    time.sleep(3)
    q.put({"type": "led", "name": "no2", "action": "off"})
    time.sleep(1)

    led_th.stop()
   
    return

if __name__ == "__main__":
    main()

実行手順 - LED

(env) $ python led.py

以下の順番でカムロボットが動作します。

  • LED1を3秒点灯
  • LED1を消灯
  • LED2を3秒点灯
  • LED2を消灯

ブザー制御

コード - ブザー

buzzer.py
from queue import Queue
import threading
import time
from gpiozero import TonalBuzzer
from gpiozero.tones import Tone
from gpiozero.pins.pigpio import PiGPIOFactory
from icecream import ic

# BUZZERのピン設定
BUZZER_PIN = 21

# Midi note: 'C4' - ド
# Midi note: 'D4' - レ
# Midi note: 'E4' - ミ
# Midi note: 'F4' - ファ
# Midi note: 'G4' - ソ
# Midi note: 'A4' - ラ
# Midi note: 'B4' - シ
# Midi note: 'C5' - ド

BUZZER_DICT = {
    "buzzer" : BUZZER_PIN,
}


class BuzzerThread(threading.Thread):
    """
    ブザー管理
    例:
    queue経由で、{"type":"buzzer", "time": "300", "bfreq":"2000"}
    を取得すると、ブザー音を300msec鳴らす
    """
    def __init__(self):
        threading.Thread.__init__(self)
        self.stop_event = threading.Event()
        self.setDaemon(True)

        self._rcv_que = Queue()
        self._buzzer = {}
        for key, pin in BUZZER_DICT.items():
            self._buzzer[key] = TonalBuzzer(pin, pin_factory=PiGPIOFactory())
        return

    def stop(self):
        self.stop_event.set()
        # cleanup
        for key in self._buzzer:
            self._buzzer[key].stop()
        return


    def run(self):
        while True:
            # time.sleep(0.050)
            item = self.rcv_que.get()
            ic("[buzzer_th]", "run : get : ", item)
            
            if "buzzer" not in item["type"]:
                ic("[buzzer_th]", "error!")
                continue

            ms_time = int(item["time"]) / 1000
            # item["note"] : 'C4', 'D4', 'E4', 'F4', 'G4', 'A4', 'B4', 'C5'
            self._buzzer[item["name"]].play(Tone(item["note"]))
            time.sleep(ms_time)
            self._buzzer[item["name"]].stop()
        return

    @property
    def rcv_que(self):
        return self._rcv_que


def main():
    import time

    buzzer_th = BuzzerThread()
    buzzer_th.start()
    q = buzzer_th.rcv_que

    q.put({"type": "buzzer", "name": "buzzer", "time": "500", "note": "C4"}) # do
    time.sleep(1)
    q.put({"type": "buzzer", "name": "buzzer", "time": "500", "note": "D4"}) # re
    time.sleep(1)
    q.put({"type": "buzzer", "name": "buzzer", "time": "500", "note": "E4"}) # mi
    time.sleep(1)
    q.put({"type": "buzzer", "name": "buzzer", "time": "500", "note": "F4"}) # fa
    time.sleep(1)

    buzzer_th.stop()
   
    return

if __name__ == "__main__":
    main()

実行手順 - ブザー

(env) $ python buzzer.py

以下の順番でカムロボットが動作します。

  • ドを1秒鳴らす
  • レを1秒鳴らす
  • ミを1秒鳴らす
  • ファを1秒鳴らす
  • 音を止める

表示制御

コード - 表示

oled.py
from queue import Queue
import threading
import time
from systems import SystemsData

# Imports the necessary libraries...
import socket
import fcntl
import struct
import board
import digitalio
from PIL import Image, ImageDraw, ImageFont
import adafruit_ssd1306

import sys
from icecream import ic

# OLED設定
DISP_WIDTH = 128
DISP_HEIGHT = 64
DEVICE_ADDR = 0x3C

# PATH_FONT = "./ipaexm.ttf"
PATH_FONT = "/usr/share/fonts/truetype/fonts-japanese-gothic.ttf"

class OledThread(threading.Thread):
    """
    OLED管理
    例:
    queue経由で、{"type":"oled", "time": "3000", "disp":"ip"}
    disp : ip / clear
    """
    def __init__(self):
        ic()
        threading.Thread.__init__(self)
        self.stop_event = threading.Event()
        self.setDaemon(True)

        self._rcv_que = Queue()
        self._sysdat = SystemsData()

        # Setting some variables for our reset pin etc.
        RESET_PIN = digitalio.DigitalInOut(board.D4)
        TEXT = ""

        # Very important... This lets py-gaugette 'know' what pins to use in order to reset the display
        i2c = board.I2C()
        self._oled = adafruit_ssd1306.SSD1306_I2C(DISP_WIDTH, DISP_HEIGHT, i2c, addr=DEVICE_ADDR, reset=RESET_PIN)

        # font
        self._font10 = ImageFont.truetype(PATH_FONT, 10)
        self._font12 = ImageFont.truetype(PATH_FONT, 12)
        self._font14 = ImageFont.truetype(PATH_FONT, 14)
        self._font16 = ImageFont.truetype(PATH_FONT, 16)
        self._font18 = ImageFont.truetype(PATH_FONT, 18)

        # Clear display.
        self._oled.fill(0)
        self._oled.show()
        return

    def stop(self):
        ic()
        self.stop_event.set()
        # cleanup
        self._oled.fill(0)
        self._oled.show()
        return


    def run(self):
        ic()
        while True:
            item = self.rcv_que.get()
            ic(sys._getframe().f_code.co_filename, sys._getframe().f_code.co_name, item)
            
            if "oled" not in item["type"]:
                print("[oled_th]", "error : type")
                continue
            
            self._recvice(item)
        return

    @property
    def rcv_que(self):
        return self._rcv_que

    def _recvice(self, item):
        ic()
        val_time = int(item["time"]) / 1000
        val_disp = item["disp"]

        def display_ip():
            ic()
            def get_ip_address(ifname):
                s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
                return socket.inet_ntoa(
                    fcntl.ioctl(
                        s.fileno(),
                        0x8915,  # SIOCGIFADDR
                        struct.pack("256s", str.encode(ifname[:15])),
                    )[20:24]
                )
            # This sets TEXT equal to whatever your IP address is, or isn't
            try:
                TEXT = get_ip_address("wlan0")  # WiFi address of WiFi adapter. NOT ETHERNET
            except IOError:
                try:
                    TEXT = get_ip_address("eth0")  # WiFi address of Ethernet cable. NOT ADAPTER
                except IOError:
                    TEXT = "NO INTERNET!"


            # Clear display.
            self._oled.fill(0)
            self._oled.show()

            # Create blank image for drawing.
            image = Image.new("1", (self._oled.width, self._oled.height))
            draw = ImageDraw.Draw(image)

            # Draw the text
            intro = "カムロボです。"
            ip = "IPアドレス:"
            draw.text((0, 46), TEXT, font=self._font14, fill=255)
            draw.text((0, 0), intro, font=self._font18, fill=255)
            draw.text((0, 30), ip, font=self._font14, fill=255)

            # Display image
            self._oled.image(image)
            self._oled.show()

            return

        def display_clear():
            self._oled.fill(0)
            self._oled.show()
            return

        if "ip" in val_disp:
            display_ip()
        else:
            # Clear display.
            display_clear()
        return

def main():
    import time

    oled_th = OledThread()
    oled_th.start()
    q = oled_th.rcv_que

    q.put({"type": "oled", "time": "3000", "disp":"ip"})
    time.sleep(10)
    q.put({"type": "oled", "time": "3000", "disp":"clear"})
    time.sleep(1)

    oled_th.stop()
   
    return

if __name__ == "__main__":
    main()

実行手順 - 表示

(env) $ python oled.py

以下の順番でカムロボットが動作します。

  • 以下を10秒表示する
    • "カムロボです。"
    • IPアドレス
  • 表示をオフにする

🔎ポイント

LED制御 - コード制御

  • on() / off()
    • LEDが点灯する / LED消灯とする
  • blink()
    • 点灯/消灯を1秒単位で繰り返す
  • toggle()
    • 呼ばれるたびに...->点灯->消灯->点灯->消灯->..を繰り返す

ブザー制御 - 音の指定

以下の形式で指定することができます。

  • 音名+オクターブ指定("C4" : ド、"E4" : ミなど)
    • Tone("C4")
  • MIDI Note指定(60 : ド (C4と同じ))
    • Tone(midi=60)
  • 周波数の指定(400)
    • Tone(frequency=400)

表示制御 - 画像を作成して描画

コード上で使用しているPillowモジュールはサードパーティ製の画像処理モジュールです。
今回のコードでは、文字列であっても一旦画像にしています(Pillowを使用)。
oled.image()へ128x64 のモノクロ画像を渡しています。

さいごに

上記以外のラズパイの活用方法を

https://zenn.dev/kotaproj/books/raspberrypi-tips

としてまとめ中です。

GitHubで編集を提案

Discussion