Subscribe via RSS
27May/160

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.

VICE-Select

Once you see 'ascii' (or whatever you compiled) double-click it.

VICE-ascii-running

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.

DSC03724 DSC03723 DSC03725

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.

26May/160

Commodore 64: Wiring up the User Port

The User Port on the Commodore 64 is a combined parallel port + serial port. Both of these can be used after a little wiring effort. Memotronics has the ports available, with nice handles. The handle is imperative for ease of removal. You really don't want to be pulling on wires to unplug anexternal device! I purchased this and also couldn't resist the 200 random LED bag.

DSC03680 DSC03681 DSC03703

User Port Socket Alignment

There's a row of letters and a row of numbers... make sure the numbers are along the top. Looking at the back of the C64, you'll notice there's two slots for socket alignment. To the right are two pins, then the center block, then 1 pin on the left.

DSC03705 DSC03706

Yes, the socket is plugged on upside-down in that middle picture!

DSC03711 DSC03712

The socket from memotronics comes with two white spacers in a plastic bag. Slide them into the slots appropriate for each side of the socket. Once done, tin up your wires and solder through to the parallel port.

Connecting it to our circuit

The whole reason behind this is to control the circuit described here and here. The goal was to use a standard interface, being the parallel port. The Amiga and the PC happily work; the Commodore was next. As mentioned above, the data pins are there, we just need to wire it through.

One really good point: once you've attached the wire to the parallel port end, draw a black line down pin one to the other end. Knowing which wire is which will get confusing once you've jammed it through the user port socket 'cap'. I actually surprised myself expecting to see the black line elsewhere and was glad I'd drawn it.

With the User Port socket in hand, I reviewed a few documents to work out which pins did what. All pinouts was a good source, until I realised that 'I' was missing. I assumed they got it wrong and so went searching further. The c64 Wiki has a pinout also, which also doesn't include 'I'. What's that old saying? No 'I' in 'Team'? There's no 'I' in 'User Port' either...

One quick check elsewhere at hardwarebook.info told me that the port I had from memotronics, which has an 'I', is incorrect. So... just make sure that the

The 'middle' 8 lower pins of the socket are the data pins that 1-to-1 match D0-D7 of the parallel port. You can then use Pin 1 and 12 to GND on the parallel (18-25) and yes, 17 is GND, but not on Amiga!

DSC03708 DSC03709 DSC03710

DSC03713 DSC03717 DSC03718

Putting it all together

Everything plugged together nicely... I powered up the circuit first; you should always make sure perpipherals are powered on first, prior to turning on the C64. Either way, LEDs lit as per usual and then it was time to hack in BASIC. You can see on the screen that I tried to output an 8-bit binary value to the 595... but the results weren't stable. It was late and I'd accomplished the first task: The pins were wired in and the LEDs between the optos and the C64 lit as expected!

DSC03720 DSC03721 DSC03722

What's Next?

Either I continue hacking BASIC and get the 595 driven, or I use something like cc65 and write real C. I think I'll go the latter. I also want to hook up the Serial port here... so I'll extend my little dongle to have a serial port hanging out of it also.

See this post on cc65 for an example of how to program the Usre Port via C!