1. 项目概述:一个能“考”你记忆力的硬件游戏机
如果你对硬件编程感兴趣,或者想找一个能真正把代码和物理世界连接起来的项目来练手,那么这个基于Arduino的记忆游戏机绝对是个绝佳的选择。它不像纯软件项目那样抽象,你能亲手触摸到每一个发光二极管(LED)和按钮,亲眼看到你的程序如何让它们“活”起来。这个项目的核心,就是利用Arduino Uno这块小小的开发板,制作一个经典的“西蒙说”(Simon Says)类型的记忆游戏:设备会按顺序点亮一组彩色的LED,玩家需要记住这个序列,然后通过按下对应颜色的按钮来复现它。随着游戏进行,序列会越来越长,挑战你的瞬时记忆极限。
听起来简单?但要把这个想法从电路图变成你桌上一个能稳定运行、有模有样的游戏机,里面涉及的知识点可不少。你需要理解如何用Arduino的GPIO(通用输入输出)引脚同时控制输入(按钮)和输出(LED),需要计算限流电阻来保护你的LED不被烧毁,需要编写逻辑清晰的状态机代码来处理游戏流程,甚至还需要考虑如何为你的作品做一个漂亮又实用的外壳。整个过程,就是一个微缩版的嵌入式产品开发流程。无论你是电子爱好者、编程初学者,还是想找点有趣项目做的创客,跟着做一遍,你对硬件系统的理解会上一个台阶。我当年就是被这样一个项目“拖下水”,从此爱上了这种看得见摸得着的编程乐趣。
2. 核心硬件选型与电路设计思路
2.1 为什么是Arduino Uno?
在开始动手之前,我们先聊聊为什么选择Arduino Uno作为这个项目的大脑。市面上微控制器很多,比如树莓派Pico、ESP32等。选择Uno,首要原因是它的生态成熟和入门友好性。它的引脚布局规整,数字引脚(Digital Pins)和模拟引脚(Analog Pins)区分明确,对于这个只需要处理数字开关信号(按钮按下/松开,LED亮/灭)的项目来说,完全够用且易于理解。其次,Uno基于ATmega328P芯片,其5V的工作电压与我们选用的LED和按钮的常见工作电压匹配,省去了电平转换的麻烦。最后,Arduino IDE的简单易用和庞大的社区支持,意味着你在编程和调试中遇到的几乎任何问题,都能快速找到答案。对于第一个硬件交互项目,降低环境搭建的复杂度,把精力集中在核心逻辑上,至关重要。
2.2 元器件清单与功能解析
一份清晰的物料清单是成功的一半。下面这个表格不仅列出了所需物品,还解释了每一件在电路中的作用,让你知其然也知其所以然。
| 元器件 | 数量 | 规格/备注 | 在项目中的作用与原理 |
|---|---|---|---|
| Arduino Uno | 1 | 主流开发板,带USB接口 | 项目主控,运行游戏逻辑代码,通过引脚输出控制信号、读取输入信号。 |
| 面包板 | 1 | 400孔或830孔无焊试验板 | 用于快速、非永久性地搭建和测试电路,方便修改和排查问题。 |
| LED | 4 | 红、绿、蓝、白各一,直径5mm | 视觉输出设备。不同颜色对应不同按钮,用于显示游戏序列和反馈。 |
| 按钮(微动开关) | 4 | 常开型,四脚 | 玩家输入设备。按下时连通电路,向Arduino发送一个高电平或低电平信号(取决于电路设计)。 |
| 电阻 | 8 | 220欧姆,1/4瓦 | 核心保护元件。4个用于LED限流(串联),防止电流过大烧毁LED;4个用于按钮上拉或下拉(根据设计),确保引脚电平稳定。 |
| 杜邦线 | 若干 | 公对公(Male-Male) | 连接Arduino引脚与面包板,或面包板上不同节点。是电路的“血管”。 |
| 细铜线 | 1段 | 单芯线,约0.5mm直径 | 用于在面包板内部进行短距离、精细的跳线,特别是在空间紧凑时连接同一排的孔位。 |
| USB数据线 | 1 | A口转B口(方口) | 为Arduino供电,并上传程序代码。 |
| (可选)按钮帽 | 4 | 颜色最好与LED对应 | 改善按钮手感和外观,让按压更舒适,也便于玩家识别。 |
| (可选)3D打印机 | - | - | 用于打印自定义的游戏外壳,让作品更完整、美观,并保护内部电路。 |
注意:关于电阻值的计算。为什么是220欧姆?这是一个经验值,但有其道理。假设我们使用典型的红色LED,其正向压降(VF)约为1.8V-2.2V,工作电流(IF)推荐在10-20mA。Arduino引脚输出5V。根据欧姆定律:R = (Vcc - VF) / IF。取VF=2V, IF=15mA,则 R = (5-2)/0.015 ≈ 200欧姆。选择稍大一点的220欧姆标准阻值,可以将电流限制在约13.6mA,既能保证LED足够亮,又留有安全余量,是非常稳妥的选择。对于其他颜色的LED,计算方式相同,只需查询其具体的VF值。
2.3 电路连接原理:输入与输出的“舞蹈”
这个项目的电路本质上是四个完全相同的“LED-按钮”配对单元,并联在Arduino上。理解一个单元的连接,就理解了全部。
输出回路(LED控制):每个LED的正极(长脚)通过一个220Ω电阻,连接到Arduino的一个数字输出引脚(例如引脚2)。LED的负极(短脚)连接到面包板的负极总线(GND)。当程序将对应引脚设置为HIGH(5V)时,电流从引脚流出,经电阻、LED流向GND,LED点亮。设置为LOW(0V)时,LED熄灭。
输入回路(按钮读取):这里采用上拉电阻的接法,这是确保信号稳定的关键。按钮的一端连接到Arduino的一个数字输入引脚(例如引脚6)。同一端,通过一个220Ω电阻连接到正极总线(5V)。按钮的另一端直接连接到GND。当按钮未按下时,输入引脚通过上拉电阻被“拉”到5V(HIGH),程序读取为高电平。当按钮按下时,引脚直接短路到GND,变为0V(LOW),程序读取为低电平。这种设计避免了引脚悬空时可能产生的随机电平波动。
四个这样的单元,分别占用Arduino的8个数字引脚(4个输出,4个输入),就构成了完整的硬件交互界面。电源方面,将面包板的正极总线(+)连接到Arduino的5V引脚,负极总线(-)连接到Arduino的GND引脚,为整个电路供电。
3. 分步硬件搭建与关键操作要点
3.1 第一步:LED与限流电阻的布局
这是视觉部分的基础,务必确保LED极性正确,否则不会发光。
- 规划位置:将面包板横放,在中间凹槽的上半部分,从左到右依次放置红、绿、蓝、白四个LED。我习惯每个LED之间间隔3-5个孔位,为后续连接留出空间。
- 插入LED:确保所有LED的方向一致。通常,LED的长脚为正极(阳极),短脚为负极(阴极)。将每个LED的正极插入同一行(例如第15行)的一个孔,负极插入下方一行(例如第16行)的对应孔。统一将正极放在右侧是一个好习惯,便于后续检查。
- 连接限流电阻:取4个220Ω电阻。对于每个LED,将其正极所在的那一列(例如E15孔),与下方空着的一行(例如E17孔)用电阻连接起来。电阻没有极性,两端任意插。这样,电流未来会从Arduino引脚→电阻→LED正极→LED负极→GND,电阻起到了关键的限流作用。
实操心得:在面包板上,同一行的五个孔(A-E或F-J)在内部是连通的。利用这个特性,你可以把LED正极插在E15,电阻一端插在E15(与LED正极连通),另一端插在E17。这样连接既牢固又清晰。务必在通电前用万用表二极管档或肉眼再次核对LED极性,反接虽不会立刻损坏,但不会亮。
3.2 第二步:按钮与上拉电阻的安装
按钮是玩家交互的触点,其连接的稳定性直接影响游戏体验。
- 放置上拉电阻:在面包板底部(凹槽下半部分),选择四行(例如第25、30、35、40行),在每一行的F列孔位插入一个220Ω电阻。电阻的另一端,需要连接到正极总线。假设你的正极总线在面包板最上方的长条(标有“+”),就用杜邦线将这些电阻的另一端(例如第25、30、35、40行的J列)都连接到正极总线的任意孔。
- 安装按钮:选择四脚常开型微动开关。将其跨坐在面包板的中间凹槽上,使得按钮的同一侧的两个引脚分别位于凹槽上方和下方的同一列(例如,按钮左侧两脚在C列和D列,跨接凹槽)。将按钮一侧的一个引脚(例如左上脚),与刚才放置的上拉电阻所在行(例如第25行E列)用短线或铜线连接。这样,这个引脚就既通过电阻接到了5V,又将成为Arduino的输入检测点。
- 连接按钮另一端:将按钮另一侧的两个引脚(在凹槽另一边)用短线在内部连通,并统一连接到面包板的负极总线(GND)。这样,当按钮按下时,输入检测点就从通过电阻接5V,变成了直接接GND,完成了高电平到低电平的转变。
3.3 第三步:连接Arduino与控制信号线
现在要把面包板上的电路和大脑(Arduino)连接起来。
- 供电:用一根杜邦线,连接Arduino的5V引脚到面包板正极总线(+)。再用另一根杜邦线,连接Arduino的GND引脚到面包板负极总线(-)。至此,整个电路的电源通道建立。
- 连接LED控制线:用4根杜邦线,分别将Arduino的数字引脚2, 3, 4, 5(作为输出)连接到对应LED的限流电阻“自由端”。例如,引脚2的线连接到第一个(红色)LED电阻的空置端(之前插在E17孔的那个脚)。
- 连接按钮信号线:再用4根杜邦线,分别将Arduino的数字引脚6, 7, 8, 9(作为输入)连接到对应按钮的上拉电阻节点。即连接到每个按钮与上拉电阻相连的那个引脚(例如第一个按钮的第25行E列)。
3.4 第四步:使用铜线完成内部布线
这一步最考验耐心和细致,目的是用更美观紧凑的方式完成面包板内部的连接,特别是将LED的负极和按钮的GND端汇总到总线上。
- 连接LED负极到GND总线:剪4段约2英寸的细铜线,剥开两端。对于每个LED,将其负极(例如第16行J列)所在的列,用铜线垂直向下引到面包板底部的负极总线(-)。确保铜线插紧,接触良好。
- 连接按钮公共端到GND总线:对于每个按钮,其连接到GND的那一侧引脚可能不在同一行。你需要用更短的铜线(约1英寸),将这一侧的两个引脚在面包板下半区内部横向连接起来,并最终引出一根线接到负极总线(-)。也可以将四个按钮的GND端先在一个空行上汇合,再用一根线接到总线。
- 最终检查:这是通电前最后的机会。对照电路图或原理,从电源(5V)出发,沿着每条通路走一遍,检查是否有短路(正负极直接碰在一起)、断路(该连的没连)、以及LED和按钮方向是否正确。也可以用万用表通断档,检查关键连接点是否导通。
4. 游戏逻辑的软件实现与编程详解
硬件是躯体,软件是灵魂。下面我们深入代码,看看如何让这一堆元器件按我们的规则“跳舞”。
4.1 基础配置与变量定义
首先,我们需要在代码开头进行引脚映射和常量定义,这是良好编程习惯的开始。
// 引脚定义:保持与硬件连接一致 const int ledPins[] = {2, 3, 4, 5}; // 控制LED的引脚 const int buttonPins[] = {6, 7, 8, 9}; // 读取按钮的引脚 const int numLeds = 4; // LED/按钮的数量 // 游戏参数 #define PLAYER_WAIT_TIME 3000 // 玩家输入最大等待时间(毫秒) int sequence[100]; // 存储生成的灯光序列,假设最多100步 int sequenceLength = 0; // 当前序列的长度 int currentStep = 0; // 玩家当前需要复现的序列第几步 bool gameActive = false; // 游戏是否正在进行 bool inputEnabled = false; // 是否允许玩家输入 unsigned long inputTimeout; // 玩家输入超时的时间点注意:使用数组来管理引脚,可以让我们用循环来处理多个LED和按钮,极大简化代码。
PLAYER_WAIT_TIME定义为3000毫秒(3秒),这是一个经过测试比较合理的值,给玩家反应时间,又不至于让游戏节奏太拖沓。sequence数组要足够大,以防高手玩出很长的序列。
4.2 核心函数剖析:从初始化到游戏循环
setup()函数:硬件初始化
void setup() { Serial.begin(9600); // 初始化串口,用于调试输出信息 // 设置LED引脚为输出模式,并初始化为低电平(熄灭) for (int i = 0; i < numLeds; i++) { pinMode(ledPins[i], OUTPUT); digitalWrite(ledPins[i], LOW); } // 设置按钮引脚为输入模式,并启用内部上拉电阻 // 注意:如果你在硬件上已经接了外部上拉电阻(如本项目),则应使用INPUT模式。 // 但更常见的做法是省去外部电阻,直接使用INPUT_PULLUP模式,此时按钮另一端应接GND。 // 本项目硬件已接上拉,故用INPUT。若你省去了外部电阻,请改为INPUT_PULLUP。 for (int i = 0; i < numLeds; i++) { pinMode(buttonPins[i], INPUT); } randomSeed(analogRead(A0)); // 用未连接的模拟引脚噪声作为随机数种子 initializeGame(); // 调用游戏初始化函数 }这里有一个关键点:上拉电阻的使用。代码注释中提到了两种方式。本项目采用外部物理电阻上拉,所以引脚模式设为INPUT。许多简化电路会利用Arduino芯片内部的上述电阻,将模式设为INPUT_PULLUP,同时将按钮另一端接GND。两种方式逻辑相反:外部上拉时,未按下为高电平(HIGH),按下为低电平(LOW);内部上拉时,未按下为高电平(内部拉高),按下也为低电平(接GND)。在代码中判断按钮是否按下时,需要根据你的电路选择判断条件。
flashAllLeds()函数:状态反馈
void flashAllLeds(int times, int delayTime) { for (int i = 0; i < times; i++) { // 全部点亮 for (int j = 0; j < numLeds; j++) { digitalWrite(ledPins[j], HIGH); } delay(delayTime); // 全部熄灭 for (int j = 0; j < numLeds; j++) { digitalWrite(ledPins[j], LOW); } delay(delayTime); } }这个函数用于游戏开始、胜利、失败时的视觉提示。通过控制闪烁次数和频率,可以传达不同的信息。例如,游戏开始时快速闪烁两次,失败时快速闪烁五次。
generateNextSequence()函数:生成挑战
void generateNextSequence() { sequence[sequenceLength] = random(0, numLeds); // 生成一个0到3的随机数,对应一个LED/按钮 sequenceLength++; Serial.print("New Sequence: "); for (int i = 0; i < sequenceLength; i++) { Serial.print(sequence[i]); Serial.print(" "); } Serial.println(); }每次玩家成功通过一轮,就调用此函数在序列末尾添加一个新的随机步骤。random(0, numLeds)生成一个介于0(包含)和numLeds(不包含)之间的整数,正好对应ledPins和buttonPins数组的索引。
playSequence()函数:演示序列
void playSequence() { for (int i = 0; i < sequenceLength; i++) { int ledIndex = sequence[i]; digitalWrite(ledPins[ledIndex], HIGH); delay(500); // LED点亮持续时间 digitalWrite(ledPins[ledIndex], LOW); delay(300); // 步骤间的间隔时间 } }这是游戏的核心展示环节。函数遍历当前序列,依次点亮对应的LED。delay(500)和delay(300)的时间决定了游戏的速度和节奏感,你可以调整这些值来改变游戏难度。
4.3 游戏主循环与玩家输入处理
loop()函数是游戏逻辑的调度中心,它需要处理两种主要状态:演示序列和等待玩家输入。
void loop() { if (!gameActive) { // 游戏未开始,等待启动信号(例如按某个按钮) // 这里可以简化为上电即开始,或设置一个启动按钮 startNewGame(); } // 状态机:演示阶段 -> 输入阶段 -> 判断阶段 static enum { SHOW_PATTERN, GET_INPUT, CHECK_INPUT } gameState = SHOW_PATTERN; switch (gameState) { case SHOW_PATTERN: playSequence(); gameState = GET_INPUT; currentStep = 0; inputEnabled = true; inputTimeout = millis() + PLAYER_WAIT_TIME; // 设置输入超时时刻 break; case GET_INPUT: if (millis() > inputTimeout) { // 玩家超时未响应 gameOver(); gameState = SHOW_PATTERN; break; } for (int i = 0; i < numLeds; i++) { // 根据你的上拉电路选择判断条件 // 外部上拉:按下为 LOW // 内部上拉(INPUT_PULLUP):按下也为 LOW if (digitalRead(buttonPins[i]) == LOW) { // 防抖延时,避免一次按下触发多次 delay(50); // 等待按钮释放 while(digitalRead(buttonPins[i]) == LOW) { /* 等待 */ } delay(50); // 释放防抖 // 提供视觉反馈:点亮对应的LED digitalWrite(ledPins[i], HIGH); delay(200); digitalWrite(ledPins[i], LOW); // 检查按下的按钮是否正确 if (i == sequence[currentStep]) { currentStep++; if (currentStep >= sequenceLength) { // 玩家正确完成了本轮序列 gameState = CHECK_INPUT; } inputTimeout = millis() + PLAYER_WAIT_TIME; // 重置超时计时器 } else { // 按错了按钮 gameOver(); gameState = SHOW_PATTERN; } break; // 一次只处理一个按钮按下 } } break; case CHECK_INPUT: // 玩家正确完成了一轮 flashAllLeds(2, 200); // 胜利闪烁提示 delay(1000); generateNextSequence(); // 增加序列难度 gameState = SHOW_PATTERN; // 回到演示阶段,开始下一轮 break; } }这是游戏逻辑的核心——一个简单的状态机。它清晰地划分了游戏的三个阶段:SHOW_PATTERN(演示)、GET_INPUT(获取输入)、CHECK_INPUT(检查并进入下一轮)。在GET_INPUT状态中,代码不断扫描四个按钮,并加入了按键消抖逻辑,这是硬件编程中防止误触的必备技巧。同时,通过millis()函数管理超时,使得游戏体验更完整。
gameOver()与startNewGame()函数
void gameOver() { flashAllLeds(5, 100); // 快速闪烁5次表示失败 Serial.print("Game Over! Final Score: "); Serial.println(sequenceLength - 1); // 分数是成功通过的轮数 delay(2000); initializeGame(); } void startNewGame() { flashAllLeds(2, 150); // 闪烁两次表示游戏开始 initializeGame(); gameActive = true; } void initializeGame() { sequenceLength = 0; currentStep = 0; generateNextSequence(); // 生成第一个随机步骤 }这些函数处理游戏的开始与结束,提供清晰的视觉和串口反馈,并重置游戏状态。
5. 外壳设计与制作:从原型到产品
一个裸露的面包板电路是“原型”,加上外壳才能称为“产品”。外壳不仅美观,更能保护电路,提供更好的用户体验。
5.1 设计考量与建模要点
使用Fusion 360、Tinkercad或SolidWorks等软件进行设计。核心考量如下:
- 精确测量:首先用卡尺精确测量你的面包板长、宽、高,以及四组LED和按钮的中心距。这个距离决定了外壳开孔的位置。
- 开孔设计:
- LED孔:直径略大于LED灯珠直径(通常5mm LED开5.5mm孔),深度要确保LED能刚好凸出或与表面平齐。
- 按钮孔:方形或圆形,尺寸要匹配你使用的按钮帽或按钮本身的按压部分。如果使用按钮帽,需预留卡扣空间。
- 线缆出口:在侧面或背面设计一个凹槽或孔洞,让USB线可以穿出。
- 固定方式:设计卡扣或螺丝柱来固定面包板。可以在外壳底部设计四个立柱,插入面包板背面的孔中。更稳固的方式是设计上盖,将面包板夹在中间。
- 散热与观察:虽然本项目功耗极低,但良好的通风总是好的。可以在外壳侧面或背面设计一些栅格。如果想展示内部电路,可以考虑用透明亚克力板做上盖。
5.2 3D打印与后期处理
- 切片设置:将设计好的模型导出为STL格式,导入切片软件(如Cura、PrusaSlicer)。对于此类外壳,建议:
- 层高:0.2mm,平衡打印速度与表面质量。
- 填充率:15%-20%即可,保证强度同时节省材料和时间。
- 支撑:如果模型有悬空部分(如内部的固定柱),需要生成支撑。记得在打印后小心去除。
- 打印后处理:
- 清理支撑:使用镊子或剪钳仔细去除所有支撑材料。
- 试装配:打印完成后,先不要安装电路,将外壳上下盖合起来,检查开孔是否对齐,面包板能否放入,按钮能否顺畅按下。
- 打磨:如果开孔略小或边缘有毛刺,使用小锉刀或砂纸进行精细打磨。对于按钮孔,尤其需要确保按钮能自由活动,无卡滞。
- 总装:将面包板电路小心地放入下壳。特别注意:在合上外壳的过程中,要确保没有挤压或拉扯到任何跳线,特别是那些细铜线。可以先将LED和按钮穿过对应的孔,再将面包板轻轻压入定位柱。最后盖上上盖,拧紧螺丝或扣紧卡扣。
6. 调试、优化与扩展玩法
6.1 常见问题排查速查表
即使按照步骤操作,第一次也难免遇到问题。下表列出了常见故障现象、可能原因及解决方法。
| 现象 | 可能原因 | 排查步骤与解决方法 |
|---|---|---|
| 上电后所有LED不亮 | 1. 电源未接通 2. 电源正负极接反 3. 总GND或5V线虚接 | 1. 检查USB线是否插紧,Arduino电源指示灯是否亮。 2. 用万用表检查面包板正负总线电压是否为5V。 3. 重新插拔连接总线的杜邦线。 |
| 单个LED不亮 | 1. LED极性接反 2. 该路限流电阻虚焊或损坏 3. 控制该LED的引脚线松动 4. 程序中该引脚未正确设置为输出 | 1. 将LED拔出调转方向再插入。 2. 更换该电阻,或检查电阻两端是否接触良好。 3. 检查连接该LED的杜邦线两端。 4. 检查 setup()中对应引脚是否在ledPins数组并被设置为OUTPUT。 |
| LED常亮或微亮 | 1. 控制引脚模式错误(如设为输入) 2. 电路存在轻微短路 3. 内部上拉电阻影响(若误用) | 1. 确认程序中将该引脚设置为OUTPUT。2. 检查面包板该LED附近是否有锡屑或导线搭接。 3. 如果引脚模式曾设为 INPUT_PULLUP,改为OUTPUT后需重启。 |
| 按钮无反应 | 1. 按钮信号线接错或虚接 2. 上拉电阻未接或开路 3. 按钮引脚模式设置错误 4. 代码中判断逻辑与硬件不匹配 | 1. 用万用表通断档,按下按钮时检查信号线到GND是否导通。 2. 检查上拉电阻是否一端接5V,一端接按钮引脚。 3. 确认 pinMode设置为INPUT(外部上拉)或INPUT_PULLUP(内部上拉)。4.重点:根据你的上拉方式,检查 if(digitalRead(pin) == LOW)或== HIGH是否正确。 |
| 按钮按下触发多次 | 按键抖动(Bouncing) | 在代码中读取按钮状态后,增加消抖延时,如delay(10),并等待按钮释放。 |
| 序列播放混乱 | 1.ledPins和buttonPins数组顺序不对应2. 随机数种子固定,序列总是相同 | 1. 确保数组索引0,1,2,3分别对应红、绿、蓝、白LED及其按钮。 2. 使用 randomSeed(analogRead(A0))读取悬空模拟引脚噪声作为种子。 |
| 游戏逻辑错乱 | 状态机逻辑错误或变量未重置 | 1. 使用串口打印调试信息,观察gameState、currentStep等变量的变化。2. 确保在游戏开始或结束时正确调用 initializeGame()重置所有状态变量。 |
6.2 性能优化与功能扩展
基础版本运行稳定后,你可以尝试以下优化和扩展,让游戏更具挑战性和趣味性:
增加难度梯度:
- 速度变化:随着
sequenceLength增加,在playSequence()函数中减少delay(500)和delay(300)的时间,让灯光闪烁更快。 - 加入声音:连接一个无源蜂鸣器到另一个数字引脚。在播放序列、按钮按下、成功/失败时,用
tone()函数播放不同频率的提示音,打造多感官体验。 - 随机间隔:在序列播放时,每个灯光之间的间隔时间也随机化,增加记忆难度。
- 速度变化:随着
提升交互反馈:
- 得分显示:添加一个四位数码管或OLED屏幕,实时显示当前分数(
sequenceLength - 1)和最高分。 - 渐变效果:使用PWM(脉宽调制)引脚控制LED,用
analogWrite()实现灯光淡入淡出效果,而非简单的亮灭。 - 多游戏模式:通过增加一个模式切换按钮,实现“经典模式”、“速度模式”、“镜像模式”(玩家需按相反顺序重复)等。
- 得分显示:添加一个四位数码管或OLED屏幕,实时显示当前分数(
代码结构优化:
- 使用非阻塞延时:将
delay()替换为基于millis()的时间判断,这样在等待期间Arduino仍然可以处理其他任务(如扫描按钮),使系统响应更灵敏。 - 面向对象封装:如果LED和按钮数量很多,可以创建一个
GameButton类,将引脚、状态、消抖逻辑封装起来,使主程序更简洁。
- 使用非阻塞延时:将
这个项目从一根线、一个电阻开始,到最终成为一个可以与人交互的智能游戏装置,完整地走通了嵌入式开发中“需求-设计-实现-调试”的闭环。它最宝贵的价值不在于复现了一个游戏,而在于让你亲手触摸了电流的路径,理解了代码如何驱动硬件,并解决了其中遇到的所有“为什么”和“怎么办”。当你按下自己焊接的按钮,看到自己编写的代码点亮对应的LED,并成功挑战自己的记忆极限时,那种成就感是纯软件编程无法给予的。我的个人记录是21步,你的挑战开始了。