subtitle "uart_lib.asm by L. Wyard-Scott" ;;; ********************************************************************* ;;; Library Name: uart_lib.asm ;;; Purpose: Routines for configuration and use of the ;;; on-board UART. ;;; This module assumes that it is located entirely ;;; within the same page of ROM. ;;; Revision: 0.4 ;;; Date: 03 October 2005 ;;; Author: L. Wyard-Scott ;;; Copyright: Public Domain ;;; Device: PIC16F873, PIC16F874, PIC16F873A, PIC16F874A, ;;; PIC16F876, PIC16F877, PIC16F876A, PIC16F877A. ;;; ********************************************************************* ;;; Special Directions: ;;; - To use these routines, include "uart_lib.h" in the source code ;;; calling the routines. ;;; - The crystal frequency needs to be specified on the command line: ;;; FXTAL=xx ;;; Where valid choices of FXTAL are integer representations of ;;; the crystal frequency. ;;; 3 --> 3.57945 MHz ;;; 4 --> 4.00 MHz ;;; 5 --> 5.0688 MHz ;;; 7 --> 7.15909 MHz ;;; 10 --> 10.00 MHz ;;; 12 --> 12.00 MHz ;;; 16 --> 16.00 MHz ;;; 20 --> 20.00 MHz ;;; - To prevent getting stuck in an infinite loop when using a ;;; simulator that does not support the UART, define UART_NOWAIT ;;; on the assembly command-line. ;;; ;;; ;;; ********************************************************************* ;;; Revision History: ;;; 0.4 - 23 February 2006 - LWS ;;; - Repaired TxStringK and TxStringKN by moving one ;;; banksel and adding an additional one. ;;; 0.3 - 03 October 2005 - LWS ;;; - Added support for 16F87XA devices. ;;; 0.2 - 24 February 2005 - LWS ;;; - Added support for various crystal frequencies and more ;;; baudrates. ;;; - Maintained size of code during UART simulation. ;;; - Cleaned up specification of UART simulation. ;;; - Made UART_Init save and restore W. ;;; 0.1 - 20 Oct 2002 - LWS ;;; 0.0 - 13 October 2002 - LWS ;;; - Creation. ;;; ********************************************************************* ;;; Ideas for future work: ;;; - clean up specification of hardware handshaking. Currently ;;; the RTS and CTS pins are hard-coded -- pass in by assembler ;;; command-line define. ;;; - create software emulation of a UART for instances where ;;; either more than one serial port is required, or a device ;;; does not have a UART. ;;; - add handles to routines to allow multiple serial ports. ;;; - make multitasking-compliant. ;;; In order to use these routines for multitasking, ;;; *all* variables, defined below, must be saved ;;; and restored during context-switching. However, ;;; Since there is only one UART, it doesn't really ;;; make sense to multitask these routines until ;;; handles (and software emulation) is implemented. ;;; ********************************************************************* ;;; ********************************************************************* ;;; NOTE: An error will be generated by the assembler if any labels ;;; defined as global in this file are the same as those in the main ;;; file (or other libraries). list ; Turn on list output. Note that device ; type needs to be specified to the ; assembler (on the command line, or through ; the MPLAB IDE). ;; Include register, bit, and other info specific to ;; the specified device. ifdef __16F873 #include messg "Assembling uart_lib.asm for PIC16F873." endif ifdef __16F874 #include messg "Assembling uart_lib.asm for PIC16F874." endif ifdef __16F876 #include messg "Assembling uart_lib.asm for PIC16F876." endif ifdef __16F877 #include messg "Assembling uart_lib.asm for PIC16F877." endif ifdef __16F873A #define A_Device #include messg "Assembling uart_lib.asm for PIC16F873A." endif ifdef __16F874A #define A_Device #include messg "Assembling uart_lib.asm for PIC16F874A." endif ifdef __16F876A #define A_Device #include messg "Assembling uart_lib.asm for PIC16F876A." endif ifdef __16F877A #define A_Device #include messg "Assembling uart_lib.asm for PIC16F877A." endif ifdef UART_NOWAIT messg "Assembling code for simulation only: UART waits are disabled." messg "The code will not be usable on the target device." endif ;;; Ensure that the crystal frequency has been specified on the ;;; assembler command line. ifndef FXTAL error "Timing element frequency not specified on command line." error "Possible values for FXTAL are:" error "3 --> 3.57945 MHz" error "4 --> 4.00 MHz" error "5 --> 5.0688 MHz" error "7 --> 7.15909 MHz" error "10 --> 10.00 MHz" error "12 --> 12.00 MHz" error "16 --> 16.00 MHz" error "20 --> 20.00 MHz" endif ;;; Establish the 9 bits which configure the baudrate - BRGH:SPBRG. ;;; This, of course, depends on the timing element frequency. ;;; In order to quickly derive all the values, the following TCL script ;;; was used: ;;; #!/bin/sh ;;; # the next line restarts using tclsh \ ;;; exec tclsh "$0" "$@" ;;; ;;; set tcl_precision 17 ;;; ;;; set baudlist [list 300 1200 2400 4800 9600 19200 33600 38400 57600] ;;; set xtallist [list 3.57945e6 4.00e6 5.0688e6 7.15909e6 10.00e6 12.00e6 16.00e6 20.00e6 ] ;;; ;;; foreach xtal $xtallist { ;;; puts stdout "" ;;; puts stdout "Crystal Frequency: $xtal MHz" ;;; puts stdout "--------------------------" ;;; foreach baud $baudlist { ;;; puts stdout " Baudrate: $baud" ;;; puts stdout " -----------------" ;;; foreach speed [list 64 16] { ;;; if {$speed == 64} { ;;; puts -nonewline stdout " Low BRGH=0 " ;;; } else { ;;; puts -nonewline stdout " High BRGH=1 " ;;; } ;;; ;;; set Xl [expr ($xtal / ($baud.0 * $speed.0))] ;;; set Xlb [expr round($Xl) - 1] ;;; if {$Xlb > 255} { ;;; set Xlb 255 ;;; } ;;; ;;; puts -nonewline stdout "SPBRG [format %3d $Xlb] " ;;; set bps [expr $xtal/($speed.0 * ($Xlb.0 + 1.0))] ;;; puts -nonewline stdout "[format %8.2f $bps]bps " ;;; set e [expr (100.0 * (($bps - $baud)/$baud.0))] ;;; puts -nonewline stdout "Error: [format %8.2f $e]%" ;;; if {[expr abs($e)] > 2.00} { ;;; puts -nonewline stdout "(Err)" ;;; } else { ;;; puts -nonewline stdout " <-- Candidate" ;;; } ;;; puts stdout "" ;;; } ;;; } ;;; } ;;; Not all baudrates are supported by all crystals. The trapping for ;;; specification of an illegal baudrate is performed in the header ;;; file when it is included. The one side-effect of this is that ;;; any code which includes uart_lib.h needs to have the crystal ;;; frequency specified. if (FXTAL == 3) messg "3.579545 MHz timing element specified." BRGH_VALUE300 EQU 0 SPBRG_VALUE300 EQU 185 ; 300.69bps Error: 0.23% <-- Candidate BRGH_VALUE1200 EQU 1 SPBRG_VALUE1200 EQU 185 ; 1202.77bps Error: 0.23% <-- Candidate BRGH_VALUE2400 EQU 1 SPBRG_VALUE2400 EQU 92 ; 2405.54bps Error: 0.23% <-- Candidate BRGH_VALUE4800 EQU 1 SPBRG_VALUE4800 EQU 46 ; 4759.91bps Error: -0.84% <-- Candidate BRGH_VALUE9600 EQU 1 SPBRG_VALUE9600 EQU 22 ; 9726.77bps Error: 1.32% <-- Candidate BRGH_VALUE19200 EQU 0 SPBRG_VALUE19200 EQU 2 ; 18642.97bps Error: -2.90%(Err) BRGH_VALUE33600 EQU 1 SPBRG_VALUE33600 EQU 6 ; 31959.38bps Error: -4.88%(Err) BRGH_VALUE38400 EQU 1 SPBRG_VALUE38400 EQU 5 ; 37285.94bps Error: -2.90%(Err) BRGH_VALUE57600 EQU 1 SPBRG_VALUE57600 EQU 3 ; 55928.91bps Error: -2.90%(Err) endif if (FXTAL == 4) messg "4 MHz timing element specified." BRGH_VALUE300 EQU 0 SPBRG_VALUE300 EQU 207 ; 300.48bps Error: 0.16% <-- Candidate BRGH_VALUE1200 EQU 0 SPBRG_VALUE1200 EQU 51 ; 1201.92bps Error: 0.16% <-- Candidate BRGH_VALUE2400 EQU 0 SPBRG_VALUE2400 EQU 25 ; 2403.85bps Error: 0.16% <-- Candidate BRGH_VALUE4800 EQU 0 SPBRG_VALUE4800 EQU 12 ; 4807.69bps Error: 0.16% <-- Candidate BRGH_VALUE9600 EQU 1 SPBRG_VALUE9600 EQU 25 ; 9615.38bps Error: 0.16% <-- Candidate BRGH_VALUE19200 EQU 1 SPBRG_VALUE19200 EQU 12 ; 19230.77bps Error: 0.16% <-- Candidate BRGH_VALUE33600 EQU 1 SPBRG_VALUE33600 EQU 6 ; 35714.29bps Error: 6.29%(Err) BRGH_VALUE38400 EQU 1 SPBRG_VALUE38400 EQU 6 ; 35714.29bps Error: -6.99%(Err) BRGH_VALUE57600 EQU 1 SPBRG_VALUE57600 EQU 3 ; 62500.00bps Error: 8.51%(Err) endif if (FXTAL == 5) messg "5.0688 MHz timing element specified." BRGH_VALUE300 EQU 0 SPBRG_VALUE300 EQU 255 ; 309.38bps Error: 3.12%(Err) BRGH_VALUE1200 EQU 0 SPBRG_VALUE1200 EQU 65 ; 1200.00bps Error: 0.00% <-- Candidate BRGH_VALUE2400 EQU 0 SPBRG_VALUE2400 EQU 32 ; 2400.00bps Error: 0.00% <-- Candidate BRGH_VALUE4800 EQU 1 SPBRG_VALUE4800 EQU 65 ; 4800.00bps Error: 0.00% <-- Candidate BRGH_VALUE9600 EQU 1 SPBRG_VALUE9600 EQU 32 ; 9600.00bps Error: 0.00% <-- Candidate BRGH_VALUE19200 EQU 1 SPBRG_VALUE19200 EQU 16 ; 18635.29bps Error: -2.94%(Err) BRGH_VALUE33600 EQU 1 SPBRG_VALUE33600 EQU 8 ; 35200.00bps Error: 4.76%(Err) BRGH_VALUE38400 EQU 1 SPBRG_VALUE38400 EQU 7 ; 39600.00bps Error: 3.12%(Err) BRGH_VALUE57600 EQU 1 SPBRG_VALUE57600 EQU 5 ; 52800.00bps Error: -8.33%(Err) endif if (FXTAL == 7) messg "7.15909 MHz timing element specified." BRGH_VALUE300 EQU 0 SPBRG_VALUE300 EQU 255 ; 436.96bps Error: 45.65%(Err) BRGH_VALUE1200 EQU 0 SPBRG_VALUE1200 EQU 92 ; 1202.80bps Error: 0.23% <-- Candidate BRGH_VALUE2400 EQU 1 SPBRG_VALUE2400 EQU 185 ; 2405.61bps Error: 0.23% <-- Candidate BRGH_VALUE4800 EQU 1 SPBRG_VALUE4800 EQU 92 ; 4811.22bps Error: 0.23% <-- Candidate BRGH_VALUE9600 EQU 1 SPBRG_VALUE9600 EQU 46 ; 9520.07bps Error: -0.83% <-- Candidate BRGH_VALUE19200 EQU 1 SPBRG_VALUE19200 EQU 22 ; 19454.05bps Error: 1.32% <-- Candidate BRGH_VALUE33600 EQU 1 SPBRG_VALUE33600 EQU 12 ; 34418.70bps Error: 2.44%(Err) BRGH_VALUE38400 EQU 1 SPBRG_VALUE38400 EQU 11 ; 37286.93bps Error: -2.90%(Err) BRGH_VALUE57600 EQU 1 SPBRG_VALUE57600 EQU 7 ; 55930.39bps Error: -2.90%(Err) endif if (FXTAL == 10) messg "10 MHz timing element specified." BRGH_VALUE300 EQU 0 SPBRG_VALUE300 EQU 255 ; 610.35bps Error: 103.45%(Err) BRGH_VALUE1200 EQU 0 SPBRG_VALUE1200 EQU 129 ; 1201.92bps Error: 0.16% <-- Candidate BRGH_VALUE2400 EQU 0 SPBRG_VALUE2400 EQU 64 ; 2403.85bps Error: 0.16% <-- Candidate BRGH_VALUE4800 EQU 1 SPBRG_VALUE4800 EQU 129 ; 4807.69bps Error: 0.16% <-- Candidate BRGH_VALUE9600 EQU 1 SPBRG_VALUE9600 EQU 64 ; 9615.38bps Error: 0.16% <-- Candidate BRGH_VALUE19200 EQU 1 SPBRG_VALUE19200 EQU 32 ; 18939.39bps Error: -1.36% <-- Candidate BRGH_VALUE33600 EQU 1 SPBRG_VALUE33600 EQU 18 ; 32894.74bps Error: -2.10%(Err) BRGH_VALUE38400 EQU 1 SPBRG_VALUE38400 EQU 15 ; 39062.50bps Error: 1.73% <-- Candidate BRGH_VALUE57600 EQU 1 SPBRG_VALUE57600 EQU 10 ; 56818.18bps Error: -1.36% <-- Candidate endif if (FXTAL == 12) messg "12 MHz timing element specified." BRGH_VALUE300 EQU 0 SPBRG_VALUE300 EQU 255 ; 732.42bps Error: 144.14%(Err) BRGH_VALUE1200 EQU 0 SPBRG_VALUE1200 EQU 155 ; 1201.92bps Error: 0.16% <-- Candidate BRGH_VALUE2400 EQU 0 SPBRG_VALUE2400 EQU 77 ; 2403.85bps Error: 0.16% <-- Candidate BRGH_VALUE4800 EQU 0 SPBRG_VALUE4800 EQU 38 ; 4807.69bps Error: 0.16% <-- Candidate BRGH_VALUE9600 EQU 1 SPBRG_VALUE9600 EQU 77 ; 9615.38bps Error: 0.16% <-- Candidate BRGH_VALUE19200 EQU 1 SPBRG_VALUE19200 EQU 38 ; 19230.77bps Error: 0.16% <-- Candidate BRGH_VALUE33600 EQU 1 SPBRG_VALUE33600 EQU 21 ; 34090.91bps Error: 1.46% <-- Candidate BRGH_VALUE38400 EQU 1 SPBRG_VALUE38400 EQU 19 ; 37500.00bps Error: -2.34%(Err) BRGH_VALUE57600 EQU 1 SPBRG_VALUE57600 EQU 12 ; 57692.31bps Error: 0.16% <-- Candidate endif if (FXTAL == 16) messg "16 MHz timing element specified." BRGH_VALUE300 EQU 0 SPBRG_VALUE300 EQU 255 ; 976.56bps Error: 225.52%(Err) BRGH_VALUE1200 EQU 0 SPBRG_VALUE1200 EQU 207 ; 1201.92bps Error: 0.16% <-- Candidate BRGH_VALUE2400 EQU 0 SPBRG_VALUE2400 EQU 103 ; 2403.85bps Error: 0.16% <-- Candidate BRGH_VALUE4800 EQU 1 SPBRG_VALUE4800 EQU 207 ; 4807.69bps Error: 0.16% <-- Candidate BRGH_VALUE9600 EQU 1 SPBRG_VALUE9600 EQU 103 ; 9615.38bps Error: 0.16% <-- Candidate BRGH_VALUE19200 EQU 1 SPBRG_VALUE19200 EQU 51 ; 19230.77bps Error: 0.16% <-- Candidate BRGH_VALUE33600 EQU 1 SPBRG_VALUE33600 EQU 29 ; 33333.33bps Error: -0.79% <-- Candidate BRGH_VALUE38400 EQU 1 SPBRG_VALUE38400 EQU 25 ; 38461.54bps Error: 0.16% <-- Candidate BRGH_VALUE57600 EQU 1 SPBRG_VALUE57600 EQU 16 ; 58823.53bps Error: 2.12%(Err) endif if (FXTAL == 20) messg "20 MHz timing element specified." BRGH_VALUE300 EQU 0 SPBRG_VALUE300 EQU 255 ; 1220.70bps Error: 306.90%(Err) BRGH_VALUE1200 EQU 0 SPBRG_VALUE1200 EQU 255 ; 1220.70bps Error: 1.73% <-- Candidate BRGH_VALUE2400 EQU 0 SPBRG_VALUE2400 EQU 129 ; 2403.85bps Error: 0.16% <-- Candidate BRGH_VALUE4800 EQU 0 SPBRG_VALUE4800 EQU 64 ; 4807.69bps Error: 0.16% <-- Candidate BRGH_VALUE9600 EQU 1 SPBRG_VALUE9600 EQU 129 ; 9615.38bps Error: 0.16% <-- Candidate BRGH_VALUE19200 EQU 1 SPBRG_VALUE19200 EQU 64 ; 19230.77bps Error: 0.16% <-- Candidate BRGH_VALUE33600 EQU 1 SPBRG_VALUE33600 EQU 36 ; 33783.78bps Error: 0.55% <-- Candidate BRGH_VALUE38400 EQU 1 SPBRG_VALUE38400 EQU 32 ; 37878.79bps Error: -1.36% <-- Candidate BRGH_VALUE57600 EQU 1 SPBRG_VALUE57600 EQU 21 ; 56818.18bps Error: -1.36% <-- Candidate endif ;;; -------------------------------------------------------------------- ;;; Assembler Equates Section. Define assembly-time constants here ;;; using the EQU assembler directive. ;;; -------------------------------------------------------------------- USE_HARDWARE_FLOW_CONTROL EQU 0 ; Don't use hardware flow control. ; change this to 1 if you choose to ; use hardware flow-control, at which ; point, the following pins become active. CTS_PIN EQU 0x02 ; On Port B RTS_PIN EQU 0x01 ; On Port B Tx_PIN EQU 0x06 ; On Port C Rx_PIN EQU 0x07 ; On Port C ;;; --------------------------------------------------------------------- ;;; Variable Address Assignments. ;;; --------------------------------------------------------------------- UDATA ; Start of the uninitialized data section. ; The following statements reserve memory, ; the address of which is allocated by the ; linker. ;; For UART_TxStringK. PTRHIGH: res 1 ; High byte of the saved pointer. PTRLOW: res 1 ; Low byte of the saved pointer. ;; Make these variables identifiable ;; to calling code. global PTRHIGH, PTRLOW TemporaryVars: UDATA_OVR ;; For UART_TxStringN NUMBYTESTOSEND res 1 ; Number of bytes left to send. ;; For UART_RxString. PTRSAVEHIGH: res 1 ; A place to save the pointer. PTRSAVELOW: res 1 NUMCHARS: res 1 ; The number of characters received. MAXCHARS: res 1 ; The maximum number of characters that ; will be accepted. RXDCHAR: res 1 ; Individual received character. ;;; -------------------------------------------------------------------- ;;; Subroutines. ;;; -------------------------------------------------------------------- CODE ; Start the code section. ;;; ******************************************************************** ;;; Subroutine Name: UART_Init ;;; Description: Initializes the UART for 8N1 at a specified ;;; baudrate. No interrupts are generated. ;;; Since this library makes an attempt to ;;; various crystal frequencies, this routine ;;; is much larger than what you might need. ;;; ;;; Also initializes flow-control ;;; if USE_HARDWARE_FLOW_CONTROL is 1. ;;; The RTS and CTS connections reflect the ;;; the signal names at the PC-end of the ;;; connector. ;;; Requires: A baudrate definition in W. Possible values ;;; are: ;;; BAUD_300,BAUD_1200,BAUD_2400,BAUD_4800, ;;; BAUD_9600,BAUD_19200,BAUD_33600,BAUD_38400, ;;; BAUD_57600 ;;; The values of these, and those which are ;;; valid for the timing element frequency ;;; are outlined in uart_lib.h. ;;; Returns: Nothing. ;;; Locations Affected: Modifies PTRSAVEHIGH. ;;; ******************************************************************** UART_Init: global UART_Init ; Make this subroutine callable outside ; the module. ;; Save W. banksel PTRSAVEHIGH movwf PTRSAVEHIGH banksel PORTC ; Set to bank0 ;; Set the level of the pins bcf PORTC,Tx_PIN bcf PORTC,Rx_PIN IF USE_HARDWARE_FLOW_CONTROL bcf PORTB,RTS_PIN ; This line is likely not required. bsf PORTB,CTS_PIN ; Initial condition for the CTS line. ENDIF banksel TRISC ; Set to bank1 ;; Set the pin directions. IF USE_HARDWARE_FLOW_CONTROL bsf TRISB,RTS_PIN ; RTS Pin made input. bcf TRISB,CTS_PIN ; CTS Pin made output. ENDIF bsf TRISC,Tx_PIN ; Tx and Rx pins are made input. bsf TRISC,Rx_PIN ; The UART will make Tx an output ; when required. bcf PIE1,RCIE ; Disable UART interrupts bcf PIE1,TXIE ; (both transmit and receive). ;; Determine the baudrate based upon the value in W. ;; The possible values follow and are used as an ;; index into a lookup table. ;; 300bps W: 0 ;; 1200bps W: 2 ;; 2400bps W: 4 ;; 4800bps W: 6 ;; 9600bps W: 8 ;; 19200bps W: 10 ;; 33600bps W: 12 ;; 38400bps W: 14 ;; 57600bps W: 16 ;; The value in W forms the offset into the lookup table ;; at the end of this subroutine. ;; W is saved in PTRHIGH. This is used, although the ;; name of the variable is misleading. ;; Point to the table: upper byte. banksel PTRHIGH movlw high UART_Init_BaudTable movwf PTRHIGH banksel PTRSAVEHIGH movf PTRSAVEHIGH,W ;; Point to the table: lower byte plus offset. banksel PTRLOW addlw low UART_Init_BaudTable movwf PTRLOW ;; Handle carry to high byte. btfsc STATUS,C incf PTRHIGH,F ;; Look up the byte. This is the BRGH value. call UART_GetStringKChar banksel TXSTA andlw 0xFF btfss STATUS,Z goto UART_Init_BRGH_high UART_Init_BRGH_low: bcf TXSTA,2 goto UART_Init_Get_SPBRG UART_Init_BRGH_high: bsf TXSTA,2 UART_Init_Get_SPBRG: ;; Get the next byte which is the SPBRG value. ;; Increment the pointer. banksel PTRLOW incf PTRLOW,F btfsc STATUS,Z incf PTRHIGH,F call UART_GetStringKChar banksel SPBRG movwf SPBRG ; Establish the baudrate. bsf TXSTA,TXEN ; Enable the transmitter. bcf TXSTA,TX9 ; 8N1 data format. banksel RCSTA ; Set to bank0. movlw 0x90 ; Initialize Rx status and control register. movwf RCSTA ; 8N1 data format, enable receiver, ; continuous receive. ;; Restore W. banksel PTRSAVEHIGH movf PTRSAVEHIGH,W return ;; A look-up table to help with establishing the baudrate. UART_Init_BaudTable: dt BRGH_VALUE300,SPBRG_VALUE300 dt BRGH_VALUE1200,SPBRG_VALUE1200 dt BRGH_VALUE2400,SPBRG_VALUE2400 dt BRGH_VALUE4800,SPBRG_VALUE4800 dt BRGH_VALUE9600,SPBRG_VALUE9600 dt BRGH_VALUE19200,SPBRG_VALUE19200 dt BRGH_VALUE33600,SPBRG_VALUE33600 dt BRGH_VALUE38400,SPBRG_VALUE38400 dt BRGH_VALUE57600,SPBRG_VALUE57600 ;;; ******************************************************************** ;;; Subroutine Name: UART_RxByte ;;; Description: Receives a byte through the UART and returns ;;; it in W. This routine blocks until a character ;;; is received. ;;; Requires: Nothing. ;;; Returns: The character in W. ;;; Locations Affected: None. ;;; ******************************************************************** UART_RxByte: global UART_RxByte ; Make this subroutine callable outside ; the module. banksel PIR1 ; Set to bank0 ;; Use only if hardware flow control is desired. IF (USE_HARDWARE_FLOW_CONTROL) bcf PORTB,CTS_PIN ; Enable reception by clearing CTS. ENDIF ;; Loop until a character is received. UART_RxByteLoop: IFNDEF UART_NOWAIT btfss PIR1,RCIF ; Poll the interrupt flag. goto UART_RxByteLoop ELSE nop ; Keep the size of the code the same nop ; during UART simulation. ENDIF ;; Use only if hardware flow control is desired. IF (USE_HARDWARE_FLOW_CONTROL) bsf PORTB,CTS_PIN ; Disable reception by setting CTS ENDIF ;; Grab the character from the receive buffer and return in W. movf RCREG,W return ;;; ******************************************************************** ;;; Subroutine Name: UART_KbHit ;;; Description: Determines if a character has been received by ;;; the UART (but does not return the character ;;; itself). To read the actual character (if one ;;; has been received), use a statement like ;;; movf RCREG,W ;;; Requires: Nothing. ;;; Returns: STATUS, C = 1 (true) if a character has ;;; been received. ;;; STATUS, C = 0 (false) if no character is ;;; waiting. ;;; Locations Affected: None. ;;; ******************************************************************** UART_KbHit: global UART_KbHit ; Make this subroutine callable outside ; the module. banksel PIR1 ; Set to bank0 btfss PIR1,RCIF ; Check the receive status flag. goto UART_KbHitFalse UART_KbHitTrue: ;; A character has been received. Return true in the STATUS ;; register carry bit. bsf STATUS,C return UART_KbHitFalse: ;; No character has been received. Return false. bcf STATUS,C return ;;; ******************************************************************** ;;; Subroutine Name: UART_TxByte ;;; Description: Sends the byte in W out through the UART. ;;; This routine will block until the Tx Buffer is ;;; available. ;;; Requires: The byte to send in W. ;;; Returns: Nothing. ;;; Locations Affected: None. ;;; ******************************************************************** UART_TxByte: global UART_TxByte ; Make this subroutine callable outside ; the module. banksel PORTB ; Set to bank0 (to access PIR1, TXREG). IF (USE_HARDWARE_FLOW_CONTROL) UART_TxByteRTSLoop: btfsc PORTB,RTS_PIN ; check RTS to see if data can be sent goto UART_TxByteRTSLoop ENDIF ; USE_HARDWARE_FLOW CONTROL IFNDEF UART_NOWAIT ;; The following is used only if there is hardware flow control. UART_TxByteWait btfss PIR1, TXIF ; check that buffer is empty goto UART_TxByteWait ELSE nop ; Keep the size of the code the same nop ; when simulating the UART. ENDIF ; IFNDEF UART_NOWAIT movwf TXREG ; Send data return ;;; ******************************************************************** ;;; Subroutine Name: UART_TxStringK ;;; Description: Uses the UART to send out a NULL-terminated ;;; string constant (a string that resides ;;; in the code space). ;;; This routine calls another (specialized) ;;; subroutine, UART_GetStringKChar ;;; in order to overcome the ;;; PIC CPU limitation not allowing pointers ;;; to constants. ;;; To send a string in RAM, see UART_TxString. ;;; Requires: PTRHigh - high byte of address of string ;;; constant. ;;; PTRLow - low byte of address of string ;;; constant. ;;; Returns: Nothing. ;;; Locations Affected: PTRHigh, PTRLow will point at the NULL. ;;; W will contain the NULL. ;;; ******************************************************************** UART_TxStringK: global UART_TxStringK ; Make this subroutine callable outside ; the module. ;; This routine uses a "trick" to allow specification of ;; and arbitrary string constant by PTRHigh:PTRLow. ;; The trick is to make a subroutine call to an intermediate ;; piece of code which then uses a "computed goto" to ;; jump to a string character. ;; When making a subroutine call, the return address ;; gets pushed onto the stack. The intermediate subroutine ;; itself does not pull the value off the stack, but ;; instead passes control to the table.. ;; This is slightly different than the suggested method ;; by Microchip in AN556 (Implementing a Table Read) ;; wherein the table itself includes an instruction to ;; complete a computed goto statement. UART_TXStringKLoop: banksel PTRLOW ;; Get a character. call UART_GetStringKChar ;; Return to this page if the string constant was ;; located in another page. pagesel UART_TXStringKLoop ;; Check to see if it is a NULL. andlw 0xFF ; Does nothing but set/clear Z in STATUS. btfsc STATUS,Z return ; String transmission complete. ;; Send the character. call UART_TxByte banksel PTRLOW ;; Increment the pointer. incf PTRLOW,F ; Low byte. btfsc STATUS,Z incf PTRHIGH,F ; High byte, if there is a carry ; from the low byte. ;; Process another character. goto UART_TXStringKLoop UART_GetStringKChar: ;; Address of string is PTRHigh:PTRLow. ;; Use a computed GOTO to get the character to send. movf PTRHIGH,W movwf PCLATH ; High byte of address. movf PTRLOW,W movwf PCL ; Low Byte of address. ;; Execution should never past here. In a way, ;; this is a subroutine without a return statement. ;; The actual return is contained along with the ;; string 'table' data. ;;; ******************************************************************** ;;; Subroutine Name: UART_TxStringKN ;;; Description: Uses the UART to send out N bytes of a NULL- ;;; terminated string constant (a string that ;;; resides in the code space). ;;; This routine calls another (specialized) ;;; subroutine, UART_GetStringKChar ;;; in order to overcome the ;;; PIC CPU limitation not allowing pointers ;;; to constants. ;;; To send a string in RAM, see UART_TxString. ;;; Requires: PTRHigh - high byte of address of string ;;; constant. ;;; PTRLow - low byte of address of string ;;; constant. ;;; Returns: Nothing. ;;; Locations Affected: PTRHigh, PTRLow will point at the NULL. ;;; W will contain the NULL. ;;; ******************************************************************** UART_TxStringKN: global UART_TxStringKN ; Make this subroutine callable outside ; the module. ;; This routine uses a "trick" to allow specification of ;; and arbitrary string constant by PTRHigh:PTRLow. ;; The trick is to make a subroutine call to an intermediate ;; piece of code which then uses a "computed goto" to ;; jump to a string character. ;; When making a subroutine call, the return address ;; gets pushed onto the stack. The intermediate subroutine ;; itself does not pull the value off the stack, but ;; instead passes control to the table.. ;; This is slightly different than the suggested method ;; by Microchip in AN556 (Implementing a Table Read) ;; wherein the table itself includes an instruction to ;; complete a computed goto statement. banksel NUMBYTESTOSEND movwf NUMBYTESTOSEND UART_TXStringKNLoop: banksel PTRLOW ;; Get a character. call UART_GetStringKChar ;; Return to this page if the string constant was ;; located in another page. pagesel UART_TXStringKNLoop ;; Send the character. call UART_TxByte ;; Check if there are more bytes to send. banksel NUMBYTESTOSEND decfsz NUMBYTESTOSEND,F goto UART_TXStringKNIncPointer return UART_TXStringKNIncPointer banksel PTRLOW ;; Increment the pointer. incf PTRLOW,F ; Low byte. btfsc STATUS,Z incf PTRHIGH,F ; High byte, if there is a carry ; from the low byte. ;; Process another character. goto UART_TXStringKNLoop ;;; ******************************************************************** ;;; Subroutine Name: UART_TxString ;;; Description: Sends a NULL-terminated string in RAM through ;;; the UART. ;;; To send a string constant in ROM, see subroutine ;;; UART_TxStringK. ;;; Requires: IRP (STATUS:7) and FSR point to the string ;;; in RAM. (See PIC16F87X data sheet section 2.5 ;;; for more information on indirect addressing.) ;;; Returns: Nothing. ;;; Locations Affected: W will contain 0 (NULL). IRP, FSR will point ;;; to the NULL of the string. ;;; ******************************************************************** UART_TxString: global UART_TxString ; Make this subroutine callable outside ; the module. ;; This routine requires no RAM bank switching since ;; INDF, StATUS, and FSR all appear in every bank. movf INDF,W ; Get the character pointed to by ; indirect addressing. btfsc STATUS,Z ; Exit if the NULL is found. return call UART_TxByte ; Send the character out. incf FSR,F ; Increment the LSByte of the pointer. btfsc STATUS,C ; Check to see if there has been a carry. bsf STATUS,IRP ; If so, ensure that a wrap to an upper ; bank occurs. goto UART_TxString ;;; ******************************************************************** ;;; Subroutine Name: UART_TxStringN ;;; Description: Sends N characters of a string out through ;;; the UART. This routine will transfer characters ;;; past any terminating NULL, if the number of ;;; specified characters is beyond the NULL. ;;; To send a NULL-terminated string in RAM, see ;;; subroutine UART_TxString ;;; To send a string constant in ROM, see subroutine ;;; UART_TxStringK. ;;; Requires: W contains the number of characters to transmit. ;;; IRP (STATUS:7) and FSR point to the string ;;; in RAM. (See PIC16F87X data sheet section 2.5 ;;; for more information on indirect addressing.) ;;; Returns: Nothing. ;;; Locations Affected: W will contain 0. IRP, FSR will point ;;; to the last transmitted byte. ;;; ******************************************************************** UART_TxStringN: global UART_TxStringN ; Make this subroutine callable outside ; the module. ;; Save the character count. banksel NUMBYTESTOSEND movwf NUMBYTESTOSEND UART_TxStringNLoop: movf INDF,W ; Get the character pointed to by ; indirect addressing. call UART_TxByte ; Send the character out. ;; Determine if all desired characters are sent out. banksel NUMBYTESTOSEND decf NUMBYTESTOSEND,F btfsc STATUS,Z ; If zero, then the routine is done. return incf FSR,F ; Increment the LSByte of the pointer. btfsc STATUS,C ; Check to see if there has been a carry. bsf STATUS,IRP ; If so, ensure that a wrap to an upper ; bank occurs. goto UART_TxStringNLoop ;;; ******************************************************************** ;;; Subroutine Name: UART_RxString ;;; Description: Receives a string from the user, terminated by ;;; a new-line character. ;;; Requires: IRP (STATUS:7) and FSR point to the start of an ;;; initialized string structure in RAM. ;;; ;;; On call: ;;; Position: byte0 byte1 byte2 byte3 .... byte n ;;; Value: MaxChars ;;; ;;; MaxChars is the maximum number of characters ;;; that will be acccepted by this routine, excluding ;;; the NULL. Thus, if the buffer has size (n+1), as ;;; shown, then it can hold (n-1) characters plus the ;;; NULL. The maximum value that can be placed in ;;; the MaxChars position for a buffer of this size is ;;; (n-1). A lower number of characters, of course, ;;; is permitted. ;;; ;;; From the perspective of buffersize, the memory ;;; allocated to this structure must be at least ;;; the maximum string length plus 3. ;;; ;;; This routine can handle strings of up to ;;; 125 characters. Any more will require a ;;; rewrite of some of the signed pointer arithmetic. ;;; ;;; Returns: The NULL-terminated string and the number of ;;; non-NULL characters in it as part of the structure. ;;; ;;; Position: byte0 byte1 byte2 byte3 .... byte n ;;; Value: MaxChars NumUsed String data.........NULL ;;; ;;; Where NumUsed is the number of characters that ;;; were received, excluding the appended NULL. ;;; The string data is NULL-terminated. The new-line ;;; character is stripped. ;;; ;;; W contains the number of characters received (a ;;; duplicate of the value in "byte1". ;;; ;;; Locations Affected: None. (IRP:FSR will still point to the start ;;; of the structure.) ;;; ******************************************************************** UART_RxString: global UART_RxString ; Make this subroutine callable outside ; the module. ;; Move the max number of chars to variable MAXCHARS. banksel NUMCHARS movf INDF,W ; *IRP:FSR->W movwf MAXCHARS ; W->MAXCHARS. clrf NUMCHARS ; 0->NUMCHARS ;; Save the IRP:FSR pointer. movf FSR,W ; Save the lower byte. movwf PTRSAVELOW movf STATUS,W ; Isolate and save the IRP bit. andlw 0x80 movwf PTRSAVEHIGH ;; Make IRP:FSR point to the string data by incrementing it ;; by 2. incf FSR,F incf FSR,F UART_RxStringGetChar: ;; Get a character and save it. call UART_RxByte banksel RXDCHAR movwf RXDCHAR ;; Is the character a backspace? sublw '\b' ; Compare with the backspace. btfsc STATUS,Z goto UART_RxStringIsBackspace ;; Restore the character. movf RXDCHAR,W ;; Is the character a newline? sublw '\r' btfsc STATUS,Z goto UART_RxStringIsNewline ;; Falling through to here means the character is neither a ;; backspace nor a return character. ;; Ensure that there is room for the character. ;; Get the maximum number of allowed characters into W. movf MAXCHARS,W ;; Compare this with the number already received. subwf NUMCHARS,W btfsc STATUS,Z ; If they are equal, then ; there is no room for the character. goto UART_RxStringSendBell ;; There is room for the character. ;; Add one to the NUMCHARS variable to indicate that the ;; byte has been received. incf NUMCHARS,F ;; Place the character in the location pointed to. movf RXDCHAR,W movwf INDF ;; Echo the character back to the user. call UART_TxByte ;; Make the pointer point to where the next character is to be ;; inserted. incf FSR,F ;; Go get another character. goto UART_RxStringGetChar UART_RxStringSendBell: movlw '\a' ; Alarm character. call UART_TxByte goto UART_RxStringGetChar UART_RxStringIsBackspace: ;; To get here, the backspace key has been pressed. ;; Determine if there is room to backspace. movf NUMCHARS,W btfsc STATUS,Z ; If not, let the user know. goto UART_RxStringSendBell ;; There is room to backspace. Decrement number of chars. decf NUMCHARS,F ;; Send a backspace, a space, and a backspace to ;; remove the previous character and move back. ;; This destroys the pointer to the buffer. movlw high UART_RxStringErase movwf PTRHIGH movlw low UART_RxStringErase movwf PTRLOW call UART_TxStringK ;; Move to the now empty location by decrementing it. movlw -1 addwf FSR, F ;; Go and get another character. goto UART_RxStringGetChar UART_RxStringIsNewline: ;; The only way that this subroutine terminates is ;; if the user presses the key. ;; Append a NULL. clrf INDF ;; Restore the pointer. movf STATUS,W ; Restore the IRP bit. iorwf PTRSAVEHIGH,W movwf STATUS movf PTRSAVELOW,W ; Restore the lower byte. movwf FSR ;; Move the pointer over to the byte that contains ;; the number of characters entered. incf FSR,F ;; Set the number of entered characters. movf NUMCHARS,W movwf INDF ;; Reset the pointer (again). movlw -1 addwf FSR,F return ;; Define the string constant that backs up the cursor, over- ;; writes the last character, and then backs up to the empty ;; spot. UART_RxStringErase: dt "\b \b",0 ;;; -------------------------------------------------------------------- ;;; Constant Data (if not placed along with the subroutines). ;;; -------------------------------------------------------------------- END