# H_NES **Repository Path**: yzzd123/H_NES ## Basic Information - **Project Name**: H_NES - **Description**: 一个NES模拟器,源码纯C - **Primary Language**: C - **License**: GPL-3.0 - **Default Branch**: master - **Homepage**: http://t.csdn.cn/ze6gv - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 10 - **Created**: 2025-01-03 - **Last Updated**: 2025-01-03 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # H_NES ## 介绍 一个NES模拟器,源码纯C语言编写, ## 特性 1.多制式支持(NTSC PAL Dendy) 2.拥有存档,加载存档功能 3.较为深入的模拟精度(PPU每渲染8个像素就执行CPU运算) 4.较高精度的音频输出,由于APU的一些特性,输出的音频样本要达到1.79MHz(NTSC制式下的CPU频率)才能完美模拟输出的音频波形。 >但实际上很难找到那么高采样率的DAC,H_NES渲染音频的采样率被设计为CPU频率的1/4(即使是这样,也只对某些分频(APU配置)下的高频信号有影响),300多KHz。实际输出还需要对些样本等间隔抽样(H_NES有个配置项可以配置) 5.APU模拟的一些改动 >5.1.APU的硬件缺陷修复,实机的APU三角波通道的相位是无法控制的,这会造成使用三角波通道是可能会出现爆音问题(噼里啪啦声),例如《淘金者》的标题界面就有明显的爆音现象,但这在H_NES上不会出现。 5.2.DMC模拟去掉了8Bit的读取缓冲区,使得样本数据读操作提前被进行。(这几乎不会带来其他影响) ## 局限         1.暂时只实现了Mapper0 ~ 4 , Mapper7 , Mapper21 ~ 23 , Mapper25 , Mapper65 其中Mapper7,21,22,25未得到测试 >Mapper0~4已经能运行大多数rom了 ## 移植说明 ### 代码准备 `\H_NES`文件夹为NES源代码,其下的`H_NES.h`包含接口声明和注释, 将其中代码添加到自己的工程中包含`H_NES.h`即可调用H_NES的所有API ### 配置文件 `\H_NES\LL\H_NES_UserConfig.h`中可以放置用户的H_NES配置选项,`H_NES\H_NES\API\H_NES_Config.h`中有默认配置以及配置的说明 ### 调用顺序参考 1.nes=new_H_NES_Console(LL_Function,Malloc,Free) 创建特定与底层接口的NES句柄, 返回NES句柄,LL_Function其中包含一些函数指针,用于输出视频数据和音频数据以及读取手柄数据 2.H_NES_SetTelevisionStandards(nes,Standards) 设置电视标准(不是必须,默认为NTSC) `Standards`为`vH_NES_TelevisionStandards_NTSC PAL Dendy` 3.H_NES_LoadROM(nes,NESROM) 载入ROM (此步可以由H_NES_LoadROM_Scatter()与H_NES_GetROM_Info()配合来代替) 4.创建一个线程,在新创建的线程中调用H_NES_Start(nes),后续的操作仍在原来的线程中进行 5.H_NES_WaitUntilRun(nes) 这被设计为必须的,用于同步该线程与新创建的线程 6.此时可以调用一些API控制NES 例如加载存档,获取存档,复位主机等 7.H_NES_Stop(nes) 第4.完成后,无论是H_NES意外退出后还是用户需要主动退出,都需要调用这个 8.H_NES_UnloadROM(nes) `第4.未执行` 或者 `第7.执行完毕` 后,可调用这个,以释放为运行rom分配的空间 9.delete_H_NES_Console(nes) 释放nes占用的资源 >API调用尽可能只在同一个线程中进行,除了**H_NES_Start(nes)需要单独创建一个线程来调用** >**Example\STM32H750VB_NES\Code\nes.c为底层接口实现(这个文件比较混乱),可以作为移植的例子** H_NES将底层都提取了出来,只要保证 *new_H_NES_Console(LL_Function,Malloc,Free)* 的参数是合理的,剩下的操作都是调用API了(除了启动NES需要新建一个线程以外) ### 输入输出 #### 视频输出 H_NES输出256x240的画面 H_NES通过 *LL_Function->Console.RendersLine(v,ColorIndexArray,line)* 输出每一扫描行的颜色索引(输出的并不是实际的RGB值, 需要调色板转换为实际的颜色)每次输出256像素。 输入参数 *line* 在0~239之间,当 *line* 为239时代表该帧的最后一个扫描行输出完毕。 (每次只输出一个扫描行允许显示数据能更快的输出到屏幕,能够省掉高达一个帧的缓存空间) #### 音频输出 H_NES通过 *LL_Function->Console.AudioOutput(v,Samples,Length)* 来输出音频样本,音频样本的类型与输出长度等参数由配置选项决定 输出采样率由配置`vH_NES_APU_OutputDiv`和`vH_NES_APU_OutputChannelRaw`共同决定 CPU6502主频为 **NTSC:236250000.0/11/12**Hz **PAL:26601712.5/16**Hz **Dendy:236250000/11/12**Hz *输出采样率* 为 *CPU6502主频/4/vH_NES_APU_OutputDiv* ,如果 *vH_NES_APU_OutputChannelRaw* 生效,则 *vH_NES_APU_OutputDiv* 无效(强制为1) 输出*样本类型*可配置为16Bit(已经合成的)有符号单声道输出,也可以输出32Bit(各个通道数据分布在这32Bit中,详细格式见**API\H_NES_Config.h**)的各个通道的分量输出 输出*样本长度*由配置选项决定,为一个固定值 更多相关配置见配置文件 #### 手柄输入 H_NES通过 *LL_Function->Console.GetStandardControllerStatus(v)* 获取手柄输入,返回的16Bit的值各位的定义通过宏 *vH_NES_StandardControllerStatus_* xxx 定义 >**其他接口详见H_NES_LL_Function的定义** #### 代码内存位置分配 像STM32H750VBT这种单片机, 代码位置对性能影响较大, 而其内部的ITCM仅有64KB 所以需要考虑代码位置分配问题 性能优先级从高到低 **H_NES_CPU.c** CPU使用指针函数数组来实现,代码的访问顺序很随机,该文件最优先分配到ITCM中 **H_NES_APU.c H_NES_IO.c** 其内部的代码被频繁地调用退出,作为仅次于CPU的优先级 **H_NES_PPU.c H_NES_Console.c** PPU是性能占用最高的,但其的调用退出的频率与帧率一致,频率较低,很适合有Cache的情况,如果ITCM仍有空间,也可置于其中。 **Cartridge\Mapper中的文件** Mapper相关被频繁调用的部分 ## 例子         提供了一个在STM32H750VBT上运行的实例,得益于其优秀的性能,H_NES得以全速运行(60FPS,开启音频渲染,CPU占用率基本在80%以下,如果游戏ROM在SRAM中的话能更快) 1.通过串口输出运行信息和传入手柄按键信息 2.提供一个串口上位机,发送按键信息(目前仅支持1P,虽然H_NES支持2P,但实例中未使用),显示单片机发来的信息。 3.SPI串口屏显示,ST7789v的240x320的屏幕。极限满屏刷新达70+FPS(在80MHz下的SCK频率,只刷新了256x240的区域) ,完全可达NES渲染需要 4.SAI输出IIS音频信号,接入解码芯片即可出声 由于想快速测试,写得比较匆忙,SAI输出的是标准44100Hz的音频,但H_NES的音频抽样分频只能是整数(使用了10分频) 这会造成速度略微有些差别,但这可以微调PLL得到修正 >**最近的更新优化了PPU渲染, 进一步降低性能需求, CPU占用率降至60%~70%** ### 硬件 1.串口 PA9,PA10------------------串口 2.音频输出 PE11,PE12,PE13,PE14-------SAI2(至于怎么连CubeMX点一下就知道了) 3.显示屏(ST7789v 240x320) 有些模组把CS阉割掉了(接地),但CS对同步主时钟有帮助,即使受到干扰,但干扰不会传递到下一帧(下一次CS选通)。对于高帧率应用,CS线很有必要 PB3-----------------------SPI1_SCK (最好串个100R电阻) PD7-----------------------SPI1_MOSI (最好串个100R电阻) PD6-----------------------RES PD5-----------------------DC PA15----------------------CS PB4-----------------------背光 4.SPIFlash(存游戏必备) 建议W25q128jvsiq的(128或者以下的,速度能达到133MHz的) 如果是256的,读取时会多花若干个时钟来传递地址(我才不会说我的下载算法其实没有支持这个) PB2-----------------------Flash1_SCK,Flash2_SCK PE7-----------------------Flash2_IO0 PE8-----------------------Flash2_IO1 PE9-----------------------Flash2_IO2 PE10----------------------Flash2_IO3 PD11----------------------Flash1_IO0 PD12----------------------Flash1_IO1 PE2-----------------------Flash1_IO2 PD13----------------------Flash1_IO3 PB6-----------------------Flash1_CS,Flash2_CS 双Flash, 速度更快。这个实例将以120MHz的时钟访问Flash ### 食用方法 更详细的步骤在-> https://blog.csdn.net/qq_42907191/article/details/123357899 1.预先将Example\Bootloader下的文件下载到H750的内部Flash中 2.想玩的游戏通过Example\多文件合并.exe 合并成一个.bin文件(其实就是加了文件大小,便于搜索,实际上H_NES只识别数组) 3.Example\STlinkUtility下载算法\ 下的文件复制到 STlinkUtility或者STM32CubeProgrammer安装目录下的ExternalLoader文件夹中 4.打开STlinkUtility或者STM32CubeProgrammer,加载下载算法,把前面生成的.bin文件下载到0x90040000中 5.Example\MDK下载算法\ 下的文件放置到 MDK安装目录\ARM\Flash\ 中 6.打开 Example\STM32H750VB_NES 下载程序 7.打开 Example\串口手柄\ 下的上位机,左下角的输入框是按键输入,W-上 A-左 S-下 D-右 K-B L-A 发送: 空格FN-下一个游戏 空格FS-保存存档 空格FL-加载存档