Commodore 64: cc65 and the User Port
Ok, we've build the user port to parallel port adapter. We've build the parallel port interface. We've hooked a shift register onto it and now we want to control it. Doing this through BASIC would be fun... but compiling real C code and getting this onto the C64 will be thoroughly more enjoyable. Go and download cc65, along with your favourite emulator. VICE works very well on Windows.
Getting a sample running
Before we even think of real hardware, let's get the samples running on VICE. Once you have everything installed, open a command prompt. Switch to the directory where cc65 is installed. Also set your path to the cc65/c64/bin folder. Run cc65 to check if you've got everything correct.
Microsoft Windows [Version 10.0.10586] (c) 2015 Microsoft Corporation. All rights reserved. C:\Users\windows_user_1>d: D:\>cd cc65 D:\cc65>set path=d:\cc65\bin D:\cc65>cc65 cc65: No input files D:\cc65>
Download the samples from the github repository and save them nearby. Switch into the samples folder. Choose a sample to compile; I chose ascii. Compile it. Don't expect much from the compiler if there is no error. We're going to use cl65 here which also does all the linking for us.
D:\cc65>cd samples D:\cc65\samples>dir Volume in drive D is Data Directory of D:\cc65\samples 26/05/2016 12:40 PM <DIR> . 26/05/2016 12:40 PM <DIR> .. 17/05/2016 02:26 PM 2,300 ascii.c 17/05/2016 02:26 PM 8,068 diodemo.c 17/05/2016 02:26 PM 2,455 enumdevdir.c 17/05/2016 02:26 PM 6,928 fire.c 17/05/2016 02:26 PM <DIR> geos 17/05/2016 02:26 PM 6,592 gunzip65.c 17/05/2016 02:26 PM 1,956 hello.c 17/05/2016 02:26 PM 3,772 Makefile 17/05/2016 02:26 PM 3,711 mandelbrot.c 17/05/2016 02:26 PM 7,345 mousetest.c 17/05/2016 02:26 PM 6,236 multidemo.c 17/05/2016 02:26 PM 69,766 nachtm.c 17/05/2016 02:26 PM 3,117 overlaydemo.c 17/05/2016 02:26 PM 8,573 plasma.c 17/05/2016 02:26 PM 5,865 README 17/05/2016 02:26 PM 2,876 sieve.c 17/05/2016 02:26 PM 5,269 tgidemo.c 17/05/2016 02:26 PM <DIR> tutorial 16 File(s) 144,829 bytes 4 Dir(s) 1,717,242,986,496 bytes free D:\cc65\samples>cl65 -O -t c64 ascii.c D:\cc65\samples>
Quickly check that there's a binary called 'ascii' in the folder with no extension.
D:\cc65\samples>dir Volume in drive D is Data Directory of D:\cc65\samples 26/05/2016 12:52 PM <DIR> . 26/05/2016 12:52 PM <DIR> .. 26/05/2016 12:52 PM 2,648 ascii 17/05/2016 02:26 PM 2,300 ascii.c 26/05/2016 12:52 PM 2,767 ascii.o ...
You've got a compiled application! Let's run it. Open up VICE (x64.exe is the c64 version) and choose File -> Autostart disk/tape image. You'll need to browse to where you compiled the sample and set the filter to all files.
Once you see 'ascii' (or whatever you compiled) double-click it.
Feel free to play with the other samples and see what C code is available and explained.
Poking the Port
BASIC had two commands that altered system memory. PEEK and POKE were essentially READ and WRITE. They allowed the user to change values in RAM or read values back. Hitting certain addresses did certain things. Specifically, on the C64, POKING 56579 altered the input/output configuration of the User Port and then POKING 56577 changed the values being sent.
To do this in C, we need equivalent functions. Fortunately, the cc65 wiki has all the information we need. It turns out that no function is required, you can simply write to ports by setting the address (via a pointer) to the value you require. To make it a little less difficult to read, they've also provided macros to do the same thing. They'll help when you come back to the code 6 months down the track and can't read a thing you wrote!
#define POKE(addr,val) (*(unsigned char*) (addr) = (val)) #define POKEW(addr,val) (*(unsigned*) (addr) = (val)) #define PEEK(addr) (*(unsigned char*) (addr)) #define PEEKW(addr) (*(unsigned*) (addr))
There's also pokes for 'WORDs' there, but we don't really need them. Look here for a huge list of what you can PEEK and POKE. Turns out there's more memory address to poke here.
Note: These defines are also in peekpoke.h, just include that!
Moving the cursor
By default, as per CMD or any other Console, the text just rolls down the screen. Scrolling (with a buffer) is something that you actually need to implement in C64, so either start preparing for a buffer, or just get ready to use a single screen and clean up after yourself.
I want to draw a diagram of the 8 LEDs I'm about to control, so for this purpose we'll need to be able to place characters at certain positions. This involves moving the cursor to the required location and then outputting the character.
Fortunately, functions are already there to do all this... just use the gotoxy as per the source listing below.
Controlling the parallel port and 74HC595
So, we now have everything we need to write out the data required to control the 595. I'll just list it below. There's more information back here on how it actually works.
#include <stdlib.h> #include <string.h> #include <conio.h> #include <joystick.h> #include <peekpoke.h> #define USERPORT_DATA 0xDD01 #define USERPORT_DDR 0xDD03 static const char Text [] = "Train Controller!"; void shiftout(int val) { int i = 0, zzz = 0; for (i = 7; i >= 0; i--) { POKE(USERPORT_DATA, (val & (1 << i)) == val ? 1 : 0); POKE(USERPORT_DATA , 2); for (zzz = 0; zzz < 1000; zzz++) {} POKE(USERPORT_DATA , 0); } POKE(USERPORT_DATA , 4); POKE(USERPORT_DATA , 0); } int main (void) { unsigned char XSize, YSize; int i = 0, z = 0, zz = 0; //set all pins to output. POKE(USERPORT_DDR, 255); /* Set screen colors, hide the cursor */ textcolor (COLOR_WHITE); bordercolor (COLOR_BLACK); bgcolor (COLOR_BLACK); cursor (0); /* Clear the screen, put cursor in upper left corner */ clrscr (); /* Ask for the screen size */ screensize (&XSize, &YSize); /* Top line */ cputc (CH_ULCORNER); chline (XSize - 2); cputc (CH_URCORNER); /* Vertical line, left side */ cvlinexy (0, 1, YSize - 2); /* Bottom line */ cputc (CH_LLCORNER); chline (XSize - 2); cputc (CH_LRCORNER); /* Vertical line, right side */ cvlinexy (XSize - 1, 1, YSize - 2); /* Write the greeting in the mid of the screen */ gotoxy ((XSize - strlen (Text)) / 2, YSize / 2); cprintf ("%s", Text); /* MARQUEE */ for (zz = 0; zz < 4; zz++) { for (i = 0; i < 8; i++) { shiftout(1 << i); for (z = 0; z < 8; z++) { gotoxy (((XSize - 15) / 2) + (z * 2), (YSize / 2) + 4); cputc((z == i ? 'X' : '_')); } } for (i = 7; i >= 0; i--) { shiftout(1 << i); for (z = 0; z < 8; z++) { gotoxy (((XSize - 15) / 2) + (z * 2), (YSize / 2) + 4); cputc((z == i ? 'X' : '_')); } } } /* Wait for the user to press a key */ (void) cgetc (); /* Clear the screen again */ clrscr (); /* Done */ return EXIT_SUCCESS; }
Note: some cputc calls reference defined characters such as CH_URCORNER, etc... These are PETSCII, the embedded character set of the Commodore. The wiki page has the numbers for the characters. Go to the include folder of cc65 and then cbm.h to see if the character is defined, otherwise just put the value in as a raw number.
And the result.. the bloody thing worked first go. Pretty scary actually... compiling and executing C code on the C64 was too easy. Of course, I cheated by using an SD2IEC. The load command was LOAD "0:TRAINCTL",8 followed by RUN.
You'll note that my keys are still on order... I can't wait for them to arrive as pressing 8 is tedious. Also that the last shot shows two Xs lit. Blame shutter speed and screen refresh times.
What's next?
Maybe it's time to hook up the serial port? nanoflite has provided a C driver which may well help us. Otherwise it's time to write a real railway controller for the parallel port interface... but I should actually finish that first.