It didn’t work. Not straight away, anyhow.
Once I’d wired up the 6551 ACIA chip, my UART of choice to give the Zolatron 64 a serial port, I threw together some code to test it. It was pretty simple – it took my already known-good code that prints a message to the LCD and added to that a routine to send a fixed message out over the serial port.
The ACIA lives at $B000 on my address map. So one of the things we do is define the addresses of its various registers, some of the settings we’ll want to apply and some bit masks.
; ACIA addresses ACIA_DATA_REG = $B000 ; transmit/receive data register ACIA_STAT_REG = $B001 ; status register ACIA_CMD_REG = $B002 ; command register ACIA_CTRL_REG = $B003 ; control register ; Following are values for the control register, setting eight data bits, ; no parity, 1 stop bit and use of the internal baud rate generator ACIA_8N1_2400 = %10011010 ACIA_8N1_9600 = %10011110 ACIA_8N1_19K2 = %10011111 ; Value for the command register: No parity, echo normal, RTS low with no IRQ, ; IRQ enabled on receive, data terminal ready ACIA_CMD_CFG = %00001011 ; Mask values to be ANDed with status reg to check state of ACIA ACIA_IRQ_SET = %10000000 ACIA_TX_RDY_BIT = %00010000 ACIA_RX_RDY_BIT = %00001000
We set up the ACIA:
; SETUP ACIA lda #0 sta ACIA_STAT_REG ; reset ACIA sta ACIA_INFO_REG ; also zero-out info register lda #ACIA_8N1_9600 ; set control register config sta ACIA_CTRL_REG lda #ACIA_CMD_CFG ; set command register config sta ACIA_CMD_REG
Towards the end of the code, I have a serial message defined. This ends with a line feed (ASCII 10) and then a null byte terminator:
.serial_msg equs "Zolatron 64 serial message" equb 10 equb 0
And here’s the routine that prints it to the serial port, which I implemented as a sub-routine because I intend to generalise it later. I’m using the X register as an offset counter, to determine which character in the message we want to send next. We pull that character into the Accumulator and then write that character to the data register. Because we’re in write mode and are addressing that register, this has the effect of telling the ACIA to send the character. It’s really that simple. We keep incrementing X until the character it pulls in is ASCII 0, at which point we’re done.
.serial_msg_send ; SERIAL MESSAGE LOOP ldx #0 ; set message offset to 0 .send_char lda serial_msg,x ; load next char beq serial_send_end ; if char is 0, we've finished jsr acia_wait_send_clr sta ACIA_DATA_REG inx jmp send_char .serial_send_end rts
Not so fast
You’ll note how this routine calls another subroutine, acia_wait_send_clr. This is meant to check that the previous character has been sent and the ACIA is clear to receive another incoming character. The subroutine waits until this is the case. And here’s how I first implemented it:
.acia_wait_send_clr pha ; these five lines are how it should be done lda ACIA_STAT_REG and #ACIA_TX_RDY_BIT beq acia_wait_send_clr pla
The idea is that we load the ACIA’s status register into A and AND it against a bit mask to check the state of the TX ready bit. If this produces zero, then we’re not ready, so loop around again. There are two other important instructions – the initial PHA pushes the contents of the Accumulator on to the stack, to preserve its contents. At the end of the routine, we pull those contents back from the stack and into A. That way, we don’t muck up anything important.
Study that bit of code. It contains a fatal error.
The main section of the code has an eternal loop that calls the routine to send the message, then delays for a brief moment, then does it all again, forever.
Huh? What now?
Like I said. It didn’t work. On the plus side, the message was appearing on the serial port, so something was working. I have the Zolatron hooked up to my electronics bench computer (a LattePanda Alpha) via an Adafruit FTDI Friend. With CuteCom running on the Alpha I could see the Zolatron valiantly attemtping to communicate. But all I ever got was the first two characters of the message and then nothing. No looping.
I suspected that one of the bugs present in various versions of the 6551, as discussed last time, might be the culprit. So I edited the .acia_wait_send_clr subroutine, replacing the check for the ready bit with just a very short loop. Basically, I put a value into the X register and then decremented it until it was zero.
That worked. Hmmm. It must be to do with those bugs, right?
I tried playing with different starting values for the X register, with the aim of reducing the delay to as short a time as possible. I’d started with $FF, which worked, so next I tried the opposite extreme and started with $01. That didn’t work at all. So then I tried a mid-way point, $F0, and that worked. I was about to start working my way down when I spotted something in the .acia_wait_send_clr subroutine code – actually in the original version, which I’d simply commented out.
The fatal error I mentioned earlier jumped out at me. Every time the ready bit wasn’t ready and I had to go around the loop again, I was including the instruction to push the Accumulator contents to the stack. But this is something that should have happened only once. I was probably overflowing the stack. Doh!
I got rid of the delay loop and changed the code to read like this:
.acia_wait_send_clr pha ; preserve A state on stack .acia_wait_send_loop lda ACIA_STAT_REG ; get the status register state and #ACIA_TX_RDY_BIT ; compare with ready bit beq acia_wait_send_loop ; if not set, loop pla ; recover state of A rts
Now there’s only one push to the stack. The loop returns to a point just after this push.
The reason my delay loop version had worked is that I’d commented out the PHA and PLA. I didn’t need them because the loop didn’t use the Accumulator. This revised code worked!
Okay. Now we have a working serial port, it’s time to test all those 6551 chips I bought. To recap, I ended up with three kinds.
- R6551 chips, which have the CTS bug.
- R65C51 Rockwell chips, which are supposed to be okay.
- R65C51 Western Digital chips – at least, that’s what the seller said they were, but when they turned up they were branded ‘GT Eµ’.
The last one of those was what I had in the Zolatron when everything started working. So I was confident about that. The weird thing is, if it had been a genuine WD chip, it shouldn’t have worked. That’s the version with the stuck-high ready to send bit in the Transmit Data Register. I’d already established that a ready-to-send routine that basically does nothing except wait for a very short time doesn’t work. As the bit is stuck high with WD chips, my current, working routine should always send without waiting, which I know doesn’t work. So I’m pretty confident that these are not the WD version of the CMOS chip. Maybe the GT chips are based on the Rockwell design?
The three original R6551 chips all worked. However, occasionally the computer would need a few resets before it jumped into action. I’m thinking this is to do with the CTS bug.
Of the 65C51 chips with the Rockwell logo, two worked. The Rockwell incarnation is supposed to be solid. Against all expectations, the chip with the very crisp lettering – I mean, it looked brand new – was one of the ones that worked. I’d assumed this had to be a re-lasered phony. It has a 1997 date code. But hey ho.
That left seven non-working chips. More investigation is needed to determine if they are simply dead or fakes, but that can wait for another day. I have five working CMOS chips and three original R6551s to play with.
Next step, writing code to deal with incoming traffic.