////////////////////////////////////////////////////////////////////////////////// // 本程序移植自网友ye781205的NES模拟器工程 // ALIENTEK STM32F407开发板 // NES主函数 代码 // 正点原子@ALIENTEK // 技术论坛:www.openedv.com // 创建日期:2014/7/1 // 版本:V1.0 ////////////////////////////////////////////////////////////////////////////////// #include "nes_main.h" #include "dac.h" #include "main.h" #include "mymem.h" #include "nes_apu.h" #include "nes_mapper.h" #include "nes_ppu.h" #include "stm32f4xx.h" #include "ff.h" #include "string.h" uint8_t nes_frame_cnt; // nes帧计数器 int MapperNo; // map编号 int NES_scanline; // nes扫描线 int VROM_1K_SIZE; int VROM_8K_SIZE; uint8_t PADdata; // 手柄1键值 [7:0]右7 左6 下5 上4 Start3 Select2 B1 A0 uint8_t PADdata1; // 手柄2键值 [7:0]右7 左6 下5 上4 Start3 Select2 B1 A0 uint8_t *NES_RAM; // 保持1024字节对齐 uint8_t *NES_SRAM; NES_header *RomHeader; // rom文件头 MAPPER *NES_Mapper; MapperCommRes *MAPx; uint8_t *spr_ram; // 精灵RAM,256字节 ppu_data *ppu; // ppu指针 uint8_t *VROM_banks; uint8_t *VROM_tiles; apu_t *apu; // apu指针 uint16_t *wave_buffers; uint16_t *i2sbuf1; // 音频缓冲帧,占用内存数 367*4 字节@22050Hz uint16_t *i2sbuf2; // 音频缓冲帧,占用内存数 367*4 字节@22050Hz uint8_t *romfile; // nes文件指针,指向整个nes文件的起始地址. ////////////////////////////////////////////////////////////////////////////////////// // 记录nes是否需要跳出 static uint8_t g_nes_break = 0; // 加载ROM // 返回值:0,成功 // 1,内存错误 // 3,map错误 uint8_t nes_load_rom(void) { uint8_t *p; uint8_t i; uint8_t res = 0; p = (uint8_t *)romfile; if (strncmp((char *)p, "NES", 3) == 0) { RomHeader->ctrl_z = p[3]; RomHeader->num_16k_rom_banks = p[4]; RomHeader->num_8k_vrom_banks = p[5]; RomHeader->flags_1 = p[6]; RomHeader->flags_2 = p[7]; if (RomHeader->flags_1 & 0x04) p += 512; // 有512字节的trainer: if (RomHeader->num_8k_vrom_banks > 0) // 存在VROM,进行预解码 { VROM_banks = p + 16 + (RomHeader->num_16k_rom_banks * 0x4000); #if NES_RAM_SPEED == 1 // 1:内存占用小 0:速度快 VROM_tiles = VROM_banks; #else VROM_tiles = mymalloc(RomHeader->num_8k_vrom_banks * 8 * 1024); // 这里可能申请多达1MB内存!!! if (VROM_tiles == 0) VROM_tiles = VROM_banks; // 内存不够用的情况下,尝试VROM_titles与VROM_banks共用内存 compile(RomHeader->num_8k_vrom_banks * 8 * 1024 / 16, VROM_banks, VROM_tiles); #endif } else { VROM_banks = mymalloc(8 * 1024); VROM_tiles = mymalloc(8 * 1024); if (!VROM_banks || !VROM_tiles) res = 1; } VROM_1K_SIZE = RomHeader->num_8k_vrom_banks * 8; VROM_8K_SIZE = RomHeader->num_8k_vrom_banks; MapperNo = (RomHeader->flags_1 >> 4) | (RomHeader->flags_2 & 0xf0); if (RomHeader->flags_2 & 0x0E) MapperNo = RomHeader->flags_1 >> 4; // 忽略高四位,如果头看起来很糟糕 // printf("use map:%d\r\n",MapperNo); for (i = 0; i < 255; i++) // 查找支持的Mapper号 { if (MapTab[i] == MapperNo) break; if (MapTab[i] == -1) res = 3; } if (res == 0) { switch (MapperNo) { case 1: MAP1 = mymalloc(sizeof(Mapper1Res)); if (!MAP1) res = 1; break; case 4: case 6: case 16: case 17: case 18: case 19: case 21: case 23: case 24: case 25: case 64: case 65: case 67: case 69: case 85: case 189: MAPx = mymalloc(sizeof(MapperCommRes)); if (!MAPx) res = 1; break; default: break; } } } return res; // 返回执行结果 } // 变量NES_RAM申请内存时的辅助指针 void *nes_ram_alignment; // 释放内存 void nes_sram_free(void) { // myfree(NES_RAM); myfree(nes_ram_alignment); myfree(NES_SRAM); myfree(RomHeader); myfree(NES_Mapper); myfree(spr_ram); myfree(ppu); myfree(apu); myfree(wave_buffers); myfree(i2sbuf1); myfree(i2sbuf2); myfree(romfile); if ((VROM_tiles != VROM_banks) && VROM_banks && VROM_tiles) // 如果分别为VROM_banks和VROM_tiles申请了内存,则释放 { myfree(VROM_banks); myfree(VROM_tiles); } switch (MapperNo) // 释放map内存 { case 1: // 释放内存 myfree(MAP1); break; case 4: case 6: case 16: case 17: case 18: case 19: case 21: case 23: case 24: case 25: case 64: case 65: case 67: case 69: case 85: case 189: myfree(MAPx); break; // 释放内存 default: break; } NES_RAM = 0; NES_SRAM = 0; RomHeader = 0; NES_Mapper = 0; spr_ram = 0; ppu = 0; apu = 0; wave_buffers = 0; i2sbuf1 = 0; i2sbuf2 = 0; romfile = 0; VROM_banks = 0; VROM_tiles = 0; MAP1 = 0; MAPx = 0; } // 为NES运行申请内存 // romsize:nes文件大小 // 返回值:0,申请成功 // 1,申请失败 uint8_t nes_sram_malloc(uint32_t romsize) { uint16_t i = 0; // NES_RAM需要0x800大小的内存,这里多申请0x400的内存,用于1024字节对齐 nes_ram_alignment = mymalloc(0x800 + 0x400); NES_RAM = (void *)(((uint32_t)nes_ram_alignment + 0x400) & 0xfffffc00); NES_SRAM = mymalloc(0X2000); RomHeader = mymalloc(sizeof(NES_header)); NES_Mapper = mymalloc(sizeof(MAPPER)); spr_ram = mymalloc(0X100); ppu = mymalloc(sizeof(ppu_data)); apu = mymalloc(sizeof(apu_t)); // sizeof(apu_t)= 12588 wave_buffers = mymalloc(APU_PCMBUF_SIZE * 2); i2sbuf1 = mymalloc(APU_PCMBUF_SIZE * 4 + 10); i2sbuf2 = mymalloc(APU_PCMBUF_SIZE * 4 + 10); romfile = mymalloc(romsize); // 申请游戏rom空间,等于nes文件大小 if (i == 64 || !NES_RAM || !NES_SRAM || !RomHeader || !NES_Mapper || !spr_ram || !ppu || !apu || !wave_buffers || !i2sbuf1 || !i2sbuf2 || !romfile) { nes_sram_free(); return 1; } memset(NES_SRAM, 0, 0X2000); // 清零 memset(RomHeader, 0, sizeof(NES_header)); // 清零 memset(NES_Mapper, 0, sizeof(MAPPER)); // 清零 memset(spr_ram, 0, 0X100); // 清零 memset(ppu, 0, sizeof(ppu_data)); // 清零 memset(apu, 0, sizeof(apu_t)); // 清零 memset(wave_buffers, 0, APU_PCMBUF_SIZE * 2); // 清零 memset(i2sbuf1, 0, APU_PCMBUF_SIZE * 4 + 10); // 清零 memset(i2sbuf2, 0, APU_PCMBUF_SIZE * 4 + 10); // 清零 memset(romfile, 0, romsize); // 清零 return 0; } int nes_xoff = 0; // 显示在x轴方向的偏移量(实际显示宽度=256-2*nes_xoff) int nes_yoff = 0; // 开始nes游戏 // pname:nes游戏路径 // 返回值: // 0,正常退出 // 1,内存错误 // 2,文件错误 // 3,不支持的map uint8_t nes_load(uint8_t *pname, void *lcd_addr, int x, int y) { nes_xoff = x; nes_yoff = y; FIL *file; FILINFO *file_info; UINT br; uint8_t res = 0; g_nes_break = 0; file = mymalloc(sizeof(FIL)); file_info = mymalloc(sizeof(FILINFO)); if (file == 0) return 1; // 内存申请失败. res = f_stat((char *)pname, file_info); if (res != FR_OK) // 打开文件失败 { myfree(file); myfree(file_info); return 2; } res = nes_sram_malloc(file_info->fsize); // 申请内存 if (res == 0) { f_open(file, (char *)pname, FA_READ); f_read(file, romfile, file_info->fsize, &br); // 读取nes文件 res = nes_load_rom(); // 加载ROM if (res == 0) { Mapper_Init(); // map初始化 cpu6502_init(); // 初始化6502,并复位 PPU_reset(lcd_addr); // ppu复位 apu_init(); // apu初始化 nes_sound_open(0, APU_SAMPLE_RATE); // 初始化播放设备 nes_emulate_frame(); // 进入NES模拟器主循环 nes_sound_close(); // 关闭声音输出 } } f_close(file); myfree(file); // 释放内存 myfree(file_info); nes_sram_free(); // 释放内存 return res; } // 运行已经加载在内存中的游戏 uint8_t nes_start(uint8_t *file, int fileSize, void *lcd_addr, int x, int y) { nes_xoff = x; nes_yoff = y; uint8_t res = 0; g_nes_break = 0; res = nes_sram_malloc(fileSize); // 申请内存 if (res == 0) { mymemcpy(romfile, file, fileSize); // 复制一份在运行空间 res = nes_load_rom(); // 加载ROM if (res == 0) { Mapper_Init(); // map初始化 cpu6502_init(); // 初始化6502,并复位 PPU_reset(lcd_addr); // ppu复位 apu_init(); // apu初始化 nes_sound_open(0, APU_SAMPLE_RATE); // 初始化播放设备 nes_emulate_frame(); // 进入NES模拟器主循环 nes_sound_close(); // 关闭声音输出 } } nes_sram_free(); // 释放内存 return res; } // 设置游戏显示窗口 void nes_set_window(void) {} // 读取游戏手柄数据 void nes_get_gamepadval(void) { #include "usart.h" PADdata = USART3_GetKey(); PADdata1 = 0; } // nes模拟器主循环 void nes_emulate_frame(void) { uint8_t nes_frame; // TIM3_Int_Init(10000-1,8400-1);//启动TIM3 ,1s中断一次 nes_set_window(); // 设置窗口 while (1) { // LINES 0-239 PPU_start_frame(); // LCD_SwitchLayerBuff();//切换帧 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); } // LCD_ExitLayerBuff (); 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(); nes_get_gamepadval(); // 每3帧查询一次USB apu_soundoutput(); // 输出游戏声音 nes_frame_cnt++; nes_frame++; if (nes_frame > NES_SKIP_FRAME) nes_frame = 0; // 跳帧 uint8_t nesBreak(void); if (nesBreak()) break; if (Touch_GetState()->x[1] != 0) break; } // TIM3->CR1&=~(1<<0);//关闭定时器3 } uint8_t nesBreak(void) { uint8_t t = 0; if ((PADdata & 0x0c) == 0x0c) { g_nes_break = 0x0c; } if ((g_nes_break) && (t = USART3_GetKeyPressed(), t & 0x0c)) { g_nes_break &= ~t; if (g_nes_break == 0) return 1; } return 0; } // 在6502.s里面被调用 void debug_6502(uint16_t reg0, uint8_t reg1) { // printf("6502 error:%x,%d\r\n",reg0,reg1); } ////////////////////////////////////////////////////////////////////////////////// // nes,音频输出支持部分 static vu8 g_buff_Invalid = 0; // 数据无效时要重新填充数据 static vu8 g_buff_useing; // 当前DMA在使用哪个缓冲区 static DAC_UserStruct g_dac; static void dma_tx_callback(DAC_UserStruct *dac) { uint16_t i; g_buff_useing = dac->buff_useing; g_buff_Invalid = 1; } // NES打开音频输出 int nes_sound_open(int samples_per_sync, int sample_rate) { // 初始化DAC g_dac.buff1 = (uint32_t *)i2sbuf1; g_dac.buff2 = (uint32_t *)i2sbuf2; g_dac.buff_size = APU_PCMBUF_SIZE * 4; g_dac.rate = RATE22050; g_dac.call_back = dma_tx_callback; DAC_NormalInit(&g_dac); return 1; } // NES关闭音频输出 void nes_sound_close(void) { DAC_NormalDeInit(&g_dac); } void nes_apu_fill_buffer(int samples, uint16_t *wavebuf) { if (DAC_GetDacHander() != &g_dac) return; uint16_t i; uint16_t *p; while (g_buff_Invalid == 0) // 等待传输完成 { rt_thread_delay(5); }; if (g_buff_useing == 0) { p = (uint16_t *)i2sbuf2; } else { p = (uint16_t *)i2sbuf1; } { for (i = 0; i < APU_PCMBUF_SIZE; i++) { p[2 * i] = ((wavebuf[i] + 0x8000) >> 4); p[2 * i + 1] = p[2 * i]; } } // 填充了数据之后设置为有效 g_buff_Invalid = 0; }