如何使用 Adafruit FadeCandy 来控制 LED

概述

图片来源:Adafruit

Adafruit 与 Scanlime 旗下 Micah 合作推出了一款内置抖动功能的驱动板 Fadecandy,可以通过 USB 进行控制。Fadecandy 包括了硬件和软件,让 WS2811/WS2812 可寻址 LED 艺术项目变得更容易构建和控制,项目外观更漂亮,对微控制器资源的消耗也更少。FadeCandy 对初学者来说很容易上手,对专业人士来说也是一种高级工具。

Fadecandy 服务器软件可以与一块或几十块 Fadecandy 板通信。它可以在 Windows、Linux、Mac OS 或 Raspberry Pi 等嵌入式平台上运行。开放像素控制 (OPC) 协议是一种将像素数据传送到 Fadecandy 服务器上的简单方法。每块控制器板最多支持 512 个 LED,排列成 8 条,每条 64 个,但不支持 RGBW LED。在本文发表时,只支持 RGB LED。本文后面将讨论基本编程。

当下这个项目在八个列中各使用了 52 个 Adafruit NeoPixels,旨在吸引玩家参加夹娃娃机游戏,同时也充当倒计时器使用。使用大量可寻址 LED 和直接连接到 GPIO 的传统 Arduino 库会拖累处理器,导致游戏功能的延迟以及执行时间问题。由于 Raspberry Pi 3 已经安排给夹娃娃机提供声音效果,与 FadeCandy 的照明通信将添加到 Pi 3 任务列表中。

确定电源要求

在使用 LED 灯条时,一个重要的考虑因素是耗电量。虽然每个颜色的 LED 只用 20 毫安的电流,但以 Adafruit NeoPixel 为例,如果三个 LED 都处于激活状态,那么每个 Adafruit NeoPixel 有三种颜色,总共 60 毫安。乘以每灯条上并联的 52 个 NeoPixels,每个灯条的最大电流将超过 3 安培。将这一电流乘以 8 列,潜在电流为 25 安培,这还不包括项目周围的各种小灯条。但是,为什么一个项目会让每个 NeoPixel 中的所有 LED 都同时长时间开启呢?答案是不会,所以电源的选择就成了一个猜谜游戏。假设 75% 的 NeoPixels 在任何时候都处于开启状态,并且每个都只显示一种颜色,那么电流就会下降到 6 安培左右。对照明方案进行编程,并用仪表测试电流,是确定的唯一方法。

考虑使用分线板来解决设计和布线问题

在开始编程之前,使用 KiCad 设计一个定制 PCB,将 FadeCandy 连接分接到方便的端子块上,以便于进行连接和输出布局。对于夹娃娃机来说,面向后方的 LED 在一个通道上,面向右方、面向左方和面向前方的 LED 也在一个通道上,以保留通道。上部和下部奖品滑道 LED 单独分接,留下两个通道用于辅助连接,以防万一。

分线板必须包含能够处理电源总线潜在电流的印制线。为电路板选择 2 盎司铜片也有助于处理更大的电流。图 1 和图 2 显示了使用 KiCad 和 DigiKey 的 PCB Builder 工具创建的 PCB 实例。

图 1.厂家的裸板。

图 2.大电流电源总线印制线。

PCB Builder 工具准备

一旦完成开发,可使用 KiCad 中 PCB 设计工具中 File 菜单下的 Plot 功能导出定制 PCB。在 Plot 对话框中,选择 Generate Drill File 按钮,将 Gerber 钻孔文件保存到您选择的文件夹中。接下来,选择 Plot 按钮。KiCad 将创建另外的 Gerber 文件,并将其放在与钻孔文件相同的文件夹中。使用 Windows 文件管理器,导航到包含 Gerber 文件的文件夹。选择所有与 PCB 有关的文件;然后右击选中的文件,选择发送到压缩 (zipped) 文件夹。在原文件夹中会出现一个新的压缩文件夹。

DigiKey 基于 Web 的 PCB Builder 工具有助于订购定制 PCB,选项多,供应商选择范围广。一旦启动 PCB Builder 工具,选择 Upload Gerber File(上传 Gerber 文件)按钮;然后导航并选择先前创建的压缩文件夹。一个 PCB Tool Viewer 窗口打开,显示电路板的图像和生产中要包括的文件/层的列表,如图 3 所示。

图 3.PCB Builder Viewer;使用 DigiKey 的 PCB Builder 工具的第一步。

PCB Builder Viewer 提供了许多工具来检查提供的 PCB。将光标悬停在 PCB 图像上并滚动鼠标滚轮即可放大和缩小图像,同时光标变为手形,可以在各个方向上移动 PCB。通过切换图层列表中每个图层的眼形图标,可以有选择地查看图层。

选择 Finish Upload(完成上传)按钮,进入下一订购步骤。下一个窗口显示了 PCB 的统计数据,以及可供选择的选项清单,如颜色和铜厚(见图 4)。注意,进行选择会改变价格和供应商供货量,同时他们可能不提供所选择的选项。从一块电路板的数量开始,然后根据需要进行其他选择。

图 4.选择 PCB 规格、供应商和数量。

当所有选择都做完了,并且确定了首选的供应商,然后将板子的数量增加 1,观察价格。重复这一步骤,直到价格增加。这种方法可确定能以最低价格生产的最大板子数量。准备好订购时,选择 Add to Cart(添加至购物车)按钮。

组装定制的分线板

完成的 PCB 上有 LED 灯条端子块电源端子块和一个 16 针针座。Adafruit FadeCandy 板上有排针,并与 3D 打印的垫片一起插入 PCB 上的针座,以支持电路板的 USB 端。见图 5。

图 5.填充完整的定制分线板。

为了完成该板并开始编程,我们用一个由 8 列 26 个 Adafruit Neopixels 组成的测试平台来演示这个概念,后来在实际的夹娃娃机器中升级到 52 个 Neopixels。

将 LED 灯条连接到绿色的端子块上,检查电源和信号连接是否正确。将 5 V 电源连接到黑色的端子块上,检查电源和信号连接是否正确。图 6 显示了使用分线板之前的 NeoPixel 布线,图 7 显示了布线改进。

图 6.使用分线板前的试验台接线。

图 7。使用分线板后的试验台接线。

在硬件和接线就位后,使用适当的 USB 电缆将 Raspberry Pi 3 连接到 FadeCandy;然后将 Raspberry Pi 连接到显示器、键盘和鼠标。给系统通电,开始编程。FadeCandy 被设置成一个客户端,通过 USB 从作为服务器的 Pi 那里接收数据。在这个设置中,Pi 也通过 USB 的串行连接与 Arduino Mega 进行通信。Mega 处理游戏机的所有输入和输出,并简单地告诉 Pi 游戏是否正在进行中。Pi 处理声光效果。

FadeCandy 的功能和应用范围很广。许多简单和复杂的例子都可以在网上找到,并且有更多的实例不断加入。下面的代码展示了一些非常基本的多线程功能,以满足这个项目照明的具体需要。Pi 被编程为用基色淹没 Neopixels,然后增加随机的闪光来突现这个领域。当游戏进行时,其中两个灯条被转换为可视化的倒计时时钟。参见视频 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

Don Johanneck 是 DigiKey 的技术内容开发人员,自 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