;**********************************************************************
;    Filename:        lmod.asm                                        *
;    Date:            2004-02-01                                      *
;    File Version:    1.0                                             *
;                                                                     *
;    Author:          Per Zander                                      *
;**********************************************************************
;    Files required:  p16c505.inc                                     *
;********************************************************************** 
;    Description:                                                     
; 
;    General:
;
;    This is a program for model railroad lamp 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).
;
;    Lamp control packet:
;
;    A lamp 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. If the c bit is cleared, the selected port is 
;    deactivated. 
;
;    Intensity configuration packet:
;
;    An intensity 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 |    Intensity              |
;    |___|___________________________|
;
;    The intensity can be set in steps of 1/64 of max intensity. A value of 64 
;    or higher will give full intensity, which is the default setting.
;
;**********************************************************************

;*---------- 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               ; Contact X0 left  on RB0                 
sw0r    EQU     0x01               ; Contact X0 right on RB1
sw1l    EQU     0x02               ; Contact X1 left  on RB2
sw1r    EQU     0x00               ; Contact X1 right on RC0 
sw2l    EQU     0x01               ; Contact X2 left  on RC1
sw2r    EQU     0x02               ; Contact X2 right on RC2
sw3l    EQU     0x04               ; Contact X3 left  on RC4
sw3r    EQU     0x03               ; Contact X3 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 lamp output:

 
sig_msk EQU     0x0C               ; Mask for outputs.
wrap_c  EQU     0x0D               ; Counter 1...64->1     
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. ----------*
;******************************************************

;*---------- 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
        goto    main_loop          ; 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
        goto    main_loop

;*---------- 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
        goto    main_loop

;*---------- 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.
        goto    main_loop
    
;*---------- 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.
        goto    main_loop

;*---------- 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      
        goto    main_loop

        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   0x1
        movwf   wrap_c           ; Initialize intensity counter to 1.
        movlw   0x40             ; Default full strength on all outputs. 
        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
        clrf    sig_msk          ; Clear the mask for output values. 
        movlw   0xFF
        movwf   rec_d            ; Set receiver shift register to all 1's.  

;******************************************
;*---------- Main program loop. ----------*
;******************************************


main_loop

        call    serial_proc
        decf    wrap_c, F        ; t11       
        btfsc   STATUS, Z 
        bsf     wrap_c, 6        ; If (wrap_c == 0) wrap_c = 64;  
        movf    wrap_c, W
        subwf   sw3l_c, W        ; t15 
        rlf     sig_msk, F       ; Set bit in sig_msk if port count >= wrap_c
        movf    wrap_c, W
        subwf   sw3r_c, W
        rlf     sig_msk, F
        movf    wrap_c, W
        subwf   sw2r_c, W
        rlf     sig_msk, F       ; t22  
        movf    wrap_c, W
        subwf   sw2l_c, W
        rlf     sig_msk, F       ; t25 
        call    serial_proc
        movf    wrap_c, W        ; t11
        subwf   sw1r_c, W
        rlf     sig_msk, W
        movwf   PORTC            
        movf    wrap_c, W
        subwf   sw1l_c, W
        rlf     sig_msk, F
        movf    wrap_c, W
        subwf   sw0r_c, W
        rlf     sig_msk, F      
        movf    wrap_c, W
        subwf   sw0l_c, W
        rlf     sig_msk, W       ; t23        
        andlw   0x07
        movwf   PORTB            ;t25         
        call    serial_proc
        nop                      ; t11 
        nop  

;*---------- Jump to current command handler state. ----------*

command_proc

        movf    cmd_st, W          ; t13
        movwf   PCL                ; t14, t15

;*----- The command procedure ends with a jump to main_loop. -----* 

endofprog

        END                       ; directive 'end of program'

