Closed cbmeeks closed 1 year ago
Hey CB. I'm working on the consumable version now. Here's some snippets from my existing dual AY-3-8910 emulation to get you started. I'm using my fork of emu2149.
Globals / magic numbers:
#define PSG_CLOCK 2000000
#define PSG_GPIO 20
#define PSG_PWM_WRAP 1024
PSG* psg0 = NULL;
PSG* psg1 = NULL;
uint audioSlice = 0;
Initialization (called once)
void configureAudio()
{
// init the two PSGs
int sampleRate = vgaParams.hSyncParams.freqHz; // I'm updating the audio in my VGA hsync blanking ~37.87KHz
psg0 = PSG_new(PSG_CLOCK, sampleRate);
psg1 = PSG_new(PSG_CLOCK, sampleRate);
PSG_setVolumeMode(psg0, EMU2149_VOL_AY_3_8910); // AY-3-8910 mode
PSG_setVolumeMode(psg1, EMU2149_VOL_AY_3_8910); // AY-3-8910 mode
PSG_reset(psg0);
PSG_reset(psg1);
// init the GPIO pins for PWM
gpio_set_function(PSG_AUDIO_GPIO , GPIO_FUNC_PWM);
gpio_set_function(PSG_AUDIO_GPIO + 1, GPIO_FUNC_PWM);
audioSlice = pwm_gpio_to_slice_num(PSG_AUDIO_GPIO );
pwm_set_clkdiv_int_frac(audioSlice, 1, 0);
pwm_set_wrap(audioSlice, PSG_PWM_WRAP);
pwm_set_both_levels(audioSlice, 0, 0);
pwm_set_enabled(audioSlice, true);
}
Updates (called in my hsync event handler at ~37.87KHz:
PSG_calc(psg0);
PSG_calc(psg1);
// mix the various channels from the PSGs how you see fit
uint16_t leftAudio = abs((((int32_t)psg0->ch_out[0] + (int32_t)psg1->ch_out[1] + (int32_t)psg0->ch_out[2] + (int32_t)psg1->ch_out[2]))) * PWM_WRAP >> 14;
uint16_t rightAudio = abs((((int32_t)psg1->ch_out[0] + (int32_t)psg0->ch_out[1] + (int32_t)psg0->ch_out[2] + (int32_t)psg1->ch_out[2]))) * PWM_WRAP >> 14;
pwm_set_both_levels(audioSlice, leftAudio, rightAudio);
Thanks for the response. I definitely have a lot to learn on this one. My main goal is to have some sort of PSG from the Pico...all being controlled by a 6502.
Oh, so you're using a physical 6502? And the PICO is acting as the RAM/peripherals? One step I failed to mention was reading/writing from/to the virtual PSG(s). Each PSG will need 3-4 addresses mapped. For example, all of my I/O is mapped at 0x7fxx and my PSGs are mapped at 0x7f40 and 0x7f44. The way my PSGs are wired on my actual HBC-56, writing to 0x7f40 will latch a register to write to, 0x7f41 will write a register value and reading from 0x7f42 will read a value from the latched register. The address mappings don't matter, but you will need a similar process.
Here's some snippets from the PICO-56 memory read/write functions which show how the various addresses ling to the PSG_readreg/PSG_writereg functions:
#define PSG0_BASE_PORT 0x40
#define PSG1_BASE_PORT 0x44
void cpuMemWrite(uint16_t addr, uint8_t val)
{
if (addr < 0x7f00) // RAM
{
ram[addr] = val;
}
else if (addr < 0x8000) // IO
{
switch (addr & 0xff)
{
case 0x10:
vrEmuTms9918WriteData(tms9918, val);
break;
case 0x11:
vrEmuTms9918WriteAddr(tms9918, val);
break;
case PSG0_BASE_PORT:
psg0Reg = val;
break;
case PSG0_BASE_PORT | 0x01:
PSG_writeReg(psg0, psg0Reg, val);
break;
case PSG1_BASE_PORT:
psg1Reg = val;
break;
case PSG1_BASE_PORT | 0x01:
PSG_writeReg(psg1, psg1Reg, val);
break;
default:
// unknown device
break;
}
}
}
uint8_t cpuMemRead(uint16_t addr)
{
if (addr & 0x8000) // ROM
{
return ROM[addr & 0x7fff];
}
else if (addr < 0x7f00) // RAM
{
return ram[addr];
}
else // IO
{
switch (addr & 0xff)
{
case 0x10:
{
return vrEmuTms9918ReadData(tms9918);
}
case 0x11:
{
uint8_t value = vrEmuTms9918ReadStatus(tms9918);
releaseInterrupt(TMS_INT);
return value;
}
// <snip> handle keyboard, nes, interrupt controller, etc.
case PSG0_BASE_PORT | 0x02:
{
return PSG_readReg(psg0, psg0Reg);
}
case PSG1_BASE_PORT | 0x02:
{
return PSG_readReg(psg1, psg1Reg);
}
default:
return 0x00;
}
}
}
}
Thank you for this information!
Yeah, I am designing a new SBC with an actual 6502. The Pico(s) will act as video, audio, etc.
I will dig into this more when I get back. Headed out on a nice vacation tomorrow.
Thanks!
I was wondering if you have released the source for your AY-3-8910 emulation on the Pico. I am working on a similar project and would really love some information on that. :-)
Thanks!