先用个视频展示一下效果:
特点:支持双手柄双人玩耍、可小屏/全屏、可选择游戏、一键静音/恢复、跳帧调节、屏幕可调亮度、支持存档/读档……
接下来才是本文正文:
起先,我以为w806也就只能玩玩俄罗斯方块这种级别的游戏:
然后有次在csdn上看到这位大佬的文章“单片机---HLK-W801移植Nes模拟器”,通过他的最后一章研究得出一个数据:infones的帧生成速度是28fps左右,w801的主从spi传输时钟最高20MHZ所以理论最高每秒传输20帧,最终加上实际执行时的各种开销耗时实际刷屏速度为11fps左右。这没毛病,就算实际刷屏跑满能到20fps,这对nes模拟器还是远远不够的,要知道nes游戏在PAL制式是50帧、NTSC是60帧的,于是我更认为w80x系列的芯片是做不了nes模拟器了。
直到有一天,又在csdn上看到了一位大佬的文章“W800 W801 W806 SDIO的SPI模式驱动TFT LCD 突破低速SPI的20M限制 高达120M的SPI”,瞬间眼前一亮,前面所说的nes模拟器刷屏速度问题不就解决了吗?于是大感兴趣,准备好好学习一下这位大佬的思路,无奈发现这文章竟然还需要开通vip才能阅读,这。。。。
穷逼的我只好止步于此了,刚准备关掉网页,这时眼睛嫖过评论发现有好几条评论,于是决定先看看评论没准能得到点信息,幸好csdn没有也把评论也给需要vip才能查看。
终于在一条评论中得到了我期望的信息“W80X驱屏方案 SDIO SPI模式 最高120M时钟驱动LCD屏 LVGL界面开发”,顶isme大佬。
赶紧下载了这份代码,通过了一段时间的努力学习,终于搞明白了原理,大佬们的变通能力真流弊
于是回到开头所说的那个NES模拟器,这回刷屏的问题应该不是问题了,那么接下来的问题是啥呢?是infones的帧生成速度!每秒只有28fps,这你敢信?百度了一下其它mcu的nes模拟器情况,stm32上有超频后以不到200MHZ流畅玩的,esp32也有畅玩的。那么问题来了w801这可是一颗240MHZ的cpu,不至于还跑不过一个跑不到200MHZ的stm32吧。
看来是软件运行效率的问题,大概了解了一下开源的那些个nes模拟器:
infones是一个很早之前由岛国人开发的模拟器,其软件设计结构清晰明了,移植层也被独立抽离出来,小白都能轻松上手,相应的,软件设计的非常清晰易懂,那就意味着运行效率不够好了,网上不少说它慢,还有声音只能听个响啥的问题。
使用arm核的mcu,有个比较变态的nes模拟器,6502模拟全用汇编实现的,这种执行效率高,一片好评。
esp32使用的nes模拟器应该是从arduino社区被发布出来的,它的nes模拟运算跑在一个核上, 刷屏和声音由另一个核去做。
看起来最好的方法还是移(借)植(鉴)开源模拟器,w801使用的平头哥的cpu,汇编还不同于arm的汇编,咱的汇编也不咋行,移植难度比较大,所以汇编版本的模拟器直接就不考虑了。
esp32的模拟部分也是跑的单核,所以这点上看和跑infones一样,考虑到咱是个nes模拟器小白,目前完全不懂任何nes模拟知识,所以还是选择通俗易懂的infones算了,把它的运行效率搞搞就好了。
当然也不用从头搞,如果有csdn下载能力的话,可以直接从开头那位大佬的文章处下载w801现成的代码,但是我的话么有能力下载呀,只好下载另一位大佬的代码“手把手教你移植InfoNES(到HANKER-LM4F232)”,这位大佬写了系列的文章,可以都看下了解下优化思路。
因为上次听一个朋友将联盛德有一款w806比较火,所以我这次也想使用w806来做这个模拟器,况且这个模拟器也用不到wifi和蓝牙功能。w80x系列都是同一颗cpu,代码其实通用的,区别就只是部分配置的差异,比较一下两份sdk很容易就知道怎把w801sdk用在w806上了。代码很容易就能集进w801的sdk,进行编译,这里也不多说。
于是先上淘宝购买了一块w806,拿到手之后,看了半天,又对比了一下人家卖的w801,不由心里失望:
这板子设计也太不走心了,这么多的io,竟然只有2个gnd,1个3.3v,0个5v,这可是用usb线供电的,连个5v供电都没。
话不多说了,还是搞起代码吧,首先要搞明白为啥infones的绘图速度低,以最经典的超级玛丽为例,首先超级玛丽nes文件按十六进制复制到代码中,先不用适配lcd驱动来刷屏,就测一下运行超级玛丽时的fps情况,每5秒统计一下总帧数,跑完是26fps左右,因为infones非常好移植,所以我又把它也挪到esp32上(配置为只用cpu0,主频也配为240M),运行后发现,esp32也才24fps左右。看来这不是cpu效率问题了,w806的cpu效率还略高于esp32的单核,那么这就属于纯属代码执行效率低问题,毕竟能运行nes模拟器的esp32跑infones也这样。
通过和esp32的对比时,发现一个有趣的现象:如果游戏文件存在flash中,xip执行时读取,那么w806速度就会降低到11fps左右,而esp32没有变化,于是就得到了一个关键的信息,要把infones尽量变小,以减少xip执行时的耗时开销和腾出更多的ram,然后游戏文件要读取保存在ram才能提速。
先把mapper进行裁剪,只保留了0-3这四个最简单也是常用的mapper,这样infones就大大瘦身了。
然后分析代码流程,发现瓶颈主要在于大量的循环和函数调用,执行的函数其实就俩:
模拟6502用来执行游戏指令代码的K6502_Step( STEP_PER_SCANLINE )函数 和
模拟PPU用来将NT、Patten Table、AT、Pallette中的数据组合成像素颜色数据的InfoNES_DrawLine()函数,当然最后还有一个我们用来按行刷屏的InfoNES_LoadLine()函数。
接下来就是先不用管InfoNES_LoadLine(),把6502模拟和PPU模拟绘图给使劲优化。各种招数用起来,大力出奇迹!
经过好几个晚上、周末假期等闲暇时间的琢磨,终于有天,绘图生成的帧的速度达到了惊人的76fps:
这时,infones的运行效率问题已经解决,如果是使用高速的并口屏,那么整个活就直接完成了,搞不好还得加延时以抑制过快的速度。
但是咱不是并口屏,咱是慢速的串口屏,而且咱还没实际刷到屏上去呢,所以咱马上给自己定个flag,让实际刷屏达到PAL制式50fps。那么接下来就是实际刷屏了。
屏幕我是用4.0寸的st7796s,分辨率480*320,最高好像可以支持到80MHZ时钟,还带有触摸和sd卡座:
nes原始的分辨率是256*224,所以就先拿256*224直接输出lcd,刷屏效果很感人:
达到了惊人的33fps,30fps其实就可以玩了,但是距离50fps还是比较遥远的,于是,我又动了一番脑筋,一番折腾之后,刷屏速度又有了明显提升:
已经达到了45fps左右。
打点测了一下256*224目前刷一屏大约是17ms左右,那么45fps大约是765ms,目前的问题在于刷屏太慢,这需要考虑多整几个发送缓冲区让dma循环使用去搬以减少cpu等待时间(一般可以整两个即可达到目的)。考虑到45fps已经很接近了50fps了,继续提升速度空间比较小了,还比较消耗内存,所以暂且就这样,留给以后再进一步优化。
ps:lcd多块buf发送后来做了验证,提升效果很小(只能提高2-3帧)。生成像素数据很快,刷屏速度太慢,需要很多buf来做缓冲让dma首尾相连去无缝发送,这样占用的内存和速度完全不成正比,而咱有需要大量可用的内存来存放游戏rom,所以完全可以忽略多buf机制了,毕竟3帧的影响太小了。
接下来就是声音了,infones的声音确实只是听个响的,音质完全没有,所以百度了一番,从nester抄取了apu的算法,直接可以输出16位44100采样率的数据,这下听着好多了,不过了节省内存,最终还是选择了22050采样率的音质。音频选择使用i2s输出,简单的可以选择MAX98357模块,这个是放大器可以直接驱动3Ω8w的喇叭发出响亮的声音,但是因为喇叭音质问题,我使用了之前改装天猫精灵时用过的pcm5102(小紫板),这个模块仅是音频输出无放大器功能,所以需要喇叭自带功放才能用好。
使用i2s输出声音的时候,又遇到了问题:本来apu可以改为不输出16位而用8位,这样还能减轻计算量提升速度,但是我是用8位数据后,游戏中apu产生的声音是8位单声道22050采样率的数据,直接用这个参数配置驱动,播放直接没音。因为我对i2s并不熟,所以就乱试了一通,最后竟然发现驱动接口里要配为16位数据格式竟然就能播放了,接着杂音很大,我估计是传输的数据格式缺斤少两了,于是又对声音数据累积了2段,填写驱动长度的时候还要除以2,这样之后竟然杂音小了很多,虽然看起来还是哪儿没配对,但这里我就不纠结了,声音差不多就可以了。
wm_i2s_ws_config(WM_IO_PB_13);//lrck wm_i2s_do_config(WM_IO_PB_11);//data wm_i2s_ck_config(WM_IO_PB_12);//blck I2S_InitDef opts = { I2S_MODE_MASTER, I2S_CTRL_MONO, I2S_RIGHT_CHANNEL, I2S_Standard_MSB, I2S_DataFormat_16, 22050, 5000000 }; wm_i2s_port_init(&opts); ...... //游戏每次生成378长度的16位声音(也就是756字节),需要累积两次也就是攒够756*2字节的数据才去传输, //传输接口填写的长度是756: wm_i2s_transmit_dma(&sound_hdma_tx, (uint16_t *)sound_buf, 756); //sound_buf每次填充新数据前保证了dma已经传输完成 ......
不知道是wmsdk的i2s驱动有问题,还是我不会用的缘故,但是这段音频导出用电脑播放很正常,有知道原因的朋友不妨请下面留意指点一下我哈。
看到屏带有sd卡座,所以考虑从sd卡上去取nes文件来自主选择游戏,所以从使用业界标杆esp32模拟器的韦老师工程“esp32_100ask_project”里抄了个菜单功能。
然后又斥资十六元购买了一个9孔的fc游戏手柄(据老板说这玩意进货价才是九块),用来畅玩游戏:
当然还有一张祖传的512M sd卡,用来存放nes游戏文件:
又看到lcd屏带有背光控制引脚,所以使用pwm搞了个亮度调节功能(或许应该在整个光敏电阻,来个自动调节亮度……)。
因为这款lcd分辨率是480*320,如果游戏画面能达到这么大那就看着很舒服了,所以我就尝试着对画面进行了拉伸,当然想法很美妙,效果又一次很感人。画面拉大以后,刷屏需要的数据更多,每行变多成了1.5条,竖线也需要1.73条,所以循环变得更多耗时也增大了,屏变大了传输一屏数据需要的时间也增多了。打点测试了一下,刷完一屏需要48ms,软件循环大概耗去了8ms,真正刷屏传输大约40ms。
到这里我突然灵机一动,因为为了充分利用cpu,一直工作在240MHZ,对于这个屏,spi只能分配为60MHZ,那么在刷屏时可以临时将cpu降为160MHZ spi就可以调整为80MHZ,传输完再改回去,那么这样啥也不耽搁速度还能加快,试了一下,这样传输一屏需30ms左右,大大提高了传输效率,这样480*320分辨率就可以达到30fps左右了。达到30fps,来点跳帧也就可以畅玩了,至此全屏也就达到了目的。
代码暂且还没有整理好,稍后整理后公布,感兴趣的朋友完全可以按照上文的思路和流程自己制作哈,玩这个重在自己动手探索,如果照着保姆教程像个机器人一样跑一遍那是没有任何灵魂的。
对于想体验的小伙伴,可以直接点此下载固件烧录畅玩 ---> w80x_nes.zip 。
w80x系列芯片通用,都可以烧录使用,亲测w801和w806都可以使用。
需注意:目前只能畅玩90kb以下的nes游戏,卡玩700kb以下nes游戏,当然游戏也得是mapper0-3的才行。后续考虑使用psram,再增加个mapper4,应该就可以玩比较大型的游戏了。
接线图:
lcd(使用st7796s): blk -----WM_IO_PA_07 scl -----WM_IO_PA_09 mosi-----WM_IO_PA_10 dc -----WM_IO_PA_12 rst -----WM_IO_PA_13 cs -----WM_IO_PA_14 sd卡: cs-----WM_IO_PB_04 ck-----WM_IO_PB_02 di-----WM_IO_PB_03 do-----WM_IO_PB_05 游戏要放置在sd卡根目录下的nes文件夹中。 i2s(需自带mck): lrck-----WM_IO_PB_13 data-----WM_IO_PB_11 blck-----WM_IO_PB_12 fc手柄(5v供电): clock-----WM_IO_PB_08 latch-----WM_IO_PB_09 data -----WM_IO_PB_10 在游戏选择菜单 按上下键选择列表中的游戏,按start/a/b键玩游戏 游戏中按键说明(进入游戏后才会生效): 重启到菜单-----select + start 调高亮度-----select + up 降低亮度-----select + down 调高跳帧数-----select + right 降低跳帧数-----select + left 全屏游戏-----select + a 静音/播放-----select + b
后来我又修改了一版,增加了俄罗斯方框游戏和双手柄支持,可以畅玩90坦克、魂斗罗1等游戏了,接线和按键相比上面版本发生变化如下:
fc手柄1(5v供电): clock-----WM_IO_PB_07 latch-----WM_IO_PB_08 data -----WM_IO_PB_09 fc手柄2(5v供电): clock-----WM_IO_PB_01 latch-----WM_IO_PB_00 data -----WM_IO_PA_15 在游戏选择菜单 按上下键选择列表中的nes游戏,按start键玩游戏 直接按a键玩俄罗斯方框游戏 直接按b键播放avi视频(播放/media/tangmaru.avi,需pc端提前转码故不建议尝试)
固件点此下载 ---> w800_nes_2p.zip。
说明:
1. 进入某些游戏后发现有点慢时,就可以调高点跳帧数,感觉过快时可以降低点跳帧数。
2. 必须要接fc手柄才能正常使用,否则会不停重启复位。
整理了一份调试通的源码,供感兴趣的朋友参考 ---> w806_mynes.7z (只支持cdk编译)
闲置无用,出当时玩这个时所买的全套模块,可咸鱼交易:https://m.tb.cn/h.5Ds0sxY?tk=p2kpWna18ue。