·3 分钟阅读

ESP32 上跑 NES 模拟器:移植与踩坑记录

记录在 ESP32-S3 掌机上移植 NES 模拟器的全过程:nofrendo 核心的库化改造、WiFi 传 ROM、屏幕驱动、以及崩溃排查。

ESP32嵌入式模拟器

ESP32 上跑 NES 模拟器:移植与踩坑记录

在资源受限的 ESP32-S3 上跑 NES 模拟器,是把 nofrendo 模拟器核心搬上 Arduino 框架的完整过程。这篇记录整个移植链路和踩过的坑。

系统架构

flowchart LR
    WIFI[WiFi AP + WebServer] -->|上传 ROM| SPIFFS[SPIFFS 存储]
    SPIFFS --> NOFRENDO[nofrendo 模拟核心]
    NOFRENDO --> DISP[GC9A01 屏显]
    BT[BLE 手柄] --> INPUT[按键输入]
    INPUT --> NOFRENDO

核心改造:nofrendo 库化

nofrendo 是 ESP-IDF 官方的 NES 模拟器参考实现,但它是按 ESP-IDF 组件组织的。要在 Arduino 框架里用,第一步是把它的全部 C 实现(65 个文件)搬到 lib/nofrendo/ 下,作为 Arduino 库编译。

这一步解决的是"两个框架的组织差异"——nofrendo 的 .c 文件之间用相对 include,搬进 lib 后编译顺序和头文件路径都要重新理顺。

WiFi 传 ROM:摆脱反复烧录

每次改 ROM 都重新烧录太痛苦,所以加了一个 WebServer:

flowchart LR
    PHONE[手机/电脑浏览器] -->|连接热点| AP[ESP32 WiFi AP]
    AP -->|上传 .nes 文件| SPIFFS[SPIFFS]
    SPIFFS -->|/go 路由| RUN[启动模拟]
  • 板子开机开热点,浏览器连上去上传 ROM 到 SPIFFS
  • /go 路由直接启动 nofrendo 跑 ROM
  • 早期用 -nosound 无声版,先把画面跑通

屏幕驱动:USE_HSPI_PORT 是命门

TFT_eSPI 驱动 GC9A01 屏,最关键的配置是端口选择:

#define GC9A01_DRIVER
#define USE_HSPI_PORT        // 必须!否则崩溃 StoreProhibited
#define TFT_WIDTH  128
#define TFT_HEIGHT 128
#define SPI_FREQUENCY 40000000

不用 USE_HSPI_PORT 会直接 StoreProhibited 崩溃——这是花了不少时间才定位到的。

屏幕的引脚映射(T-QT Pro):

功能 GPIO
MOSI GPIO2
SCLK GPIO3
CS GPIO5
DC GPIO6
RST GPIO1
背光 BL GPIO10

按键与输入

T-QT Pro 只有两个实体按键,映射成 NES 的两个键:

按键 GPIO NES 键
左键 SELECT GPIO0 A(确认)
右键 START GPIO47 START

BLE 手柄后来也接上了(ESP32-BLE-Gamepad 库),但早期调试时是注释状态,先用两个实体键跑通流程。

踩过的坑

坑 现象 解决
SPI 端口选错 一进显示初始化就 StoreProhibited 崩溃 强制 USE_HSPI_PORT
SPIFFS 数据损坏 上传 ROM 后崩溃,反复起不来 全擦 SPIFFS 后恢复正常
USB 挂死 崩溃后串口连不上 重新上电 / 进下载模式
ROM 太大 大 ROM 放不进 SPIFFS 先用小的(如魂斗罗 168KB)跑通

最终固件编出来约 853KB,能在 128×128 的屏上稳定跑起来。这个项目最深的体会是:在受限硬件上,"跑通"本身就是最大的坎,每个功能都要在性能、内存、体验之间反复权衡。