Arduino双机交互:光敏电阻与伺服电机实现自动化游戏操控 1. 项目概述当硬件开始“对话”如果你玩过Chrome浏览器在断网时出现的那个小恐龙跳跃游戏并且对Arduino这类开源硬件有点兴趣那你可能想过能不能让机器自己玩这个游戏更进一步如果让一块Arduino板子运行游戏另一块板子来“看”着屏幕并自动操控会是什么景象这听起来像是个极客的玩笑但它恰恰是一个绝佳的嵌入式系统入门项目完美诠释了微控制器MCU如何作为“大脑”协调“眼睛”传感器和“手”执行器来完成一个具体的自动化任务。这个项目的核心就是构建两套独立的Arduino系统让它们进行一场跨越物理空间的“对话”。系统A负责游戏逻辑的呈现它将经典的Chrome恐龙游戏简化后运行在一块搭载了LCD按键盾板的Arduino Uno上。系统B则扮演“玩家”的角色其核心是一个光敏电阻LDR它紧贴在系统A的LCD屏幕特定位置充当“视觉”传感器实时检测屏幕上是否有障碍物仙人掌经过。一旦检测到信号系统B的“大脑”另一块Arduino Uno会立刻做出决策驱动一只微型伺服电机Servo的摆臂轻轻按下系统A盾板上的跳跃按键。整个过程“感知-决策-执行”的自动化闭环清晰可见。这不仅仅是让恐龙自动跳跃那么简单。对于初学者你能亲手实践数字/模拟信号读取、阈值判断、伺服电机控制等基础技能。对于有经验的开发者它涉及了系统间无通信协议的交互设计、传感器抗干扰校准、实时性考量等更深层的问题。无论是用于教学演示、自动化测试原型还是单纯作为一个有趣的创意项目它都能让你对硬件编程和系统集成有更直观、更深刻的理解。接下来我将拆解这个项目的每一个环节从设计思路到代码细节从硬件连接到调试技巧分享我在实现过程中积累的所有经验。2. 系统设计与核心思路拆解在开始焊接任何一根线之前理清整个系统的设计思路至关重要。这个项目可以清晰地划分为两个子系统游戏运行端Dino Game Unit和游戏操控端Player Unit。它们之间没有使用任何有线如I2C、UART或无线如蓝牙、Wi-Fi的通信协议而是通过“光”这种物理媒介进行单向、间接的交互。这种设计选择背后有它的巧妙之处和局限性值得我们深入探讨。2.1 为何选择“光信号”作为交互媒介最直接的想法可能是用杜邦线直接连接两块Arduino的IO口或者用串口发送指令。但本项目采用了更“物理”的方式用光敏电阻LDR读取LCD屏幕的亮度变化。这主要基于以下几点考量解耦与简化两块Arduino完全独立供电、独立编程。你不需要处理复杂的通信协议如定义数据包格式、处理通信错误、同步双方状态大大降低了软件复杂度和调试难度。系统B完全通过“观察”系统A的输出屏幕图像来行动这是一种典型的“传感器-环境”交互模式。非侵入式这种方式不需要对游戏运行端系统A的硬件或代码进行任何修改以适配通信功能。游戏代码可以保持其独立性和完整性就像在单独运行一样。这符合模块化设计思想每个子系统功能内聚。教学价值它生动地展示了传感器如何从物理世界这里是光信号获取信息。LDR检测亮度变化是一个经典的模拟输入案例比直接读取一个数字信号更能让人理解模数转换ADC和阈值判定的意义。当然这种方式也有其局限性主要是抗干扰能力较弱环境光变化会影响LDR以及信息带宽极低只能传递“有/无”障碍物这种简单信息。但对于“检测固定位置、固定颜色的障碍物”这个特定任务它足够简单有效。2.2 双系统硬件架构解析让我们从硬件角度俯瞰整个系统系统A游戏运行端核心控制器Arduino Uno。选择Uno是因为其资源内存、IO足以运行一个简化版的图形游戏且生态丰富兼容性好。人机交互界面LCD按键盾板。这块盾板直接插在Uno上提供了16x2字符的液晶显示屏和五个独立按键包括我们需要的“跳跃”键。它解决了显示和输入的问题无需额外布线非常整洁。功能运行一个用Arduino代码重写的简化版恐龙游戏。恐龙自动向前跑屏幕上有随机生成的障碍物用字符模拟的仙人掌。当玩家或我们的系统B按下盾板上的“选择”键通常映射为跳跃功能时恐龙跳跃。系统B游戏操控端核心控制器另一块Arduino Uno。感知单元光敏电阻LDR与一个固定电阻如10kΩ组成分压电路连接到模拟输入引脚如A0。LDR被物理固定在系统A的LCD屏幕正前方。决策与执行单元Arduino读取LDR的模拟值通过阈值判断逻辑决定是否“跳跃”。若判定跳跃则驱动一个微型伺服电机如SG90电机的摆臂被机械定位在系统A盾板的跳跃按键上方。辅助单元最初可能包含一个LED作为触发指示灯但实际中发现其光线会干扰旁边的LDR故在最终设计中通常移除或仅在调试时使用。两个系统通过“光”连接系统A的屏幕显示特定的像素变化仙人掌经过 - 系统B的LDR感知到该点的亮度变化 - 系统B的Arduino处理信号并触发伺服电机 - 伺服电机按下系统A的按键。这个闭环就是整个项目的灵魂。3. 硬件准备与连接细节工欲善其事必先利其器。正确的硬件选型和可靠的连接是项目成功的基石。这里我会列出详细的物料清单并解释关键元器件的选型原因然后给出清晰的接线图与焊接建议。3.1 物料清单与选型考量系统A游戏运行端Arduino Uno R31个项目基石。不建议用更便宜的板子如Nano直接替代盾板因为引脚布局和物理尺寸可能不兼容。1602 LCD按键盾板1个这是关键部件。务必确认是兼容Arduino Uno的“按键盾板”Keypad Shield它通常集成有LCD16字符x2行、背光、一个电位器调节对比度以及5个按键上、下、左、右、选择。我们主要利用其LCD和“选择”键。系统B游戏操控端Arduino Uno R31个另一块Uno。微型伺服电机 SG901个选择SG90是因为它体积小、扭矩足够按下按键、价格低廉且控制简单标准PWM信号。舵机内部包含电机、减速齿轮组和反馈电路可以精确控制角度。光敏电阻 GL55281个这是一种常用的硫化镉CdS光敏电阻电阻值随光照增强而减小。选择它是因为其灵敏度适中响应速度对于本项目几十毫秒级绰绰有余。10kΩ 直插电阻1个用于与LDR组成分压电路。10kΩ是一个与LDR在室内光照下阻值范围匹配良好的值能产生变化范围较大的分压信号。杜邦线公对公、公对母若干用于连接。小型面包板1个用于快速搭建和测试电路后期可焊接。可选LED及220Ω限流电阻用于初期调试指示正式运行建议移除以防光干扰。注意关于电源两个Arduino最好分别使用独立的5V/1A以上的USB电源适配器供电。果共用一个电源要确保其电流输出能力足够2A并注意共地问题。伺服电机在动作瞬间电流可能达到500mA若与Arduino共用电脑USB口供电可能导致电脑USB端口保护或板子复位。3.2 电路连接详解与焊接建议系统A的连接最简单只需将LCD按键盾板直接插在Arduino Uno的引脚排母上确保方向正确通常盾板的引脚排针与Uno的排母完全对应。插紧即可无需焊接。系统B的电路搭建是重点原理图如下系统B (Player Arduino Uno) --------------------------------- 模拟引脚 A0 ---[LDR]--- 5V | [10kΩ Resistor] | GND --------------------------------- 数字引脚 9 (PWM) --- 伺服电机信号线(橙色/白色) 5V --- 伺服电机电源线(红色) GND --- 伺服电机地线(棕色/黑色) --------------------------------- 可选数字引脚 13 --- LED() --- [220Ω] --- GND分压电路原理LDR和10kΩ电阻串联在5V和GND之间它们的连接点接到A0。当光照强时LDR电阻变小A0点的电压相对于GND升高光照弱时LDR电阻变大A0点电压降低。Arduino的ADC模数转换器将这个0-5V的电压转换为0-1023的整数值。伺服电机连接SG90有三根线。红色正极接5V棕色或黑色负极接GND橙色或白色信号线接一个支持PWM的数字引脚如9号。PWM脉冲宽度调制信号用于控制舵机角度。焊接建议 为了系统稳定和美观建议在面包板测试无误后将系统B的核心电路LDR、电阻、伺服电机的信号线焊接在一块洞洞板万用板上并使用排针或排母与Arduino连接。对于LDR可以焊接两根较长的导线建议使用不同颜色的排线以便将其引到系统A的屏幕前固定。伺服电机也可以焊接杜邦线母头方便插拔。焊接时务必注意烙铁温度和时间避免烫坏光敏电阻或伺服电机的塑料部件。4. 软件实现代码逐行解析与优化硬件就绪后赋予它们“智能”的代码就是灵魂。本项目有两份独立的Arduino草图Sketch我们将逐一深入分析并分享关键的调试技巧和优化空间。4.1 系统A恐龙游戏代码剖析系统A的代码核心是在LCD上模拟一个横向卷轴的恐龙游戏。由于1602液晶是字符型无法显示精细图像所以我们需要用自定义字符Custom Character来构建恐龙和仙人掌的图形。核心逻辑流程初始化设置LCD创建恐龙、仙人掌等自定义字符图形。游戏循环 a. 检测按键来自盾板或系统B的物理按压。如果“选择”键被按下设置恐龙为“跳跃”状态。 b. 根据恐龙是否处于跳跃状态计算其垂直位置Y坐标。 c. 在屏幕缓冲区一个逻辑上的数组中移动障碍物仙人掌向左。 d. 随机生成新的障碍物。 e. 检测碰撞如果恐龙图形与障碍物图形在屏幕缓冲区中的位置重叠则游戏结束。 f. 将屏幕缓冲区的数据渲染到LCD的特定位置。 g. 延迟一定时间如60-100毫秒控制游戏帧率。关键代码片段与解释#include LiquidCrystal.h // 使用Arduino自带的液晶库 LiquidCrystal lcd(8, 9, 4, 5, 6, 7); // 初始化液晶对象引脚定义需匹配你的盾板 // 自定义字符数组用于绘制恐龙和仙人掌 byte dinoChar[8] { ... }; // 恐龙的位图数据 byte cactusChar[8] { ... }; // 仙人掌的位图数据 bool isJumping false; int dinoY 0; // 恐龙垂直位置0表示地面 int jumpHeight 2; // 最大跳跃高度字符行数 int jumpCounter 0; void setup() { lcd.begin(16, 2); // 初始化16x2 LCD lcd.createChar(0, dinoChar); // 将自定义字符存入LCD的CGRAM lcd.createChar(1, cactusChar); // ... 其他初始化 } void loop() { // 1. 检测跳跃按键按键盾板的‘选择’键通常连接到模拟引脚A0通过读取特定电压值判断 int key analogRead(A0); if (key 100) { // 根据你的盾板文档这个阈值可能不同常见值选择键~50 isJumping true; jumpCounter 0; } // 2. 更新恐龙跳跃状态 if (isJumping) { dinoY jumpPattern[jumpCounter]; // 使用一个预定义的数组来控制跳跃轨迹抛物线 jumpCounter; if (jumpCounter sizeof(jumpPattern)) { isJumping false; dinoY 0; } } // 3. 更新障碍物位置向左移动 // 4. 碰撞检测 // 5. 渲染到LCD // ... delay(80); // 控制游戏速度 }实操心得LCD盾板按键读取不同厂商的LCD按键盾板其按键的模拟电压值可能略有差异。最可靠的方法是先在setup()里写一个简单的循环打印出analogRead(A0)的值然后分别按下每个键记录下对应的数值范围。通常“选择”键的值最小最接近0“右”键最大接近1023。用这些实测值作为判断阈值比用网上找的固定值更可靠。4.2 系统B玩家控制代码精讲系统B的代码相对简单但更注重稳定性和抗干扰。其核心是一个状态机不断读取环境光强判断是否有“事件”发生。核心逻辑流程初始化配置伺服电机引脚将其移动到初始未按下按键位置。初始化串口用于调试输出光敏值。主循环 a. 读取LDR连接的模拟引脚如A0的值。 b. 对读取的值进行简单的软件滤波例如取最近几次读数的平均值以减少噪声波动。 c. 将滤波后的值与一个预设的“触发阈值”进行比较。 d. 如果光值高于阈值说明屏幕对应位置变亮可能是仙人掌的白色背景或仙人掌本身经过且当前不处于触发冷却期则触发跳跃动作。 e. 触发动作快速驱动伺服电机到一个角度模拟按下按键然后迅速返回原位。同时进入一个短暂的“冷却期”例如200毫秒防止单次障碍物触发多次跳跃。 f. 如果光值低于阈值则维持待机状态。关键代码片段与解释#include Servo.h // 使用Arduino自带的舵机库 Servo myServo; const int ldrPin A0; const int servoPin 9; const int threshold 700; // 触发阈值需要根据实际环境校准 const unsigned long cooldownTime 200; // 冷却时间单位毫秒 int ldrValue 0; int filteredValue 0; unsigned long lastTriggerTime 0; bool isInCooldown false; void setup() { Serial.begin(9600); // 初始化串口用于调试输出光敏值 myServo.attach(servoPin); myServo.write(85); // 初始角度确保摆臂不触碰按键 delay(1000); } void loop() { // 1. 读取并滤波 ldrValue analogRead(ldrPin); // 简单移动平均滤波 filteredValue (filteredValue * 0.7) (ldrValue * 0.3); // 2. 检查冷却状态 if (isInCooldown) { if (millis() - lastTriggerTime cooldownTime) { isInCooldown false; } } // 3. 阈值判断与触发 if (!isInCooldown filteredValue threshold) { triggerJump(); lastTriggerTime millis(); isInCooldown true; } // 选串口输出调试信息 Serial.print(Raw:); Serial.print(ldrValue); Serial.print( Filtered:); Serial.print(filteredValue); Serial.print( Threshold:); Serial.println(threshold); delay(10); // 短暂延迟控制采样率 } void triggerJump() { myServo.write(95); // 按下按键的角度需要根据机械结构微调 delay(50); // 保持按下状态一小段时间 myServo.write(85); // 返回初始角度 }注意事项阈值校准是成败关键threshold这个值不是固定的。它取决于你的LDR型号、与屏幕的距离、屏幕亮度、环境光等因素。务必进行校准将系统全部搭建好在游戏运行时打开串口监视器观察当仙人掌经过LDR检测点时filteredValue的读数是多少。将这个值减去一个安全余量比如20-50作为你的触发阈值。环境光变化如开关灯会严重影响这个值因此最好在光线稳定的环境下运行。5. 机械组装、校准与系统联调代码烧录完毕硬件连接无误接下来就是最考验动手能力和耐心的环节——机械组装与系统联调。这部分工作决定了你的“机器人玩家”是反应敏捷还是呆若木鸡。5.1 LDR与伺服电机的定位与固定LDR光敏电阻的定位确定检测点在系统A的LCD屏幕上运行游戏观察仙人掌出现的位置。仙人掌通常从屏幕右侧出现向左移动。你需要找一个固定的、仙人掌必定会经过的“检测线”。通常选择屏幕底部一行第二行靠左的某个字符位置比如第7个字符从0开始计数是第6个。因为恐龙在底部仙人掌也出现在底部。固定LDR将LDR用热熔胶或蓝丁胶小心地固定在屏幕正前方对准你选定的检测点。关键是要让LDR的感光面尽可能贴近LCD表面以减少环境光的干扰同时又要避免用力挤压导致屏幕损坏。可以使用一小块黑色海绵或橡皮泥制作一个遮光罩套在LDR周围进一步隔绝侧面环境光。连接将LDR的两根长导线连接到系统B的电路上。伺服电机Servo的定位与安装确定按压点找到LCD按键盾板上用于跳跃的“选择”键。制作摆臂SG90通常附带多个塑料摆臂。选择一个合适的或者用轻质材料如冰棍棒自制一个。摆臂末端需要粘贴一个柔软、有弹性的触头如一小块硅胶或海绵以避免硬接触损坏按键也能保证按压可靠。固定舵机你需要用积木、乐高件、3D打印的支架或者直接用扎带和热熔胶将舵机牢固地固定在盾板按键的正上方。确保舵机在初始位置write(85)时摆臂触头刚好轻微离开按键表面或刚好接触但未下压在触发位置write(95)时能明显将按键按下一定行程通常1-2毫米足够。角度校准在代码中triggerJump()函数里的myServo.write(95);和myServo.write(85);这两个角度值需要根据你的机械安装实际情况进行微调。通过串口发送命令或修改代码反复测试找到能可靠触发按键又不使舵机堵转过度用力的角度。5.2 系统联调与性能优化当两个系统物理上组装在一起后按以下步骤进行联调独立测试先分别给两个系统上电。系统A应能正常显示并运行恐龙游戏手动按下盾板按键恐龙应能跳跃。系统B的舵机应能归位到初始角度打开串口监视器应能看到不断输出的光敏值。阈值精细校准启动系统A的游戏让恐龙跑起来。观察系统B的串口输出记录下没有仙人掌经过检测点时的filteredValue基线值。记录下有仙人掌经过检测点时的filteredValue峰值值。将触发阈值设置为阈值 基线值 (峰值值 - 基线值) * 0.6。这个0.6的比例因子可以调整值越大越不易误触发防环境光突变但也要保证能检测到仙人掌。触发时机调试观察恐龙跳跃时机。理想情况是仙人掌前端到达恐龙位置时恐龙刚好跳起。如果跳早了可以尝试稍微增加cooldownTime或在检测逻辑中加入更复杂的判断比如需要光值持续高过阈值一段时间。如果跳晚了可能是LDR反应慢或机械动作延迟可以尝试减小cooldownTime或者优化舵机动作速度但注意速度太快可能按不稳。抗干扰优化软件滤波上文代码中的filteredValue (filteredValue * 0.7) (ldrValue * 0.3);是一种一阶低通滤波能平滑掉突然的噪声。你可以调整系数0.7和0.3相加为1系数越大滤波效果越强但响应也越慢。环境光稳定尽量在室内光线稳定的环境下运行。避免阳光直射或灯光闪烁如某些LED灯的环境。物理遮光重申LDR的遮光罩非常重要。6. 常见问题排查与进阶玩法即使按照步骤操作你也可能会遇到一些“坑”。这里汇总了一些常见问题及其解决方法并分享一些让这个项目更有趣的扩展思路。6.1 故障排查速查表现象可能原因排查步骤与解决方案系统A游戏无显示1. 盾板接触不良2. 对比度调节不当3. 代码引脚定义错误1. 重新插拔LCD盾板确保所有引脚接触良好。2. 调节盾板上的蓝色电位器对比度调节直到屏幕出现字符。3. 检查代码中LiquidCrystal lcd(8,9,4,5,6,7);这行确保与你盾板的引脚定义一致有些盾板是(8,9,4,5,6,7)有些是别的。系统B舵机不转动或乱转1. 电源功率不足2. 信号线连接错误3. 代码中舵机对象未绑定attach1. 使用独立电源为Arduino供电或确保USB电源适配器能提供足够电流1A。2. 确认舵机信号线黄/白接在了PWM引脚如9红线接5V棕/黑线接GND。3. 检查setup()中是否有myServo.attach(servoPin);语句。LDR读数无变化或变化很小1. LDR或电阻接错2. LDR距离屏幕太远3. 屏幕亮度太低或检测点不对1. 用万用表检查分压电路确认A0引脚电压随遮挡LDR而变化。2. 将LDR尽可能贴近LCD屏幕表面。3. 调高LCD背光亮度如果盾板支持并确认LDR对准了游戏中仙人掌必经的像素列。恐龙总是提前跳或跳得太晚1. 触发阈值设置不当2. LDR检测点位置不对3. 舵机响应有延迟1. 重新进行阈值校准方法见第5.2节。2. 调整LDR在屏幕前的左右位置找到最佳的触发点。3. 检查舵机动作是否顺畅减少triggerJump()函数中的delay但需保证按键能被按下。系统B误触发无仙人掌也跳1. 环境光突变如人影掠过2. 滤波系数太小噪声大3. 阈值设置过低1. 加强LDR的物理遮光。2. 增大滤波公式中的历史值权重如从0.7改为0.8。3. 适当提高触发阈值。游戏运行卡顿1. 系统A游戏循环delay时间太短刷新过快2. 系统B循环中调试输出Serial.print过多1. 适当增加系统A主循环中的delay值如从80ms增加到100ms。2. 在系统B代码中注释掉或减少Serial.print语句串口输出会占用大量时间。6.2 项目扩展与进阶思路当基础版本成功运行后你可以尝试以下扩展让项目更具挑战性和学习价值多障碍物识别与策略优化原版游戏有仙人掌和飞鸟两种障碍。你可以尝试在屏幕不同高度放置两个LDR分别检测低处和高处的障碍。系统B的代码需要升级根据哪个LDR被触发以及恐龙当前状态是否在跳跃来决定是跳还是蹲下映射到另一个按键。这引入了简单的状态判断逻辑。引入通信协议放弃光信号改用串口UART通信。系统A在生成障碍物时通过TX引脚发送一个简单的字符指令如‘C’代表仙人掌给系统B的RX引脚。系统B收到指令后触发跳跃。这能实现更精确、抗干扰的同步让你学习到基本的串口通信知识。性能统计与显示在系统B上增加一个OLED小屏幕实时显示游戏得分、跳跃次数、成功率等统计信息。这需要学习I2C通信和OLED库的使用。机器学习“调参”将阈值、滤波系数、冷却时间等参数设置为变量编写一个简单的算法让系统在运行过程中根据跳跃成功率可通过检测游戏是否结束来间接判断自动微调这些参数模拟一个简单的自适应过程。硬件升级用摄像头模块如OV7670替代LDR结合简单的图像处理在更强大的板子如Raspberry Pi或ESP32-CAM上实现真正的视觉识别。这是从“模拟信号处理”到“数字图像处理”的巨大飞跃。这个项目从构思到实现充满了硬件交互的乐趣。它像一座桥梁连接了虚拟的游戏世界和真实的物理操作。每一次调试每一次校准都是对“感知-决策-执行”这一自动化核心概念的亲手实践。当你看到伺服电机精准地按下按键恐龙灵巧地跃过障碍时那种成就感远超单纯玩一款游戏。希望这份详细的指南能帮你顺利搭建起自己的“Arduino双机游戏系统”并在此基础上探索出更多可能。