;**********************************************************************
; f627-10mhz.asm                                                      *
;   This file is code for a gps locked frequency standard             *
;   on the PICmicro PIC16F627.                                        *
;**********************************************************************
;                                                                     *
;    Filename:	    f627-10mhz.asm                                    *
;    Date:          Mar 24 2003                                       *
;    File Version:                                                    *
;                                                                     *
;    Author:        Lawrence Glaister VE7IT                           *
;    Company:       Glaister Consulting                               *
;                                                                     *
;                                                                     *
;**********************************************************************
;                                                                     *
;    Files required:                                                  *
;        p16f627.inc                                                  *
;                                                                     *
;                                                                     *
;**********************************************************************
;                                                                     *
;    Notes:                                                           *
;                                                                     *
; - build using                                                       *
; assembler package found at                                          *
;  http://gputils.sourceforge.net/     or download page of            *
;  https://sourceforge.net/project/showfiles.php?group_id=41924       *                                             *
;   gpasm f627-10mhz.asm                                              *
;                                                                     *
;OLD:  tpasm -l f627-10mhz.lst -o intel f627-10mhz.hex f627-10mhz.asm *
; - burn  using                                                       *
;   pp627 ( a modified version of pp84 by L.P Glaister )              *
;   pp627 f627-10mhz.hex                                              *
;                                                                     *
;**********************************************************************

	list      p=16f627, n=58, c=80      ; list directive to define processor
	radix  	dec
	
	#include <P16F627.INC>        ; processor specific variable definitions
; turn off crossing page boundary message
;	ERRORLEVEL -306, -302

; see page 96 of data sheet for description of config bits
; code protection off
; watch dog timer off
; brown out detection on
; power up timer enabled
; high speed xtal osc
; master clear pin used
; low voltage programming off
; NOTE: rb4 pin 10 must be pulled low (510ohms to gnd) to be
; sure HVP mode will work.
	__CONFIG _CP_OFF & _WDT_OFF & _BODEN_ON & _PWRTE_ON & _HS_OSC & _MCLRE_ON & _LVP_OFF

; THESE 4 NIBBLES CAN BE USED FOR SOFTWARE VERSION NUMBER
	__idlocs	H'F0AD'


XTAL_FREQ 	equ 11064595	; marked 11.0592 mhz (from 8051 project)
FAM_17		equ 0		; not a 17xxx family pic (for baud macro)

; I/O defns for MAX541 16 bit DAC on port A
DAC_CS		equ 2		; MAX541 /cs
DAC_CLK		equ 1 		; MAX541 clk
DAC_DAT		equ 0		; MAX541 data

;***** RAM LOCATION DEFINITIONS
; locations 70-7f are common to all 4 banks (no bank select issues)
; Bank 0 has 96 bytes of general purpose regs 20h-7fh (hi 16 are common to all banks)
; *** Bank0 *** 80 bytes 0x20 - 0x6f
	CBLOCK	0x020
	ACCaLO				;16 bit math accumulator
	ACCaHI
	ACCbLO				;16 bit math accumulator
	ACCbHI	   			   
	dac_h
	dac_l				; output word for max541 16 bit dac
	temp1				; temp for led flash routine
	dac_tmp				; temp for bit counter in update dac routine
	dac_sr				; temp sr for clocking MAX541
	ser_cnt				; serial byte counter
	ser_last			; last ser char rx'd
	ser_b60				; gps exponent/sign word
	ser_b61				; gps exponent/sign word
	exp_h				; exponent of freq error reported by gps
	exp_l
	gps_err				; flag indicating dac limits exceeded
	flasher				; counter used for flashing led display
	ENDC

; *** Bank1 *** 80 bytes 0xA0 - 0xEF
	CBLOCK	0x0A0

	ENDC

; *** Bank2 ***	48 Bytes 0x120 - 0x14f
	CBLOCK	0x120
	ENDC


; *** Bank0/1/2/3 mirrored in all banks 0x70, 0xF0, 0x170, 0x1F0, 16 bytes
	CBLOCK	0x070
	w_temp               ; variable used for context saving 
	status_temp          ; variable used for context saving
	ENDC



;	ORG	H'2100'
;	DE	"(c)2003 L.P. Glaister"
	
BLED1   EQU     0       ; o/p  led
BLED2   EQU     3       ; o/p  led
SOH	EQU	1	; start of header char in serial stream


;**********************************************************************
; MACROS
;********************
;
;   Macro UART_BAUD <baud>
;
;   This macro sets assembler variables to indicate the best baud rate
;   generator configuration for the USART in asynchronous mode, given the
;   desired baud rate and the oscillator frequency.  No code is generated.
;   The following assembler variables are set:
;
;     VAL_SPBRG  -  Value for the SPBRG baud rate generator register.
;
;     BAUD_REAL  -  Real (not desired) baud rate resulting from the
;       selected settings.
;
;     VAL_TXSTA  -  Value for the TXSTA regisIB4   In addition to
;       selecting the proper baud rate generator configuration, this
;       value also selects the following:
;
;       Asynchronous mode
;       8 bits per character
;       transmitter enabled
;
;   This macro produces an assembly time warning if the closest available
;   baud rate is more then 2.9% from the desired baud rate (off by 25% of
;   a bit time in the middle of the last bit).  It produces an assembly
;   error if the baud rate error is 5.8% or higher.
;
uart_baud macro  baud
         local   err         ;baud rate error in parts per 1000
;
;   Init values assuming will use high speed mode.
;
  if FAM_17                  ;17Cxx family doesn't have high speed mode
val_txsta set    (1 << TXEN) ;set TXSTA register value
    else
val_txsta set    (1 << TXEN) | (1 << BRGH) ;init TX config for high speed mode
    endif
val_spbrg set    XTAL_FREQ * 16 / baud - 256 ;find divider value, 8 fraction bits
val_spbrg set    (val_spbrg + 128) >> 8 ;round and scale to final SPBRG value
baud_real set    XTAL_FREQ / (16 * (val_spbrg + 1)) ;find actual selected baud rate
;
;   Switch to low speed mode if the baud rate generator value would require
;   more than 8 bits to represent, or this chip has not high speed mode.
;
  if (val_spbrg > 255) || FAM_17 ;too slow for high speed mode or no HS ?
    if ! FAM_17
val_txsta set    val_txsta & ~(1 << BRGH) ;disable high speed mode
      endif
val_spbrg set    XTAL_FREQ * 4 / baud - 256 ;find divider value, 8 fraction bits
val_spbrg set    (val_spbrg + 128) >> 8 ;round and scale to final SPBRG value
    if val_spbrg > 255       ;clip at largest allowable baud rate generator value
val_spbrg set    255
      endif
baud_real set    XTAL_FREQ / (64 * (val_spbrg + 1)) ;find actual selected baud rate
    endif

err      set     (1000 * (baud_real - baud)) / baud ;baud rate err in parts/1000
  if err < 0
err      set     -err        ;make absolute value of error
    endif
  if err > 58
         error   "Baud rate error exceeds 5.8%"
    endif
  if err > 29
         messg   "WARNING: Baud rate error exceeds 2.9%"
    endif
         endm

; Alternate calcs to check baud rate divisor
; Calculates baudrate when BRGH = 0, adjust for rounding errors
brgh0 equ ((((10*XTAL_FREQ/(64*9600))+5)/10)-1)
; Calculates baudrate when BRGH = 1, adjust for rounding errors
brgh1 equ ((((10*XTAL_FREQ/(16*9600))+5)/10)-1)



;**********************************************************************
	ORG     0x000             ; processor reset vector
	NOP			; required for the ICD 
	CLRF	STATUS		; ensure we are at bank0	
	CLRF    PCLATH        	; ensure page bits are cleared ( before GOTO xxx !!! )

	goto    start             ; go to beginning of program


	ORG     0x004             ; interrupt vector location
	movwf   w_temp            ; save off current W register contents
	movf	STATUS,W          ; move status register into W register
	movwf	status_temp       ; save off contents of STATUS register


; isr code can go here or be located as a call subroutine elsewhere


	movf    status_temp,W     ; retrieve copy of STATUS register
	movwf	STATUS            ; restore pre-isr STATUS register contents
	swapf   w_temp,F
	swapf   w_temp,W          ; restore pre-isr W register contents
	retfie                    ; return from interrupt



start
	MOVLW   B'11111111'     ; PORT REGS TO DEFAULTS
	MOVWF   PORTA
	MOVLW   B'11111111'
	MOVWF   PORTB
	
	MOVLW	7		; turn comparators off
	MOVWF	CMCON		; and enable pins for I/O


	BSF     STATUS,RP0      ; OPTION & TRIS ARE HI REGS

	MOVLW   B'00000111'
	; B7:   RBPU   - ENABLE PORT B PULL-UP RES, 0=OFF
	; B6:   INTEDG - RB0 INT EDGE SEL 0=-VE
	; B5:   T0CS   - TMR0 SOURCE, 0=FOSC/4, 1=RA4 PIN
	; B4:   T0SE   - RA4CLK PIN EDGE SELECT, 0=+VE
	; B3:   PSA    - PRESCALE ASSIGN 0=TIMER, 1=WDG
	; B2-0: PS2-0  - PRESCALE RATE 111=256 FOR TIMER (=127 FOR WDT)

	MOVWF   OPTION_REG

	MOVLW   B'11000110'     ; 0=OUTPUT
	MOVWF   TRISB

	MOVLW   B'11100000'     ; 1=INPUT
	MOVWF   TRISA

	BCF     STATUS,RP0      ; BACK TO LO REGS

	MOVLW   B'00000000'        
	; B7: GIE  - GLOBAL INT ENABLE, 0=OFF
	; B6: EEIE - EERAW WRITE DONE INT ENABLE, 0=OFF
	; B5: T0IE - TMR0 O/FLOW INT ENABLE, 0=OFF
	; B4: INTE - RB0 INT ENABLE, 0=OFF
	; B3: RBIE - PORTB CHANGE-OF-STATE INT ENABLE, 0=OFF
	; B2: T0IF - TMR0 O'FLOW FLAG, 1=O/FLOW, S/WARE MUST RESET
	; B1: INTF - RB0 INT FLAG, ACTIVE HIGH
	; B0: RBIF - PORTB CHANGE-OF-STATE FLAG, ACTIVE HIGH
	MOVWF   INTCON

	call init_uart
;	call init_gps		; init the gps board

	movlw	H'80'
	movwf	dac_h
	clrf	dac_l
	call 	dacout		; set dac to 32768

start01
	bcf	PORTA,4		; +sign led on
	; set all error leds on
	bcf	PORTB,0		; led on
	bcf	PORTB,3		; led on
	bcf	PORTB,4		; led on
	bcf	PORTB,5		; led on
main  
	bsf	PORTA,3		; yellow led off IDLE

	
	clrf	ser_cnt		; zero packet byte counter

	btfss PIR1,RCIF         ; check for received data
        goto main

	; this may be start of pkt if we are lucky	
	bcf	PORTA,3		; yellow led on for duration of pkt processing

        movf 	RCREG,W         ; save received data in W
	sublw	SOH
	btfss	STATUS,Z
	goto	main		; keep looking for SOH

	; got SOH, look for packet ID byte
	call	receive
	sublw	H'15'		; look for packet id = 21
	btfss	STATUS,Z
	goto	main		; keep looking for SOH
	
	; got packet 21 id byte, now look for complement of it
	call	receive
	sublw	H'ea'		; look for packet id = /21
	btfss	STATUS,Z
	goto	main		; keep looking for SOH
	
	; ok, next byte is the packet length 0..255
	call	receive
	clrf	ser_cnt		; setup counter for offsets into body

	; now comes the body
	; we are interested in only 2 bytes
	; buff[61] & 0x80 is the sign bit of osc error
	; exp = ((buff[61] & 0x7f) << 4) + ((buff[60] & 0xf0) >> 4) - 1023;
	
main01	call	receive
	movwf	ser_last
	incf	ser_cnt,f	; start walking up the counter
	
	movf	ser_cnt,w
	sublw	D'61'		; discard 1st 60 bytes
	btfss	STATUS,Z
	goto	main01
	
	; byte counter == 61
	movfw	ser_last
	movwf	ser_b60
	
	call	receive
	movwf	ser_b61
	
	; sign of error = buff[61] & 0x80
	; first red led indicate sign of error
	bsf	PORTA,4		;turn off +led		
	btfss	ser_b61,7
	bcf	PORTA,4		;+led on
	
	; exp = ((buff[61] & 0x7f) << 4) + ((buff[60] & 0xf0) >> 4) - 1023
	; typical values are -30 to -37 with -37 about 1e-12 and -32 about 1e-8
	movf	ser_b60,w
	movwf	exp_l
	movf	ser_b61,w
	movwf	exp_h
	
	rrf	exp_h,f
	rrf	exp_l,f		;shift everybody down 4 places
	rrf	exp_h,f	
	rrf	exp_l,f
	rrf	exp_h,f	
	rrf	exp_l,f
	rrf	exp_h,f
	rrf	exp_l,f

	movlw	H'07'		; clean up unused upper bits
	andwf	exp_h,f
	
	; if the exponent == 0, we have serial com with gps, but gps has
	; not aquired satelites yet.
	
	btfsc	STATUS,Z
	goto	start01		; big error condx
	
	; at this point exp will be 993 (big error) to 986 (small error)

	call	clrled		; turn off all 4 error magnitude leds
	
;	we use the binary exponent to determine the rate of
;	correction of the dac voltage. Big errors get fast
;	correction, and small errors get no correction.	
	call 	loadacc		; accA = exponent
	; accB = smallest error constant
	movlw	D'989' & H'ff'	
	movwf	ACCbLO
	movlw	((D'989' & H'ff00') >> 8)
	movwf	ACCbHI
	call	D_sub		;accb = accB - accA
	movlw	0
	btfss	ACCbHI,7	; test for minus result
	goto	main02		; very small error
	bcf	PORTB,0		; small error led on	
		
	call	loadacc
	; accB =  error constant
	movlw	D'991' & H'ff'	
	movwf	ACCbLO
	movlw	((D'991' & H'ff00') >> 8)
	movwf	ACCbHI
	call	D_sub		;accb = accB - accA
	movlw	1
	btfss	ACCbHI,7	; test for minus result
	goto	main02		; small error
	bcf	PORTB,3		; med error led on	
		
	call	loadacc
	; accB =  error constant
	movlw	D'993' & H'ff'	
	movwf	ACCbLO
	movlw	((D'993' & H'ff00') >> 8)
	movwf	ACCbHI
	call	D_sub		;accb = accB - accA
	movlw	16
	btfss	ACCbHI,7	; test for minus result
	goto	main02		; small error
	bcf	PORTB,4		; med error led on	
		
	call	loadacc
	; accB =  largest error constant
	movlw	D'995' & H'ff'	
	movwf	ACCbLO
	movlw	((D'995' & H'ff00') >> 8)
	movwf	ACCbHI
	call	D_sub		;accb = accB - accA
	movlw	96
	btfss	ACCbHI,7	; test for minus result
	goto	main02		; small error

	bcf	PORTB,5		; large error led on	
	movlw	192		; big correction for power ups
		
	; update dac by w amount
main02
	incf	flasher,f	; count each update cycle
	clrf	gps_err		; assume no error on dac o/p
	btfsc	ser_b61,7
	call 	dac_inc
	
	btfss	ser_b61,7
	call 	dac_dec

	btfss	gps_err,0
	goto	main		;discard rest of packet

	; get here when dac inc or dec has hit a limit
	; 4 leds have already been set with error magnitude
	; on alternate cycles, we clear them to flash display
	btfsc	flasher,0
	call	clrled
	goto	main

;	routine to turn off all 4 error magnitude display leds
clrled
	bsf	PORTB,0		; led off
	nop
	bsf	PORTB,3		; led off
	nop
	bsf	PORTB,4		; led off
	nop
	bsf	PORTB,5		; led off
	return

			
;	setup accummulator for subtract
	; accA = exponent
loadacc
	movf	exp_l,w
	movwf	ACCaLO
	movf	exp_h,w
	movwf	ACCaHI
	return

; ***********************************************************************
;
;  INIT_UART - Initialises UART
;  enables recevier and transmitter
;  Make sure to be at bank0
	uart_baud(9600)
init_uart
	; make sure pins are setup before calling this routine
	; TRISC:6 and TRISC:7 must be set ( as for output, but operates as input/output )
	; furthermore its advised that interrupts are disabled during this routine
	
	; setup baudrate
	MOVLW	SPBRG 	; get adress for serial baud reg
	MOVWF	FSR	; setup fsr
	MOVLW	val_spbrg
	MOVWF	INDF	; and store it

	; enable transmitter
	MOVLW	TXSTA	; get adress for serial enable reg
	MOVWF	FSR	; setup fsr
	MOVLW	val_txsta; preset enable transmitter and high speed mode
	MOVWF	INDF	; and set it

	; enable recevier
	MOVLW	(1<<SPEN)|(1<<CREN) ; preset serial port enable and continous recevie
	MOVWF	RCSTA	; set it

	; enable reciever interrupt
;	MOVLW	PIE1	; get adress for periphial irq's
;	MOVWF	FSR	; setup fsr
;	BSF	INDF,RCIE ; enable reciever irq
;	BSF	INTCON,PEIE ; and periphial irq must also be enabled

        movf RCREG,W
        movf RCREG,W
        movf RCREG,W            ; flush receive buffer

	RETURN
;
; ---------------------------------------------------------------------
; RECEIVE CHARACTER FROM RS232 AND STORE IN W
; ---------------------------------------------------------------------
; This routine does not return until a character is received.
;
receive btfss PIR1,RCIF         ; (5) check for received data
        goto receive

        movf RCREG,W            ; save received data in W
        return
;
; ---------------------------------------------------------------------
; SEND CHARACTER IN W VIA RS232 AND WAIT UNTIL FINISHED SENDING
; ---------------------------------------------------------------------
;
send    movwf TXREG             ; send data in W

TransWt bsf STATUS,RP0          ; RAM PAGE 1
WtHere  btfss TXSTA,TRMT        ; (1) transmission is complete if hi
        goto WtHere

        bcf STATUS,RP0          ; RAM PAGE 0
        return

;
; ---------------------------------------------------------------------
; MESSAGE
; send int string to gps
; ---------------------------------------------------------------------
;
init_gps
	movlw  'V'
        call send
        movlw  'E'
        call send
        movlw  '7'
        call send
        movlw  'I'
        call send
        movlw  'T'
        call send
        movlw  ' '
        call send
        movlw  'G'
        call send
        movlw  'P'
        call send
        movlw  'S'
        call send
        movlw  '-'
        call send
        movlw  'S'
        call send
        movlw  'T'
        call send
        movlw  'D'
        call send
        movlw  0x0D ; CR
        call send
        movlw  0x0A ; LF
        call send
        return
;
; ---------------------------------------------------------------------
; DACOUT
; updates dac from memory variable dac_h, dac_l
; takes approx 100us to update dav
; ---------------------------------------------------------------------
;
dacout
	bcf 	PORTA,DAC_CLK	; clk low
	nop
	bcf 	PORTA,DAC_CS	; cs low

	movlw 	d'8'		; 8 hi bits to shift out
	movwf 	dac_tmp
	movf	dac_h,w		; MSB go out first
	movwf	dac_sr		; save copy in shift register

	; send 8 MSB starting with MS bit
dac001
	bcf	PORTA,DAC_DAT	; start with bit low
	rlf 	dac_sr,f	; rotate bits through c
	btfsc	STATUS,C
	bsf	PORTA,DAC_DAT	; flip data if needed
	call	dacstrobe
	decfsz	dac_tmp,f
	goto	dac001		; loop for rest of bits in byte
	
	movlw 	d'8'		; 8 hi bits to shift out
	movwf 	dac_tmp
	movf	dac_l,w		; LSB of dac word
	movwf	dac_sr		; save copy in shift register

	; send 8 LSB starting with MS bit
dac002
	bcf	PORTA,DAC_DAT	; start with bit low
	rlf 	dac_sr,f	; rotate bits through c
	btfsc	STATUS,C
	bsf	PORTA,DAC_DAT	; flip data if needed
	call	dacstrobe
	decfsz	dac_tmp,f
	goto	dac002		; loop for rest of bits in byte	
	
		
	bsf PORTA,DAC_CS	; cs hi
	return

;
; ---------------------------------------------------------------------
; DACSTROBE
; massage the DAC clock pin
; datasheet says 40-60% reqd??
; this routine only controls time clock is high, and this is adjusted
; using nops to approximate the loop time for the calling routine which
; controls the time the clock is low.
; ---------------------------------------------------------------------
;
dacstrobe
	bsf	PORTA,DAC_CLK	;strobe active rising edge
	nop			;try to keep 50% cycle
	nop
	nop
	nop
	nop
	nop
	nop
	bcf	PORTA,DAC_CLK
	return
	
;
; ---------------------------------------------------------------------
; DAC_INC
; 
; routine called to increment dac voltage by amount contained in w.
; W is added to the DAC voltage buffer dac_h, dac_L
; 
; ---------------------------------------------------------------------
;

dac_inc
	addwf	dac_l,f
	btfsc	STATUS,C
	incfsz	dac_h,f
	goto	dacout		; no problems, return from there	

	; we get here if we overflowed 16 bits
	; limit to 0xffff
	movlw	H'ff'
	movwf	gps_err
	movwf	dac_l
	movwf	dac_h
	goto	dacout		; return from there

;
; ---------------------------------------------------------------------
; DAC_DEC
; 
; routine called to decrement dac voltage by amount contained in w.
; W is subtracted to the DAC voltage buffer dac_h, dac_L
; 
; ---------------------------------------------------------------------
;

dac_dec
	subwf	dac_l,f
	btfss	STATUS,C
	decfsz	dac_h,f
	goto	dacout	; no problems	

	; we get here if we get close to underflow 16 bits
	; limit to 0x0100
	clrf	dac_l
	clrf	dac_h
	incf	dac_h,f
	movlw	H'ff'
	movwf	gps_err
	goto	dacout
;
;*******************************************************************
;                 Double Precision Addition & Subtraction
;
;*******************************************************************;
;   Addition :  ACCb(16 bits) + ACCa(16 bits) -> ACCb(16 bits)
;      (a) Load the 1st operand in location ACCaLO & ACCaHI ( 16 bits )
;      (b) Load the 2nd operand in location ACCbLO & ACCbHI ( 16 bits )
;      (c) CALL D_add
;      (d) The result is in location ACCbLO & ACCbHI ( 16 bits )
;
;   Performance :
;               Program Memory  :       07
;               Clock Cycles    :       08
;*******************************************************************;
;   Subtraction : ACCb(16 bits) - ACCa(16 bits) -> ACCb(16 bits)
;      (a) Load the 1st operand in location ACCaLO & ACCaHI ( 16 bits )
;      (b) Load the 2nd operand in location ACCbLO & ACCbHI ( 16 bits )
;      (c) CALL D_sub
;      (d) The result is in location ACCbLO & ACCbHI ( 16 bits )
;
;   Performance :
;               Program Memory  :       14
;               Clock Cycles    :       17
;
;
;       Program:          DBL_ADD.ASM 
;       Revision Date:   
;                         1-13-97      Compatibility with MPASMWIN 1.40
;
;*******************************************************************;
;
;*******************************************************************
;         Double Precision Subtraction ( ACCb - ACCa -> ACCb )
;
D_sub   call    neg_A           ; At first negate ACCa; Then add
;
;*******************************************************************
;       Double Precision  Addition ( ACCb + ACCa -> ACCb )
;
D_add   movf    ACCaLO,W
	addwf   ACCbLO, F       ;add lsb
	btfsc   STATUS,C        ;add in carry
	incf    ACCbHI, F
	movf    ACCaHI,W
	addwf   ACCbHI, F       ;add msb
	retlw   0
;
;
neg_A   comf    ACCaLO, F       ; negate ACCa ( -ACCa -> ACCa )
	incf    ACCaLO, F
	btfsc   STATUS,Z
	decf    ACCaHI, F
	comf    ACCaHI, F
	retlw   0


	END                       ; directive 'end of program'


