基于STM32设计的掌上游戏机详细开发过程

i++;

LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值

 LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值

i++;

LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值

 LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值

i++;

LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值

 LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值

i++;

LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值

 LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值

}

for(i=sx;idummy_buffer[i]];//得到颜色值

 LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值

i++;

 LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值

 LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值

i++;

LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值

 LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值

i++;

LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值

 LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值

i++;

LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值

 LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值

i++;

LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值

 LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值

i++;

LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值

 LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值

i++;

LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值

 LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值

i++;

LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值

 LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值

i++;

 LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值

 LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值

i++;

LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值

 LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值

i++;

LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值

 LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值

i++;

LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值

 LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值

i++;

LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值

 LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值

i++;

LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值

 LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值

i++;

LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值

 LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值

}

}else

{

for(i=sx;idummy_buffer[i++]]; 

LCD_RAM=NES_Palette[ppu->dummy_buffer[i++]]; 

LCD_RAM=NES_Palette[ppu->dummy_buffer[i++]]; 

LCD_RAM=NES_Palette[ppu->dummy_buffer[i++]]; 

LCD_RAM=NES_Palette[ppu->dummy_buffer[i++]]; 

LCD_RAM=NES_Palette[ppu->dummy_buffer[i++]]; 

LCD_RAM=NES_Palette[ppu->dummy_buffer[i++]]; 

LCD_RAM=NES_Palette[ppu->dummy_buffer[i++]]; 

LCD_RAM=NES_Palette[ppu->dummy_buffer[i++]]; 

LCD_RAM=NES_Palette[ppu->dummy_buffer[i++]]; 

LCD_RAM=NES_Palette[ppu->dummy_buffer[i++]]; 

LCD_RAM=NES_Palette[ppu->dummy_buffer[i++]]; 

LCD_RAM=NES_Palette[ppu->dummy_buffer[i++]]; 

LCD_RAM=NES_Palette[ppu->dummy_buffer[i++]]; 

LCD_RAM=NES_Palette[ppu->dummy_buffer[i++]]; 

LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];         

}

}

};i++)>;i++)>;i++)>

运行完刷屏的for循环函数,一帧游戏图像就显示在LCD上了。

接下来就是扫描按键值,完成游戏人物的控制,函数里调用了NesGetGamepadval()函数,读取按键值刷新按键状态。

NesGetGamepadval()函数代码如下:

/*

键值说明:  

开始键:8

选择建:4

方向右:128

方向左:64

方向上:16

方向下:32

功能键上/左:2

功能键下/右:1

组合键:方向右与

读取游戏手柄数据和功能键左 :130

*/

void NesGetGamepadval(void)

{  

u8 key;

//PADdata0=GetJoypadKey();//读取手柄1的值

//printf("%drn",PADdata0);

key=GetKeyValue(0);

if(key==1)PADdata0=8;

else if(key==2)PADdata0=128;

else if(key==3)PADdata0=64;

else if(key==4)PADdata0=1;

else PADdata0=0;

}

NES游戏模拟器定义了两个全局变量,分别记录游戏手柄1和游戏手柄2的数据,因为NES游戏是可以两个人一起玩的。

u8 PADdata0;   //手柄1键值 [7:0]右7 左6 下5 上4 Start3 Select2 B1 A0  

u8 PADdata1;   //手柄2键值 [7:0]右7 左6 下5 上4 Start3 Select2 B1 A0  

只需要在这个函数给这两个全局变量赋予正确的值,游戏人物就可以按照正常的动作画面出现。

至于你的物理按键采用FC游戏手柄,还是普通的其他按键,只要这两个全局变量的值正确那就没问题。 所有手柄采用什么不重要,关键把代码这里逻辑看懂,看懂了你就知道程序的运行逻辑了。

到此,版本1的 主要代码就分析完毕了,其他的详细过程可以看工程源码,把程序跑起来了,一切都懂了。

六、工程源码分析: 以完整版本(3)为例

这个版本加入了游戏手柄,VS1053、SD、FATFS文件系统等功能,这里接着第五章分析,下面就主要分析新加入的代码内容。

6.1 FC游戏手柄介绍

FC游戏手柄,大致可分为两种:一种手柄插口是 11 针的,一种是 9 针的。但 11 针的现在市面上很少了,现在几乎都是使用 9 针 FC 组装手柄,下面就是介绍的是 9 针 FC 手柄,该手柄还有一个特点,就是可以直接和DR9 的串口头对插!这样同开发板的连接就简单了。

FC 手柄的外观如图所示:

这种手柄一般有 10 个按键(实际是 8 个键值):上、下、左、右、 Start、 Select、 A、 B、 A连发、 B 连发。这里的 A 和 A 连发是一个键值,而 B 和 B 连发也是一个键值,只是连发按键当你一直按下的时候,会不停的发送(方便快速按键,比如发炮弹之类的功能)。

FC 手柄的控制电路,由 1 个 8 位并入串出的移位寄存器(CD4021),外加一个时基集成电路(NE555,用于连发)构成。不过现在的手柄,为了节约成本,直接就在 PCB 上做绑定了,所以你拆开手柄,一般是看不到里面有四四方方的 IC,而只有一个黑色的小点,所有电路都集成到这个里面了,但是他们的控制和读取方法还是一样的。

游戏上手柄数据读取时序

从上图可看出,读取手柄按键值的信息十分简单:先 Latch(锁存键值),然后就得到了第一个按键值(A),之后在 Clock 的作用下,依次读取其他按键的键值,总共 8 个按键键值。

常规状态下,LATCH为低电平,CLK为高电平,DATA为高电平,这也是初始化端口时的状态。

单片机读取键值时序很简单,LATCH先发送一个高脉冲,数据将锁存到手柄内部的移位寄存器,然后在CLK时钟下降沿数据将从DATA低位在先连续发出。按键映射到数据的对应位上,有键按下则对应位为0,无键按下则为1.即不按任何键时,读取数据为0xFF。

键值:

[7]:右

[6]:左

[5]:下

[4]:上

[3]:Start

[2]:Select

[1]:B

[0]:A

驱动代码示例:

功    能:手柄初始化函数

硬件连接:

         CLK :PD3  --时钟线

PB10:DATA --数据线

PB11:LAT  --锁存接口

*/

void JoypadInit(void)

{

  /*1. 开时钟*/

  RCC->APB2ENR|=1<<5; //PD

  RCC->APB2ENR|=1<<3; //PB

  /*2. 配置模式*/

  GPIOD->CRL&=0xFFFF0FFF;

  GPIOD->CRL|=0x00003000;

  GPIOB->CRH&=0xFFFF00FF;

  GPIOB->CRH|=0x00003800;

  /*3. 上拉*/

  GPIOD->ODR|=1<<3;

}

/*

功  能:获取手柄的按键值

返回值:保存了一帧按键的状态

键值:

[7]:右

[6]:左

[5]:下

[4]:上

[3]:Start

[2]:Select

[1]:B

[0]:A

*/

u8 GetJoypadKey(void)

{

  u8 key=0,i;

  JOYPAD_LAT=1; //开始锁存

  DelayUs(30);

  JOYPAD_LAT=0; //锁存当前的按键状态

  for(i=0;i<8;i++)

  {

key=key>>1;

   if(JOYPAD_DATA==0)key|=0x80;

JOYPAD_CLK=1;  //输出一个上升沿,告诉手柄发送数据

DelayUs(30);

JOYPAD_CLK=0;  //数据线保持稳定

       DelayUs(30);

}

return key;

}

6.2 加载NES游戏:nes_load函数

这里的nes_load函数和第五章的区别就是,游戏数据的来源是从SD卡读取的。

传入游戏名称去SD卡上打开指定文件,读取数据进来。

这里用到了外部SRAM内存,因为读出的数据需要存放到数组里,STM32F103ZET6本身的内存只有64K,肯定不够用,这里申请的空间是从外部SRAM模块里申请的,所以开发板还得带一个SRAM芯片才行,没有自带就去淘宝买一个SRAM模块即可(淘宝有个叫微雪的店铺就有卖)。

详细代码如下:

u8 nes_load(u8* pname)

{

FIL *file; 

UINT br;

u8 res=0;   

file=malloc(sizeof(FIL));  

if(file==0)return 1;//内存申请失败.  

res=f_open(file,(char*)pname,FA_READ);

if(res!=FR_OK)//打开文件失败

{

printf("%s 文件打开失败!rn",pname);

free(file);

return 2;

}

else

{

printf("%s 文件打开成功!rn",pname);

}

res=nes_sram_malloc(file->fsize);//申请内存 

if(res==0)

{

f_read(file,romfile,file->fsize,&br);//读取nes文件

NESrom_crc32=get_crc32(romfile+16, file->fsize-16);//获取CRC32的值

res=nes_load_rom();//加载ROM

if(res==0)

{   

NesClockSet(16);

//UsartInit(USART1,128,115200);

JoypadInit();

cpu6502_init();//初始化6502,并复位  

Mapper_Init();//map初始化

PPU_reset();//ppu复位

apu_init();//apu初始化 

nes_sound_open(0,APU_SAMPLE_RATE);//初始化播放设备

nes_emulate_frame();//进入NES模拟器主循环 

nes_sound_close();//关闭声音输出

}

}

f_close(file);

free(file);//释放内存

nes_sram_free();//释放内存

return res;

这里面调用了nes_sound_open函数初始化了音频设备(VS1053)。这个非常重要,要理解游戏声音是如何输出的,就认真看这里的流程。

nes_sound_open函数里初始化了VS1053音频设备,然后开启了定时器中断,使用定时器去调用VS1053的播放接口,在定时器中断服务器函数里完成声音数据的输出,这里声音是存放在一个全局缓冲区里,后面游戏在主循环里运行的时候会不断的向这个缓冲区填数据,定时器超时进中断就查询是否有音乐可以播放,有就播放,没有就出来。

VS1052声音播放代码示例:

//音频播放回调函数

void nes_vs10xx_feeddata(void)

{  

u8 n;

u8 nbytes;

u8 *p; 

if(nesplaybuf==nessavebuf)return;//还没有收到新的音频数据

if(VS1053_DREQ!=0)//可以发送数据给VS10XX

p=nesapusbuf[nesplaybuf]+nesbufpos; 

nesbufpos+=32; 

if(nesbufpos>APU_PCMBUF_SIZE)

{

nesplaybuf++;

if(nesplaybuf>(NES_APU_BUF_NUM-1))nesplaybuf=0;

nbytes=APU_PCMBUF_SIZE+32-nesbufpos;

nesbufpos=0; 

}else nbytes=32;

for(n=0;n;n++)>

poYBAGDYdXCAWkKMAAAAK8RNs4s030.png

nes_sound_open函数代码如下:

//NES打开音频输出

int nes_sound_open(int samples_per_sync,int sample_rate) 

{

u8 *p;

u8 i; 

p=malloc(100);//申请100字节内存

if(p==NULL)return 1;//内存申请失败,直接退出

printf("sound open:%drn",sample_rate);

for(i=0;i>8)&0XFF;

p[28]=sample_rate&0XFF;//设置字节速率(8位模式,等于采样率)

p[29]=(sample_rate>>8)&0XFF; 

nesplaybuf=0;

nessavebuf=0;

VS1053_Reset();   //硬复位

VS1053_SoftReset(); //软复位 

VS1053_SetVol(200);  //设置音量等参数 

//复位解码时间

    VS1053_WriteCmd(SPI_DECODE_TIME,0x0000);

VS1053_WriteCmd(SPI_DECODE_TIME,0x0000); //操作两次

while(VS1053_SendMusicData(p));//发送wav head

while(VS1053_SendMusicData(p+32));//发送wav head

TimerInit(TIM6,72,1000);//1ms中断一次

free(p);//释放内存

return 1;

}(nes_wav_head);i++)>

初始化完毕之后,就调用nes_emulate_frame函数进入到游戏主循环。

6.3 游戏主循环代码

现在这份代码比第五章代码增加了一个声音输出函数,调用VS1053,播放游戏的声音。

apu_soundoutput函数代码如下:

//apu声音输出

void apu_soundoutput(void)          

u16 i;

apu_process(wave_buffers,APU_PCMBUF_SIZE);

for(i=0;i<30;i++)if(wave_buffers[i]!=wave_buffers[i+1])break;//判断前30个数据,是不是都相等?

if(i==30&&wave_buffers[i])//都相等,且不等于0

{

for(i=0;i;i++)wave_buffers[i]=0;>

最后调用了nes_apu_fill_buffer 函数将数据赋值给VS1053缓冲区进行播放。

在前面已经分析了音频初始化代码,里面初始化了定时器,会不断的查询缓冲区是否有音乐数据需要播放,有就播放,没有就输出,这个函数就是向音频缓冲区填充数据的。

nes_apu_fill_buffer 函数代码如下:

//NES音频输出到VS1053缓存

void nes_apu_fill_buffer(int samples,u8* wavebuf)

 u16i;

u8 tbuf;

for(i=0;i(NES_APU_BUF_NUM-1))tbuf=0;

while(tbuf==nesplaybuf)//输出数据赶上音频播放的位置了,等待.

DelayMs(5);

}

nessavebuf=tbuf; 

};i++)>

到此,音频的主要代码就分析完毕了。 可以下载程序去体验一下游戏,怀恋童年时光了

关键字:STM32  掌上游戏机  开发过程引用地址:基于STM32设计的掌上游戏机详细开发过程声明:本文内容及配图由平台用户或入驻媒体撰写。文章观点仅代表作者本人,不代表EEWorld网站立场。文章及其配图仅供工程师学习之用,如有内容侵权或违规,请联系本站处理,邮箱地址:bbs_service@eeworld.com.cn

相关知识

掌上游戏机
2024 年值得关注的即将推出的掌上游戏机
掌上游戏机 游戏
基于手持移动设备的教学游戏设计与开发
百元内掌上游戏机,怀旧与现代的完美结合
2024年掌上游戏机十大品牌排行榜
好玩的掌上游戏机
掌上游戏机哪个好 掌上游戏机选购指南
乐信旗下的分期乐商城产品测评之掌上游戏机
任天堂掌上游戏机3DS使用说明

网址: 基于STM32设计的掌上游戏机详细开发过程 http://www.hyxgl.com/newsview343042.html

推荐资讯