;**********************************************************************
;    Filename:        serial.asm                                      *
;    Date:            2004-02-04                                      *
;    File Version:    1.1                                             *
;                                                                     *
;    Author:          Per Zander                                      *
;**********************************************************************
;    Files required:  p12c508.inc/p12c509.inc/p16c505.inc             *
;                                                                     *
;                     These are device dependent include files that   *
;                     define macros for the different registers,      *
;                     ports etc. of the selected device. They are     *
;                     delivered with the MPLAB development            *
;                     environment that can be downloaded from the     *
;                     Microchip homepage (www.microchip.com).         *  
;**********************************************************************
;    Description:
; 
;    This is a template for a PIC12C508/PIC12C09/PIC16C505 program
;    containing a simple serial receiver. The code can probably be
;    used with other PIC devices too, but I haven't tested that.
;
;    The serial receiver format is 8 bits, no parity, no handshaking.
;    One stop bit is sufficient. Data is inverted, so that an rs232 
;    signal can be used directly (after appropriate clamping).
;
;    The baudrate depends on the rate with which the receiver subroutine
;    is called. It needs to be called four times for each bit, and it 
;    will consume 11 instruction cycles (44 clock cycles) each time it 
;    is called. This includes the time for the call and return instructions.
;
;    Example:
;    With a baud rate of 9600 baud the bit time is 104 microseconds.
;    The receiver subroutine must then be called every 26th microsecond.
;    With a 4 MHz PIC you have 1 instruction per microsecond, and thus
;    your main program must have 26 - 11 = 15 instruction cycles between
;    each call to the receiver subroutine.  
;
;    It is important that your main program uses the exact number of
;    instruction cycles, and you have to insert nop's or delay loops
;    to ensure correct timing. Note that some instructions (e.g. 
;    call and goto) require two instruction cycles. 
;      
;**********************************************************************

;*---------- Device select. ----------*

; Replace this with the directive and include file for the appropriate
; PIC device.

	  list      p=16c505            ; List directive to define processor
	  #include <p16c505.inc>        ; Processor specific variable
                                        ; definitions

;*---------- Configuration specification. ----------*

; Replace this with the appropriate configuration for your application.
; Note that the internal RC oscillator will not give you sufficient 
; frequency accuracy for serial communication. Therefore I recommend
; the use of a crystal oscillator.

	  __CONFIG   _CP_OFF & _WDT_OFF & _MCLRE_OFF & _XT_OSC

;*---------- Port definitions. ----------*

; I have selected port RC5 on the PIC16C505 as the serial receiver
; input port. Change this if you use another pin for the input.
; PORTC in PIC16C505 is register 7. PORTB in PIC16C505 and the GPIO
; in PIC12C508/PIC12C509 is register 6. 

dport   EQU     0x07               ; Data in  on PORTC
din     EQU     0x05               ; Data in  on bit 5     

;*---------- Variable definitions. ----------*

; You can of course use other registers than those I have selected,
; just change the numbers. Registers from 7 and up to 0x1f are general 
; registers in PIC12C508/PIC12C509. In PIC16C505 the general registers 
; are from 8 and up to 0x1f. 
; In PIC12C509 and PIC16C505, register numbers between  0x10 and 0x1f 
; define multiple registers through banking. These registers can be used 
; too, but make sure that you always have the same bank selected when 
; calling the receiver subroutine.

ser_st  EQU     0x08               ; Serial state pointer.
rec_d   EQU     0x09               ; Receive data shift register.
cmd_fl  EQU     0x0A               ; Flags used by the serial receiver.           
new_d   EQU     0x0B               ; Received_data.

;*---------- Program flag definitions. ----------*

; This defines which bits in the 'cmd_fl' register that are used for
; the serial receiver.

dav     EQU     0x00               ; Data available flag from receiver.
dend    EQU     0x01               ; Serial receiver end of byte flag. 

; The received data is passed to the calling program through the 'new_d'
; register. The availability of new data is signalled by the 'dav' bit 
; of the 'cmd_fl' register. When the calling program detects the 'dav'
; bit set, it should copy the data and then reset the 'dav' bit.

;**********************************************************************
;* The program starts here after reset.
;**********************************************************************

        ORG     0x3FF             ; Processor reset vector.

; The PIC processors are pre-programmed with an oscillator calibration
; value. If you use an OTP version (plastic package), you don't need
; to have the calibration value in your code. But with the UV-erasable
; version (ceramic package with window), you need to put in this 
; instruction except for the first time you program it. It will do no 
; harm to always have it there. Most programmers skip this if it is 
; already programmed. It must be a 'movlw' instruction, but the value
; doesn't matter if you use a crystal oscillator.

        movlw   0x38              ; Osc. cal. value.

        ORG     0x000             ; Coding begins here.

startofprog

; You should always place the main program after the subroutines,
; because the 'call' and 'movwf PCL' instructions can only reach the 
; first 256 lines of code. 

        goto    init_code          
                                  
;*****************************************************
;*---------- Serial receive state machine. ----------*
;*****************************************************

;*---------- Jump to current state. ----------*

serial_proc

        movf   ser_st, W           
        movwf  PCL                

;*---------- Idle state. Must see stop bit to leave it. ----------*

ser_idle

        btfss   dport, din       
        movlw   wait_start       
        movwf   ser_st
        nop
        retlw   0                

;*---------- Wait for start bit. ----------*

wait_start

        btfsc   dport, din
        movlw   sample_data
        movwf   ser_st
        bcf     cmd_fl, dend          ; Clear data end flag.
        retlw   0 

;*---------- Sample data in. ----------*

sample_data

        btfsc   dport, 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   dport, 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   dport, din
        bsf     cmd_fl, dav       ; Set dav if correct stop bit.
        movlw   ser_idle
        movwf   ser_st
        retlw   0

; Add your other subroutines here, if you have any.

;*******************************************
;*---------- Main program start. ----------*
;*******************************************

init_code

; These initial values must be set for the serial receiver: 

        movlw   ser_idle
        movwf   ser_st           ; Initial state of serial receiver.
        clrf    cmd_fl           ; Clear dav and dend flags
        movlw   0xFF
        movwf   rec_d            ; Set receiver shift register to all 1's.  

; Here you add the initial settings needed for your main program: 

;... Your init code ...

;******************************************
;*---------- Main program loop. ----------*
;******************************************

main_loop

        call    serial_proc       ; This consumes 11 instruction cycles.
        
; Add your main program code here. Be careful to maintain the correct timing.   

        goto    main_loop         ; This consumes 2 instruction cycles.  

endofprog

        END                       ; directive 'end of program'

