2025年8月12日火曜日

IRリモートでRGBLED点灯

 今回は Raspberry Pi Pico W を使って、赤外線リモコンの信号を受信し、RGB LED を光らせる実験に挑戦してみましょう。

テレビのリモコンなどに使われている赤外線(IR)リモコンは、ボタンを押すと「特定のパターンの信号」を赤外線で送信しています。この信号を、赤外線受信モジュール(たとえば VS1838B など)で受け取り、Pico W に接続して解析します。

リモコンのボタンによって異なる信号が届くので、受信したパルスの長さや間隔を読み取り、「どのボタンが押されたか」を判定できます。今回は、その判定結果に合わせて RGB LED の色を変える のが目標です。たとえば

  • 1ボタンを押すと白色

  • 2ボタンで赤色

  • 3ボタンで緑色

  • 4ボタンで青色

というように、ボタンごとに LED の色が変わるようにします。

RGB LED は赤・緑・青の 3 つの LED が 1 つにまとまった部品で、それぞれの色を ON/OFF(あるいは明るさを変える)ことで多彩な色を作れます。まずは 「IR 信号を受け取って LED を光らせる」 という基本動作ができれば OK。慣れてきたら、次のステップとしてなめらかな PWM 制御やグラデーション発光などにも挑戦してみましょう。


回路図

PL9823-F5

📚 使用ライブラリとモジュール

アイコンライブラリ/モジュール主な役割本コードで使っている代表的 API
📡UpyIrRxIR 受信専用の軽量クラス。受信ピンからのパルス列をキャプチャし、キャリブレーション済みリストに変換してくれるrecord()get_calibrate_list()ERROR_NONE
🔧machine.PinRaspberry Pi Pico W の GPIO を制御する MicroPython 標準モジュールPin()(モード Pin.IN
time時刻取得とスリープ用の MicroPython 標準モジュールticks_ms()ticks_diff()sleep() / sleep_ms()

ライブラリ:DLして下図のようにRaspbery PI Pico Wにアップロードします。


NEC赤外線リモコン信号デコーダ(MicroPython + UpyIrRx用)

from UpyIrRx import UpyIrRx
from machine import Pin
import time

def decode_nec(raw_data):
    if len(raw_data) < 66:
        return None

    lead_mark = raw_data[0]
    lead_space = raw_data[1]
    if not (8500 < lead_mark < 9500 and 4000 < lead_space < 5000):
        return None

    bits = ""
    for i in range(2, 66, 2):
        mark = raw_data[i]
        space = raw_data[i + 1]
        if not (400 < mark < 700):
            return None
        if 400 < space < 700:
            bits += "0"
        elif 1500 < space < 1800:
            bits += "1"
        else:
            return None

    def bits_to_byte(b):
        val = 0
        for i in range(8):
            if b[i] == '1':
                val |= (1 << i)
        return val

    addr     = bits_to_byte(bits[0:8])
    addr_inv = bits_to_byte(bits[8:16])
    cmd      = bits_to_byte(bits[16:24])
    cmd_inv  = bits_to_byte(bits[24:32])

    if addr ^ addr_inv != 0xFF or cmd ^ cmd_inv != 0xFF:
        return None

    return {"address": addr, "command": cmd}

IR_PIN = 16
ir = UpyIrRx(Pin(IR_PIN, Pin.IN))

while True:
    err = ir.record(wait_ms=1500, blank_ms=200)
    if err == ir.ERROR_NONE:
        raw = ir.get_calibrate_list()
        result = decode_nec(raw)
        if result:
            print("✅ NEC信号デコード成功")
            print("📮 アドレス: 0x{:02X}".format(result["address"]))
            print("🔘 コマンド: 0x{:02X}".format(result["command"]))
    # エラーや待機中の表示はなしでスルー
    time.sleep(0.1)



>>> %Run -c $EDITOR_CONTENT

MPY: soft reboot
✅ NEC信号デコード成功
📮 アドレス: 0x00
🔘 コマンド: 0x16
✅ NEC信号デコード成功
📮 アドレス: 0x00
🔘 コマンド: 0x0C
✅ NEC信号デコード成功
📮 アドレス: 0x00
🔘 コマンド: 0x18
✅ NEC信号デコード成功
📮 アドレス: 0x00
🔘 コマンド: 0x5E
✅ NEC信号デコード成功
📮 アドレス: 0x00
🔘 コマンド: 0x08
✅ NEC信号デコード成功
📮 アドレス: 0x00
🔘 コマンド: 0x1C
✅ NEC信号デコード成功
📮 アドレス: 0x00
🔘 コマンド: 0x5A
✅ NEC信号デコード成功
📮 アドレス: 0x00
🔘 コマンド: 0x42
✅ NEC信号デコード成功
📮 アドレス: 0x00
🔘 コマンド: 0x52
✅ NEC信号デコード成功
📮 アドレス: 0x00
🔘 コマンド: 0x4A

📝 処理の流れ

    ⚙️ ライブラリとピンの準備

     UpyIrRx, Pin, time をインポートし、IR 受信用ピン番号を決定

    🛠 NEC デコード関数 decode_nec() を定義

        ・パルス列のフォーマット確認(リードバースト長・ビット長)

        ・“0”/“1” 判定 → 32 bit へ変換 → 補数チェックで妥当性を検証

    🔧 IR 受信モジュールを初期化

      ir = UpyIrRx(Pin(IR_PIN, Pin.IN))

    🔄 メインループ

         ir.record() で最長 1.5 s 待機し、パルス列をキャプチャ

         受信成功 (ERROR_NONE) なら get_calibrate_list() で μs 単位リストを取得

    🧩 NEC デコード

      キャプチャしたリストを decode_nec() へ渡し、アドレス&コマンドに変換

    ✅ デコード成功時の表示

      16 進数でアドレス/コマンドをプリントし、判定の可視化

    💤 CPU 負荷軽減

      0.1 秒スリープでポーリング間隔を調整し省電力化

📌 decode_nec(raw_data)

先頭 9 ms+4.5 ms のリードバーストを確認し、560 µs のマークと 0/1 用スペース長を解析。32 bit の補数ペアで誤受信を排除する

🏷 bits_to_byte()

“01010101” 形式の文字列を整数 0x55 に変換するユーティリティ

📡 UpyIrRx.record()

高速割り込みでパルス幅をバッファリングし、無信号 200 ms で自動終了

🛠 get_calibrate_list()

キャリブレーション済み(π/38 kHz クロック換算済み)の μs リストを返すので解析がシンプル

🚦 デコード結果の利用

今回は print() で確認するだけだが、ここに RGB LED 制御を追加すれば “ボタン → LED 色” が簡単に実現できる

🚀 拡張アイデア

    受信コードを辞書にマッピングして多色点灯/PWM グラデーション

    受信失敗時のタイムアウト表示やリトライ機能を追加

    複数フォーマット(Sony, RC5 など)をデコードする汎用ライブラリ化

🌈 PL9823-F5 用 RGB LED デモプログラム


# pl9823_demo.py
# Raspberry Pi Pico (RP2040) + MicroPython v1.22 以降
from machine import Pin
from neopixel import NeoPixel   # Pico 用の組込みモジュール
import time

LED_PIN   = 17   # データ線を繋いだ GPIO 番号に合わせる
NUM_LEDS  = 1    # PL9823‑F5 を1個だけ接続
BRIGHTNESS = 0.2 # 0.01.0 で輝度スケーリング(省電力)

np = NeoPixel(Pin(LED_PIN, Pin.OUT), NUM_LEDS)

def set_pixel(rgb):
    # 0255 のタプルを輝度スケーリングして書き込む
    r, g, b = rgb
    scale = BRIGHTNESS
    np[0] = (int(g*scale), int(r*scale), int(b*scale))  # 注: GRB 並び
    np.write()

def wheel(pos):
    # 0255 → レインボー (Adafruit 定番アルゴリズム)
    if pos < 85:
        return (255 - pos*3, pos*3, 0)
    elif pos < 170:
        pos -= 85
        return (0, 255 - pos*3, pos*3)
    else:
        pos -= 170
        return (pos*3, 0, 255 - pos*3)

print("PL9823-F5 demo start")
while True:
    # レインボーグラデーション
    for i in range(256):
        set_pixel(wheel(i))
        time.sleep_ms(20)

    # 単色テスト (赤→緑→青→白→消灯)
    for color in [(255,0,0), (0,255,0), (0,0,255), (255,255,255), (0,0,0)]:
        set_pixel(color)
        time.sleep(1)

⚙️ 初期設定

・🔌 GPIO17 を出力モードに設定

・💡 NeoPixel モジュールで LED(1個)を初期化

・🔋 輝度 (BRIGHTNESS) を 0.2 に設定(省電力)

🎨 set_pixel(rgb) 関数

・🎚️ RGB 値を輝度スケーリング(0〜255 → 0〜51)

・🔄 GRB の並びにして NeoPixel に送信

・📝 np.write() で LED に反映

🌈 wheel(pos) 関数

・🔢 入力値(0〜255)を元にレインボーカラーを生成

・🌟 Adafruit の定番アルゴリズムを使用

・🔁 色相:赤 → 緑 → 青 のグラデーションを形成

🔁 メインループの流れ

1.🌈 レインボーグラデーション

 ・wheel(i) で 0〜255 の色を順番に表示

 ・⏱️ 20ms ごとに更新(滑らかな色の遷移)

2.🎯 単色表示テスト

 ・🟥 赤 → 🟩 緑 → 🟦 青 → ⬜ 白 → ⚫ 消灯

 ・⏱️ 各色を 1秒間表示

IRリモコン信号でPL9823-F5フルカラーLEDを制御

from UpyIrRx import UpyIrRx
from machine import Pin
from neopixel import NeoPixel
import time
import gc

# ==== 設定 ====
IR_PIN, LED_PIN = 16, 17
NUM_LEDS        = 1
BRIGHTNESS      = 0.25
TOL             = 0.45  # パルス許容誤差 45%

# ==== NeoPixel 初期化 ====
np = NeoPixel(Pin(LED_PIN, Pin.OUT), NUM_LEDS)

def set_pixel(rgb):
    r, g, b = rgb
    scale = BRIGHTNESS
    np[0] = (int(g * scale), int(r * scale), int(b * scale))  # GRB順
    np.write()
    time.sleep_us(50)

def match(val, target):
    lo = target * (1.0 - TOL)
    hi = target * (1.0 + TOL)
    return lo <= val <= hi

def decode_nec(raw):
    if len(raw) == 0:
        return None, False
    if len(raw) < 4:
        return None, False

    # 明らかに早すぎるノイズパルスは無視(例: <2000us
    if raw[0] < 2000:
        return None, False

    if 500 <= raw[0] <= 10000:
        if match(raw[1], 4500):
            pass  # 正常なヘッダー
        elif match(raw[1], 2250):
            return 0xFFFF, True  # リピート信号
        else:
            return None, False
    else:
        return None, False

    bits = 0
    count = 0
    for i in range(2, min(len(raw) - 1, 66), 2):
        mark, space = raw[i], raw[i + 1]
        if not match(mark, 560):
            return None, False
        if match(space, 560):
            bits |= (0 << count)
        elif match(space, 1690):
            bits |= (1 << count)
        else:
            return None, False
        count += 1

    if count < 32:
        return None, False

    addr  = bits & 0xFF
    addr_i = (bits >> 8) & 0xFF
    cmd   = (bits >> 16) & 0xFF
    cmd_i = (bits >> 24) & 0xFF

    if (addr ^ addr_i) != 0xFF or (cmd ^ cmd_i) != 0xFF:
        return None, False

    return cmd, False

# ==== コマンド → RGB ====
CMD_COLOR = {
    0x16: (128, 128, 0),  # 白(控えめ)
    0x0C: (255, 0, 0),    # 赤
    0x18: (0, 255, 0),    # 緑
    0x5E: (0, 0, 255),    # 青
    0x08: (255, 255, 0),  # 黄
    0x1C: (0, 255, 255),  # シアン
    0x5A: (255, 0, 255),  # マゼンタ
    0x42: (128, 0, 128),  # グレー
    0x52: (255, 128, 0),  # オレンジ
    0x4A: (0, 0, 0),      # 消灯
}
DEFAULT_COLOR = (32, 32, 32)

# ==== IR 初期化 ====
ir = UpyIrRx(Pin(IR_PIN, Pin.IN, Pin.PULL_UP))

print("IR ⇒ RGB LED demo (final tuned version)")

last_cmd = None
gc_counter = 0
error_counter = 0

while True:
    result = ir.record(wait_ms=1000, blank_ms=180)
    if result == ir.ERROR_NONE:
        raw = ir.get_calibrate_list()
        gc.collect()
        cmd_val, is_repeat = decode_nec(raw)
        del raw
        if cmd_val is None:
            error_counter += 1
            if error_counter >= 10:
                print("Resetting IR receiver...")
                ir = UpyIrRx(Pin(IR_PIN, Pin.IN, Pin.PULL_UP))
                error_counter = 0
            time.sleep(0.005)
            continue

        if is_repeat:
            if last_cmd is None:
                continue
            cmd_val = last_cmd
        else:
            last_cmd = cmd_val

        cmd_val &= 0xFF
        color = CMD_COLOR.get(cmd_val, DEFAULT_COLOR)
        set_pixel(color)
        print("cmd=0x{:02X} → RGB {}".format(cmd_val, color))

        gc_counter += 1
        if gc_counter >= 100:
            gc.collect()
            print("Free memory:", gc.mem_free())
            gc_counter = 0
        error_counter = 0
        time.sleep(0.005)

    elif result == ir.ERROR_TIMEOUT:
        pass  # 通常状態:無反応ならスキップ
    else:
        error_counter += 1
        if error_counter >= 10:
            print("Resetting IR receiver...")
            ir = UpyIrRx(Pin(IR_PIN, Pin.IN, Pin.PULL_UP))
            error_counter = 0
        time.sleep(0.005)



>>> %Run -c $EDITOR_CONTENT

MPY: soft reboot
IR ⇒ RGB LED demo (final tuned version)
cmd=0x4ARGB (0, 0, 0)
cmd=0x16RGB (128, 128, 0)
cmd=0x0CRGB (255, 0, 0)
cmd=0x18RGB (0, 255, 0)
cmd=0x5ERGB (0, 0, 255)
cmd=0x08RGB (255, 255, 0)
cmd=0x1CRGB (0, 255, 255)
cmd=0x5ARGB (255, 0, 255)
cmd=0x42RGB (128, 0, 128)
cmd=0x52RGB (255, 128, 0)
cmd=0x4ARGB (0, 0, 0)
cmd=0x0CRGB (255, 0, 0)
cmd=0x18RGB (0, 255, 0)
cmd=0x5ERGB (0, 0, 255)

🔁 処理の流れ(流れ図スタイル+アイコン)

🔧 初期化

・⚙️ GPIO16 を IR受信機(VS1838B等)用に入力+プルアップ設定

・💡 GPIO17 を NeoPixel(PL9823-F5)データ出力に設定

・🧠 NeoPixel オブジェクトと UpyIrRx 赤外線受信オブジェクトを初期化

・🧹 ガベージコレクションカウンタ・エラーカウンタを初期化

🔄 メインループ処理

1.📡 ir.record() で赤外線信号を受信(最大1000ms待機)

2.🧮 受信成功 → get_calibrate_list() で正規化されたパルス列取得

3.🔍 decode_nec() でNECフォーマット解析:

 ・📉 不完全やノイズの短い信号は無視

 ・🔁 リピートコード(0xFFFF)も検出対応

 ・✅ チェックサム検証付きで信頼性高

4.🎮 コマンド取得成功時:

 ・📝 最後のコマンドを保存(リピート対応)

 ・🎨 コマンドに対応するRGB色を辞書から取得

 ・🌈 NeoPixelへ色を書き込み

 ・🖨 色とコマンドをログ出力 cmd=0xXX → RGB (r,g,b)

5.🧹 100回ごとに gc.collect() を実行しメモリ管理

6.⚠️ 受信失敗・ノイズ時はエラーカウント増加 → 10回でIRを再初期化

0 件のコメント:

コメントを投稿

Google Spread Sheetの利用

  以前に ESP32 で作っていたものを、Raspberry Pi Pico W + MicroPythonで再現してみました。 1.Googleスプレッドシートの設定 1.Google Drive → 右クリック → Google スプレッドシート 2.作成して共有をクリック