基于Arduino的发光曲棍球游戏:嵌入式系统入门实战

发布时间:2026/5/31 15:36:44

基于Arduino的发光曲棍球游戏:嵌入式系统入门实战 1. 项目概述与核心思路几年前我在一个创客空间里看到几个孩子围着一块发光的板子玩得不亦乐乎那是一个自制的电子曲棍球游戏。它没有复杂的3D图形只有几个LED灯模拟的球拍和球但那种纯粹的、由自己亲手搭建硬件并编程实现的互动乐趣让我印象深刻。后来我发现用Arduino来复现这样一个项目不仅是重温经典游戏更是一个绝佳的嵌入式系统入门实战。它把枯燥的引脚控制、传感器读取和状态机逻辑封装在一个看得见、摸得着、能玩的游戏里。这个“基于Arduino的发光曲棍球游戏”本质上是一个状态驱动的交互式系统。它的核心玩法很简单两个玩家各自控制一个“球拍”由一组NeoPixel LED模拟去击打一个移动的“球”另一个LED目标是将球推入对方球门。但在这简单的表象下涉及了嵌入式开发的多个核心概念输入处理通过按钮和电位器、输出控制驱动LED和LCD屏幕、游戏逻辑球的运动、碰撞检测、得分计算以及实时响应。对于初学者而言完成这个项目就等于亲手打通了从电路原理图到可执行代码的完整链路理解了微控制器如何作为“大脑”协调各个“器官”工作。整个项目我会分成几个核心阶段来拆解首先是硬件架构与电路设计搞清楚每个元件为什么选它、接在哪里然后是核心游戏逻辑的编程实现这是项目的灵魂接着是人机交互的打磨比如用LCD显示分数、用蜂鸣器增加音效最后是从仿真到实物的迁移与调试。我会在每一步都分享我踩过的坑和总结的技巧比如如何避免按钮抖动导致误操作、如何让NeoPixel的动画更流畅、以及怎么在Tinkercad仿真和实际硬件有差异时快速排错。无论你是刚接触Arduino的新手还是想找一个有趣项目练手的爱好者跟着这篇教程你都能做出一个能和朋友对战、充满成就感的发光曲棍球台。2. 硬件选型与电路设计解析2.1 核心控制器与输入设备选型项目的“大脑”我们选用经典的Arduino Uno R3。选择它的理由很充分首先它拥有14个数字I/O口和6个模拟输入口完全能满足我们这个项目4个按钮、1个电位器、1个蜂鸣器、1个LCD、2个NeoPixel灯条的引脚需求。其次其基于ATmega328P的架构稳定可靠社区资源极其丰富任何问题几乎都能找到答案。对于初学者Uno的USB编程方式和简单的IDE环境能让你专注于逻辑而非环境配置。输入部分主要由按钮和电位器构成。四个按钮分别控制两个玩家的上下移动。这里我强烈建议使用常开型瞬态按钮也就是按下导通、松开断开的这种。为什么不用自锁开关因为游戏操作需要频繁、快速的点击瞬态按钮手感更符合直觉。每个按钮需要连接一个10kΩ的上拉电阻到VCC5V。这是一个关键细节当按钮未按下时上拉电阻将输入引脚稳定在HIGH5V电平按下时引脚被下拉到GND0V变为LOW。如果不加上拉电阻引脚悬空会产生不确定的电平导致误触发。Arduino内部虽有上拉功能通过pinMode(pin, INPUT_PULLUP)启用但外部物理电阻在某些抗干扰场景下更可靠我习惯在需要稳定性的地方都用上。电位器在这里扮演了一个非常巧妙的角色调节游戏难度或球速。它本质上是一个可变电阻中间抽头的电压随着旋转而变化。我们将它接在5V和GND之间中间抽头接到Arduino的一个模拟输入口如A0。Arduino的ADC模数转换器会读取到一个0-1023之间的值我们可以将这个值映射为球的移动速度延迟。这是一种低成本、高交互性的模拟输入方式比通过代码菜单调节要直观得多。2.2 输出设备NeoPixel与LCD显示屏显示部分是项目的视觉核心。我们使用NeoPixel LED灯条来模拟球拍和球。NeoPixelWS2812B是Adafruit公司推广的一种智能RGB LED每个像素点内部都集成了驱动芯片。它的巨大优势在于“单线控制”只需要一个数字引脚如引脚6发送数据就能控制串联在一起的数十甚至上百个LED每个LED的颜色和亮度都可以独立编程。这比用多个引脚控制多个普通LED要简洁高效得多。对于这个游戏我们可以用两个各含3-5个LED的短灯条代表球拍再用其中一个LED作为球。通过编程让“球”LED在灯条间跳跃就能形成移动动画。注意NeoPixel对时序要求很严格必须使用专门的库如Adafruit_NeoPixel来控制。接线时数据输入DIN脚接Arduino数字引脚VCC接5VGND接Arduino GND。务必在VCC和GND之间靠近灯条处并联一个330-1000μF的电解电容并在数据线串联一个300-500Ω的电阻。电容用于缓冲上电时的瞬时电流冲击防止损坏第一个LED电阻则用于阻尼数据线上的振铃噪声提高信号稳定性。这是很多新手会忽略但至关重要的步骤。LCD显示屏1602型16字符x2行用于显示比分和游戏状态。我们采用I2C接口的LCD模块。传统1602 LCD需要连接多达6个引脚RS, EN, D4-D7而I2C模块通过一个转接板只需要4根线VCC, GND, SDA, SCL就能控制大大节省了引脚。SDA和SCL分别接在Arduino Uno的A4和A5引脚这是硬件的I2C接口。使用I2C需要对应的库如LiquidCrystal_I2C初始化时指定设备地址常见为0x27或0x3F即可。蜂鸣器无源用于产生简单的音效如得分、碰撞声。无源蜂鸣器需要输入特定频率的方波才能发声这通过Arduino的tone()函数很容易实现。将它连接到一个数字引脚如引脚8和GND即可。2.3 完整电路连接图与原理根据以上分析我们可以绘制出清晰的电路连接图。以下是各元件与Arduino Uno引脚的对应关系建议在面包板上搭建时遵循此布局以便与后续代码对应元件引脚/接口连接到Arduino引脚说明按钮1 (玩家1上)一脚数字引脚 2通过10kΩ上拉至5V按钮2 (玩家1下)一脚数字引脚 3通过10kΩ上拉至5V按钮3 (玩家2上)一脚数字引脚 4通过10kΩ上拉至5V按钮4 (玩家2下)一脚数字引脚 5通过10kΩ上拉至5V电位器中间抽头模拟引脚 A0两侧引脚分别接5V和GNDNeoPixel灯条1DIN (数据输入)数字引脚 6VCC接5V GND接GNDNeoPixel灯条2DIN (数据输入)数字引脚 7VCC接5V GND接GNDI2C LCDSDAA4 (SDA)VCC接5V GND接GNDSCLA5 (SCL)无源蜂鸣器正极数字引脚 8负极接GND电路原理核心整个电路是一个典型的微控制器应用系统。Arduino通过数字输入引脚2-5持续扫描按钮状态LOW表示按下。模拟引脚A0读取电位器的分压值。根据这些输入内部程序游戏逻辑更新游戏状态。然后程序通过数字引脚6和7向NeoPixel发送数据流控制哪个LED亮、亮什么颜色通过I2C总线向LCD发送指令更新显示内容通过数字引脚8输出特定频率的方波驱动蜂鸣器。所有这些都是在一个loop()函数中高速循环完成的利用人眼的视觉暂留和听觉感知形成了连贯的交互体验。实操心得在面包板上搭建时建议用不同颜色的杜邦线区分功能如红色接5V黑色接GND黄色接信号线。电源5V和GND最好从面包板两侧的电源轨引出确保每个元件都能就近取电避免因长距离走线导致电压下降。首次上电前务必再三检查电源有无短路特别是LED和LCD的VCC和GND不要接反。3. 游戏逻辑与核心代码实现3.1 状态定义与游戏初始化在编写任何一行驱动硬件的代码之前我们必须先想清楚游戏的内在逻辑。这本质上是一个状态机。我们需要用变量来表征游戏的核心状态球拍位置两个变量存储玩家1和玩家2球拍的中心LED索引例如在一条5颗LED的灯条上位置可以是0到4。球的位置与速度需要变量记录球当前在哪条灯条NeoPixel 1 或 2上的哪个LED索引以及它的移动方向向左还是向右对应向哪个玩家移动。比分两个变量分别记录玩家1和玩家2的得分。游戏状态是正在进行中还是得分后的暂停或是游戏结束在setup()函数中我们需要完成所有初始化工作初始化串口用于调试。设置按钮引脚为输入模式并启用内部上拉电阻INPUT_PULLUP。初始化NeoPixel对象设置LED数量并清空显示。初始化LCD显示欢迎语或初始比分。初始化蜂鸣器引脚为输出。将球拍和球放置在初始位置如球拍在灯条中间球在场地中央。#include Adafruit_NeoPixel.h #include Wire.h #include LiquidCrystal_I2C.h // 引脚定义 #define PIN_NEO1 6 #define PIN_NEO2 7 #define PIN_BUZZER 8 #define NUM_LEDS 5 // 每条灯条上的LED数量 // 按钮引脚使用内部上拉因此按下为LOW #define BTN_P1_UP 2 #define BTN_P1_DOWN 3 #define BTN_P2_UP 4 #define BTN_P2_DOWN 5 // 游戏状态变量 int paddle1Pos 2; // 玩家1球拍中心位置 (0-4) int paddle2Pos 2; // 玩家2球拍中心位置 int ballStrip 0; // 球在哪条灯条上0: Strip1, 1: Strip2 int ballPos 2; // 球在灯条上的位置 (0-4) int ballDir 1; // 球的移动方向: 1向右(向玩家2), -1向左(向玩家1) int scoreP1 0; int scoreP2 0; bool gameActive true; // 对象初始化 Adafruit_NeoPixel strip1 Adafruit_NeoPixel(NUM_LEDS, PIN_NEO1, NEO_GRB NEO_KHZ800); Adafruit_NeoPixel strip2 Adafruit_NeoPixel(NUM_LEDS, PIN_NEO2, NEO_GRB NEO_KHZ800); LiquidCrystal_I2C lcd(0x27, 16, 2); // 地址可能是0x3F需用I2C扫描程序确认 void setup() { Serial.begin(9600); // 初始化按钮引脚启用内部上拉 pinMode(BTN_P1_UP, INPUT_PULLUP); pinMode(BTN_P1_DOWN, INPUT_PULLUP); pinMode(BTN_P2_UP, INPUT_PULLUP); pinMode(BTN_P2_DOWN, INPUT_PULLUP); // 初始化NeoPixel strip1.begin(); strip1.show(); strip2.begin(); strip2.show(); // 初始化LCD lcd.init(); lcd.backlight(); lcd.setCursor(0,0); lcd.print(Glow Hockey); lcd.setCursor(0,1); lcd.print(Score: 0 - 0); pinMode(PIN_BUZZER, OUTPUT); // 绘制初始状态 updateDisplay(); }3.2 输入扫描与球拍移动逻辑游戏的主循环loop()需要高效地处理三件事读取输入、更新游戏状态、刷新输出。首先处理输入。由于我们使用了上拉电阻按钮未按下时引脚读数为HIGH按下时为LOW。直接读取可能会因为机械抖动产生多次触发因此可以加入简单的软件防抖逻辑检测到按下状态后延迟一小段时间如50毫秒再读取一次如果仍然是按下状态则确认有效。球拍的移动逻辑是当检测到“上”按钮按下且球拍位置未到达顶端paddlePos 0时将球拍位置减1反之“下”按钮按下且未到达底端时位置加1。移动后需要立即更新NeoPixel的显示。void loop() { if (!gameActive) { // 游戏结束或暂停状态处理 return; } // 1. 读取输入并更新球拍位置 readInputs(); // 2. 更新球的位置 updateBall(); // 3. 检查碰撞与得分 checkCollisions(); // 4. 刷新所有显示 updateDisplay(); // 5. 控制游戏速度 int potValue analogRead(A0); int gameDelay map(potValue, 0, 1023, 50, 300); // 将电位器值映射为延迟时间(ms) delay(gameDelay); } void readInputs() { // 玩家1控制 if (digitalRead(BTN_P1_UP) LOW paddle1Pos 0) { delay(50); // 简单防抖 if (digitalRead(BTN_P1_UP) LOW) { paddle1Pos--; } } if (digitalRead(BTN_P1_DOWN) LOW paddle1Pos NUM_LEDS-1) { delay(50); if (digitalRead(BTN_P1_DOWN) LOW) { paddle1Pos; } } // 玩家2控制逻辑相同 if (digitalRead(BTN_P2_UP) LOW paddle2Pos 0) { delay(50); if (digitalRead(BTN_P2_UP) LOW) { paddle2Pos--; } } if (digitalRead(BTN_P2_DOWN) LOW paddle2Pos NUM_LEDS-1) { delay(50); if (digitalRead(BTN_P2_DOWN) LOW) { paddle2Pos; } } }3.3 球的运动、碰撞检测与得分逻辑这是游戏逻辑中最有趣的部分。updateBall()函数负责根据当前方向移动球。ballStrip和ballPos共同定义了球的位置。例如ballStrip0, ballPos2表示球在第一条灯条的中间LED上。碰撞检测分为两种情况与边界的碰撞当球移动到某条灯条的尽头ballPos为0或NUM_LEDS-1意味着球撞到了上下边界此时应该反弹即ballDir方向不变但球在下次更新时需要向反方向移动这可以通过在边界处等待球拍拦截来实现更简单的逻辑是直接让球从边界弹回但这不符合曲棍球物理。更真实的做法是如果球到达边界时对应位置的球拍没有接住则判失分。与球拍的碰撞当球移动到某条灯条的端点ballPos 0且ballStrip 0代表玩家1的球门区ballPos NUM_LEDS-1且ballStrip 1代表玩家2的球门区此时需要检查对方玩家的球拍是否位于接球位置。例如球飞向玩家1ballStrip 0 ballPos 0如果此时paddle1Pos与ballPos匹配或在一定误差范围内则成功拦截球反弹ballStrip变为1方向反转如果未拦截则玩家2得分。得分后需要更新比分在LCD上显示播放得分音效并将球重置到场地中央短暂暂停后继续游戏。void updateBall() { // 根据方向移动球 ballPos ballDir; // 检查是否移动到另一条灯条即从一端移动到另一端 if (ballPos 0) { // 球从Strip1的左端离开进入Strip2的右端 ballStrip 1; ballPos NUM_LEDS - 1; ballDir -1; // 进入Strip2后向左移动向玩家1 } else if (ballPos NUM_LEDS) { // 球从Strip2的右端离开进入Strip1的左端 ballStrip 0; ballPos 0; ballDir 1; // 进入Strip1后向右移动向玩家2 } } void checkCollisions() { // 检查与球拍的碰撞在球到达端点时 if (ballStrip 0 ballPos 0) { // 球在Strip1最左端即玩家1的球门区 if (abs(paddle1Pos - ballPos) 1) { // 允许1个LED的误差范围 // 成功拦截反弹 ballStrip 1; ballPos 0; ballDir 1; playSound(523); // 播放一个音调C5 } else { // 未拦截玩家2得分 scoreP2; scoreGoal(); } } else if (ballStrip 1 ballPos NUM_LEDS-1) { // 球在Strip2最右端即玩家2的球门区 if (abs(paddle2Pos - ballPos) 1) { // 成功拦截反弹 ballStrip 0; ballPos NUM_LEDS - 1; ballDir -1; playSound(659); // 播放另一个音调E5 } else { // 未拦截玩家1得分 scoreP1; scoreGoal(); } } } void scoreGoal() { // 得分处理 gameActive false; // 暂停游戏 updateLcdScore(); // 更新LCD显示 playSound(784); // 播放得分音效G5 delay(1000); // 暂停1秒 // 重置球的位置到中央 ballStrip 0; ballPos NUM_LEDS / 2; ballDir (random(0, 2) 0) ? -1 : 1; // 随机初始方向 gameActive true; // 恢复游戏 } void playSound(int frequency) { tone(PIN_BUZZER, frequency, 200); // 播放指定频率200ms }3.4 显示更新与效果优化updateDisplay()函数负责将所有状态变量“渲染”到硬件上。对于NeoPixel我们需要先清空所有LED的颜色然后根据paddle1Pos、paddle2Pos、ballStrip和ballPos设置特定LED的颜色。例如可以将球拍LED设置为蓝色球设置为红色未使用的LED保持熄灭。这里有一个提升体验的技巧不要只点亮一个LED代表球拍。可以让球拍覆盖连续的3个LED中心LED高亮两侧稍暗这样在视觉上更像一个“拍子”也增加了接球的容错率上面碰撞检测中的误差范围就是为此服务。使用strip.setPixelColor(index, color)函数设置颜色最后调用strip.show()一次性更新所有LED这样可以避免更新过程中的闪烁。LCD显示需要定期更新比分。为了减少频繁的I2C通信可以只在得分或初始化时更新LCD内容。void updateDisplay() { // 1. 清空所有NeoPixel for (int i0; iNUM_LEDS; i) { strip1.setPixelColor(i, 0); strip2.setPixelColor(i, 0); } // 2. 绘制玩家1球拍在strip1上例如用蓝色 for (int i-1; i1; i) { // 绘制中心及左右各一个LED int ledIndex paddle1Pos i; if (ledIndex 0 ledIndex NUM_LEDS) { int brightness (i0) ? 100 : 30; // 中心最亮 strip1.setPixelColor(ledIndex, strip1.Color(0, 0, brightness)); } } // 3. 绘制玩家2球拍在strip2上例如用绿色 for (int i-1; i1; i) { int ledIndex paddle2Pos i; if (ledIndex 0 ledIndex NUM_LEDS) { int brightness (i0) ? 100 : 30; strip2.setPixelColor(ledIndex, strip2.Color(0, brightness, 0)); } } // 4. 绘制球红色 if (ballStrip 0) { strip1.setPixelColor(ballPos, strip1.Color(150, 0, 0)); } else { strip2.setPixelColor(ballPos, strip2.Color(150, 0, 0)); } // 5. 更新显示 strip1.show(); strip2.show(); } void updateLcdScore() { lcd.clear(); lcd.setCursor(0,0); lcd.print(P1:); lcd.print(scoreP1); lcd.setCursor(8,0); lcd.print(P2:); lcd.print(scoreP2); lcd.setCursor(0,1); if (scoreP1 5) { lcd.print(Player 1 Wins!); gameActive false; } else if (scoreP2 5) { lcd.print(Player 2 Wins!); gameActive false; } else { lcd.print(Game On!); } }4. 从Tinkercad仿真到实物制作的迁移4.1 Tinkercad仿真环境搭建与验证对于没有硬件在手或想先验证逻辑的开发者Tinkercad是一个完美的起点。它是一个在线的电路仿真与Arduino编程平台。操作流程如下创建新电路在元件库中搜索并添加Arduino Uno R3、面包板、按钮、电阻、电位器、蜂鸣器、LCD1602 with I2C和NeoPixel搜索WS2812B或RGB LED Strip。注意Tinkercad的元件库可能没有完全一样的模块可以用通用RGB LED模拟NeoPixel或者用多个独立LED模拟灯条但代码逻辑需要相应调整。按原理图连线严格按照前面章节的引脚定义进行连接。Tinkercad的连线工具很直观连接后引脚处会有提示。编写并上传代码在代码编辑区将我们上面讨论的完整代码粘贴进去。Tinkercad使用的是基于Blocks的简化代码环境但可以切换到“文本”模式直接编写C代码。启动仿真点击“开始仿真”按钮。你可以点击虚拟按钮来控制观察虚拟LED的亮灭和串口监视器的输出验证游戏逻辑是否正确。仿真环境下的注意事项Tinkercad对NeoPixel库的支持可能不完美如果遇到问题可以尝试用多个独立数字引脚控制多个普通LED来模拟球拍和球虽然代码会更复杂但能验证核心逻辑。仿真的最大价值在于验证逻辑和排查语法错误它无法完全模拟硬件上的电气特性如信号噪声、电源波动。4.2 实物制作步骤与焊接建议仿真成功后就可以着手实物制作了。你需要准备所有列出的元件、一个面包板、若干杜邦线和一台安装了Arduino IDE的电脑。布局规划在面包板上先规划好元件位置。建议将Arduino放在一侧电源轨分布在上下两侧。将LCD、NeoPixel、蜂鸣器这些“输出”设备放在一侧按钮和电位器这些“输入”设备放在另一侧使布线清晰。电源先行首先连接5V和GND总线。确保所有元件的VCC和GND都能方便地连接到这两条总线上。逐模块搭建不要一次性连接所有线。建议按功能模块搭建并测试先搭按钮电路连接一个按钮、上拉电阻到Arduino上传一个简单的测试程序如按下按钮点亮板载LED确保输入检测正常。再测试NeoPixel单独连接一条NeoPixel灯条上传Adafruit库中的示例代码如strandtest确保能控制颜色。然后连接LCD连接I2C LCD上传一个显示“Hello World”的示例确保通信正常。最后整合所有模块单独测试无误后再按照总电路图连接在一起。关于焊接如果希望项目更牢固可以考虑将电路从面包板迁移到洞洞板或定制PCB上进行焊接。焊接时注意先焊接矮的元件电阻、IC座再焊接高的元件电容、接口。NeoPixel灯条和LCD的引脚比较密集焊接时要防止短路。可以使用排针先将它们转换成可插拔的模块。电源线和地线尽量使用粗一点的导线并在关键元件如NeoPixel的电源引脚附近并联一个100μF的电解电容和一个0.1μF的瓷片电容分别用于缓冲低频和高频噪声。4.3 上电调试与常见问题排查第一次给整合好的系统上电是最令人紧张又兴奋的时刻。遵循以下步骤可以最大化成功率目视检查再次检查所有连接特别是VCC和GND有无接反、短路。检查按钮、电位器、LED、LCD的引脚是否对应正确。分步上传代码不要直接上传完整的游戏代码。先上传一个最简单的“系统自检”程序例如让所有NeoPixel显示白色、LCD显示“Ready”、蜂鸣器响一声。这可以快速验证所有输出设备是否正常工作。使用串口调试在代码中大量使用Serial.print()语句。例如在readInputs()函数中打印按钮状态和球拍位置在checkCollisions()中打印碰撞事件。通过Arduino IDE的串口监视器波特率设为9600你可以像“透视”一样看到程序内部的运行状态这对于排查逻辑错误至关重要。常见问题与解决问题NeoPixel不亮或颜色错乱。排查首先检查电源。NeoPixel需要足够的电流确保你的5V电源如电脑USB口或适配器能提供至少1A的电流如果LED较多。检查数据线是否接对以及是否在代码中正确初始化了引脚和LED数量。解决确保strip.begin()和strip.show()被调用。尝试降低第一个数据引脚串联电阻的阻值如从500Ω降到220Ω。在strip.show()后加一个小的delay(1)。问题LCD无显示。排查最常见原因是I2C地址不对。使用一个简单的I2C扫描程序Arduino IDE示例如File - Examples - Wire - i2c_scanner来查找你LCD模块的正确地址通常是0x27或0x3F。解决在代码中修改LiquidCrystal_I2C lcd(0x27, 16, 2);的地址参数。同时检查背光是否亮起调节LCD模块上的电位器如果有来调整对比度。问题按钮反应不灵或连发。排查机械按钮的抖动问题。我们代码中用了简单的延时防抖但对于快速连击可能不够。解决实现更健壮的防抖逻辑例如记录按钮状态变化的时间戳只有按下状态持续超过50ms才认定为有效。或者使用Bounce2这类防抖库。问题游戏运行卡顿。排查loop()循环中是否有不必要的长时间delay()updateDisplay()中是否进行了大量耗时的计算解决将delay(gameDelay)改为非阻塞的延时方式如用millis()记录时间。优化NeoPixel的显示更新只在状态确实改变时才调用show()。5. 功能扩展与项目优化思路一个基础版本运行稳定后你就可以考虑给它增加一些“灵魂”让它变得更好玩、更精致。增加音效与灯光特效音效不要只用一个频率的“嘀”声。可以为碰撞、得分、游戏结束定义不同的音调和节奏。使用tone()函数配合delay()或millis()可以演奏简单的旋律。灯光特效得分时可以让所有NeoPixel快速闪烁胜利方的颜色。游戏结束时可以跑一个彩虹循环动画。利用Adafruit_NeoPixel库中丰富的颜色函数如ColorHSV()、渐变效果可以轻松实现。引入更复杂的游戏规则变速球球在被击打后可以根据球拍移动的速度来增加球速模拟真实的击打力度。障碍物在场地中间随机点亮一个LED作为障碍物球碰到后会随机反弹。技能系统长按某个按钮可以触发“加速”或“加长球拍”的临时技能增加策略性。提升硬件与外观定制外壳用亚克力板、木板或3D打印制作一个真正的“球台”外壳将灯条嵌入凹槽中按钮和电位器安装在侧面LCD嵌在面板上。这能极大提升项目的完成度和美观度。电源独立使用一块9V电池或移动电源通过Arduino的直流电源接口供电让项目脱离电脑成为真正的独立游戏机。无线控制尝试用两个Arduino Nano或ESP32开发板通过蓝牙或2.4G无线模块制作无线手柄来控制球拍彻底摆脱连线的束缚。这个项目最吸引我的地方在于它像一个活的“脚手架”。你完成了基础版本就掌握了嵌入式开发的核心循环感知-计算-执行。之后每一个你想到的酷炫点子无论是灯光、声音还是新规则都可以在这个脚手架上添加、修改、测试。它从不是一个“做完”的项目而是一个你不断与之对话、并看着它在你手中不断进化的电子伙伴。我自己的第一个版本只有简单的亮灭后来加了音效又用3D打印做了外壳现在它是我工作台上最受欢迎的减压小玩具。希望你的版本能有更多独特的创意。

相关新闻