;********************************************************************** ;* Filename: uart.asm ;* Project: EPROM emulator ;* Date: 05 June 2005 ;* Version: 0.1 ;* Author: Philip Pemberton ;* ;* UART driver. Based on code by Olin Lathrop / Embed Inc. ;********************************************************************** ; *************************************************************** ; * Copyright (C) 2003, Embed Inc (http://www.embedinc.com) * ; * * ; * Permission to copy this file is granted as long as this * ; * copyright notice is included in its entirety at the * ; * beginning of the file, whether the file is copied in whole * ; * or in part and regardless of whether other information is * ; * added to the copy. * ; * * ; * The contents of this file may be used in any way, * ; * commercial or otherwise. This file is provided "as is", * ; * and Embed Inc makes no claims of suitability for a * ; * particular purpose nor assumes any liability resulting from * ; * its use. * ; *************************************************************** include "config.inc" ; Device configuration ;********************************************************************** ;** HEADERS ;********************************************************************** include "pins.inc" ; Pindefs include "mac_fifo.inc" ; FIFO macros extern intr_ret_uart ; Interrupt return point ;********************************************************************** ;** CONSTANTS ;********************************************************************** baud equ 115200 ; Baud rate txfifosize equ 8 ; Transmit FIFO size rxfifosize equ 32 ; Receive FIFO size lbank equ 0 ;register bank for the local state of this module lbankadr equ bankadr(lbank) ;address within local state register bank ;;;; BRG setup - from Olin Lathrop (Embed Inc) ; ; Init the non-baud rate bits of TXSTA. ; val_txsta set b'00100000' ; X------- not used in asynchronous mode ; -0------ select 8 bits (not 9 bits) per char ; --1----- enable the transmitter ; ---0---- select asynchronous mode ; ----X--- unused ; -----0-- high/low baud rate select, will be set later ; ------X- read-only status bit ; -------X 9th bit of transmit data, not used val_RCSTA set b'10010000' ;set receiver configuration ; 1------- enable the serial port hardware ; -0------ select 8 bits per received character ; --X----- unused in asynchronous mode ; ---1---- enable the receiver ; ----0--- disable address detection ; -----XXX read-only status bits ; ; Init values assuming will use high speed mode. ; val_txsta set val_txsta | (1 << BRGH) val_spbrg set freq_osc * 16 / baud - 256 ;find divider value, 8 fraction bits val_spbrg set (val_spbrg + 128) >> 8 ;round and scale to final SPBRG value baud_real set freq_osc / (16 * (val_spbrg + 1)) ;find actual selected baud rate ; ; Switch to low speed mode if the baud rate generator value would require ; more than 8 bits to represent, or this chip has no high speed mode. ; if (val_spbrg > 255) ;too slow for high speed mode? val_txsta set val_txsta & ~(1 << BRGH) ;disable high speed mode val_spbrg set freq_osc * 4 / baud - 256 ;find divider value, 8 fraction bits val_spbrg set (val_spbrg + 128) >> 8 ;round and scale to final SPBRG value if val_spbrg > 255 ;clip at largest allowable baud rate generator value val_spbrg set 255 endif baud_real set freq_osc / (64 * (val_spbrg + 1)) ;find actual selected baud rate endif err set (1000 * (baud_real - baud)) / baud ;baud rate err in parts/1000 if err < 0 err set -err ;make absolute value of error endif if err > 58 error "Baud rate error exceeds 5.8%" endif if err > 29 messg "WARNING: Baud rate error exceeds 2.9%" endif ;********************************************************************** ;** RAM VARIABLES ;********************************************************************** ; Global state. defram gbankadr ; Local state defram lbankadr fifo_define txfifo, txfifosize ; Transmit FIFO fifo_define rxfifo, rxfifosize ; Receive FIFO uartFlags res 1 ; UART flags uartTemp res 1 ; Temporary storage uartITemp res 1 ; Temporary storage for ISR uartITemp2 res 1 ; Temporary storage for ISR ;********************************************************************** ;** FLAGS ;********************************************************************** ; Byte in receive buffer #define RXFLAG uartFlags, 0 ; Byte in transmit buffer #define TXFLAG uartFlags, 1 ;********************************************************************** ;** SUBROUTINES ;********************************************************************** .uart CODE ; Code segment ;---------------------------------- ; Subroutine: uart_Init ; Inputs: ; None ; Outputs: ; None ; Function: ; Initialise the UART hardware. glbsub uart_Init dbankif lbankadr fifo_init txfifo ; Initialise the transmit FIFO fifo_init rxfifo ; Initialise the transmit FIFO CLRF uartFlags ; Clear UART flags BSF TXFLAG ; Transmit fifo is empty dbankif SPBRG MOVLW val_spbrg ; Set up SPBRG MOVWF SPBRG dbankif RCSTA MOVLW val_RCSTA ; Set up RCSTA MOVWF RCSTA dbankif TXSTA MOVLW val_txsta ; Set up TXSTA MOVWF TXSTA dbankif PIE1 BSF PIE1, RCIF ; Enable UART receive interrupts dbankif RTS_PORT bcf RTS ; Set RTS active leave ; End of subroutine ;---------------------------------- ; Subroutine: uart_Put ; Inputs: ; W = byte to transmit ; Outputs: ; None ; Function: ; Insert a byte into the transmit buffer. glbsub uart_Put dbankif lbankadr movwf uartTemp ; Save the byte we're going to transmit put_wait: btfss TXFLAG ; output FIFO can accept another byte ? goto put_wait ; FIFO is full, go back and check again dbankif lbankadr ; The FIFO has room for at least one more byte. ibankif lbankadr intr_off ; Temporarily disable interrupts fifo_put txfifo, txfifosize, uartTemp ; Stuff the byte into the output FIFO dbankif PIE1 BSF PIE1, TXIE ; Make sure UART transmit interrupt is enabled intr_on ; Re-enable interrupts ; Clear TXFLAG if the FIFO is full. FLAG_SOUT is currently set. dbankif lbankadr intr_off ; Temporarily disable interrupts fifo_skip_full txfifo, txfifosize ; Is the FIFO full? GOTO put_nfull ;FIFO still has room, done with FLAG_SOUT dbankif lbankadr BCF TXFLAG ;indicate serial line output FIFO is full put_nfull: unbank ;skip to here if FIFO not completely full intr_on ;re-enable interrupts leave ; End of subroutine ;---------------------------------- ; Subroutine: uart_Get ; Inputs: ; None ; Outputs: ; W = received byte ; Function: ; Returns a byte from the receive buffer. glbsub uart_Get ; Wait until an input byte is available. dbankif lbankadr get_wait: btfss RXFLAG ; Is an input byte available? goto get_wait ; No input byte available yet, check again. ; The FIFO contains at least one input byte. dbankif lbankadr ibankif lbankadr intr_off ; Temporarily disable interrupts fifo_get rxfifo, rxfifosize, uartTemp ; Get the byte from the FIFO into REG0 fifo_skip_empty rxfifo ; No more input bytes available? GOTO get_nemt ; FIFO is not completely empty dbankif lbankadr BCF RXFLAG ; Indicate no input byte immediately available get_nemt: dbank? ; Skip to here if FIFO is not completely empty intr_on ; Re-enable interrupts movf uartTemp, W ; received byte -> W leave ; End of subroutine ;---------------------------------- ; Entrypoint: uart_TXIntr ; Inputs: ; None ; Outputs: ; None ; Function: ; Transmit interrupt handler. ; This routine is jumped to from the interrupt handler during an interrupt ; when the UART is ready to accept a new byte. This routine must jump back ; to INTR_RET_UART when done handling the interrupt condition. ; ; Since this routine is running during an interrupt, it must not modify ; the general registers and other global state. Any call stack locations ; used here will not be available to the foreground code. glbent uart_TXIntr ; UART transmit interrupt handler dbankif lbankadr bsf TXFLAG ; FIFO guaranteed not to be full after this interrupt ; Disable this interrupt if the serial line output FIFO is empty. The ; interrupt is always enabled when a byte is put into the FIFO. dbankif lbankadr fifo_skip_nempty txfifo ; a byte is available in the FIFO GOTO xmit_off ; no byte available, disable this interrupt ; There is at least one byte in the FIFO. Send it. dbankif lbankadr ibankif lbankadr fifo_get txfifo, txfifosize, uartITemp ; get the data byte into UART_ITMP1 MOVF uartITemp, w ; get the data byte into W dbankif TXREG MOVWF TXREG ; write the data byte to the UART ; Disable this interrupt if the FIFO is now empty. dbankif lbankadr fifo_skip_empty txfifo ; nothing more left to send now ? GOTO intr_leave ; still more to send, don't disable the interrupt xmit_off: dbankis lbankadr ; disable the UART transmit ready interrupt dbankif PIE1 BCF PIE1, TXIE ; disable this interrupt GOTO intr_leave ; done handling the interrupt ;---------------------------------- ; Entrypoint: uart_RXIntr ; Inputs: ; None ; Outputs: ; None ; Function: ; Receive interrupt handler. ; This routine is jumped to from the interrupt handler during an interrupt ; when the UART has received a new byte. This routine must jump back to ; INTR_RET_UART when done handling the interrupt condition. ; ; Since this routine is running during an interrupt, it must not modify ; the general registers and other global state. Any call stack locations ; used here will not be available to the foreground code. glbent uart_RXIntr ; UART receive interrupt handler ; Save the original RCSTA register value in UART_ITMP1, then save the ; data byte in UART_ITMP2. The UART incoming data register must be ; read to clear the interrupt condition, but the framing error bit ; is only valid before the data byte is read. dbankif RCSTA movf RCSTA, w ; save snapshot of receive status reg in UART_ITMP1 dbankif lbankadr movwf uartITemp dbankif RCREG movf RCREG, w ; save data byte in UART_ITMP2, clear intr condition dbankif lbankadr movwf uartITemp2 ; Reset the receiver if an overrun occurred. This is the only way to ; clear an overrun condition. dbankif RCSTA btfss RCSTA, OERR ; input overrun condition ? goto recv_derrov ; no overrun condition bcf RCSTA, CREN ; disable then enable receiver to clear the error bsf RCSTA, CREN ; re-enable the receiver recv_derrov: ; done dealing with overrun error condition ; Ignore the data byte if it was not properly followed by the stop bit. ; This is called a "framing error". dbankif lbankadr btfsc uartITemp, FERR ; no framing error with this data byte ? goto intr_leave ; framing error, don't process this byte further ; Stuff the received byte into the FIFO if there is room for it. dbankif lbankadr ibankif lbankadr ;;;;; RTSCHECK fifo_skip_full rxfifo, rxfifosize-8 ; at least 8 spare bytes in the fifo? bcf RTS ; yes, so set RTS active fifo_skip_nfull rxfifo, rxfifosize-8 bsf RTS ; no, so set RTS inactive ;;;;; RTSCHECK fifo_skip_nfull rxfifo, rxfifosize ; FIFO has room for another byte ? goto intr_leave ; FIFO is full, ignore the new byte fifo_put rxfifo, rxfifosize, uartITemp2 ; stuff the new data byte into the FIFO dbankif lbankadr bsf RXFLAG ; indicate a serial line input byte is available ;;;;;;;;;;;;;;;;;;;;;;;;;;; intr_leave: unbank ; common code to return to interrupt handler gjump intr_ret_uart ; done handling this interrupt END