Use the PSG: Difference between revisions
m (→Frequency bytes in C, meant for tone 1: formatting) |
m (→Examples:: formatting, paragraphs) |
||
(3 intermediate revisions by the same user not shown) | |||
Line 29: | Line 29: | ||
=== Command Formats === | === Command Formats === | ||
{| class="wikitable" | |||
|+ | |||
!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 | |||
|} | |||
{| class="wikitable" | |||
|+ | |||
!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 [[wikipedia:Crystal_oscillatir_frequencies|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: | |||
=== Frequency bytes in C, | 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: ==== | |||
<p>These examples focus on PSG1, so send these bytes to address 0xD600 (left PSG) | |||
<p>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.<br> | |||
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<br> | |||
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<br> | |||
0b1001 1111 Attenuation command that targets PSG1, Tone 1 to completely turn off its volume. This can be used as a "note off" event<br> | |||
<p>These examples focus on PSG2, Tone 2, so send these bytes to address 0xD610 (right PSG) | |||
<p>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.<br> | |||
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#<br> | |||
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#<br> | |||
0b1011 1111 Attenuation command that targets PSG2, Tone 2 to completely turn off its volume. This can be used as a "note off" event<br> | |||
=== 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[] = { | uint8_t psgLow[] = { | ||
Line 65: | Line 213: | ||
uint8_t | ==== 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}; |
Latest revision as of 06:24, 24 August 2025
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};