1. 项目概述:打造你的专属光之宝石
如果你也喜欢在漫展上展示自己精心制作的道具,或者单纯想为房间增添一个酷炫的、可交互的发光装饰,那么这个项目正适合你。今天要分享的,是如何亲手制作一个灵感源自经典游戏《刺猬索尼克》中的“混沌翡翠”的蓝牙控制LED发光道具。它不仅仅是一个静态的模型,更是一个融合了3D打印、嵌入式编程和无线控制技术的完整创客项目。核心在于使用Adafruit的Circuit Playground Bluefruit开发板,通过CircuitPython编程,让你能用手机App随心所欲地改变内置10颗NeoPixel LED灯的颜色和亮度,让“翡翠”真正焕发生命力。
这个项目特别适合有一定动手能力的电子爱好者、Cosplay道具制作者,以及想要入门物联网和智能硬件的朋友。你不需要是编程专家,CircuitPython的语法非常接近Python,易于上手;也不需要昂贵的设备,一台普通的FDM 3D打印机和基础的焊接工具(甚至免焊)就能完成。整个过程,你会接触到从3D模型切片打印、微控制器固件烧录、库文件管理,到蓝牙通信协议应用等一系列实用技能。最终拿到手里的,是一个完全由你定制、可无线控制的个性化发光艺术品。下面,我就把整个从设计思路到组装调试的完整流程和盘托出,其中包含了不少官方指南里未必会提的实操细节和避坑心得。
2. 核心硬件选型与设计思路解析
2.1 主控板:为什么是Circuit Playground Bluefruit?
在这个项目中,核心大脑是Adafruit的Circuit Playground Bluefruit(后文简称CPB)。选择它,而非普通的Arduino或ESP32,是经过深思熟虑的。
首先,集成度与易用性是最大优势。CPB板载了10颗可独立寻址的RGB NeoPixel LED、一个运动传感器、一个温度传感器、一个光传感器、一个蜂鸣器以及多个触摸感应焊盘。这意味着我们无需额外焊接任何LED灯带或复杂的传感器电路,大大降低了硬件门槛和出错概率。对于发光道具这个应用场景,板载的10颗LED已经足够产生均匀、绚烂的光效。
其次,内置蓝牙低能耗(BLE)是关键。CPB搭载了Nordic nRF52840芯片,支持BLE 5.0。这使得它与手机的连接非常稳定、功耗极低。我们无需像使用HC-05/06蓝牙模块那样处理复杂的串口AT指令,CircuitPython的adafruit_ble库已经为我们封装好了所有蓝牙通信的底层细节,开发者只需关注应用逻辑。
最后,CircuitPython开发环境对新手极其友好。它支持代码热重载(修改代码后保存即生效),并通过USB存储盘(CIRCUITPY)的形式管理文件和库,比传统的Arduino IDE编译-上传流程直观太多。对于快速原型开发和调试,这能节省大量时间。
注意:市面上有多个版本的Circuit Playground,务必确认你购买的是带有“Bluefruit”字样的版本。早期的Circuit Playground Express不具备蓝牙功能,无法实现本项目中的手机遥控。
2.2 电源方案:锂电池的选型与安全考量
一个便携的发光道具,稳定的无线供电是灵魂。项目推荐使用3.7V的锂聚合物(LiPo)电池。这里有几个需要仔细权衡的点:
- 容量与体积的平衡:官方推荐了500mAh、420mAh和350mAh三种规格。容量越大,续航越久,但电池也越厚。经过实测,500mAh电池(型号1578)对于“中号”和“大号”翡翠外壳的空间绰绰有余,能提供长达数小时的续航。但对于“小号”外壳,其内部空间紧张,350mAh带短线的电池(型号4237)是更稳妥的选择,避免组装时挤压电池导致风险。
- 充电安全:强烈建议使用Adafruit的Micro-Lipo充电器(产品号1904或4410)。这些充电器内置了完善的充电管理芯片(如MCP73831),能提供恒流/恒压充电,并具备电池温度监控、自动停充等功能。切勿直接通过CPB板上的USB口长时间为电池充电,虽然CPB有简单的充电电路,但其充电电流和保护措施不如专用充电器完善。
- 电池接口:CPB的电池接口是JST PH 2-pin。购买电池时请确认接头匹配。如果不匹配,需要自行焊接或使用转接线。操作心得:焊接JST接头时,先给线头和插座针脚分别上锡,然后用镊子辅助快速焊接,避免热量堆积过多损坏塑料插座。
2.3 结构设计:3D打印外壳的工程优化
原作者提供了三种尺寸(小、中、大)的模型,这不仅仅是比例缩放,更包含了针对不同打印机性能和使用场景的工程优化。
“无支撑”设计是模型的一大亮点。所有悬垂结构的角度都经过精心设计,确保在FDM打印机上能以45度法则成功打印,避免了拆除支撑带来的表面损伤和清理麻烦。这对于追求最终作品表面光洁度的道具来说至关重要。
壁厚与透光性的权衡:模型壁厚统一为1.2mm。这个厚度经过计算,既能保证结构强度(尤其是卡扣部分),又能让内部LED光线充分扩散,形成均匀的“体发光”效果,而不是一个个明显的点光源。打印时建议使用2圈壁厚(Shell)和0.4mm的挤出线宽,这样正好是3条挤出线(0.4mm * 3 = 1.2mm),使得外壳壁均匀致密,透光效果最佳。
装配结构:采用了“PCB支架+翡翠支架”的二级固定方式。PCB支架通过卡扣固定CPB主板,再通过M2.5螺丝与翡翠支架连接,最后翡翠支架用M3螺丝固定在底壳上。这种模块化设计的好处是,你可以轻松拆开更换电池或升级主板,而无需破坏整个外壳。卡扣设计也使得上下壳的闭合非常方便。
3. 软件环境搭建与代码深度解析
3.1 CircuitPython固件烧录:从零开始的正确姿势
拿到全新的CPB板后,第一步是让它运行CircuitPython。这个过程虽然简单,但有几个细节决定了成功率。
- 获取正确的UF2文件:前往CircuitPython官网,找到“Circuit Playground Bluefruit”的页面,下载最新的
.uf2固件文件。关键点:务必下载稳定版(Stable),而非开发版(Alpha/Beta),除非你需要特定新功能。 - 进入引导加载程序模式:用一条数据线(非充电线)连接CPB和电脑。快速双击板子中央的复位按钮。成功的标志是:10颗NeoPixel先全部变红,然后全部变绿,随后电脑上会出现一个名为
CPLAYBTBOOT的U盘。常见问题:如果LED长亮红色或不反应,99%是USB线或USB口的问题。请换一条确认能传输数据的手机数据线,并尝试电脑后置的USB接口。 - 拖放烧录:将下载的
.uf2文件拖入CPLAYBTBOOT盘符。完成后,该盘符会消失,并出现一个新的名为CIRCUITPY的盘符。这表明CircuitPython系统已成功启动。
实操心得:在Windows系统上,如果
CPLAYBTBOOT盘符出现后很快消失,可能是系统自动安装了驱动并尝试挂载。此时可以尝试更快速地完成双击复位和拖放文件的操作,或者在设备管理器中禁用该设备的自动驱动安装。
3.2 库文件管理与项目代码部署
CircuitPython的库管理方式非常直观:将需要的库文件复制到CIRCUITPY盘下的lib文件夹中。对于本项目,我们需要以下库(可通过Adafruit的CircuitPython库包获取):
adafruit_ble:蓝牙通信核心库。adafruit_bluefruit_connect:用于解析Adafruit Bluefruit LE Connect App发送的标准数据包。neopixel:控制NeoPixel LED。
高效部署方法:官方提供了“项目包”下载,这是一个zip文件,里面已经包含了必要的库和写好的code.py。解压后,将对应CircuitPython版本的文件夹内容全部复制到CIRCUITPY根目录即可。这是最不容易出错的方式。
代码解析:让我们深入看一下核心的code.py,理解其工作原理:
import board import neopixel from adafruit_bluefruit_connect.packet import Packet from adafruit_bluefruit_connect.color_packet import ColorPacket from adafruit_ble import BLERadio from adafruit_ble.advertising.standard import ProvideServicesAdvertisement from adafruit_ble.services.nordic import UARTService ble = BLERadio() uart_service = UARTService() advertisement = ProvideServicesAdvertisement(uart_service) pixels = neopixel.NeoPixel(board.NEOPIXEL, 10, brightness=0.1) while True: # 1. 广播阶段:等待手机连接 ble.start_advertising(advertisement) while not ble.connected: pass # 空循环,等待连接 ble.stop_advertising() # 2. 连接保持阶段:处理手机发来的数据 while ble.connected: if uart_service.in_waiting: # 检查是否有数据到来 packet = Packet.from_stream(uart_service) # 解析数据流为包 if isinstance(packet, ColorPacket): # 判断是否是颜色包 print(packet.color) # 串口打印颜色值(调试用) pixels.fill(packet.color) # 用收到的颜色填充所有LED逻辑剖析:代码建立了一个状态机。首先,板子不断广播自己提供UART服务。当手机App连接后,停止广播,进入连接循环。在连接状态下,它持续监听UART通道。一旦收到数据,就尝试解析成标准包。如果解析出来是一个ColorPacket(即从App颜色选择器发来的数据),则提取其中的RGB颜色值,并调用pixels.fill()方法设置所有LED的颜色。brightness=0.1是一个初始设置,亮度较低以省电,后续可通过App调整。
3.3 手机App配置与连接技巧
在手机端,你需要安装“Adafruit Bluefruit LE Connect”应用。连接步骤如下:
- 打开App,确保手机蓝牙已开启。
- 扫描设备,你应该能看到一个名为“CIRCUITPYxxxx”的设备(xxxx是CPB的唯一标识符)。
- 点击连接。
- 连接成功后,进入“Controller”控制器界面,选择“Color Picker”颜色选择器。
连接稳定性技巧:
- iOS用户:如果遇到连接频繁断开,可以尝试在手机的蓝牙设置中找到该设备,忽略它,然后在App内重新扫描连接。有时系统缓存会导致问题。
- Android用户:确保App拥有所有必要的权限(定位权限常用于蓝牙扫描)。如果连接后发送颜色无反应,检查一下手机是否处于省电模式,该模式可能会限制后台蓝牙通信。
- 通用建议:在代码中,可以通过
ble.name = \"MyEmerald\"来修改蓝牙广播的名称,这样在多个项目中更容易区分。
4. 3D打印工艺全流程详解
4.1 材料选择:透光PLA的奥秘
外壳的视觉效果很大程度上取决于打印材料。官方推荐使用“自然透明”或“象牙白”的半透明PLA。这里有几个重要的概念需要厘清:
- 完全透明 vs. 半透明:完全透明的PLA(如完全透明的PETG)打印出来,你会清晰地看到内部的电路板和LED灯珠,破坏了“宝石”的质感。而半透明PLA更像磨砂玻璃,它能将点光源扩散成柔和的面光源,形成均匀的发光体,这正是我们想要的“翡翠”内部发光效果。
- 颜色影响:象牙白(Ivory White)或自然色(Natural)的半透明PLA,其本身带有非常浅的暖黄色调,这模拟了翡翠的底色。当LED发出绿光时,会与底色混合,产生更丰富、更深邃的绿色光泽,比使用纯白色半透明PLA的效果更佳。
- 品牌选择:除了推荐的品牌,像eSUN、Sunlu、Overture等主流品牌的半透明PLA系列也都有不错的表现。选购时关键看用户评价中的“透光性”和“层间结合力”。
4.2 切片参数精细化设置
使用Cura、PrusaSlicer等软件进行切片时,以下参数需要特别关注:
- 层高:建议使用0.2mm层高。这是一个在打印质量、强度和耗时之间取得良好平衡的通用值。过低的层高(如0.1mm)对透光性提升有限,但会大幅增加打印时间。
- 壁厚与线宽:如前所述,模型壁厚1.2mm。设置壁厚循环数为2,挤出线宽为0.4mm(与喷嘴直径一致)。这样切片软件会精确计算并生成3条挤出路径来填满1.2mm的厚度,确保壁的均匀和密闭性,避免漏光。
- 顶部/底部厚度:建议设置为0.8mm(约4层)。足够的顶层厚度可以防止在强光下看到内部的填充图案(infill pattern)。
- 填充密度:对于这种装饰性模型,10%-15%的填充率足以提供结构支撑。填充图案建议选择“网格”或“三角形”,它们能提供均匀的支撑且不影响透光。
- 打印速度:外壁打印速度建议设置在40-50mm/s。过快的速度可能导致挤出不足,使墙壁出现微孔,影响强度和光扩散效果。
- 冷却:必须开启冷却风扇,并且建议设置为100%。良好的冷却能确保PLA迅速固化,减少模型翘曲,并让每一层的轮廓更清晰,最终提升表面质量和透光均匀性。
4.3 支撑、附着与后处理
- 支撑:如设计所述,所有模型均无需任何支撑。在切片软件中务必关闭支撑生成选项。
- 附着:这是打印成功的关键一步。模型顶部(emerald-xx-top.stl)是一个大面积的悬空薄片,极易在打印过程中从热床上翘起或脱落。强制要求使用裙边(Brim)。建议裙边线数设置为8-10条,宽度5-8mm。这能提供巨大的附着面积。打印完成后,用美工刀或铲子小心地从模型底部剥离裙边即可。
- 后处理:打印完成后,仔细检查并移除所有的裙边。可以用细砂纸(如600目)轻轻打磨底部与热床接触的边缘,使其更平整,便于上下壳的紧密闭合。切勿对外壳的透光面进行打磨或上漆,这会严重破坏光扩散效果。
5. 机械组装与电子集成步骤
5.1 步骤详解:从零件到整体
组装过程讲究顺序和手法,正确的流程能避免返工和损坏零件。
- 安装电池:将锂电池放入PCB支架底部的预留空间。注意电池方向,让电池的JST插头朝向PCB支架上为电线预留的开口处。轻轻理顺势线,避免过度弯折。
- 安装主控板:将Circuit Playground Bluefruit板对准PCB支架。板子上的10个 NeoPixel LED应朝外(即未来朝向翡翠外壳的顶部)。仔细对准板子上的定位孔和支架上的塑料定位柱,轻轻按压,你会听到清脆的“咔嗒”声,表示板子四周的卡扣已经锁紧。技巧:可以先对齐一边的卡扣按下,再按压另一边,交替进行,更容易安装。
- 连接支架与底座:将翡翠支架(bracket)放置到PCB支架下方,对齐四个M2.5的螺丝孔。从上方插入四颗M2.5x6mm的尼龙螺丝,然后在下方用尼龙防滑螺母拧紧。使用尼龙硬件的好处是绝缘、轻便,且不会划伤PCB。拧紧时力度适中,感觉螺母压紧支架即可,不要过度用力导致尼龙螺纹滑丝或支架变形。
- 固定到底壳:将上一步组装好的“主板电池模块”翻转过来,放置到3D打印的翡翠底壳(emerald-xx-bot.stl)内部。你会看到底壳内部有四个对应的立柱。使用四颗M3x6mm的金属螺丝,从翡翠支架的孔中穿下,拧入底壳的立柱中。金属螺丝能提供更强的连接力。同样,拧紧至感觉有阻力即可,避免撑裂3D打印的塑料立柱。
- 连接电池:最后,将电池的JST插头插入CPB板上标有“BAT”的端口。注意插头的方向,通常有凸起的一面对应端口有凹槽的一面。
- 闭合外壳:这是最令人满足的一步。将翡翠顶壳(emerald-xx-top.stl)对准底壳。观察结构,底壳边缘内侧有一圈微小的卡钩,顶壳边缘有一圈对应的凹槽。将上下壳大致对齐,然后均匀施压,沿着边缘一路按压,你会听到一连串轻微的“啪啪”声,这表明卡扣已全部啮合。闭合后,外壳应严丝合缝,没有明显的缝隙或翘曲。
5.2 组装过程中的常见问题与排查
问题:主板卡扣无法扣紧或感觉松动。
- 排查:检查PCB支架的卡扣臂是否有打印缺陷或断裂。确认CPB板是否完全放置到位,没有被电池或电线顶住。
- 解决:如果卡扣过松,可以在CPB板与卡扣接触的边缘贴一小条电工胶布增加厚度。如果卡扣断裂,可以使用一小滴氰基丙烯酸酯胶水(快干胶)进行修复,但务必小心,不要将活动关节粘死。
问题:上下壳无法紧密闭合,中间有缝隙。
- 排查:首先检查内部组件,尤其是电池和电线,是否堆叠过高,顶住了顶壳。其次,检查底壳边缘的卡钩是否打印完整,有无拉丝或瘤状物阻碍结合。
- 解决:重新整理内部线缆,确保电池平铺。用镊子或刀尖小心清理卡钩上的打印瑕疵。如果缝隙依然存在,可能是打印收缩导致尺寸略有偏差,可以尝试用热风枪或吹风机对底壳边缘(卡钩部分)进行非常短暂和轻柔的加热,使其微微软化,然后迅速合盖并保持压力直至冷却定型。此操作有风险,需谨慎。
问题:螺丝孔对不齐或螺丝拧入困难。
- 排查:FDM打印的螺纹孔内可能有少许塑料丝或尺寸偏小。
- 解决:对于M3的金属螺丝孔,可以先用一个M3的金属螺丝徒手(不要用电钻)尝试拧入,利用螺丝的螺纹“攻丝”,清理孔内杂物。对于M2.5的尼龙螺丝孔,如果过紧,可以用对应尺寸的钻头或锥子轻轻扩大一下。
6. 功能测试、优化与扩展玩法
6.1 上电测试与蓝牙配对
组装完成后,首次上电测试建议按以下步骤进行:
- 断开电池,先通过USB线将CPB连接到电脑。此时,CIRCUITPY盘应正常出现,且板载的红色电源LED常亮,NeoPixel可能呈现默认颜色或熄灭。这可以验证主板基本功能正常。
- 打开串行监视器(如Mu编辑器、Thonny或VS Code的串行终端),波特率设置为115200。你应该能看到板子启动时输出的CircuitPython版本信息,以及我们代码中的
print(packet.color)语句输出的调试信息。 - 连接电池,断开USB线。此时板子应由电池供电。按下复位键,板子应重新启动。
- 打开手机Bluefruit LE Connect App,扫描并连接设备。进入Color Picker,选取一个颜色并发送。此时,串行监视器应打印出类似
(0, 255, 0)的RGB元组,同时翡翠内部的LED应变为你选择的颜色。
6.2 代码优化与功能扩展
基础的颜色控制只是起点,CircuitPython的强大之处在于易于修改和扩展。这里分享几个进阶思路:
1. 亮度记忆与平滑过渡:当前的代码,每次断电后亮度会重置。我们可以添加亮度记忆和颜色渐变效果。
import board import neopixel import time from adafruit_bluefruit_connect.packet import Packet from adafruit_bluefruit_connect.color_packet import ColorPacket from adafruit_ble import BLERadio from adafruit_ble.advertising.standard import ProvideServicesAdvertisement from adafruit_ble.services.nordic import UARTService import storage ble = BLERadio() uart_service = UARTService() advertisement = ProvideServicesAdvertisement(uart_service) # 尝试从文件读取保存的亮度,默认为0.3 try: with open(\"/brightness.txt\", \"r\") as f: saved_brightness = float(f.read()) except (OSError, ValueError): saved_brightness = 0.3 pixels = neopixel.NeoPixel(board.NEOPIXEL, 10, brightness=saved_brightness, auto_write=False) current_color = (0, 255, 0) # 默认绿色 pixels.fill(current_color) pixels.show() def set_color_smooth(new_color): global current_color # 简单的RGB线性插值,实现渐变 steps = 20 r_step = (new_color[0] - current_color[0]) / steps g_step = (new_color[1] - current_color[1]) / steps b_step = (new_color[2] - current_color[2]) / steps for i in range(steps): r = int(current_color[0] + r_step * i) g = int(current_color[1] + g_step * i) b = int(current_color[2] + b_step * i) pixels.fill((r, g, b)) pixels.show() time.sleep(0.02) # 控制渐变速度 current_color = new_color while True: ble.start_advertising(advertisement) while not ble.connected: pass ble.stop_advertising() while ble.connected: if uart_service.in_waiting: packet = Packet.from_stream(uart_service) if isinstance(packet, ColorPacket): set_color_smooth(packet.color) # 可选:将当前颜色保存到文件 # with open(\"/color.txt\", \"w\") as f: # f.write(f\"{packet.color[0]},{packet.color[1]},{packet.color[2]}\")2. 利用板载传感器增加交互:CPB板载了加速度计。我们可以实现“敲击切换模式”或“摇动变色”的功能。
import board import neopixel from adafruit_bluefruit_connect.packet import Packet from adafruit_bluefruit_connect.color_packet import ColorPacket from adafruit_ble import BLERadio from adafruit_ble.advertising.standard import ProvideServicesAdvertisement from adafruit_ble.services.nordic import UARTService from adafruit_circuitplayground import cp # 使用CircuitPlayground库简化传感器访问 import time ble = BLERadio() uart_service = UARTService() advertisement = ProvideServicesAdvertisement(uart_service) pixels = neopixel.NeoPixel(board.NEOPIXEL, 10, brightness=0.3, auto_write=False) color_mode = 0 # 0: 手机控制, 1: 自动渐变 last_tap_time = 0 while True: # 检测双击切换模式 if cp.tapped: current_time = time.monotonic() if current_time - last_tap_time < 0.5: # 双击间隔小于0.5秒 color_mode = 1 if color_mode == 0 else 0 pixels.fill((100, 100, 100)) # 反馈提示 pixels.show() time.sleep(0.2) pixels.fill((0,0,0)) pixels.show() last_tap_time = current_time if color_mode == 0: # 原有蓝牙控制逻辑 ble.start_advertising(advertisement) while not ble.connected: # 在等待连接时,仍可检测敲击 if cp.tapped: # ... 处理模式切换 break pass ble.stop_advertising() while ble.connected: if uart_service.in_waiting: packet = Packet.from_stream(uart_service) if isinstance(packet, ColorPacket): pixels.fill(packet.color) pixels.show() else: # 模式1: 自动彩虹渐变 for j in range(255): if cp.tapped: # 在渐变过程中也能响应敲击退出 break for i in range(10): rc_index = (i * 256 // 10) + j pixels[i] = cp.colorwheel(rc_index & 255) pixels.show() time.sleep(0.01)6.3 维护与充电指南
- 充电:当LED灯光明显变暗或无法点亮时,就需要充电了。务必使用推荐的Adafruit Micro-Lipo充电器。将电池从主板上拔下,连接到充电器,充电器指示灯为红色表示正在充电,变为绿色表示充满。一次完整的充电通常需要1-2小时。
- 长期存放:如果计划长时间不使用,建议将电池充电至约50%的电量(电压约3.8V-3.9V)后断开存放。这有利于延长锂电池的寿命。
- 清洁:清洁3D打印外壳时,使用干燥的软布轻轻擦拭即可。避免使用酒精或其他有机溶剂,它们可能会使PLA表面变得浑浊或开裂。
这个项目从一颗螺丝、一行代码开始,到最后捧在手中发出自定义光芒的宝石,整个过程充满了动手创造的乐趣。它完美地展示了如何将开源的硬件、易用的软件和个性化的制造结合起来。当你成功让它亮起的那一刻,所获得的成就感远非购买一个成品可比。更重要的是,你获得了一个可无限扩展的平台——你可以修改代码实现呼吸灯、音乐频谱可视化,甚至通过蓝牙接收手机通知并变换颜色。希望这份详细的指南能帮你扫清障碍,祝你制作顺利。