16C550 UART

Hardware and Z80 driver explained

(Page restored from wayback machine)

Some of this code is based heavily on Matt Cook's monitor code, which was used a starting point when we began the M62's BIOS

This code is using Telemart assembler (TASM), you may need to modify it to work with other assemblers

I will add some schematics here soon, but the 16C550 UART resides at the base I/O address of 0x08 (this can be changed to what is required)


Table of contents:
 16C550 Pinout (TQFP and DIP packages)
 Chip select configuration
 16C550 Register layout
 Initialising the 16C550 UART
 Checking for character available or transmit buffer available
 Basic Receive and Transmit functions
 Transmitting a null-terminated string

16C550 Pinout (TQFP and DIP packages)


Chip select configuration

To select the UART, you need to address decode the I/O port and provide CS0 and CS1 with a logic high, and CS2 with a logic low. Tying CS0 and CS1 to +5V then connecting CS2 to the output of your address decoder (active low) keeps it simple

This example decodes the UART to a base port address of 0x00


16C550 Register layout

A2 A1 A0 READ MODE WRITE MODE
General Register Set (THR/RHR, IER/ISR, MCR/MSR, LCR/LSR, SPR):
000 Receive Holding Register Transmit Holding Register
001 Interrupt Enable Register Interrupt Enable Register
010 Interrupt Status Register FIFO Control Register
011 Line Control Register Line Control Register
100 Modem Control Register Modem Control Register
101 Line Status Register Reserved
110 Modem Status Register Reserved
111 Scratchpad Register Scratchpad Register
Baud Rate Generator Registers (DLL/DLM). Accessible only when LCR bit-7 is set to 1.
000 LSB of Divisor Latch LSB of Divisor Latch
001 MSB of Divisor Latch MSB of Divisor Latch

To make use of the register layout, we start by creating some defines:

 UART_BASE: .EQU $00 ; Base address for the UART
 UART0: .EQU UART_BASE ;Data in/out
 UART1: .EQU UART_BASE+1 ;Check RX
 UART2: .EQU UART_BASE+2 ;Interrupts
 UART3: .EQU UART_BASE+3 ;Line control
 UART4: .EQU UART_BASE+4 ;Modem control
 UART5: .EQU UART_BASE+5 ;Line status
 UART6: .EQU UART_BASE+6 ;Modem status
 UART7: .EQU UART_BASE+7 ;Scratch register

Initialising the 16C550 UART

Next we need to initialise the UART itself. In this case I'm using a 4MHz clock as the input and I want to have a Baud rate of 9600:

 _UART_INIT:
    LD A,$80 ;Mask to Set DLAB Flag
    OUT (UART3),A
    LD A,26 ;Divisor = 26 for 9600 @ 4MHz, you can use 12 for 9600bps @ 1.8432 Mhz clock
    OUT (UART0),A ;Set BAUD rate to 9600
    LD A,00
    OUT (UART1),A ;Set BAUD rate to 9600
    LD A,$03
    OUT (UART3),A ;Set 8-bit data, 1 stop bit, reset DLAB Flag
    LD A,$01
    OUT (UART1),A ;Enable receive data available interrupt only
    RET

Checking for character available or transmit buffer available

These functions will check to see if the UART is ready to receive and transmit
Note: These are blocking functions and will loop indefinitely until a character is available or the buffer has space

 _UART_RX_RDY:
    PUSH AF
 UART_RX_RDY_LP:
    IN A,(UART5) ;Fetch the control register
    BIT 0,A ;Bit will be set if UART is ready to receive
    JP Z,UART_RX_RDY_LP
    POP AF
    RET

 _UART_TX_RDY:
    PUSH AF
 UART_TX_RDY_LP:
    IN A,(UART5) ;Fetch the control register
    BIT 5,A ;Bit will be set if UART is ready to send
    JP Z,UART_TX_RDY_LP
    POP AF
    RET

Basic Receive and Transmit functions

To do anything useful with the UART we are going to need to receive and send characters
Think of these as the getc and putc functions for our driver
With these basic functions, the code will put iself into a loop checking to see if there is a character available or if the transmit buffer has space before proceeding.
(Functions that use a timeout will be added in the near future)

 UART_RX:
    CALL UART_RX_RDY ;Make sure UART is ready to receive
    IN A,(UART0) ;Receive character in UART to A
    RET

 UART_TX:
    CALL UART_TX_RDY ;Make sure UART is ready to receive
    OUT (UART0),A ;Transmit character in A to UART
    RET

Transmitting a null-terminated string

This function will take the address of the string in HL and then increment through that memory location transmitting each character it finds until it reaches a null character (0x00)

 UART_PRNT_STR:
    PUSH AF
    PUSH HL
 UART_PRNT_STR_LP:
    LD A,(HL)
    CP $00 ;Test for null
    JP Z,UART_END_PRNT_STR ;Jump if null is found
    CALL UART_TX
    INC HL ;Increment pointer to next char
    JP UART_PRNT_STR_LP ;Transmit loop
 UART_END_PRNT_STR:
    POP HL
    POP AF
    RET