Basically, here’s how it works. If Input A is low (the switch is open so the input is pulled down to ground by the pull down resistor R4), Q1 is off since no current is flowing into its base. Therefore current does not flow to the output, which is pulled low by R3.

The same with Input B.

But if both buttons are pressed, both inputs are high, and both transistors are on, so current flows from +5 to the LED.

Here is the breadboard design:

Here is the device, with no buttons pushed:

Here is the device with button one pushed:

Here is the device with button two pushed:

Here is the device with both buttons pushed, i.e. the AND gate activated:

This post is a consideration of the C64 keyboard. It is not exhaustive, and it is not systematic. I just want to consider a couple of topics: the keyboard matrix, keyboard codes, the conversion tables, and the keyboard buffer.

When a key is pressed on the keyboard on the Commodore 64, the kernal tries to read the keyboard matrix.

First, here is what the matrix looks like – the image is from the Commodore 64 Service Manual:

We can watch the keyboard matrix work ourselves, using a BASIC program.

First, we need to turn off the kernal IRQ handler. This link tells us how to do that. Basically, we need to put a zero in the first bit of byte 56334 ($DC0E). Doing this will disable the keyboard, so we want to turn the IRQ handler back on at the end of the program, by putting a one in the first bit of byte 56334 ($DC0E). I turn the handler off in line 10 and back on in line 1000.

Now we are ready to probe the keyboard matrix. Here’s how that works. First, we poke a byte into $DC00 (56320) that indicates which set of eight possible characters we want to test for. In that byte, seven of the eight bits will be high, and one of the bits will be low – the low byte indicates which set of characters we are looking for (which row in the matrix we are probing). So the possible values are:

254 (b11111110) for row 0

253 (b11111101) for row 1

251 (b11111011) for row 2

247 (b11110111) for row 3

239 (b11101111) for row 4

223 (b11011111) for row 5

191 (b10111111) for row 6

127 (b01111111) for row 7

In the program below, I set the row byte in line 100. In the example, we are reading row 0.

Then, from 120 to 150 a FOR/NEXT loop waits for a key to be pressed. We can read the column number of the key pressed in byte $DC01 (56321). If no key is being pressed, that position will hold an $FF (255). If a key is being pressed, it will hold the column number of the pressed key, using the same format as for the rows above (254 -> 0, 253 -> 1, 251 -> 2, etc.) Thus, the number we poked into $DC00 and the number we read peeked from $DC01 gives us 8 x 8 or 64 possible characters as we see in the image from the service manual above.

Here is the code:

10 POKE 56334, PEEK(56334) AND 254
100 POKE 56320, 254
120 FOR I = 1 TO 5000
130 X = PEEK(56321)
140 IF X <> 255 THEN PRINT X : GOTO 1000
150 NEXT I
1000 POKE 56334, PEEK(56334) OR 1

Here is the matrix that I worked out, by putting all eight row values into $DC00 in line 100, then pushing keys until one of them gives a value that is not $FF in $DC01. I used my real C64C to be sure I was seeing what the real keyboard matrix looked like, rather than an artifact of WinVICE.

It is the same as the one from the manual, except that I reorganized the rows and columns to go in numeric order of the byte they contain, rather than trying to preserve any of the geography of the actual keyboard.

When the matrix has been read, the keyboard code is placed in the zero page memory position 203 ($CB). Then, before the keyboard is scanned again, the value in 203 is moved to 197 ($C5). When no key is being pressed, the value 64 ($40) is placed in 203, then is moved to 197 before the next scan. So, the situation that indicates “a new key has been pressed” is that 197 contains 64 and 203 contains a value less than 64.

This little program tests this. Because the keyboard matrix scan is fast and BASIC is slow, I have to press a key several times in order to have BASIC catch the transition between not pressed and pressed – awkward, but it demonstrates the concept.

100 X = PEEK(197) : Y = PEEK(203)
120 IF X = 64 AND Y <> 64 THEN PRINT Y
140 GOTO 100

Here is the table I developed using my real C64C. It is pleasingly alike to the table found on the c64-wiki here.

The table on the wiki indicates that code 63 ($3F) is RUN/STOP but I didn’t include that as the program in its current state can’t test that.

Having acquired the keyboard code for the pressed key, we now need to determine whether or not a SHIFT or CTRL or C= key was simultaneously pressed. To do that we can read memory position 653. It will equal:

1 if SHIFT is pressed

2 if C= is pressed

4 if CTRL is pressed

0 if none of those

So, after reading the matrix and assigning a keyboard code, and determining if a SHIFT or CTRL or C= is simultaneously pressed, the next step is to determine what character should be entered into the keyboard buffer as a result of the key press.

There are four lookup tables:

60289-60353 ($EB81-$EBC1) – for no simultaneous key press

60354-60418 ($EBC2-$EC02) – for SHIFT pressed

60419-60483 ($EC03-$EC43) – for C= pressed

60536-60600 ($EC78-$ECB8) – for CTRL pressed

Let’s look at one or two elements of these lookup tables. I know that if I press A, the keyboard code is 10. PEEK(60289 + 10) gives me 65, which is in fact the PETSCII code for A.

PEEK(60354 + 10) gives me 193, which is the PETSCII code for a playing card spades symbol in a box, which is what I would get on the Commodore if I typed in SHIFT-A.

A statement like IF PEEK(653) = 0 THEN A$ = CHR$(PEEK(60289 + PEEK(197))) shows how the lookup tables work.

Let’s take a look now at the keyboard buffer, which is ten bytes from 631-640 ($0277-$0280) that hold as yet unprocessed key presses. Zero page byte 198 ($C6) holds the number of entries in the buffer – from 0 to 10. The little program below lets you watch the keyboard buffer being filled in, including tracking the number of entries. When an input statement is encountered, the buffer is read in, and the number entries reset to zero. So run the program, and press keys while the loops are running. Be sure that your last key is a RETURN.

100 FOR K = 1 TO 20 105 PRINT CHR$(147) 110 FOR J = 1 TO 5 120 FOR I = 631 TO 640 130 PRINT PEEK(I), 140 NEXT I 150 PRINT PEEK(198) : PRINT
160 NEXT J 170 NEXT K

Here is a kind of fun program that lists itself when run. It puts the characters “L”,”I”,”S”,”T” and RETURN into the buffer, and then the number of entries (5) into 198. When the program is finished, the buffer, including the RETURN, is read and processed by BASIC, and the program lists itself.:

That’s all for now. For a cultural dive into the 8-bit era, I recommend dusting off your copy of Orson Scott Card’s Ender’s Game – it’s a lot of fun.

5 print chr$(147)
10 u=10
20 dim a%(2^u + 1)
25 m = int((2^u)/log(2^u))
30 dim b%(int(1.5 * m))
50 print "sieve"
60 print "====="
70 print "limit","primes"," time"
80 print "-----","------"," ----"
100 for n = 8 to u
105 t = time
110 lm = 2^n : sm = int(sqr(lm))
200 for i = 1 to lm : a%(i)=1 : next i
210 a%(1) = 0
500 for p = 2 to sm
520 if a%(p) = 0 then 620
560 for ct = 2*p to lm step p
580 a%(ct) = 0
600 next ct
620 next p
630 pc = 0
640 for i = 1 to lm
660 if a%(i) = 0 then 700
680 pc = pc + 1
700 next i
705 s = (time-t)/60
706 s = int(100*s)/100
500 for p = 2 to sm
520 if a%(p) = 0 then 620
560 for ct = 2*p to lm step p
580 a%(ct) = 0
600 next ct
620 next p
630 pc = 0
640 for i = 1 to lm
660 if a%(i) = 0 then 700
680 pc = pc + 1
700 next i
705 s = (time-t)/60
706 s = int(100*s)/100
710 print "2^";n,pc,s
720 next n
1000 print : print "Trial"
1010 print "====="
1020 print "limit","primes"," time"
1030 print "-----","------"," ----"
1040 for n = 8 to u
1042 lm = 2^n
1045 b%(1) = 2
1050 t = time
1060 pc = 1
1070 for x = 3 to lm
1080 for y = 1 to pc
1090 w = x/b%(y)
1095 if w = int(w) then 1200
1100 if (b%(y)*b%(y)) > x then goto 1120
1110 next y
1120 pc = pc + 1
1130 b%(pc) = x
1200 next x
1270 s = (time-t)/60
1275 s = int(100*s)/100
1300 print "2^";n,pc,s
1400 next n

I wrote a short program in assembler, using Turbo Macro Pro, to implement a Sieve of Eratosthenes to find the 8 bit prime numbers.

Right click here to download the disk image: tmpsieve

Here is the screenshot:

The ML program places a basic program in memory that is just:

10 SYS 4096

So, once SIEVE is loaded, (LOAD “SIEVE”,8,1), all one has to do is type RUN to run the sieve.

chrout = $ffd2
sieve = $0900 ; 8 bit sieve
*= $0800
;the 00 byte that starts basic
.byte $00
; pointer to the next basic line
; which is at $080c
.byte $0c,$08
; line 10
.byte $0a,$00
; the basic token for sys
.byte $9e
; a space
.byte $20
; "4096"
.byte $34,$30,$39,$36
; 00 for the end of the basic line
.byte $00
; 00 00 for the end of the basic
; program
.byte $00,$00
w1 = $0880; working variable
w2 = $0881; working variable
w3 = $0882; working variable
w4 = $0883; working variable
w5 = $0884; working variable
hx = $0885; holder for x
hy = $0886; holder for y
hdig = $0887; hundreds digit
tdig = $0888; tens digit
odig = $0889; ones digit
head = $0890 ; header
; this is the header
; it is 68 bytes
*= $0890
;[clr][sp][sp][sp]
.byte $93,$20,$20,$20
;[sp][sp][sp][sp]
.byte $20,$20,$20,$20
;[sp]eig
.byte $20,$45,$49,$47
;ht[sp]b
.byte $48,$54,$20,$42
;it[sp]p
.byte $49,$54,$20,$50
;rime
.byte $52,$49,$4d,$45
;[sp]num
.byte $20,$4e,$55,$4d
;bers
.byte $42,$45,$52,$53
;[cr][sp][sp][sp]
.byte $0d,$20,$20,$20
;[sp][sp][sp][sp]
.byte $20,$20,$20,$20
;by[sp]a
.byte $42,$59,$20,$41
;[sp]sie
.byte $20,$53,$49,$45
;ve[sp]o
.byte $56,$45,$20,$4f
;f[sp]er
.byte $46,$20,$45,$52
;atos
.byte $41,$54,$4f,$53
;then
.byte $54,$48,$45,$4e
;es[cr][cr]
.byte $45,$53,$0d,$0d
*= $1000
jmp main
mult10 ; multiply by 10 subroutine
asl a
sta w1
asl a
asl a
adc w1
rts
; now, in order to output base-10
; values to the screen we need
; to get a char which represents
; each digit in the base-10
; representation of the 8-bit
; number to be output
; we will store the hundreds place
; digit in hdig, the tens plae
; digit in tdig, and the ones
; place digit in odig
; get hundreds digit subroutine
gethundig
cmp #$c8 ; is a gt 200?
bcc hless200
ldx #$02 ; if here, hdig is
stx hdig ; 2 so store it
sec
sbc #$c8 ; subtract 200 from
rts ; a and return
hless200
cmp #$64 ; is a gt 100?
bcc hless100
ldx #$01 ; if here, hdig is
stx hdig ; 1 so store it
sec
sbc #$64 ; subtract 100 from
rts ; a and return
hless100
ldx #$00 ; if here, hdig is
stx hdig ; 0 so store it
rts
gettendig
; when this sub is run, a is 0-99
; start by putting a 9 in x,
; multiplying that by 10, then
; seeing if 90 is higher than the
; test number - if not, tdig is 9,
; and so on
sta w2; preserve a
ldx #$09
tryaten
txa
jsr mult10
sta w3; w3 = 90,80,70,etc
lda w2
cmp w3
bcc tennotfound
txa ; we hav found our ten
sta tdig
lda w2
sec
sbc w3
rts ; we've found the digit,
; and left the ones place
; in a - so return
tennotfound
dex ; try the next lower
bne tryaten
ldy #$00; if here, tdig=0
sty tdig
lda w2
rts
getonedig
sta odig; by this time, the
; ones digit is all that
; is left in a
rts
getdigs
jsr gethundig
jsr gettendig
jsr getonedig
rts
printdigs
jsr getdigs
lda hdig
ora #$30; convert to ascii
jsr chrout
lda tdig
ora #$30
jsr chrout
lda odig
ora #$30
jsr chrout
lda #$20; two spaces
jsr chrout
jsr chrout
rts
dosieve
ldx #$00; use x to index loop1
lda #$01
loop1
sta sieve,x; fill the sieve wit
inx
bne loop1
lda #$00
ldx #$00
sta sieve,x; 0 is not prime
inx
sta sieve,x; 1 is not prime
; w1 is counter as we step
; w2 is step size
lda #$01
sta w1
sta w2
nextpass
inc w2; if we are on first pass
; through sieve, we just
; increased step from 1 to 2
; which is what we want
lda w2; the step size is also
; the first element in
; any pass
sta w1
cmp #$10; have we reached 16?
; if so, we ar done
bne not16
rts
not16
ldx w2
lda sieve,x; does x point to a
; zero in the sieve?
beq nextpass ; ifso x composite
; so move on
nextstep
lda w1
txa
beq nextpass; we've stepped all
; the way through
; sieve so increase
; step
lda w1
adc w2 ; add the step
bcs nextpass; if the carry flag
; is set we're past
; the end of the
; sieve so nextpass
sta w1
tax ; update x pointer
lda #$00
sta sieve,x; x not prime so
; mark with a zero
jmp nextstep
printheader
ldx #$00
phloop
lda head,x
jsr chrout
inx
cpx #$44
bne phloop
rts
main
jsr dosieve
jsr printheader
;sieve now marks primes
;with a 1 - step and
;print primes
ldy #$00
sty w5; w5 is primes on
; this current line
probesieve
lda sieve,y
beq movetonext
tya
stx hx
sty hy
jsr printdigs
ldy hy
ldx hx
inc w5
lda w5
cmp #$08; end of line?
bne movetonext
lda #$0d
jsr chrout
lda #$00
sta w5; reset line index
movetonext
iny
bne probesieve
lda #$0d
jsr chrout
jsr chrout
rts