I have ported my old project “pNesX” game console emulator to the nucleo.
Dependencies: SDFileSystem mbed
Intro
I have ported my old project “pNesX” to the STM32 Nucleo. The pNesX is a NES emulator for the PlayStation that I have created 16 years ago!
Emulation part was almost without change, the sound part was newly added.
Parts
STM32 Nucleo F446RE |
QVGA 2.2 TFT SPI (with the SD card slot) |
Audio jack(TS or TRS) |
USB Connector |
Register 100k, 10k, 4.7k, 100 |
Capacitor 0.01uF, 2.2uF |
Breadboard |
Wires |
Computer Speakers |
USB GamePad |
Wiring diagram
TFT J2 | Nucleo |
---|---|
VCC | 3V3 |
GND | GND |
CS | PB_5(D4) |
Reset | PA_10(D2) Pull Up(100k) |
D/C | PA_8(D7) |
MOSI | PA_7(D11) |
SCK | PA_5(D13) |
LED | LED-100ohm-3V3 |
MISO | PA_6(D12) |
TFT J4 | Nucleo |
---|---|
SD_CS | PA_9 |
SD_MOSI | PB_15 |
SD_MISO | PB_14 |
SD_SCK | PB_13 |
Audio | Nucleo |
---|---|
TIP | PA_4(A2) |
USB con. | Nucleo |
---|---|
GND | GND |
+ | PA_12 |
- | PA_11 |
5V | 5V |
Limitations
- Since the rest of the RAM is about 50kbyte, maximum capacity of the game ROM is about 50kbyte.
- The length of the file name up to 32 characters.
- The number of files in the folder is up to 100.
Used Library
- SDFileSystem by Neil Thiessen
- F401RE-USBHost by Norimasa Okamoto
- USBHostGamepad by Yuuichi Akagawa
Diff: pNesX_System_Nucleo.cpp
- Revision:
- 0:3dac1f1bc9e0
diff -r 000000000000 -r 3dac1f1bc9e0 pNesX_System_Nucleo.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pNesX_System_Nucleo.cpp Sun Apr 03 07:45:29 2016 +0000 @@ -0,0 +1,483 @@ +/*===================================================================*/ +/* */ +/* pNesX_System_Nucleo.cpp : The function which depends on a system */ +/* (for Nucleo F446RE) */ +/* */ +/* 2016/1/20 Racoon */ +/* */ +/*===================================================================*/ + +/*-------------------------------------------------------------------*/ +/* Include files */ +/*-------------------------------------------------------------------*/ + +#include "mbed.h" +#include "SDFileSystem.h" +#include "tft.h" + +//#define PS_GAMEPAD + +#if !defined(PS_GAMEPAD) +#include "USBHostGamepad.h" +USBHostGamepad *pad; +#else +#include "pspad.h" +#endif + +#include "pNesX.h" +#include "pNesX_System.h" + +extern int pNesX_Filer(); +extern void ApuMute(bool mute); + +SDFileSystem *sd; //(PB_15, PB_14, PB_13, PA_9, "sd", NC, SDFileSystem::SWITCH_NONE, 20000000); + +DigitalIn userbutton(USER_BUTTON); + +extern WORD LineData[][256]; +extern WORD *pDrawData; + +//#define SHOW_FPS + +#if defined(SHOW_FPS) +Timer timer; +int fps = 0; +int dmawait; +#endif + +/*-------------------------------------------------------------------*/ +/* ROM image file information */ +/*-------------------------------------------------------------------*/ + +extern char szRomFolder[]; +extern char szRomFilename[]; +extern char szRomPath[]; + +char szRomName[ 256 ]; +char szSaveName[ 256 ]; +int nSRAM_SaveFlag; + +// Palette data +const WORD NesPalette[ 64 ] = +{ + 0xef7b,0x1f00,0x1700,0x5741,0x1090,0x04a8,0x80a8,0xa088,0x8051,0xc003,0x4003,0xc002,0x0b02,0x0000,0x0000,0x0000, + 0xf7bd,0xdf03,0xdf02,0x3f6a,0x19d8,0x0be0,0xc0f9,0xe2e2,0xe0ab,0xc005,0x4005,0x4805,0x5104,0x0000,0x0000,0x0000, + 0xdfff,0xff3d,0x5f6c,0xdf9b,0xdffb,0xd3fa,0xcbfb,0x08fd,0xc0fd,0xc3bf,0xca5e,0xd35f,0x5b07,0xcf7b,0x0000,0x0000, + 0xffff,0x3fa7,0xdfbd,0xdfdd,0xdffd,0x38fd,0x96f6,0x15ff,0xcffe,0xcfdf,0xd7bf,0xdbbf,0xff07,0xdffe,0x0000,0x0000 +}; + +/*-------------------------------------------------------------------*/ +/* Function prototypes */ +/*-------------------------------------------------------------------*/ + +int LoadSRAM(); +int SaveSRAM(); + +/*===================================================================*/ +/* */ +/* LoadSRAM() : Load a SRAM */ +/* */ +/*===================================================================*/ +int LoadSRAM() +{ +/* + * Load a SRAM + * + * Return values + * 0 : Normally + * -1 : SRAM data couldn't be read + */ + + return 0; +} + +/*===================================================================*/ +/* */ +/* SaveSRAM() : Save a SRAM */ +/* */ +/*===================================================================*/ +int SaveSRAM() +{ +/* + * Save a SRAM + * + * Return values + * 0 : Normally + * -1 : SRAM data couldn't be written + */ + + + // Successful + return 0; +} + +/*===================================================================*/ +/* */ +/* pNesX_Menu() : Menu screen */ +/* */ +/*===================================================================*/ +int pNesX_Menu() +{ +/* + * Menu screen + * + * Return values + * 0 : Normally + * -1 : Exit pNesX + */ + +#if defined(SHOW_FPS) + timer.stop(); +#endif + + ApuMute(true); + + switch (pNesX_Filer()) + { + case 0: // Selected a file + if ( pNesX_Load( szRomPath ) == 0 ) + { + // Set a ROM image name + strcpy( szRomName, szRomFilename ); + + // Load SRAM + LoadSRAM(); + } + break; + + case 1: // Return to Emu + break; + + case 2: // Reset + if ( szRomName[ 0 ] != 0 ) + { + // Reset pNesX + pNesX_Reset(); + } + + break; + + case -1: // Error + printf("Filer Error\r\n"); + while(1); + } + + tft_clear(TFT_BLACK); + tft_set_window(32, 8, NES_DISP_WIDTH + 32 - 1, NES_DISP_HEIGHT + 8 - 1); + + ApuMute(false); + +#if defined(SHOW_FPS) + timer.start(); +#endif + + return 0; +} + +/*===================================================================*/ +/* */ +/* pNesX_ReadRom() : Read ROM image file */ +/* */ +/*===================================================================*/ +int pNesX_ReadRom( const char *pszFileName ) +{ +/* + * Read ROM image file + * + * Parameters + * const char *pszFileName (Read) + * + * Return values + * 0 : Normally + * -1 : Error + */ + FileHandle* file; + + /* Open ROM file */ + file = sd->open(pszFileName, O_RDONLY); + if ( file == NULL ) + { + printf("open error\r\n"); + return -1; + } + + /* Read ROM Header */ + file->read( &NesHeader, sizeof NesHeader); + if ( memcmp( NesHeader.byID, "NES\x1a", 4 ) != 0 ) + { + /* not .nes file */ + file->close(); + return -1; + } + + /* Clear SRAM */ + memset( SRAM, 0, SRAM_SIZE ); + + /* If trainer presents Read Triner at 0x7000-0x71ff */ + if ( NesHeader.byInfo1 & 4 ) + { + printf("Read Trainer\r\n"); + file->read( &SRAM[ 0x1000 ], 512 ); + } + + printf("RomSize: %d\r\n", NesHeader.byRomSize); + /* Allocate Memory for ROM Image */ + ROM = (BYTE *)malloc( NesHeader.byRomSize * 0x4000 ); + printf("ROM addr:%x\r\n", ROM); + + /* Read ROM Image */ + file->read( ROM, 0x4000 * NesHeader.byRomSize ); + + if ( NesHeader.byVRomSize > 0 ) + { + /* Allocate Memory for VROM Image */ + VROM = (BYTE *)malloc( NesHeader.byVRomSize * 0x2000 ); + printf("VROM addr:%x\r\n", VROM); + + /* Read VROM Image */ + file->read( VROM, 0x2000 * NesHeader.byVRomSize ); + } + + /* File close */ + file->close(); + + /* Successful */ + return 0; +} + +/*===================================================================*/ +/* */ +/* pNesX_ReleaseRom() : Release a memory for ROM */ +/* */ +/*===================================================================*/ +void pNesX_ReleaseRom() +{ +/* + * Release a memory for ROM + * + */ + if ( ROM ) + { + free( ROM ); + ROM = NULL; + } + + if ( VROM ) + { + free( VROM ); + VROM = NULL; + } +} + +/*===================================================================*/ +/* */ +/* pNesX_LoadFrame() : */ +/* Transfer the contents of work frame on the screen */ +/* */ +/*===================================================================*/ +void pNesX_LoadFrame() +{ +/* + * Transfer the contents of work frame on the screen + * + */ +#if defined(SHOW_FPS) + fps++; + + if (timer.read_ms() >= 1000) + { + timer.stop(); + printf("%d %d %d\r\n", fps, timer.read_ms(), dmawait); + fps = 0; + timer.reset(); + timer.start(); + } + dmawait = 0; +#endif +} + +void pNesX_TransmitLinedata() +{ + while (SpiHandle.State != HAL_SPI_STATE_READY) + { +#if defined(SHOW_FPS) + dmawait++; +#endif + } + HAL_SPI_Transmit_DMA(&SpiHandle, (uint8_t *)pDrawData, 256 * 2); +} + +#if !defined(PS_GAMEPAD) +const BYTE UsbPadTable[] = {0x10, 0x90, 0x80, 0xa0, 0x20, 0x60, 0x40, 0x50}; +#endif + +/*===================================================================*/ +/* */ +/* pNesX_PadState() : Get a joypad state */ +/* */ +/*===================================================================*/ +void pNesX_PadState( DWORD *pdwPad1, DWORD *pdwPad2, DWORD *pdwSystem ) +{ +/* + * Get a joypad state + * + * Parameters + * DWORD *pdwPad1 (Write) + * Joypad 1 State R L D U St Se B A + * + * DWORD *pdwPad2 (Write) + * Joypad 2 State + * + * DWORD *pdwSystem (Write) + * Input for pNesX + * + */ + +#if !defined(PS_GAMEPAD) + *pdwPad1 = ((pad->report[4] & 0x20) >> 5) | + ((pad->report[4] & 0x10) >> 3) | + ((pad->report[5] & 0x30) >> 2); + + if (!(pad->report[4] & 8)) + { + *pdwPad1 |= UsbPadTable[pad->report[4] & 7]; + } + + *pdwPad1 = *pdwPad1 | ( *pdwPad1 << 8 ); + + *pdwPad2 = 0; + *pdwSystem = ((pad->report[5] & 0x4) >> 2) | + (((pad->report[5] & 0xa) == 0xa) << 1); + + USBHost::poll(); +#else + + unsigned short pad1, pad2; + pspad_read(&pad1, &pad2); + // R L D U St Se B A + + // SE -- -- ST U R D L L2 R2 L1 R1 TR O X SQ + *pdwPad1 = ((pad1 & 0x0400) >> 3) | + ((pad1 & 0x0100) >> 2) | + ((pad1 & 0x0200) >> 4) | + ((pad1 & 0x0800) >> 7) | + ((pad1 & 0x1000) >> 9) | + ((pad1 & 0x8000) >> 13) | + ((pad1 & 1) << 1) | + ((pad1 & 2) >> 1); + + *pdwPad1 = *pdwPad1 | ( *pdwPad1 << 8 ); + + *pdwPad2 = 0; + + *pdwSystem = ((pad1 & 0x80) >> 7) | + (((pad1 & 0x50) == 0x50) << 1); + +#endif + +} + +/*===================================================================*/ +/* */ +/* pNesX_MemoryCopy() : memcpy */ +/* */ +/*===================================================================*/ +void *pNesX_MemoryCopy( void *dest, const void *src, int count ) +{ +/* + * memcpy + * + * Parameters + * void *dest (Write) + * Points to the starting address of the copied block to destination + * + * const void *src (Read) + * Points to the starting address of the block of memory to copy + * + * int count (Read) + * Specifies the size, in bytes, of the block of memory to copy + * + * Return values + * Pointer of destination + */ + + memcpy(dest, src, count ); + return dest; +} + +/*===================================================================*/ +/* */ +/* pNesX_MemorySet() : */ +/* */ +/*===================================================================*/ +void *pNesX_MemorySet( void *dest, int c, int count ) +{ +/* + * memset + * + * Parameters + * void *dest (Write) + * Points to the starting address of the block of memory to fill + * + * int c (Read) + * Specifies the byte value with which to fill the memory block + * + * int count (Read) + * Specifies the size, in bytes, of the block of memory to fill + * + * Return values + * Pointer of destination + */ + + memset(dest, c, count); + return dest; +} + +/*===================================================================*/ +/* */ +/* DebugPrint() : Print debug message */ +/* */ +/*===================================================================*/ +void pNesX_DebugPrint( char *pszMsg ) +{ +} + +/*===================================================================*/ +/* */ +/* pNesX Initialise */ +/* */ +/*===================================================================*/ +int main() +{ + // TFT initialize + tft_init(); + tft_clear(TFT_BLACK); + tft_set_window(32, 8, NES_DISP_WIDTH + 32 - 1, NES_DISP_HEIGHT + 8 - 1); + + // SD card Initialize + sd = new SDFileSystem(PB_15, PB_14, PB_13, PA_9, "sd", NC, SDFileSystem::SWITCH_NONE, 20000000); + sd->crc(true); + sd->large_frames(true); + sd->write_validation(true); + + strcpy(szRomFolder, "/sd"); + +#if !defined(PS_GAMEPAD) + // USB Gmaepad Initialize + pad = new USBHostGamepad(); + pad->connect(); +#else + pspad_init(); +#endif + + // Apu Initialize + ApuInit(); + + /*-------------------------------------------------------------------*/ + /* Start pNesX */ + /*-------------------------------------------------------------------*/ + + pNesX_Main(); +} + +