Serial Interface

The RS-232 serial interface (see page 3 of the schematics PDF Icon) is built up from a UART, with which the CPU interfaces, and a MAX-232 which converts the UART's signals to those required by the RS-232 specification. The other side of the MAX-232 is therefore hooked up to a regular 9-pin serial port. An external oscillator is used as a reference from which the UART can generate various baud rates. Aside from these major components, only a few resistors and capacitors are required to get this relatively simple circuit working.

Communication between the CPU and the UART is achieved through a number of registers. These registers can be written to in order to program the UART or send data, or read from to check the UART's status or read data in. The UART occupys eight of the Z80's I/O ports (although there are more than eight registers, some addresses point to different registers depending on special flags in other registers). On my system, the UART is connected to the /DS1 line, meaning that the UART occupies ports 0x20 to 0x27.

Programming

Initialisation

Before using the UART, it must of course be initialised with the desired baud rate, parity settings and such like. Many of these values can be quickly set by writing the approriate values to the control registers. For my own purposes it's not, as yet, necessary to reconfigure these values when the system is running, so I can get away with the following very simple piece of initialisation code (the values used are taken directly from the UART's datasheet):

ld      a,0x00          ; Disable all interrupts
out     (0x21),a        ; Send to Interrupt Enable Register
ld      a,0x80          ; Mask to set DLAB on
out     (0x23),a        ; Send to Line Control Register
ld      a,12            ; Divisor of 12 = 9600 bps with 1.8432 MHz clock
out     (0x20),a        ; Set LSB of divisor
ld      a,00            ; This will be the MSB of the divisior
out     (0x21),a        ; Send to the MSB register
ld      a,0x03          ; 8 bits, 1 stop, no parity (and clear DLAB)
out     (0x23),a        ; Write new value to LCR

The setting of the baud rate deserves special explanation. Instead of setting the baud rate as a literal value (e.g. 9600 for 9.6 Kbps) the speed is programmed as a divisor. This means that the external oscillator's frequency is divided by 16, then by your programmed value to generate the final baud rate. This is why the somewhat random-looking oscillator frequency of 1.8432 MHz is used. It so happens that 1,843,200 Hz / 16 = 115,200 bps, which is a commonly used baud rate for serial communication. This is the maximum speed we can achieve with an oscillator of this frequency, as the lowest possible divisor is 1, and 115,200 / 1 is of course 115,200. Using a programable divisor of 2, 3, 6 or 12 gives us some other commonly used speeds of 57.6 K, 38.4 K, 19.2 K and 9600 bps respectively.

Reading and Writing

With the chore of initialisation out of the way, we can get on with the job in hand - namely reading and writing data. This is again done using the registers of our freind the UART, who kindly takes care of all those teadious little things like managing queues of bytes, controlling the datastream, timing and other such things you should be glad you don't need to worry about.

Because of this, all we need to de to write data to the UART is check it's ready to receive a byte (and if not, wait until it is) then simply send the byte to the apropriate I/O port:

; Sends byte in A to the UART
uart_tx:        call    uart_tx_ready
                out     (0x20),a
                ret
 
; Returns when UART is ready to receive
uart_tx_ready:
                push    af
uart_tx_ready_loop:
                in      a,(0x25)        ; fetch the control register
                bit     5,a             ; bit will be set if UART is ready
                jp      z,uart_tx_ready_loop
                pop     af
                ret

Reading data is just as simple - first check if there's any data to read (or wait until there is) then read the data from the UART:

; Wait for a byte from the UART, and save it in A
uart_rx:        call    uart_rx_ready
                in      a,(0x20)
                ret
 
; Returns when UART has received data
uart_rx_ready:
                push    af
uart_rx_ready_loop:
                in      a,(0x25)        ; fetch the conrtol register
                bit     0,a             ; bit will be set if UART has data
                jp      z,uart_rx_ready_loop
                pop     af
                ret

These examples come from my project's serial interface driver in the source code repository.

Troubleshooting

As simple as the circuitry and programming of the serial interface is, there's plenty of potential for getting things wrong. As the serial interface was the primary way of knowing whether the system was working at all, it was ticky to see what was happening when things weren't working quite right...

I wrote a very simple program which would simply output a continuous stream of characters to the serial port. With this written to the flash ROM, I connected the system up to a PC's serial port with a null modem cable and, using a terminal emulator to mointor the port, fired the system up for the first time. I was greeted by a steady stream of what looked like line noise on the screen which, strangely enough, changed when I pressed a key, sending a character back to the Z80 system. Obviously, this wasn't exactly the desired result, but it was at least something!

Looking at the stream of characters, I tried desparately to find a pattern which related it to the expected output. Was I simply missing a bit in the ASCII codes? Was there an extra bit where there shouldn't be? Were the bits simply just in the wrong order? In the end it turned out there wasn't a pattern to be found. Why? Because I'd based my design on a couple of different schematics I'd found on the Internet and in datasheets, I'd managed to mix up the pinouts with bits from both designs. Putting this right wasn't too hard after I'd found the error of my ways, so I fired up the system again, expecting something better...

And there it was - a single letter 'A' on the screen. Having programmed a steady stream of characters, this was somewhat shorter than the output I was expecting, but it was output none the less. What's more, it was even correct output! This single character was enough to tell me that the CPU, memory (both ROM and RAM) were functioning - either that or I'd been presented with the expected output by pure chance. It was time to go through that test program again, this time with a fine-toothed comb.

Assembly language being a far cry for the high-level languages I was accustomed too, debugging was always going to be slow. In the end I had to trace through the program, step by step, visualising the contents of each register and double checking I'd used the correct test conditions to loop through the output characters. After much deliberation I finally found the problem. As ever, it was a subtle error, but bad enough to ruin what was otherwise a perfectly good test program. In the routine to write a charater to the UART, I'd been a little adventurous and tried to preserve the contents of the registers I'd be using by pushing them to the stack. Unfortunately, popping the AF register after I'd done promptly overwrote the flag I wanted to test, resulting in a rather tight loop that was very infinite indeed. The following charaters in the sequence therefore didn't get a look in.

The moral of the story? Keep it simple! Get the program working first, then start making it better if necessary. With this kind of program, this golden rule really does come into its own.