
1. 项目概述用Arduino复刻经典打地鼠游戏作为一个电子爱好者我总想把手头的Arduino和各种元器件玩出点新花样。这次我决定挑战一个经典街机项目——电子打地鼠。市面上虽然有现成的玩具但自己从头设计、画板、编程把一堆零散的LED、按钮和芯片变成一个能玩的游戏机那种成就感是完全不同的。这个项目不仅考验硬件设计能力比如如何用有限的I/O口驱动大量LED和数码管也考验软件上的逻辑和时序控制。最终我完成了一个双人竞技版的Arduino打地鼠游戏机它拥有25个“地鼠洞”LED、8位数码管显示分数和时间以及完整的游戏逻辑。整个系统的核心是一块自主设计的L形主控PCB它通过4片74HC595移位寄存器扩展出32路输出巧妙地驱动了一个25x8的LED矩阵并将8个数码管集成其中。这篇文章我将详细拆解从电路设计、PCB布局到Arduino代码编写的全过程无论你是刚接触Arduino的新手还是想深入了解数字电路和矩阵扫描的爱好者都能从中找到可复现的细节和避坑指南。2. 核心硬件设计与思路解析2.1 系统架构与芯片选型考量项目的核心矛盾在于Arduino Uno有限的I/O口通常仅14个数字口与众多外设需求之间的矛盾。我们需要控制25个LED地鼠、8个7段数码管每段需要7个笔画加1个小数点共8段但通过动态扫描可大幅减少引脚还要读取9个按钮8个地鼠按钮1个开始按钮以及其他输入。直接连接需要数十个I/O显然不现实。我的解决方案是采用“串行转并行”的思路。输出方面选择了经典的74HC595移位寄存器。每片595有8个并行输出通过3根线数据、时钟、锁存即可串联控制多片理论上可以无限扩展。本项目使用了4片595提供了32路输出。为什么是4片这是经过精确计算的8个数码管采用动态扫描每个数码管的8个段选a-gdp需要8路信号阴极或阳极控制而位选信号选择点亮哪个数码管也需要8路。如果采用更常见的1片595控制段选另用一片595或直接使用Arduino口进行位选需要8816路输出。但本项目还将25个LED集成了一个8x9的矩阵中实际用了25个所以是8行x?列具体行列划分后文详述驱动这个矩阵又需要行、列控制线。经过对LED布局和扫描算法的优化最终将数码管段选、位选以及LED矩阵的行、列控制信号全部整合到了这32路输出上做到了“物尽其用”。输入方面按钮则直接利用Arduino的模拟口和数字口并搭配电阻网络做上拉或下拉简化电路。2.2 PCB布局的巧妙构思L形主板与模块化设计为了让整个装置更整洁、可靠我决定设计定制PCB而不是在洞洞板上飞线。PCB分为两大模块L形主控板这是系统的大脑承载Arduino Nano或Uno通过排针连接、4片74HC595、所有必要的电阻、晶体管以及输入输出的接口插座。数码管显示板一块独立的PCB上面焊接8个共阴极CC7段数码管和它们的限流电阻。这块板通过8pin排座与主控板连接。为什么主控板要设计成L形这是本设计的一个亮点。当L形主控板与长方形的数码管显示板拼合时它们能组合成一个更大的规整矩形。这样做有两个巨大优势生产省钱在工厂制板时PCB通常是拼版后一起生产的。L形矩形的组合可以像拼图一样紧密排列最大限度地利用整块覆铜板面积减少了板材浪费对于小批量生产或自己打样能有效降低成本。结构稳固这种拼合方式使得两块PCB在物理上可以互相支撑更容易固定在游戏机的外壳上整体性更强。主控板上使用了大量的排针插座9PIN、8PIN、5PIN等目的是将所有外部连接按钮、LED矩阵、数码管板、电源都通过排线连接便于后续的组装、调试和维护也使得主板看起来非常清爽。3. 电路原理深度剖析3.1 输出驱动移位寄存器矩阵与晶体管扩流4片74HC595串联形成一条32位的串行移位寄存器链。Arduino只需3个数字引脚就能按位送入32个控制信号然后通过一个锁存信号同时更新所有595的输出效率极高。这32路输出被分配用于驱动LED矩阵和数码管LED矩阵驱动25个地鼠灯我将25个LED布置成了一个8行x 4列的矩阵实际用了8x432个位置但只焊接25个对应地鼠洞布局。这样驱动一个8x4的矩阵本来需要8412根线现在通过595的输出可以控制行选通和列数据。行驱动阴极矩阵的8行共用阴极每行的阴极通过一个BC547 NPN晶体管接地。595的输出口直接驱动晶体管基极。这里必须用晶体管因为当一行中多个LED同时点亮时该行阴极的总电流可能超过100mA远超74HC595单个输出口20-35mA的驱动能力。晶体管在这里起到了电流放大和开关的作用。列驱动阳极矩阵的4列对应阳极。每个LED的阳极都串联了一个100Ω的限流电阻然后连接到595的输出上。100Ω的阻值是基于红色LED典型压降2V电源5V期望电流约20mA计算得出的(5V - 2V) / 0.02A ≈ 150Ω选用100Ω可获得稍亮且安全的电流。数码管驱动8个共阴极数码管也采用动态扫描。32路输出中的一部分被分配为8个位选信号通过晶体管控制每个数码管的阴极接地另一部分被分配为8个段选信号控制a-g dp段。动态扫描的原理是在极短的时间内如每秒扫描60帧以上依次快速点亮每一个数码管利用人眼的视觉暂留效应看起来就像是8个数码管同时稳定显示。3.2 输入电路按钮与电阻网络游戏有9个关键按钮8个对应地鼠洞的打击按钮1个游戏开始/复位按钮。此外还预留了玩家选择、难度调节电位器等输入接口。所有按钮都采用下拉电阻的设计。按钮一端接5V另一端通过一个10kΩ电阻接地同时这个连接点也接到Arduino的输入引脚。当按钮未按下时输入引脚通过电阻被拉低到GNDArduino读到LOW当按钮按下时5V直接连接到输入引脚Arduino读到HIGH。这种设计可以有效避免引脚悬空时产生不确定的电平。为了节省空间和焊接工序我没有使用9个独立的10kΩ电阻而是选用了一个SIL-1010联贴片排阻。这个排阻就像一个集成了10个电阻的芯片只有一个公共端。我将公共端接地排阻的另外9个引脚分别连接9个按钮和Arduino的9个输入口完美实现了下拉功能。同样的原理也用在其他输入上使用了SIL-6排阻。注意原计划中还有一个“传感器”输入但因为物料原因最终取消了。这在PCB上留下了一个空置的5pin接口和相应的排阻位置。在你自己设计时如果不确定某个功能是否必要可以像这样预留位置但最好在最终版本中注明或移除避免混淆。4. 软件逻辑与Arduino代码实现4.1 核心状态机与游戏逻辑游戏软件的核心是一个状态机它定义了游戏的不同阶段如“待机”、“进行中”、“游戏结束”等。状态机使程序逻辑清晰易于管理和扩展。enum GameState { IDLE, PLAYING, GAME_OVER }; GameState currentState IDLE; unsigned long gameStartTime; const unsigned long GAME_DURATION 60000; // 游戏时长60秒 int score 0; int currentMole -1; // 当前出现地鼠的位置编号 unsigned long moleTimer; // 地鼠出现/消失的计时器在主循环loop()中通过判断currentState来执行不同的代码块IDLE状态显示欢迎信息等待玩家按下“开始”按钮。PLAYING状态检查游戏时间是否用完用完则切换到GAME_OVER。运行“地鼠生成算法”随机选择一个LED位置作为地鼠并控制其点亮。设置一个随机生存时间如0.5秒到2秒时间到则地鼠消失。实时扫描所有按钮。如果在地鼠存活期间对应的按钮被按下则加分并立即让该地鼠消失给予击中反馈然后快速生成下一个。实时更新数码管显示展示剩余时间和当前分数。GAME_OVER状态显示最终分数等待重启。4.2 底层驱动移位寄存器发送与矩阵扫描这是项目中最关键、最需要精细时序控制的部分。我们需要一个函数能够将代表32个LED和数码管状态的32个比特bit发送到595链中并且还要实现动态扫描。// 假设我们有一个32位的全局变量 shiftRegisterData 来存储所有输出状态 uint32_t shiftRegisterData 0; void updateShiftRegisters() { digitalWrite(LATCH_PIN, LOW); // 由于是32位需要分4次发送每次8位一个字节 // 注意发送顺序最后一片595输出QH的数据最先被送入因为它位于链的末端 shiftOut(DATA_PIN, CLOCK_PIN, MSBFIRST, (shiftRegisterData 24) 0xFF); // 发送第4片的数据 shiftOut(DATA_PIN, CLOCK_PIN, MSBFIRST, (shiftRegisterData 16) 0xFF); // 发送第3片的数据 shiftOut(DATA_PIN, CLOCK_PIN, MSBFIRST, (shiftRegisterData 8) 0xFF); // 发送第2片的数据 shiftOut(DATA_PIN, CLOCK_PIN, MSBFIRST, shiftRegisterData 0xFF); // 发送第1片的数据 digitalWrite(LATCH_PIN, HIGH); // 锁存数据所有595同时更新输出 }对于LED矩阵和数码管的动态扫描我们需要一个定时中断。Arduino的Timer1库非常适合做这件事。在中断服务程序ISR中关闭当前正在显示的行和数码管位选防止鬼影。根据shiftRegisterData中对应的位计算并设置下一行/下一个数码管需要点亮的LED段或列。打开下一行/下一个数码管的位选导通对应的晶体管。循环进行扫描频率通常设置在100Hz以上画面才会稳定无闪烁。实操心得动态扫描的时序一定要精确。关闭上一帧和开启下一帧之间的间隔要尽可能短否则会出现“鬼影”不该亮的LED微微发亮。确保你的晶体管开关速度足够快BC547没问题并且digitalWrite操作在中断里要高效。可以考虑直接操作Arduino的端口寄存器如PORTB、PORTD来获得极速的引脚控制但这需要对硬件寄存器有了解。4.3 防抖与响应优化按钮输入必须进行软件防抖。机械按钮在按下和弹起的瞬间会产生一段时间的电平抖动可能被Arduino误读为多次按下。const int DEBOUNCE_DELAY 50; // 防抖延时50毫秒 unsigned long lastDebounceTime 0; int lastButtonState LOW; int reading; void checkButton() { reading digitalRead(BUTTON_PIN); if (reading ! lastButtonState) { lastDebounceTime millis(); // 重置防抖计时器 } if ((millis() - lastDebounceTime) DEBOUNCE_DELAY) { // 此时状态稳定进行业务逻辑判断 if (reading HIGH lastButtonState LOW) { // 检测到上升沿按钮被按下 hitMole(); } } lastButtonState reading; }为了游戏体验地鼠的生成算法需要一点“智能”。完全随机可能让地鼠出现在刚被打过的洞里或者长时间不出现在某个角落。可以加入一些权重机制让一段时间未出现的地鼠位置有更高的概率被选中让游戏节奏更均衡。5. 组装、调试与问题排查实录5.1 PCB焊接与组装顺序先贴片后直插首先焊接主控板上的贴片元件如电阻网络SIL。使用烙铁和镊子注意不要连锡。焊接芯片底座务必使用IC插座千万不要把74HC595直接焊死在板上。这样万一芯片损坏静电或接反电源很容易烧更换起来会非常方便。焊接接口排针、排母是连接的核心要确保焊接垂直、牢固。焊接完成后可以用万用表通断档检查是否有虚焊。分模块测试不要等全部焊完再上电。可以先焊接主控板的电源部分和Arduino接口通电测试5V和3.3V是否正常。然后插上一片595写一个简单的测试程序如让它的8个输出依次闪烁验证焊接和芯片是否正常。连接显示板将数码管显示板通过排线连接到主控板。单独测试数码管写一个显示“8.8.8.8.”的程序检查所有段和所有位是否都能正常点亮。连接LED矩阵这部分最繁琐。25个LED是直接安装在外壳上的需要用长脚杜邦线或导线逐个连接到主控板的对应接口。务必做好标记建议先点亮单个再测试一行一列最后用程序扫描整个矩阵。5.2 常见问题与解决方案速查表问题现象可能原因排查步骤与解决方案整个系统无反应电源指示灯不亮1. 电源接反或电压不对2. 主电源线路有短路1. 检查电源适配器极性中心正极常见用万用表测量输出电压是否为5V。2. 断开电源用万用表蜂鸣档测量5V与GND之间的电阻如果接近0欧姆说明存在严重短路需仔细检查焊接。Arduino程序上传失败1. 板卡型号选错2. 串口被占用3. USB线或芯片问题1. 在IDE中正确选择板型如Arduino Nano w/ ATmega328P。2. 关闭可能占用串口的软件如串口监视器、其他IDE。3. 尝试按一下复位键再上传或换一条数据线。部分LED或数码管段不亮1. 该路限流电阻虚焊或阻值错误2. LED本身损坏或极性焊反3. 对应的595输出位损坏1. 用万用表测量电阻两端是否导通阻值是否为100Ω。2. 用万用表二极管档单独测试LED或短接一个已知好的LED到该点位测试。3. 写一个简单程序单独测试该595芯片的每一个输出位看是否都能正常拉高/拉低。显示闪烁、抖动或鬼影严重1. 动态扫描频率太低2. 晶体管开关速度慢或驱动不足3. 中断被长时间关闭1. 提高定时器中断的频率尝试从100Hz提高到200Hz或更高。2. 检查晶体管基极电阻是否合适通常1k-10k确保能提供足够基极电流使其饱和导通。3. 确保在中断服务程序ISR中执行最少的代码避免在loop()或其它地方长时间关闭全局中断。按钮响应不灵或连击1. 软件防抖参数设置不当2. 下拉电阻未接或虚焊3. 按钮本身接触不良1. 调整防抖延时DEBOUNCE_DELAY通常在20-50ms之间寻找最佳值。2. 用万用表测量按钮未按下时输入引脚电压是否接近0VLOW。3. 更换一个按钮测试。游戏运行一段时间后死机或复位1. 电源功率不足2. 程序内存泄漏或堆栈溢出3. 芯片过热1. 所有LED和数码管全亮时电流最大。估算峰值电流如25个LED20mA 8个数码管8段*10mA确保你的5V电源适配器能提供至少1.5A以上的电流。2. 检查代码中是否不当使用了String类或动态内存分配尽量使用静态数组。使用Serial.print(freeMemory())监控内存变化。3. 触摸74HC595和晶体管如果烫手说明电流过大或散热不良检查负载是否有短路。5.3 最终整合与外壳设计当所有电路测试无误后就可以进行总装。将主控板、数码管板固定到自制或改造的外壳内。25个LED作为“地鼠洞”均匀分布在外壳面板上按钮则安装在每个LED旁边。排线整理好并用扎带固定。外壳材料可以选择亚克力、木材或3D打印。设计时务必考虑散热孔虽然功耗不大、按钮手感以及整体的美观度。一个建议是将数码管显示部分稍微抬高让玩家在击打时能轻松看到分数和时间变化。完成所有硬件组装后烧录最终的游戏程序。第一次按下开始按钮看到地鼠随机亮起奋力敲击得分数码管数字飞快跳动——那一刻所有调试的辛苦都值了。这个项目不仅仅是一个游戏机更是一个融合了数字电路、微控制器编程、硬件设计和动手能力的综合实践它带给你的乐趣和知识远超一个成品玩具。