基于Circuit Playground Express的红外激光对战系统设计与实现
1. 项目概述与核心思路
如果你手头正好有两块Adafruit的Circuit Playground Express开发板,又觉得单纯点亮LED或者做个温度计有点不过瘾,那今天这个项目绝对能让你玩上瘾。我们将利用板子上一个常被忽略的功能——红外收发器,来制作一套完整的、可互相对战的激光对战系统。这不仅仅是“遥控器控制另一个板子亮灯”那么简单,我们会实现一个完整的游戏逻辑:每被“击中”一次,板子上的NeoPixel环形灯就会多亮起一盏,直到被击中十次后“阵亡”,所有灯变红并伴随音效。两块板子互为“枪”和“靶子”,你可以和朋友来一场紧张刺激的对战,或者把它当作一个有趣的射击靶来练习瞄准。
这个项目的魅力在于,它完美结合了硬件感知、无线通信和游戏逻辑。红外通信本身是一种非常经典且廉价的短距离无线技术,我们每天用的电视遥控器就是最好的例子。在创客项目中,利用它来实现设备间的简单指令传输,既稳定又直观。Circuit Playground Express(后面我们简称CPX)板载了红外发射管(TX)和接收管(RX),这为我们省去了外接模块的麻烦。而MakeCode图形化编程环境,则让编写复杂的“发射-接收-判断-反馈”逻辑变得像搭积木一样简单,即使你没有深厚的代码功底,也能轻松上手。
整个项目的核心思路非常清晰:将一块CPX设置为“发射器”,当按下按钮时,通过红外LED发射一个代表特定颜色(比如靛蓝色)的数字编码信号。另一块作为“接收器”的CPX,在接收到这个信号后,会解析这个数字,并执行一系列游戏逻辑:增加“被击中次数”的计数器,根据次数点亮相应数量的LED,并在达到上限后触发“阵亡”状态(全红灯+音效),同时锁定自身,无法再作为发射器使用,直到复位。由于代码是对称的,每块板子都同时具备发射和接收功能,因此两块板子可以平等地对战。
2. 硬件准备与电路解析
2.1 核心硬件清单与选型考量
这个项目对硬件的要求非常精简,核心就是两块CPX开发板。我强烈推荐使用Adafruit原厂的Circuit Playground Express,而不是经典的Circuit Playground(后者没有红外功能)或其他兼容板,因为原厂板在MakeCode中的兼容性和库支持是最完善的。CPX可以看作是Arduino的一个高度集成、对初学者极其友好的版本,它把你想得到和想不到的传感器、输出设备都集成在了一个小圆板上:10个可编程RGB NeoPixel LED、运动传感器、温度传感器、光线传感器、声音传感器、两个按钮、一个滑动开关、一个红外收发器、一个扬声器,甚至还有电容触摸接口。对于这个项目,我们主要用到它的红外收发器、NeoPixel LED、按钮、扬声器和电源接口。
除了开发板,独立的供电是关键。CPX虽然可以通过USB供电,但为了能拿着它到处跑、实现真正的“对战”,我们必须使用电池供电。这里选择的是3节AAA电池盒,带开关和JST-PH 2针接口的版本。为什么是3节AAA?因为CPX的工作电压范围是3.3V-5.5V。3节全新的碱性电池可以提供约4.5V的电压,正好落在理想区间,既能稳定驱动板子所有功能,又不会因电压过高造成风险。锂电池虽然能量密度高,但单节3.7V的电压需要搭配升压电路,而两节串联又可能超过5.5V,对于这个简单项目来说,AAA电池盒是最可靠、最安全易得的选择。你需要准备两个这样的电池盒,以及总共6节AAA电池。
最后,你还需要两根Micro-USB数据线,用于最初的程序烧录。一旦代码烧录完成并切换到电池供电,数据线就可以收起来了。所以,完整的清单是:
- Circuit Playground Express 开发板 x 2
- 带开关的3xAAA电池盒 x 2
- AAA碱性电池 x 6
- Micro-USB数据线 x 2
注意:务必使用碱性电池。可充电的镍氢电池(如eneloop)单节电压通常只有1.2V,三节串联只有3.6V,处于CPX工作电压的下限边缘,可能导致红外发射功率不足、传输距离缩短,或者在大电流驱动NeoPixel时出现不稳定。新手第一次搭建,强烈建议用全新碱性电池,排除电源问题。
2.2 红外通信硬件原理浅析
虽然我们用MakeCode积木编程,但了解一点底层硬件原理,能帮你更好地调试和理解项目。CPX上的红外通信系统主要由两部分组成:
- 红外发射管(IR TX LED):位于板子边缘,标有“TX”的小透明LED。当它通电时,会发出人眼不可见的红外光。在MakeCode中,我们通过
红外发送数字积木来控制它。实际上,这个积木会让红外管以特定的频率(通常是38kHz,这是大多数红外接收头的标准载波频率)闪烁,将我们要发送的数字编码成一系列长短不同的红外脉冲。你可以把它想象成一个快速的、人眼看不见的“手电筒”,在用莫尔斯电码发送信息。 - 红外接收器(IR Receiver):位于板子另一侧,标有“RX”的一个黑色的小方块。它的内部是一个光电二极管和一套解调电路。它的作用不是简单地检测有无红外光,而是专门检测以38kHz频率调制的红外信号。这能有效过滤掉环境中常见的连续红外光源(如太阳光、白炽灯)的干扰,只“听”我们发射器发出的特定“音调”的信号。当它收到正确的信号后,会将其解调,还原出数字信息,并触发
当红外接收到数据积木。
这两个元件在板子上的物理位置是固定的。在进行对战时,你需要确保发射器的TX LED大致对准接收器的RX窗口。红外光的传播有一定的散射角,就像手电筒的光锥。默认情况下,这个光锥比较宽,容易瞄准但也少了点挑战性。后文我们会介绍如何通过加装“枪管”来收窄光束,提升游戏难度和真实感。
3. 游戏逻辑设计与MakeCode编程详解
3.1 项目初始化与状态设置
打开MakeCode编辑器(建议使用桌面版或Chrome浏览器访问makecode.adafruit.com),新建一个项目。我们所有的代码都将放在这个项目中,并且同一份代码会同时烧录到两块CPX板子上,因为它们角色是对等的。
首先进行初始化。拖入一个当启动时积木。这个积木里的代码只会在板子通电或复位时执行一次,用于设置游戏的初始状态。我们需要做两件事:
- 初始化NeoPixel灯环:从“灯光...更多”类别中,拖入
设置所有像素颜色为 红色积木,将其颜色改为“白色”。这行代码的作用是在游戏开始时,将10个LED灯全部点亮为白色,作为一个醒目的“设备已就绪”指示灯。白色光亮度高,能清晰表明板子已上电且程序正常运行。 - 创建并初始化变量:这是游戏逻辑的核心。我们需要两个变量来记录状态。
health(生命值):代表这个“目标”是否还能被射击或主动射击。我们设1为“存活”,0为“被击毁”。初始值设为1。hits(击中次数):记录这个目标当前被击中了多少次。初始值设为0。
在“变量”类别中,点击“创建一个变量”,分别创建这两个变量。然后拖入两个将 项目 设为 0的积木到当启动时循环内,分别将health设为1,将hits设为0。
至此,初始化部分完成。板子启动后,会白灯全亮,并且内部记录着:生命值满格(1),被击中次数为0。
3.2 发射器逻辑:按钮触发与红外信号发送
现在来构建“开枪”的逻辑。我们希望按下按钮A时,板子能作为发射器,向对方发送一个“子弹”信号。
- 建立按钮监听:从“输入”类别中,拖入
当 按钮 A 被点击积木。但这里有个关键细节:被点击指的是“按下并释放”的完整动作。在快速对战游戏中,我们希望按下的一瞬间就触发,响应更快。所以,点击积木上“A”旁边的下拉菜单,将其改为A,然后再点击“被点击”,选择被按下。这样,只要手指按下按钮,事件就会立刻触发。 - 添加视觉反馈:因为红外光不可见,开枪时我们需要一个可见的提示。CPX板载了一个单独的红色LED,连接到引脚13(在较新的MakeCode版本中,这个引脚被直接命名为“LED”)。从“引脚”类别(在“高级”抽屉里),拖入
数字写入 引脚 A0 至 假积木。将其中的“A0”改为“LED”(或“D13”),“假”改为“真”。这行代码会在按下按钮时点亮这颗红色LED,模拟“枪口闪光”。 - 发送红外信号:这是发射部分的核心。从“红外”类别,拖入
红外发送数字 0积木到按钮事件中。这个“0”就是我们要发送的消息内容。在这个游戏中,我们发送的数字实际上代表一种“颜色代码”。例如,我们可以定义发送“靛蓝色”代表一颗有效的子弹。从“灯光...更多”类别中,找到颜色选择器积木(通常是一个色块),将其拖拽到红外发送数字积木的“0”上,覆盖它,然后在弹出的颜色选择器中选择“靛蓝”(Indigo)。这样,当我们按下按钮A,板子就会向外发射一个代表“靛蓝色”的红外编码信号。
实操心得:你可以为不同的按钮分配不同的“颜色子弹”。例如,复制整个
当 按钮 A 被按下的积木组合,将按钮改为“B”,发送的颜色改为“绿色”。这样,一块板子就有了两种“武器”,增加了游戏的可玩性。在接收逻辑里,我们可以根据接收到的不同颜色数字来触发不同的效果(比如不同颜色的击中光效)。
3.3 接收器逻辑:信号解码与游戏状态更新
接收器逻辑是整个游戏的“大脑”,负责处理被击中后的所有反应。我们使用当红外接收到数据积木来响应。这个积木会提供一个名为receivedNumber(新版本中叫num)的变量,里面存储着发送方发来的数字(在我们这里就是颜色代码)。
添加接收指示:同样,为了直观知道收到了信号,我们可以点亮红色LED。复制之前
数字写入 LED 至 真的积木,放到当红外接收到数据积木里面。构建核心游戏逻辑:这里我们需要用条件判断来实现一个状态机。拖入一个
如果 为 真 则积木。- 条件判断:我们首先要检查自己的
health是否为1(即是否还存活)。从“逻辑”类别拖入一个0 = 0积木,替换掉“真”。将第一个“0”用变量health替换,第二个“0”改为“1”。这个条件的意思是:如果我的生命值等于1(存活),则执行接下来的被击中逻辑。 - 存活状态下的处理(
如果分支): a.增加击中计数:拖入以 1 为幅度更改 hits积木。 b.灯光反馈:我们希望每被击中一次,就多点亮一个NeoPixel。从“灯光”类别,拖入设置像素颜色 从 0 至 0 为 红色积木。这里需要一点技巧:将“从”后面的0替换为0,将“至”后面的0替换为变量hits。但注意,hits是从0开始计数的,而像素索引是从0到9。为了从第一个灯开始点亮,我们可以用hits - 1。更简单直接的方法是:设置像素颜色从0至hits,但第一次击中(hits=1)时,会点亮索引0和1两个灯?不对。实际上,我们需要的是点亮前N个灯。MakeCode有一个图形 绘制条形图到 高度 0积木(在“灯光...更多”里),但用在这里不直观。一个清晰的做法是:设置像素颜色 从 0 至 hits 为 靛蓝,并将颜色改为与发射信号一致的颜色(如靛蓝)。但这样每次都会重新设置从0到hits的所有灯。为了效果更好,我们可以每次只点亮一个新的灯:设置像素颜色 在 hits - 1 为 靛蓝。这里有一个关键点:当hits为0时,hits - 1是 -1,会导致错误。因此,安全的做法是在更改 hits之后,用设置像素颜色 在 hits 为 靛蓝,但第一次击中后hits=1,点亮的是索引1的灯(第二个灯),留出索引0的灯作为特殊状态?这会让灯光顺序看起来不连续。经过实践,最直观且无错的逻辑是:在如果内部,先更改 hits,然后设置像素颜色 从 0 至 hits 为 靛蓝。这样,第一次击中(hits变为1)时,点亮索引0的灯;第二次击中(hits变为2)时,点亮索引0和1的灯,以此类推。非常符合直觉。 c.判断是否被击毁:被击中后,需要判断是否达到了致命一击(比如10次)。拖入另一个如果 为 真 则积木,条件设置为hits = 10(或你设定的最大生命值)。如果条件成立,说明目标被摧毁了。 i.更新生命状态:在内部如果中,加入将 health 设为 0。 ii.触发摧毁效果:加入设置所有像素颜色为 红色,并将颜色改为红色。再加入播放音调 中音 C 持续 四分之一拍(从“音乐”类别),可以选择一个更有冲击力的音效,比如“power down”(关机音效)直到播放完毕。这会给玩家一个明确的“目标已摧毁”的声光反馈。 - 死亡状态下的处理(
否则分支):如果health不等于1(即health为0),说明这个目标已经被摧毁了。那么它不应该再对红外信号做出任何“被击中”的反应。我们可以在这里什么都不做,或者加一个简单的提示,比如让所有LED闪烁一下红色表示“我已死亡,别打了”。但为了简化,我们可以留空。
- 条件判断:我们首先要检查自己的
关闭接收指示LED:在
当红外接收到数据的最后,别忘了加入数字写入 LED 至 假来熄灭那个红色LED,表示一次接收处理完成。完善发射逻辑的条件:现在,我们需要回过头去修改发射逻辑。一个已经被摧毁(
health为0)的目标,应该不能开枪。所以,在当 按钮 A 被按下的事件处理中,最外层也应该套上一个如果 health = 1 则的判断。只有生命值为1时,才能执行点亮LED和发送红外信号的操作。否则,按下按钮应无反应。这模拟了“阵亡后无法攻击”的游戏规则。
3.4 代码整合与调试技巧
将上述所有积木按逻辑拼接起来,你的完整代码块就形成了。它应该包含:
当启动时:初始化灯光和变量。当 按钮 A 被按下(内含如果 health = 1判断):处理发射子弹。当红外接收到数据(内含如果 health = 1判断):处理被击中、更新状态、判断死亡并触发特效。
在烧录代码前,有几点调试建议:
- 分步测试:不要一次性写完所有逻辑。可以先测试红外收发是否正常。写一个最简单的程序:A板按下按钮发送数字1,B板收到数字1后点亮所有LED。确保基本的通信是通的。
- 利用串口输出:在MakeCode的“串口”类别中,有
串行写入行积木。你可以在关键节点(如发送信号时、收到信号时、变量改变时)输出调试信息到控制台,这对于排查逻辑错误非常有用。 - 灯光状态辅助:除了游戏逻辑灯,你可以用板载的红色LED(LED)作为状态指示灯。例如,开机常亮表示就绪,快速闪烁表示正在发送,慢速闪烁表示已死亡等。
当你确认代码逻辑无误后,用USB线连接第一块CPX,点击MakeCode编辑器的“下载”,将程序烧录进去。然后对第二块CPX重复此操作。现在,两块板子都拥有了相同的、完整的游戏程序。
4. 机械组装、供电与实战优化
4.1 电源组装与固定
代码准备就绪后,我们来解决供电和“持握”的问题。
- 安装电池:为两个电池盒各装入三节AAA电池。注意正负极方向,电池盒内部通常有清晰的标识。
- 连接开发板:将电池盒的JST-PH插头连接到CPX板边缘的电源接口。这个接口有防反插设计,方向不对是插不进去的,可以放心操作。
- 开启电源:打开电池盒上的开关。此时,CPX板应该会自动启动,NeoPixel灯环亮起白色,表示程序已运行,设备进入待机状态。
- 固定组合:电池盒和CPX板是分离的,玩起来不方便。我们需要将它们固定在一起。有两种简单可靠的方法:
- 尼龙扎带:这是最牢固的方式。找两根较短的尼龙扎带,分别穿过CPX板上的两个大孔(它们本身就是为固定设计的)和电池盒上的固定孔或缝隙,拉紧并剪掉多余部分。这样组合体非常结实耐用。
- 双面泡沫胶:如果想更轻便或可拆卸,可以使用高粘性的双面泡沫胶(如VHB胶带)。剪一小块,粘在电池盒和CPX板背面之间,用力按压几秒钟即可。这种方式足够应对桌面把玩,但剧烈奔跑时可能有脱落风险。
现在你得到了两个独立的、由电池供电的“游戏单元”。每个单元既是枪也是靶。
4.2 提升游戏体验:制作“枪管”
默认情况下,CPX的红外发射管(TX LED)发出的光锥角度较宽,在几米内很容易就能“击中”目标,游戏缺乏挑战性。我们可以通过添加一个“枪管”来收窄光束,这就像给手电筒加了一个聚光透镜,会让光斑变小,传播更远,但也要求你瞄得更准。
材料选择与制作:
- 吸管:最易得的材料。黑色或不透明的吸管效果最好,可以减少内部杂散反射。将吸管一端剪齐,直接套在TX LED上。如果有点松,用一点点橡皮泥(蓝丁胶)在内部固定一下。
- 废旧圆珠笔或签字笔的笔芯/笔杆:取出内部的笔芯和油墨,剩下的透明塑料笔杆是非常理想的枪管材料。长度适中,透光性好。
- 用纸卷制:用黑色卡纸或普通纸涂黑,紧紧卷成细管,用胶带固定。你可以通过调整卷的层数来控制内径,使其刚好能套住LED。
安装方法:直接将准备好的管状物套在CPX板上标有“TX”的透明红外发射LED上。如果套不紧,可以用一小块可塑橡皮(Poster Putty,也叫蓝丁胶)在管子和板子之间粘一下。这种材料不残留胶痕,可反复使用,非常适合这种临时固定。
注意事项:枪管不宜过长,5-10厘米足矣。过长反而可能因为轻微的晃动导致瞄准线偏离更大。确保枪管内壁干净,无遮挡。安装后,你可以对着另一块板子的红外接收窗(RX)进行测试,会发现需要更精确的瞄准才能触发击中效果,游戏乐趣大大提升。
4.3 游戏规则扩展与实战技巧
基础版本已经很有趣,但你完全可以在此基础上定制自己的规则:
- 修改生命值:在初始化时,将
health的初始值设为5或10,并在接收逻辑中判断hits >= 5等条件来触发死亡。同时,记得修改灯光反馈的逻辑,比如用hits * 2来控制点亮的灯数,让进度显示更合理。 - 添加复活机制:目前“阵亡”后需要手动复位(按一下板子背面的复位按钮)。你可以编程实现长按某个按钮(比如按钮B)来重置游戏状态:在
当 按钮 B 被按下事件中,设置health为1,hits为0,并重置灯光为白色。 - 团队模式与颜色编码:如果你有超过两块板子,可以设计团队战。例如,为团队A的板子编程,使其只响应“蓝色”子弹(发送数字代表蓝色),被击中后亮蓝灯;团队B则响应“绿色”子弹。这样,通过发射不同颜色的信号,就能区分敌我,实现多人大乱斗。
- 增加声音多样性:除了被击毁时的音效,你还可以为每次被击中添加一个简短的“嘀”声,为每次发射添加一个“啾”的音效,让游戏反馈更丰富。MakeCode的音乐库提供了很多现成的音效和旋律。
实战对战时的小技巧:
- 环境光干扰:强烈的日光或某些LED灯可能包含红外成分,偶尔会干扰接收。尽量在室内光线稳定的环境下游戏。
- 瞄准要领:红外光沿直线传播。瞄准时,想象你手中的“枪管”需要射出一条直线,对准对方板子上那个黑色的红外接收窗口(RX)。有了枪管后,需要更精细的瞄准。
- 距离测试:在开阔地,有效射程大约在3-5米。隔着手臂、薄衣服等障碍物,信号可能会衰减或中断。
- 复位同步:一局结束后,双方需要同时复位板子(按下复位键或通过你编程的复位功能)才能开始新的一局,确保起点公平。
5. 常见问题排查与进阶思路
即使按照步骤操作,也可能会遇到一些小问题。下面是一个快速排查指南:
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 按下按钮,自己的板子没反应(红灯不亮,没声音) | 1. 电池电量不足或开关未开。 2. 程序未成功烧录。 3. 按钮A损坏(罕见)。 | 1. 检查电池盒开关,更换新电池测试。 2. 重新通过USB连接电脑,在MakeCode中点击“下载”再次烧录程序,观察烧录过程是否报错。 3. 尝试按下按钮B(如果你编程了的话),或测试其他功能(如摇晃板子触发加速度计事件)来确认板子是否正常运行。 |
| 按下按钮,自己板子红灯亮,但对方板子无反应 | 1. 距离太远或未对准。 2. 枪管遮挡了发射管。 3. 对方板子已“死亡”(health=0)。 4. 红外通信受强光干扰。 | 1. 拉近距离(1米内)测试,确保发射管(TX)正对对方接收窗(RX)。 2. 暂时取下枪管测试。 3. 复位对方板子(按复位键)。 4. 移动到光线较暗或无直射阳光的地方测试。 |
| 对方板子偶尔会自己亮灯或触发音效 | 环境中存在其他红外信号源,如电视遥控器、空调遥控器等。 | 这是正常现象。我们的程序只响应特定的数字编码(如代表靛蓝色的数字),其他遥控器的编码不同,通常不会触发。如果频繁误触发,可以尝试在代码中改变发送的数字(换一个不常用的颜色代码)。 |
| NeoPixel灯光显示不正常(颜色错乱、不按顺序亮) | 1. 灯光控制逻辑有误,特别是索引计算。 2. 程序逻辑冲突,多个事件同时修改灯光状态。 | 1. 回头检查设置像素颜色 从 0 至 hits这部分逻辑,确保hits变量在每次击中后正确递增。可以在每次更改hits后,用串行写入行打印出它的值来调试。2. 确保在 当启动时和当红外接收到数据中对灯光的控制是唯一的,没有其他循环或事件在同时改变灯光。 |
| 音效不播放或播放异常 | 1. 扬声器被意外静音(板子上的滑动开关拨到了“静音”一端)。 2. 音效积木被放在了错误的分支或条件中。 | 1. 检查CPX板边缘的滑动开关,确保它不在“静音”位置。 2. 确认播放音效的积木(如 播放声音 power down 直到结束)确实被放在了“目标被摧毁”的条件分支(如果 hits = 10 则内部)里。 |
对于想要更进一步的玩家,这个项目可以作为一个跳板:
- 转向文本编程:尝试用CircuitPython(Adafruit基于Python的固件)重写这个游戏。你会对红外通信的底层协议(如NEC编码)有更深理解,并能实现更复杂的消息传递(如发送字符串命令、玩家ID、伤害值等)。
- 加入传感器:利用CPX上的加速度计,实现“甩枪上膛”或“躲避动作”(快速移动板子进入短暂无敌状态)的体感操作。
- 制作外观:用3D打印或纸板为你的“枪”和“靶子”制作一个炫酷的外壳,把它从一个开发板原型变成一个真正的玩具。
这个项目的精髓在于,它用最简单的硬件和直观的图形化编程,构建了一个包含完整状态逻辑和交互反馈的嵌入式系统。当你成功实现第一次对战射击时,那种亲手创造交互乐趣的成就感,是任何现成玩具都无法比拟的。
