Mike McLaren - K8LH (Westland, MI) says:
Here is a Serial I/O demo for the 16F819 which uses 3X bit rate interrupts as well as Rx and Tx state machines in the ISR to achieve full-duplex 9600 baud serial I/O with 16-byte circular Tx and Rx buffers...
While this demo could easily be considered a novelty for using nearly 50%
of the overall processing time at 9600 baud with an 8-MHz clock, I suspect
it could be quite useful with a faster clock and/or at slower baud
rates...
list p=16F819, b=8, c= 102, n=71, t=on, st=off, f=inhx32
;******************************************************************
;* *
;* Filename: 16F819 Serial 1.asm *
;* Author: Mike McLaren, K8LH (k8lh_at_arrl.net) *
;* Date: 08-Jun-05 (last revision 12-Jun-05) *
;* *
;* Full Duplex Bit-Banged 9600 Baud Serial I/O Demo *
;* (based on a nearly identical 12F683 Demo) *
;* *
;* ·Uses 16F819 INTOSC running at 8-MHz *
;* ·Bit rate error 0.6% plus or minus 1.0% for INTOSC *
;* ·Bit-banged 9600 baud serial I/O *
;* ·Full Duplex (TX and RX simultaneously) *
;* ·Interrupts at approximately 3X bit rate every *
;* 34.5 usecs (every 69 instruction cycles) *
;* ·Circular 16-byte receive ahead buffer *
;* ·Circular 16-byte transmit ahead buffer *
;* ·Inverted TX and RX signals (MAX232A or similar *
;* inverting RS-232 interface required) *
;* ·ISR and Init232, Put232, and Get232 support routines *
;* fit comfortably in the first 192 words of code space *
;* occupying memory from 0004 through 00BF (188 words) *
;* *
;* MPLab: 7.11 (tabs=8) *
;* MPAsm: 4.01 *
;* *
;******************************************************************
#include <p16f819.inc>
errorlevel -302
__config _LVP_OFF & _PWRTE_ON & _WDT_OFF & _INTRC_IO
;
; Serial I/O file register variables & buffers
;
TX_RPTR equ 0x20 ; TX buffer Rd pointer (isr)
TX_WPTR equ 0x21 ; TX buffer Wr pointer (main)
TXCHAR equ 0x22 ; TX character (main, Put232)
TXWORK equ 0x23 ; TX ISR work byte (isr)
;
RX_RPTR equ 0x24 ; RX buffer Rd pointer (main)
RX_WPTR equ 0x25 ; RX buffer Wr pointer (isr)
RXCHAR equ 0x26 ; RX character (main, Get232)
RXWORK equ 0x27 ; RX ISR work byte (isr)
;
TX_SM equ 0x28 ; TX state machine var (isr)
RX_SM equ 0x29 ; RX state machine var (isr)
;
RXBUFF equ 0xA0 ; RX buffer, 16 bytes A0h-AFh
TXBUFF equ 0xB0 ; TX buffer, 16 bytes B0h-BFh
;
; 16F819 serial port pin defines
;
#define RXPIN PORTB,2 ; RS-232 RX (RB2)
#define TXPIN PORTB,5 ; RS-232 TX (RB5)
;
; file locations used by ISR to save and restore context
;
W_ISR equ 0x70 ; ISR 'W'
S_ISR equ 0x71 ; ISR 'STATUS'
P_ISR equ 0x72 ; ISR 'PCLATH'
F_ISR equ 0x73 ; ISR 'FSR'
;
; other vars
;
FSRTMP equ 0x74 ; Put232 & Get232 (main)
PTRL equ 0x75 ; PutStr routine (main)
PTRH equ 0x76 ; PutStr routine (main)
;******************************************************************
;
; _Title macro - home cursor, clear screen, print a string
;
_Title macro str ;
local String, Print
movlw low String ;
movwf PTRL ;
movlw high String ;
movwf PTRH ;
goto Print ;
String dt h'1B',"[2J" ; <esc>[2J sequence
dt str,0
Print call PutString ; print string
endm
;******************************************************************
;
; Hardware notes
;
; <1> INTOSC 8-MHz, 500-nsec instruction cycle time
; <2> RB2 (pin 08) > RXPIN, 'bit banged' serial input
; <3> RB5 (pin 11) > TXPIN, 'bit banged' serial output
; <4> RS-232 signals inverted (use MAX232A or similar)
; <5>
; <6>
; <7>
;
;
; This program simply prints a text string to Hyperterminal
; and echos characters coming from Hyperterminal...
;
; Setup Hyperterminal for 9600, 8, 1, none... Use a MAX232 or
; similar level shifting circuit (I use a pair of 2N7000s) for
; connection between the 16F819 and the PC...
;
;******************************************************************
;* *
;* *
;* *
;* *
;* *
;******************************************************************
org 0x0000
RESET clrf STATUS ; |B0
goto MAIN ; |B0
;******************************************************************
;* *
;* Interrupt Service Routine for a Full Duplex Bit-Banged *
;* 9600 Baud Serial I/O with 16 byte circular receive and *
;* transmit buffers... *
;* *
;* Interrupts are generated at approximately 3 times the *
;* bit rate for 9600 baud at 34.5-usec intervals or every *
;* 69 instruction cycles. *
;* *
;* The transmit and receive processes are executed in the *
;* correct sequence each interrupt cycle by using a state *
;* machine variable and jump table for both RX and TX. *
;* *
;* After detecting a start bit, the receive bit stream is *
;* sampled every third interrupt cycle in the approximate *
;* middle third of each bit (between 33% and 66%). *
;* *
;* The 16 byte circular TXBUFF is located at B0..BF in RAM *
;* and will buffer 15 bytes. The "unload buffer" process *
;* is performed in the ISR after sending a character and *
;* the "load buffer" process is performed outside the ISR *
;* in the Put232 subroutine. *
;* *
;* The 16 byte circular RXBUFF is located at A0..AF in RAM *
;* and will buffer 15 bytes. The "load buffer" process is *
;* performed in the ISR after receiving a character and *
;* the "unload buffer" process is performed outside of the *
;* ISR in the Get232 subroutine. *
;* *
;* Of the 69 instruction cycles between interrupts the ISR *
;* uses between 34 and 35 cycles average each interrupt or *
;* approximately 49.3% to 50.7% of the overall processing *
;* time available. The TX code uses almost the same number *
;* of instruction cycles when transmitting or idle. The RX *
;* code uses 1 more instruction cycle average per interrupt *
;* when receiving than it does when idle. *
;* *
;* 34.0 cycles avg (49.3% mcu overhead) RX idle *
;* 35.0 cycles avg (50.7% mcu overhead) RX in progress *
;* *
;******************************************************************
org 0x0004
;
; save main program context
;
; 09 cycles every interrupt cycle
;
ISR movwf W_ISR ; save W-reg |B?
swapf STATUS,W ; doesn't change STATUS bits |B?
movwf S_ISR ; save STATUS reg |B?
clrf STATUS ; bank 0 |B0
movf PCLATH,W ; get PCLATH |B0
movwf P_ISR ; save PCLATH |B0
bcf PIR1,TMR2IF ; clear TMR2 interrupt flag |B0
;
; enter the TX state machine
;
; 06 cycles, including TX SM jump, every interrupt cycle
;
clrf PCLATH ; |B0
movf TX_SM,W ; get TX state machine |B0
addwf PCL,f ; off we go |B0
;
; the TX state machine table (cycle times include ISR entry,
; TX state machine jump, and RX state machine jump)
;
; TX idle, 24.0 of 69.0 instructions per interrupt (34.8%)
; TX proc, 24.0 of 69.0 instructions per interrupt (34.8%)
;
goto TX_CHK ; TX idle 24 cycles |B0
goto TX_0 ; start bit 24 cycles |B0
goto TX_BUF ; 31 cycles |B0
goto TX_NXT ; 21 cycles |B0
goto TX_BIT ; bit 0 28 cycles |B0
goto TX_NXT ; 21 cycles |B0
goto TX_NXT ; 21 cycles |B0
goto TX_BIT ; bit 1 28 cycles |B0
goto TX_NXT ; 21 cycles |B0
goto TX_NXT ; 21 cycles |B0
goto TX_BIT ; bit 2 28 cycles |B0
goto TX_NXT ; 21 cycles |B0
goto TX_NXT ; 21 cycles |B0
goto TX_BIT ; bit 3 28 cycles |B0
goto TX_NXT ; 21 cycles |B0
goto TX_NXT ; 21 cycles |B0
goto TX_BIT ; bit 4 28 cycles |B0
goto TX_NXT ; 21 cycles |B0
goto TX_NXT ; 21 cycles |B0
goto TX_BIT ; bit 5 28 cycles |B0
goto TX_NXT ; 21 cycles |B0
goto TX_NXT ; 21 cycles |B0
goto TX_BIT ; bit 6 28 cycles |B0
goto TX_NXT ; 21 cycles |B0
goto TX_NXT ; 21 cycles |B0
goto TX_BIT ; bit 7 28 cycles |B0
goto TX_NXT ; 21 cycles |B0
goto TX_NXT ; 21 cycles |B0
goto TX_PTR ; stop bit 28 cycles |B0
goto TX_NXT ; 21 cycles |B0
goto TX_RES ; reset 23 cycles |B0
;
; perform TX "unload buffer" function and send the stop bit
;
; 13 cycles, including RX SM jump, 01 in 30 TX cycles
;
TX_PTR incf TX_RPTR,W ; W = TX_RPTR + 1 |B0
andlw h'0F' ; keep in range 00..0F |B0
addlw TXBUFF ; make it range B0..BF |B0
movwf TX_RPTR ; update TX_RPTR |B0
bsf TXPIN ; send stop bit |B0
goto TX_NXT ; increment TX state |B0
;
; reset TX state machine during final third of the stop bit
; for 138.5 usecs (1 and 1/3 bit times) between TX characters
;
TX_RES clrf TX_SM ; reset TX state machine |B0
goto TX_XIT ; |B0
;
; copy character from RXBUFF to TXWORK after sending start bit
;
; 17 cycles, including RX SM jump, 01 in 30 TX cycles
;
TX_BUF movf FSR,W ; get FSR |B0
movwf F_ISR ; save it |B0
movf TX_RPTR,W ; get TX buffer Rd ptr [B0..BF] |B0
movwf FSR ; setup indirect address |B0
movf INDF,W ; get data |B0
movwf TXWORK ; put it in a work register |B0
movf F_ISR,W ; |B0
movwf FSR ; restore FSR |B0
goto TX_NXT ; increment TX state |B0
;
; transmit bit
;
; 13 cycles, including RX SM jump, 08 in 30 TX cycles
;
TX_BIT rrf TXWORK,f ; |B0
btfsc STATUS,C ; is it a '1'? |B0
TX_1 bsf TXPIN ; yes, send it |B0
btfss STATUS,C ; is it a '0'? |B0
TX_0 bcf TXPIN ; yes, send it |B0
goto TX_NXT ; increment TX state |B0
;
; if there's a transmit character buffered, bump the TX_SM var
; to initiate the transmit process, else exit leaving TX_SM=00
;
; 09 cycles, including RX SM jump, every interrupt cycle until
; a TX operation is initiated
;
TX_CHK movf TX_RPTR,W ; get TX buffer Rd ptr [B0..BF] |B0
xorwf TX_WPTR,W ; xor TX buffer Wr ptr [B0..BF] |B0
btfss STATUS,Z ; skip empty (TX_RPTR=TX_WPTR) |B0
TX_NXT incf TX_SM,f ; inc TX state machine |B0
;******************************************************************
;
; enter the RX state machine (cycle times include ISR_XIT)
;
; RX idle, 10.0 of 69.0 instructions per interrupt (14.5%)
; RX proc, 11.0 of 69.0 instructions per interrupt (15.9%)
;
TX_XIT movf RX_SM,W ; get RX state machine |B0
addwf PCL,f ; off we go |B0
;
; the RX state machine table
;
goto RX_CHK ; RX idle 10 cycles |B0
goto RX_NXT ; start bit 09 cycles |B0
goto RX_NXT ; 09 cycles |B0
goto RX_NXT ; 09 cycles |B0
goto RX_BIT ; bit 0 14 cycles |B0
goto RX_NXT ; 09 cycles |B0
goto RX_NXT ; 09 cycles |B0
goto RX_BIT ; bit 1 14 cycles |B0
goto RX_NXT ; 09 cycles |B0
goto RX_NXT ; 09 cycles |B0
goto RX_BIT ; bit 2 14 cycles |B0
goto RX_NXT ; 09 cycles |B0
goto RX_NXT ; 09 cycles |B0
goto RX_BIT ; bit 3 14 cycles |B0
goto RX_NXT ; 09 cycles |B0
goto RX_NXT ; 09 cycles |B0
goto RX_BIT ; bit 4 14 cycles |B0
goto RX_NXT ; 09 cycles |B0
goto RX_NXT ; 09 cycles |B0
goto RX_BIT ; bit 5 14 cycles |B0
goto RX_NXT ; 09 cycles |B0
goto RX_NXT ; 09 cycles |B0
goto RX_BIT ; bit 6 14 cycles |B0
goto RX_NXT ; 09 cycles |B0
goto RX_NXT ; 09 cycles |B0
goto RX_BIT ; bit 7 14 cycles |B0
goto RX_BUF ; buffer 19 cycles |B0
goto RX_NXT ; 09 cycles |B0
goto RX_NXT ; stop bit 09 cycles |B0
goto RX_PTR ; ptr & reset 18 cycles |B0
;
; copy completed character in RXWORK byte to RXBUFF
;
; 19 cycles, including ISR_XIT, 01 in 30 RX cycles
;
RX_BUF movf FSR,W ; |B0
movwf F_ISR ; save FSR |B0
movf RX_WPTR,W ; RX buffer Wr pointer [A0..AF] |B0
movwf FSR ; setup indirect address |B0
movf RXWORK,W ; get completed work byte |B0
movwf INDF ; place it in the RX buffer |B0
movf F_ISR,W ; |B0
movwf FSR ; restore FSR |B0
goto RX_NXT ; increment RX state |B0
;
; receive bit
;
; 14 cycles, including ISR_XIT, 08 in 30 RX cycles
;
RX_BIT btfsc RXPIN ; is it a 0? |B0
bsf STATUS,C ; no, make it a 1 |B0
rrf RXWORK,f ; shift into our work byte |B0
goto RX_NXT ; increment RX state |B0
;
; perform RX buffer 'load' function after receiving byte
;
; 18 cycles, including ISR_XIT, 01 in 30 RX cycles
;
RX_PTR incf RX_WPTR,W ; |B0
andlw RXBUFF+h'0F' ; |B0
xorwf RX_RPTR,W ; buffer full (WPTR+1=RPTR)? |B0
bz RX_RES ; yes, branch |B0
xorwf RX_RPTR,W ; no, restore WPTR+1 value |B0
movwf RX_WPTR ; update WPTR |B0
;
; reset RX state machine after receiving a complete character
; and during the last 3rd (66%-100%) of the stop bit to allow
; setup time for detecting the next start bit
;
RX_RES clrf RX_SM ; reset RX state machine |B0
goto ISR_XIT ; |B0
;
; test for start bit (low)
;
; 10 cycles, including ISR_XIT, each cycle until RX initiated
;
RX_CHK btfss RXPIN ; start bit? |B0
RX_NXT incf RX_SM,f ; inc RX state machine |B0
;
; 08 cycles each interrupt cycle
;
ISR_XIT movf P_ISR,W ; |B0
movwf PCLATH ; restore PCLATH |B0
swapf S_ISR,W ; |B0
movwf STATUS ; restore STATUS |B?
swapf W_ISR,f ; don't screw up STATUS |B?
swapf W_ISR,W ; restore W-reg |B?
retfie ; return from interrupt |B?
;******************************************************************
;* *
;* Companion Put232 and Get232 subroutines *
;* *
;******************************************************************
;
; Put232 - enter with character to be sent in W
; - performs TXBUFF 'load buffer' operation
;
Put232 movwf TXCHAR ; save character |B0
Pwait incf TX_WPTR,W ; W = WPTR + 1 |B0
andlw h'0F' ; keep it in range 00..0F |B0
addlw TXBUFF ; make it in range B0..BF |B0
movwf RXCHAR ; save here temporarily |B0
xorwf TX_RPTR,W ; buffer full (WPTR+1=RPTR)? |B0
bz Pwait ; yes, branch, wait |B0
movf FSR,W ; get FSR |B0
movwf FSRTMP ; save it |B0
movf TX_WPTR,W ; get TX buffer Wr ptr (B0..BF) |B0
movwf FSR ; setup indirect address |B0
movf TXCHAR,W ; get character |B0
movwf INDF ; place it in TX buffer |B0
movf FSRTMP,W ; |B0
movwf FSR ; restore FSR |B0
movf RXCHAR,W ; get saved TX_WPTR+1 value |B0
movwf TX_WPTR ; update TX_WPTR |B0
movf TXCHAR,W ; restore W entry data |B0
return ; |B0
;
; Get232 - exit with received character in W & RXCHAR var
; - performs RXBUFF 'unload buffer' operation
;
Get232 movf RX_RPTR,W ; |B0
xorwf RX_WPTR,W ; RPTR = WPTR (buff empty)? |B0
bz Get232 ; yes, loop, wait for character |B0
movf FSR,W ; |B0
movwf FSRTMP ; save FSR |B0
movf RX_RPTR,W ; |B0
movwf FSR ; setup indirect address |B0
movf INDF,W ; get RXBUFF[RPTR] character |B0
movwf RXCHAR ; save it for later |B0
movf FSRTMP,W ; |B0
movwf FSR ; restore FSR |B0
incf RX_RPTR,W ; W = RX_RPTR+1 |B0
andlw RXBUFF+h'0F' ; keep it in range of A0..AF |B0
movwf RX_RPTR ; update RX_RPTR |B0
movf RXCHAR,W ; get receive character |B0
return ; |B0
;******************************************************************
;* *
;* Companion Init232 subroutine *
;* *
;******************************************************************
;
; initialize RS-232 variables before turning on interrupts
;
Init232 clrf RX_SM ; clr RX state machine var |B0
clrf TX_SM ; clr TX state machine var |B0
movlw RXBUFF ; RX circular buffer address |B0
movwf RX_RPTR ; set RX buffer Rd pointer |B0
movwf RX_WPTR ; set RX buffer Wr pointer |B0
movlw TXBUFF ; TX circular buffer address |B0
movwf TX_RPTR ; set TX buffer Rd pointer |B0
movwf TX_WPTR ; set TX buffer Wr pointer |B0
;
; put TXPIN latch in the stop condition and setup TRIS data
; direction for TXPIN output and RXPIN input (select bank 1
; to access the TRIS register instead of the PORT register)
;
bsf TXPIN ; put TXPIN latch in stop state |B0
bsf STATUS,RP0 ; select Bank 1 for TRIS access |B1
bcf TXPIN ; set TXPIN as output |B1
bsf RXPIN ; set RXPIN as input |B1
;
; configure TIMER2 for 34.5-usec interrupts (8-MHz clock)
;
; note: INTCON is 00000000 after any reset
; T2CON is 00000000 after any reset (pre=1, post=1)
; PIE1 is 000-0000 after any reset
; PIR1 is 000-0000 after any reset
;
bsf PIE1,TMR2IE ; enable TMR2 interrupts |B1
movlw d'69'-1 ; number of 500-nsec ticks |B1
movwf PR2 ; 34.5-usec interrupts |B1
bcf STATUS,RP0 ; select Bank 0 |B0
bsf INTCON,GIE ; enable global interrupts |B0
bsf INTCON,PEIE ; enable peripheral interrupts |B0
bsf T2CON,TMR2ON ; start TMR2 |B0
return ; |B0
;******************************************************************
;
; Print String - enter with string address in PTRL & PTRH
;
PutString
call GetTable ; get a table character |B0
andlw b'11111111' ; |B0
btfsc STATUS,Z ; last character? |B0
return ; yes, return |B0
call Put232 ; output char |B0
incfsz PTRL,F ; increment pointer |B0
goto PutString ; |B0
incf PTRH,F ; |B0
goto PutString ; |B0
;
GetTable
movf PTRH,W ; |B0
movwf PCLATH ; |B0
movf PTRL,W ; |B0
movwf PCL ; |B0
;******************************************************************
;* *
;* *
;* *
;* *
;* *
;******************************************************************
MAIN clrf CCP1CON ; capture/compare module off |B0
bsf STATUS,RP0 ; bank 1 |B1
movlw h'06' ; |B1
movwf ADCON1 ; turn ADC module off |B1
movlw b'11111111' ; |B1
movwf TRISA ; set Port A all inputs |B1
clrf TRISB ; set Port B all outputs |B1
;
; setup INTOSC for 8-MHz and wait for oscillator to stabilize
;
movlw b'01110000' ; |B1
movwf OSCCON ; 8-mhz INTOSC system clock |B1
STABLE btfss OSCCON,IOFS ; oscillator stable? |B1
goto STABLE ; no, branch |B1
bcf STATUS,RP0 ; bank 0 |B0
;
; Initialize RS-232 Serial I/O
;
call Init232 ; Initialize RS-232 Serial I/O |B0
;
; _Title macro - home cursor, clear screen, and print a string
;
_Title "K8LH 16F819 Full Duplex Serial I/O Demo\r\n\n"
;
; Echo characters coming from Hyperterminal...
;
TEST call Get232 ; receive character |B0
call Put232 ; echo character |B0
goto TEST ; loop forever |B0
;
;******************************************************************
end