===Interrupt Driven RS232 Receive and Transmit===
This is my fully buffered, fully interrupt driven RS232 module for PIC16F877. It is does not waste any time with sending characters and waiting for them to go (I hate routines like that!). With this set you can Tx by filling a buffer and then set the Tx running and just get on with your application - the interrupt will take care of everything and stop once the buffer is empty.
It relies on some macros which you'll find elsewhere in the library.
Enjoy
Preamble
; originally for PIC16F877 - you should be able to adapt it without too much hassle
; *** Bank0/1/2/3 mirrored in all banks 0x70, 0xF0, 0x170, 0x1F0, 16 bytes
; accesible from all banks
CBLOCK 0x70
GENTEMP ; 0
FLAGS ; 3 application and subsystem flags
; - 0 RX Buffer active - we have stuff in the buffer
; - 1 RX Buffer OVF - the buffer has overflowed
; - 2 RS232 TXINPROGRESS flag - we must wait to put a char in the buffer if set
; - 3 RX Buffer full - no more space - next char will overflow
; - 4 RX buffer has recieved a
; - 5
; - 6
; - 7
RXCHTEMP ; 4
TXTEMPFSR ; 5 |
TXTEMPSTATB ; 6 |
TXCHTEMP ; 7 | context saving in TXBUFFQ
TXTEMPSTAT ; 8 /
SAVED_W ; 9 |
SAVED_STATUS ; A | context saving in ISR
SAVED_PCLATH ; B |
SAVED_FSR ; C /
ENDC
; *** Bank1 *** 80 bytes
CBLOCK 0xA0
RXSHUFFSRC
RXSHUFFDST
RXBUFFRDPTR
RXBUFFWRPTR
RXBUFF:RXBUFFSIZE
ENDC
; *** Bank2 *** extra ram 96 bytes
CBLOCK 0x110
TXBUFFPTR ; when TXINPROGRESS=0; points to the free place in the buffer for TXBUFFQ
; when TXINPROGRESS=1; used to step through the buffer by TXBUFFUNQ
TXBUFF: TXBUFFSIZE
; this buffer is a good size general purpose text buffer. Although it is aimed
; at RS232 TX, it can be used to hold strings for any reason
; binary and bcd outputs write here but the output doesn't go anywhere
; until we say (or the buffer overflows)
ENDC
; *** Bank3 *** extra ram 96 bytes
CBLOCK 0x190
CTR,II ; gp counters
TF,TF2 ; pointers
ENDC
ISR considerations
ISR:
ORG 4
PUSH
;BANK0 is implicit from the PUSH macro
; the handler routines are arranged in order of urgency
ISR_RX_IRQ: ; RS232 Rx char recieved
SKIPHI PIR1,5
GOTO ISR_RX_IRQRET
LO PIR1,5 ; clear flag
MOVFW RCREG
GOTO RXBUFFQ ; in the RS232 module
ISR_RX_IRQRET:
ISR_TX_IRQ: ; RS232 Tx Complete
SKIPHI PIR1,4
GOTO ISR_TX_IRQRET
CALL TXBUFFUNQ
ISR_TX_IRQRET:
ISREND:
POP
RETFIE
the actual RS232 routines
;
; RS232 Module
; Routines:
; TXBUFFQ Place the char in W in the buffer but doesn't send anything. If you fill the buffer, it will trigger TXSTART
; and you'll be kept waiting while the buffer empties, then your char is put in the buffer for next time.
; TXBUFFUNQ only called as part of the Tx ISR! ***do not call*** Sets the Tx flag and so empties the buffer to the RS232 TX line in the background.
; TXSTART Start TXBUFFUNQ - set the flag to begin outputting chars from the buffer - usually causes an immediate interrupt (because of TXEN=1)
; chars must be buffered. To output a single char immediately:
; MOVLW "*" - my character
; CALL TXBUFFQ - effectively a "print W" routine
; CALL TXSTART - char will be output as part of the buffer
;
; RXBUFFQ This is the ISR handler for RX - places the Rx byte in the buffer
; RXBUFFREAD Read a character from the buffer if there is one; returns W=0 if not
; RXBUFFSHUFFLE Remove read chars from the buffer
; RXBUFFCLEAR Clear the buffer and reset all pointers & flags
;
;
; if the buffer fills during TXBUFFQ, TXSTART is called implicitly. Thus TXBUFFQ can *always* take your char
; but you might have to wait for the buffer to empty. Cannot buffer chars while sending - yet!
; buffer is empty after TXBUFFUNQ
;
; FLAGS,2 is a global "TX in progress flag"
;
; has specific register requirements - see the kernel
TXBUFFSIZE EQU D'80'+1 ;+1 allows full buffer size plus the zero endstop
RXBUFFSIZE EQU D'40'
#DEFINE TXINPROGRESS FLAGS,2 ; TX Buffer is being emptied - no more queuing until finished
#DEFINE RXBUFFACTIVE FLAGS,0 ; RX Buffer active - we have stuff in the buffer
#DEFINE RXBUFFEROVF FLAGS,1 ; RX Buffer OVF - the buffer has overflowed - the data is unreliable because chars have been lost
#DEFINE RXBUFFERFULL FLAGS,3 ; RX Buffer FULL - next char will cause overflow
;*************************************
; INSIDE THE ISR!!!!
;*************************************
; un-queue the next character in the buffer. Buffer must end with zero byte
; if the buffer is empty (we don't want any more interrupts), ensure we have
; finished sending the last byte and disable the Tx and thus its interrupt.
TXBUFFUNQ:
BANK2 ; all TX Buffers & ptrs are in BANK2, don't use quick banks coz of INDF
MOVLF LOW TXBUFF,FSR ; calculate the current character position in the buffer
MOVFW TXBUFFPTR
ADDWF FSR ; here INDF is the nth charctaer in the buffer
MOVFW INDF
JMPZ NOCHARS ; end of the data?
BANK0F ; quick bank0 :
MOVWF TXREG
BANK2F ; quick bank2 :
INCF TXBUFFPTR ; ... and increment the pointer for the next char
TIDYEXIT:
BANK0F
RETURN
; we have a char zero - we are at the end of the data or have nothing to send.
; We interrupted (we are here) so we need to disable TXEN but not until
; TRMT goes high
TXBUFFCLR:
NOCHARS:
CLRF TXBUFFPTR ; clear the Tx buffer: reset the pointer to 0...
CLRF TXBUFF ; ... and clear the fisrt byte in the buffer
BANK1
BTFSS TXSTA,TRMT ; check if the last character has finished sending
GOTO TIDYEXIT ; if not, just exit
LO TXSTA,TXEN ; We finished sending so disable the Tx to remove the interrupt
LO TXINPROGRESS ; tell the world we are no longer emptying the buffer
GOTO TIDYEXIT ; and play nicely
; the RX buffer routine
; jumped-to from the ISR RX handler so consider it in the ISR
; W contains the recieved char
RXBUFFQ:
BANK1
BTFSC RXBUFFERFULL ; the buffer has space ?
GOTO RXBUFFBROKE
MOVWF RXCHTEMP ; save the char
CP RXCHTEMP,D'13'
BTFSC STATUS,Z
HI FLAGS,4 ; current character is a
MOVLW LOW RXBUFF ; point to the start of the buffer...
ADDWF RXBUFFWRPTR,W ; add the pointer
MOVWF FSR ; here INDF is the nth character in the buffer
MOVFF RXCHTEMP,INDF ; put the char in the buffer
INCF RXBUFFWRPTR ; move the pointer along
HI RXBUFFACTIVE ; signal we have stuff
CP RXBUFFWRPTR,RXBUFFSIZE ; check for end of buffer
SKIPNZ ; recieve buffer is not full
HI RXBUFFERFULL ; you need to take some stuff out of the buffer immediately
GOTO TIDYEXIT
RXBUFFBROKE:
HI RXBUFFEROVF ; oh dear... got a char but no room for it
GOTO TIDYEXIT
;*************************************
; OUTSIDE THE ISR!!!!
;*************************************
; this starts the TX buffer emptying. It does this by simply enabling TXIF
; then everything is handed off to the ISR.
TXSTART:
BTFSC TXINPROGRESS ; jump back if we are already doing it
RETURN
DI
MOVFF STATUS,TXTEMPSTATB ; preserve the bank bits
HI TXINPROGRESS ; tell the world we are emptying the buffer
BANK2
CLRF TXBUFFPTR ; this pointer is used to empty the buffer now
BANK1
HI TXSTA,TXEN ; we'll get an almost immediate interrupt after EI and TXREG will be
; rapidly filled with the first 2 bytes, after that we can expect interrupts
; every ~100uS. Don't try to put anything in the TX buffer. If you do, a
; wait up to TXBUFFZIZE*100uS (while it empties) will occur
MOVFF TXTEMPSTATB,STATUS ; restore the bank bits
EI
RETURN
; queue a character in the next free space in the buffer. If the buffer fills
; then it will call txstart to empty the buffer to make room.
; routine must be single threaded. If you write it from the ISR, chance it happens
; while you were writing it anyway, regs get corrupted and it crashes the system
TXBUFFQ:
MOVWF TXCHTEMP ; save the char
MOVFF STATUS,TXTEMPSTAT ; preserve the bank bits
MOVFF FSR,TXTEMPFSR
BTFSC TXINPROGRESS ; if we are emptying the buffer, we must wait before we can proceed
GOTO $-1
DI ; other things use FSR
BANK2 ; all TX Buffers & ptrs are in BANK2
MOVLF LOW TXBUFF,FSR ; point to the start of the buffer...
MOVFW TXBUFFPTR ; add the pointer
ADDWF FSR ; here INDF is the nth character in the buffer
MOVFF TXCHTEMP,INDF ; put the char in the buffer
INCF TXBUFFPTR ; move thge pointer along
INCF FSR ; point to the next position
CLRF INDF ; always write a zero byte after each char. automatically inserts EOB char
CP TXBUFFPTR,TXBUFFSIZE-1 ; check for end of buffer
CALLZ TXSTART ; transmit buffer is full so empty it
MOVFF TXTEMPFSR,FSR
MOVFF TXTEMPSTAT,STATUS ; restore the bank bits
EI ; interrupts potentially been delayed 30-ish uS but it is tidy this way
RETURN
;reset the RX BUFFER
RXBUFFCLEAR:
DI
MOVFF STATUS,RXCHTEMP ; preserve the bank bits
BANK1
CLRF RXBUFFRDPTR
CLRF RXBUFFWRPTR
LO RXBUFFERFULL
LO RXBUFFEROVF
LO RXBUFFACTIVE
LO FLAGS,4
MOVFF RXCHTEMP,STATUS ; restore the bank bits
EI
RETURN
; shuffle the top of the buffer down. from RXBUFFERPTR to zero
; this way we can recieve partial bits and still leave them in a
; state they can be parsed sequentially, i.e. we don't have to
; take everything in the buffer in one go
RXBUFFSHUFFLE:
DI
MOVFF STATUS,TXTEMPSTAT ; preserve the bank bits - using TX temp stat !
BANK1
BUFFEMPTY: ; this is exit for the read routine; if there was nothing to
; read, either coz the buffer is empty or coz the WR & RD pointers
; are the same, we try to do a shuffle to keep things tidy
MOVFW RXBUFFWRPTR
JMPZ NOSHUFFLE ; if WR is already zero, then nothing to do
;adjust the WR pointer
MOVFW RXBUFFRDPTR ; WR pointer - RD pointer = new WR pointer
JMPZ NOSHUFFLE ; if RD is zero, we have no where to go
SUBWF RXBUFFWRPTR ; otherwise compute a new place to write to
; calculate the source & destination pointers in the buffer
MOVLF LOW RXBUFF,RXSHUFFDST ; destination for the data
ADDWF RXBUFFRDPTR,W
MOVWF RXSHUFFSRC ; source of the data
;now go round in a loop until the pointer is at the end of the buffer+1 (after the INCF)
SHUFFLOOP:
MOVFF RXSHUFFSRC,FSR ;move the byte
MOVFF INDF,RXCHTEMP
MOVFF RXSHUFFDST,FSR
MOVFF RXCHTEMP,INDF
;calculate new positions
INCF RXSHUFFSRC
INCF RXSHUFFDST
INCF RXBUFFRDPTR
CP RXBUFFRDPTR,RXBUFFSIZE+1; have we reached the buffer end
JMPNZ SHUFFLOOP ; go again if not
MOVFW RXBUFFWRPTR ; otherwise, point to first position (where our data
SKIPNZ
LO RXBUFFACTIVE ; if the WR pointer is 0 then the buffer is empty
LO RXBUFFERFULL ; we shuffled so the buffer can't be full
NOSHUFFLE:
CLRF RXBUFFRDPTR
MOVFF TXTEMPSTAT,STATUS ; restore the bank bits
MOVLW 0 ; this is here for the read exit
EI
RETLW 0
; read a character from the RXBUFFER
RXBUFFREAD:
DI
MOVFF STATUS,TXTEMPSTAT ; preserve the bank bits - using TX temp stat !
BANK1
MOVFW RXBUFFWRPTR ; if the write pointer is zero, nothing there
JMPZ BUFFEMPTY
SUBWF RXBUFFRDPTR,W ; compare RD & WR pointers, don't care so long as they not the same
JMPZ BUFFEMPTY ; if they are attempt a shuffle
;looks good, lets compute the buffer position and get our character
MOVLW LOW RXBUFF ; destination for the data
ADDWF RXBUFFRDPTR,W
MOVWF FSR
MOVFF INDF,DATAL
INCF RXBUFFRDPTR ; move the read ptr along
MOVFF TXTEMPSTAT,STATUS ; restore the bank bits
MOVFW DATAL ; in W
EI
RETURN
; end RS232 module