PET Keyboard Test

A little tool to test and explore the keyboard matrix of any Commodore PET.

Title illustration

Back in 2022, I wrote a post on the various variants of PET keyboards, their layout and the corresponding keyboard matrices. Complementing this, there’s now a little test program, which lets you test and explore the keyboard matrix visually, including simultaneous key presses. Which may be useful both for testing a physical PET keyboard, but also for determining those keyboard matrix values for use in games, etc. The program supports both the bgraphics and the business keyboard and comes in two variants, once for 40-column PETs and once for 80-column PETs. (These are essentially the same program, but with individual lookup tables for screen positions.)

Run the 40-columns program in in-browser emulation on a PET 2001:

And here with the business keyboard (PET 2001/B):

You can find the two programs for download here: www.masswerk.at/pet/prgs/#keyboard-test

Here’s what the program looks like, providing a visual representation of the keyboard matrix and high-lighting active nodes:

Screenshot of the keyboard test program running on a 40-columns Commodore PET computer
Screenshot of the keyboard test program (40-columns PET).

Code (MOS 6502)

The program performs a low-level read-out of the keyboard matrix as polled by the PIA 1 and provides a visual representation of the active nodes in the matrix. Here’s how we do it:

First, as a preliminary measure, we disable interrupts and prepare for the read-out by setting up Control Register A (CRA) of PIA 1, address $E811, and Control Register B (CRB) at address $E813. (In the same configuration the PET editor is using.) Then, in an endless loop, we iterate over the 10 rows (0..9) of the keyboard matrix, polling each row by writing to Port A (PA) of the PIA at address $E810 and then reading the combined column values for that row from Port B (PB) at address $E812.
The rest is interpreting the column data (one bit per column, active low) and displaying it. Essentially, we select a screen row for each row of the keyboard matrix and display a state indicator at a respective screen column for each bit of the column read-out onto a predrawn grid (not included here).

The program uses a few zero-page locations, namely

ROW     ....  1 byte, temp. store for row count
COL     ....  1 byte, temp. store for columns read-out
P_DEST  ....  2 bytes, pointer to screen row address

For lookup tables (exclusively used for display positions), see below.

And here is the main event:

;here from setting up and drawing the basic screen graphics


main	sei                ;disable interrupts
	lda #$3C           ;set up for keyboard polling
	sta $E811          ;PIA1_CRA (Control Register A)
	lda #$35           ;and for reading keyboard cols
	sta $E813          ;PIA1_CRB (Control Register B)

mainLoop	lda #0             ;init row count
	sta ROW

pollRow	lda ROW            ;get row count
	ora #$F0           ;poll value: $F<n> for row
	sta $E810          ;poll PIA1_PA (Port A)
	lda $E812          ;read cols from PIA1_PB (Port B)
	sta COL

	lda ROW            ;set up video pointer for row
	clc
	adc #9             ;add vertical screen offset
	tay
	lda video_lo,y     ;low-byte from lookup table
	sta P_DEST         ;set pointer (low)
	lda video_hi,y     ;high-byte from lookup table
	sta P_DEST+1       ;set pointer (high)

	                   ;now interpret the column value
	ldx #7             ;iterate over 8 bits (7..0)
colLoop	lda COL
	asl                ;shift MSB into carry
	sta COL
	bcs bitHi          ;inspect it, state = active low
bitLo	lda #$CF           ;screen code for active
	bne skip           ;unconditional (we just loaded a non-zero value)
bitHi	lda #$5B           ;screen code for inactive
skip	ldy pos,x          ;get screen column for bit
	sta (P_DEST),y     ;display it
	dex
	bpl colLoop        ;loop for next column bit

	ldy ROW            ;iterate rows
	cpy #9             ;row 9 done?
	beq startOver      ;yes
	iny                ;increment row count
	sty ROW
	bne pollRow        ;and redo for next row (unconditional)
            
startOver	lda #$F9           ;check stop key
	sta $E810          ;row 9, bit 4 (all keyboards!)
	lda $E812
	and #%00010000
	bne mainLoop       ;not active, redo
            

exit	cli                ;enable interrupts
	(…)
	rts

(The code is fully relocatable, just the locations of the lookup tables and the hardware addresses are static.)

And this is what the lookup tables look like:

;constant definitions

VIDEO      = $8000
SCREENCOLS = 40                ;80 for 80-column PETs


;video row addresses

video_lo
	.byte <(0*SCREENCOLS)+VIDEO
	.byte <(1*SCREENCOLS)+VIDEO
	.byte <(2*SCREENCOLS)+VIDEO
	 (…)
	.byte <(24*SCREENCOLS)+VIDEO

video_hi
	.byte >(0*SCREENCOLS)+VIDEO
	.byte >(1*SCREENCOLS)+VIDEO
	.byte >(2*SCREENCOLS)+VIDEO
	 (…)
	.byte >(24*SCREENCOLS)+VIDEO


;screen col. offsets for column display (bits 0..7, right to left)

pos	.byte 30, 28, 26, 24, 22, 20, 18, 16

A similar lookup table for video row addresses is actually found in the PET ROM, but the basic intention here is to keep this agnostic of any ROM versions and variations.
In principle, while this is here a loadable program, the code could be burned into a test ROM.

(A test ROM may choose to implement lookup tables for both screen sizes and try to determine the actually screen size, e.g., by inspecting for the presence of a CRTC and its setup. Something I was just too lazy to do. Or it may come up with an altogether different display scheme.)

And a Mod, Possibly

Finally, for good measure and beauty, here’s a possible enhancement, especially for use in test ROMs, which only concerns the 40-columns version of the program:

The original PET 2001 uses slow SRAM for video memory, which results in interference snow on the screen whenever we were to access the video memory while the screen is redrawn, due to a bus conflict. Hence, we may want to make sure that we‘re only writing to screen memory during the V-BLANK interval, as indicated by bit 5 of the Port B register of the VIA (VIA PB at $E840) being low. Which is, BTW, also how the PET’s BASIC before version 4 does it.
For this, we should also make sure that this bit is actually set up for reading, as in the corresponding bit in the Data Direction Register B (VIA DDRB at $E842) being set to low, as it should be anyway.

However, mind that this introduces another variable, namely, the VIA and the circuitry for setting that bit working correctly.

For this mod, we’d have to add a few instructions, just at the entrance of “main”, to configure VIA DDRB:

main	sei                ;disable interrupts

	                   ;40-cols version only: 
	lda $E842          ;configure VIA DDRB
	and #%11011111     ;make sure that bit 5 is low (default)
	sta $E842          ;so that it is set up for input
	
	(…)

And, at label “skip”, where we’re actually writing to screen memory, we’d insert the following modification:

	(…)
skip	ldy pos,x          ;(as before) get screen column for bit

	                   ;40-cols version only: 
	pha                ;save accumulator onto stack
	lda #$20           ;before writing, wait for bit 5 of 
waitVBlank	bit $E840          ;VIA Port B being low = V-BLANK
	bne waitVBlank     ;to prevent snow on PET 2001 screens
	pla                ;restore accumulator from stack
	
	sta (P_DEST),y     ;(as before) display it
	(…)

It may be worth noting that, while not used by BASIC 4, bit 5 of VIA DB is cleared during V-BLANK on PETs with the dynamic board and CRTC-PETs, as well, so this should work with any hardware configuration.

While tested (in emulation), this mod is not included in the test program as availabe for download. Other than serving the edge-case of preventing interference snow on the PET 2001, this enhancement has no practical impact and is probably not worth it. But it may be still worth pointing out how this could be done…

Anyhow, if we weren’t in the know already, now we know why PRINT-ing was kind of slow with BASIC 1 and 2 (or 3, depending on the preferred numbering scheme), as found on the original PET 2001s.

— And that’s all, folks. —