(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)
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
| A2 | A1 | A0 | READ MODE | WRITE MODE | ||||||
|---|---|---|---|---|---|---|---|---|---|---|
| General Register Set (THR/RHR, IER/ISR, MCR/MSR, LCR/LSR, SPR): | ||||||||||
| 0 | 0 | 0 | Receive Holding Register | Transmit Holding Register | ||||||
| 0 | 0 | 1 | Interrupt Enable Register | Interrupt Enable Register | ||||||
| 0 | 1 | 0 | Interrupt Status Register | FIFO Control Register | ||||||
| 0 | 1 | 1 | Line Control Register | Line Control Register | ||||||
| 1 | 0 | 0 | Modem Control Register | Modem Control Register | ||||||
| 1 | 0 | 1 | Line Status Register | Reserved | ||||||
| 1 | 1 | 0 | Modem Status Register | Reserved | ||||||
| 1 | 1 | 1 | Scratchpad Register | Scratchpad Register | ||||||
| Baud Rate Generator Registers (DLL/DLM). Accessible only when LCR bit-7 is set to 1. | ||||||||||
| 0 | 0 | 0 | LSB of Divisor Latch | LSB of Divisor Latch | ||||||
| 0 | 0 | 1 | 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
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
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
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
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