十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

树莓派4B+RFID实体卡带:打造自动识别启动的N64复古游戏机

树莓派4B+RFID实体卡带:打造自动识别启动的N64复古游戏机 树莓派4B、GPIO、RetroPie、N64模拟器这几个词单独看都不稀有但把它们组合成一台带实体卡带的复古游戏机就完全是另一码事了。这个项目最终做出来的效果一台小主机放在电视柜上旁边摆着几个外形接近N64原版卡带的3D打印壳子。你拿起写着“塞尔达时之笛”的卡顺着插槽推到底电视上自动开始游戏玩够了直接拔卡模拟器进程自动退出换一张插进去下一款游戏无缝启动。整个过程没有菜单、没有手柄滚动列表体验上更接近一台真的N64。树莓派用的是4B系统走的RetroPie卡带识别用的是接在GPIO上的MFRC522 RFID模块。文章后面会把所有关键决策、硬件接线、Python识别脚本、与RetroPie联动的方式以及调试时真正折磨人的坑都写清楚。不管你是想复刻一台完整机器还是只打算在树莓派上做一个“插卡启动游戏”的小功能这篇应该都能帮你少走弯路。1. 项目构思与整体架构1.1 为什么非要做成实体卡带数字版游戏和模拟器菜单明明更方便为什么还要给自己找这个麻烦最开始我其实是图省事RetroPie装上EmulationStation之后游戏都在一个列表里手柄选一选就能玩。但用了一个星期之后我发现一个问题菜单里躺着几百个ROM我反而不知道玩什么。选择太多的时候人往往会关掉菜单去刷手机。实体卡带恰恰解决了这个问题。它的本质是把“游戏选择”做成了物理操作——卡带本身就是菜单。你看到桌子上摆着哪几张卡就知道这台机器的游戏库有多大你拿起哪张卡就决定了接下来玩什么。这种唯一性和确定性在体验上是数字列表完全替代不了的。尤其是家里有小孩或者长辈的时候告诉他们“想玩哪个就插哪个”一分钟就能学会不用教怎么操作手柄选游戏。从技术层面说卡带也提供了一种有趣的人机交互方式拔插动作可以直接触发事件——插卡启动、拔卡退出。这比在UI里按Home键退出、再进另一个ROM要直接得多。做这个项目之后我最大的感触是有些看上去“退步”的设计反而能带来完全不同的使用感受。1.2 三种卡带识别方案对比要让树莓派知道“现在插的是哪张卡”方案其实不止一个我认真对比过三条路线。第一种是RFID/NFC方案。MFRC522是13.56MHz的读卡模块通过SPI接到GPIO模块10到20块钱卡贴标签更是几毛钱一个。优点是卡带和主机之间完全没有金属触点长期插拔零磨损不会有氧化接触不良的毛病缺点是读卡距离有限通常只有2到4厘米需要把卡带放到比较精确的位置。第二种是I2C EEPROM方案。在卡带里嵌入一片AT24C08之类的EEPROM插槽用POGO Pins或金手指和板子上的电路连通识别信息存在芯片里。这种方案非常“真实”卡带和主机之间有物理电接触会让人想起真正的卡带机。但问题也很明显触点容易氧化、POGO Pins的位置精度要求高、卡带外壳设计和制作难度陡增。DIY做起来很容易因为几个触点对不齐而翻车。第三种是电阻分压或拨码开关方案用ADC读电阻值或者用十几个GPIO读拨码组合来区分卡带。成本最低但机械结构复杂每张卡要做一套跳线扩展性很差。我的选择很明确——RFID。它在可靠性和实现难度之间取得了最好的平衡插拔无磨损、模块便宜、树莓派上驱动成熟。而且RFID标签本身就是一张薄片粘在卡带壳里就行外壳设计完全不用考虑导电触点。方案成本触点磨损实现难度可靠性RFID/NFC低无低高I2C EEPROM中有高中电阻分压极低有中低1.3 整体框架与工作流程整个系统的结构可以拆成四层最底层是树莓派4B和RetroPie负责模拟运行N64游戏中间层是MFRC522读卡模块负责感知卡带的存在和身份再上面是一个Python守护脚本负责读UID、查映射表、拉起或关闭模拟器最外围是3D打印的卡带壳和主机的卡槽结构。工作流程是这样的机器通电后Python脚本常驻后台以极短的间隔轮询读卡器。待机状态下没有卡脚本什么都不做。插入卡带后读卡器得到一个UID脚本在games.json映射表里查找这个UID对应的游戏名和ROM路径。如果当前已经有模拟器在跑先杀掉旧进程再通过RetroPie的runcommand启动新游戏。拔卡时读卡器检测不到UID脚本把正在运行的模拟器进程关掉系统回到待机插卡状态。这套流程听起来简单但真正的坑全在细节里读卡器在无卡时会抛出异常、进程杀掉之后要等端口释放、runcommand必须用非阻塞方式启动。后面的章节我会挨个展开讲。2. 硬件准备与接线实现2.1 树莓派选型与系统安装树莓派方面我推荐4B起步。N64模拟对CPU单核性能要求不低3B也能跑但很多游戏会掉到30帧以下体验打折扣。4B的话2GB内存版本勉强够用预算允许直接上4GB版本更稳跑mupen64plus的同时还能给后台脚本留足余量。系统安装我走了最简单的路线去RetroPie官网下载官方镜像文件用balenaEtcher烧录到一张32GB以上的TF卡里。烧好后插卡开机第一次进入的是EmulationStation图形界面。在开始做卡带之前先进RetroPie-Setup把N64的核心装上。路径是RetroPie菜单 → RetroPie Setup → Manage packages → Manage libretro cores → 安装lr-mupen64plus-next。这个核心是Mupen64Plus的libretro版本在Pi 4上的兼容性和画质都优于旧核心。散热和供电这两件事必须提前做。Pi 4跑N64模拟器时CPU会持续高负载裸板会烫得厉害我买的是带风扇的铝合金外壳散热片一定要贴到位。电源方面别用手机充电头凑合建议用官方5V/3A电源否则高负载时可能直接重启卡带插一半突然断电的感觉可不好受。2.2 GPIO引脚规划与MFRC522接线MFRC522读卡模块一共有8个引脚其中我们需要接6个SDA、SCK、MOSI、MISO、RST、GND外加3.3V电源。树莓派4B的GPIO排针是40脚的注意别被网上的老教程误导Arduino和树莓派的接线方式不一样。我用的接线对照表如下全部按BCM编号MFRC522引脚树莓派引脚BCM编号SDA (SS)Pin 24GPIO8 (CE0)SCKPin 23GPIO11MOSIPin 19GPIO10MISOPin 21GPIO9IRQ不接无GNDPin 6GNDRSTPin 22GPIO253.3VPin 1 / Pin 173.3V红色警告MFRC522是3.3V逻辑的模块千万不要接到5V排针上。我见过不止一个人图省事直接把VCC接5V然后模块直接冒烟。树莓派的GPIO对3.3V以上电压非常敏感5V灌进去不只是烧模块的问题连同GPIO口一起烧掉也有可能。接好线之后进入系统执行sudo raspi-config在Interface Options里把SPI接口打开重启。然后执行ls /dev/spidev*如果能看见spidev0.0说明SPI已经正常启用。这一步漏了的话后面所有读卡代码都会报错而且是那种很诡异的“设备找不到”错误。2.3 卡带外壳与插槽设计卡带壳有两条路可选。一是直接拆原装N64卡带壳把里面的电路板掏空贴上RFID标签。这种方案外观最有味道前提是你手头正好有损坏的、不值得修的原装卡带。我收了几张洋垃圾价格不高但成色参差不齐用起来心里有点舍不得。二是在Thingiverse或Printables上找N64卡带外形的STL模型用PLA或PETG打印。打印参数不用太讲究层高0.2mm填充15%就够。重点在于卡带内部要留出放RFID标签的位置——圆形NTAG213或NTAG216标签直径25mm左右用双面胶或者热熔胶固定在壳内侧。标签粘的位置很关键后面调试会发现这决定了识别成功率。主机端的插槽我同样是用3D打印做的设计成一个带导向槽的结构卡带可以从正面水平推入推到底后有轻微的阻挡感表示到位。读卡模块的天线就固定在槽底部注意天线的中心位置要和卡带推到底后的标签位置重合。这个配合关系是整个项目第一个真正需要调试的地方我的经验是先把读卡器和卡带都固定好一点一点调整深度找到每次都能稳定读到UID的位置然后再去画最终的外壳千万不要上来就按想象画一个卡槽。2.4 卡带标签与映射标识外观和识别信息要对应起来这是很容易被忽视的细节。我给每张卡带都设计了一个贴纸正面是游戏名和图案背面标注一个自己编的编号。每张卡对应一个RFID标签UID我会把UID抄在卡带背面的内侧这样之后改映射表的时候不用重新拿读卡器扫一遍就能知道是哪张卡。如果做多张卡建议用颜色区分类别比如箱脊贴不同颜色的贴纸或者用不同颜色的打印耗材。虽然插卡识别靠的是UID不靠颜色但人的视觉直觉会帮你快速找到想玩的那张卡。3. 软件联动与游戏切换3.1 环境依赖与SPI接口开启软件部分从依赖开始。RetroPie官方镜像自带Python 3但缺少GPIO和SPI相关的库需要手动安装。sudo apt update sudo apt install -y python3-pip python3-dev sudo pip3 install RPi.GPIO spidev mfrc522这里装的mfrc522库是spidev GPIO封装好的轮询式读卡库简单场景足够用。也可以选择pi-rc522库区别不大挑一个顺着用就行。装完之后写一个三行的小脚本先验证能不能读到卡from mfrc522 import SimpleMFRC522 reader SimpleMFRC522() id, text reader.read() print(fUID: {id})把卡带贴上读卡器执行这个脚本如果输出了数字UID说明整个硬件链路是通的可以继续往下做。如果卡住不动或者报错先回去检查SPI和接线不要急着写业务逻辑。3.2 编写卡带识别与映射脚本识别逻辑的核心是一个轮询循环。我用games.json来维护UID和游戏ROM的映射关系这样以后新增卡带时不用改Python代码只改JSON文件就行。{ abc123def456: { name: The Legend of Zelda: Ocarina of Time, rom: /home/pi/RetroPie/roms/n64/zelda_oot.z64 }, 66f0a1b2c3d4: { name: Super Mario 64, rom: /home/pi/RetroPie/roms/n64/super_mario_64.z64 } }主脚本的逻辑分三块读UID、查映射、控制模拟器。为了不让轮询在无卡时刷屏报错必须在异常处理里把“无卡”作为正常状态。下面是核心部分的参考代码#!/usr/bin/env python3 import subprocess import json import time import RPi.GPIO as GPIO from mfrc522 import SimpleMFRC522 GAME_MAP_PATH /home/pi/retropie-cart/games.json CURRENT_GAME None def load_game_map(): with open(GAME_MAP_PATH, r, encodingutf-8) as f: return json.load(f) def stop_emulator(): subprocess.call([pkill, -f, retroarch]) subprocess.call([pkill, -f, mupen64plus]) time.sleep(3) def launch_game(rom_path): cmd f/opt/retropie/supplementary/runcommand/runcommand.sh 0 _SYS_ n64 {rom_path} subprocess.Popen(cmd, shellTrue) def main(): global CURRENT_GAME reader SimpleMFRC522() game_map load_game_map() last_uid None while True: try: uid, _ reader.read() uid_str str(uid).strip() if uid_str ! last_uid: last_uid uid_str game game_map.get(uid_str) if game: print(finserted: {game[name]}) stop_emulator() launch_game(game[rom]) CURRENT_GAME game[name] else: print(funknown tag: {uid_str}) except Exception: if last_uid is not None: print(cartridge removed, stopping game) stop_emulator() CURRENT_GAME None last_uid None time.sleep(0.3) if __name__ __main__: try: main() except KeyboardInterrupt: GPIO.cleanup()这段代码有几处值得说明。last_uid的作用是避免同一张卡重复触发启动只有UID发生变化或者从有变无时才执行动作。stop_emulator里的等待时间是必须的因为模拟器进程被kill之后音频驱动和GPU资源不会立刻释放立刻启动下一个游戏可能导致画面黑屏。等待2到3秒是最稳的。3.3 与RetroPie联动启动、退出、切换RetroPie之所以比直接装个模拟器更方便在于它有一套叫runcommand的启动机制。这个脚本负责处理核心选择、视频模式切换、性能配置、日志记录等问题。在命令行里手动启动N64游戏的完整写法是/opt/retropie/supplementary/runcommand/runcommand.sh 0 _SYS_ n64 /home/pi/RetroPie/roms/n64/game.z64参数0表示前台运行并等待游戏退出。这在我们这个场景里恰恰不合适因为Python脚本需要持续轮询读卡器不能阻塞在游戏进程上。所以代码里用的是subprocess.Popen非阻塞方式让runcommand在后台拉起模拟器Python脚本继续跑自己的循环。退出模拟器这块要注意区分两种情况。如果你用的是mupen64plus独立版本进程名是mupen64plus如果用lr-mupen64plus-next核心实际运行的进程是retroarch。所以pkill的时候两个名字都要覆盖到。这样设计的代价是如果以后想在同一台机器上同时玩其他系统的游戏比如SFC、MD拔卡时会把所有RetroArch实例一起杀掉。但我的定位是一台专用N64机器这个代价可以接受。如果你要做多机种一体机最好把当前系统的标识记下来再用pgrep -f精确匹配。拔卡杀进程之后模拟器会正常退出所有画面回到待机状态。这时候如果插一张新卡脚本在下一轮循环里就会检测到新UID再次走启动流程。这就是“可插拔”体验的完整闭环。3.4 开机自启与状态显示为了让这台机器像游戏机而不是电脑开机要自动进入待机识别状态。推荐用systemd来管理Python脚本。[Unit] DescriptionRetroPie Cartridge Daemon Afternetwork.target [Service] ExecStart/usr/bin/python3 /home/pi/retropie-cart/cart_daemon.py Restartalways Userpi [Install] WantedBymulti-user.target把文件存到/etc/systemd/system/cartridge.service然后执行sudo systemctl enable cartridge开机就会自动跑起来。这里要注意Userpi不能漏因为RetroPie的ROM目录和配置都归pi用户用root跑会踩到权限问题。状态显示是提升体验很有效的一步。我加了一块0.96寸的I2C OLED屏接I2C接口SDA→GPIO2SCL→GPIO3显示当前状态“INSERT CART”待机或者显示当前游戏名。为什么不建议用SPI屏因为SPI总线已经被MFRC522占用了再挂一块SPI屏会冲突。I2C OLED和SPI的读卡器互不干扰这是最优组合。4. 调试排障与性能优化4.1 常见问题速查表整个项目做完我总结了一份高频问题表几乎每个做类似项目的人都可能遇到现象原因解决办法读卡脚本卡死不返回SPI未开启或接线错误检查raspi-config已启用SPI确认所有引脚按表格接好读到的UID不稳定标签位置偏离天线中心调整卡带内标签位置反复测试找到稳定触发点插卡后游戏没启动games.json的UID或ROM路径错误先用测试脚本读取UID确认和JSON里一致检查ROM是否存在、权限是否正确游戏能启动但拔卡不退出Python脚本被游戏进程阻塞检查是否错误使用了阻塞方式调用runcommand应使用Popenkill之后启动新游戏黑屏模拟器进程释放不充分在kill和launch之间增加等待或者把等待时间延长到3秒以上树莓派高温降频散热不足加风扇散热壳避免长时间高负载运行4.2 GPIO冲突排查GPIO冲突是RetroPie环境里最容易忽略的雷区。RetroPie本身是完整的操作系统除了游戏前端后台还有蓝牙、Wi-Fi、音频等一堆服务。MFRC522用的SPI0引脚GPIO8/9/10/11和RST引脚GPIO25在默认设置下没有冲突但如果你之前装了LED驱动、模拟按键模块或者GPIO风扇监控就很可能撞车。排查思路很简单先用gpio readall看看引脚当前分配情况再逐个停掉可疑服务测试。另外如果你接了I2C OLEDSDA/SCL默认是GPIO2和GPIO3这个和MFRC522的SPI引脚不冲突但和HAT板上的EEPROM地址可能会冲突。如果I2C设备扫不到优先检查是否有地址重叠和总线上拉的干扰。4.3 识别稳定性优化RFID方案虽然接触无磨损但识别稳定性是刚上手时最劝退人的环节。我遇到过读卡时而成功、时而超时的情况排查下来原因主要有三个。距离和位置。MFRC522的有效读卡距离一般在2到4厘米但有效范围实际上是一个不规则的空间区域标签中心稍微偏离几毫米就可能读不到。解决方式是固定好卡槽后用一张卡反复插拔几十次找到成功率最高的标签位置然后把标签位置和卡槽深度固化下来。线材长度和干扰。SPI信号在长线上衰减严重尤其是杜邦线超过20厘米后读卡错误率会明显上升。模块最好就近放在卡槽附近线材尽量短。如果无法避免长线可以试着降低SPI时钟频率但库的默认频率通常已经比较保守优先还是缩短物理距离。轮询节奏。读卡循环里每轮间隔0.3秒比较合适。太快会持续占用GPIO资源太慢会让人觉得插卡没反应。我测试下来0.2到0.4秒之间是甜点区间。4.4 N64模拟性能参数建议卡带识别的体验做好了还得让游戏跑得流畅才算完整。在Pi 4B上lr-mupen64plus-next核心配合GLideN64插件RetroPie默认配置已经能跑不少重量级游戏。如果遇到帧数不足先别急着超频优先做三件事第一在RetroArch的video设置里把分辨率降到720p第二关闭垂直同步之外的额外特效第三确认TF卡是A2等级的高性能卡ROM加载速度和IO延迟会影响游戏内卡顿。如果这些还不够可以给Pi 4做适度超频。在/boot/config.txt里加上over_voltage6 arm_freq2000 gpu_freq750加超频配置之前务必确认散热方案到位否则高负载下会触发降频保护反而更卡。我的建议是先做好风扇散热再考虑超频。折腾完这个项目我个人的体会是RFID和树莓派的组合远没有名字看起来那么复杂真正决定成败的反而是物理上那个不起眼的细节——卡带插入深度和天线位置的匹配。如果你只打算做到插卡识别这一步建议别急着设计最终外壳先拿一块亚克力板把读卡器固定好手拿卡带反复找最佳位置找到稳定触发点之后再画壳子。这一步做好后面的代码和联动都是水到渠成的事。
返回列表