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

/media/uploads/beaglescout007/nucleo_ex06_emu.png

TFT J2Nucleo
VCC3V3
GNDGND
CSPB_5(D4)
ResetPA_10(D2) Pull Up(100k)
D/CPA_8(D7)
MOSIPA_7(D11)
SCKPA_5(D13)
LEDLED-100ohm-3V3
MISOPA_6(D12)
TFT J4Nucleo
SD_CSPA_9
SD_MOSIPB_15
SD_MISOPB_14
SD_SCKPB_13
AudioNucleo
TIPPA_4(A2)
USB con.Nucleo
GNDGND
+PA_12
-PA_11
5V5V

https://youtu.be/jL24IjT6LnI

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

pNesX_System_Nucleo.cpp

Committer:
beaglescout007
Date:
2016-04-03
Revision:
0:3dac1f1bc9e0

File content as of revision 0:3dac1f1bc9e0:

/*===================================================================*/
/*                                                                   */
/*  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();    
}