前言
之前我们完成了NES移植必须的输入部分https://bbs.elecfans.com/jishu_2316440_1_1.html
显示部分
https://bbs.elecfans.com/jishu_2315132_1_1.html。
具备了移植NES模拟器的基本要素。
我们现在基于此完成NES模拟器的移植。
代码见
https://github.com/qinyunti/LiteNES.git
过程
移植只需要修改HAL.C文件
实现以下接口
- wait_for_frame
fce_run中循环调用该函数,用该函数延时控制1/FPS(S)执行一次
- nes_set_bg_color 将整个屏幕填充一种颜色
- nes_flush_buf 更新显示缓冲区
- nes_hal_init 初始化
- nes_flip_display 真正将显示缓冲区的内容写入LCD
- nes_key_state获取按键状态
分别对应LCD和KEY的实现
代码见
Lcd.c
#include <stdint.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <linux/fb.h>
#define LCD_TEST 0
static int s_fd = -1;
static uint8_t *s_pfb = 0;
static uint32_t s_len_u32 = 0;
struct fb_fix_screeninfo finfo;
struct fb_var_screeninfo vinfo;
void lcd_clear(uint32_t color);
int lcd_init(char* dev)
{
int ret = -1;
memset(&finfo,0,sizeof(finfo));
memset(&vinfo,0,sizeof(vinfo));
s_fd = open(dev,O_RDWR);
if(s_fd < 0)
{
fprintf(stderr,"open %s err, %s\n",dev,strerror(errno));
return -1;
}
ret = ioctl(s_fd, FBIOGET_FSCREENINFO, &finfo);
if(ret < 0)
{
fprintf(stderr,"ioctl finfo err, %s\n",strerror(errno));
close(s_fd);
return -1;
}
ret = ioctl(s_fd, FBIOGET_VSCREENINFO, &vinfo);
if(ret < 0)
{
fprintf(stderr,"ioctl vinfo err, %s\n",strerror(errno));
close(s_fd);
return -1;
}
vinfo.xoffset = 0;
vinfo.yoffset = 0;
ret = ioctl(s_fd, FBIOPAN_DISPLAY, &vinfo);
if(ret < 0)
{
fprintf(stderr,"ioctl vinfo err, %s\n",strerror(errno));
close(s_fd);
return -1;
}
s_len_u32 = vinfo.xres_virtual * vinfo.yres_virtual * vinfo.bits_per_pixel / 8;
s_pfb = mmap(NULL, s_len_u32, PROT_READ | PROT_WRITE, MAP_SHARED, s_fd, 0);
if(s_pfb == NULL)
{
fprintf(stderr,"mmap len %u err\n",s_len_u32);
close(s_fd);
return -1;
}
fprintf(stderr,"x = %d, y = %d\r\n",vinfo.xres,vinfo.yres);
fprintf(stderr,"vx = %d, vy = %d\r\n",vinfo.xres_virtual,vinfo.yres_virtual);
fprintf(stderr,"ox = %d, oy = %d\r\n",vinfo.xoffset,vinfo.yoffset);
fprintf(stderr,"bits = %d\r\n",vinfo.bits_per_pixel);
fprintf(stderr,"gray = %d\r\n",vinfo.grayscale);
fprintf(stderr,"red offset:%d length:%d msb_right:%d\r\n",vinfo.red.offset,vinfo.red.length,vinfo.red.msb_right);
fprintf(stderr,"green offset:%d length:%d msb_right:%d\r\n",vinfo.green.offset,vinfo.green.length,vinfo.green.msb_right);
fprintf(stderr,"blue offset:%d length:%d msb_right:%d\r\n",vinfo.blue.offset,vinfo.blue.length,vinfo.blue.msb_right);
fprintf(stderr,"transp offset:%d length:%d msb_right:%d\r\n",vinfo.transp.offset,vinfo.transp.length,vinfo.transp.msb_right);
lcd_clear(0x000000);
return 0;
}
void lcd_deinit(void)
{
if(s_pfb != 0)
{
munmap(s_pfb,s_len_u32);
}
if(s_fd > 0)
{
close(s_fd);
}
}
void lcd_setpixel(int index,int x, int y,uint32_t color)
{
uint32_t offset;
if(index == 0)
{
offset=0;
}
else
{
offset = vinfo.xres*vinfo.yres*vinfo.bits_per_pixel/8;
}
if(x>vinfo.xres || y>vinfo.yres)
{
return;
}
if(vinfo.bits_per_pixel == 16)
{
*((uint16_t*)(s_pfb + offset + (y*vinfo.xres + x)*2)) = (uint16_t)color;
}
else
{
*((uint32_t*)(s_pfb + offset + (y*vinfo.xres + x)*4)) = 0xFF000000 | color;
}
}
void lcd_switch(int index)
{
if(index==0)
{
vinfo.yoffset = 0;
}
else
{
vinfo.yoffset = vinfo.yres;
}
ioctl(s_fd, FBIOPAN_DISPLAY, &vinfo);
}
void lcd_clear(uint32_t color)
{
for(int i=0;i<vinfo.xres;i++)
{
for(int j=0;j<vinfo.yres;j++)
{
lcd_setpixel(0,i, j,color);
lcd_setpixel(1,i, j,color);
}
}
}
void lcd_getsize(uint32_t* x,uint32_t* y)
{
*x = vinfo.xres;
*y = vinfo.yres;
}
#if LCD_TEST
int main(int argc, char* argv[])
{
lcd_init(argv[1]);
lcd_clear(0xFFFF0000);
sleep(1);
lcd_clear(0xFF00FF00);
sleep(1);
lcd_clear(0xFF0000FF);
sleep(1);
for(int i=0;i<vinfo.xres;i++)
{
for(int j=0;j<vinfo.yres;j++)
{
lcd_setpixel(i, j, (i<<12 | j));
}
}
}
#endif
Lcd.h
#ifndef LCD_H
#define LCD_H
#include <stdint.h>
int lcd_init(char* dev);
void lcd_clear(uint32_t color);
void lcd_setpixel(int index,int x, int y,uint32_t color);
void lcd_getsize(uint32_t* x,uint32_t* y);
void lcd_switch(int index);
#endif
Key.c
#include<stdint.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include <linux/input.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define KEY_TEST 0
int s_keys_fd = -1;
uint32_t s_keys_state = 0;
void key_setstate(int code , int vaule, uint32_t* key)
{
if(vaule == 0)
{
switch(code)
{
case 296:
*key &= ~(1u<<3);
break;
case 297:
*key &= ~(1u<<4);
break;
case 288:
*key &= ~(1u<<5);
break;
case 289:
*key &= ~(1u<<8);
break;
case 290:
*key &= ~(1u<<6);
break;
case 291:
*key &= ~(1u<<7);
break;
case 292:
*key &= ~(1u<<1);
break;
case 294:
*key &= ~(1u<<2);
break;
default:
break;
}
}
else
{
switch(code)
{
case 296:
*key |= (1u<<3);
break;
case 297:
*key |= (1u<<4);
break;
case 288:
*key |= (1u<<5);
break;
case 289:
*key |= (1u<<8);
break;
case 290:
*key |= (1u<<6);
break;
case 291:
*key |= (1u<<7);
break;
case 292:
*key |= (1u<<1);
break;
case 294:
*key |= (1u<<2);
break;
default:
break;
}
}
}
int key_getstate(int key)
{
if(s_keys_state & (1u<<key))
{
return 1;
}
else
{
return 0;
}
}
void* key_poll(void* arg)
{
char ret[2];
struct input_event t;
s_keys_fd = open((char*)arg, O_RDONLY);
if(s_keys_fd <= 0)
{
printf("open %s device error!\n",(char*)arg);
return 0;
}
while(1)
{
if(read(s_keys_fd, &t, sizeof(t)) == sizeof(t))
{
if(t.type==EV_KEY)
{
if(t.value==0 || t.value==1)
{
key_setstate(t.code, t.value, &s_keys_state);
}
}
else
{
}
}
}
return 0;
}
void key_init(void* arg)
{
pthread_t id;
int res = pthread_create(&id,NULL,key_poll,arg);
if(res !=0 )
{
printf("pthread_create err\r\n");
}
}
#if KEY_TEST
int main(int argc, char* argv[])
{
key_init(argv[1]);
while(1);
}
#endif
Key.h
#ifndef KEY_H
#define KEY_H
void key_init(void* arg);
int key_getstate(int key);
#endif
编译
aarch64-linux-gnu-gcc -I include src/key.c src/lcd.c src/main.c src/hal.c src/fce/*.c -o litenes -lpthread
运行
导入到开发板,chmod +x litenes添加可执行权限
坦克大战
./litenes ./tank.nes /dev/fb0 /dev/input/event8
冒险岛
./litenes ./mxd.nes /dev/fb0 /dev/input/event8
中国象棋
./litenes ./xq.nes /dev/fb0 /dev/input/event8
总结与问题
- 目前现实的双缓冲操作还需要优化,所以显示闪烁。
- 显示区域没有做自适应全屏,所以只能小分辨率显示。