1. 项目缘起:当情怀遇见技术
最近在整理老物件,翻出了一台尘封已久的经典游戏机。看着那熟悉的卡带和略显笨拙的手柄,一股怀旧的情绪涌上心头。相信很多和我一样的80、90后,童年里都有一台这样的“神机”,它可能是红白机,也可能是世嘉MD,或是小霸王学习机。在那个物质不算丰富的年代,这些机器承载了我们无数的快乐时光。然而,时过境迁,这些老机器要么早已损坏,要么被束之高阁,想重温那份纯粹的快乐变得不那么容易。
于是,一个念头在我脑中成型:为什么不自己动手,复刻一台呢?这不仅仅是为了玩游戏,更是一种情怀的寄托,一次对经典的致敬,以及一次绝佳的技术实践。今天,我想和大家分享的,就是如何从零开始,复刻一台属于你自己的经典版游戏机。这个过程,融合了硬件选型、软件模拟、外壳制作与系统整合,是一次充满乐趣的“软硬兼施”之旅。无论你是电子爱好者、复古游戏迷,还是想找个有成就感的DIY项目,相信这篇详尽的记录都能给你带来启发。
2. 核心思路与方案选型:不走寻常路的复刻之路
复刻经典游戏机,市面上有成熟的方案,比如直接购买树莓派Zero等开发板,刷入现成的复古游戏系统镜像。但这太“快餐”了,失去了DIY的乐趣和学习的意义。我的目标是:在保证可玩性和怀旧感的前提下,尽可能深入地理解其工作原理,并亲手打造每一个环节。
2.1 硬件核心的抉择:性能、功耗与成本的平衡
硬件是躯干。我们需要一个能流畅运行模拟器、功耗低、接口丰富且社区支持好的核心板。
- 方案A:树莓派系列。这是最流行的选择,社区资源极其丰富,有大量现成的复古游戏系统(如RetroPie, Recalbox)。但树莓派4B或Zero 2 W对于FC、SFC、MD等16位及以下主机性能过剩,且价格不菲。更重要的是,它“太简单了”,更像是在组装一台微型电脑,而非“创造”游戏机。
- 方案B:ESP32系列。强大的无线功能,但原生性能主要面向物联网,运行高级模拟器(如Snes9x)较为吃力,需要深度优化,对新手不友好。
- 方案C:全志F1C100s/F1C200s。这是一颗国产的、性价比极高的ARM9芯片。它内置了64MB DDR内存,主频高达900MHz,足以流畅模拟FC、SGB、GBA、甚至部分PS1游戏。最关键的是,它没有内置Flash,需要外接SPI Nor Flash或TF卡启动,这让我们可以从零开始构建Bootloader和系统,学习嵌入式开发的完整流程。其低廉的价格(核心板约20-30元)也让我们在折腾时毫无心理压力。
我的选择:全志F1C100s。理由很明确:学习价值最大化。我们需要自己移植U-Boot、编译Linux内核、配置设备树、构建根文件系统、移植模拟器前端和后端。这个过程能让你彻底明白一台游戏机从通电到出现游戏列表,中间到底发生了什么。当然,这意味着一开始会面临更多挑战。
2.2 软件体系的构建:从零开始的“灵魂”注入
确定了硬件,软件架构就需要精心设计。我们的目标是一个精简、高效、专为游戏优化的系统。
- Bootloader (U-Boot):负责初始化最基础的硬件(如DRAM、时钟),并从存储设备加载内核。我们需要为F1C100s定制编译U-Boot,并配置正确的启动参数。
- Linux内核:系统的核心。需要根据F1C100s的芯片手册,配置并编译一个精简的内核,启用必要的驱动:Framebuffer显示驱动、USB Host驱动(用于接手柄)、SD/MMC驱动、声卡驱动(通过I2S接口)。
- 根文件系统:采用Buildroot构建。Buildroot能帮助我们自动化地生成一个极其精简的根文件系统,只包含运行模拟器所必需的工具和库(如SDL2, libpng, zlib)。我们将采用
busybox提供基础命令,系统启动后直接运行一个前端程序,而不是进入标准的Linux Shell。 - 模拟器与前端:
- 后端:选择
RetroArch。它是一个统一的、模块化的模拟器前端框架,其核心是LibretroAPI。各种游戏机核心(如FCEUmmfor FC,PicoDrivefor MD,gpspfor GBA)作为“核心”被加载。它的优势在于配置统一,手柄映射一致,且社区活跃。 - 前端:为了极致的简洁和复古感,我放弃了
EmulationStation这类图形化前端,而是选择了一个非常轻量级的命令行前端:lr-menu。它直接在Framebuffer上绘制一个简单的游戏列表,通过手柄选择启动。这最大程度地减少了系统开销,让资源全部留给游戏本身。
- 后端:选择
2.3 外壳与交互:情怀的实体化
硬件和软件是内在,外壳和交互则是外在的灵魂。
- 外壳设计:使用3D建模软件(如Fusion 360)进行设计。灵感来源于经典的任天堂红白机(NES),但做了等比例缩小以适应小尺寸屏幕和主板。外壳需要预留:屏幕开口、按键孔、USB接口孔、电源开关孔、散热孔、扬声器孔。设计时务必考虑组装顺序和内部结构的固定方式(如立柱)。
- 屏幕选择:一块3.5英寸的IPS液晶屏,分辨率480x320,通过RGB接口直接驱动。IPS屏保证了良好的可视角度和色彩表现。
- 输入设备:使用经典的SNES(超任)风格USB手柄。其按键布局(ABXY,LR)兼容绝大多数复古游戏。我们在系统中通过
udev规则和RetroArch配置,将USB手柄映射为统一的输入设备。 - 音频输出:F1C100s通过I2S接口连接一颗简单的音频编解码芯片(如SGTL5000)或直接使用PCM5102A这类I2S DAC,驱动一个小型扬声器。
3. 核心环节实操详解:从电路板到游戏画面
这一部分,我将拆解最关键的几个实操步骤,其中包含大量容易踩坑的细节。
3.1 硬件焊接与基础电路搭建
首先,你需要一块F1C100s的核心板(通常已焊接好芯片和内存)或自己焊接。更简单的方式是购买现成的“荔枝派 Nano”这类开发板。
关键步骤与避坑点:
- 电源管理:F1C100s核心电压为1.2V,IO电压为3.3V。必须使用稳定的LDO(低压差线性稳压器)供电,如RT9193。特别注意:上电顺序有要求,应先上1.2V核心电压,再上3.3V IO电压,否则可能损坏芯片。使用带有使能引脚(EN)的LDO,并通过一个简单的RC延时电路来控制上电顺序,是稳妥的做法。
- SPI Flash焊接:这是存储Bootloader和内核的地方。型号推荐W25Q128(16MB)。焊接时务必注意方向(Pin1对齐)。焊接完成后,用万用表蜂鸣档检查有无短路。常见坑点:Flash芯片质量参差不齐,劣质芯片可能导致系统无法启动或运行不稳定。建议从正规渠道购买。
- RGB屏幕连接:屏幕通常通过FPC排线连接。你需要一个RGB接口的转接板,将屏幕的引脚(如数据线、时钟、行场同步、使能)连接到F1C100s对应的GPIO上。这里最大的坑在于设备树(DTS)的配置。你需要根据屏幕规格书,在设备树中正确配置
lcd节点,包括时序参数(如hbp,hfp,vbp,vfp)、像素时钟、屏幕尺寸等。一个参数错误就会导致花屏、闪屏或无显示。提示:可以先在开发板上用杜邦线连接测试,确认屏幕驱动和参数正确后,再设计PCB或进行永久性焊接。
3.2 软件环境搭建与系统编译
这是整个项目的软件核心,步骤繁多但逻辑清晰。
1. 交叉编译工具链我们是在x86电脑上编译ARM芯片的程序,所以需要交叉编译工具链。对于F1C100s(ARM9,arm926ej-s架构),可以使用gcc-linaro-7.5.0-arm-linux-gnueabi。
# 下载并设置工具链 wget https://releases.linaro.org/components/toolchain/binaries/7.5-2019.12/arm-linux-gnueabi/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabi.tar.xz tar -xvf gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabi.tar.xz export PATH=$PATH:/你的路径/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabi/bin export CROSS_COMPILE=arm-linux-gnueabi-2. 编译U-Boot获取适配F1C100s的U-Boot源码(通常来自GitHub上的衍生版本,如Lichee-Pi的仓库)。
git clone https://github.com/Lichee-Pi/u-boot.git -b nano-v2018.01 cd u-boot # 配置为荔枝派Nano make ARCH=arm licheepi_nano_defconfig # 编译 make ARCH=arm -j$(nproc)编译完成后,会生成u-boot-sunxi-with-spl.bin文件。这就是我们需要烧录到SPI Flash最前端的Bootloader。
3. 编译Linux内核同样获取适配内核。
git clone https://github.com/Lichee-Pi/linux.git -b nano-5.2-tf cd linux # 使用默认配置 make ARCH=arm licheepi_nano_defconfig # 进入图形化配置界面,确保以下驱动被启用: # Device Drivers -> Graphics support -> Frame buffer Support -> SSD1306/SSD1307/SSD1322/SSD1331/SSD1351/SSD1362/SSD1373 OLED/PLED/... # 如果你的屏幕是OLED # Device Drivers -> Sound card support -> Advanced Linux Sound Architecture -> ALSA for SoC audio support -> Allwinner SoC Audio support # Device Drivers -> Input device support -> Generic input layer (needed for keyboard, mouse, ...) -> Event interface # 保存退出后编译 make ARCH=arm -j$(nproc)编译后得到arch/arm/boot/zImage(内核镜像)和arch/arm/boot/dts/suniv-f1c100s-licheepi-nano.dtb(设备树二进制文件)。
4. 使用Buildroot构建根文件系统Buildroot能极大地简化根文件系统的构建。
git clone https://github.com/buildroot/buildroot.git cd buildroot make menuconfig关键配置:
- Target Architecture->
ARM (little endian) - Target Architecture Variant->
arm926t - Toolchain-> 选择你之前下载的Linaro 7.5外部工具链路径。
- System configuration-> 设置主机名、欢迎语等。
- Target packages-> 这是核心:
- 勾选
Retroarch及其所有需要的依赖(如SDL2, libpng, zlib, freetype)。 - 勾选
lr-menu(可能需要手动添加自定义包)。 - 勾选
busybox(默认已选)。 - 在
Filesystem images中,选择生成cpio或ext2/4格式的镜像。
- 勾选
配置完成后,执行make。这个过程会下载所有源代码并编译,耗时较长。最终在output/images/目录下得到根文件系统镜像(如rootfs.cpio)。
5. 整合与烧录现在我们有三个核心文件:u-boot-sunxi-with-spl.bin,zImage,suniv-f1c100s-licheepi-nano.dtb,以及根文件系统。 我们需要将它们打包成一个最终的固件,并烧录到SPI Flash或TF卡。
一种简单的方法是使用dd命令,按照特定的偏移地址将文件写入TF卡:
# 假设TF卡设备是 /dev/sdX,请务必确认清楚! # 1. 烧录uboot到8KB偏移处 sudo dd if=u-boot-sunxi-with-spl.bin of=/dev/sdX bs=1024 seek=8 # 2. 创建两个分区:第一个FAT32(存放内核和dtb),第二个EXT4(存放根文件系统) sudo fdisk /dev/sdX # 创建分区,略 sudo mkfs.vfat /dev/sdX1 sudo mkfs.ext4 /dev/sdX2 # 3. 挂载第一个分区,复制内核和dtb sudo mount /dev/sdX1 /mnt sudo cp zImage suniv-f1c100s-licheepi-nano.dtb /mnt/ # 4. 挂载第二个分区,解压根文件系统 sudo mount /dev/sdX2 /mnt2 sudo tar -xvf output/images/rootfs.tar -C /mnt2/ # 假设Buildroot生成的是tar包 # 5. 编辑/mnt2/etc/inittab或systemd配置,确保系统启动后自动运行lr-menu或retroarch # 例如,在inittab末尾添加:::sysinit:/usr/bin/lr-menu sudo umount /mnt /mnt2将TF卡插入开发板,上电,如果一切顺利,你将看到屏幕点亮,并进入一个简单的游戏列表界面。
3.3 外壳设计与3D打印
使用Fusion 360进行建模。
- 精确测量:用游标卡尺精确测量你的主板、屏幕、电池、扬声器等所有内部元件的尺寸和固定孔位置。这是设计的基础,差之毫厘,装起来就非常痛苦。
- 分体设计:将外壳分为上盖、下盖和中间可能的面板。设计卡扣或螺丝柱进行固定。重要经验:螺丝柱的内径要略大于你的螺丝直径,并预留热熔胶或螺丝胶的余量。对于受力部位(如手柄接口处),可以设计加强筋。
- 散热与走线:在主板芯片上方和电池附近设计通风孔。内部预留线槽,让排线和电源线可以规整地放置,避免挤压屏幕或干扰按键。
- 按键设计:如果你打算集成实体按键(而不仅是依赖USB手柄),需要设计按键帽和内部的微动开关支架。按键行程和手感需要反复测试调整。
- 导出与打印:将各个部件导出为STL格式。使用Cura等切片软件生成G-code。打印材料推荐PLA+,强度较好。打印时注意支撑的设置,特别是对于内部复杂的结构。
实操心得:
- 公差是关键:3D打印存在收缩率。在设计时,对于需要紧密配合的孔(如螺丝孔),可以预先将孔径设计得比理论值小0.2-0.3mm,打印后再用钻头或丝锥修正。对于滑动配合,则需要预留至少0.5mm的间隙。
- 试装迭代:不要指望一次设计就完美。打印出第一个版本后,立刻进行试装。记录下所有过紧、过松、干涉的地方,回到软件中修改,然后打印第二个测试版。通常需要2-3个迭代才能得到一个合身的外壳。
4. 系统整合与前端配置:打造专属游戏库
硬件组装好,系统能启动,接下来就是让它变成一台真正的游戏机。
4.1 RetroArch与核心配置
系统启动后,通过串口或SSH登录(如果你保留了网络功能),进行配置。
- 安装Libretro核心:将提前交叉编译好的核心文件(如
fceumm_libretro.so,picodrive_libretro.so)放入/usr/lib/libretro/目录。 - 配置RetroArch:首次运行
retroarch命令,会在~/.config/retroarch/目录下生成配置文件retroarch.cfg。- 视频驱动:设置为
fbdev(帧缓冲驱动)。video_driver = "fbdev"。 - 音频驱动:设置为
alsa。audio_driver = "alsa"。 - 输入驱动:设置为
udev。input_driver = "udev"。 - 手柄映射:连接USB手柄,运行
retroarch-joyconfig工具(需单独编译)或手动在配置文件中设置。关键是将手柄的物理按键映射到RetroArch的虚拟按钮(input_player1_a_btn等)。
- 视频驱动:设置为
- 游戏列表生成:将游戏ROM文件(.nes, .smc, .bin等)放入某个目录,如
/games/。使用RetroArch自带的扫描功能,或者使用lr-menu这样的工具,它通常需要一个简单的文本文件来定义游戏列表,格式如:
每一行包含游戏名、ROM路径和核心名。Super Mario Bros. (NES) | /games/nes/Super Mario Bros.nes | fceumm Sonic The Hedgehog (MD) | /games/md/Sonic.bin | picodrive
4.2 轻量级前端lr-menu的集成与优化
lr-menu是一个用C语言编写的简单前端,它直接读取一个游戏列表文件,并在屏幕上绘制出来。我们需要修改其源代码以适应我们的屏幕分辨率(480x320),并交叉编译。
- 获取与修改源码:从GitHub获取
lr-menu源码。主要修改main.c中的SCREEN_WIDTH和SCREEN_HEIGHT宏定义。你还可以修改字体、颜色和布局。 - 交叉编译:
将生成的export CC=arm-linux-gnueabi-gcc makelr-menu可执行文件放入根文件系统的/usr/bin/。 - 设置自启动:修改根文件系统中的初始化脚本(如
/etc/inittab或/etc/rc.local),在最后添加一行:/usr/bin/lr-menu &。这样系统启动后就会自动运行前端。
5. 常见问题与深度排查实录
在复刻过程中,你几乎一定会遇到下面这些问题。这里记录了我的排查过程和解决方案。
5.1 上电无任何反应,串口无输出
- 可能原因1:电源问题。
- 排查:用万用表测量核心板上的1.2V和3.3V电压是否正常、稳定。检查电源线是否接反。
- 解决:检查LDO电路,确认上电顺序。更换LDO或电源。
- 可能原因2:Bootloader未正确烧录或Flash损坏。
- 排查:使用编程器(如CH341A)重新读取Flash内容,确认前1KB的数据是否与
u-boot-with-spl.bin文件头部一致。 - 解决:重新烧录Bootloader。如果多次失败,尝试更换SPI Flash芯片。
- 排查:使用编程器(如CH341A)重新读取Flash内容,确认前1KB的数据是否与
5.2 屏幕点亮但花屏、闪屏或显示错位
- 可能原因:设备树中LCD参数配置错误。
- 排查:这是最常见的问题。仔细核对屏幕数据手册中的时序图,包括
hbp(行后沿)、hfp(行前沿)、hspw(行同步脉宽)、vbp(场后沿)、vfp(场前沿)、vspw(场同步脉宽)、pixel_clk(像素时钟)等。一个参数错误就会导致信号不同步。 - 解决:修改内核源码中的设备树文件(.dts),重新编译内核和dtb。有时也需要检查屏幕的
DE(数据使能)模式是否配置正确。
- 排查:这是最常见的问题。仔细核对屏幕数据手册中的时序图,包括
5.3 系统启动后,USB手柄无法识别
- 可能原因1:内核未启用USB Host驱动。
- 排查:在内核编译菜单中,确认
Device Drivers -> USB support -> Support for Host-side USB以及对应的USB控制器驱动(如DWC2 USB controller)已启用。 - 解决:重新配置内核,编译并更新。
- 排查:在内核编译菜单中,确认
- 可能原因2:电源带载能力不足。
- 排查:有些USB手柄在初始化时瞬时电流较大。如果使用电池供电或LDO输出电流不足(如只有500mA),可能导致手柄识别失败或系统重启。
- 解决:更换输出电流更大的电源(如1A以上),或在USB端口增加大电容缓冲。
5.4 运行游戏时严重卡顿或声音爆音
- 可能原因1:系统负载过高或散热不良。
- 排查:通过
top命令查看CPU占用率。运行游戏时是否接近100%?触摸主芯片是否烫手? - 解决:优化内核,关闭所有不需要的服务和进程。确保外壳有良好的散热孔。可以考虑为芯片添加小型散热片。
- 排查:通过
- 可能原因2:模拟器核心未针对该平台优化。
- 排查:F1C100s是ARM9,没有硬件浮点单元(FPU)。如果模拟器核心大量使用浮点运算,性能会极差。
- 解决:寻找或编译使用定点数运算的模拟器核心。在
RetroArch设置中,尝试启用Threaded Video(线程化视频)或降低视频分辨率(Video Scale),有时能提升流畅度。
- 可能原因3:音频驱动或配置问题。
- 排查:检查
alsa是否正常工作,aplay -l是否能列出声卡。在RetroArch设置中,尝试降低音频采样率(如从44100Hz降到22050Hz)。 - 解决:确保设备树中I2S和声卡编解码芯片的配置正确。调整
RetroArch的audio_out_rate参数。
- 排查:检查
5.5 自制外壳按键手感生硬或卡键
- 可能原因:按键结构与行程设计不合理。
- 排查:按键帽与微动开关的接触面是否平整?按键帽在键槽内的上下活动空间是否足够(通常需要0.5-1mm的间隙)?复位弹簧(如果有)力度是否合适?
- 解决:这是机械设计问题。修改3D模型,增加按键帽的导向部分长度,确保垂直运动。在微动开关顶部粘贴一小块圆形海绵或橡胶,可以改善手感并降低噪音。对于卡键,仔细打磨键槽内部,确保无毛刺。
这个复刻项目,从一颗芯片开始,到捧在手里能运行《超级马里奥》的完整设备,其意义远超玩几个游戏本身。它是一次完整的嵌入式开发实践,涵盖了硬件设计、底层驱动、系统构建、应用移植和工业设计。过程中遇到的每一个错误,都迫使你去查阅数据手册、分析源码、调试硬件,这种解决问题的过程,是任何现成产品都无法给予的成就感。最终,当你用自己打造的设备,重温那些像素点构成的经典时,那份快乐是加倍的。它不再只是一台游戏机,而是你技术能力与童年情怀凝结而成的一个作品。