;**********************************************************************
;    Filename:        swmod.asm                                       *
;    Date:            2004-01-31                                      *
;    File Version:    1.0                                             *
;                                                                     *
;    Author:          Per Zander                                      *
;**********************************************************************
;    Files required:  p16c505.inc                                     *
;********************************************************************** 
;    Description:                                                     
; 
;    General:
;
;    This is a program for model railroad switch control via a 9600 baud
;    serial link.
;
;    Serial protocol:
;
;    9600 baud, 8 bits, no parity, no handshaking. Data is inverted, so that
;    an rs232 signal can be used directly (after appropriate clamping).
;
;    Switch control packet:
;
;    A switch control packet consists of two bytes.
;    First byte:
;
;      7   6   5   4   3   2   1   0
;     _______________________________
;    | 0 |    Device address         |
;    |___|___________________________|
;
;    Second byte:
;
;      7   6   5   4   3   2   1   0
;     _______________________________
;    | 1 | 0 | c | 0 | 0 |Port number|
;    |___|___|___|___|___|___________|
;
;    A correct packet addressed to the device, and with the c bit set, activates 
;    the selected port for a predetermined amount of time. The default pulse 
;    width is 0.3 s, but can be configured individually for each port.
;    If the c bit is cleared, the selected port is deactivated immediately. 
;
;    Pulse width configuration packet:
;
;    A pulse width configuration packet consists of three bytes. 
;    First byte:
;
;      7   6   5   4   3   2   1   0
;     _______________________________
;    | 0 |    Device address         |
;    |___|___________________________|
;
;    Second byte:
;
;      7   6   5   4   3   2   1   0
;     _______________________________
;    | 1 | 1 | X | 0 | 0 |Port number|
;    |___|___|___|___|___|___________|
;
;    Third byte:
;
;      7   6   5   4   3   2   1   0
;     _______________________________
;    | 1 |    Pulse width            |
;    |___|___________________________|
;
;    The pulse width is selected in units of 20 ms. 
;
;**********************************************************************

;*---------- Device address definition. ----------*

my_addr EQU     0x00                  ; Change for each unit. MSB should be 0.
hi_addr EQU     0x80                  ; Should be 0x80. 

;*---------- Device select. ----------*

	  list      p=16c505            ; List directive to define processor
	  #include <p16c505.inc>        ; Processor specific variable definitions

;*---------- Configuration specification. ----------*

	  __CONFIG   _CP_OFF & _WDT_OFF & _MCLRE_OFF & _XT_OSC

;*---------- Port definitions. ----------*

din     EQU     0x05               ; Data in        on RC5     
sw0l    EQU     0x00               ; Switch 0 left  on RB0                 
sw0r    EQU     0x01               ; Switch 0 right on RB1
sw1l    EQU     0x02               ; Switch 1 left  on RB2
sw1r    EQU     0x00               ; Switch 1 right on RC0 
sw2l    EQU     0x01               ; Switch 2 left  on RC1
sw2r    EQU     0x02               ; Switch 2 right on RC2
sw3l    EQU     0x04               ; Switch 3 left  on RC4
sw3r    EQU     0x03               ; Switch 3 right on RC3

;*---------- Variable definitions. ----------*

; For receiver:

ser_st  EQU     0x08               ; Serial state pointer.
rec_d   EQU     0x09               ; Receive data shift register.
new_d   EQU     0x0A               ; Received_data.

; For command handler:

cmd_st  EQU     0x0B

; For switch output:

cnt_st  EQU     0x0C 
wrap_c  EQU     0x0D     
sw0l_t  EQU     0x10               ; Timing values
sw0r_t  EQU     0x11
sw1l_t  EQU     0x12
sw1r_t  EQU     0x13
sw2l_t  EQU     0x14
sw2r_t  EQU     0x15
sw3l_t  EQU     0x16
sw3r_t  EQU     0x17
sw0l_c  EQU     0x18               ; Counter values.
sw0r_c  EQU     0x19
sw1l_c  EQU     0x1A
sw1r_c  EQU     0x1B
sw2l_c  EQU     0x1C
sw2r_c  EQU     0x1D
sw3l_c  EQU     0x1E
sw3r_c  EQU     0x1F

; General:

cmd_fl  EQU     0x0F

;*---------- Program flag definitions. ----------*

dav     EQU     0x00               ; Data available flag from receiver.
dend    EQU     0x01               ; Serial receiver end of byte flag. 

;**********************************************************************
;* The program starts here after reset.
;**********************************************************************

        ORG     0x3FF             ; Processor reset vector.

        movlw   0x38              ; Osc. cal. value.

        ORG     0x000             ; Coding begins here.

startofprog

        movwf   OSCCAL            ; Update register with factory cal value. 
        clrf    FSR               ; Ensure FSR register points to Bank0. 
        goto    init_code         ; Jump over a few words to enable
                                  ; reprogramming.
        ORG     0x006                                 

;*****************************************************
;*---------- Serial receive state machine. ----------*
;*****************************************************

;*---------- Jump to current state. ----------*

serial_proc

        movf   ser_st, W          ; t2 
        movwf  PCL                ; t3, t4

;*---------- Idle state. Must see stop bit to leave it. ----------*

ser_idle

        btfss   PORTC, din        ; t5
        movlw   wait_start       
        movwf   ser_st
        nop
        retlw   0                 ;t9, t10

;*---------- Wait for start bit. ----------*

wait_start

        btfsc   PORTC, din
        movlw   sample_data
        movwf   ser_st
        bcf     cmd_fl, dend          ; Clear data end flag.
        retlw   0 

;*---------- Sample data in. ----------*

sample_data

        btfsc   PORTC, din
        bcf     rec_d, 7         ; Clear data bit if input is logic 0.
        movlw   get_end_bit
        movwf   ser_st
        retlw   0

;*---------- Wait for next bit. ----------*

get_end_bit

        btfss   rec_d, 0
        bsf     cmd_fl, dend    
        movlw   shift_data
        movwf   ser_st
        retlw   0 

;*---------- Shift data in. ----------*

shift_data

        rrf     rec_d, F        ; Shift received data. 
        bsf     rec_d, 7        ; Preset current bit to 7.
        movlw   test_bitnr
        movwf   ser_st
        retlw   0

;*---------- Check if it is the last bit. ----------*

test_bitnr

        movlw   sample_data
        btfsc   cmd_fl, dend
        movlw   sample_last      ; Last bit if start bit is in rec_bit.
        movwf   ser_st
        retlw   0

;*---------- Sample last bit. ----------*     

sample_last
  
        btfsc   PORTC, din
        bcf     rec_d, 7         ; Clear data bit if input is 0.
        movlw   wait_end

ser_return

        movwf   ser_st
        retlw   0

;*---------- Wait before sampling stop bit. ----------*

wait_end

        movlw   set_new_data
        goto    ser_return

;*---------- Move new data. ----------*

set_new_data

        movf    rec_d, W
        movwf   new_d
        movlw   preset_rec
        movwf   ser_st
        retlw   0

;*---------- Set receive data to all 1's. ----------*

preset_rec

        movlw   0xFF
        movwf   rec_d
        movlw   sample_stop
        movwf   ser_st
        retlw   0
 
;*---------- Sample stop bit. ----------*

sample_stop

        btfss   PORTC, din
        bsf     cmd_fl, dav       ; Set dav if correct stop bit.
        movlw   ser_idle
        movwf   ser_st
        retlw   0

;******************************************************
;*---------- Command handler state machine. ----------*
;******************************************************

;*---------- Jump to current state. ----------*

command_proc

        movf    cmd_st, W          ; t13
        movwf   PCL                ; t14, t15

;*---------- Idle state, wait for dav. ----------*

idle_w_dav

        movlw   my_addr            ; t16
        xorwf   new_d, W           ; Check for my address.
        movlw   wait_dav           ; Default next state
        btfsc   cmd_fl, dav
        btfss   STATUS, Z
        movlw   idle_w_dav         ; Stay if no dav or address mismatch.  
        movwf   cmd_st
        bcf     cmd_fl, dav
        retlw   0                  ; t24, t25

;*---------- Address match found, wait for new dav. ----------*

wait_dav

        movlw   my_addr
        xorwf   new_d, W
        movlw   check_hi_addr
        btfsc   cmd_fl, dav
        btfsc   STATUS, Z
        movlw   wait_dav           ; Stay here if no dav or address match.
        movwf   cmd_st
        bcf     cmd_fl, dav
        retlw   0   

;*---------- Check high address bits. ----------*

check_hi_addr

        movlw   hi_addr
        xorwf   new_d, W
        andlw   0x98               ; Mask out relevant bits, and set Z flag.
        movlw   decode_cmd
        btfss   STATUS, Z
        movlw   idle_w_dav         ; Go to idle if there is hi addr mismatch.
        movwf   cmd_st
        nop
        retlw   0

;*---------- Decode command. ----------*

decode_cmd

        movlw   perform_cmd
        btfsc   new_d, 6
        movlw   get_param
        movwf   cmd_st
        movf    new_d, W
        andlw   7
        iorlw   0x10
        movwf   FSR                ; Set FSR to the selected counter.
        retlw   0    
    
;*---------- Get a parameter value. ----------*

get_param

        btfsc   cmd_fl, dav
        movlw   idle_w_dav
        movwf   cmd_st             ; Go to idle when dav occurs.
        movf    new_d, W
        btfsc   cmd_fl, dav
        btfss   new_d, 7
        movf    INDF, W            ; Keep old value if new addr. or no dav. 
        movwf   INDF               ; Update parameter.
        retlw   0

;*---------- Perform the command. ----------*

perform_cmd

        movlw   idle_w_dav
        movwf   cmd_st
        movf    INDF, W
        andlw   0x7f               ; Mask off highest bit
        bsf     FSR, 3             ; Redirect FSR to counters.
        movwf   INDF
        btfss   new_d, 5
        clrf    INDF      
        retlw   0

;**********************************************
;*---------- Counter state machine. ----------*
;**********************************************

;*---------- Jump to current state. ----------*

counter_proc

        movf    cnt_st, W
        movwf   PCL

;*---------- Divide by 256. ----------*

count_wrap

        incf    wrap_c, F          ; t16
        movlw   run_port0
        btfsc   STATUS, Z
        movwf   cnt_st             ; Handle ports if counter wraps.
        movlw   8
        btfsc   STATUS, Z
        movwf   wrap_c             ; Adjust counter when it wraps. 
        nop
        retlw   0

;*-------- Counter for port0. ----------*

run_port0

        movlw   run_port1          ; t16 
        movwf   cnt_st
        decf    sw0l_c, F
        btfsc   sw0l_c, 7          ; Detect end count.
        goto    port0_expired      ; t20, t21
        bsf     PORTB, sw0l        ; Set output if counting.
        goto    end_port0          ; t22, t23
        
port0_expired

        clrf    sw0l_c             ; Undo the decrement if counter expired.
        bcf     PORTB, sw0l        ; Clear output.

end_port0

        retlw   0

;*-------- Counter for port1. ----------*

run_port1

        movlw   run_port2          ; t16 
        movwf   cnt_st
        decf    sw0r_c, F
        btfsc   sw0r_c, 7          ; Detect end count.
        goto    port1_expired      ; t20, t21
        bsf     PORTB, sw0r        ; Set output if counting.
        goto    end_port1          ; t22, t23
        
port1_expired

        clrf    sw0r_c             ; Undo the decrement if counter expired.
        bcf     PORTB, sw0r        ; Clear output.

end_port1

        retlw   0

;*-------- Counter for port2. ----------*

run_port2

        movlw   run_port3          ; t16 
        movwf   cnt_st
        decf    sw1l_c, F
        btfsc   sw1l_c, 7          ; Detect end count.
        goto    port2_expired      ; t20, t21
        bsf     PORTB, sw1l        ; Set output if counting.
        goto    end_port2          ; t22, t23
        
port2_expired

        clrf    sw1l_c             ; Undo the decrement if counter expired.
        bcf     PORTB, sw1l        ; Clear output.

end_port2

        retlw   0

;*-------- Counter for port3. ----------*

run_port3

        movlw   run_port4          ; t16 
        movwf   cnt_st
        decf    sw1r_c, F
        btfsc   sw1r_c, 7          ; Detect end count.
        goto    port3_expired      ; t20, t21
        bsf     PORTC, sw1r        ; Set output if counting.
        goto    end_port3          ; t22, t23
        
port3_expired

        clrf    sw1r_c             ; Undo the decrement if counter expired.
        bcf     PORTC, sw1r        ; Clear output.

end_port3

        retlw   0

;*-------- Counter for port4. ----------*

run_port4

        movlw   run_port5          ; t16 
        movwf   cnt_st
        decf    sw2l_c, F
        btfsc   sw2l_c, 7          ; Detect end count.
        goto    port4_expired      ; t20, t21
        bsf     PORTC, sw2l        ; Set output if counting.
        goto    end_port4          ; t22, t23
        
port4_expired

        clrf    sw2l_c             ; Undo the decrement if counter expired.
        bcf     PORTC, sw2l        ; Clear output.

end_port4

        retlw   0

;*-------- Counter for port5. ----------*

run_port5

        movlw   run_port6          ; t16 
        movwf   cnt_st
        decf    sw2r_c, F
        btfsc   sw2r_c, 7          ; Detect end count.
        goto    port5_expired      ; t20, t21
        bsf     PORTC, sw2r        ; Set output if counting.
        goto    end_port5          ; t22, t23
        
port5_expired

        clrf    sw2r_c             ; Undo the decrement if counter expired.
        bcf     PORTC, sw2r        ; Clear output.

end_port5

        retlw   0

;*-------- Counter for port6. ----------*

run_port6

        movlw   run_port7          ; t16 
        movwf   cnt_st
        decf    sw3l_c, F
        btfsc   sw3l_c, 7          ; Detect end count.
        goto    port6_expired      ; t20, t21
        bsf     PORTC, sw3l        ; Set output if counting.
        goto    end_port6          ; t22, t23
        
port6_expired

        clrf    sw3l_c             ; Undo the decrement if counter expired.
        bcf     PORTC, sw3l        ; Clear output.

end_port6

        retlw   0

;*-------- Counter for port7. ----------*

run_port7

        movlw   count_wrap         ; t16 
        movwf   cnt_st
        decf    sw3r_c, F
        btfsc   sw3r_c, 7          ; Detect end count.
        goto    port7_expired      ; t20, t21
        bsf     PORTC, sw3r        ; Set output if counting.
        goto    end_port7          ; t22, t23
        
port7_expired

        clrf    sw3r_c             ; Undo the decrement if counter expired.
        bcf     PORTC, sw3r        ; Clear output.

end_port7

        retlw   0

 ;************************************************************

        ORG     0x100
  
init_code

        movlw   0xc7             ; Internal timer clock, max prescaler.
        option 
        clrf    PORTB            ; Clear output ports.             
        clrf    PORTC 
        movlw   0x38             ; Set output ports to out. 
        tris    PORTB             
        movlw   0x20
        tris    PORTC  
        movlw   ser_idle
        movwf   ser_st           ; Initial state of serial receiver.
        movlw   idle_w_dav
        movwf   cmd_st           ; Initial state of command handler.  
        movlw   count_wrap
        movwf   cnt_st           ; Initial state of counters.
        movlw   0xF              ; Default nominal pulse width 0.30 s. 
        movwf   sw0l_t
        movwf   sw0r_t
        movwf   sw1l_t
        movwf   sw1r_t
        movwf   sw2l_t
        movwf   sw2r_t
        movwf   sw3l_t
        movwf   sw3r_t
        clrf    sw0l_c           ; Clear all pulse counters.  
        clrf    sw0r_c
        clrf    sw1l_c
        clrf    sw1r_c
        clrf    sw2l_c
        clrf    sw2r_c
        clrf    sw3l_c
        clrf    sw3r_c
        clrf    cmd_fl           ; Clear dav and dend flags
        movlw   0xFF
        movwf   rec_d            ; Set receiver shift register to all 1's.
        movwf   wrap_c           ; Set counter divider to -1.  

;******************************************
;*---------- Main program loop. ----------*
;******************************************


main_loop

        call    serial_proc
        call    command_proc
        call    serial_proc
        call    counter_proc 
        call    serial_proc
        nop                     ; t11 
        nop 
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        goto    main_loop       ;t24, t25  

endofprog

        END                       ; directive 'end of program'

