Tanuki_Bayashin’s diary

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

【Raspberry Pi Pico】NeopixelをたくさんのVRで制御する

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

【目次】

1.はじめに

前回投稿した記事でMCP3208を用いて、A/D変換のチャンネルを増やすことを試みました。

tanuki-bayashin.hatenablog.com

それは今回の取り組みと大きく関わっています。Raspberry Pi Pico(以下Picoと表記)では、自由に利用できるA/D変換のポートが3つまでだからです。

以前Neopixelに関する記事で、3つのVRでNeopixelのいろいろな要素を制御しましたが、今回はさらに2つVRを追加して新たな動きを付け加えたいと思います。

以前書いた記事:
tanuki-bayashin.hatenablog.com


2.ソースコード

今回製作したコードを以下に示します。
今までの記事で何度も取り上げて来たり、前回の記事と内容がかぶる部分は説明を省きます。

※お断り:
このソースコードの中で”cvt_col” というライブラリを使用しています。このライブラリは以下のサイトの方が制作されたものです。
#14-5 マイコン内蔵フルカラーLED ~DHAの電子工作教室~【Raspberry Pi Pico】 - YouTube

リスト1 今回のソースコード

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

# クロック周波数を180MHzにしています。(オーバークロックです)
machine.freq(180_000_000)

#SPI通信にてMCP3208から値を持ってくるインスタンスを生成します。
spi_sck = Pin(2)
spi_tx  = Pin(3)
spi_rx  = Pin(4)

# 周波数 400kHz で SPI ペリフェラル 0 を作成
spi = SPI(0, baudrate=400000, sck=spi_sck, mosi=spi_tx, miso=spi_rx)
cs = Pin(5, mode=Pin.OUT, value=1)      # ピン 5 でチップセレクトを作成。
vr_data = [0.0, 0.0, 0.0, 0.0, 0.0]

# 変換係数です
K_speed = 6.0 / (4095)
K_val = 20.0 / (4095)
K_sat = 150.0 / (4095)
K_x = 9.0 / (4095)
K_y = 9.0 / (4095)

#WS2812 のLEDの数を指定します
NUM_LEDS = 100
# Pico特有のstate machine を用いてWS2812の制御を行っています
# 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 SPI_read():
    rxdata = bytearray(15) # 3bytes * 5 VRs
    vr = []
    # ch0の電圧を測定
    cs(0)
    rxdata[0] = spi.read(1, 0x06)[0]
    rxdata[1] = spi.read(1, 0x00)[0]
    rxdata[2] = spi.read(1, 0x00)[0]
    cs(1)
    utime.sleep_us(10)

    # ch1の電圧を測定
    cs(0)
    rxdata[3] = spi.read(1, 0x06)[0]
    rxdata[4] = spi.read(1, 0x40)[0]
    rxdata[5] = spi.read(1, 0x00)[0]
    cs(1)
    utime.sleep_us(10)

    # ch2の電圧を測定
    cs(0)
    rxdata[6] = spi.read(1, 0x06)[0]
    rxdata[7] = spi.read(1, 0x80)[0]
    rxdata[8] = spi.read(1, 0x00)[0]
    cs(1)
    utime.sleep_us(10)

    # ch3の電圧を測定
    cs(0)
    rxdata[9] = spi.read(1, 0x06)[0]
    rxdata[10] = spi.read(1, 0xC0)[0]
    rxdata[11] = spi.read(1, 0x00)[0]
    cs(1)
    utime.sleep_us(10)

    # ch4の電圧を測定
    cs(0)
    rxdata[12] = spi.read(1, 0x07)[0]
    rxdata[13] = spi.read(1, 0x00)[0]
    rxdata[14] = spi.read(1, 0x00)[0]
    cs(1)
    utime.sleep_us(10)

    for i in range(5):
        data = ((rxdata[i*3+1] & 0x0f) << 8) + rxdata[i*3+2]
        vr.append(data)

    return vr

#ここから処理がスタートします
if __name__ == '__main__':
    # Process arguments
    print('Press Ctrl-C to quit.')
    
    r_max = 4.5 * math.sqrt(2)
    color = 0.0
    try:
        # 無限ループです
        while True:
            # A/D変換により値を読み取っています。
            # 変数変換をしています。
            vr_data = SPI_read()
            
            # speed: -3.0~3.0
            speed = vr_data[0] * K_speed - 3.0
            color = (color - speed) % 60.0
            # v: 0~20
            v = int(vr_data[1] * K_val)
            # s:105~255
            s = int(vr_data[2] * K_sat) + 105
            # center_x: 0.0~9.0
            center_x = K_x * vr_data[3]
            # center_y: 0.0~9.0
            center_y = K_y * vr_data[4]

            # Neopixelを同心円上に色を変化させて表示するための計算です
            for i in range(NUM_LEDS):
                # 左から何列目かを計算し、4.5を引いています
                x = float(i % 10) - center_x
                # 下から何段目かを計算し、4.5を引いています
                y = float(i) / 10.0 - center_y
                # 中心(4.5, 4.5)からの距離を求め、最大値を1に規格化しています
                r = math.sqrt(x*x + y*y) / r_max
                r_int = int(r  * 40.0 + color) % 60

                #それぞれのLEDの色を計算し、値を書き込んでいます
                (r, g, b) = cvt_col.hsv_to_rgb(r_int * 6, s, v)
                ar_color(i, r, g, b)
            # 色を表示しています
            sm.put(ar,8)
            utime.sleep_us(100)

    # ctl-C が押されたときの処理です
    except KeyboardInterrupt:
        #### clear ws2812b
        clear_all()
        cs(1) # ペリフェラルを選択解除。
        spi.deinit()

解説:
 SPI機能に関する部分を中心に見ていきます。(前回の記事も参考にしていただけるとありがたいです)

① 2行  SPI用のライブラリをimport しています。

② 11~17行  ピンを指定しSPI用のインスタンスを生成しています。

③ 18,19行  チップセレクトの指定と、読み取った値の格納用の変数を定義しています。

④ 64~111行  関数 SPI_read() の定義をしています。中を見ていきます。
  65,66行  格納用と戻り値用の変数を定義しています。
  67~72行  ch0のデータを読みだしています。
  73行    10μsecだけ待ち時間を設けています。安全のため付け加えました。
  75~105行  ch1~ch4のデータを読み取っています。
  107~109行 生の値を12bitのデータに変換しています。
  111行    戻り値を返しています。

⑤ 112行  メインループに入ります。

⑥ 125行  SPI_read()関数によりA/D変換の値を読み取っています。

⑦ 127~137行 読み取った値 vr_data をもとに、制御に必要な各値を計算しています。

⑧ 141~144行 この部分が、今回新たに付け加えたかった処理に当たります。center_x、center_yを中心に円は広がっていきます。

⑨ 160、161行 Ctrl‐Cが押された後の処理です。Neopixelを消した後、選択を解除し、SPIバスをオフにします。

処理の説明は以上です。

※9行目でクロック周波数を調整していますが(オーバークロック)、値を大きくしすぎるとPicoの調子が悪くなるようです。

3.制御している様子

実際に回路を動かしている様子です。

動画:

youtu.be

以前VR3つで制御していた(スピード、明るさ、鮮やかさ)のに付け加えて、光が広がる中心の位置が縦、横に移動しています。

(それだけだったりします)
( ゚Д゚)

ここまでお付き合い下さり、ありがとうございました。