PIC Specific RS232 routine

by Bob Ammerman

This progam allows a 16F84A at 20MHz to simultaneously receive 8 full 9600 communication links. The transmit side of the UART is _not_ implemented.

Note that the code has been tested under simulation, but not in actual hardware.

        list    p=16F84a,f=INHX32
        #include <p16F84a.inc>

;=======================================================================================
; Copyright (c) 2000 by Robert V Ammerman (rammerman@adelphia.net as of Jun 2003)
;
; This code may be used for any legal purpose, commerical or not. I only ask that you
; let me know if you use it and that you leave this copyright notice unchanged in 
; your source code.
;
; Also, you understand that I have no liability if this code doesn't work as expected.
;
; Software UAR(no T) code developed by and used with the permission of:
; 
;       Robert V Ammerman
;       RAm Systems
;       (contract development of high performance, high function, low-level software)
;
;======================================================================================
;
; This code is a sample UAR(no T) that receives 8 channels at 9600 baud on a 20Mhz 16F84A
; (or other midrange PIC).
;
; This code has been tested until simulation only, not on real hardware. Use at your own
; risk!
;
;
; The program works by handling timer interrupts at 3x the nominal bit rate and running a 
; state machine for each channel to acquire the data. 
;
; The interrupts are at the rate of:
;
;       20000000/4/174 == 28735.63 Hz.
;
; The 'perfect' rate would be 9600*3 == 28800 Hz.
;
; The bit rate error is thus about 0.22 percent.
;
; The first trick behind this code is the way the state machines are run: instead of
; handling each channel one at a time the program uses 'vertical arithmetic' to 
; process all eight channels together.
;
; The second trick is the way that the program works thru processing the eight
; input bits on the channels, accumulating them into the 'receiver shift register'
; variables, and determining when a byte has been completely received. This is done 
; using only 3 instructions per channel.
;
; Using these two tricks results in code that uses only 76 instructions per interrupt, 
; including context save and restore, but not interrupt latency. Since interrupts 
; are generated every 174 instructions this leaves about 54 percent of the CPU available 
; for 'task level' code.
;
; One important thing to note: since this code does _not_ double buffer the receiver, 
; task level code must process a received byte within 4/3 of a bit time (approximately 
; 390 task level instructions), otherwise the overrun flag will be set for that channel.

    CBLOCK      0x0C

; The state visible to task-level code for the UART receivers

    rsr_chan:8    ; Receiver shift registers for channels 0..7
        rcv_ready         ; Bitmap of channels with bytes ready
        frame_err         ; Bitmap of channels with framing error
        overrun_err       ; Bitmap of channels with overrun error

; The current inputs from the 8 serial ports

        curr_input

; The following bitmapped variable is used to track when a UART shift 
; register is full and thus we have to check for the stop bit at the 
; next sample time.
 
        full 
 
; The following bitmapped variables maintain the state data for each of the 
; channels. Note that each channel will always have exactly 1 one bit set
; in these variables to indicate what state that channel is in.

        wait_start              ; Channel is waiting for start bit
        confirm_start           ; Channel is verifying that it is really in start bit
        ignore1                 ; Channel is waiting for sample time
        ignore2                 ; Channel is waiting for sample time
        sample                  ; Channel is sampling the input state
        stop_exp                ; Channel is expecting the stop bit

; Temps used in computing the new state

        work

        new_wait_start
        new_ignore1

; Save area for interrupts

        int_w_save
        int_status_save

    ENDC

;==============================================================================
; Main entry point

    goto        start

;==============================================================================
; Interrupt handler

    org         0x04

; context save

    movwf       int_w_save       
    swapf       STATUS,W
    movwf       int_status_save

; Clear the timer interrupt and adjust the timer to correct its period

    bcf         INTCON,T0IF

; We want a period of 174 instructions, which is 1/3 of a bit time
; So we have to add 256-174 to the counter. 
; But the counter delays updating by 2 cycles.
; So we really add 256-174+2 to get the
; desired period.

    movlw       D'256'-D'174'+D'2'
    addwf       TMR0,F

; Get the input and prepare to process it

    comf        PORTB,W         ; Get current input state 
    movwf       curr_input      ; Save a copy
    movwf       work
    
; Process all the inputs.

; Note that we only shift in bits for channels that are in the 'sample'
; state.
 

rcv_chan macro n

; The next instruction does two things:
;       1: It puts 'receive shift register full' status, if any, 
;       for the previous channel into the high bit of 'work'.
;       2: It puts the input data bit for the current channel into
;               carry.
 
    rrf         work,F 

; See if this channel is ready to sample

    btfsc       sample,n      ; Do we need a new bit?

; If this channel is ready to sample, the following instruction will be 
; executed to perform two functions:
;       1: It puts the input data bit into the receiver shift register.
;       2: It puts the 'receive shift register full' status into carry.

    rrf         rsr_chan+n,F    ; Yes, move it in
 endm

        rcv_chan 0
        rcv_chan 1
        rcv_chan 2
        rcv_chan 3
        rcv_chan 4
        rcv_chan 5
        rcv_chan 6
        rcv_chan 7

; At this point 'work' contains the 'receive shift register full' bits for
; channels 0..6 (in bits 1..7) and carry has the bit for channel 7.

        rrf     work,W  ; Bring in channel 7's 
                        ;'receive shift register full' bit

; Note that the bits in WREG that correspond to channels that were not
; sampled on this cycle are garbage. Zero them out and then turn on the 
; full bits for the sampled channels that we just filled.

    andwf       sample,W        ; (can only include chans with new bits)
    iorwf       full,F          ; Turn on full bits for sampled channels

; compute the new state variables based on the old state and the 
; 'curr_input' and 'full' vectors

; Note: a stop bit is a 1, a start bit is a 0

; Here are the transitions of the state machine:
; 
;       stop_exp                -> wait_start
;                                   *set rcv_ready bits
;                                   *set frame_err bits

;       wait_start              -> INPUT==1 -> wait_start
;                                  INPUT==0 -> confirm_start

;       confirm_start           -> INPUT==1 -> wait_start 
;                                  INPUT==0 -> ignore1

;       ignore1                 -> ignore2

;       ignore2                 -> FULL==0 -> sample
;                                  FULL==1 -> stop_exp
;                                       *clear full bits

;       sample                  -> ignore1
;                                       *set overrun error bits
;                                       *store data bits
;                                       *set full bits

; Turning this around into math to compute the new values of the state
; bit vectors and 'full', 'rcv_ready', 'overrun_err' and 'frame_err' vectors 
; based on the old state bit vectors and the 'input' and 'full' vectors
; we get: 
;
;   NOTE: These comments are written assuming all the assignments happen
;       simultaneously:
;
;       confirm_start   <- (wait_start & ~input) 
;       ignore1         <- (confirm_start & ~input) | sample
;       ignore2         <- ignore1
;       sample          <- (ignore2 & ~full)
;       stop_exp        <- (ignore2 & full)
;       full            <- full & ~ignore2
;       wait_start      <- stop_exp 
;                        | (wait_start & input)
;                        | (confirm_start & input)
;       rcv_ready       <- rcv_ready | stop_exp
;       frame_err       <- frame_err | (stop_exp & ~input)
;       overrun_err     <- overrun_err | (sample & rcv_ready)

; new_wait_start = ((wait_start | confirm_start) & input) | stop_exp

        movf    wait_start,W
        iorwf   confirm_start,W
        andwf   curr_input,W
        iorwf   stop_exp,W
        movwf   new_wait_start

; new_ignore1 = (confirm_start & ~input) | sample

        comf    curr_input,W
        andwf   confirm_start,W
        iorwf   sample,W
        movwf   new_ignore1

; overrun_err |= sample & rcv_ready

        movf    sample,W
        andwf   rcv_ready,W
        iorwf   overrun_err,F

; sample = ignore2 & ~full

        comf    full,W
        andwf   ignore2,W
        movwf   sample

; rcv_ready |= stop_exp

        movf    stop_exp,W
        iorwf   rcv_ready,F

; frame_err |= stop_exp & ~input

        comf    curr_input,W
        andwf   stop_exp,W
        iorwf   frame_err,F

; stop_exp = ignore2 & full

        movf    ignore2,W
        andwf   full,W
        movwf   stop_exp

; full = full & ~ignore2
 
        comf    ignore2,W
        andwf   full,F

; ignore2 = ignore1

        movf    ignore1,W
        movwf   ignore2

; confirm_start = wait_start & ~input

        comf    curr_input,W
        andwf   wait_start,W
        movwf   confirm_start

; wait_start = new_wait_start

        movf    new_wait_start,W
        movwf   wait_start

; ignore1 = new_ignore1
        movf    new_ignore1,W
        movwf   ignore1

; Context restore

        swapf   int_status_save,W
        movwf   STATUS
        swapf   int_w_save,F
        swapf   int_w_save,W

        retfie

;===============================================================================
; Mainline code

start:

; initialize all the UART status values

        movlw   0x80
        movwf   rsr_chan+0
        movwf   rsr_chan+1
        movwf   rsr_chan+2
        movwf   rsr_chan+3
        movwf   rsr_chan+4
        movwf   rsr_chan+5
        movwf   rsr_chan+6
        movwf   rsr_chan+7

        clrf    rcv_ready
        clrf    frame_err

        clrf    full

        movlw   0xFF
        movwf   wait_start

        clrf    confirm_start
        clrf    ignore1
        clrf    ignore2
        clrf    sample
        clrf    stop_exp

        bsf     STATUS,RP0
        bcf     OPTION_REG-0x80,T0CS
        bcf     STATUS,RP0

        bsf     INTCON,T0IE
        bsf     INTCON,GIE

forever:
    btfsc   rcv_ready,0
    call    rcv_0
    btfsc   rcv_ready,1
    call    rcv_1
    btfsc   rcv_ready,2
    call    rcv_2
    btfsc   rcv_ready,3
    call    rcv_3
    btfsc   rcv_ready,4
    call    rcv_4
    btfsc   rcv_ready,5
    call    rcv_5
    btfsc   rcv_ready,6
    call    rcv_6
    btfsc   rcv_ready,7
    call    rcv_7
    goto    forever

rcvchan macro   chan

rcv_#v(chan):
    btfsc   frame_err,chan
    goto    ferr_#v(chan)
    btfsc   overrun_err,chan
    goto    oerr_#v(chan)
    movf    rsr_chan+chan,W
     ; do something with the byte in W
    bcf     rcv_ready,chan
    movlw   0x80
    movwf   rsr_chan+chan
    return
ferr_#v(chan):
    bcf     frame_err,chan
    bcf     rcv_ready,chan
     ; deal with framing error condition
    movlw   0x80
    movwf   rsr_chan+chan
    return
oerr_#v(chan)
    bcf     overrun_err,chan
    bcf     rcv_ready,chan
     ; deal with overrun condition
    movlw   0x80
    movwf   rsr_chan+chan
    return
 endm

    rcvchan 0
    rcvchan 1
    rcvchan 2
    rcvchan 3
    rcvchan 4
    rcvchan 5
    rcvchan 6
    rcvchan 7

        end


Comments:

See: