【电子DIY】DIY行车记录仪为游戏机之主控软件开发(11)NES移植
代码见:https://github.com/qinyunti/py32f403-nes.git
一.前言
前面我们已经准备好了文件系统,NES游戏文件的导入导出,TFT显示驱动等各种基础功能,万事俱备只欠东风,现在只剩关键的一步,移植NES游戏模拟器。
二.移植过程
代码见git仓库
主要代码如下
其中nes_main是需要移植的文件
2.1按键移植
根据我们手柄设计,按键信息按照4字节帧发送
即0xAA 0x55 0xXX 0xYY
其中AA55为帧头,XX是8个按键信息对应8位,对应位为1表示未按下按键,为0表示按下按键。按键信息通过蓝牙模块发送,主控这么通过蓝牙模块的串口接收,解析过程如下
Key.c中
#include "uart.h"
#include "xprintf.h"
static uint8_t s_key_buffer[4];
static int s_key_buffer_len = 0;
static int s_key = 0xFF;
uint8_t key_get(void)
{
static uint8_t s_pre_key = 0xFF;
uint8_t tmp;
uint8_t len;
do{
len = uart_read(1,&tmp,1);
s_key_buffer[s_key_buffer_len] = tmp;
s_key_buffer_len++;
if(s_key_buffer_len >= 4){
if((s_key_buffer[0]==0xAA) && (s_key_buffer[1]==0x55) && ((s_key_buffer[2]+s_key_buffer[3])==(uint8_t)255)){
s_key = s_key_buffer[2];
s_key_buffer_len = 0;
}else{
/* 错误帧,丢掉第一个字节 */
s_key_buffer[0] = s_key_buffer[1];
s_key_buffer[1] = s_key_buffer[2];
s_key_buffer[2] = s_key_buffer[3];
s_key_buffer_len = 3;
s_key = 0xFF;
}
}else{
/* 不足帧长 */
}
}while(len > 0);
if(s_pre_key != s_key){
s_pre_key = s_key;
xprintf("key:%02x\r\n",s_key);
}
return s_key;
}
由于原始按键信息和NES对应可能不一致,所以还需要转换下
nes_main.c中
void nes_get_gamepadval(void)
{
uint8_t key;
uint8_t data = 0;
/* key输入值 U D L R 4 5 6 7
* 3 1 2 0 SELECT START B A
* 转为PADdata:[7:0]右7 左6 下5 上4 Start3 Select2 B1 A0
*/
key = key_get();
if((key & ((uint8_t)1<<0)) == 0){
data |= ((uint8_t)1<<7);
}
if((key & ((uint8_t)1<<1)) == 0){
data |= ((uint8_t)1<<5);
}
if((key & ((uint8_t)1<<2)) == 0){
data |= ((uint8_t)1<<6);
}
if((key & ((uint8_t)1<<3)) == 0){
data |= ((uint8_t)1<<4);
}
if((key & ((uint8_t)1<<4)) == 0){
data |= ((uint8_t)1<<2);
}
if((key & ((uint8_t)1<<5)) == 0){
data |= ((uint8_t)1<<3);
}
if((key & ((uint8_t)1<<6)) == 0){
data |= ((uint8_t)1<<1);
}
if((key & ((uint8_t)1<<7)) == 0){
data |= ((uint8_t)1<<0);
}
PADdata=data;
PADdata1=0; //没有手柄2,故不采用.
}
2.2显示移植
nes_ppu.c中
调用我们的显示接口显示一行
void scanline_draw(int LineNo)
{
uint16 i;
u16 sx,ex;
do_scanline_and_draw(ppu->dummy_buffer);
sx=nes_xoff+8;
ex=256+8-nes_xoff;
for(i=sx;i<ex;i++)
{
line_buffer[i-nes_xoff] = ppu->dummy_buffer;
}
st7789_itf_fill_direct(0,ex-sx,LineNo,1,line_buffer);
}
这里需要先查找颜色画板将ppu->dummy_buffer转换为RGB颜色,
同时使用fill接口即一次填充区域替代逐点显示来提高效率。由于内存限制,这里直接使用写TFT,而不是先缓存然后帧同步。
2.3动态内存接口与存储需求
nes_main.c中
实现动态内存分配与释放接口
void* nes_malloc(uint32_t size)
{
void* p = pvPortMalloc(size);
if(p == (void*)0){
xprintf("nes malloc %d err\r\n",size);
}else{
xprintf("nes malloc %d ok\r\n",size);
}
return p;
}
void nes_free(void* p)
{
vPortFree(p);
xprintf("nes free\r\n");
}
以下是需要动态分配的内存
首先最大的部分是游戏文件加载的缓存,这里最大加载25KB的游戏文件。
然后是nes运行需要的各种资源,这里删掉了音频相关apu,因为apu占用资源比较多,这里只有64k ram不够用。
最终分配如下
打印如下
其中552是读文件时分配的FIL,用完释放,其他需要的时24592的游戏缓存,nes运行需要的15328,所以一共大概40字节。另外NES_RAM虽然大小是2K但是需要1k对齐,所以分配了3K。
对的配置大小如下
最终的程序占用ram如下,64KB用完
三.测试
Git仓库中有很多25KB的游戏可以导入,
这里导入了3个,
创建一个任务运行nes
nes_task_init("0:/jtxz.nes");
static void nes_task(void *arg)
{
nes_main(arg);
return;
}
void nes_task_init(char* name)
{
TaskHandle_t xHandle;
xprintf("nes %s\r\n",name);
memset(nesname,0,sizeof(nesname));
memcpy(nesname,name,strlen(name));
xTaskCreate( nes_task, "nes", 512, nesname, 1, &xHandle );
}
void nes_main(char* name)
{
nes_load(name);
nes_play_romfile();
}
详见后续视频
四.总结
以上就完成了整个项目开发,从手柄软硬件到主控软硬件详细分享了整个过程。使用64 KB RAM也可以玩转25KB的NES游戏,git仓库中分享了很多游戏,可以加载尽情玩耍了。虽然使用直接写屏,但是显示帧率也还可以看不到刷屏的感觉。后面就开始整合到行车记录仪,然后去车上试玩了。