Mandelbrot Set on the C64

Well, it took 36 hours, but at last my C64 finished calculating and drawing the non-diverging section of the Mandelbrot set.

Mandelbrot Set on a C64

Here is the code, in Basic:

10 rem ** mandelbrot hires
20 rem
40 yu = 1.5 : yd = -1.5
50 xu = 2 : xd = -2
60 xr = xu – xd : yr = yu – yd
70 xs = xr/320 : ys = yr/200
100 rem set vic address
110 v = 53248
120 rem set graphic ram address
130 ga = 8192
140 rem set video ram address
150 vr = 1024
160 rem set border to black
170 poke v+32,0

500 gosub 20000 : rem turn on hires
510 gosub 21000 : rem graphic ram area
520 gosub 22000 : rem set color ram
530 gosub 23000 : rem clr graphic ram

1000 for xc = 0 to 319
1010 for yc = 0 to 199
1012 x0 = xc*(xr/320) + xd

1014 y0 = (yc-200)*(yr/200) – yd
1020 x = 0
1030 y = 0
1040 it = 0
1050 mx = 250
1060 a = x*x+y*y
1070 if a > 4 then goto 1200
1080 b = x*x-y*y + x0
1090 y = 2*x*y + y0
1100 x = b
1110 it = it + 1
1120 if it < mx then goto 1060
1150 gosub 30000 : rem set (xc,yc)
1200 next yc
1210 next xc
1400 poke 198,0 : wait 198,1
1450 gosub 24000
1500 end

20000 rem turn on hi res graphics
20010 rem 1. set bits 5/6 of v+17
20020 rem 2. clr bit 4 of v+22
20030 poke v+17,peek(v+17) or (11*16)
20040 poke v+22,peek(v+22) and (255-16)
20050 return
21000 rem set graphic ram area
21010 rem 1. set bit 3 of v+24
21020 poke v+24, peek(v+24) or 8
21030 return
22000 rem set color ram
22010 rem 1. color ram is 1024-2023
22020 rem 2. set background 1 – white
22030 rem 3. set foreground 0 – black
22040 co = 0*16 + 1
22050 for i = vr to vr+1000
22060 poke i,co
22070 next i
22080 return
23000 rem clear graphic ram
23010 rem 1. graphic ram is ga to
23020 rem ga + 8000
23030 for i = ga to ga +8000
23040 poke i,0
23050 next i
23060 return

24000 rem turn graphics off
24010 rem 1. clr bits 5/6 of v+17
24020 rem 2. clr bit 4 of v+22
24030 rem 3. clr bit 3 of v+24
24040 poke v+17,peek(v+17) and (255-96)
24050 poke v+22,peek(v+22) and (255-16)
24060 poke v+24,peek(v+24) and (255-8)
24070 return
30000 rem set pixel
30010 ra = 320*int(yc/8)+(yc and 7)

30020 ba = 8*int(xc/8)
30030 ma = 2^(7-(xc and 7))
30040 ad = ga + ra + ba
30050 poke ad,peek(ad) or ma
30060 return
31000 rem clr pixel
31010 ra = 320*int(yc/8)+(yc and 7)

31020 ba = 8*int(xc/8)
31030 ma = 255-2^(7-(xc and 7))
31040 ad = ga + ra + ba
31050 poke ad,peek(ad) or ma
31060 return

A keyboard sonata …

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:

C64 Keyboard Matrix

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.

my-keyboard-matrix

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.

keyboard-codes

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. 

You can find an excellent PETSCII table here.

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.:

self-list

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.

ender's_game_cover_isbn_0312932081