Tanuki_Bayashin’s diary

電子工作を趣味としています。最近はラズベリーパイPicoというマイコンを使って楽しんでいます

Neopixel を使ってカラフルな表示をした

※なにか気になる点がありましたらコメント欄にご記入ください。また、工作や回路を製作する場合には、細かい作業などに対して、細心の注意を払われるようお願いいたします。

目次

1.はじめに

 Adafruit 社から販売されているNeopixel というフルカラーLEDを多数つなげた製品があります。
今回はこれを利用して色相を変化させて表示してみました。

写真1 レインボーカラーのNeopixel

訂正 rev.1 2023/05/29
その1:プログラムのバグを発見し、修正しました。
具体的内容~リスト1内、71-72行 変数の型が合っていませんでした。
同87行 コメント文がただの文字になっていました。
その2:adafruite が提供しているのはPython ではなく、circuitPythonでした

訂正 rev.2 2023/08/20
リスト1の42行目のコメント文の手前に全角スペースが入っていました。

2.回路構成

図1 実体配線図

 図1に今回用いた回路の実体配線図を示します。制御にはRaspberry Pi Pico(以下Picoと表記します) を利用しました。すでに発表されてから2年ほど経っているので、よく知られているマイコンかと思います。(以下参照)
Buy a Raspberry Pi Pico – Raspberry Pi


Picoの電源は39番ピンで1.8~5.5Vの範囲内で動作します。GNDは合計8本ありますが、どれか1本つないでおけば動くかと思います。
また、今回はNeopixelへの出力ピンをGP16(物理的には21番ピン)に指定しました。

※Pico内にてコードのファイル名を "main.py" として電源をつないだ場合、このプログラムが動作を始めます。

 Neopixelの配線は電源(3.5~5.5V)とGNDと信号線の3本です。安全のため2.2Ωの抵抗を電源ラインに入れてあります。信号線はPicoの21番の端子につなげました。

3.コード

Pico内部のコードをリスト1に示します。

リスト1 main.py

#必用なライブラリをインポートします
import array, utime, math
from machine import Pin
import rp2
from rp2 import PIO, StateMachine, asm_pio

#WS2812 のLEDの数を指定します
NUM_LEDS = 100
# Pico特有のstate machine を用いてWS2812の制御を行っています
#このあたりの処理はリスト1の下のリンク先の図書より引用しました
# PIO State Machine to display ws2812
@asm_pio(sideset_init=PIO.OUT_LOW, out_shiftdir=PIO.SHIFT_LEFT, autopull=True,\
         pull_thresh=24)
def ws2812():
    T1 = 2
    T2 = 5
    T3 = 3
    label("bitloop")
    out(x, 1)                .side(0)    [T3 - 1]
    jmp(not_x, "do_zero")    .side(1)    [T1 - 1]
    jmp("bitloop")           .side(1)    [T2 - 1]
    label("do_zero")
    nop()                    .side(0)    [T2 - 1]

# Create the StateMachine with the ws2812 program, outputting on Pin(16)().
sm = StateMachine(0, ws2812, freq=8000000, sideset_base=Pin(16))

# start the StateMachine, it will wait for data on its FIFO.
sm.active(1)

# Display a pattern on the LEDs via an array of LED RGB values.
ar = array.array("I", [0 for _ in range(NUM_LEDS)])

#position の位置のLEDをred, green, blue の値にて指定します
def ar_color(position, red, green, blue):
    ar[position] = (green<<16) + (red<<8) + blue

#全てのLEDを消灯する関数です
def clear_all():
    for i in range(NUM_LEDS):
        ar[i] = 0
    sm.put(ar,8)

# 色をカラフルに表示するための仕掛けが(ちょこっと)秘められています
def triangle(x):
    if x < 20.0:
        y = int(1.5 * x)
    elif x < 40.0:
        y = 60 - int(1.5 * x)
    else:
        y = 0
    return y

# 6個のリストのメモリーを確保しています
red   = [0.0 for i in range(NUM_LEDS)]
green = [0.0 for i in range(NUM_LEDS)]
blue  = [0.0 for i in range(NUM_LEDS)]

y_red   = [0.0 for i in range(NUM_LEDS)]
y_green = [0.0 for i in range(NUM_LEDS)]
y_blue  = [0.0 for i in range(NUM_LEDS)]

#ここから処理がスタートします
if __name__ == '__main__':
    # Process arguments
    print('Press Ctrl-C to quit.')

    # Neopixelを同心円上に色を変化させて表示するための計算です
    r_max = math.sqrt(4.5 * 4.5 * 2)
    for i in range(NUM_LEDS):
        x = float(i % 10) - 4.5        # 左から何列目かを計算し、4.5を引いています
        y = float(i) / 10.0 - 4.5        # 下から何段目かを計算し、4.5を引いています

        # 中心(4.5, 4.5)からの距離を求め、最大値を1に規格化しています
        r = math.sqrt(x*x + y*y) / r_max

        # 同心円状に色を変えて表示するための処理です。20、0、40と値をずらしています
        red[i]   = (r * 40 + 20.0) % 60.0
        green[i] = (r * 40 + 0.0 ) % 60.0
        blue[i]  = (r * 40 + 40.0) % 60.0
    
    
    try:
        # 無限ループです
        while True:

            #それぞれのLEDの色を計算し、表示しています
            for i in range(NUM_LEDS):
            
                # 1ステップ前から色を1つずらします
                red[i]   = (red[i]   - 1.0) % 60
                green[i] = (green[i] - 1.0) % 60
                blue[i]  = (blue[i]  - 1.0) % 60

                # 関数triangle() により色を計算しています
                y_red[i]   = triangle(red[i])
                y_green[i] = triangle(green[i])
                y_blue[i]  = triangle(blue[i])

                # 色を表示しています
                ar_color(i, y_red[i], y_green[i], y_blue[i])
                sm.put(ar,8)
                utime.sleep_us(100)
            utime.sleep_us(1)

    # ctl-C が押されたときの処理です
    except KeyboardInterrupt:
        #### clear ws2812b
        clear_all()

Raspberry Pi Pico の解説本です。巻末の付録にWS2812Bへの応用として10-29行目の処理が載っていました。(自分は理解できませんでした)
※詳しくは知らないのですが、 Adafruit 社からもNeopixel のためのPython circuitPython で利用できるライブラリが公開されているようです。
www.raspberrypi.com

3.1 コードの解説

 以下に上のコードについて解説します。

10 ー29行:
 WS2812B(Neopixel のうちの一つ)を制御するための一連の処理。Pico独自のState Machine という機能を用いています。※筆者はこの部分は理解していないです。

35ー36行:
 関数 ar_color() の定義です。
 Neopixel の番号と色を指定しています。番号は左下が0番、そのまま右に行って9番まで進み、ひとつ上の段に行くと一番左が10番・・・というようにして一番右上に行くと99番といった具合です。
 色は赤、緑、青にて指定します。それぞれ0-255までの値で指定できます。

39-43行:
 関数clear_all() の定義です。全てのLEDをOFFの状態にします。

45―52行:
 関数 triangle() を定義しています。図2にグラフを示します。(ちょっとした工夫があります。後述します)

64行目の If 文、83行目のtry文、107行目の except 文:
  これらの組み合わせにより、ctlーCを押すことによりプログラムを終了することができます。

69ー80行:
 同心円状に色が異なる模様の初期値を計算し、それぞれのRGBの値を変数red[i]、green[i]、 bulue[i] に代入しています。
 色を変えるのにちょっとした小技を使っているので、後ほど説明します。

85-104行:
 メインループの処理です。色を指定する変数red[i]、green[i]、blue[i]から1を引き60の割ったときのあまり(剰余)を代入しています。

 ・101、102行 ~ i 番目のLEDにrgbを設定し、表示しています。
 ・103、104行~ 待ち時間を100μsec と1μsec 設けています。100μsec 空けておかないとNeopixel の表示がおかしくなるようです。

107行以降:
 ctl-Cが押されたときの処理です。全てのLEDを消灯します。

図2 関数 triangle() のグラフ

3.2 色を変化させる処理

 まず図2で示す関数を用意します。便宜上 triangle() と書いておきます。このとき、初期値として変数red に20.0、green に0.0、blue 40.0を与えます。すると初期の段階では図3で見ると、x=0の状態になります。
 コード内の変数で表現すると、y_red がmax でy_green がこれから大きくなり、y_blue は少しの間0のままになります。つまりこの段階ではLEDは赤く光ります。
 次にこれらの変数に同じ値を加え続けると、図3のグラフの右の方に移行していきます。なので、LEDの色は赤と緑が等しくなる(=黄色)のを通り過ぎ、緑になります。
 さらに時間が経過すると、さらにグラフの右側に進み、シアン、青、マゼンタと徐々に色を変えていき、右端では元の赤に戻るという仕掛けです。
 以上が自分が考えた色を変える仕掛けです。(大したことない)

図3Neopixelを色を変えて光らせる原理

4.動画

以下のように表示されます

youtu.be

以上です。ありがとうございました。