注釈

こんにちは、SunFounder Raspberry Pi & Arduino & ESP32 Enthusiast Community on Facebookへようこそ!他の愛好家と一緒に、Raspberry Pi、Arduino、ESP32の世界により深く入り込みましょう。

参加する理由

  • 専門家サポート: 購入後の問題や技術的な課題を、コミュニティと私たちのチームの助けを借りて解決します。

  • 学習と共有: ヒントやチュートリアルを交換して、スキルを向上させましょう。

  • 限定プレビュー: 新製品の発表や先行プレビューに早期アクセスできます。

  • 特別割引: 最新製品を特別割引でお楽しみいただけます。

  • 季節限定キャンペーンとプレゼント: プレゼント企画やホリデーキャンペーンに参加しましょう。

👉 一緒に発見し、創造する準備はできましたか? [こちら] をクリックして、今すぐ参加しましょう!

4.10 Hue Knob

はじめに

このレッスンでは、 Hue Knob (色相ノブ)を作成します。これは、ロータリーエンコーダーを使用して円形 WS2812 LED モジュールの色相(Hue)を調整できるインタラクティブなカラーコントローラーです。

この WS2812 LED モジュールには、12 個の個別に制御可能な WS2812 RGB LED が搭載されており、Fusion HAT+ の SPI ベース NeoPixel インターフェースを通して制御されます。 外部のロータリーエンコーダーは、標準の GPIO ピンを介してリアルタイムのユーザー入力を提供します。

エンコーダーを回転させると、12 個の LED が RGB カラースペクトル全体を滑らかに循環します。 エンコーダーの内蔵ボタンを押すと、色相が初期値にリセットされます。


必要なもの

このプロジェクトで必要なコンポーネントは以下のとおりです。

COMPONENT INTRODUCTION

PURCHASE LINK

ジャンパーワイヤー

購入

ロータリーエンコーダモジュール

購入

Circular WS2812 LED Module

-

Fusion HAT+

-

Raspberry Pi

-


配線図

コンポーネントの組み立ては、以下の配線図を参照してください。

../_images/4.10_hub_color_bb.png

セットアップ手順

  1. コードを実行する前に、必要なライブラリをインストールします。

    このライブラリは、SPI 通信を使用して NeoPixel LED を制御するための機能を提供します。

    sudo pip3 install adafruit-circuitpython-neopixel-spi --break
    
  2. このチュートリアルで使用するすべてのサンプルコードは ai-lab-kit ディレクトリに含まれています。以下の手順でサンプルを実行してください。

    cd ~/ai-lab-kit/python/
    sudo python3 4.10_Hue_Knob.py
    
  3. スクリプトを実行すると、WS2812 LED リングがロータリーエンコーダーに応答します。

    • LED は赤色(Hue 0°)で開始します。

    • エンコーダーを回すと RGB カラーホイール全体を滑らかに循環します(赤 → 黄 → 緑 → 青 → 紫 → 赤)。端末には現在の Hue と RGB 値が表示されます。

    • エンコーダーのボタンを押すと Hue が 0°(赤)にリセットされます。

    • Ctrl+C を押すと終了し、プログラム終了前にすべての LED が消灯します。


コード

以下は、このプロジェクトで使用する Python スクリプトです。

#!/usr/bin/env python3

from fusion_hat.motor import Motor
from fusion_hat.pin import Pin, Mode, Pull
from fusion_hat.adc import ADC
from time import sleep, time
import math

BtnPin = Pin(22, mode=Mode.IN, pull=Pull.DOWN)
motor = Motor("M0")
thermistor = ADC("A3")

level = 0
currentTemp = None
markTemp = None

PRINT_INTERVAL = 1.0
_last_print = 0.0

button_event = False  # flag: button was pressed

def temperature(samples=5, delay=0.01):
   """Read thermistor multiple times and return averaged Celsius (float) or None."""
   vals = []
   for _ in range(samples):
      analogVal = thermistor.read()
      Vr = 3.3 * float(analogVal) / 4095.0
      if (3.3 - Vr) <= 0.1:
            return None
      Rt = 10000.0 * Vr / (3.3 - Vr)
      tempK = 1.0 / (((math.log(Rt / 10000.0)) / 3950.0) + (1.0 / (273.15 + 25.0)))
      vals.append(tempK - 273.15)
      sleep(delay)
   return sum(vals) / len(vals)

def motor_run(lv):
   lv = max(0, min(4, lv))
   motor.power(0 if lv == 0 else lv * 25)
   return lv

def changeLevel():
   """Button press: cycle level 0~4 and set a flag for main loop to print."""
   global level, button_event
   level = (level + 1) % 5
   button_event = True

BtnPin.when_activated = changeLevel

def main():
   global level, currentTemp, markTemp, _last_print, button_event

   markTemp = temperature()
   while True:
      currentTemp = temperature()
      if currentTemp is None:
            print("Sensor read failed. Please check the sensor.")
            sleep(0.5)
            continue

      # Handle button event in main loop (stable timing)
      if button_event:
            button_event = False
            markTemp = currentTemp
            print(f"[Button] Level -> {level} | Temp: {currentTemp:.2f} °C | Mark: {markTemp:.2f} °C")

      # Periodic temperature print
      now = time()
      if now - _last_print >= PRINT_INTERVAL:
            if markTemp is None:
               markTemp = currentTemp
            print(f"Temp: {currentTemp:.2f} °C | Mark: {markTemp:.2f} °C | Level: {level}")
            _last_print = now

      # Auto adjust level based on ±5°C
      if markTemp is None:
            markTemp = currentTemp

      if level != 0:
            diff = currentTemp - markTemp
            if diff <= -5:
               level = max(0, level - 1)
               markTemp = currentTemp
               print(f"[Auto] Temp down -> Level {level} (Temp: {currentTemp:.2f} °C)")
            elif diff >= 5:
               level = min(4, level + 1)
               markTemp = currentTemp
               print(f"[Auto] Temp up   -> Level {level} (Temp: {currentTemp:.2f} °C)")

      level = motor_run(level)
      sleep(0.5)

try:
   main()
except KeyboardInterrupt:
   print("\nExiting...")
finally:
   motor.stop()
   sleep(0.1)

コードの解説

  1. NeoPixel の初期化

    • スクリプトは SPI を使用して NeoPixel LED リング/ストリップ( LED_COUNT = 12 )を制御します。

    • auto_write=False を設定しているため、LED の更新は strip.show() が呼ばれたときのみ実行されます。これによりちらつきを防ぎ、パフォーマンスが向上します。

    • 起動時には strip.fill(0)strip.show() によって LED をすべて消灯します。

  2. ロータリーエンコーダーのハードウェア設定

    • 3 本の GPIO ピンを使用します: - CLK_PINDT_PIN は回転方向とステップを検出するためのクアドラチャ信号です。 - SW_PIN はエンコーダーボタン入力です。

    • すべてのピンは内部プルアップ( Pull.UP )を使用し、ボタンは アクティブ LOW (押されたとき 0 )です。

  3. 主要パラメータ(動作調整)

    • DETENTS_PER_CYCLE は、色相を 0~360° 一周させるのに必要な物理クリック数を定義します。 - 値を大きくすると、より細かい色調整が可能になります。

    • TRANSITIONS_PER_DETENT は、生のクアドラチャ信号を 1 クリックとして換算するための係数です。 - 多くのエンコーダーは 1 クリックにつき 2 つの遷移を出力しますが、4 つ出力するものもあります。この値を調整すると精度が向上します。

  4. hue_to_rgb()

    • 0.0 ~ 1.0 の範囲の色相値を RGB タプル (R, G, B)0 ~ 255 )に変換します。

    • HSV カラーモデルを利用して滑らかな色変化を実現します。

  5. apply_color_from_detent()

    • エンコーダーのクリック数を色相インデックスに変換します: - hue_idx = detent % DETENTS_PER_CYCLE

    • 色相を RGB に変換し、 strip.fill(color)strip.show() で LED を更新します。

    • last_hue_idx を使用して、色が変化していない場合の不要な更新を防ぎます。

    • デバッグ用に、現在の色相角度、クリック値、RGB カラーを端末へ出力します。

  6. reset_all()

    • 内部カウンターをリセットします: - raw (生の遷移カウント) - last_detent (最後に表示されたクリック値) - last_hue_idx (最後に表示された色相インデックス)

    • apply_color_from_detent(0) を呼び出し、LED を初期色(Hue = 0°)に戻します。

  7. メインループ(ポーリング + 回転方向検出)

    • プログラムは CLK を継続的にポーリングして変化を監視します。 - CLK が変化した場合、エンコーダーがクアドラチャシーケンス上で 1 ステップ移動したことを意味します。

    • 回転方向は DT と CLK を比較して判定します。 - dt.value() != c の場合は一方の回転方向(増加) - それ以外の場合は減少

    • 生の遷移カウントはクリック数に変換されます。 - detent = raw // TRANSITIONS_PER_DETENT

    • LED の色は、クリック値が変化したときのみ更新されます。

  8. ボタンリセットとデバウンス

    • ボタンが押された場合( sw.value() == 0 )、 reset_all() が呼び出されます。

    • 短いデバウンス遅延を入れ、ボタンが離されるまで待機することで、1 回の押下で複数回リセットされるのを防ぎます。

  9. 安全な終了

    • Ctrl + C を押すとプログラムが終了します。

    • finally ブロックで strip.fill(0)strip.show() を実行し、すべての LED を消灯して安全な状態で終了します。

トラブルシューティング

  • LED が点灯しない

    • WS2812 LED モジュールの配線を確認してください。

    • Fusion HAT+ の SPI NeoPixel インターフェースが有効になっているか確認してください。

    • 対応している WS2812 / WS2812B LED モジュールを使用していることを確認してください。

  • 色の変化が速すぎる/遅すぎる

    • STEPS_PER_CYCLE を調整して感度を変更してください。

  • ボタンを押しても色相がリセットされない

    • SW ピンが GPIO27 に接続されていることを確認してください。

    • ピン設定が pull=Pin.PULL_UP になっているか確認してください。

  • スクリプトがすぐ終了する

    • pause()signal から正しくインポートされているか確認してください。

    • SPI を使用している別のプロセスが動作していないか確認してください。

試してみよう

このプロジェクトをさらに拡張してみましょう。例えば次のようなアイデアがあります。

  1. 明るさ調整の追加

    別の変数(例:エンコーダーの押し込み+回転)を使い、LED の明るさを 0~255 で調整できるようにします。

  2. 複数の表示モードを追加

    エンコーダーを押すことでモードを切り替えます。

    • 単色表示(デフォルト)

    • レインボーアニメーション

    • ブリージング(呼吸)エフェクト

    • カラー ワイプ

  3. 電源トグル機能

    エンコーダーボタンを長押しすると LED リングを ON/OFF できるようにします。

  4. 色変化をより滑らかにする

    STEPS_PER_CYCLE を増やすか、補間処理を追加してより滑らかな色遷移を実現します。

  5. 回転方向フィードバック

    時計回りに回すと LED を緑、反時計回りでは赤に点灯させるなどのフィードバックを追加します。

これらの小さな拡張により、シンプルな「Hue Knob」を多用途な RGB コントローラーへ発展させることができます。