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 <cr> ; - 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 <cr> 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