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
pNesX_Apu.cpp
- Committer:
- beaglescout007
- Date:
- 2016-04-03
- Revision:
- 0:3dac1f1bc9e0
File content as of revision 0:3dac1f1bc9e0:
/*===================================================================*/ /* */ /* pNesX_Apu.cpp : NES APU emulation (for Nucleo) */ /* */ /* 2016/1/20 Racoon */ /* */ /*===================================================================*/ #include "mbed.h" #include "SDFileSystem.h" #include "pNesX.h" #include "pNesX_System.h" //==================== // DAC //==================== AnalogOut DAC_Out(PA_4); WORD TrDac, P1Dac, P2Dac, NsDac, MixDac; #define DACFreq 24000 // Pulse clock(8 steps) = 1789773 / 2 / DACFreq * 1000 #define PuClock 37287 // Triangle clock(32 steps) = 1789773 / DACFreq * 1000 #define TrClock 74574 //==================== // DAC Timer //==================== #define TIMx TIM4 #define TIMx_CLK_ENABLE __HAL_RCC_TIM4_CLK_ENABLE #define TIMx_IRQn TIM4_IRQn #define TIMx_IRQHandler TIM4_IRQHandler TIM_HandleTypeDef TimHandle; //==================== // Audio channel variables //==================== const BYTE LengthTable[] = {10, 254, 20, 2, 40, 4, 80, 6, 160, 8, 60, 10, 14, 12, 26, 14, 12, 16, 24, 18, 48, 20, 96, 22, 192, 24, 72, 26, 16, 28, 32, 30}; // Minimum Volume #define MV 4096 const WORD Pulse_Table[4][8] = { 0,MV, 0, 0, 0, 0, 0, 0, 0,MV,MV, 0, 0, 0, 0, 0, 0,MV,MV,MV,MV, 0, 0, 0, MV, 0, 0,MV,MV,MV,MV,MV}; // Pulse1 bool P1Enable; int P1Timer; int P1WaveLength; int P1WavePeriod; int P1Lapse; int P1Duty; int P1Vol; int P1LengthCounter; int P1EnvSeq; int P1EnvCnt; int P1Sweep; int P1SwCnt; int P1SwLevel; bool P1SwReloadFlag; bool P1SwOverflow; // Pulse2 bool P2Enable; int P2Timer; int P2WaveLength; int P2WavePeriod; int P2Lapse; int P2Duty; int P2Vol; int P2LengthCounter; int P2EnvSeq; int P2EnvCnt; int P2Sweep; int P2SwCnt; int P2SwLevel; bool P2SwReloadFlag; bool P2SwOverflow; // Triangle bool TrEnable; int TrTimer; const WORD Triangle_Table[] = {61440,57344,53248,49152,45056,40960,36864,32768,28672,24576,20480,16384,12288,8192,4096,0, 0,4096,8192,12288,16384,20480,24576,28672,32768,36864,40960,45056,49152,53248,57344,61440}; bool LinearCounterReloadFlag; int LinearCounter; int TrLengthCounter; int TrWaveLength; int TrWavePeriod; int TrLapse; // Noise bool NsEnable; const WORD Noise_Freq[] = {0,0,0,0,0,0,12000,11186,8860,7046,4710,3523,2349,1762,880,440}; int NsFreq; int NsSum; int NsVol; int NsLengthCounter; int NsEnvSeq; int NsEnvCnt; // DMC int DmOutLevel; /*-------------------------------------------------------------------*/ /* DAC Timer callback */ /*-------------------------------------------------------------------*/ extern "C" { void TIMx_IRQHandler(void) { HAL_TIM_IRQHandler(&TimHandle); } void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim) { TIMx_CLK_ENABLE(); HAL_NVIC_SetPriority(TIMx_IRQn, 4, 0); HAL_NVIC_EnableIRQ(TIMx_IRQn); } static void Error_Handler(void) { while(1); } } // extern "C" /*-------------------------------------------------------------------*/ /* Randomize value (Noise channel) */ /*-------------------------------------------------------------------*/ WORD ShiftReg = 1; WORD Rand96() { ShiftReg |= ((ShiftReg ^ (ShiftReg >> 6)) & 1) << 15; ShiftReg >>= 1; return (ShiftReg & 1) ? MV : 0; } WORD Rand32k() { ShiftReg |= ((ShiftReg ^ (ShiftReg >> 1)) & 1) << 15; ShiftReg >>= 1; return (ShiftReg & 1) ? MV : 0; } /*-------------------------------------------------------------------*/ /* DAC Timer Interrup */ /*-------------------------------------------------------------------*/ void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { // Pulse1 if (P1Enable && P1LengthCounter > 0 && !P1SwOverflow && P1Timer > 8) { P1Lapse += PuClock; P1Lapse %= P1WavePeriod; P1Dac = Pulse_Table[P1Duty][P1Lapse / P1WaveLength] * P1Vol; } // Pulse2 if (P2Enable && P2LengthCounter > 0 && !P2SwOverflow && P2Timer > 8) { P2Lapse += PuClock; P2Lapse %= P2WavePeriod; P2Dac = Pulse_Table[P2Duty][P2Lapse / P2WaveLength] * P2Vol; } // Triangle if (TrEnable && LinearCounter > 0 && TrLengthCounter > 0) { TrLapse += TrClock; TrLapse %= TrWavePeriod; TrDac = Triangle_Table[TrLapse / TrWaveLength]; TrDac -= (TrDac * DmOutLevel / 295); } // Noise if (NsEnable && NsLengthCounter > 0) { NsSum += NsFreq; if (NsSum >= DACFreq) { NsSum %= DACFreq; NsDac = ((APU_Reg[0xe] & 0x80) ? Rand96() : Rand32k()) * NsVol; NsDac -= (NsDac * DmOutLevel / 295); } } // Mixing MixDac = (P1Dac + P2Dac + TrDac + NsDac) / 4; // Output to DAC DAC_Out.write_u16(MixDac); } /*-------------------------------------------------------------------*/ /* Apu Initialize Function */ /*-------------------------------------------------------------------*/ void ApuInit() { // Setup DAC Timer TimHandle.Instance = TIMx; TimHandle.Init.Prescaler = (uint32_t) ((SystemCoreClock / 2) / 240000) - 1; // 240000Hz TimHandle.Init.Period = 10 - 1; // 240000/10=24000Hz TimHandle.Init.ClockDivision = 0; TimHandle.Init.CounterMode = TIM_COUNTERMODE_UP; if(HAL_TIM_Base_Init(&TimHandle) != HAL_OK) Error_Handler(); if(HAL_TIM_Base_Start_IT(&TimHandle) != HAL_OK) Error_Handler(); } /*-------------------------------------------------------------------*/ /* Apu Mute */ /*-------------------------------------------------------------------*/ void ApuMute(bool mute) { if (mute) { if(HAL_TIM_Base_Stop_IT(&TimHandle) != HAL_OK) Error_Handler(); } else { if(HAL_TIM_Base_Start_IT(&TimHandle) != HAL_OK) Error_Handler(); } } /*-------------------------------------------------------------------*/ /* Apu Write Function */ /*-------------------------------------------------------------------*/ void ApuWrite( WORD wAddr, BYTE byData ) { APU_Reg[ wAddr ] = byData; switch( wAddr ) { //==================== // Pulse1 //==================== case 0: P1Duty = (byData & 0xc0) >> 6; if (byData & 0x10) { // constant volume P1Vol = byData & 0xf; } else { // envelope on P1Vol = 15; P1EnvCnt = (byData & 0xf) + 1; } break; case 1: P1SwReloadFlag = true; P1SwLevel = (byData & 7); break; case 2: case 3: P1Timer = APU_Reg[3] & 7; P1Timer = (P1Timer << 8) | APU_Reg[2]; P1WaveLength = (P1Timer+1) * 1000; P1WavePeriod = P1WaveLength * 8; if (wAddr == 3) { P1LengthCounter = LengthTable[(byData & 0xf8) >> 3]; // Reset sequencer P1Lapse = 0; P1EnvSeq = P1EnvCnt; P1Sweep = 0; P1SwCnt = 0; P1SwOverflow = false; } break; //==================== // Pulse2 //==================== case 4: P2Duty = (byData & 0xc0) >> 6; if (byData & 0x10) { // constant volume P2Vol = byData & 0xf; } else { // envelope on P2Vol = 15; P2EnvCnt = (byData & 0xf) + 1; } break; case 5: P2SwReloadFlag = true; P2SwLevel = (byData & 7); break; case 6: case 7: P2Timer = APU_Reg[7] & 7; P2Timer = (P2Timer << 8) | APU_Reg[6]; P2WaveLength = (P2Timer+1) * 1000; P2WavePeriod = P2WaveLength * 8; if (wAddr == 7) { P2LengthCounter = LengthTable[(byData & 0xf8) >> 3]; // Reset sequencer P2Lapse = 0; P2EnvSeq = P2EnvCnt; P2Sweep = 0; P2SwCnt = 0; P2SwOverflow = false; } break; //==================== // Triangle //==================== case 0x8: LinearCounterReloadFlag = true; break; case 0xA: case 0xB: TrTimer = APU_Reg[0xB] & 7; TrTimer = (TrTimer << 8) | APU_Reg[0xA]; TrWaveLength = TrTimer * 1000; TrWavePeriod = TrWaveLength * 32; if (wAddr == 0xB) { TrLengthCounter = LengthTable[(byData & 0xf8) >> 3]; LinearCounterReloadFlag = true; } break; //==================== // Noise //==================== case 0xC: if (byData & 0x10) { // constant volume NsVol = byData & 0xf; } else { // envelope on NsVol = 15; NsEnvCnt = (byData & 0xf) + 1; } break; case 0xF: NsLengthCounter = LengthTable[(byData & 0xf8) >> 3]; // Reset sequencer NsEnvSeq = NsEnvCnt; break; case 0xE: NsFreq = Noise_Freq[byData & 0xf]; break; //==================== // DMC(PCM) //==================== case 0x11: DmOutLevel = byData & 0x7f; break; //==================== // Control //==================== case 0x15: if (byData & 1) { P1Enable = true; } else { P1Enable = false; P1LengthCounter = 0; } if (byData & 2) { P2Enable = true; } else { P2Enable = false; P2LengthCounter = 0; } if (byData & 4) { TrEnable = true; } else { TrEnable = false; TrLengthCounter = 0; } if (byData & 8) { NsEnable = true; } else { NsEnable = false; NsLengthCounter = 0; } break; } // Switch( wAddr ) } /*-------------------------------------------------------------------*/ /* 240Hz Clock */ /*-------------------------------------------------------------------*/ void pNesX_ApuClk_240Hz() { //==================== // Pulse1 Envelope //==================== if (!(APU_Reg[0] & 0x10) && P1EnvSeq > 0) { P1EnvSeq--; if (P1EnvSeq == 0) { if (P1Vol > 0) { --P1Vol; P1EnvSeq = P1EnvCnt; } else { if (APU_Reg[0] & 0x20) { P1Vol = 15; P1EnvSeq = P1EnvCnt; } } } } //==================== // Pulse2 Envelope //==================== if (!(APU_Reg[4] & 0x10) && P2EnvSeq > 0) { P2EnvSeq--; if (P2EnvSeq == 0) { if (P2Vol > 0) { --P2Vol; P2EnvSeq = P2EnvCnt; } else { if (APU_Reg[4] & 0x20) { P2Vol = 15; P2EnvSeq = P2EnvCnt; } } } } //==================== // Triangle Linear Counter //==================== if (LinearCounterReloadFlag) { LinearCounter = APU_Reg[0x8] & 0x7f; } else if (LinearCounter > 0) { LinearCounter--; } if (!(APU_Reg[0x8] & 0x80)) { LinearCounterReloadFlag = false; } //==================== // Noise Envelope //==================== if (!(APU_Reg[0xc] & 0x10) && NsEnvSeq > 0) { NsEnvSeq--; if (NsEnvSeq == 0) { if (NsVol > 0) { --NsVol; NsEnvSeq = NsEnvCnt; } else { if (APU_Reg[0xc] & 0x20) { NsVol = 15; NsEnvSeq = NsEnvCnt; } } } } } /*-------------------------------------------------------------------*/ /* 120Hz Clock */ /*-------------------------------------------------------------------*/ void pNesX_ApuClk_120Hz() { //==================== // Pulse1 Sweep //==================== if (P1SwReloadFlag) { P1Sweep = ((APU_Reg[1] & 0x70) >> 4) + 1; P1SwCnt = 0; P1SwReloadFlag = false; } else if (APU_Reg[1] & 0x80 && !P1SwOverflow) { P1SwCnt++; if (P1SwCnt == P1Sweep) { P1SwCnt = 0; int sweep = P1Timer >> P1SwLevel; int value = P1Timer; if (APU_Reg[1] & 8) { value = value - sweep - 1; if (value < 8) { P1SwOverflow = true; return; } } else { value = value + sweep; if (value > 0x7ff) { P1SwOverflow = true; return; } } P1Timer = value; APU_Reg[3] &= 7; APU_Reg[3] |= value >> 8; APU_Reg[2] = value & 0xff; P1WaveLength = (value + 1) * 1000; P1WavePeriod = P1WaveLength * 8; } } //==================== // Pulse1 Sweep //==================== if (P2SwReloadFlag) { P2Sweep = ((APU_Reg[5] & 0x70) >> 4) + 1; P2SwCnt = 0; P2SwReloadFlag = false; } else if (APU_Reg[5] & 0x80 && !P2SwOverflow) { P2SwCnt++; if (P2SwCnt == P2Sweep) { P2SwCnt = 0; int sweep = P2Timer >> P2SwLevel; int value = P2Timer; if (APU_Reg[5] & 8) { value = value - sweep; if (value < 8) { P2SwOverflow = true; return; } } else { value = value + sweep; if (value > 0x7ff) { P2SwOverflow = true; return; } } P2Timer = value; APU_Reg[7] &= 7; APU_Reg[7] |= value >> 8; APU_Reg[6] = value & 0xff; P2WaveLength = (value + 1) * 1000; P2WavePeriod = P2WaveLength * 8; } } } /*-------------------------------------------------------------------*/ /* 60Hz Clock */ /*-------------------------------------------------------------------*/ void pNesX_ApuClk_60Hz() { // Pulse1 if (!(APU_Reg[0] & 0x20) && P1LengthCounter > 0) { P1LengthCounter--; } // Pulse2 if (!(APU_Reg[4] & 0x20) && P2LengthCounter > 0) { P2LengthCounter--; } // Triangle if (!(APU_Reg[0x8] & 0x80) && TrLengthCounter > 0) { TrLengthCounter--; } // Noise if (!(APU_Reg[0xc] & 0x20) && NsLengthCounter > 0) { NsLengthCounter--; } } /*-------------------------------------------------------------------*/ /* Status */ /*-------------------------------------------------------------------*/ BYTE ApuRead( WORD wAddr ) { if (wAddr == 0x15) { return (P1LengthCounter > 0) | (P2LengthCounter > 0) << 1 | (TrLengthCounter > 0) << 2 | (NsLengthCounter > 0) << 3; } return APU_Reg[ wAddr ]; }