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: