Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Dependencies: SDFileSystem mbed-dev
Fork of Nucleo_Ex06_EMU by
Diff: pNesX_Apu.cpp
- Revision:
- 0:3dac1f1bc9e0
diff -r 000000000000 -r 3dac1f1bc9e0 pNesX_Apu.cpp
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/pNesX_Apu.cpp Sun Apr 03 07:45:29 2016 +0000
@@ -0,0 +1,674 @@
+/*===================================================================*/
+/* */
+/* 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 ];
+}
+
