Use the PSG
The PSG is inside the FPGA of the F256Jr, F256K, F256Jr2 and F256K2
The F256 machines have a dual PSG based on the SN76489 inside the Beatrix FPGA which generate 3 tones of square wave
and one noise generator each.
Register Name | Address | Description |
---|---|---|
PSG_LEFT | 0xD600 | Send all formatted commands here to the left PSG |
PSG_RIGHT | 0xD610 | Send all formatted commands here to the right PSG |
PSG_COMBINED_LEFT_RIGHT | 0xD608 | Send all formatted commands here to both PSG |
SYS1 | 0xD6A1 | System Ctrl reg: bit2 sets PSG_ST = psg status. if 0, left and right are mixed to monaural output. if 1, left and right are normal stereo |
Command Formats
D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 | Command Description |
---|---|---|---|---|---|---|---|---|
1 | R2 | R1 | R0 | F3 | F2 | F1 | F0 | R's: which register to affect (see below), F's: low four bits of freq |
0 | X | F9 | F8 | F7 | F6 | F5 | F4 | X: unused, F's: high six bits of freq |
1 | R2 | R1 | R0 | X | FB | F1 | F0 | X: unused, FB: control type, F's: frequency of noise gn. |
1 | R2 | R1 | R0 | A3 | A2 | A1 | A0 | R's register target, A's: four attenuation bits |
R2 | R1 | R0 | Channel | Purpose |
---|---|---|---|---|
0 | 0 | 0 | Tone 1 | Frequency |
0 | 0 | 1 | Tone 1 | Attenuation |
0 | 1 | 0 | Tone 2 | Frequency |
0 | 1 | 1 | Tone 2 | Attenuation |
1 | 0 | 0 | Tone 3 | Frequency |
1 | 0 | 1 | Tone 3 | Attenuation |
1 | 1 | 0 | Noise | Control |
1 | 1 | 1 | Noise | Attenuation |
How to use the master formula with frequencies
To get to the PSG clock, you start with a 315 000 000 Hz reference divided by 22, yielding 14,318,181.81... (81 repeating) called the 14.31818 MHz NTSC M color subcarrier. Then, you divided it again by 4, yielding 3,579,545 Hz as the PSG master clock..
To generate the values needed for the PSG register, follow this procedure:
1) divide the PSG master clock of 3,579,545 Hz by 32, and then also divide that by the frequency in Hz of the note. n = psg_master_clock / (32 * f) where n is the number destined for the registers and f is the frequency in Hz.
2) convert the integer rounded number you get into a 10-bit resolution binary number
3) the bottom 4 bits are sent to the first part of a "frequency command" while the top 6 bits are used in the second part of the same "frequency command". See examples below and see pre-generated values even further below.
Examples:
These examples focus on PSG1, so send these bytes to address 0xD600 (left PSG)
0b1001 0100 Attenuation command that targets PSG1, Tone 1 to set a middle volume. This can be used as a "note on" event if you had set the frequency prior to this.
0b1000 1101 Frequency setting command part 1, that targets PSG1, Tone 1. This sends the first lower 4 bits of information based on a transformed 440 Hz
0b0000 1111 Frequency setting command part 2, that targets PSG1, Tone 1. This sends the final higher 6 bits of information based on a transformed 440 Hz
0b1001 1111 Attenuation command that targets PSG1, Tone 1 to completely turn off its volume. This can be used as a "note off" event
These examples focus on PSG2, Tone 2, so send these bytes to address 0xD610 (right PSG)
0b1011 0100 Attenuation command that targets PSG2, Tone 2 to set a middle volume. This can be used as a "note on" event if you had set the frequency prior to this.
0b1010 1001 Frequency setting command part 1, that targets PSG2, Tone 2. This sends the first lower 4 bits of information based on a transformed 208 Hz G#
0b0010 0001 Frequency setting command part 2, that targets PSG2, Tone 2. This sends the final higher 6 bits of information based on a transformed 208 Hz G#
0b1011 1111 Attenuation command that targets PSG2, Tone 2 to completely turn off its volume. This can be used as a "note off" event
Frequency bytes in arrays meant for the C language, hard coded
Command with high bits for the frequency are always the same
uint8_t psgHigh[] = {
0x3f, 0x3b,0x38,0x35,0x32,0x2f,0x2c,0x2a,0x27,0x25,0x23,0x21,
0x1f, 0x1d,0x1c,0x1a,0x19,0x17,0x16,0x15,0x13,0x12,0x11,0x10,
0x0f, 0x0e,0x0e,0x0d,0x0c,0x0b,0x0b,0x0a,0x09,0x09,0x08,0x08,
0x07, 0x07,0x07,0x06,0x06,0x05,0x05,0x05,0x04,0x04,0x04,0x04,
0x03, 0x03,0x03,0x03,0x03,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
0x01, 0x01,0x01,0x01};
Command with Low frequency bits, table for tone 1 (the high nybble will be set to 8):
uint8_t psgLow[] = {
0x86, 0x8d,0x87,0x84,0x84,0x87,0x8d,0x84,0x8e,0x8b,0x89,0x89,
0x8b, 0x8e,0x83,0x8a,0x82,0x8b,0x86,0x82,0x8f,0x8d,0x8c,0x8c,
0x8d, 0x8f,0x81,0x85,0x89,0x8d,0x83,0x89,0x8f,0x86,0x8e,0x86,
0x8e, 0x87,0x80,0x8a,0x84,0x8e,0x89,0x84,0x8f,0x8b,0x87,0x83,
0x8f, 0x8b,0x88,0x85,0x82,0x8f,0x8c,0x8a,0x87,0x85,0x83,0x81,
0x8f, 0x8d,0x8c,0x8a};
Command with Low frequency bits, table for tone 2 (the high nybble will be set to A):
uint8_t psgLow[] = {
0xA6, 0xAd,0xA7,0xA4,0xA4,0xA7,0xAd,0xA4,0xAe,0xAb,0xA9,0xA9,
0xAb, 0xAe,0xA3,0xAa,0xA2,0xAb,0xA6,0xA2,0xAf,0xAd,0xAc,0xAc,
0xAd, 0xAf,0xA1,0xA5,0xA9,0xAd,0xA3,0xA9,0xAf,0xA6,0xAe,0xA6,
0xAe, 0xA7,0xA0,0xAa,0xA4,0xAe,0xA9,0xA4,0xAf,0xAb,0xA7,0xA3,
0xAf, 0xAb,0xA8,0xA5,0xA2,0xAf,0xAc,0xAa,0xA7,0xA5,0xA3,0xA1,
0xAf, 0xAd,0xAc,0xAa};
Command with Low frequency bits, table for tone 3 (the high nybble will be set to C):
uint8_t psgLow[] = {
0xC6, 0xCd,0xC7,0xC4,0xC4,0xC7,0xCd,0xC4,0xCe,0xCb,0xC9,0xC9,
0xCb, 0xCe,0xC3,0xCa,0xC2,0xCb,0xC6,0xC2,0xCf,0xCd,0xCc,0xCc,
0xCd, 0xCf,0xC1,0xC5,0xC9,0xCd,0xC3,0xC9,0xCf,0xC6,0xCe,0xC6,
0xCe, 0xC7,0xC0,0xCa,0xC4,0xCe,0xC9,0xC4,0xCf,0xCb,0xC7,0xC3,
0xCf, 0xCb,0xC8,0xC5,0xC2,0xCf,0xCc,0xCa,0xC7,0xC5,0xC3,0xC1,
0xCf, 0xCd,0xCc,0xCa};