在线观看www成人影院-在线观看www日本免费网站-在线观看www视频-在线观看操-欧美18在线-欧美1级

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

基于STM32設(shè)計的掌上游戲機詳細開發(fā)過程

DS小龍哥-嵌入式技術(shù) ? 來源:DS小龍哥-嵌入式技術(shù) ? 作者:DS小龍哥-嵌入式技 ? 2022-02-28 13:35 ? 次閱讀

?

一、環(huán)境與硬件介紹

開發(fā)環(huán)境:keil5

代碼風格: 寄存器風格,沒有采用庫函數(shù),底層代碼全部寄存器方式編寫,運行效率高,注釋清楚。

MCU型號: STM32F103ZET6

開發(fā)板: 正常的一塊STM32開發(fā)板,帶LCD插槽,帶4顆獨立按鍵。

游戲模擬器: NES游戲模擬器

LCD : ALIENTEK的3.5寸屏幕。(屏幕型號不重要,隨便一款都可以的,把屏幕底層驅(qū)動代碼寫好,適配即可)

聲音輸出設(shè)備 : 采用VS1053 (SPI接口,操作方便)

游戲手柄: 支持FC游戲手柄

完成這個掌上游戲機需要使用的硬件設(shè)備不復(fù)雜,如果想要體驗游戲,需要的必備硬件:

1. (必要)STM32F103系列最小系統(tǒng)版一個

2. (必要)LCD屏一塊。 2.8寸就可以了,價格便宜。

3. (非必要)FC游戲手柄一個,驅(qū)動時序很簡單(后面有單獨章節(jié)介紹),支持組合鍵,玩游戲體驗感非常好。

如果不用FC游戲手柄,使用開發(fā)板幾個獨立按鍵也行,只是手感不好。

4. (非必要)VS1053或者其他系列聲卡模塊一個,游戲是有聲音的,要完美的體驗游戲聲卡肯定是要的,不要也可以玩,只是沒有聲音而已。VS1053模塊支持SPI接口控制,時序簡單,驅(qū)動代碼也不復(fù)雜,資料比較多,學(xué)起來,理解起來很容易。

5. (非必要)SD卡一張。主要存儲NES游戲文件,可以動態(tài)加載想要玩的游戲,切換比較方便。

如果沒有SD卡,也想體驗也可以,直接把游戲取模成二進制放在數(shù)組里存放到STM32的FLASH里即可,STM32F103ZET6有512K的FLASH,存放一個游戲完全夠用,加載速度更加快。

6. (非必要) SRAM外部擴展內(nèi)存,如果不需要從SD里加載游戲,就不需要外部內(nèi)存;如果使用SD卡加載游戲,就需要把游戲數(shù)據(jù)從SD卡里讀取出來,然后放在SRAM外部擴展內(nèi)存芯片里。因為STM32F103ZET6本身只有64K內(nèi)存,放不下。

游戲體驗:STM32可以超頻到128M,運行起來還是非常流暢,玩起來的感覺和正常的FC游戲機是一樣的,沒有卡頓,延遲。

游戲模擬器移植的是NES模擬器,開發(fā)過程中,代碼編寫了3個版本:

版本1:精簡版的掌上游戲機,最適合學(xué)習,代碼牽扯很少,只有外設(shè)硬件只用到了LCD而已,最適合學(xué)習,理解代碼運行原理;不支持聲音輸出,不支持FC游戲手柄,不支持SD卡和文件系統(tǒng)(也就是不支持從SD卡上選擇游戲加載)。 這個版本的游戲是直接使用數(shù)組存放在代碼里的,游戲的操作是通過開發(fā)板上的4個按鍵控制(開發(fā)板的4個按鍵,分別控制角色的前進、后退、暫停、跳躍),因為只有4個按鍵,沒有支持組合按鍵,所以體驗起來不是很舒服,控制比較困難,完美體驗還是要繼續(xù)加上FC游戲手柄。

版本2:這也是精簡版的掌上游戲機,在版本1的基礎(chǔ)之上加了VS1053模塊,支持聲音輸出,體驗感要好一點,能聽到游戲聲音。

版本3:這是完整版本的掌上游戲機,加入了FC游戲手柄支持,加入了VS1053聲卡驅(qū)動,加入了SD卡和FATFS文件系統(tǒng),可以正常從SD卡加載指定的游戲運行,體驗非常好。

3個版本的源代碼和NES的游戲集合,在下面的第3章有下載地址。

二、游戲運行效果(超級瑪麗示例)

2.1 超級瑪麗運行截圖

poYBAGDYdXCAWkKMAAAAK8RNs4s030.png

?

三、資料下載地址

3.1 NES游戲集合下載

一共有293款游戲,總有一款適合你。常見的超級瑪麗、魂斗羅、都有包含的。

地址:https://download.csdn.net/download/xiaolong1126626497/20722451

3.2 工程源碼下載

地址:https://download.csdn.net/download/xiaolong1126626497/20973545

poYBAGDYdXCAWkKMAAAAK8RNs4s030.png

?

一共3個版本,它們之間的區(qū)別在第一章已經(jīng)介紹過。

三個都是keil工程,下載下來直接編譯、下載運行體驗。

四、什么是NES ?

NES就是紅白機的游戲,所謂的NES意思是歐美版的紅白機,F(xiàn)C的美版,Nintendo entertainment system(任天堂娛樂系統(tǒng)),而日本的紅白機則叫family computer(FC)。

發(fā)展歷史-來至百度百科
1983年7月15日,由日本任天堂株式會社(原本是生產(chǎn)日式撲克即“花札”)的宮本茂先生領(lǐng)導(dǎo)開發(fā)的一種第三代家用電子游戲機:FC,全稱:Family Computer,也稱作:Famicom;在日本以外的地區(qū)發(fā)售時則被稱為NES,全稱:Nintendo Entertainment System;在中國大陸、臺灣和香港等地,因其外殼為紅白兩色,所以人們俗稱其為“紅白機”,正式進入市場銷售,并于后來取得了巨大成功,由此揭開了家用電子游戲機遍布世界任何角落,電子游戲全球大普及的序幕。

1985年,NES在北美地區(qū)的銷量3300萬臺,比日本地區(qū)高出近一倍, 也占據(jù)了其全球市場份額的一半。 NES在北美首發(fā)時的捆綁游戲《打鴨子》(Duck hunt)總共取得近3000萬套(基本全部來自北美市場)銷量, [6] 這在紅白機游戲中名列第二,僅次于《超級馬力歐》。

1986年,任天堂在美國收3.1億美元,這一年美國游戲產(chǎn)業(yè)的規(guī)模4.3億美元,而在一年前,深陷雅達利沖擊的美國游戲業(yè)的收入僅1億美元。 [7] 1988年發(fā)售的《超級馬力歐兄弟3》(Super Mario Bros. 3)在美國售出700萬套,在日本銷量達400萬,銷售額5.5億美元。

1989年,任天堂的游戲機已占領(lǐng)美國90%和日本95%的市場,任天堂成為游戲界巨無霸。


2003年7月,F(xiàn)C發(fā)售二十周年,任天堂宣布FC游戲機正式停產(chǎn)。至此,F(xiàn)C全世界已累計銷售6000萬部以上。至今中國大陸、臺灣、香港與泰國甚至日本等地仍然在制造FC規(guī)格的兼容品。

任天堂成為了現(xiàn)代游戲產(chǎn)業(yè)的開創(chuàng)者,在很多方面上確立了現(xiàn)代電子游戲的標準。
FC巨大成功使任天堂年純利從1985年開始一直保持5億美元以上 ,其股票成為東京證券交易所績優(yōu)股代名詞,一度超越了3萬日元,市值超松下等企業(yè),很多人都把任天堂成功譽為新時代商業(yè)神話。
任天堂紅白機(FC/NES)發(fā)行于1983年,在日本發(fā)行之后引起了不小的轟動,兩年之后進軍北美市場,更加奠定了任天堂的家用游戲機霸主地位。當人們正需要一個高品質(zhì)的家用游戲機的時候,任天堂拿出了他們的全部家當,首發(fā)的數(shù)款游戲都贏得了玩家的贊譽,超級馬力歐更成為了永遠的經(jīng)典。在那個年代,擁有一臺紅白機應(yīng)該是孩子們最大的夢想了。 根據(jù)外媒的數(shù)據(jù),在1990年30%的美國家庭都擁有NES主機。

五、工程源碼分析: 以精簡版本(1)為例

工程源碼全部采用寄存器代碼風格,基本上每行都有詳細的注釋;雖然STM32支持庫函數(shù)方式開發(fā),效率更加快,但是寄存器方式可以更方便了解CPU底層寄存器的一些配置,對以后在學(xué)習使用其他類型的微處理器是非常有幫助的。

5.1 工程文件布局

poYBAGDYdXCAWkKMAAAAK8RNs4s030.png

?

5.2 主函數(shù)代碼

主函數(shù)里完成LCD屏幕初始化,按鍵初始化,LED燈初始化,串口初始化,F(xiàn)C游戲手柄初始化,默認把LCD屏幕清屏為黑色。

LCD屏采用FSMC驅(qū)動的,把FSMC時序速度配置到最快,達到STM32能支持的最快速度,提高LCD刷屏速度。

初始化完畢最后,調(diào)用了LoadNes函數(shù),完成游戲加載;如果加載失敗,就回到下面執(zhí)行while循環(huán),閃爍LED燈。

代碼如下:

#include "stm32f10x.h"
#include "led.h"
#include "lcd.h"
#include "delay.h"
#include "key.h"
#include "usart.h"
#include 
#include 
#include "joypad.h"

extern u8 LoadNes(u8* pname,u32);


//游戲文件可以通過winhex文件生成C源碼數(shù)組
extern const unsigned char nes_data1[40976];//超級瑪麗游戲的文件
extern const unsigned char nes_data2[262160];//魂斗羅游戲的文件


/*
移植說明:
1. 加入游戲手柄
2. 優(yōu)化了游戲刷新的幀率
3. 加入開發(fā)板本身自帶按鍵控制
*/
int main()
{
	BeepInit();		      //蜂鳴器初始化
	LedInit();             //LED燈初始化 
	UsartInit(USART1,72,115200);
	KeyInit();            //按鍵初始化
	printf("串口工作正常!\r\n");
	LcdInit(); 	 		    //LCD初始化
	//JoypadInit();  		//游戲手柄初始化
	LcdClear(0xFFFF);
	

/*
0000 0000:保留
0000 0001: DATAST保持時間=2個HCLK時鐘周期
0000 0010: DATAST保持時間=3個HCLK時鐘周期
……
1111 1111: DATAST保持時間=256個HCLK時鐘周期(這是復(fù)位后的默認數(shù)值)
0、1、2、3、4、5、6、7、8、9、10、11、12、13、14
*/
	LcdClear(0);
 	
	//開始運行游戲
	LoadNes((unsigned char*)nes_data1,40976);  //超級瑪麗
	//LoadNes((unsigned char*)nes_data2,262160);  //魂斗羅
	while(1)
	{	
		   LED1=!LED1;
		   DelayMs(400);
	}
}

poYBAGDYdXCAWkKMAAAAK8RNs4s030.png

5.3 加載NES游戲:LoadNes函數(shù)介紹

LoadNes函數(shù)原型:

u8 LoadNes(unsigned char* pname,u32 size)
poYBAGDYdXCAWkKMAAAAK8RNs4s030.png

該函數(shù)傳入NES游戲數(shù)據(jù)地址,和游戲數(shù)據(jù)大小進來。

現(xiàn)在這個版本沒有使用SD卡和文件系統(tǒng),游戲的文件數(shù)據(jù)是直接加到代碼里編譯的。

poYBAGDYdXCAWkKMAAAAK8RNs4s030.png

?

這兩個數(shù)組是超級瑪麗和魂斗羅的數(shù)據(jù)。(直接使用打開文件,使用WinHEX軟件打開,全選,右鍵編輯,選擇復(fù)制,選擇C源碼,復(fù)制成數(shù)組形式粘貼到keil里即可)

poYBAGDYdXCAWkKMAAAAK8RNs4s030.png

?

函數(shù)里面主要完成了NES模擬器基本的初始化。

主要完成了STM32超頻配置,配置鎖相環(huán)為16倍,超頻到128MHZ。

超頻配置代碼如下:

/*
函數(shù)功能:頻率設(shè)置
參    數(shù):PLL,倍頻數(shù)
*/
void NesClockSet(u8 PLL)
{
	u8 temp=0;	 
	RCC->CFGR&=0XFFFFFFFC;	//修改時鐘頻率為內(nèi)部8M	   
	RCC->CR&=~0x01000000;  	//PLLOFF  
 	RCC->CFGR&=~(0XF<<18);	//清空原來的設(shè)置
 	PLL-=2;									//抵消2個單位
	RCC->CFGR|=PLL<<18;   	//設(shè)置PLL值 2~16
	RCC->CFGR|=1<<16;	  	  //PLLSRC ON 
	FLASH->ACR|=0x12;	  	  //FLASH 2個延時周期
 	RCC->CR|=0x01000000;  	//PLLON
	while(!(RCC->CR>>25));	//等待PLL鎖定
	RCC->CFGR|=0x02;		    //PLL作為系統(tǒng)時鐘	 
	while(temp!=0x02)    	  //等待PLL作為系統(tǒng)時鐘設(shè)置成功
	{   
		temp=RCC->CFGR>>2;
		temp&=0x03;
	}  
} 
poYBAGDYdXCAWkKMAAAAK8RNs4s030.png

接下來初始化NES游戲模擬器的必要參數(shù),最后調(diào)用NesEmulateFrame函數(shù)進入NES游戲主循環(huán)代碼,開始運行游戲。

LoadNes函數(shù)完整代碼如下:

/*
函數(shù)功能:開始nes游戲
參    數(shù):pname:nes游戲路徑  u32 size 游戲大小
返 回 值:
				0,正常退出
				1,內(nèi)存錯誤
				2,文件錯誤
				3,不支持的map
*/
u8 LoadNes(unsigned char* pname,u32 size)
{
    u8 res=0;   
    res=NesSramMalloc();			//申請內(nèi)存 
    romfile=(u8*)pname;       //游戲源碼地址
    NESrom_crc32=get_crc32(romfile+16,size-16);//獲取CRC32的值	
    res=LoadNesRom();					//加載ROM
    printf("res=%d\r\n",res);	
    NesClockSet(16);          //設(shè)置系統(tǒng)時鐘為128MHZ 16*8
    JoypadInit();             //游戲手柄初始化
    cpu6502_init();						//初始化6502,并復(fù)位	  	 
    Mapper_Init();						//map初始化
    PPU_reset();							//ppu復(fù)位
    apu_init(); 							//apu初始化 
    NesEmulateFrame();		    //進入NES模擬器主循環(huán) 
    return res;
} 
poYBAGDYdXCAWkKMAAAAK8RNs4s030.png

5.3 NES游戲主循環(huán)代碼

poYBAGDYdXCAWkKMAAAAK8RNs4s030.png

?

詳細代碼如下:

//nes模擬器主循環(huán)
void NesEmulateFrame(void)
{  
	u8 nes_frame;
	NesSetWindow();//設(shè)置窗口
	while(1)
	{	
		// LINES 0-239
		PPU_start_frame();
		for(NES_scanline = 0; NES_scanline< 240; NES_scanline++)
		{
			run6502(113*256);
			NES_Mapper->HSync(NES_scanline);
			//掃描一行		  
			if(nes_frame==0)scanline_draw(NES_scanline);
			else do_scanline_and_dont_draw(NES_scanline); 
		}  
		NES_scanline=240;
		run6502(113*256);//運行1線
		NES_Mapper->HSync(NES_scanline); 
		start_vblank(); 
		if(NMI_enabled()) 
		{
			cpunmi=1;
			run6502(7*256);//運行中斷
		}
		NES_Mapper->VSync();
		// LINES 242-261    
		for(NES_scanline=241;NES_scanline<262;NES_scanline++)
		{
			run6502(113*256);	  
			NES_Mapper->HSync(NES_scanline);		  
		}	   
		end_vblank(); 
		NesGetGamepadval();	//每3幀讀取游戲手柄數(shù)據(jù)
		nes_frame++;
		if(nes_frame>NES_SKIP_FRAME)
		{
			nes_frame=0;//跳幀  
		}
	}
}
poYBAGDYdXCAWkKMAAAAK8RNs4s030.png

進來就先調(diào)用了NesSetWindow(void)函數(shù),設(shè)置窗口大小,這里面就調(diào)用了LCD的接口,如果是其他的LCD屏,使用本代碼只需要把這里適配一下即可。

u8 nes_xoff=0;	//顯示在x軸方向的偏移量(實際顯示寬度=256-2*nes_xoff)
//設(shè)置游戲顯示窗口
void NesSetWindow(void)
{	
	u16 lcdwidth,lcdheight;

		lcdwidth=256;
		lcdheight=240; 
		nes_xoff=0;
		LcdSetWindow(32,0,lcdwidth,lcdheight);
		LcdWriteRAM_Prepare();//寫入LCD RAM的準備 	
}

接下來就進入到NES游戲的主循環(huán)代碼,開始循環(huán)一幀一幀的刷出圖像數(shù)據(jù),達到游戲的效果。

設(shè)置窗口大小之后,下面就是從NES游戲數(shù)據(jù)文件里取出顏色數(shù)據(jù),然后for循環(huán)一行一行刷屏即可。

上面的設(shè)置窗口大小的代碼其實并不是必要的,只是當前使用的LCD支持坐標自增(一般LCD都支持的),設(shè)置LCD的窗口范圍之后,連續(xù)給LCD寫數(shù)據(jù),LCD的坐標會自動自增,提高刷屏效率而已。如果你的LCD屏并不支持坐標自增或者你不會寫代碼,也想移植,那完全不用設(shè)置窗口那個函數(shù),你只需要提供一個畫點函數(shù),把for循環(huán)里的刷屏代碼里行掃描改掉就行。

函數(shù)里的這個for循環(huán)就是主要刷出圖像的代碼,如果想要移植到其他LCD屏,主要就改這里,示例代碼如下:

for(NES_scanline = 0; NES_scanline< 240; NES_scanline++)
{
	run6502(113*256);
	NES_Mapper->HSync(NES_scanline);
	//掃描一行		  
	if(nes_frame==0)scanline_draw(NES_scanline);
	else do_scanline_and_dont_draw(NES_scanline); 
} 
poYBAGDYdXCAWkKMAAAAK8RNs4s030.png

里面調(diào)用scanline_draw函數(shù)是按行掃描(也就是一行一行繪制圖像),scanline_draw函數(shù)里面也是一個for循環(huán),細化到每個像素點,按照每個像素點繪制到屏幕上,代碼里的LCD_RAM就是當前LCD屏的地址,因為當前LCD屏采用的是FSMC,這個LCD_RAM就是FSMC地址,向這個地址寫數(shù)據(jù),F(xiàn)SMC就產(chǎn)生8080時序?qū)?shù)據(jù)送給LCD顯示屏,刷新顯示出來。

scanline_draw函數(shù)詳細刷屏代碼如下:

extern u8 nes_xoff;	//顯示在x軸方向的偏移量(實際顯示寬度=256-2*nes_xoff)
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;
	if(lcddev.width==480)
	{
		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]];//得到顏色值 		
		}	
		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++)>
poYBAGDYdXCAWkKMAAAAK8RNs4s030.png

運行完刷屏的for循環(huán)函數(shù),一幀游戲圖像就顯示在LCD上了。

接下來就是掃描按鍵值,完成游戲人物的控制,函數(shù)里調(diào)用了NesGetGamepadval()函數(shù),讀取按鍵值刷新按鍵狀態(tài)。

NesGetGamepadval()函數(shù)代碼如下:

/*
鍵值說明:  

開始鍵:8
選擇建:4
方向右:128
方向左:64
方向上:16
方向下:32

功能鍵上/左:2
功能鍵下/右:1

組合鍵:方向右與

讀取游戲手柄數(shù)據(jù)和功能鍵左 :130

*/

void NesGetGamepadval(void)
{  
	u8 key;
//	PADdata0=GetJoypadKey();	//讀取手柄1的值
	//printf("%d\r\n",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;
}
poYBAGDYdXCAWkKMAAAAK8RNs4s030.png

NES游戲模擬器定義了兩個全局變量,分別記錄游戲手柄1和游戲手柄2的數(shù)據(jù),因為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  
poYBAGDYdXCAWkKMAAAAK8RNs4s030.png

只需要在這個函數(shù)給這兩個全局變量賦予正確的值,游戲人物就可以按照正常的動作畫面出現(xiàn)。

至于你的物理按鍵采用FC游戲手柄,還是普通的其他按鍵,只要這兩個全局變量的值正確那就沒問題。 所有手柄采用什么不重要,關(guān)鍵把代碼這里邏輯看懂,看懂了你就知道程序的運行邏輯了。

到此,版本1的 主要代碼就分析完畢了,其他的詳細過程可以看工程源碼,把程序跑起來了,一切都懂了。

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

這個版本加入了游戲手柄,VS1053、SD、FATFS文件系統(tǒng)等功能,這里接著第五章分析,下面就主要分析新加入的代碼內(nèi)容。

poYBAGDYdXCAWkKMAAAAK8RNs4s030.png

?

6.1 FC游戲手柄介紹

FC游戲手柄,大致可分為兩種:一種手柄插口是 11 針的,一種是 9 針的。但 11 針的現(xiàn)在市面上很少了,現(xiàn)在幾乎都是使用 9 針 FC 組裝手柄,下面就是介紹的是 9 針 FC 手柄,該手柄還有一個特點,就是可以直接和DR9 的串口頭對插!這樣同開發(fā)板的連接就簡單了。

FC 手柄的外觀如圖所示:

poYBAGDYdXCAWkKMAAAAK8RNs4s030.png

?

這種手柄一般有 10 個按鍵(實際是 8 個鍵值):上、下、左、右、 Start、 Select、 A、 B、 A連發(fā)、 B 連發(fā)。這里的 A 和 A 連發(fā)是一個鍵值,而 B 和 B 連發(fā)也是一個鍵值,只是連發(fā)按鍵當你一直按下的時候,會不停的發(fā)送(方便快速按鍵,比如發(fā)炮彈之類的功能)。

FC 手柄的控制電路,由 1 個 8 位并入串出的移位寄存器(CD4021),外加一個時基集成電路(NE555,用于連發(fā))構(gòu)成。不過現(xiàn)在的手柄,為了節(jié)約成本,直接就在 PCB 上做綁定了,所以你拆開手柄,一般是看不到里面有四四方方的 IC,而只有一個黑色的小點,所有電路都集成到這個里面了,但是他們的控制和讀取方法還是一樣的。

游戲上手柄數(shù)據(jù)讀取時序

poYBAGDYdXCAWkKMAAAAK8RNs4s030.png

?

從上圖可看出,讀取手柄按鍵值的信息十分簡單:先 Latch(鎖存鍵值),然后就得到了第一個按鍵值(A),之后在 Clock 的作用下,依次讀取其他按鍵的鍵值,總共 8 個按鍵鍵值。

常規(guī)狀態(tài)下,LATCH為低電平,CLK為高電平,DATA為高電平,這也是初始化端口時的狀態(tài)。

單片機讀取鍵值時序很簡單,LATCH先發(fā)送一個高脈沖,數(shù)據(jù)將鎖存到手柄內(nèi)部的移位寄存器,然后在CLK時鐘下降沿數(shù)據(jù)將從DATA低位在先連續(xù)發(fā)出。按鍵映射到數(shù)據(jù)的對應(yīng)位上,有鍵按下則對應(yīng)位為0無鍵按下則為1.即不按任何鍵時,讀取數(shù)據(jù)為0xFF。

鍵值:

[7]:右

[6]:左

[5]:下

[4]:上

[3]:Start

[2]:Select

[1]:B

[0]:A

驅(qū)動代碼示例:

功    能:手柄初始化函數(shù)
硬件連接:
         CLK :PD3  --時鐘線
				 PB10:DATA --數(shù)據(jù)線
				 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;
}

/*
功  能:獲取手柄的按鍵值
返回值:保存了一幀按鍵的狀態(tài)
鍵值:
[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; //鎖存當前的按鍵狀態(tài)
	  for(i=0;i<8;i++)
	  {
			 key=key>>1;
		   if(JOYPAD_DATA==0)key|=0x80;
			 JOYPAD_CLK=1;  //輸出一個上升沿,告訴手柄發(fā)送數(shù)據(jù)
			 DelayUs(30);
			 JOYPAD_CLK=0;  //數(shù)據(jù)線保持穩(wěn)定
       DelayUs(30);			
		}
		return key;
}	
poYBAGDYdXCAWkKMAAAAK8RNs4s030.png

6.2 加載NES游戲:nes_load函數(shù)

這里的nes_load函數(shù)和第五章的區(qū)別就是,游戲數(shù)據(jù)的來源是從SD卡讀取的。

傳入游戲名稱去SD卡上打開指定文件,讀取數(shù)據(jù)進來。

這里用到了外部SRAM內(nèi)存,因為讀出的數(shù)據(jù)需要存放到數(shù)組里,STM32F103ZET6本身的內(nèi)存只有64K,肯定不夠用,這里申請的空間是從外部SRAM模塊里申請的,所以開發(fā)板還得帶一個SRAM芯片才行,沒有自帶就去淘寶買一個SRAM模塊即可(淘寶有個叫微雪的店鋪就有賣)。

poYBAGDYdXCAWkKMAAAAK8RNs4s030.png

?

詳細代碼如下:

u8 nes_load(u8* pname)
{
	FIL *file; 
	UINT br;
	u8 res=0;   
	file=malloc(sizeof(FIL));  
	if(file==0)return 1;						//內(nèi)存申請失敗.  
	res=f_open(file,(char*)pname,FA_READ);
	if(res!=FR_OK)	//打開文件失敗
	{
		printf("%s 文件打開失敗!\r\n",pname);
		free(file);
		return 2;
	}
	else
	{
			printf("%s 文件打開成功!\r\n",pname);
	}
	
	res=nes_sram_malloc(file->fsize);			//申請內(nèi)存 
	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,并復(fù)位	  	 
			Mapper_Init();						//map初始化
			PPU_reset();							//ppu復(fù)位
			apu_init(); 							//apu初始化 
			nes_sound_open(0,APU_SAMPLE_RATE);	//初始化播放設(shè)備
			nes_emulate_frame();				//進入NES模擬器主循環(huán) 
			nes_sound_close();					//關(guān)閉聲音輸出
		}
	}
	f_close(file);
	free(file);//釋放內(nèi)存
	nes_sram_free();	//釋放內(nèi)存
	return res;
} 
poYBAGDYdXCAWkKMAAAAK8RNs4s030.png

這里面調(diào)用了nes_sound_open函數(shù)初始化了音頻設(shè)備(VS1053)。這個非常重要,要理解游戲聲音是如何輸出的,就認真看這里的流程。

nes_sound_open函數(shù)里初始化了VS1053音頻設(shè)備,然后開啟了定時器中斷,使用定時器去調(diào)用VS1053的播放接口,在定時器中斷服務(wù)器函數(shù)里完成聲音數(shù)據(jù)的輸出,這里聲音是存放在一個全局緩沖區(qū)里,后面游戲在主循環(huán)里運行的時候會不斷的向這個緩沖區(qū)填數(shù)據(jù),定時器超時進中斷就查詢是否有音樂可以播放,有就播放,沒有就出來。

poYBAGDYdXCAWkKMAAAAK8RNs4s030.png

?

VS1052聲音播放代碼示例:

//音頻播放回調(diào)函數(shù)
void nes_vs10xx_feeddata(void)
{  
	u8 n;
	u8 nbytes;
	u8 *p; 
	if(nesplaybuf==nessavebuf)return;//還沒有收到新的音頻數(shù)據(jù)
	if(VS1053_DREQ!=0)//可以發(fā)送數(shù)據(jù)給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函數(shù)代碼如下:

//NES打開音頻輸出
int nes_sound_open(int samples_per_sync,int sample_rate) 
{
	u8 *p;
	u8 i; 
	p=malloc(100);	//申請100字節(jié)內(nèi)存
	if(p==NULL)return 1;	//內(nèi)存申請失敗,直接退出
	printf("sound open:%d\r\n",sample_rate);
	for(i=0;i>8)&0XFF;
	p[28]=sample_rate&0XFF;			//設(shè)置字節(jié)速率(8位模式,等于采樣率)
	p[29]=(sample_rate>>8)&0XFF; 
	nesplaybuf=0;
	nessavebuf=0;	
	VS1053_Reset();		   			//硬復(fù)位
	VS1053_SoftReset();  			//軟復(fù)位 
	VS1053_SetVol(200);			  //設(shè)置音量等參數(shù) 			 

	//復(fù)位解碼時間
    VS1053_WriteCmd(SPI_DECODE_TIME,0x0000);
	VS1053_WriteCmd(SPI_DECODE_TIME,0x0000); //操作兩次
	
	while(VS1053_SendMusicData(p));	//發(fā)送wav head
	while(VS1053_SendMusicData(p+32));	//發(fā)送wav head
	TimerInit(TIM6,72,1000);	//1ms中斷一次
	free(p);				//釋放內(nèi)存
	return 1;
}(nes_wav_head);i++)>
poYBAGDYdXCAWkKMAAAAK8RNs4s030.png

初始化完畢之后,就調(diào)用nes_emulate_frame函數(shù)進入到游戲主循環(huán)。

6.3 游戲主循環(huán)代碼

現(xiàn)在這份代碼比第五章代碼增加了一個聲音輸出函數(shù),調(diào)用VS1053,播放游戲的聲音。

?

apu_soundoutput函數(shù)代碼如下:

//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個數(shù)據(jù),是不是都相等?
	if(i==30&&wave_buffers[i])//都相等,且不等于0
	{
		for(i=0;i;i++)wave_buffers[i]=0;>
poYBAGDYdXCAWkKMAAAAK8RNs4s030.png

最后調(diào)用了nes_apu_fill_buffer 函數(shù)將數(shù)據(jù)賦值給VS1053緩沖區(qū)進行播放。

在前面已經(jīng)分析了音頻初始化代碼,里面初始化了定時器,會不斷的查詢緩沖區(qū)是否有音樂數(shù)據(jù)需要播放,有就播放,沒有就輸出,這個函數(shù)就是向音頻緩沖區(qū)填充數(shù)據(jù)的。

nes_apu_fill_buffer 函數(shù)代碼如下:

//NES音頻輸出到VS1053緩存
void nes_apu_fill_buffer(int samples,u8* wavebuf)
{	 
 	u16	i;	
	u8 tbuf;
	for(i=0;i(NES_APU_BUF_NUM-1))tbuf=0;
	while(tbuf==nesplaybuf)//輸出數(shù)據(jù)趕上音頻播放的位置了,等待.
	{ 
		DelayMs(5);
	}
	nessavebuf=tbuf; 
}	;i++)>
poYBAGDYdXCAWkKMAAAAK8RNs4s030.png

到此,音頻的主要代碼就分析完畢了。 可以下載程序去體驗一下游戲,懷戀童年時光了

?審核編輯:符乾江

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • STM32
    +關(guān)注

    關(guān)注

    2270

    文章

    10900

    瀏覽量

    356046
  • 游戲機
    +關(guān)注

    關(guān)注

    9

    文章

    299

    瀏覽量

    33445
收藏 人收藏

    評論

    相關(guān)推薦

    HAL庫在STM32開發(fā)中的重要性

    HAL庫(Hardware Abstraction Layer Library,硬件抽象層庫)在STM32開發(fā)中扮演著至關(guān)重要的角色。以下是HAL庫在STM32開發(fā)中的重要性分析: 一
    的頭像 發(fā)表于 12-02 13:35 ?336次閱讀

    索尼PS5 Pro游戲機震撼發(fā)布

    游戲界的萬眾矚目下,索尼于近日舉行的PS5技術(shù)展示會上,由首席架構(gòu)師Mark Cerny親自揭曉了萬眾期待的PS5 Pro游戲機。這款全新力作以699美元的定價,定于11月7日盛大發(fā)售,無疑為游戲愛好者們帶來了前所未有的震撼與
    的頭像 發(fā)表于 09-11 16:59 ?703次閱讀

    基于 FPGA 的飛機大戰(zhàn)游戲系統(tǒng)設(shè)計

    喜歡老式電子游戲的買家。一些愛好者一直在收集復(fù)古游戲產(chǎn)品,一些普通玩家也開始收集舊式磁帶和CD,還有小時候玩過的游戲機。 雖然復(fù)古游戲只占全球 1090 億美金
    發(fā)表于 07-24 20:03

    微軟開發(fā)者成功在任天堂 NES 游戲機上運行.NET

    游戲機被譽為“紅白”,搭載 1.78 MHz 的 8 位 CPU,內(nèi)存僅有 2KB,游戲卡帶最大容量可達 512 Kilobytes。然而,這樣的設(shè)備如今運行安卓/iOS app 明顯吃力,因為現(xiàn)今的主流應(yīng)用均大于 55-2
    的頭像 發(fā)表于 05-30 14:36 ?459次閱讀

    索尼或?qū)⒂诮诠糚S5 Pro游戲機及《宇宙機器人》新作發(fā)布日期

    5月份,Jeff Grubb曾透露索尼計劃近期發(fā)布PS5 Pro游戲主機,而近期揭示的眾多信息中亦可見到這個發(fā)布會的痕跡,甚至傳言《宇宙機器人》將作為新的作品亮相,進一步暗示索尼可能在此次發(fā)布會上推出PS5 Pro游戲機。
    的頭像 發(fā)表于 05-28 15:56 ?595次閱讀

    YXC可編程振蕩器,頻點22.578MHz,工作電壓3.3V,應(yīng)用于游戲機

    游戲機是一種專門用于游戲運行的電子設(shè)備,它通過外界載體(如光盤、卡帶等)來運行游戲。與電腦和手機等多功能設(shè)備不同,游戲機專注于游戲體驗,具有
    的頭像 發(fā)表于 05-23 17:43 ?298次閱讀
    YXC可編程振蕩器,頻點22.578MHz,工作電壓3.3V,應(yīng)用于<b class='flag-5'>游戲機</b>

    任天堂Switch初代游戲機模擬運行Windows 11 ARM效果展示

    5 月 14 日,某開發(fā)者PatRyk在X平臺發(fā)布帖子,展示了將任天堂Switch初代游戲機在Linux環(huán)境中通過QEMU模擬Windows 11 ARM系統(tǒng)的效果。
    的頭像 發(fā)表于 05-14 10:16 ?863次閱讀

    Funkey游戲機新作,基于全志T113的全新版本

    目名稱: T113-S3-FunKeys過往項目:V3s掌項目組說: 雙核FunKey掌的發(fā)布不僅是我們團隊的努力成果,更是開源社區(qū)的共同成就。我們鼓勵更多的開發(fā)者加入到我們的項目中來,共同探索
    發(fā)表于 05-11 11:04

    OrangePi?Neo:好玩不貴,最具性價比的游戲來了!

    3月24日,香橙派在深圳發(fā)布了備受期待的OrangePiNeo游戲。據(jù)稱,這款游戲售價4099元起,將為用戶帶來強大的性能和豐富的游戲
    的頭像 發(fā)表于 04-11 17:28 ?628次閱讀
    OrangePi?Neo:好玩不貴,最具性價比的<b class='flag-5'>游戲</b>掌<b class='flag-5'>機</b>來了!

    索尼PSV及PSC游戲機售后服務(wù)終止

    據(jù)悉,PlayStation Vita這一款由索尼計算機娛樂有限公司創(chuàng)新研發(fā)的掌上電腦,通稱為PSV,其名字VITA源于拉丁文中代表生命的意思。
    的頭像 發(fā)表于 03-28 10:43 ?776次閱讀

    任天堂計劃2025年3月發(fā)布Switch 2游戲機

    根據(jù)推測,這款新游戲機或擁有類似原有型號Switch的手持特性,且配備尺寸較大的屏幕。盡管大部分業(yè)內(nèi)人士預(yù)期新機即刻上市,但任天堂決定先進行銷售規(guī)劃及為開發(fā)者預(yù)留足夠的時間來制作受歡迎的游戲,從而達到簡化銷售
    的頭像 發(fā)表于 02-27 16:45 ?3043次閱讀

    Stages—研發(fā)過程可視化建模和管理平臺

    Stages是美國UL Solutions旗下UL Method Park GmbH的產(chǎn)品,用于幫助企業(yè)定義、管理、發(fā)布、控制、優(yōu)化其研發(fā)過程,同時使其研發(fā)過程符合CMMI、ASPICE
    的頭像 發(fā)表于 02-05 14:36 ?398次閱讀
    Stages—研<b class='flag-5'>發(fā)過程</b>可視化建模和管理平臺

    氮化鎵芯片研發(fā)過程

    氮化鎵芯片(GaN芯片)是一種新型的半導(dǎo)體材料,在目前的電子設(shè)備中逐漸得到應(yīng)用。它以其優(yōu)異的性能和特點備受研究人員的關(guān)注和追捧。在現(xiàn)代科技的進步中,氮化鎵芯片的研發(fā)過程至關(guān)重要。下面將詳細介紹氮化鎵
    的頭像 發(fā)表于 01-10 10:11 ?1055次閱讀

    家用游戲機的控制器接口類型

    家用游戲機的控制器接口類型是游戲機和手柄之間進行連接和通信的關(guān)鍵部分。它決定了用戶如何與游戲機進行交互,并直接影響到游戲體驗的質(zhì)量和多樣性。本文將
    的頭像 發(fā)表于 01-04 11:23 ?1467次閱讀

    應(yīng)用在游戲機觸摸屏中的觸摸感應(yīng)芯片

    觸屏游戲機通常由屏幕、主板、處理器、內(nèi)存、電源、按鍵、觸控器器等組成。其中,觸控器器是實現(xiàn)屏幕觸控功能的重要組成部分。
    的頭像 發(fā)表于 01-03 09:32 ?893次閱讀
    應(yīng)用在<b class='flag-5'>游戲機</b>觸摸屏中的觸摸感應(yīng)芯片
    主站蜘蛛池模板: 四虎中文| 韩国三级视频在线| videossexotv极度另类高清| 九九热精品在线| 99久久国产免费 - 99久久国产免费| 人人澡人| 天天干夜夜做| 日本不卡视频在线视频观看| 日韩高清一级| 午夜影剧院| 清朝荒淫牲艳史在线播放| 日韩视频 中文字幕 视频一区| 99久久99久久久99精品齐| 最近高清免费观看视频| 婷婷色站| 夜夜春夜夜爽| 天天综合网网欲色| 免费能看的黄色网址| 欧美射射射| 伊人久久大香线蕉综合网站 | 人人舔| 午夜久久免费视频| 男男gay污小黄文| 激情天堂| 欧美一区色| 人人插人人爱| 酒色网址| 五月婷婷六月婷婷| 亚洲成人午夜影院| 国产无套粉嫩白浆| 国产经典三级在线| 91大神免费视频| 免费国产黄网站在线观看视频| 嘿嘿午夜| 四虎影院黄色| 日本在线黄色网址| 午夜爱爱网站| 天天操天天干天天舔| 曰本三级香港三级人妇99视频| www.87福利| 男女交性视频免费播放|