VIC20/C64 Serial Port

One project I wanted to play with was connecting the RS-232 port on the VIC-20/C64 to the other computers.  This should be a relatively simple task, although once connected, the door to Pandora's Box flaps wide open.  Knowing that, I decide to count on that small remaining piece of hope, and bravely charge ahead.

Step one: Build the interface.

The Commodore "User Port" is the inferface presented to provide what they shamelessly called the "RS-232 Port."  For the sake of cost-effective computers, we shan't berate them for providing timing and signals, but the signals are not the +/- 12-volt 20mA current source/sink drivers, but 0-5V CMOS-driven cignals similar to what are found on UARTs and microcontrollers.  Historically, these signals were lassed through MC1488 drivers and MC1489 receivers.

Since then, the world has evolved, and there are a wide field of transceivers available.  Being familiar with Analog Device's ADM3202, I decided this is a nice chip to use to build up the interface. Basically, it takes the 5V (4.88V measured) input power and steps it up to about 9.4V (measured), and then generate -9.1V (measured) for the negative side supply.  From there, pin "M" passes though the transmitter to pin 2 of a DB-9 connector. Pin 3 of the DB-9 connector passes back through the receiver, and to pins "C" and "B" of the User Port.  Don't forget to connect ground to pin 5 of the DB-9! Yes, the other signals are available on the User Port, and "RS-232" includes these signals.  However, I opted to not use these signals and go with the RxD/TxD signals only.  Adding the others would not be a big deal; I just wanted to get the port running, though.  You could configure the Vic-20/C64 as DCE, but then you need a "null modem" as well.  I have one..... uhm....... somewhere.......

Note: this configures the VIC-20/C-64 as date terminal equipment; not as data control equipment.  If you want the VIC-20/C64 to be the DCE instead of DTE, swap pins 2 and 3 on the DB-9 connector

(schematic to follow... someday...)

Step two: test the output.

The first thing I wanted to do was check the output of the RS-232 port.  Without writting assembly-code drivers, the best speed the VIC-20 (or C64) will provide is 2400 baud! No, I didn't leave off any zeros.  Now.... how do you get the VIC to talk to the RS232-port?

In Commodore-esque, you have to open a file, but here, the filename is actually a set of protocol specifiers.  The first character of the name is actually a "control register" that sets the stop bits, word length, and baud rate.  (documentation to be added)  To get 2400 baud, 8-N-1, this control register needs to be set to 10 (00001010 binary).

The second byte of the file name is atually a "command register."  This sets the parity options, duplex, and handshake options.  (documentation to be added)  Again, the values I want for 8-N-1 and given that I am only using RxD/TxD signals, I find that a value of 0 will work just fine.

After the channel is open, data is simply sent to the open channel using the PRINT# command.  So, I type the following:

OPEN 2,2,0,CHR$(10)+CHR$(0)

A quick note.... that trailing semicolon (;) on the print statement is just to tell the computer not to send a carriage-return after the character.  I only wanted to see the "A" come out.

Another thing to keep in mind, the baud rate of 2400 is not recommended for use in BASIC.  The highest baud rate recommended for use in BASIC is only 1200 baud.  We can get away with it in these simple examples because the issue is incomming data at higher baud rates comes in so fast that the BASIC program can not keep up; eventually, the input buffer overflows and data gets lost/over-written.  When purely sending data, the data can be sent out at higher baudrates, it's just that the data will end up being spaced out rather than compressed.  this doesn't hurt anything - it just means that the data is not transmitted as quickly as you might expect.

The following table can be used to determine the first CHR$ value:
CHR$ Baud
1 50
2 75
3 110
4 134.5
5 150
6 300
7 600
8 1200
9 1800
10 2400
11 3600
12 4800
13 7200
14 9600
15 19200

So, a quick check of the oscilloscope, and I see:

Yes, it seems to measure 2.5kHz instead of 2.4kHz, but when I zoom in tighter, I see:

This time, I find 2.38kHz.  Most of the other bits check out more accurately.

I also not that the voltage swing is nearly the full 20 Volts, so I decide to move on....

Step three: talk to big brother.

At this point, I decide to connect the RS-232 port to another computer and see if I can make them talk to each other.  Hmm... make sure you grab a serial cable that actually has the pins connected! This is where I make the sad discovery that I had originally typed CHR$(110) instead of CHR$(10) in my OPEN statement, which caused the data to be sent at about 37Hz, and 5 data bits instead of 8, so just garbage showed up.  Once I realized my error and fixed my OPEN statement, everything started working.... kind of.  You see, CBM character codes and ASCII character codes don't line up very well.  CBM used 26 upper-case letters and lots of graphics characters where ASCII uses 26 upper-case and 26 lower-case letters.  The upper-case letters align OK, but any lower-case letters in ASCII are graphics characters and shapes.  For this reason, CBM machines generally used only upper-case characters in programming, while IBM machines of that same era tended to be case-insensitive.

None-the-less, I was able to send characters and strings from the VIC-20 to the larger computer.  In fact, I thought it would be amusing to transfer a file from the VIC-20 to the larger computer:

OPEN 2,2,0,CHR$(10)+CHR$(0)

Ahhhh..... yes..... I forgot.  CBM, Micro$oft Windoze, and un*x all use different markers for the end-of-line. CBM uses a single character, "Carriage Return," which is character 13 in the CBM and ASCII tables.  Micro$oft uses a "Carriage Retrn/Line Feed" combination, so the end of line was always marked by a character 13 followed by a character 10.  And then, just for fun, un*x decided to use the "Line Feed" to mark the end of lines.  (dirty word!) The effect is, as each line is typed out, it fails to cause the screen to scroll; the cursore resets to the far left of the same line, and types over the previous line.  A little amusing, but totally ineffective for seeing the file.

So... on the host computer, I turn on capturing.  I will find a file named "capture" in my .seyon directory.  on the VIC-20, I re-type the LIST command.  I watch the dance of characters again, and when done, I stop the capture.  Now, on the host machine, I rename the capture file so I don't over-write it later, and I perform a little command-line magic:

cd ~/.seyon
mv capture VIC20_3-DMAZE.prg
perl -pi -e 's/\r/\n/g' VIC20_3-DMAZE.prg
cat VIC20_3-DMAZE.prg

And yippee! I see the lovely little program.  In short, the magic performed is the sed command, s/\r/\n/g. What this means is, "when you find a \r (carriage return), switch it with a \n (new line), and do this globally (thoughtout the entire stream)."  Of course, this means that if I wanted to edit the program on my host computer, then send it back to the VIC-20, I would have to switch everything back, using:

perl -pi -e 's/\n/\r/g' VIC20_3-DMAZE.prg

Alternatively, I can use sed to accomplish the same things:

cat capture | sed "s/^M/&\n/g" > my.prg

Note that the ^M is not a pair of characters, it is generated by typing Ctrl-V Ctrl-M to get the sequence.  Also, the quotes are important; the whole thing fails to even run if the quotes are left out.

However..... there is another problem with sending the program back to the VIC-20.  The CMD command works great for redirecting output to the host.  It does nothing in terms of changing the input, so while the CMD 2 sends output to the RS-232 port, nothing tells the VIC-20 to use the RS-232 port as the default input stream; it still gets its input from the keyboard.  The closes CBM commands are INPUT# and GET#.  For various reasons (primarily buffering errors locking the computer), INPUT# should be avoided, and GET# is the prefered command.  BUT.  And this is a big but.... the GET# (and the INPUT#) commands can only be used within a program.  They can not be used from the command-line, and thus, can not be used to load a program.

January 2018 Addendum

Just for fun, I was playing around with the VIC-20's bigger, younger brother, the Commodore 64.  Generally, everything is the same; the "USER port" is the same so the hardware works the same, and the commands work the same. I did find one useful tidbit, though. If the serial port is opened with a file number greater than 127, the system automatically appends a newline character after each carriage return. So, the sequence above can be changed to

OPEN 128,2,0,CHR$(10)+CHR$(0)
CMD 128

and the listing will now include the standard DOS/Windoze carriage-return/linefeed combination.  A little cleanup will still be a good idea when connectng to a Un*x/Linux machine, which doesn't care for the carriage-return.

I also attempted to use Abacus Software's "Super-C Compiler" to communicate with the serial port.  To say this was disappointing is a huge understatement.  They apparently tried to write their own serial-port drivers, presumably because BASIC places the serial port buffers at the end of the BASIC program stores in memory, but the 'C' program is structuerd entirely differently, so the location of these buffers is... well... only Abacus knows. Even low baud rates did not work very well, so this option will only work if you are willing to drop to assembly language programming.


At the physical layer, the RS-232 port works when a bit of external circuitry is added.  As long as the baud rate is kept below 2400, the data layer works well, also.  Above these layers, however, things get sticky.  Out-of-the-box, as long as users use upper-case letters and most numbers and punctuation, everything is OK.  But moving beyond that, it falls to programming to manage ASCII/CBM conversions, and cope with transfering files to/from the VIC-20/C-64.

Wenton's email (