This program provides an interactive display and editor for the AY-3-8912 Programmable Sound Generator (PSG) chip registers. It uses OUT 245 and IN 246 to write to and read from the PSG’s address and data ports respectively, showing all 15 registers (0–14) with their human-readable function names, current binary representations, and decimal values on a color-formatted screen. The binary display routine works by successively subtracting powers of two from the register value, implementing a manual bit-extraction loop without relying on bitwise operators. Users can select any register by number and enter a new value, with range validation (0–14 for registers, 0–255 for values) enforced by BEEP error tones and re-prompting loops.
Program Analysis
Program Structure
The program is divided into clearly labelled sections separated by REM blocks. Execution flows through an initialisation subroutine, then enters a main input loop:
- Lines 30: Calls the screen setup subroutine at line 500.
- Lines 100–150: Main interactive loop — prompts for a register number and a value, writes to the PSG, refreshes the display, and repeats.
- Lines 300–350: Subroutine that reads a register’s current value via
IN 246and prints its 8-bit binary representation followed by its decimal value. - Lines 500–540: Screen initialisation — sets colours, prints column headers, iterates through all 15 registers reading their names from DATA and displaying their initial values.
- Lines 610–620: DATA statements providing human-readable names for each PSG register.
PSG Hardware Interface
The AY-3-8912 PSG in the TS2068 is accessed via two I/O ports. Port 245 selects the register address, and port 246 reads or writes the register data. The program uses OUT 245,r to latch a register number, then OUT 246,v to write a value or IN 246 to read the current contents. This is the standard two-step address/data protocol required by the AY chip.
Register Display
All 15 registers (0–14) are displayed in a fixed-position table on screen. The AT r+4,12 addressing places each register’s information on a row offset by 4 from the top, corresponding to row positions 4 through 18. Three columns are maintained: the register number at column 12, the binary value at column 17, and the decimal value following it.
Binary Conversion Routine (Lines 310–350)
The binary display does not use bitwise operations (unavailable in Sinclair BASIC). Instead it implements a manual base-2 decomposition using successive subtraction. power starts at 128 and is halved each iteration. For each bit position, the current value of mod is tested by subtracting power; a negative result means the bit is 0 (restore mod), otherwise the bit is 1. This is a classic BASIC technique for extracting individual bits:
power= 128, 64, 32, 16, 8, 4, 2, 1 across 8 loop iterations (x= 0 to 7)- A “0” or “1” character is PRINTed directly for each bit, producing the binary string left-to-right (MSB first)
- After the loop,
IN 246is called again to print the decimal value — note the register address is still latched from line 310
Input Validation
Both input prompts include range checks with audible error feedback. Register numbers outside 0–14 trigger BEEP 1,10 and loop back to line 100. Values outside 0–255 trigger BEEP 1,5 and return to line 110 (re-prompting for the value only, not the register). The differing BEEP pitches give the user an audible distinction between the two error types.
Screen Layout
The screen uses PAPER 1 (blue), BORDER 1, and INK 6 (yellow) for a coloured display. The header row at line 2 uses INVERSE 1 for the column title bar. The currently-selected register is highlighted using FLASH 1 during value entry (line 110) and reverted to normal after (line 140).
Register Names (DATA)
| Register | Name |
|---|---|
| 0 | Ch.A Fine |
| 1 | Coarse |
| 2 | Ch.B Fine |
| 3 | Coarse |
| 4 | Ch.C Fine |
| 5 | Coarse |
| 6 | Noise |
| 7 | Enable |
| 8 | Ch.A Ampl |
| 9 | Ch.B Ampl |
| 10 | Ch.C Ampl |
| 11 | Env.Fine |
| 12 | Coarse |
| 13 | Env.Shape |
| 14 | Port |
Anomalies and Notes
- Line 201 contains a stray
REM Print contentsthat is never reached; execution jumps directly to line 300 viaGO SUB 300. It appears to be a leftover label comment. - In the initialisation loop (line 530),
LET r=yis used before callingGO SUB 300so that the subroutine has the correct register number inr. This reuses the global variablerwhich is also the main loop’s register variable — a minor coupling that works correctly here since setup runs before any user input. - The decimal value is read with a second
IN 246call at line 350 rather than storing the value already read intomodat line 310. This is functionally correct since the register address remains latched, but reads the hardware twice unnecessarily. - The title “PSG REGISTRS” on line 520 is truncated (missing a second ‘E’), apparently due to column width constraints.
Content
Source Code
10 REM PSG REGISTER PROGRAM
11 REM
20 REM Set up
21 REM
30 GO SUB 500
40 REM
41 REM Loop
42 REM
100 INPUT "Register?";r: IF r<0 OR r>14 THEN BEEP 1,10: GO TO 100
110 PRINT AT r+4,12; FLASH 1;r
120 INPUT "Value?";v: IF v<0 OR v>255 THEN BEEP 1,5: GO TO 110
130 OUT 245,r: OUT 246,v: GO SUB 300
140 PRINT AT r+4,12;r
150 GO TO 100
201 REM Print contents
300 REM
302 REM
310 OUT 245,r: LET mod=IN 246: LET power=128: PRINT AT r+4,17;
320 FOR x=0 TO 7: LET mod=mod-power: IF mod<0 THEN PRINT "0";: LET mod=mod+power: GO TO 340
330 PRINT "1";
340 LET power=power/2: NEXT x
350 PRINT " ";IN 246;" ": RETURN
500 REM
501 REM Print screen
502 REM
510 PAPER 1: BORDER 1: INK 6: CLS
520 PRINT TAB 2;"PSG REGISTRS";TAB 20;"CONTENTS": PRINT AT 2,0; INVERSE 1;"Function No.";TAB 18;"Binary Dec. ": PRINT
530 FOR y=0 TO 14: READ a$: PRINT a$;AT y+4,12;y: LET r=y: GO SUB 300: NEXT y
540 RETURN
600 REM
601 REM String data
602 REM
610 DATA "Ch.A Fine"," Coarse","Ch.B Fine"," Coarse","Ch.C Fine"," Coarse","Noise"
620 DATA "Enable","Ch.A Ampl","Ch.B Ampl","Ch.C Ampl","Env.Fine"," Coarse","Env.Shape","Port"
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.
