Subscribe via RSS

Parallel Port: Digital to Analog

Most parallel ports on most computers (Amiga, C64, PC, etc...) have (at least) 8 true digital pins that one can interface with for either input or output. This can be extended using shift registers or multiplexers; I've written up an example of this here.

With a lot more pins at one's disposal, more items can be controlled; as long as what you want to control is in the digital realm. To control a standard DC motor, for example, you'll need to be in the analog world and we'll describe how to get there below.

Digital to Analog Converters

The basic principal is to use a number of digital inputs and map these to relevant resistance values on an output. If you had 4 pins available, then you could determine the maximum resistance you needed and divide by the number of pins. You'd then make sure that, as each pin was brought HIGH, that the resistance summed towards the final value that you required.

You can either use a bunch of resistors to do this, or an integrated DAC circuit.

R/2R Ladder

This circuit consists of a bunch of resisters in parallel/serial, using properties of such combinations to provide a stepped resistance output. This is known as an R/2R Ladder and is a popular method for converting digital signals to analog.

We're going to implement one using 10k/20k resistors and an LM358 opamp to buffer the output. This part of the circuit is borrowed from the 8-bit digital to analog converter circuit over at IKALOGIC.

Combining it into our Parallel Port Interface

The 8 outputs from the 595 need to be de-coupled from the LEDs and provided as the inputs to the resistor ladder.


As that we can send any value to the 595, you don't really need to be careful as to which end is the MSB, but do remind yourself of it as it'll become important when writing the software.

DSC03731 DSC03732 DSC03733

You should now be able to check the voltage on pins 1 or 2 to see the variance between 0 and 5v when you're switching the bits on and off.

DSC03740 DSC03745 DSC03746

To my surprise, the output voltage was nearly exactly double the byte value being output by both the Commodore 64 and the Windows Parallel Port.

Once the test breadboard produced the result I wanted, I confirmed it all by soldering it together on the PCB. Not the ugliest mess I've created, but not very far off! And... it works.

DSC03755 DSC03754 DSC03751

What's next?

Now that we have an analog interface, we can control a PWM throttle. To do this, we'll go back to the Arduino world and steal a motor controller. I've previously worked with H-Bridges before and will use a module to make this easy. Controlling direction will be easy also!


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.


D:\>cd cc65

D:\cc65>set path=d:\cc65\bin

cc65: No input files


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


 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


Quickly check that there's a binary called 'ascii' in the folder with no extension.


 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.

VICE-Select 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);
		for (zzz = 0; zzz < 1000; zzz++) {}

int main (void)
    unsigned char XSize, YSize;
    int i = 0, z = 0, zz = 0;

    //set all pins to output.
    /* 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.


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 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!


Parallel Port: Shift Registers

Now that we've controlled a LED by a single pin on the Parallel Port, it's time to expand our abilities. The goal of this post is to describe the usage of Shift Registers and then consume a minimal amount of pins, 3 out of the 8 available, to control up to 64 outputs!

A Shift What?

So, you have 8 digital input/output pins on the Parallel Port. This isn't the greatest amount and it'd be really nice to extend the amount available. There are a few ways, but a popular method is to employ a shift register.

A shift register is an IC which takes a 'serial' input and then outputs this on it's output pins in 'parallel'. By 'serial', we mean that the IC accepts input data on one pin only. This data is provided by you, as either a '1' or a '0'. Once you've set the value on the input pin, you then toggle another pin, known as the 'clock' pin, to tell the IC that the data is ready to be consumed.

The shift register, once the clock pin is toggled to a '1' and then back to a '0', will look at the input pin and then copy this value into it's internal register. We're using a 74HC595 which has 8 internal registers and therefore 8 output pins. At this point, the IC shifts all values on it's 8 internal registers up one slot and then sets the very first internal slot to the value you've provided.

You can then provide 7 more values, toggling the clock pin between each, an the IC will shift them along it's internal registers. What you've now done is set an 8-bit value inside this IC. Once the IC contains the value you've requested, you toggle the 'latch' pin and the IC will shift these 8 values out to the 8 output pins. These are known as 'parallel' as there is a single pin for each value; as opposed to the input which is serial (or one-after-another) on a single pin.

So, we've used 3 pins/wires here to control 8 output pins... pretty neat hey? We've turned our original 8 Parallel Port pins into (8-3) + 8... 13!

But wait, there's more! There's an 'output' pin on the shift register that reports the 'shifted off' value. What can you do with this? Feed it into a neighbour shift register as the 'input'. Hook up the same clock/latch wires as the first register and then, when you shift the 9th bit into the first, the very first bit you shifted in will be shifted out of the first register and into the second.

This means that, with the same 3 wires, you now have 16 outputs! Of course, you can keep doing this. I can't find anywhere that mentions how many you can effectively chain up. Of course, as you add more, the time to toggle out the values increases and you need to make sure that your code/hardware has time to think about other things instead of spending it all talking to the shift registers.

Connecting it to our previous circuit

We only used one line of the parallel port in the previous circuit and therefore only controlled one LED. For this first shift register, we'll need 3 lines. First thing is to hook up three LEDs with matching resistors. We could just hook two other lines up direct to our new shift register, but I like being able to visualise the data (and troubleshoot issues!)

Parallel-Port with 74HC595With this circuit, you'll be able to use the sample code in the previous post to control the 3 LEDs.

Sending out a 1, 2 or 4 should switch them on individually. Sending any combination of these, by adding them together, will turn on the associated LEDs. For example, sending out the decimal value 3 will switch on the first and second LED. The value 5 will switch on the first and third LED. As the value makes it to the port, it is split into it's individual bits, which are then translated to the data pins.

Once these are working, we're going to splice in some opto-couplers. We don't want any untoward voltages getting back to the parallel port. Optocouplers contain an LED and an internal light sensor. When power is applied to the input, the LED lights and the light sensor receives the signal. This is then output to the secondary circuit. This effectively provides an 'air gap' between the two circuits.

From these couplers we can control our shift register(s). Hook the three outputs to the 74HC595 shift registers SERIAL, CLOCK and LATCH pins. Remember the order as they each play key roles (as per the description of how they work above.)

Once you're ready, check that the three input LEDs react accordingly to basic Parallel Port data. Note that you may get erroneous data coming out of the shift register from the get-go. Data coming off the Parallel Port during system boot cannot be controlled and may cause havoc. We'll do something about this in a later article.

Building this required a LED array... you could do it easier and get one of those bar-graph arrays. Wiring up all the individual LEDs gets a little tricky.

DSC03682 DSC03683 DSC03684

Controlling the data output

Based on the initial description of the shift register, we know that we have to control the 3 data lines in a special sequence. First thing we need is an 8-bit data value to send out. Once we have this we can send each data bit out via the SERIAL line; toggling the CLOCK signal in-between. Finally, toggling the LATCH should see our value displayed in a glorious binary LED representation!

I've used Windows and the Parallel Port code here to manually try and turn on LEDs. My wires are hooked up as D0:SERIAL, D1:CLOCK and D2:LATCH. I am going to send 00000001 as the value to ensure that all LEDs are turned off bar the first.

  1. Ensure that LATCH is low (red)
  2. Toggle D0 to RED, this is a '0' for the first bit of the serial value.
  3. Now toggle the CLOCK (D1) on and off 7 times.
  4. Toggle D0 to GREEN.
  5. Toggle the CLOCK on/off once more.
  6. Toggle D2 on and off...

If everything is hooked up, then you should now have one LED showing on the output of the 74HC595. It didn't quite work with this initial circuit... LEDs would light a little randomly.

DSC03685 DSC03691 DSC03693


Although the above circuit worked, it was not reliable! Every now and then one LED (or two) past the one I wanted to switch on would also switch on! Sure, my soldering was dodgy, let alone the wiring also being messy. Either way, noise was being introduced and the flipping of the serial and clock was jumbled, causing random output.

The solution to this was to put a 100nf capacitor across the +5v and gnd supplying the 74hc595. This cap should be put as close to the IC pins as possible. Once in place, this stabilised the data from the PC Parallel port.


- HowTo: Understand the 74HC595 (David Caffey)
- Gammon Forum: Using a 74HC595 output shift register as a port-expander
- protostack: Introduction to 74HC595 shift register – Controlling 16 LEDs

What's Next?

Next is a 12v throttle. I intend on using the described sample here. The only issue is that it wants a potentiometer to vary the output voltage. This is an analogue method; we'll convert it to digital by calculating 8 resistor values to imitate the throttle at 8 positions.

I'll write this up soon once I've completed the circuit.