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_Mapper.cpp

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

File content as of revision 0:3dac1f1bc9e0:

/*===================================================================*/
/*                                                                   */
/*  pNesX_Mapper.cpp : pNesX Mapper Function                         */
/*                                                                   */
/*  1999/11/03  Racoon  New preparation                              */
/*                                                                   */
/*===================================================================*/

/*-------------------------------------------------------------------*/
/*  Include files                                                    */
/*-------------------------------------------------------------------*/

#include "pNesX.h"
#include "pNesX_System.h"
#include "pNesX_Mapper.h"
#include "K6502.h"

/*-------------------------------------------------------------------*/
/*  Table of Mapper initialize function                              */
/*-------------------------------------------------------------------*/

struct MapperTable_tag MapperTable[] =
{
  { 0, Map0_Init },
  { 1, Map1_Init },
  { 2, Map2_Init },
  { 3, Map3_Init },
  { 4, Map4_Init },
  { -1, NULL }
};

/*===================================================================*/
/*                                                                   */
/*                            Mapper 0                               */
/*                                                                   */
/*===================================================================*/

/*-------------------------------------------------------------------*/
/*  Initialize Mapper 0                                              */
/*-------------------------------------------------------------------*/
void Map0_Init()
{
  /* Initialize Mapper */
  MapperInit = Map0_Init;

  /* Write to Mapper */
  MapperWrite = Map0_Write;

  /* Callback at VSync */
  MapperVSync = Map0_VSync;

  /* Callback at HSync */
  MapperHSync = Map0_HSync;

  /* Set ROM Banks */
  ROMBANK0 = ROMPAGE( 0 );
  ROMBANK1 = ROMPAGE( 1 );
  ROMBANK2 = ROMLASTPAGE( 1 );
  ROMBANK3 = ROMLASTPAGE( 0 );

  /* Set PPU Banks */
  if ( NesHeader.byVRomSize > 0 )
  {
    for ( int nPage = 0; nPage < 8; ++nPage )
      PPUBANK[ nPage ] = &VROM[ nPage * 0x400 ];

    pNesX_SetupChr();
  }

  /* Set up wiring of the interrupt pin */
  K6502_Set_Int_Wiring( 1, 1 ); 
}

/*-------------------------------------------------------------------*/
/*  Mapper 0 Write Function                                          */
/*-------------------------------------------------------------------*/
void Map0_Write( WORD wAddr, BYTE byData )
{
/*
 *  Dummy Write to Mapper
 *
 */
}

/*-------------------------------------------------------------------*/
/*  Mapper 0 V-Sync Function                                         */
/*-------------------------------------------------------------------*/
void Map0_VSync()
{
/*
 *  Dummy Callback at VSync
 *
 */
}

/*-------------------------------------------------------------------*/
/*  Mapper 0 H-Sync Function                                         */
/*-------------------------------------------------------------------*/
void Map0_HSync()
{
/*
 *  Dummy Callback at HSync
 *
 */
}

/*===================================================================*/
/*                                                                   */
/*                            Mapper 1                               */
/*                                                                   */
/*===================================================================*/

BYTE Map1_Reg[ 4 ];

int Map1_Cnt;
BYTE Map1_Latch;

/*-------------------------------------------------------------------*/
/*  Initialize Mapper 1                                              */
/*-------------------------------------------------------------------*/
void Map1_Init()
{
  int nPage;

  /* Initialize Mapper */
  MapperInit = Map1_Init;

  /* Write to Mapper */
  MapperWrite = Map1_Write;

  /* Callback at VSync */
  MapperVSync = Map0_VSync;

  /* Callback at HSync */
  MapperHSync = Map0_HSync;

  pNesX_MemorySet( Map1_Reg, 0, sizeof Map1_Reg );
  Map1_Cnt = Map1_Latch = 0;

  /* Set ROM Banks */
  ROMBANK0 = ROMPAGE( 0 );
  ROMBANK1 = ROMPAGE( 1 );
  ROMBANK2 = ROMLASTPAGE( 1 );
  ROMBANK3 = ROMLASTPAGE( 0 );

  /* Set PPU VROM Banks */
  if ( NesHeader.byVRomSize > 0 )
  {
    for ( nPage = 0; nPage < 8; ++nPage )
      PPUBANK[ nPage ] = &VROM[ nPage * 0x400 ];

    pNesX_SetupChr();
  }

  /* Set up wiring of the interrupt pin */
  K6502_Set_Int_Wiring( 1, 1 );
}

/*-------------------------------------------------------------------*/
/*  Mapper 1 Write Function                                          */
/*-------------------------------------------------------------------*/
void Map1_Write( WORD wAddr, BYTE byData )
{
/*
 * MMC1
 */

  int nReg;
  int nROMPos;
  int nBank;
  int nVBank;
  int nPage;

  nReg = ( wAddr >> 13 ) - 4;

  if ( byData & 0x80 )
  {
    // Reset
    Map1_Reg[ 0 ] |= 0xc;
    Map1_Cnt = 0;
    Map1_Latch = 0;
    return;
  }
  else
  {
    // Bit Data Set
    Map1_Latch |= ( ( byData & 1 ) << Map1_Cnt );
    ++Map1_Cnt;
  }
  
  if ( Map1_Cnt < 5 )
    return;

  // 5Bits Latched
  Map1_Reg[ nReg ] = ( nReg == 3 ) ? ( Map1_Latch & 0xf ) : Map1_Latch;
  Map1_Cnt = 0;
  Map1_Latch = 0;
  
  // Name Table Mirroring
  pNesX_Mirroring( ( Map1_Reg[ 0 ] & 3 ) ^ 3 );
    
  // Select Program ROM Bank

  // 256K ROM Position Selection
  nROMPos = 0;
  if ( NesHeader.byRomSize == 32 )
  {
    /* 512K Program ROM */
    // 0 (First 256K) or 32 (Second 256K)
    nROMPos = ( Map1_Reg[ 1 ] & 0x10 ) << 1;
  }
  else
  if ( NesHeader.byRomSize == 64 )
  {
    /* 1024K Program ROM */
    if ( Map1_Reg[ 0 ] & 0x10 )
    {
      // Acknowledge 256K selection register 1 
      nROMPos = ( ( Map1_Reg[ 1 ] & 0x10 ) << 1 ) |
               ( ( Map1_Reg[ 2 ] & 0x10 ) << 2 );
    }
    else 
    {
      // Ignore 256K selection register 1
      // 0 (First 256K) or 64 (Third 256K)
      nROMPos = ( Map1_Reg[ 1 ] & 0x10 ) << 2;
    }
  }
    
  // Set Program ROM Bank
  if ( !( Map1_Reg[ 0 ] & 8 ) )
  {
    // 32K ROM Bank
    /* Set ROM Banks */
    nBank = ( ( Map1_Reg[ 3 ] << 2 ) + nROMPos ) % ( NesHeader.byRomSize << 1 );

    ROMBANK0 = ROMPAGE( nBank );
    ROMBANK1 = ROMPAGE( nBank + 1 );
    ROMBANK2 = ROMPAGE( nBank + 2 );
    ROMBANK3 = ROMPAGE( nBank + 3 );
  }
  else
  if ( Map1_Reg[ 0 ] & 4 )
  {
    // 16K ROM Bank at 0x8000
    nBank = ( ( Map1_Reg[ 3 ] << 1 ) + nROMPos ) % ( NesHeader.byRomSize << 1 );
    ROMBANK0 = ROMPAGE( nBank );
    ROMBANK1 = ROMPAGE( nBank + 1 );
    ROMBANK2 = ROMLASTPAGE( 1 );
    ROMBANK3 = ROMLASTPAGE( 0 );
  }
  else
  {
    // 16K ROM Bank at 0xc000
    nBank = ( ( Map1_Reg[ 3 ] << 1 ) + nROMPos ) % ( NesHeader.byRomSize << 1 );
    ROMBANK0 = ROMPAGE( 0 );
    ROMBANK1 = ROMPAGE( 1 );
    ROMBANK2 = ROMPAGE( nBank );
    ROMBANK3 = ROMPAGE( nBank + 1 );
  }

  // Select PPU VROM Bank
  if ( NesHeader.byVRomSize > 0 )
  {
    if ( Map1_Reg[ 0 ] & 0x10 )
    {
      // 4K VROM Switching
      nVBank = Map1_Reg[ 1 ] % ( NesHeader.byVRomSize << 1 );
      for ( nPage = 0; nPage < 4; ++nPage )
        PPUBANK[ nPage ] = &VROM[ nVBank * 0x1000 + nPage * 0x400 ];

      nVBank = Map1_Reg[ 2 ] % ( NesHeader.byVRomSize << 1 );
      for ( nPage = 0; nPage < 4; ++nPage )
        PPUBANK[ nPage + 4 ] = &VROM[ nVBank * 0x1000 + nPage * 0x400 ];
    }
    else
    {
      // 8K VROM Switching
      nVBank = ( Map1_Reg[ 1 ] & 0xe ) % ( NesHeader.byVRomSize << 1 );
      for ( nPage = 0; nPage < 8; ++nPage )
        PPUBANK[ nPage ] = &VROM[ nVBank * 0x1000 + nPage * 0x400 ];
    }

    pNesX_SetupChr();
  }
}

/*===================================================================*/
/*                                                                   */
/*                        Mapper 2 (UNROM)                           */
/*                                                                   */
/*===================================================================*/

/*-------------------------------------------------------------------*/
/*  Initialize Mapper 2                                              */
/*-------------------------------------------------------------------*/
void Map2_Init()
{
  /* Initialize Mapper */
  MapperInit = Map2_Init;

  /* Write to Mapper */
  MapperWrite = Map2_Write;

  /* Callback at VSync */
  MapperVSync = Map0_VSync;

  /* Callback at HSync */
  MapperHSync = Map0_HSync;

  /* Set ROM Banks */
  ROMBANK0 = ROMPAGE( 0 );
  ROMBANK1 = ROMPAGE( 1 );
  ROMBANK2 = ROMLASTPAGE( 1 );
  ROMBANK3 = ROMLASTPAGE( 0 );

  /* Set up wiring of the interrupt pin */
  K6502_Set_Int_Wiring( 1, 1 ); 
}

/*-------------------------------------------------------------------*/
/*  Mapper 2 Write Function                                          */
/*-------------------------------------------------------------------*/
void Map2_Write( WORD wAddr, BYTE byData )
{
  /* Set ROM Banks */
  byData %= NesHeader.byRomSize;
  byData <<= 1;

  ROMBANK0 = ROMPAGE( byData );
  ROMBANK1 = ROMPAGE( byData + 1 );
}


/*===================================================================*/
/*                                                                   */
/*                     Mapper 3 (VROM Switch)                        */
/*                                                                   */
/*===================================================================*/

/*-------------------------------------------------------------------*/
/*  Initialize Mapper 3                                              */
/*-------------------------------------------------------------------*/
void Map3_Init()
{
  int nPage;

  /* Initialize Mapper */
  MapperInit = Map3_Init;

  /* Write to Mapper */
  MapperWrite = Map3_Write;

  /* Callback at VSync */
  MapperVSync = Map0_VSync;

  /* Callback at HSync */
  MapperHSync = Map0_HSync;

  /* Set ROM Banks */
  ROMBANK0 = ROMPAGE( 0 );
  ROMBANK1 = ROMPAGE( 1 );
  ROMBANK2 = ROMLASTPAGE( 1 );
  ROMBANK3 = ROMLASTPAGE( 0 );

  /* Set PPU Banks */
  if ( NesHeader.byVRomSize > 0 )
  {
    for ( nPage = 0; nPage < 8; ++nPage )
      PPUBANK[ nPage ] = &VROM[ nPage * 0x400 ];
    pNesX_SetupChr();
  }

  /* Set up wiring of the interrupt pin */
  /* "DragonQuest" doesn't run if IRQ isn't made to occur in CLI */
  K6502_Set_Int_Wiring( 1, 0 ); 
}

/*-------------------------------------------------------------------*/
/*  Mapper 3 Write Function                                          */
/*-------------------------------------------------------------------*/
void Map3_Write( WORD wAddr, BYTE byData )
{
  int nPage;

  /* Set PPU Banks */
  byData %= NesHeader.byVRomSize;
  for ( nPage = 0; nPage < 8; ++nPage )
    PPUBANK[ nPage ] = &VROM[ byData * 0x2000 + nPage * 0x400 ];

  pNesX_SetupChr();
}

/*===================================================================*/
/*                                                                   */
/*                        Mapper 4 (MMC3)                            */
/*                                                                   */
/*===================================================================*/

BYTE Map4_VROM_Base;
BYTE Map4_ROM_Base;
BYTE Map4_Cmd;
BYTE Map4_Banks_Reg[ 8 ];
BYTE Map4_IRQ_Cnt;
BYTE Map4_IRQ_Set;
BYTE Map4_IRQ_Enable;

/*-------------------------------------------------------------------*/
/*  Initialize Mapper 4                                              */
/*-------------------------------------------------------------------*/
void Map4_Init()
{
  /* Initialize Mapper */
  MapperInit = Map4_Init;

  /* Write to Mapper */
  MapperWrite = Map4_Write;

  /* Callback at VSync */
  MapperVSync = Map4_VSync;

  /* Callback at HSync */
  MapperHSync = Map4_HSync;

  Map4_VROM_Base = Map4_ROM_Base = Map4_Cmd = Map4_IRQ_Cnt = Map4_IRQ_Set = Map4_IRQ_Enable = 0;

  Map4_Banks_Reg[ 0 ] = 0;
  Map4_Banks_Reg[ 1 ] = 2;
  Map4_Banks_Reg[ 2 ] = 4;
  Map4_Banks_Reg[ 3 ] = 5;
  Map4_Banks_Reg[ 4 ] = 6;
  Map4_Banks_Reg[ 5 ] = 7;
  Map4_Banks_Reg[ 6 ] = 0;
  Map4_Banks_Reg[ 7 ] = 1;

  Map4_Write( 0x8000, 0 );
  Map4_Write( 0x8001, 0 );

  /* Set up wiring of the interrupt pin */
  K6502_Set_Int_Wiring( 1, 1 ); 
}

/*-------------------------------------------------------------------*/
/*  Mapper 4 Write Function                                          */
/*-------------------------------------------------------------------*/
void Map4_Write( WORD wAddr, BYTE byData )
{
  WORD wMapAddr;

  wMapAddr = wAddr & 0xe001;

  switch ( wMapAddr )
  {
    case 0xa000:
      // Name Table Mirroring
      pNesX_Mirroring( ~byData & 1 );
      break;

    case 0xa001:
      break;

    case 0xc000:
      Map4_IRQ_Cnt = byData;
      break;

    case 0xc001:
      Map4_IRQ_Set = byData;
      break;

    case 0xe000:
      Map4_IRQ_Cnt = Map4_IRQ_Set;
      Map4_IRQ_Enable = 0;
      break;

    case 0xe001:
      if ( Map4_IRQ_Cnt > 0 )
        Map4_IRQ_Enable = 1;
      break;

    default:
      if ( wMapAddr == 0x8000 )
      {
        Map4_VROM_Base = byData >> 7;
        Map4_ROM_Base = ( byData >> 6 ) & 1;
        Map4_Cmd = byData & 7;
        return;
      }
      else
      {
        if ( Map4_Cmd >= 6 )
          byData %= ( NesHeader.byRomSize << 1 );
        else
        if ( NesHeader.byVRomSize > 0 )
        {
          if ( Map4_Cmd < 2 )
            byData = ( byData & 0xfe ) % ( NesHeader.byVRomSize << 3 );
          else
          if ( Map4_Cmd < 6 )
            byData %= ( NesHeader.byVRomSize << 3 );
        }

        Map4_Banks_Reg[ Map4_Cmd ] = byData;
      }

      /* Set VROM Banks */
      if ( NesHeader.byVRomSize > 0 )
      {
        if ( Map4_VROM_Base )
        {
          PPUBANK[ 0 ] = VROMPAGE( Map4_Banks_Reg[ 2 ] );
          PPUBANK[ 1 ] = VROMPAGE( Map4_Banks_Reg[ 3 ] );
          PPUBANK[ 2 ] = VROMPAGE( Map4_Banks_Reg[ 4 ] );
          PPUBANK[ 3 ] = VROMPAGE( Map4_Banks_Reg[ 5 ] );
          PPUBANK[ 4 ] = VROMPAGE( Map4_Banks_Reg[ 0 ] );
          PPUBANK[ 5 ] = VROMPAGE( Map4_Banks_Reg[ 0 ] + 1 );
          PPUBANK[ 6 ] = VROMPAGE( Map4_Banks_Reg[ 1 ] );
          PPUBANK[ 7 ] = VROMPAGE( Map4_Banks_Reg[ 1 ] + 1 );
        }
        else
        {
          PPUBANK[ 0 ] = VROMPAGE( Map4_Banks_Reg[ 0 ] );
          PPUBANK[ 1 ] = VROMPAGE( Map4_Banks_Reg[ 0 ] + 1 );
          PPUBANK[ 2 ] = VROMPAGE( Map4_Banks_Reg[ 1 ] );
          PPUBANK[ 3 ] = VROMPAGE( Map4_Banks_Reg[ 1 ] + 1 );
          PPUBANK[ 4 ] = VROMPAGE( Map4_Banks_Reg[ 2 ] );
          PPUBANK[ 5 ] = VROMPAGE( Map4_Banks_Reg[ 3 ] );
          PPUBANK[ 6 ] = VROMPAGE( Map4_Banks_Reg[ 4 ] );
          PPUBANK[ 7 ] = VROMPAGE( Map4_Banks_Reg[ 5 ] );
        }

        pNesX_SetupChr();
      }

      if ( Map4_ROM_Base )
      {
        ROMBANK0 = ROMLASTPAGE( 1 );
        ROMBANK1 = ROMPAGE( Map4_Banks_Reg[ 7 ] );
        ROMBANK2 = ROMPAGE( Map4_Banks_Reg[ 6 ] );
        ROMBANK3 = ROMLASTPAGE( 0 );
      }
      else
      {
        ROMBANK0 = ROMPAGE( Map4_Banks_Reg[ 6 ] );
        ROMBANK1 = ROMPAGE( Map4_Banks_Reg[ 7 ] );
        ROMBANK2 = ROMLASTPAGE( 1 );
        ROMBANK3 = ROMLASTPAGE( 0 );
      }
  }
}

/*-------------------------------------------------------------------*/
/*  Mapper 4 V-Sync Function                                         */
/*-------------------------------------------------------------------*/
void Map4_VSync()
{
/*
 *  Callback at VSync
 *
 */
  Map4_IRQ_Cnt = Map4_IRQ_Set;
}

/*-------------------------------------------------------------------*/
/*  Mapper 4 H-Sync Function                                         */
/*-------------------------------------------------------------------*/
void Map4_HSync()
{
/*
 *  Callback at HSync
 *
 */

  if ( Map4_IRQ_Enable &&
       PPU_Scanline >= SCAN_ON_SCREEN_START && PPU_Scanline < SCAN_BOTTOM_OFF_SCREEN_START &&
       //PPU_ScanTable[ PPU_Scanline ] == SCAN_ON_SCREEN &&
       ( PPU_R1 & ( R1_SHOW_SCR | R1_SHOW_SP ) ) == ( R1_SHOW_SCR | R1_SHOW_SP ) &&
       Map4_IRQ_Cnt > 0 )
  {
    --Map4_IRQ_Cnt;
    if ( Map4_IRQ_Cnt == 0 )
    {
      Map4_IRQ_Cnt = Map4_IRQ_Set;
      IRQ_REQ;
    }
  }
}