The information I have states that the userport' serial speed is about 1200bps, but there are some carts going up to 9600bps. I would imagine that the 1200 is the limit, so I'm wondering how is the 9600 achieved ?

(I know there are expansion port adapters capable of even higher speeds, even 115200, my question is specifically about user-port ones).

  • I don't know how it's actually done, but I see two options: (1) Use the parallel port and an external shift register (2) The CIA data sheet says "max. usable baud rate is determined by line loading", so add some analog stuff to make it work faster. Do you know any more details about the "fast" cartridges, like their name? – dirkt Jun 23 '16 at 7:12
  • @dirk fast expansion carts are: Link232, Swiftlink (those do at least 38400bps) – Kuba Tyszko Jun 23 '16 at 8:21
  • Sorry, I phrased the question badly: Do you know any details, like names, about the fast (up to 9.6 kbps) cartridges for the userport? Then one can google and try to find out how they work. I found the EZ-232, and I'm looking into that now, but maybe there are others? – dirkt Jun 23 '16 at 9:11

The RS232 routines in the ROM of the C64 use port B of the CIA (PB0-PB7 on the userport) to input/output all RS232 signals including Tx and Rx. So these routines don't actually use the shift register capabilities of the CIA, and have to do the shifting, parity calculation and bit-banging in software. That's the reason the speed is limited to about 1200 bps.

If you connect the Tx and Rx signal instead of port B to the serial port (SP) of each of the two CIAs (SP1 and SP2 on the userport), you can do the shifting etc. in hardware. The driver for this "special" wiring was called "UP9600". The source for an adapted variant is e.g. here (the original link at jamtronix seems to be dead). Then indeed, as the CIA datasheet says, "the maximum usable baud rate is determined only by the line loading", and 9600 bps were possible.

So the 9600 bps were achieved by using the full capabilities of the hardware that was already present (and probably should have been used by the routines in the ROM in the first place).

An example of a schematic that used jumpers to allow both kinds of wiring can be found e.g. for the GLINK-LT User Port RS232 cartridge.

Today, many RS232-USB adapters use TTL levels anyway, so to connect to one of these, one even wouldn't need the level converters.

  • I have been trying to make my own terminal software using Vice emulator and TCPSER. But for some reason I am not able to get 2400 baud working with it using Kernal calls. I see that if I load up StrikeLink 2014, it works fine at 2400 baud. By adding some breakpoints I do however notice that StrikeLink is not calling the Kernal serial routines so obviously they have overcome the 1200 baud limit by some custom code without actually needing a hardware hack. Naturally for 9600 baud it requires the hardware hack and the special UP9600 code. – Johncl Nov 9 '17 at 15:25
  • It's not quite as simple as you make out. The CIA's shift register doesn't handle the parity calculations or the start and stop bits that are necessary to make asynchronous RS2322 serial signals work. That's why the kernal routines don't use it. I don't know what clever hack the UP9000 software used, but it's not at all obvious how they did it, and your link to the source code is dead. – user722 Dec 10 '18 at 18:36
  • @RossRidge: If performance is critical, using 256 bytes for a parity table is apt to be more practical than trying to compute parity any other way. I know there are some nice tricks that can reduce parity computation to a surprisingly small number of cycles, but they're not likely to be as fast as sequence: lda byte / sta $+1 / bit parityTable. – supercat Dec 11 '18 at 0:02
  • @supercat Calculating the parity is the easy part. Outputting and inputting the start and stop bits is the not at all obvious part. The CIA's shift registers just shift in and out 8 bits of data. They don't generate or accept the start and stop bits that make RS232 asynchronous serial ports work. The shift registers implement a synchronous serial interface instead, one that requires a external clock to determine the shift-in rate. An external clock that RS232 ports don't have. – user722 Dec 11 '18 at 1:45
  • @RossRidge: I found a new link to an adapted version of the source code (I don't know how much they adapted, maybe the original is still out there somewhere). They use a lookup table, and timer B for the receiving timing. I guess sending is just done by cycle counting, but I didn't verify that. – dirkt Dec 11 '18 at 7:09

At least 2400 bps requires no special hardware but extra software, see https://github.com/nanoflite/c64-up2400-cc65.


The design of a software UART on the 6502 involves some compromises between code size, RAM usage, and CPU loading. The Kernel's UART routines are designed to minimize code size and RAM usage at the expense of CPU loading. At higher baud rates, it's necessary to minimize CPU loading which in turn requires using code which is bigger and uses more RAM than would be needed at slower speeds.

BTW, I'm not sure why the UART pins are laid out as they are. If the receive data pin were located at bit 7 and transmit on bit 0, that would eliminate the need for any manual register saves or restores most of the times the interrupt fires. Even while transmitting and ready for reception, a typical invocation of the NMI could look like:

lsr txBuffer
rol $DD01
bit $DD0D ; Reset interrupt flag
bcc startBit1
inc $FFFB ; Service next interrupt with a routine 256 bytes higher

One would need to have many different interrupt service routines placed to allow code to switch among them efficiently, but the performance could be much faster than if code had to save and restore the values of the A, X, and/or Y registers every time the interrupt fires.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.