; Basic Wireless Communication for Microcontrollers ; Design Project #3 - 900MHz Error-Correcting Data Link ; by Sean H. Breheny ; Last Modified 9/20/2001 ; for a PIC16F876 running on a 16MHz crystal ; Thanks to Fr. Tom McGahee for providing the general-purpose "shell" code from which ; this program was developed ; Thanks to Tony Kubek for the method of computing baud rate divisor while minimizing round-off ; error ; Thanks to Scott Dattalo for the method of creating code size efficient, long delays ; PIN DEFINITIONS ; PORTA ; RA0 -> RF Receive LED (RXR) ; RA1 -> RF Transmit NACK LED (TXN) ; RA2 -> RF Transmit ACK LED (TXA) ; RA3-RA5 -> none ; PORTB ; RB0 <- RF Receive data in (RXDATA) ; RB1 -> RF Receive enable (RXEN) ; RB2 -> RF Transmit enable (TXEN) ; RB3 <- none (externally tied low via resistor to prevent accidental LVP) ; RB4 -> RF Transmit data out (TXDATA) ; RB5 -> none ; RB6 -> RF Receive NACK LED (RXN) ; RB7 -> RF Receive ACK LED (RXA) ; PORTC ; RC0 -> none ; RC1 -> none ; RC2 -> CTS out ; NOTE: for both CTS and RTS, high means stop ; RC3 <- RTS in ; low means go ; RC4 -> none ; RC5 -> none ; RC6 <- USART TX (hardware driven, set to input) ; RC7 <- USART RX (hardware use, set to input) ; PREPROCESSOR DIRECTIVES ERRORLEVEL -302 ; Turn off silly Bank-Bits Notification ; 16F876 processor, using Intel 8bit Hex output file, Default Radix is DECimal LIST P=16F876, F=INHX8M, R=DEC ; Include the header file for this processor INCLUDE P16F876.INC ; No code protection, HS (High Speed) oscillator, power-up timer on, ; LVP (Low Voltage Programming) off __CONFIG _CP_OFF & _HS_OSC & _WDT_OFF & _PWRTE_ON & _LVP_OFF ; CONSTANTS AND I/O PIN IDENTIFIERS rx0_size equ .45 ; Buffer sizes (plus one because they are circular) tx0_size equ .45 rx1_size equ .59 rx_packet_size equ .50 ; Actual buffer size (not circular) cts_min equ .10 ; Drop CTS line if less than this number of bytes in RX buffer cts_max equ .26 ; Raise CTS line if more than this number of bytes in RX buffer RTS equ 3 ; PORTC pin definitions CTS equ 2 RXRLED equ 0 ; PORTA pin definitions TXNLED equ 1 TXALED equ 2 RXALED equ 7 ; PORTB pin definitions RXNLED equ 6 RFTXDATA equ 4 RFTXEN equ 2 RXEN equ 1 RFRX equ 0 rawpaclen equ .20 ; Maximum length of packet (raw data before escaping, etc.) WAITACK equ 6 ; FLAGS0 bit definitions CRCGOOD equ 5 NACKED equ 4 SENDACK equ 2 SENDNACK equ 1 RXSTART equ 0 CUR_PACKET_VALID equ 0 ; FLAGS1 bit definition LEDDELAY equ .50 ; About a half second on time for status LEDs CODE_ACK equ 0x00 ; Packet type IDs CODE_NACK equ 0x01 CODE_RACK equ 0x02 CODE_DATA equ 0x03 FLAG equ 0xAB ; Flag byte ESCAPE equ 0xAC ; Escape byte ESCAPEXOR equ 0x20 ; XOR bytes by 0x20 to escape them RACK_TIME equ .100 ; about 0.5 second for RACK timeout SEND_TIME equ .20 ; about 0.2 second for SEND timeout ; Variables and Buffers cblock 0x20 rx0_addr: .46 ;RX0 (from PC via USART) RX circular buffer rx0_in_ptr ;buffer pointers rx0_out_ptr rx1_in_ptr rx1_out_ptr tx0_in_ptr tx0_out_ptr tx_data ; byte to be transmitted via UART - used in transmitw, rf_transmit_string ; FLAG bits for FLAGS0 and FLAGS1, in order from 7 to 0 (left to right) FLAGS0 ; [none][WAITACK][CRCGOOD][NACKED][none][SENDACK][SENDNACK][RXSTART] FLAGS1 ; [7-1 none][CUR_PACKET_VALID] BITCOUNT ; Counter to count the bits being received over the air BITSAMPLECOUNT ; Counter to count the samples per bit received over the air BITSUM ; Sum of all samples for each bit being received over the air RFRXBYTE ; Byte which has been received over the air uart1_tx_cmd_length ; Number of bytes in TX command buffer (to go out over the air) uart1_tx_data_length ; Number of bytes in TX data buffer (to go out over the air) rf_packet_buffer_length ; Number of bytes (after removing the flag and escaping) ; received over the air lastID ; packID received from last incoming packet outgoing_packID ; packID to go out with the next packet rx_CRC0 ; To store CRC computed from incoming (from the air) data rx_CRC1 ; " second byte tx_CRC0 ; To store CRC to be sent with outgoing data tx_CRC1 ; " second byte stated_packID ; packID from incoming packet just received savFSR0 ; Temp locations to save FSR in various parts of the code savFSR1 savFSR2 savFSR3 savFSR4 CRCscrp ; Scrap variable used by CRC routines CRCloopvar ; Loop counter used by CRC routines length_addr ; Address (in outgoing buffer, uart1_tx_data buffer) of length field ; First CBLOCK takes up memory up to location 0x6D endc cblock 0x70 ; Placed in a block of ram which remains the same ; address space regardless of BANK setting savew1 ; Used to save W in ISR savestatus ; Used to save STATUS in ISR savepclath ; Used to save PCLATH in ISR savefsr ; Used to save FSR in ISR USER0 ; Various temp variables USER1 USER2 USER3 USER4 USER5 USER6 endc cblock 0xA0 rx1_addr: 60 ; RX1 (from RF via software) RX circular buffer TIMER_RFBITDELAY ; Used to time length of a bit for RF TX TIMER_DELAY10MS ; Used to create 10ms interval for delay routine TIMER_SLOW ; Used to create 10ms interval for slow timers TIMER_WAITCHANNELCLEAR ; Used to wait a variable length of time since the last ; time something was received to make sure channel is clear TIMER_RACK ; Used to send a RACK if the other end doesn't respond to ; a DATA packet with either an ACK or a NACK within a ; specified time interval TIMER_SEND ; Used to send a shorter-than-maximum length packet if the PC ; doesn't send more data within a specified interval TIMER_TXALED ; Timers to turn off status LEDs after LEDDELAY time TIMER_TXNLED TIMER_RXALED TIMER_RXNLED endc cblock 0x110 tx0_addr: 46 ; Circular buffer to hold data to be sent to the PC rx_packet_addr: 50 ; Buffer to hold data received from over the air ; (could be shorter than 50) endc cblock 0x190 tx1_data_addr: 50 ; Buffer to hold outgoing DATA packet tx1_cmd_addr: 10 ; Buffer to hold outgoing COMMAND packet endc ; CODE BEGINS org h'0000' ; Set code origin to beginning of rom start goto initialize ; We must get past interrupt vector at 0004 ; INTERRUPT SERVICE ROUTINE (ISR) org h'0004' ; Interrupt vector location inthandler movwf savew1 ; Save w register! movf status,w ; W now has copy of status clrf status ; Ensure we are in bank 0 now! movwf savestatus ; Save status movf pclath,w ; Save pclath movwf savepclath clrf pclath ; Explicitly select Page 0 movf FSR,w movwf savefsr ; Save FSR vector_to_interrupt ; Test to see which interrupt ; needs servicing... btfsc intcon,t0if ; TMR0 interrupt goto service_t0if btfsc intcon,intf ; INT0 interrupt (RF start bit detected) goto service_intf btfsc PIR1,RCIF ; USART RX interrupt (start bit from PC detected) goto service_rcif btfsc PIR1,TXIF ; USART TX interrupt (TX FIFO to PC is empty) goto service_txif goto intclean ; Clear interrupt flags if we don't recognize the ; type of interrupt service_t0if ; Timer (TMR0) interrupt bsf STATUS,RP0 ; Timers are in bank 1 incf TIMER_RFBITDELAY,F ; Increment timers incf TIMER_DELAY10MS,F decfsz TIMER_SLOW,F ; Only increment "slow timers" when TIMER_SLOW rolls over goto skip_slow_timers ; "Slow timers" (Timers which update at 1/256th the rate of TMR0 interrupts) ; about 100Hz update rate ; These timers get decremented and remain at zero if they reach zero movf TIMER_WAITCHANNELCLEAR,W btfss STATUS,Z decf TIMER_WAITCHANNELCLEAR,F movf TIMER_RACK,W btfss STATUS,Z decf TIMER_RACK,F movf TIMER_SEND,W btfss STATUS,Z decf TIMER_SEND,F movf TIMER_RXALED,W btfss STATUS,Z decf TIMER_RXALED,F movf TIMER_RXNLED,W btfss STATUS,Z decf TIMER_RXNLED,F movf TIMER_TXALED,W btfss STATUS,Z decf TIMER_TXALED,F movf TIMER_TXNLED,W btfss STATUS,Z decf TIMER_TXNLED,F skip_slow_timers bcf STATUS,RP0 ; Back in bank 0 btfsc FLAGS0,RXSTART ; Go to RF receive routine if there is current incoming data goto rx1_do ; This way, rx1_do gets called 5 times per incoming bit t0if_done ; "Clean up" TMR0 interrupt ; TMR0 interrupt should occur 5*4800 Hz=24kHz movlw -167 ; To get this, we subtract 167 from TMR0 addwf TMR0,F ; So that TMR0 interrupt happens every 167 instructions bcf intcon,t0if ; Clear interrupt flag that caused interrupt. goto intclean ; Restore and return from interrupt! rx1_do ; RF receive routine btfss PORTB,RFRX ; Increment BITSUM if incoming sample is high decf BITSUM,F ; Decrement BITSUM if sample is low btfsc PORTB,RFRX incf BITSUM,F incf BITSAMPLECOUNT,F ; Increment sample count movf BITSAMPLECOUNT,W ; If we do not yet have 5 samples, finish the TMR0 interrupt addlw -5 btfss STATUS,C goto t0if_done clrf BITSAMPLECOUNT ; Otherwise, if we do have 5 samples, clear counter incf BITCOUNT,F ; Go to next bit movf BITCOUNT,W ; If BITCOUNT=1, go to the startbit routine addlw -1 btfsc STATUS,Z goto startbit rlf BITSUM,W ; Move inverse of BITSUM bit 7 into bit 7 of RFRXBYTE rrf RFRXBYTE,F ; So, if BITSUM < 0, the next bit shifted into RFRXBYTE movlw 10000000b ; is a zero (more 0 samples were received than 1 samples) xorwf RFRXBYTE,F ; Otherwise, shift in a 1 bit clrf BITSUM ; Clear BITSUM in preparation for next bit movf BITCOUNT,W ; If BITCOUNT=9 (8 data bits and 1 start bit) continue on addlw -9 ; Otherwise, finish interrupt (t0if_done) to wait for the rest btfss STATUS,C ; of the bits goto t0if_done clrf BITCOUNT ; We are going to next byte now, so clear bit counter bcf INTCON,INTF ; Clear falling edge interrupt flag nop nop bsf INTCON,INTE ; Re-enable felling edge interrupt to catch next start bit bcf FLAGS0,RXSTART ; We are no longer in the midst of receiving a byte so bcf PORTA,RXRLED ; clear RXSTART flag and turn off RXR LED. movf rx1_out_ptr,W ; See how many bytes are in RX1 buffer subwf rx1_in_ptr,W ; W=in-out btfss STATUS,C ; If out<=in, just return W addlw (rx1_size+1) ; otherwise, return (size+1)-W addlw -rx1_size ; If the number of bytes is equal to or greater than btfsc STATUS,C ; RX1_size, then the buffer is full and we just discard the goto t0if_done ; new byte by skipping ahead to T0IF done ; NOTE: size of circ buffer is one less than the allocated ; memory (think of memory length 3 example to see why) ; if we try to store three, we can fit them, but then the ; full state will be the same as the empty state ; so, for memory length 3 example, rx0_size=2, and we can ; only store 2 bytes movf rx1_in_ptr,W ; Compute address to write byte to addlw rx1_addr movwf FSR movf RFRXBYTE,w ; Place received byte in RX1 buffer movwf INDF incf rx1_in_ptr,F ; Increment "in" pointer movf rx1_in_ptr,W ; Roll it over if needed addlw -(rx1_size+1) btfsc STATUS,C clrf rx1_in_ptr goto t0if_done ; Finish interrupt startbit ; We just finished receiving the start bit, and clrf BITSUM ; there is no need to store it anywhere, just discard and goto t0if_done ; finish interrupt so we can be ready for next bit service_intf ; Start bit detected on RF receive bsf FLAGS0,RXSTART ; Set the RXSTART flag bsf PORTA,RXRLED ; Turn on the RXR LED clrf BITCOUNT ; Clear RF RX status variables clrf BITSAMPLECOUNT clrf BITSUM clrf RFRXBYTE bcf INTCON,INTE ; Disable falling-edge interrupt (which detects start bits) ; since we won't have another start bit until after we ; finish receiving this byte ; NOW, we compute a pseudo random delay movlw 0x0F ; Place lower nibble of TMR0 andwf TMR0,W ; in TIMER_WAITCHANNELCLEAR btfsc STATUS,Z ; unless the nibble is zero movlw 1 ; in which case put one bsf STATUS,RP0 movwf TIMER_WAITCHANNELCLEAR bcf STATUS,RP0 ; The RF transmit routines will wait this delay before ; sending, to make sure the channel is clear. In other words, ; TIMER_WAITCHANNELCLEAR will only expire a certain delay ; after the last time some data was received over the radio. ; When it expires, it should be safe to transmit. intf_done bcf intcon,intf ; Clear flag that caused interrupt. goto intclean ; Restore and return from interrupt! service_rcif ; PC USART (UART0) receive btfsc rcsta,oerr goto overerror ; If overflow error, handle it btfsc rcsta,ferr goto frameerror ; If framing error, handle it movf rx0_out_ptr,W ; See how many chars are in RX0 buffer subwf rx0_in_ptr,W ; W=in-out btfss STATUS,C ; If out<=in, just return W addlw (rx0_size+1) ; otherwise, return (size+1)-W addlw -cts_max ; If the number of bytes in RX0 buffer btfsc STATUS,C ; goes equal or above cts_max, then assert CTS bsf PORTC,CTS addlw cts_max addlw -(rx0_size) ; If the number of bytes is equal to or greater than btfsc STATUS,C ; RX0_size, then the buffer is full and we just discard the goto rcif_full ; new byte movf rx0_in_ptr,W ; Compute address to write incoming byte addlw rx0_addr movwf FSR movf rcreg,w ; Recover uart data movwf INDF ; Place in RX0 buffer incf rx0_in_ptr,F ; Increment RX0 "in" pointer movf rx0_in_ptr,W ; Roll it over if needed addlw -(rx0_size+1) btfsc STATUS,C clrf rx0_in_ptr goto rcif_done ; Finish interrupt rcif_full movf rcreg,w ; Read in byte to clear USART hardware ; and prevent overrun error goto rcif_done ; Just discard byte and finish interrupt overerror bcf rcsta,cren ; Pulse CREN off... movf rcreg,w ; Flush fifo movf rcreg,w ; All three elements. movf rcreg,w bsf rcsta,cren ; Turn CREN back on. ; This pulsing of CREN ; will clear the OERR flag. goto rcif_done ; Finish interrupt frameerror movf rcreg,w ; Reading RCREG clears FERR flag. goto rcif_done ; Finish interrupt rcif_done ; Just use the intclean routine to finish interrupt goto intclean service_txif movf tx0_out_ptr,W ; PC USART (UART0) transmit subwf tx0_in_ptr,W ; W=in-out btfss STATUS,C ; If out<=in, just return W addlw (tx0_size+1) ; otherwise, return (size+1)-W addlw 0 ; If there are no bytes to send, skip this btfsc STATUS,Z goto txif_none btfsc PORTC,RTS ; If the PC says "hold", disable the TX goto txif_none ; interrupt (it will be re-enabled by checkrts ; when PC releases hold. movf tx0_out_ptr,W ; Compute address to read byte from addlw tx0_addr movwf FSR bsf STATUS,IRP ; Bank 2 for TX0 buffer movf INDF,w ; Get byte bcf STATUS,IRP ; Back to bank 0 movwf txreg ; Send it incf tx0_out_ptr,F ; Increment TX0 "out" pointer movf tx0_out_ptr,W ; Roll it over if needed addlw -(tx0_size+1) btfsc STATUS,C clrf tx0_out_ptr goto txif_done ; Finish interrupt txif_none ; If there are no bytes to send, bsf STATUS,RP0 ; disable the TX FIFO empty interrupt bcf PIE1,TXIE ; Otherwise, it would interrupt continuously bcf STATUS,RP0 ; transmitw routine will re-enable if needed goto txif_done txif_done ; Finish interrupt goto intclean intclean ; Routine to clean up after interrupt movf savefsr,w movwf fsr ; Restore FSR movf savepclath,w movwf pclath ; Restore PCLATH (Page=original) movf savestatus,w movwf status ; Restore STATUS (bank=original) swapf savew1,f ; Restore w from *original* bank swapf savew1,w ; Swapf does not affect any flags retfie ; Return from interrupt ; GIE is auto-re-enabled ; End of interrupt service routine ;INITIALIZATION ROUTINE initialize ; Initialize ports and registers bcf status,rp0 ; First do page 0 stuff gie01 bcf intcon,gie ; Turn GIE off btfsc intcon,gie ; MicroChip recommends this check! goto gie01 ; !!! GOTCHA !!! without this check ; you are not sure GIE is cleared! clrf sspcon ; Sync serial not used if in async mode. clrf pir1 ; Clear peripheral flags clrf pir2 ; All of them ; NOTE: set all initial output states BEFORE setting TRIS ; so we won't have an output coming up in an unintended state clrf porta ; Clear all i/o registers... clrf portb clrf portc clrf rx0_in_ptr ; Clear a bunch of variables clrf rx0_out_ptr clrf rx1_in_ptr clrf rx1_out_ptr clrf tx0_in_ptr clrf tx0_out_ptr clrf uart1_tx_cmd_length clrf uart1_tx_data_length bsf STATUS,RP0 clrf TIMER_SLOW clrf TIMER_WAITCHANNELCLEAR clrf TIMER_RFBITDELAY clrf TIMER_DELAY10MS clrf TIMER_SLOW clrf TIMER_WAITCHANNELCLEAR clrf TIMER_RACK clrf TIMER_SEND clrf TIMER_TXALED clrf TIMER_TXNLED clrf TIMER_RXALED clrf TIMER_RXNLED bcf STATUS,RP0 clrf rf_packet_buffer_length clrf lastID comf lastID,F ; Prevents first packet (packID=0) from having same ID as lastID ; which would cause it to be discarded as a duplicate clrf outgoing_packID clrf rx_CRC0 clrf rx_CRC1 clrf tx_CRC0 clrf tx_CRC1 bcf PORTC,CTS ; De-assert CTS clrf FLAGS0 clrf FLAGS1 bsf status,rp0 ; Allow access to page 1 stuff ;********** page 1 stuff clrf pie1 ; Disable peripheral interrupts clrf pie2 ; All of them movlw b'00000111' ; Set all i/o as digital, no analog movwf adcon1 clrf TRISA ; PORTA is all outputs movlw 00001001b ; PORTB is all outputs except movwf TRISB ; RB0,RB3 movlw b'11001000' ; Set PORTC direction for i/o pins movwf trisc ; 0=output 1=input ; RC7 input _uart_rc ; !!! rc6 input _uart_tx. Actually an output, ; !!! BUT for UART use rc6 MUST be programmed ; !!! as an INPUT! ; OPTION register handles several details... bsf option_reg,not_rbpu ; Disable PORTB pullups bcf option_reg,intedg ; INT0 interrupt on falling edge, ; to detect start bits from radio bcf option_reg,t0cs ; Set TMR0 to use internal clock bcf option_reg,t0se ; TMR0 increments on rising edge, doesn't ; matter for internal clock bsf option_reg,psa ; Assign prescaler to WDT bcf option_reg,ps2 ; Prescaler values do not matter, not used bcf option_reg,ps1 bcf option_reg,ps0 clrf intcon ; Start with all interrupts disabled bsf INTCON,PEIE ; Enable peripheral interrupts (for USART) bcf INTCON,INTF ; Clear INT0 flag nop ; Wait before enabling INT0 nop bsf INTCON,INTE ; Enable INT0 edge interrupt bsf INTCON,T0IE ; Enable TMR0 rollover interrupt clrf pie1 ; Start with all #1 peripheral interrupts disabled bsf PIE1,RCIE ; Enable USART RX interrupt bcf pie2,ccp2ie ; Disable CCP2 interrupt ; USART specific initialization ;txsta=Transmit STAtus and control register. ;take nothing for granted. bcf txsta,csrc ; <7> (0) don't care in asynch mode bcf txsta,tx9 ; <6> 0 select 8 bit mode bsf txsta,txen ; <5> 1 enable transmit function ; *MUST* be 1 for transmit to work!!! bcf txsta,sync ; <4> 0 asynchronous mode. ; *MUST* be 0 !!! ; If NOT 0 the async mode is NOT selected! ; <3> (0) not implemented bcf txsta,brgh ; <2> 0 disable high baud rate generator !!! ; !!! errata sheet says NOT to set high for ; 16C74A due to excessive receive errors! ; 1 (0) trmt is read only. bcf txsta,tx9d ; <0> (0) tx9d data cleared to 0. ; Tony Kubek's method of setting baud rate divisor while adjusting for ; rounding XTAL_FREQ EQU d'16000000' ; OSC freq in Hz baudrate = d'4800' ; 4800 Baud ; calculates baudrate when BRGH = 0, adjust for rounding errors spbrg_value = (((d'10'*XTAL_FREQ/(d'64'*baudrate))+d'5')/d'10')-1 ; Use this instead of the above line if BRGH=1 ; spbrg_value = (((d'10'*XTAL_FREQ/(d'16'*baudrate))+d'5')/d'10')-1 movlw spbrg_value ; Set baud rate generator value movwf spbrg ; Revert back to page 0 bcf status,rp0 ; Allow access to page 0 stuff again ; More uart specific initialization ;rcsta=ReCeive STAtus and control register ;assume nothing. bsf rcsta,spen ; 7 spen 1=rx/tx set for serial uart mode ; !!! very important to set spen=1 bcf rcsta,rx9 ; 6 rc8/9 0=8 bit mode bcf rcsta,sren ; 5 sren 0=don't care in uart mode bsf rcsta,cren ; 4 cren 1=enable constant reception ;!!! (and low clears errors) ; 3 not used / 0 / don't care bcf rcsta,ferr ; 2 ferr input framing error bit. 1=error ; 1 oerr input overrun error bit. 1=error ;!!! (reset oerr by neg pulse clearing cren) ;you can't clear this bit by using bcf. ;It is only cleared when you pulse cren low. bcf rcsta,rx9d ; 0 rx9d input (9th data bit). ignore. call startup_delay ; This delay lets everything stabilize (such as ; MAX233) before we try to do any real work movf rcreg,w ; Clear uart receiver movf rcreg,w ; including fifo movf rcreg,w ; which is three deep. ; !!! GOTCHA !!! if you do not initially do these three reads the ; receiver may come up with an error condition on start-up. movlw 0 ;any byte will do. movwf txreg ;send out dummy byte ; to get transmit flag valid! ; !!! SUPER HUMONGOUS GOTCHA !!! if you forget to send out an initial dummy ; byte, then the txif flag never goes high and your serial input ; routine will go round and round in circles forever waiting for txif ; to go high. bsf PORTB,RXEN ; Turn on RF receiver ; Ready now to begin main user program. main bsf INTCON,GIE ; Enable interrupts (globally) ; Main processing loop loop call update_status_LEDs ; See if any of the LED timers has expired ; in which case we should turn off that LED ; This allows status LEDs to just blink on for a ; short time when an event occurs. The event routine ; (such as when a packet is ACKed) just turns on the LED ; and resets its corresponding timer. This routine turns the ; LED off when the timer runs to zero. call checkrts ; Must be called each loop to check RTS ; to see if we can re-start sending call rx1_howmany ; Check for data FROM RF addlw 0 btfss STATUS,Z call getRFdata ; If data is present, get it ; The following checks for various conditions and handles them btfsc FLAGS0,SENDACK ; If an ACK is needed, send it goto doACK ret_doACK ; return point btfsc FLAGS0,SENDNACK ; If a NACK is needed, send that goto doNACK ret_doNACK btfss FLAGS0,WAITACK ; NOTE: the check for this MUST come before NACKED goto docheckdatabuffer ; Only if we are NOT waiting for an ACK (WAITACK=0) ; we check to see if there is data waiting from the PC ; and add it to the data to be transmitted over the air ret_docheckdatabuffer ; If a NACK was received, handle it btfsc FLAGS0,NACKED goto doNACKED ret_doNACKED btfsc FLAGS0,WAITACK ; If we are waiting for an ACK (WAITACK=1) goto docheckRACK ; check to see if we need to send a RACK ret_docheckRACK movf uart1_tx_cmd_length,W ; See if we should send the data in the btfss STATUS,Z ; RF TX command buffer goto uart1_tx_cmd_not_empty movf uart1_tx_data_length,W ; See if we should send the data in the btfss STATUS,Z ; RF TX data buffer goto uart1_tx_data_not_empty goto loop ; Loop around getRFdata ; Routine to remove data from RX1 buffer movf rf_packet_buffer_length,W ; Check to see if we have already started to btfss STATUS,Z ; assemble a packet goto packet_buffer_not_empty clrf rx_CRC0 ; Clear the CRC registers so we can clrf rx_CRC1 ; begin a new CRC calculation purgeloop ; Loop to remove all extraneous bytes from buffer call rx1_howmany ; If there are no my bytes waiting, go to none_left addlw 0 btfsc STATUS,Z goto none_left call rx1_check_char ; If the next byte is a flag, go to found_flag addlw -FLAG btfsc STATUS,Z goto found_flag call rx1_purge_one ; Otherwise, just purge one byte and loop around goto purgeloop none_left ; We're finished, all the bytes have been purged return ; we can just wait for more data to come in found_flag ; A flag byte was found in the incoming data call rx1_howmany addlw -2 btfss STATUS,C return ; The flag is all that is there, we need to ; leave it there and wait for more call rx1_purge_one ; get rid of the flag and continue packet_buffer_not_empty ; We already have some previously received packet data movf rf_packet_buffer_length,W ; Compute the address of the end of the addlw rx_packet_addr ; buffer and place it in FSR movwf FSR transfer_loop ; Continue to transfer data from the RF RX buffer ; into rx_packet_buffer call rx1_howmany ; Find out how many bytes are waiting to be transfered addlw 0 btfsc STATUS,Z ; If it is zero, just go back to transmit part return movf rf_packet_buffer_length,W ; Check to see if the buffer is full addlw -rx_packet_size btfsc STATUS,C goto process_rx_packet ; If so, just process what we have so far call rx1_check_char ; Check to see if the next RF RX byte is a FLAG addlw -FLAG btfsc STATUS,Z goto process_rx_packet ; If so, we have found the end of the packet, addlw FLAG ; process it, otherwise, add FLAG back to W ; so it has the original value in it addlw -ESCAPE ; Check to see if it is an ESCAPE byte btfsc STATUS,Z goto found_escape ; If so, deal with it addlw ESCAPE ; Otherwise, go back to original value bsf STATUS,IRP ; Place in rx_packet_buffer, which is in RAM bank 2 movwf INDF bcf STATUS,IRP call rx_CRC_w ; Include byte in CRC calculation call rx1_purge_one ; Actually remove byte from RF RX buffer ; (rx1_check_char was a nondestructive read) incf FSR,F ; Increment address in rx_packet_buffer incf rf_packet_buffer_length,F ; Increment buffer length goto transfer_loop ; Loop around found_escape ; Routine to deal with ESCAPE byte call rx1_howmany ; If there are fewer than 2 bytes in the RF RX addlw -2 ; buffer, just go back to the transmit routine and wait btfss STATUS,C ; for more bytes to come in, since return ; an ESCAPE byte must be followed by another byte call rx1_purge_one ; Purge the ESCAPE byte call rx1_ser_in ; Get the next byte (destructive read) xorlw ESCAPEXOR ; De-escape the second byte bsf STATUS,IRP ; Place de-escaped value in buffer movwf INDF bcf STATUS,IRP call rx_CRC_w ; Include in CRC computation, does not need to preserve W incf FSR,F ; Increment address incf rf_packet_buffer_length,F ; Increment length goto transfer_loop ; Loop around process_rx_packet ; Classify and act upon the contents of rx_packet_buffer movf rf_packet_buffer_length,W addlw -3 ; 2 or less bytes means only CRC, nothing else btfss STATUS,C goto purge_and_return ; Nothing to process, so go back (to transmit part) decf rf_packet_buffer_length,F ; Remove CRC decf rf_packet_buffer_length,F bsf FLAGS0,CRCGOOD ; Check CRC movf rx_CRC0,W btfss STATUS,Z bcf FLAGS0,CRCGOOD movf rx_CRC1,W ; Both bytes, the result of the btfss STATUS,Z ; computation is zero if the CRC is good bcf FLAGS0,CRCGOOD movlw rx_packet_addr ; FSR=address movwf FSR bsf STATUS,IRP movf INDF,W ; Get 1st byte(type) bcf STATUS,IRP btfsc STATUS,Z ; Branch off according to type goto typeACK addlw -1 btfsc STATUS,Z goto typeNACK addlw -1 btfsc STATUS,Z goto typeRACK addlw -1 btfsc STATUS,Z goto typeDATA ; Invalid type purge_and_return ; Clear rf_packet_buffer and go back to transmit part clrf rf_packet_buffer_length return typeACK ; Handle packets of type ACK btfss FLAGS0,CRCGOOD ; Ignore if CRC bad goto purge_and_return bcf FLAGS0,WAITACK ; We are no longer waiting for an ACK bcf FLAGS0,NACKED ; We have not been NACKed bsf PORTA,TXALED ; Signal that our packet was ACKed bsf STATUS,RP0 ; Set timer for TXALED movlw LEDDELAY movwf TIMER_TXALED bcf STATUS,RP0 incf outgoing_packID,F ; Increment the packID we will use btfsc outgoing_packID,7 ; Roll it over if it reaches 128 clrf outgoing_packID clrf uart1_tx_data_length ; Clear outgoing RF TX data buffer goto purge_and_return typeNACK ; Handle packets of type NACK btfss FLAGS0,CRCGOOD ; Ignore if CRC bad goto purge_and_return bsf FLAGS0,NACKED ; We have been NACKed bsf PORTA,TXNLED ; Signal so bsf STATUS,RP0 movlw RACK_TIME ; Set RACK timer movwf TIMER_RACK movlw LEDDELAY ; Set timer for TXNLED movwf TIMER_TXNLED bcf STATUS,RP0 goto purge_and_return typeRACK ; Handle packets of type RACK btfss FLAGS0,CRCGOOD ; Ignore if CRC bad goto purge_and_return movf rf_packet_buffer_length,W addlw -2 ; 1 or less means corrupted btfss STATUS,C ; (must have gotten past CRC) goto purge_and_return ; Ignore if corrupted incf FSR,F ; Go to 2nd byte in buffer bsf STATUS,IRP movf INDF,W ; Get 2nd byte(packID) bcf STATUS,IRP subwf lastID,W ; Compare with last received packID btfss STATUS,Z ; What to do if IDs don't match, indicating that goto IDs_dont_match ; we are getting a RACK for a packet other than the last ; received one (we are out of sync with the other end) btfsc FLAGS1,CUR_PACKET_VALID ; If the last packet was valid, go here goto packet_is_valid ; Packet is invalid (or IDs don't match) IDs_dont_match bsf FLAGS0,SENDNACK ; NACK it bcf FLAGS0,SENDACK ; Make sure we don't send an ACK goto purge_and_return packet_is_valid bcf FLAGS0,SENDNACK ; Send an ACK, not a NACK bsf FLAGS0,SENDACK goto purge_and_return typeDATA ; Handle packets of type DATA btfss FLAGS0,CRCGOOD ; Handle if CRC bad (we will send NACK) goto gotbadpacket movf rf_packet_buffer_length,W addlw -4 ; 3 or less means corrupted btfss STATUS,C ; (must have gotten past CRC) goto gotbadpacket ; NACK if corrupted incf FSR,F ; Go to 2nd byte decf rf_packet_buffer_length,F ; Remove type byte from length decf rf_packet_buffer_length,F ; Remove length byte from length decf rf_packet_buffer_length,F ; Remove packID byte from length bsf STATUS,IRP movf INDF,W ; Get 2nd byte(length) bcf STATUS,IRP btfsc STATUS,Z goto gotbadpacket ; Invalid length (0) addlw -21 btfsc STATUS,C goto gotbadpacket ; Invalid length (>20) addlw 21 ; Restore W to original value subwf rf_packet_buffer_length,W btfss STATUS,Z goto gotbadpacket ; Received length and stated length do not match incf FSR,F ; Go to 3rd byte bsf STATUS,IRP movf INDF,W ; Get 3nd byte(packID) bcf STATUS,IRP movwf stated_packID ; Hold it in this variable btfsc stated_packID,7 goto gotbadpacket ; Invalid packID (cannot be > or = 128) bsf FLAGS1,CUR_PACKET_VALID ; Packet is valid bcf FLAGS0,SENDNACK ; Send an ACK, not a NACK bsf FLAGS0,SENDACK movf stated_packID,W ; Is lastID=stated_packID ? subwf lastID,W btfsc STATUS,Z ; If it is, dump packet (it is a duplicate) goto purge_and_return ; but still ACK it, since we don't want to get stuck ; in an endless loop of NACK and retry movf stated_packID,W ; lastID=stated_packID movwf lastID final_transfer_loop ; Loop to place data from packet in buffer to be sent to PC incf FSR,F ; Increment address (next byte) bsf STATUS,IRP movf INDF,W ; Get the byte bcf STATUS,IRP call transmitw ; Send it to PC (actually places in buffer if there is ; already a byte in transit to PC) decfsz rf_packet_buffer_length,F ; Continue loop until we reach the end of the goto final_transfer_loop ; buffer goto purge_and_return ; Then clear the buffer and return gotbadpacket ; What to do if we get a bad data packet bcf FLAGS1,CUR_PACKET_VALID ; Packet is NOT valid bsf FLAGS0,SENDNACK ; Send a NACK, not an ACK bcf FLAGS0,SENDACK goto purge_and_return ; Clear buffer and return doACK ; Routine to send an ACK movlw tx1_cmd_addr ; Get address of RF TX command buffer in FSR movwf FSR movlw CODE_ACK ; Place ACK in buffer as TYPE byte bsf STATUS,IRP movwf INDF bcf STATUS,IRP movlw 1 ; Length is one (transmit routine adds FLAG, CRC, etc.) movwf uart1_tx_cmd_length bcf FLAGS0,SENDACK ; We have started the ACK on its way bsf PORTB,RXALED ; Turn on LED to signal that we have bsf STATUS,RP0 ; received a good packet movlw LEDDELAY ; Set timer for RXALED movwf TIMER_RXALED bcf STATUS,RP0 goto ret_doACK ; Return (call and return not used to help prevent ; stack overflow in the next few routines) doNACK ; Routine to send a NACK movlw tx1_cmd_addr ; Same as above but use TYPE=NACK movwf FSR movlw CODE_NACK bsf STATUS,IRP movwf INDF bcf STATUS,IRP movlw 1 movwf uart1_tx_cmd_length bcf FLAGS0,SENDNACK bsf PORTB,RXNLED ; Same as above, but RXNLED instead of RXALED bsf STATUS,RP0 movlw LEDDELAY movwf TIMER_RXNLED bcf STATUS,RP0 goto ret_doNACK docheckdatabuffer ; This routine takes data from PC and assembles outgoing packets btfsc FLAGS0,NACKED ; If we have been NACKED, we should not add to the goto ret_docheckdatabuffer ; data in the buffer, so return movf uart1_tx_data_length,W addlw -23 ; 23 is max length including data and other fields btfsc STATUS,C goto ret_docheckdatabuffer ; Buffer is already full call rx0_howmany addlw 0 btfsc STATUS,Z goto ret_docheckdatabuffer ; No data (from PC) to deal with movlw tx1_data_addr ; Compute RF TX data buffer address addwf uart1_tx_data_length,W movwf FSR ; Place in FSR movf uart1_tx_data_length,W btfss STATUS,Z goto already_data ; There is already data in buffer ; Otherwise, we are starting a new packet movlw CODE_DATA ; Set TYPE=DATA bsf STATUS,IRP movwf INDF bcf STATUS,IRP incf FSR,F ; Next byte incf uart1_tx_data_length,F movlw 0 ; Length byte=0 (we haven't put any data in yet) bsf STATUS,IRP movwf INDF bcf STATUS,IRP movf FSR,W ; Record length location so we can change length movwf length_addr ; later incf FSR,F ; Next byte incf uart1_tx_data_length,F movf outgoing_packID,W ; Set pack_ID bsf STATUS,IRP movwf INDF bcf STATUS,IRP incf FSR,F ; Next byte incf uart1_tx_data_length,F already_data rx0_tx1_tloop ; Loop to transfer data from RX0 buffer to RF TX buffer call rx0_ser_in ; Get next byte bsf STATUS,IRP ; Place in buffer movwf INDF bcf STATUS,IRP incf FSR,F ; Increment address and length incf uart1_tx_data_length,F movf FSR,W ; Preserve FSR movwf savFSR0 movf length_addr,W ; Increment length byte already in buffer movwf FSR bsf STATUS,IRP incf INDF,F bcf STATUS,IRP movf savFSR0,W ; Get old FSR back movwf FSR movf uart1_tx_data_length,W addlw -23 btfsc STATUS,C goto rx0_tx1_23 ; Buffer is full, we are finished call rx0_howmany addlw 0 btfsc STATUS,Z goto done_rx0_tx1 ; No more data to transfer, we are finished goto rx0_tx1_tloop ; Otherwise, loop around rx0_tx1_23 ; If the buffer is full, do not delay sending bsf STATUS,RP0 clrf TIMER_SEND ; Clear the SEND timer to make us send ASAP bcf STATUS,RP0 goto ret_docheckdatabuffer done_rx0_tx1 ; If we are finished transfering data and bsf STATUS,RP0 ; the buffer is not yet full (we don't yet have movlw SEND_TIME ; enough data for a full packet) then set the movwf TIMER_SEND ; SEND timer so we will wait to see if more bcf STATUS,RP0 ; data comes from the PC goto ret_docheckdatabuffer doNACKED ; If we received a NACK, set the RACK timer bsf STATUS,RP0 ; so we will begin the waiting period for an movlw RACK_TIME ; ACK again (we will also resend the last packet) movwf TIMER_RACK bcf STATUS,RP0 goto ret_doNACKED docheckRACK ; Check to see if we should send a RACK bsf STATUS,RP0 movf TIMER_RACK,W bcf STATUS,RP0 btfss STATUS,Z goto ret_docheckRACK ; No, timer has not yet expired movf uart1_tx_cmd_length,W btfss STATUS,Z goto ret_docheckRACK ; No, command buffer is not empty movlw tx1_cmd_addr ; Get address of cmd buffer in FSR movwf FSR movlw CODE_RACK ; Set TYPE field=RACK bsf STATUS,IRP movwf INDF bcf STATUS,IRP incf FSR,F ; Next byte movf outgoing_packID,W ; Set the packID bsf STATUS,IRP movwf INDF bcf STATUS,IRP movlw 2 ; The length is two bytes (before FLAG,CRC,etc.) movwf uart1_tx_cmd_length bsf STATUS,RP0 movlw RACK_TIME ; Set RACK timer since we are just sending one now movwf TIMER_RACK bcf STATUS,RP0 goto ret_docheckRACK ; Return, the routine which checks to see if cmd buffer ; has data will actually do the sending uart1_tx_cmd_not_empty ; Routine to send contents of command (cmd) buffer over RF bsf STATUS,RP0 movf TIMER_WAITCHANNELCLEAR,W bcf STATUS,RP0 btfss STATUS,Z goto loop ; Can't send yet, we haven't waited long enough since the ; last time the channel was occupied, so go back to loop movlw tx1_cmd_addr ; USER6=Starting address of buffer to send movwf USER6 movf uart1_tx_cmd_length,W movwf USER5 ; USER5=length to send call rf_transmit_string ; Send clrf uart1_tx_cmd_length ; Clear buffer ; The following routine sets up a ; pseudo-random delay between now (we just sent something) ; and the next time when a transmission is allowed. movlw 0x0F ; Place lower nibble of TMR0 andwf TMR0,W ; in TIMER_WAITCHANNELCLEAR btfsc STATUS,Z ; unless the nibble is zero movlw 1 ; in which case put one bsf STATUS,RP0 movwf TIMER_WAITCHANNELCLEAR bcf STATUS,RP0 goto loop ; Return to loop uart1_tx_data_not_empty ; Routine to send contents of data buffer over RF btfsc FLAGS0,NACKED ; If we have been NACKED, go ahead and send goto skip_waitack_check btfsc FLAGS0,WAITACK goto loop ; Can't send more data ; if there is an outstanding unACKED packet ; In other words, the data in the uart1_tx_data buffer ; has already been sent and we are waiting for an ACK skip_waitack_check bsf STATUS,RP0 movf TIMER_SEND,W bcf STATUS,RP0 btfss STATUS,Z goto loop ; Can't send yet, need to wait for more data bsf STATUS,RP0 movf TIMER_WAITCHANNELCLEAR,W bcf STATUS,RP0 btfss STATUS,Z goto loop ; Can't send yet, we haven't waited long enough since the ; last time the channel was occupied, so go back to loop movlw tx1_data_addr ; USER6=Starting address of buffer to send movwf USER6 movf uart1_tx_data_length,W movwf USER5 ; USER5=length to send call rf_transmit_string ; Send bsf FLAGS0,WAITACK ; We should now wait for the ACK bcf FLAGS0,NACKED ; We have not gotten a NACK for this packet ; The following routine sets up a ; pseudo-random delay between now (we just sent something) ; and the next time when a transmission is allowed. movlw 0x0F ; Place lower nibble of TMR0 andwf TMR0,W ; in TIMER_WAITCHANNELCLEAR btfsc STATUS,Z ; unless the nibble is zero movlw 1 ; in which case put one bsf STATUS,RP0 movwf TIMER_WAITCHANNELCLEAR bcf STATUS,RP0 bsf STATUS,RP0 ; Set RACK timer since we just sent something and movlw RACK_TIME ; are waiting for an ACK movwf TIMER_RACK bcf STATUS,RP0 goto loop ; go back to loop ; NOTE: The following two CRC routines are modified versions of a routine written by ; Andrew Warren ( see http://home.netcom.com/~fastfwd/answers.html#PIC00076 ) and taken from ; the posting at www.piclist.com ( http://techref.massmind.org/techref/microchip/crc16-aw.htm ). rx_CRC_w ; Routine to compute CRC of received packets movwf CRCscrp ; Byte to include in CRC computation is in W, move to ; CRCscrp movlw 9 ; Loop once per bit (loop terminates at 1, so 8+1=9) movwf CRCloopvar rxCRCloop decfsz CRCloopvar,F goto continue_rxCRC return ; Finished, current 16 bit CRC value in rx_CRC0 and rx_CRC1 continue_rxCRC rlf CRCscrp,F ; Shift the next bit into the carry RLF rx_CRC1,F RLF rx_CRC0,F SKPC ; X^16 GOTO rxCRCloop MOVLW 00010000B ; X^12 XORWF rx_CRC0,F ; MOVLW 00100001B ; X^5 + 1 XORWF rx_CRC1,F ; GOTO rxCRCloop ; Same thing, but for TX stuff (two CRC routines are needed because ; both an RX and a TX CRC computation can be in progress at once) tx_CRC_w movwf CRCscrp movlw 9 movwf CRCloopvar txCRCloop decfsz CRCloopvar,F goto continue_txCRC return continue_txCRC rlf CRCscrp,F RLF tx_CRC1,F RLF tx_CRC0,F SKPC ; X^16 GOTO txCRCloop MOVLW 00010000B ; X^12 XORWF tx_CRC0,F ; MOVLW 00100001B ; X^5 + 1 XORWF tx_CRC1,F ; GOTO txCRCloop update_status_LEDs ; This routine checks to see which LED timers have ; expired and turns off the corresponding LEDs bsf STATUS,RP0 movf TIMER_TXALED,W bcf STATUS,RP0 btfsc STATUS,Z bcf PORTA,TXALED bsf STATUS,RP0 movf TIMER_TXNLED,W bcf STATUS,RP0 btfsc STATUS,Z bcf PORTA,TXNLED bsf STATUS,RP0 movf TIMER_RXALED,W bcf STATUS,RP0 btfsc STATUS,Z bcf PORTB,RXALED bsf STATUS,RP0 movf TIMER_RXNLED,W bcf STATUS,RP0 btfsc STATUS,Z bcf PORTB,RXNLED return checkrts ; See if PC has turned off RTS (allowing us to send stuff to ; it, and if so, and there are bytes waiting, re-enable ; TX interrupt so they will continue to be sent btfsc PORTC,RTS ; If "hold" is still on, just return return call tx0_howmany ; If no bytes are waiting, just return addlw 0 btfsc STATUS,Z return bsf STATUS,RP0 ; Re-enable TX interrupt bsf PIE1,TXIE bcf STATUS,RP0 return rx0_ser_in ; Get a byte from the RX0 buffer movf FSR,W ; Preserve FSR movwf savFSR0 recwait0 ; Wait for a byte to be available call rx0_howmany addlw 0 btfsc STATUS,Z goto recwait0 wl1 bcf intcon,gie ; Turn off interrupts and make sure they are off btfsc intcon,gie ; This is needed so that ISR won't try to modify pointers goto wl1 ; while we are using them addlw -cts_min ; If the number of bytes in rx0 buffer btfss STATUS,C ; drops below cts_min, then de-assert CTS bcf PORTC,CTS movf rx0_out_ptr,W ; Compute address in buffer addlw rx0_addr movwf FSR movf INDF,w ; Get byte into USER0 movwf USER0 incf rx0_out_ptr,F ; Increment out pointer movf rx0_out_ptr,W ; Roll over out pointer if needed addlw -(rx0_size+1) btfsc STATUS,C clrf rx0_out_ptr movf savFSR0,W ; Restore FSR movwf FSR movf USER0,W ; Byte is in W bsf intcon,gie ; Re-enable interrupts and return return rx1_check_char ; Non-destructive read from RX1 buffer wl2 bcf intcon,gie ; Disable interrupts btfsc intcon,gie goto wl2 movf FSR,W ; Preserve FSR movwf savFSR1 movf rx1_out_ptr,W ; Compute address addlw rx1_addr movwf FSR ; Note that we do not touch out pointer ; (non-desructive read) movf INDF,w ; Place byte in savFSR2 movwf savFSR2 movf savFSR1,W ; Restore FSR movwf FSR movf savFSR2,W ; Byte in W bsf intcon,gie ; Re-enable interrupts and return return rx1_purge_one ; Purge one byte from RX1 buffer wl3 bcf intcon,gie ; Disable interrupts btfsc intcon,gie goto wl3 incf rx1_out_ptr,F ; Increment out pointer movf rx1_out_ptr,W ; Roll over if needed addlw -(rx1_size+1) btfsc STATUS,C clrf rx1_out_ptr bsf intcon,gie ; Re-enable interrupts return rx1_ser_in ; Same thing as rx0_ser_in except this is from the RX1 buffer movf FSR,W ; (RX1 buffer is the buffer of data coming in over RF) movwf savFSR3 recwait1 call rx1_howmany addlw 0 btfsc STATUS,Z goto recwait1 movf rx1_out_ptr,W addlw rx1_addr movwf FSR movf INDF,w movwf USER0 incf rx1_out_ptr,F movf rx1_out_ptr,W addlw -(rx1_size+1) btfsc STATUS,C clrf rx1_out_ptr movf savFSR3,W movwf FSR movf USER0,W return ; These routines return how many bytes are in their corresponding buffers rx0_howmany movf rx0_out_ptr,W subwf rx0_in_ptr,W ; W=in-out btfss STATUS,C ; if out<=in, just return W addlw (rx0_size+1) ; otherwise, return (size+1)-W return rx1_howmany movf rx1_out_ptr,W subwf rx1_in_ptr,W ; W=in-out btfss STATUS,C ; if out<=in, just return W addlw (rx1_size+1) ; otherwise, return (size+1)-W return tx0_howmany movf tx0_out_ptr,W subwf tx0_in_ptr,W ; W=in-out btfss STATUS,C ; if out<=in, just return W addlw (tx0_size+1) ; otherwise, return (size+1)-W return ;TRANSMITW subroutine: ;enter with data in W. ;returns with data in W. transmitw ; Sends byte in W to PC, preserves W movwf tx_data ; Hold W in tx_data movf FSR,W ; Save FSR movwf savFSR4 btfsc PORTC,RTS ; If "hold" is on (RTS is set, PC is saying "wait") then goto tx0_putinbuffer ; place in buffer instead of sending immediately call tx0_howmany ; If there are currently bytes in the TX buffer, addlw 0 ; put this one in there, too btfss STATUS,Z goto tx0_putinbuffer btfss pir1,txif ; If USART is currently sending to PC, put this byte in buffer goto tx0_putinbuffer ; otherwise, send it now gietx bcf intcon,gie ; Disable interrupts btfsc intcon,gie ; making SURE they are disabled! goto gietx movf tx_data,W movwf txreg ; Send byte bsf intcon,gie ; Re-enable interrupts movf savFSR4,W ; Restore FSR movwf FSR movf tx_data,W ; Restore W return tx0_putinbuffer ; Put the byte in the buffer call tx0_howmany addlw -tx0_size ; If the number of bytes is equal to or greater than btfss STATUS,C ; tx0_size, then the buffer is full and we just discard the goto continue ; byte movf savFSR4,W ; Restore FSR movwf FSR return continue ; Buffer is not full movf tx0_in_ptr,W ; Compute address, place in FSR addlw tx0_addr movwf FSR movf tx_data,w bsf STATUS,IRP ; Bank 2 for tx0_addr movwf INDF ; Put in buffer bcf STATUS,IRP incf tx0_in_ptr,F ; Increment "in" pointer movf tx0_in_ptr,W ; Roll it over if needed addlw -(tx0_size+1) btfsc STATUS,C clrf tx0_in_ptr movf savFSR4,W ; Restore FSR movwf FSR btfsc PORTC,RTS ; If "hold" is on, return return ; and do not re-enable TX interrupt bsf STATUS,RP0 ; Otherwise, re-enable TX interrupt and return bsf PIE1,TXIE bcf STATUS,RP0 return rf_transmitw ; Sends a byte (in W) over RF ; NOTE: assumes module already in transmit mode movwf USER3 ; Hold byte in USER3 bcf PORTB,RFTXDATA ; Lower output line (sending start bit) call rf_bitdelay ; Wait one bit length movlw 8 ; We need to loop 8 times (once per bit) movwf USER4 rf_xmitloop ; Set output line according to current bit of USER3 btfsc USER3,0 bsf PORTB,RFTXDATA btfss USER3,0 bcf PORTB,RFTXDATA rrf USER3,F ; Sending LSBit first call rf_bitdelay ; Wait one bit length decfsz USER4,F ; Loop around goto rf_xmitloop bsf PORTB,RFTXDATA ; Set output high (sending stop bit) call rf_bitdelay ; Wait one bit length return ; Routine to send a whole string over RF ; Starting addr in USER6 (must be in bank 3) ; Number of chars in USER5 ; Adds FLAGs(beginning and end), two 0x255 bytes at beginning, does escaping, and ; adds CRC16, itself escaped if needed rf_transmit_string bcf INTCON,INTE ; Disable RX interrupt bcf FLAGS0,RXSTART ; Cancel any RF RX operation in progress bcf PORTA,RXRLED bcf PORTB,RXEN ; Disable receive nop ; Wait before enabling transmitter nop nop nop nop nop nop nop bsf PORTB,RFTXEN ; Enable transmit call delay10ms ; Wait for module to stabilize clrf tx_CRC0 ; Clear TX CRC 16 (in preparation for a new calculation) clrf tx_CRC1 movlw 0xFF ; Send two sync bytes (255's) call rf_transmitw movlw 0xFF call rf_transmitw movlw FLAG ; Send flag call rf_transmitw movf USER6,W ; FSR=Starting address movwf FSR rf_string_xmitloop ; Loop to handle each byte bsf STATUS,IRP ; Transmit buffer is in bank 3 movf INDF,W ; Get byte bcf STATUS,IRP movwf tx_data ; Place in tx_data, preserving W call tx_CRC_w ; Include in CRC computation movf tx_data,w ; Restore W addlw -FLAG ; Is the byte equal to the FLAG value? btfsc STATUS,Z goto tx_escape1 ; If so, escape it addlw FLAG addlw -ESCAPE ; Is the byte equal to the ESCAPE value? btfsc STATUS,Z goto tx_escape1 ; If so, escape it addlw ESCAPE escape_return1 ; Return point for escape routines movwf tx_data ; Preserve W call rf_transmitw ; Send W movf tx_data,W ; Restore W incf FSR,F ; Next address decfsz USER5,F ; Loop around while there are still bytes to send goto rf_string_xmitloop ; Finish computing CRC - need 16 zero bits movlw 0 call tx_CRC_w movlw 0 call tx_CRC_w movf tx_CRC0,W ; Get ready to send tx_CRC0 addlw -FLAG ; Escape it if it equals FLAG btfsc STATUS,Z goto tx_escape2 addlw FLAG addlw -ESCAPE ; Escape it if it equals ESCAPE btfsc STATUS,Z goto tx_escape2 addlw ESCAPE escape_return2 ; Second escape return point call rf_transmitw ; Go ahead and send it movf tx_CRC1,W ; Same process for tx_CRC1 addlw -FLAG btfsc STATUS,Z goto tx_escape3 addlw FLAG addlw -ESCAPE btfsc STATUS,Z goto tx_escape3 addlw ESCAPE escape_return3 call rf_transmitw movlw FLAG ; Send final FLAG call rf_transmitw bcf PORTB,RFTXEN ; Disable the transmitter nop ; Wait a short bit before enabling receiver nop nop nop nop nop nop nop bsf PORTB,RXEN ; Enable receiver call delay10ms ; Wait for module to stabilize bcf INTCON,INTF ; Clear receive interrupt flag nop nop bsf INTCON,INTE ; Re-enable receive interrupt return ; Escape routines for the three escape cases. Each routine sends an ESCAPE byte, ; XORs W by ESCAPEXOR, then returns. tx_escape1 movlw ESCAPE call rf_transmitw movlw ESCAPEXOR xorwf tx_data,W goto escape_return1 tx_escape2 movlw ESCAPE call rf_transmitw movlw ESCAPEXOR xorwf tx_CRC0,W goto escape_return2 tx_escape3 movlw ESCAPE call rf_transmitw movlw ESCAPEXOR xorwf tx_CRC1,W goto escape_return3 rf_bitdelay ; Times the length of one bit over the RF link bsf STATUS,RP0 ; Reset the timer clrf TIMER_RFBITDELAY bcf STATUS,RP0 rf_bitdelay_waitloop ; Wait until the timer reaches 5 bsf STATUS,RP0 ; then return movf TIMER_RFBITDELAY,W bcf STATUS,RP0 addlw -5 btfss STATUS,C goto rf_bitdelay_waitloop return delay10ms ; Ten millisecond delay used to delay during module TX/RX turnover bsf STATUS,RP0 ; Clear timer clrf TIMER_DELAY10MS bcf STATUS,RP0 delay10ms_waitloop ; Wait until timer reaches 240, then return bsf STATUS,RP0 movf TIMER_DELAY10MS,W bcf STATUS,RP0 addlw -240 btfss STATUS,C goto delay10ms_waitloop return ; Delay for 140007 cycles, including call and return ; This comes out to 35 milliseconds at 16 MHz (4 MIPS). ; This delay is executed in the initialization routine to ; allow the MAX233 and other components to stabilize before beginning ; communications. ; Thanks to Scott Dattalo for proposing this type of delay routine. startup_delay movlw low(65535-20000) movwf USER0 movlw high(65535-20000) movwf USER1 sdloop incf USER0,f skpnz incf USER1,f skpz goto sdloop return end