The serial port
A 6551 ACIA at $9000, running 19200 baud, eight data bits, no parity, one stop bit. The Kernal sets it up at power-on and wires its receive interrupt into the same ring buffer the keyboards use, so incoming serial needs nothing from you: it arrives through Chrin like any other typing.
Going out is where the choices are.
Sending
Chrout | Wherever the console is pointed |
SerialChrout | Down the cable, whatever the console is doing |
SetIOMode | Point the console at the cable, or back at the screen |
SerialChrout is the useful one on a machine with a monitor: your program keeps printing to the screen, and sends a running commentary to a terminal on your laptop at the same time.
lda #<Trace
ldy #>Trace
jsr GetIOMode
pha
lda #1
jsr SetIOMode ; console → serial
jsr PrintStr ; ... so PrintStr goes down the cable
pla
jsr SetIOMode ; and backThere is no SerialPrintStr, which is why the mode gets flipped around PrintStr like that. For a single character SerialChrout is simpler.
The registers, if you want them
SC_DATA $9000 | Read a received byte, write one to send |
SC_STATUS $9001 | Read: bit 7 set means this chip caused the interrupt |
SC_RESET $9001 | Write anything to reset the chip |
SC_CMD $9002 | Receive interrupts, and the RTS line — which carries the transmitter with it |
SC_CTRL $9003 | Baud rate and framing — $1F is 19200 8-N-1 |
The Kernal does the flow control for you: as the ring buffer fills, the interrupt handler raises RTS to ask the other end to stop, and reading the characters back out with Chrin drops it again once the buffer has drained. Sending is tangled up in the same register — the bits that raise RTS switch the transmitter off along with it — so the Kernal lowers the line again for as long as each outgoing byte takes to leave. Talk to SC_CMD yourself and you are taking all of that over, the transmitter included.
Changing the baud rate
Write a different framing byte to SC_CTRL. Both ends have to agree, and the machine at the other end of the cable is usually the harder one to change — which is why 19200 is the default and why nothing in these guides moves it.
Moving files: XModem
XModem is the protocol behind LOAD and SAVE when there is no card in the machine, and it is available directly:
XModemLoad | Receive. XFER_PTR = where to put it. On return XFER_REMAIN = how many bytes arrived |
XModemSave | Send. XFER_PTR = the bytes, XFER_REMAIN = how many |
Both answer with the carry flag: clear worked, set failed.
lda #<Incoming
sta XFER_PTR
lda #>Incoming
sta XFER_PTR + 1
jsr XModemLoad
bcs TransferFailed
; XFER_REMAIN holds the byte countUnderneath it is the 1977 protocol, unchanged: 128-byte blocks, each with a block number, its complement, and a one-byte checksum; the receiver starts the conversation by sending NAK until a block arrives; every block is answered ACK or NAK; the sender finishes with EOT. Because the receiver leads, the order of operations at the two ends matters — start the receiving side first, then the sending side, and the transfer begins on its own.
A transfer is not a background job
XModemLoad and XModemSave do not return until the transfer is over, one way or the other. Nothing else in your program runs while a file is moving, and a cable pulled halfway through is a retry loop followed by a set carry flag.
Since blocks are 128 bytes and files rarely divide by 128, the last block is padded. XFER_REMAIN counts the padding too — it is the number of bytes received, not the length of the file that was sent. Anything that needs an exact length carries its own header, which is what a .prg does with its first two bytes.
When there is no serial card
The Kernal checks. HW_SC in HW_PRESENT says whether the card is fitted, and the console falls back to the screen when it is not. Guard your own transfers the same way:
lda HW_PRESENT
and #HW_SC
beq NoCable
