一、infoNES 简介
infoNES 是一款轻量级、开源的 FC(NES)游戏机模拟器,核心目标是在资源受限的平台(如嵌入式设备、早期 PC)上实现 NES 游戏的流畅运行。其设计理念是 “简洁、高效、可移植”,源码结构清晰、无复杂依赖,成为嵌入式开发者学习模拟器原理、移植复古游戏的经典范本。
InfoNES 的整体架构,从功能上看,InfoNES 由 五个核心子系统组成:
1️⃣ CPU:6502 模拟器
软件模拟 MOS 6502 CPU,每条指令严格按照 周期(cycle)执行,使用 K6502_Step() 执行指令,是 InfoNES 中 最耗时的部分之一。
2️⃣ PPU:图形渲染系统
软件模拟 NES 的 PPU(2C02),原生分辨率256X240,以 扫描线(Scanline) 为单位渲染,通过InfoNES_DrawLine()生成一行像素数据。像素最终写入WorkFrame或 DoubleFrame。
3️⃣ APU:音频系统
软件模拟 NES 的 5 路音源:2 × Pulse、1 × Triangle、1 × Noise、1 × DPCM。通过音频输出接口:void InfoNES_SoundOutput(int samples, BYTE *wave1, BYTE *wave2, BYTE *wave3, BYTE *wave4, BYTE *wave5)输出PCM格式的音频数据。
4️⃣ Mapper:ROM 映射器(最容易出问题)
NES 游戏使用 Mapper 进行:PRG-ROM 切换、CHR-ROM 切换、IRQ 触发。不支持的 Mapper 会导致花屏 / 错图 / 死机。👉 这是“有的游戏正常,有的花屏”的根本原因。
5️⃣ 平台抽象层(移植关键)
InfoNES 把所有“与平台有关的东西”都留给你实现:
| 功能 | 需要你实现的接口 |
|---|---|
| 显示 | InfoNES_LoadFrame() |
| 扫描线输出 | InfoNES_LoadLine() |
| 输入 | InfoNES_PadState() |
| 声音 | InfoNES_SoundOutput() |
| 时间同步 | InfoNES_Wait() |
总结来讲, InfoNES 是一个轻量级、纯 C 实现的 NES 模拟器内核,适合移植到 STM32 等 MCU 平台。通过自行实现显示、输入、声音和时间同步接口,可以在资源受限的嵌入式系统上完整运行 NES 游戏。
二、移植前准备
1.源码下载
github:Page not found · GitHub
https://github.com/infoNES/infoNEScsdn:
【免费】 infoNES原生源码(方便移植到嵌入式设备上面)资源-CSDN下载
https://download.csdn.net/download/qq_34885669/92459127
2.添加源码到工程
(1)添加好头文件路径

(2)添加核心源码文件到工程
虽然infoNES的源码文件非常多,设置里面还包含很多的.cpp文件,但是不用担心,我们其实只要关注上面介绍的infoNES的 5大核心模块的源码文件就行,如图:


3.显示驱动(必须)
infoNES对显示层是完全抽象的,核心是这几类函数:
(1)InfoNES_LoadLine() 行模式显示时必须
只在这些情况启用:
#define WORKFRAME_NONE //使用行刷新
(2)InfoNES_LoadFrame() 帧模式显示时必须
只在这些情况启用:
#define WORKFRAME_SINGLE //使用单帧单缓冲刷新
#define WORKFRAME_DOUBLE //使用单帧双缓冲刷新(推荐)
infoNES对显示屏幕要求:
| 项目 | 要求 |
|---|---|
| 分辨率 | 至少支持 256×240 |
| 像素单位 | 逐像素写入(16bit / 24bit / 32bit 均可) |
| 刷新方式 | 行刷新或整帧刷新均可 |
| 帧缓冲 | 至少 1 个可写 Frame Buffer |
| 时序 | 不要求固定刷新率(由 CPU 同步) |
总结来讲,如果只是为了先实现移植使用infoNES,不考虑性能和流畅体验,其实你的屏幕驱动只要能实现在屏幕上画点就可以了!
4.手柄驱动(可选)
STM32上使用HAL库驱动9针FC手柄实现游戏自由!-CSDN博客
https://blog.csdn.net/qq_34885669/article/details/155890282
没有手柄也可以使用普通的按键来实现!只要把你的按键和游戏手柄对应的键值对应上就可以了。
5.声卡驱动(可选)
例如,我使用的是MAX98357功放芯片:STM32上使用HAL库完美实现驱动MAX98357声卡模块(I2S+DMA+音频环形缓冲区)_stm32驱动max98357-CSDN博客
https://blog.csdn.net/qq_34885669/article/details/154488774?spm=1001.2014.3001.5502
三、移植源码
讲解到infoNES的源码移植,其实就是对前面提到的infoNES_system.h的平台抽象层的移植适配:

在infoNES_system.h里面,我们可以看到infoNES给我们写好的平台抽象层的API接口,如图:

所谓的移植infoNES其实就是在根据你现有的硬件情况,其实现上面这些平台抽象层接口!
1.显示驱动移植(必须)
在移植你的屏幕驱动前,我们还得对infoNES进行一些必要的配置和裁剪:例如配置显示缓存模式、声卡裁剪等等。找到InfoNES_Types.h 头文件,在里面添加下面的宏定义:
#define WORKFRAME_DOUBLE 2 //单帧双缓存模式
#define WORKFRAME_SINGLE 1 //单帧单缓存模式
#define WORKFRAME_NONE 0 //单行无缓存模式
#define WORKFRAME_DEFINE WORKFRAME_NONE //配置显示缓存模式
#define APU_NONE //关闭声卡,可以节省大量内存消耗,内存小的建议先不移植声卡
#define TRANSPARENT_COLOR 0x0000 //透明色标记,屏幕不支持透明色的话就设置0
#define RAM_LACK //配置infoNES为低性能模式,以达到更好的兼容性和更少的内存消耗
配置完成后,我们就可以开始移植屏幕驱动了: 打开infoNES_system.c文件,我们可以看到很多平台抽象层接口,为了快速的完成infoNES的移植,我们最低要求只要实现void InfoNES_LoadLine()实现屏幕一行画面的显示:

备注:对于单帧单缓存和单帧双缓存模式显示移植,这里不介绍,因为比较复杂且消耗内存,容易移植失败,初期移植建议先用行刷新
2.手柄驱动移植(可选)
手柄键值在infoNES.h已经被定义好了,如果你没有手柄,可以使用普通的按键模拟,只要按照下面的键值对应好就也一样效果:

有手柄的可以按照下面的截图来进行手柄的移植:

3.声卡驱动移植(可选)

4.加载游戏文件
在使用infoNES加载游戏并运行游戏之前,我们必须先解决游戏ROM文件怎么存放的问题,通常解决办法有2种:
方法一:将nes游戏ROM文件转为大数组,使用const修饰以数组形式存放到flash

nes rom二进制文件下载:
【免费】nes游戏ROM二进制文件资源-CSDN下载
https://download.csdn.net/download/qq_34885669/92460079
方法二:将nes游戏文件存放到SD卡,使用文件系统去打开和读取nes rom。
这里涉及文件系统移植等,知识量还是比较多的,不是本博文的重点。
初次移植建议采用方法一!然后使用InfoNES_ReadRom抽象层接口加载游戏:
/* Read ROM image file */
int InfoNES_ReadRom( const char *pszFileName )
{
/* Read ROM Header */
BYTE * rom = (BYTE*)nes_rom;
memcpy( &NesHeader, rom, sizeof(NesHeader));
if ( memcmp( NesHeader.byID, "NES\x1a", 4 ) != 0 )
{
/* not .nes file */
return -1;
}
rom += sizeof(NesHeader);
/* Clear SRAM */
memset( SRAM, 0, SRAM_SIZE );
/* If trainer presents Read Triner at 0x7000-0x71ff */
if ( NesHeader.byInfo1 & 4 )
{
//memcpy( &SRAM[ 0x1000 ], rom, 512);
rom += 512;
}
/* Allocate Memory for ROM Image */
ROM = rom;
rom += NesHeader.byRomSize * 0x4000;
if ( NesHeader.byVRomSize > 0 )
{
/* Allocate Memory for VROM Image */
VROM = (BYTE*)rom;
rom += NesHeader.byVRomSize * 0x2000;
}
/* Successful */
return 0;
}
四、运行infoNES游戏模拟器
简单来说,运行模拟器就是首先模拟器先加载游戏,加载游戏成功后就调用InfoNES_Main();接下来你就可以愉快的玩游戏了!
int main(void)
{
//屏幕初始化
//手柄初始化
//.....
while (1)
{
if(InfoNES_Load(NULL)==0)
{
InfoNES_Main();
}
}
}
五、移植时常见问题和解决办法
1.黑屏无显示
极大可能是堆栈不足导致,请检查和修改堆栈大小,或者更换RAM更大的MCU
2.颜色显示异常
(1)红色变蓝绿色,蓝色变红色,绿色变紫褐色

这样的情况经常出现在16位色的RGB565的屏幕上,本质上就是由于高8位和低8位数据被交换了。
解决办法有2种:
方法一:直接交换像素数据的高低8位

方法二:交换infoNES调色板的高低8位(推荐)

使用修改调色板方法,还可以节省每次交换高低位的时间,提高刷新速度(实测提高5fps左右)
(2)透明色失效或异常
通常是前文提到 NES 的 TRANSPARENT_COLOR设置不对导致,建议先设置为0x0000测试。
3.编译报错内存不足
报错现象:Error: L6406E: No space in execution regions with .ANY selector matching

核心解决方法就是:
(1)减少infoNES的ram占用
比如使用行刷新,使用RAM_LACK等等。
想更彻底的减少ram占用,就可以直接修改RAM大小设置:

(2)换ram更大的MCU
(3)使用外部的sdram
这个主要还是要用到malloc去申请堆空间给nes使用,还有就是把infoNES里面很多定义的全局大数组一个个都使用malloc定义到外部的sdram。
例如:


转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/qq_34885669/article/details/155891400





