大兴营销型网站建设,上海网站推广费用,洛阳哪里有做网站的,广告公司网站没玩过NES游戏的童年#xff0c;可能不是80后的童年。我们小时候是从玩FC开始接触游戏机的#xff0c;那时真的是红极一时啊#xff0c;我上初中时还省吃俭用买了一台小霸王#xff0c;暑假里把电视机都给打爆了#xff01;那时任天堂单是FC机的主机的发售收入就超过全美的… 没玩过NES游戏的童年可能不是80后的童年。我们小时候是从玩FC开始接触游戏机的那时真的是红极一时啊我上初中时还省吃俭用买了一台小霸王暑假里把电视机都给打爆了那时任天堂单是FC机的主机的发售收入就超过全美的电视台的收入的总和在人们的心目中扎下了任天堂的这个招牌。 前言
1983年7月15日由日本任天堂株式会社原本是生产日式扑克即“花札”的宫本茂先生领导开发的一种第三代家用电子游戏机FC全称Family Computer也称作Famicom在欧美发售时则被称为nes全称Nintendo Entertainment System在中国大陆、台湾和香港等地因其外壳为红白两色所以人们俗称其为“红白机”正式进入市场销售并于后来取得了巨大成功由此揭开了家用电子游戏机遍布世界任何角落电子游戏全球大普及的序幕。 什么是InfNES
一款NES游戏模拟器。InfoNES可以很容易地被移植到各个平台作者是Martin Freij。他是一位瑞典的程序员和游戏爱好者于2002年开发了infoNES模拟器。infoNES是一个基于NES任天堂娱乐系统的模拟器旨在让人们能够在计算机上玩经典的NES游戏。
InfoNES具备良好的可移植性它将与环境有关的内容都清出了软件内核并且单独集合于一个InfoNES_System.h中我们要做的就是实现这里提到的各种函数再把InfoNES加入到我们的工程中一起编译。
最近成功实现了USB接口的FC手柄驱动使得在imx6ull开发板玩游戏具有可玩性这里将这个移植过程记录下来。如果对NES模拟器的源码实现感兴趣infoNES也是个不错的研究对象代码结构清晰可以让你了解到如何模拟实现k6502这款经典cpu的加深对计算机体系结构的理解。
接下来让我们重温下经典缅怀下童年吧
池塘外的迷路书上知鸟在声声叫着夏天......伴随着优美的歌声仿佛穿越回来了少年。
完成以下操作让你即刻拥有款移动游戏机实现童年时的梦想。
很早之前我在imax283平台上移植过infoNES那时我的github仓地址是
https://github.com/yongzhena/infoNES
这次直接拉取下来用只是修改下joypad手柄驱动的代码就可以完美运行啦。 移植过程
整个移植过程主要涉及三部分显示、声音输出和usb手柄支持。前两个直接拉取上面的我的仓直接就具备了这里着重介绍下USB手柄驱动支持。
基于fb0的LCD显示
在InfoNES_System_Linux.cpp文件中修改。显示这块儿实现两个函数一个是lcd_fb_init一个是lcd_fb_display_px。
static int lcd_fb_init()
{//如果使用 mmap 打开方式 必须是 读定方式fb_fd open(/dev/fb0, O_RDWR);if(-1 fb_fd){printf(catt open /dev/fb0 \n);return -1;}//获取屏幕参数if(-1 ioctl(fb_fd, FBIOGET_VSCREENINFO, var)){close(fb_fd);printf(catt ioctl /dev/fb0 \n);return -1;}//计算参数px_width var.bits_per_pixel /8;line_width var.xres * px_width;screen_width var.yres * line_width;lcd_width var.xres;lcd_height var.yres;printf(fb width:%d height:%d pixel:%d \n, lcd_width, lcd_height,px_width*8);fb_mem (unsigned char *)mmap(NULL, screen_width, PROT_READ | PROT_WRITE, MAP_SHARED, fb_fd, 0);if(fb_mem (void *)-1){close(fb_fd);printf(catt mmap /dev/fb0 \n);return -1;}//清屏memset(fb_mem, 0 , screen_width);return 0;
}
static int lcd_fb_display_px(WORD color, int x, int y)
{unsigned char *pen8;unsigned short *pen16;pen8 (unsigned char *)(fb_mem y*line_width x*px_width);pen16 (unsigned short *)pen8;*pen16 color;return 0;
}
以下的实现注意zoom_x_tabzoom_y_tab这两项。它的作用是对像素做了全屏和放大处理。 源码里的make_zoom_tab()就是干这个用。如果觉得屏幕很大放大后颗粒感很重能否再优化这里是个可能的优化方向。
/**/
/* */
/* InfoNES_LoadFrame() : */
/* Transfer the contents of work frame on the screen */
/* */
/**/
unsigned short ChColor(unsigned short color)
{return (color3)4|(color0x001f);
}void InfoNES_LoadFrame()
{int x,y;int line_width;WORD wColor,R,G,B,Gr;//修正 if(0 fb_fd){for (y 0; y lcd_height; y ){line_width zoom_y_tab[y] * NES_DISP_WIDTH;for (x 0; x lcd_width; x ){wColor ChColor(WorkFrame[line_width zoom_x_tab[x]]);lcd_fb_display_px(wColor, x, y);}}}/*16 bit per pixel*//* Exchange 16-bit to 256 gray *//*for (y 0; y NES_DISP_HEIGHT; y ){for (x 0; x NES_DISP_WIDTH; x ){//wColor WorkFrame[y * lcd_width x ];wColor WorkFrame[ ( y 8 ) x ];R ( ( wColor 0x7c00 ) 7 );G ( ( wColor 0x03e0 ) 2 );B ( ( wColor 0x001f ) 3 ); //Gr ( ( 9798*R 19235*G 3735*B)15);wColor(WORD)((B16)|(G8)|R);lcd_fb_display_px(wColor, x, y);}}*/
}
基于Alsa的声音支持
实现这个声音支持的前提是板子上得有基于alsa框架的音频驱动且功能正常。否则以下这些实现里需要全部留空不用实现。
/**/
/* */
/* InfoNES_SoundInit() : Sound Emulation Initialize */
/* */
/**/
void InfoNES_SoundInit( void )
{}/**/
/* */
/* InfoNES_SoundOpen() : Sound Open */
/* */
/**/
int InfoNES_SoundOpen( int samples_per_sync, int sample_rate )
{// sample_rate 采样率 44100// samples_per_sync 735// 采样率 / 8 * 声道数 44100 / 8 * 1 5512.5// 8位 声音/*声道数 1采样率 44100采样位数 8每次播放块大小NES APU 每次生成一块735*/unsigned int rate sample_rate;snd_pcm_hw_params_t *hw_params;if(0 snd_pcm_open(playback_handle, default, SND_PCM_STREAM_PLAYBACK, 0)) {printf(snd_pcm_open err\n);return -1;}printf(snd_pcm_open ok!\nsamples_per_sync%d,sample_rate%d\n,samples_per_sync,sample_rate);if(0 snd_pcm_hw_params_malloc(hw_params)){printf(snd_pcm_hw_params_malloc err\n);return -1;}if(0 snd_pcm_hw_params_any(playback_handle, hw_params)){printf(snd_pcm_hw_params_any err\n);return -1;}if(0 snd_pcm_hw_params_set_access(playback_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) {printf(snd_pcm_hw_params_any err\n);return -1;}//16bit PCM 数据if(0 snd_pcm_hw_params_set_format(playback_handle, hw_params, SND_PCM_FORMAT_U8)){printf(snd_pcm_hw_params_set_format err\n);return -1;}if(0 snd_pcm_hw_params_set_rate_near(playback_handle, hw_params, rate, 0)) {printf(snd_pcm_hw_params_set_rate_near err\n);return -1;}//单声道 非立体声if(0 snd_pcm_hw_params_set_channels(playback_handle, hw_params, 1)){printf(snd_pcm_hw_params_set_channels err\n);return -1;}if(0 snd_pcm_hw_params(playback_handle, hw_params)) {printf(snd_pcm_hw_params err\n);return -1;}snd_pcm_hw_params_free(hw_params);if(0 snd_pcm_prepare(playback_handle)) {printf(snd_pcm_prepare err\n);return -1;}return 1;
}/**/
/* */
/* InfoNES_SoundClose() : Sound Close */
/* */
/**/
void InfoNES_SoundClose( void )
{snd_pcm_close(playback_handle);
}/**/
/* */
/* InfoNES_SoundOutput() : Sound Output 5 Waves */
/* */
/**/
void InfoNES_SoundOutput( int samples, BYTE *wave1, BYTE *wave2, BYTE *wave3, BYTE *wave4, BYTE *wave5 )
{int i;int ret;unsigned char wav;unsigned char *pcmBuf (unsigned char *)malloc(samples);//printf(InfoNES_SoundOutput,samples%d\n,samples);//printf(\n);for (i0; i samples; i){wav (wave1[i] wave2[i] wave3[i] wave4[i] wave5[i]) / 5;//单声道 8位数据pcmBuf[i] wav;//printf(%02x,wav);}//printf(\n);ret snd_pcm_writei(playback_handle, pcmBuf, samples);if(-EPIPE ret){snd_pcm_prepare(playback_handle);}free(pcmBuf);return ;
}
USB手柄支持
接下来这块儿是介绍的重点实现usb手柄驱动的支持。这样才有可玩性啊。我买的这款USB的游戏手柄很便宜也很容易买到。如果你的USB手柄不是这款那么实现驱动支持的原理也是类似的万变不离宗只是键值对应关系跟我的可能不一样实测改下即可。
关于USB游戏手柄的驱动支持参见我的上篇博文iMX6ULL驱动开发 | 让imx6ull开发板支持usb接口FC游戏手柄_特立独行的猫a的博客-CSDN博客
不想按上文总结的重新编译内核的话可以把驱动单独编译成模块动态加载进去。
这里介绍下让infoNES支持usb手柄需要做哪些移植。
按键键值测试小程序
#include stdio.h
#include sys/stat.h
#include fcntl.h
#include unistd.h
#include linux/input.h #define _EV_KEY 0x01 /* button pressed/released */
#define _EV_ABS 0x03
#define _EV_MSC 0x04 int main() {printf(hello,usb hid joystick key test\n);int fd open(/dev/input/event3, O_RDONLY);struct input_event e;while(1) {read(fd, e, sizeof(e));switch(e.type) {case _EV_KEY:printf(type: %d, code: %d,value: %d, time: %d\n, e.type, e.code,e.value, e.time);break;case _EV_ABS:printf(type: %d, code: %d,value: %d, time: %d\n, e.type, e.code,e.value, e.time);break;case _EV_MSC:printf(type: %d, code: %d,value: %d, time: %d\n, e.type, e.code,e.value, e.time);break;default:if(e.type ! 0){printf(type:%d, code: %d,value: %d, time: %d\n,e.type, e.code,e.value, e.time);}}}close(fd);return 0;
}
joypad_input.cpp文件修改
主要是USBjoypadGet()接口的实现要跟FC手柄的键值对应上。
static int USBjoypadGet(void)
{/*** FC手柄 bit 键位对应关系 真实手柄中有一个定时器处理 连A 连B * 0 1 2 3 4 5 6 7* A B Select Start Up Down Left Right*///因为 USB 手柄每次只能读到一位键值 所以要有静态变量保存上一次的值static unsigned char joypad 0;struct input_event e;if(0 read (USBjoypad_fd, e, sizeof(e))){if(0x3 e.type){/*上value:0 type:0x3 code:0x1value:127 type:0x3 code:0x1*/if(0 e.value 0x1 e.code){joypad | 14;printf(Up\n);}/*下value:255 type:0x3 code:0x1value:127 type:0x3 code:0x1*/if(255 e.value 0x1 e.code){joypad | 15;printf(Down\n);}//松开if(127 e.value 0x1 e.code){joypad ~(14 | 15);}/*左value:0 type:0x3 code:0x0value:127 type:0x3 code:0x0*/if(0 e.value 0 e.code){joypad | 16;printf(Left\n);}/*右value:255 type:0x3 code:0x0value:127 type:0x3 code:0x0*/if(255 e.value 0 e.code){joypad | 17;printf(Right\n);}//松开if(127 e.value 0 e.code){joypad ~(16 | 17);}}if(0x1 e.type){/*选择value:0x1 type:0x1 code:296value:0x0 type:0x1 code:296*/if(0x1 e.value 296 e.code){joypad | 12;printf(Select\n);}if(0x0 e.value 296 e.code){joypad ~(12);}/*开始value:0x1 type:0x1 code:297value:0x0 type:0x1 code:297*/if(0x1 e.value 297 e.code){joypad | 13;printf(Start\n);}if(0x0 e.value 297 e.code){joypad ~(13);}/*Avalue:0x1 type:0x1 code:288value:0x0 type:0x1 code:288*/if(0x1 e.value 288 e.code){joypad | 10;printf(A\n);}if(0x0 e.value 288 e.code){joypad ~(10);}/*Bvalue:0x1 type:0x1 code:289value:0x0 type:0x1 code:289*/if(0x1 e.value 289 e.code){joypad | 11;printf(B\n);}if(0x0 e.value 289 e.code){joypad ~(11);}/*Xvalue:0x1 type:0x1 code:290value:0x0 type:0x1 code:290*/if(0x1 e.value 290 e.code){joypad | 10;printf(X\n);}if(0x0 e.value 290 e.code){joypad ~(10);}/*Yvalue:0x1 type:0x1 code:291value:0x0 type:0x1 code:291*/if(0x1 e.value 291 e.code){joypad | 11;printf(Y\n);}if(0x0 e.value 291 e.code){joypad ~(11);}}return joypad;}return -1;
}
完整实现
#include stdio.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include unistd.h
#include pthread.h
#include linux/input.h #define JOYPAD_DEV /dev/joypad
#define USB_JS_DEV /dev/input/event3typedef struct JoypadInput{int (*DevInit)(void);int (*DevExit)(void);int (*GetJoypad)(void);struct JoypadInput *ptNext;pthread_t tTreadID; /* 子线程ID */
}T_JoypadInput, *PT_JoypadInput;//全局变量通过互斥体访问
static unsigned char g_InputEvent;static pthread_mutex_t g_tMutex PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t g_tConVar PTHREAD_COND_INITIALIZER;static int joypad_fd;
static int USBjoypad_fd;
static PT_JoypadInput g_ptJoypadInputHead;static void *InputEventTreadFunction(void *pVoid)
{/* 定义函数指针 */int (*GetJoypad)(void);GetJoypad (int (*)(void))pVoid;while (1){//因为有阻塞所以没有输入时是休眠g_InputEvent GetJoypad();//有数据时唤醒pthread_mutex_lock(g_tMutex);/* 唤醒主线程 */pthread_cond_signal(g_tConVar);pthread_mutex_unlock(g_tMutex);}
}static int RegisterJoypadInput(PT_JoypadInput ptJoypadInput)
{PT_JoypadInput tmp;if(ptJoypadInput-DevInit()){return -1;}//初始化成功创建子线程 将子项的GetInputEvent 传进来pthread_create(ptJoypadInput-tTreadID, NULL, InputEventTreadFunction, (void*)ptJoypadInput-GetJoypad);if(! g_ptJoypadInputHead){g_ptJoypadInputHead ptJoypadInput;}else{tmp g_ptJoypadInputHead;while(tmp-ptNext){tmp tmp-ptNext;}tmp-ptNext ptJoypadInput;}ptJoypadInput-ptNext NULL;return 0;
}static int joypadGet(void)
{static unsigned char joypad 0;//printf(joypadGet val:\n);joypad read(joypad_fd, 0, 0);return joypad;
}static int joypadDevInit(void)
{joypad_fd open(JOYPAD_DEV, O_RDONLY);if(-1 joypad_fd){printf(%s dev not found \r\n, JOYPAD_DEV);return -1;}return 0;
}static int joypadDevExit(void)
{close(joypad_fd);return 0;
}static T_JoypadInput joypadInput {joypadDevInit,joypadDevExit,joypadGet,
};static int USBjoypadGet(void)
{/*** FC手柄 bit 键位对应关系 真实手柄中有一个定时器处理 连A 连B * 0 1 2 3 4 5 6 7* A B Select Start Up Down Left Right*///因为 USB 手柄每次只能读到一位键值 所以要有静态变量保存上一次的值static unsigned char joypad 0;struct input_event e;if(0 read (USBjoypad_fd, e, sizeof(e))){if(0x3 e.type){/*上value:0 type:0x3 code:0x1value:127 type:0x3 code:0x1*/if(0 e.value 0x1 e.code){joypad | 14;printf(Up\n);}/*下value:255 type:0x3 code:0x1value:127 type:0x3 code:0x1*/if(255 e.value 0x1 e.code){joypad | 15;printf(Down\n);}//松开if(127 e.value 0x1 e.code){joypad ~(14 | 15);}/*左value:0 type:0x3 code:0x0value:127 type:0x3 code:0x0*/if(0 e.value 0 e.code){joypad | 16;printf(Left\n);}/*右value:255 type:0x3 code:0x0value:127 type:0x3 code:0x0*/if(255 e.value 0 e.code){joypad | 17;printf(Right\n);}//松开if(127 e.value 0 e.code){joypad ~(16 | 17);}}if(0x1 e.type){/*选择value:0x1 type:0x1 code:296value:0x0 type:0x1 code:296*/if(0x1 e.value 296 e.code){joypad | 12;printf(Select\n);}if(0x0 e.value 296 e.code){joypad ~(12);}/*开始value:0x1 type:0x1 code:297value:0x0 type:0x1 code:297*/if(0x1 e.value 297 e.code){joypad | 13;printf(Start\n);}if(0x0 e.value 297 e.code){joypad ~(13);}/*Avalue:0x1 type:0x1 code:288value:0x0 type:0x1 code:288*/if(0x1 e.value 288 e.code){joypad | 10;printf(A\n);}if(0x0 e.value 288 e.code){joypad ~(10);}/*Bvalue:0x1 type:0x1 code:289value:0x0 type:0x1 code:289*/if(0x1 e.value 289 e.code){joypad | 11;printf(B\n);}if(0x0 e.value 289 e.code){joypad ~(11);}/*Xvalue:0x1 type:0x1 code:290value:0x0 type:0x1 code:290*/if(0x1 e.value 290 e.code){joypad | 10;printf(X\n);}if(0x0 e.value 290 e.code){joypad ~(10);}/*Yvalue:0x1 type:0x1 code:291value:0x0 type:0x1 code:291*/if(0x1 e.value 291 e.code){joypad | 11;printf(Y\n);}if(0x0 e.value 291 e.code){joypad ~(11);}}return joypad;}return -1;
}static int USBjoypadDevInit(void)
{USBjoypad_fd open(USB_JS_DEV, O_RDONLY);if(-1 USBjoypad_fd){printf(%s dev not found \r\n, USB_JS_DEV);return -1;}return 0;
}static int USBjoypadDevExit(void)
{close(USBjoypad_fd);return 0;
}static T_JoypadInput usbJoypadInput {USBjoypadDevInit,USBjoypadDevExit,USBjoypadGet,
};int InitJoypadInput(void)
{int iErr 0;//iErr RegisterJoypadInput(joypadInput);iErr RegisterJoypadInput(usbJoypadInput);return iErr;
}int GetJoypadInput(void)
{/* 休眠 */pthread_mutex_lock(g_tMutex);pthread_cond_wait(g_tConVar, g_tMutex); /* 被唤醒后,返回数据 */pthread_mutex_unlock(g_tMutex);return g_InputEvent;
}
编译生成
最后交叉编译生成可执行文件放到板子上执行即可插上USB手柄就可以玩啦运行不错还很流畅。需要注意的是为了支持声音使用了alsa的头文件并链接了libasound库。需确保你的环境里有这个库没有的话不支持声音输出可以去掉这个链接。文末有NES游戏的ROM资源。
makefile脚本
#根据实际路径修改工具链路径
CHAIN_ROOT/opt/yang/imax6ul/gcc-linaro-arm-linux-gnueabihf-4.9-2014.09_linux/bin
CROSS_COMPILE$(CHAIN_ROOT)/arm-linux-gnueabihf-#CHAIN_ROOT /home/yang/b503/ctools/gcc-linaro-arm-linux-gnueabihf-4.9-2014.09_linux/bin
#CROSS_COMPILE$(CHAIN_ROOT)/arm-linux-gnueabihf-
#CROSS_COMPILE CC : $(CROSS_COMPILE)gcc
#CC arm-poky-linux-gnueabi-gcc
TARBALL InfoNES08J# InfoNES
.CFILES ./../K6502.cpp \./../InfoNES.cpp \./../InfoNES_Mapper.cpp \./../InfoNES_pAPU.cpp \./InfoNES_System_Linux.cpp joypad_input.cpp.OFILES $(.CFILES:.cpp.o)CCFLAGS -o2 -fsigned-char -I../
LDFILGS -lstdc -L../libs # gcc3.x.xall: InfoNESInfoNES: $(.OFILES)$(CC) $(INCLUDES) -o $ $(.OFILES) $(LDFILGS) -lm -lpthread -lasound.cpp.o:$(CC) $(INCLUDES) -c $(CCFLAGS) $*.cpp -o $clean:rm -f $(.OFILES) ../*~ ../*/*~ corecleanall:rm -f $(.OFILES) ../*~ ../*/*~ core InfoNESrelease: clean alltar:( cd ..; \tar cvf $(TARBALL).tar ./*; \gzip $(TARBALL).tar \)install:install ./InfoNES /usr/local/bin
其他资源
NES红白机全屏显示
NES专题——NES游戏机简介_nesfc_金小庭的博客-CSDN博客
V3S移植nes游戏模拟器附带游戏合集_v3s编译游戏模拟器_qq_46604211的博客-CSDN博客
任天堂红白机nes游戏简介 任天堂红白机nes游戏简介
资料内含众多NES的游戏ROM文件及运行模拟器
链接https://pan.baidu.com/s/1uXAxLKGmKGwZFB3Yraq8gg 提取码qxcy
游戏合集并解压然后改名为游戏名为英文 链接https://pan.baidu.com/s/16hIWwYQQEX9aOBDG1dVa0A 提取码asdf