Adafruit FadeCandyをLED制御に利用する方法

概要

画像提供:Adafruit

Adafruitと、Scanlimeのミカとのコラボレーションによって登場したFadecandyは、USBで制御可能な内蔵ディザリング機能を備えたドライバボードです。Fadecandyはハードウェアとソフトウェアの両方で構成されているため、WS2811/WS2812アドレス指定可能LEDを使用したさまざまなアートプロジェクトをより創作・制御しやすくします。また、マイクロコントローラのリソースへの負担が少ないLEDプロジェクトの視覚的な効果をさらに向上させることができます。FadeCandyは初心者が使いやすいだけでなく、プロフェッショナルにとっても高度なツールとしての役割を果たします。

Fadecandyサーバソフトウェアは、1台または数十台のFadecandyボードと通信します。ソフトウェアの実行環境は、Windows、Linux、Mac OS、またはRaspberry Piのような組み込みプラットフォームです。Open Pixel Controlプロトコルは、ピクセルデータをFadecandyサーバに取り込むシンプルな方法です。コントローラボードごとに最大512個のLEDをサポートし、今回の構成では64個のLEDをともなうストリップ8本の配列になります。ただし、RGBW LEDには対応していません。本記事の発表時点では、RGB LEDのみがサポートされます。この記事では、基本的なプログラミングについても後述します。

今回取り上げる創作プロジェクトでは、1列に52本のAdafruit NeoPixelを使用した照明の列を8列使用してクレーンゲームにプレーヤーを誘い、照明がカウントダウンタイマの視覚的効果も発揮します。アドレス指定可能LEDや従来のArduinoライブラリをGPIOへのダイレクト接続で多数使用すると、プロセッサの処理速度が低下し、ゲーム機能の遅延や実行タイミングの問題が生じます。Raspberry Pi 3をクレーンゲームの音響効果に使う予定なので、FadeCandyによる照明用の通信をPi 3のタスクに加えようと思います。

電力要件の判定

LEDストリップを使用する際の重要な考慮点は、消費電力です。色ごとのLEDの消費電流はわずか20mAですが、たとえば単体のAdafruit NeoPixelは3色を備えており、3色のLEDすべてが動作すると60mAになります。これに、ストリップごとに並列配線された52本のNeoPixelを掛け合わせると、各ストリップの最大電流は3Aを超えます。この電流値に8列を掛け合わせると、その電流消費量は25Aにも達する可能性があり、しかもプロジェクトに使用されるその他の小さなストリップは含まれていません。しかし、はたしてプロジェクトでは、すべてのNeoPixelの全LEDを同時に長期間点灯させるでしょうか?答えは否です。そのため、必要な電源は推測するしかありません。仮に、ある時点でNeoPixelの75%が点灯し、それぞれが1色のみを表示する場合、電流消費量は約6Aまで下がります。照明シナリオをプログラミングして、電流計で消費電流を調べることが、確かな電流消費量を知る唯一の方法です。

設計と配線の問題解決に向けてブレイクアウトPCBの採用を検討

プログラミングを開始する前に、KiCadを使用してカスタムPCBを設計し、FadeCandyの接続を便利なターミナルブロックにブレイクアウトすることで、接続と出力を整理しやすくします。クレーンゲームでは、後ろ向きのLEDを1つのチャンネルに配置し、右向き、左向き、前向きのLEDも同様にしてチャンネルを維持します。上下にある景品出口用のLEDは別々にブレイクアウトし、予備の接続として2つのチャンネルを残します。

ブレイクアウトPCBには、電源バスの潜在的な電流消費に対処できるトレースを組み入れる必要があります。また、ボード用に2オンス銅を採用することで、より大きな電流に対処できるようにもなります。図1と2に、KiCadとDigiKeyのPCBビルダーを使用して作成したPCBの例を示します。

図1:ベンダー提供のベアPCB。

図2:高電流電源バスのトレース。

PCBビルダーの準備

開発が完了したら、KiCadでPCB設計ツールの[File]メニューから[Plot]機能を使用して、カスタムPCBをエクスポートします。[Plot]ダイアログボックスで[Generate Drill File]ボタンを選択し、ガーバードリルファイルを任意のフォルダに保存します。次に、[Plot]ボタンを選択します。KiCadで追加のガーバーファイルが作成され、ドリルファイルと同じフォルダに置かれます。Windowsファイルマネージャーを使用して、ガーバーファイルが含まれているフォルダに移動します。PCBに関連するすべてのファイルを選択し、ファイルのブロックを右クリックして[Send to Compressed (zipped) folder]を選択します。新しいzipフォルダが、元のフォルダに表示されます。

DigiKeyが提供するウェブベースのPCBビルダーはカスタムPCBの注文にも役立ち、幅広いオプションやベンダーを選択できます。PCBビルダーを起動したら、[Upload Gerber File]ボタンを選択し、上記で作成したzipフォルダに移動してフォルダを選択します。PCB Tool Viewerのウィンドウが開き、基板の画像と、作成時に含まれるファイル/レイヤのリストが示されます(図3)。

図3:PCB Builder Viewerは、DigiKeyのPCBビルダーを使用する最初のステップです。

PCB Builder Viewerには、作成されたPCBを調べる多くのツールが用意されています。PCB画像上にカーソルを置いてスクロールすると画像が拡大/縮小し、手のひらカーソルを使用してPCBをあらゆる方向に移動できます。レイヤリストのレイヤごとに目の形をしたアイコンを切り替えることで、レイヤを選択して表示できます。

[Finish Upload]ボタンを選択して、次の注文ステップに進みます。次のウィンドウには、PCBの統計情報および、色や銅厚などのオプションを選択するリストが表示されます(図4を参照)。なお、選択すると価格が変わったり、ベンダーによっては選択したオプションを提供していなかったりする場合もあります。最初に、ボード数量1を指定し、他のオプションを必要に応じて選択します。

図4:PCBの仕様、ベンダー、数量を選択します。

すべての選択を完了して目的のベンダーを指定したら、基板の数量を1ずつ増やして価格を見ます。価格が上がるまで、このステップを繰り返します。この方法により、最も低い価格で生産できる基板の最大数が分かります。注文内容を準備できたら、[Add to Cart]ボタンをクリックします。

カスタムブレイクアウトPCBの組み立て

完成したPCBには、LEDストリップターミナルブロック電源ターミナルブロック、および16ピンのヘッダが実装されます。Adafruit FadeCandyボードにはヘッダピンが実装されており、3Dプリントで作成されたスペーサーとともにPCBのヘッダにそのピンを挿入し、ボードのUSB側をサポートします(図5を参照)。

図5:完全実装済みのカスタムブレイクアウトボード。

ボードを実装してプログラミングを開始するために、26本のAdafruit Neopixelを8列に配置したテストベッドを使用して概念検証を行い、後から実際のクレーンゲームで52本のNeopixelにアップグレードします。

電源と信号の正しい接続を確認しながら、LEDストリップを緑色のターミナルブロックに接続します。電源とグランドの正しい接続を確認しながら、5V電源を黒色のターミナルブロックに接続します。ブレイクアウトボードを使用する前のNeopixelの配線を図6に、配線を整理した様子を図7に示します。

図6:ブレイクアウトボード使用前のテストベッド配線。

図7:ブレイクアウトボードで配線を整理したテストベッド。

ハードウェアと配線の準備ができたら、適切なUSBケーブルを使用してRaspberry Pi 3をFadeCandyに接続し、次にRaspberry Piをモニタ、キーボード、マウスに接続します。システムの電源を入れて、プログラミングを開始します。FadeCandyはクライアントとして設定され、サーバとして機能するPiからUSB経由でデータを受信します。このセットアップでは、PiはUSB経由のシリアル接続でArduino Megaとも通信します。Megaはゲーム機のすべての入出力を処理し、Piには単にゲームがプレーされているかどうかを伝えます。Piは音響や照明の効果も処理します。

FadeCandyには、幅広い機能と用途があります。オンライン上では、単純な例から複雑な例まで数多く紹介されており、その数は増えています。以下のコードは、このプロジェクトの照明で特に必要となる、きわめて基本的なマルチスレッド機能を表しています。Piは、Neopixelを基本の色で満たし、ランダムな閃光を与えてフィールドにアクセントを加えるようにプログラムされています。ゲームがプレーされると、ストリップの中の2列が視覚的なカウントダウンクロックに変わります。実際の映像は、ビデオ1でご覧ください。このプロジェクトで使用しているコードを以下に示します(リスト1)。

コピー#Raspberry Pi Game Machine Script
import serial
import threading
import queue
import random
import opc, time
import pygame

#Initialize the sound mixer
pygame.mixer.init(44100, 16, 2)

#Create a game start sound effect object
Start_Sound = pygame.mixer.Sound("glass.wav")
Start_Sound.set_volume(1.0)
Start_Sound.play()

#Create a tick-tock sound object
Tick_Sound = pygame.mixer.Sound("ticktock.wav")
Tick_Sound.set_volume(1.0)
#Tick_Sound.play(maxtime=600)

#Create an end of game sound object
End_Sound = pygame.mixer.Sound("Buzzer-sound-16.wav")
End_Sound.set_volume(1.0)
#End_Sound.play()

#Build queue objects for transfer between threads
game_q = queue.Queue(1)
users_q = queue.Queue(1)
matrix_q = queue.Queue(1)

#State the NeoPixel array for the testbed
numLEDs = 8*26
pixels = [ (0,0,0) ] * numLEDs

#Set FadeCandy meter start pixel
meterStartPix = 130

#Create a serial communication object for the Mega
serMega = serial.Serial('/dev/ttyACM0', 115200)

#Create a client object for the Open Pixel server
client = opc.Client('localhost:7890')

#Define a function for the t1 thread that reads data from the Mega
def MegaData():
        while True:
                if serMega.inWaiting() > 0:
                        GameDuration = int(serMega.readline())
                        PlayFlag = int(serMega.readline())
                        game_q.put((GameDuration, PlayFlag))
                        TotalUsers = int(serMega.readline())
                        if not users_q.full():
                                users_q.put(TotalUsers)
                                
                time.sleep(0.001)
                        
#Define a function for the t2 thread which runs the time meter Neopixels                            
def RunMeter():

        while True:
                GameDuration, PlayFlag = game_q.get()
                matrix_q.put(PlayFlag)
                SleepNum = (float(GameDuration)/100/27)
              
                if PlayFlag == 1:
                        #Quickly fill the meter with green
                        meterPix = meterStartPix
                        Start_Sound.play()
                        for i in range(0, 26):
                                pixels[meterPix] = (0, 200, 0)
                                client.put_pixels(pixels)
                                time.sleep(.02)
                                meterPix = meterPix+1
                                
                        #Fill the meter with red based on game timer
                        meterPix = meterStartPix + 25       
                        for i in range(0, 26):
                                if not game_q.empty():
                                        GameDuration, PlayFlag = game_q.get()
                                if PlayFlag == 1:
                                        pixels[meterPix] = (200, 0, 0)
                                        Tick_Sound.play(maxtime=600)
                                        client.put_pixels(pixels)
                                        time.sleep(SleepNum)
                                        meterPix = meterPix-1
                                else:
                                        break
                                
                        #Wait a tad bit
                        time.sleep(.50)
                        End_Sound.play()
                        time.sleep(.50)
                        
                        #Quickly Clear the meter with soft white
                        meterPix  = meterStartPix
                        for i in range(0, 26):

                                pixels[meterPix] = (30, 30, 30)
                                client.put_pixels(pixels)
                                time.sleep(.01)
                                meterPix = meterPix+1
                                                                
                        time.sleep(2)
                else:
                        
                        #Quickly Clear the meter with soft white
                        meterPix = meterStartPix
                        
                        for i in range(0, 26):

                                pixels[meterPix] = (30, 30, 30)
                                client.put_pixels(pixels)
                                time.sleep(.01)
                                meterPix = meterPix+1
                                
                        time.sleep(2) 
                time.sleep(0.001)
                
#Define a function for the t3 thread that controls the non-meter Neopixels               
def RunMatrix():
       
        numLEDs = 6*26
      
        while True:
                if not matrix_q.empty():
                        play_flag = matrix_q.get()
                        if play_flag == 1:
                                numLEDs = 5*26
                        else:
                                numLEDs = 6*26   

                r = random.randint(25,85)
                g = random.randint(25,85)
                b = random.randint(25,85)
                Bright = 3 
                DotNum = 10 
                              
                for j in range(5):
                        for h in range(10):
                                pixels = [ (r, g, b) ] * numLEDs

                                for g in range(DotNum):
                                        p = random.randint(0,numLEDs-1)
                                        pixels[p] = (r*Bright, g*Bright, b*Bright)
                                
                                client.put_pixels(pixels)
                                
                                if not matrix_q.empty():
                                        play_flag = matrix_q.get()
                                        if play_flag == 1:
                                                numLEDs = 5*26
                                        else:
                                                numLEDs = 6*26 
                                time.sleep(.1)

#Create thread objects                                
t1 = threading.Thread(target = MegaData)
t2 = threading.Thread(target = RunMeter)
t3 = threading.Thread(target = RunMatrix)
t1.start()
t2.start()
t3.start()

リスト1:クレーンゲームプロジェクトのLED制御に使用されるコード。

まとめ

アドレス指定可能LEDを使用すると満足感を得られますが、作業の難しさもあります。完璧な視覚効果を得るために必要なコードは、マイクロコントローラの他の操作を妨げる原因にもなりがちです。FadeCandyボードや別タイプの専用LEDドライバを使用すると、そのような問題を軽減し、照明シナリオに無限の可能性を開拓できます。カスタムPCBは適切なドライバとともに、入出力の整理や電源配分の優れた方法として利用できます。

著者について

Image of Don Johanneck

DigiKeyのテクニカルコンテンツ開発者であるDon Johanneck氏は、当社に2014年から勤務しています。最近現在の役職に移動し、彼はビデオの説明と製品の内容を書くことを担当しています。Donは、DigiKey奨学金プログラムを通じて、ノースランドコミュニティ&テクニカルカレッジのエレクトロニクステクノロジ&自動化システムで応用科学の準学士号を取得しました。彼はラジオコントロールのモデリング、ヴィンテージの機械の修復、そしていじくり回すことを楽しんでいます。

More posts by Don Johanneck
 TechForum

Have questions or comments? Continue the conversation on TechForum, Digi-Key's online community and technical resource.

Visit TechForum