;**********************************************************************
; ultralite.asm                                                       *
;   This file is code for a smart white LED lamp                      *
;   on the PICmicro PIC16F627.   See lightbar.dwg for cct.            *
; see camper-lite.sch and camper-lite.brd                             *
;                                                                     *
;**********************************************************************
;                                                                     *
;    Filename:	    ultra-lite.asm                                    *
;    Date:          Feb 24, 2004                                      *
;    File Version: 	beta test                                         *
;                   tested on hardware                                *
;                                                                     *
;    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 ultralite.asm                                               *
;                                                                     *
;                                                                     *
; - burn  using                                                       *
;   pp627 ( a modified version of pp84 by L.P Glaister )              *
;   pp627 ultralite     .hex                                          *
;                                                                     *
;**********************************************************************

	list      p=16f627, n=58, c=80      ; list directive to define processor
	radix  	dec
	
	#include <P16F627.INC>        ; processor specific variable definitions

; 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
; int rc osc (4mhz) with clk/4 on osc2 pin 15
; master clear pin used as I/O
; low voltage programming off
; NOTE: rb4 pin 10 must be pulled low to be sure HVP mode will work.
	__CONFIG _CP_OFF & _WDT_OFF & _BODEN_ON & _PWRTE_ON & _INTRC_OSC_CLKOUT & _MCLRE_OFF & _LVP_OFF

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


XTAL_FREQ 	equ 4000000	; nominal clock freq in hz


;***** 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
    bmode               ; current mode selected by pushbutton
                        ; 0 = lite level 1
                        ; 1 = lite level 2
                        ; 2 = lite level 3
                        ; 3 = off
			            ; 4 = off
	temp				; local variable used by various routines
    delay1
    off_timer           ; used for auto off function
    off_timerh          ; high order auto off timer
	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

; ****************** Macro definitions ********************************
;+++++
;	PAGE/BANK0/1/2/3 selects register bank 0/1/2/3.
;	Leave set to BANK0 normally.

BANK0	MACRO
	BCF	STATUS,RP0	; clear bank select bits
	BCF	STATUS,RP1
;	BCF	STATUS,IRP	; clear indirect adressing bit
	ENDM

BANK1	MACRO
	BSF	STATUS,RP0	; 
	BCF	STATUS,RP1	; 
;	BCF	STATUS,IRP	; clear indirect adressing bit
	ENDM

BANK2	MACRO
	BCF	STATUS,RP0	; 
	BSF	STATUS,RP1
;	BSF	STATUS,IRP	; set bit for indirect adressing
	ENDM

BANK3	MACRO
	BSF	STATUS,RP0	;
	BSF	STATUS,RP1
;	BSF	STATUS,IRP	; set bit for indirect adressing
	ENDM
;**********************************************************************
;* Program description
;*
; on powerup, the processor examines the state of RA0 comparitor
; to decide if it has been a long time or a short time since last
; powerup. If its been a short time, then we increment the light
; output mode, else we restore last light output level.
; Low battery detection (about 11.5v) is enabled when rb7 jmp'd to gnd
;
;**********************************************************************



;**********************************************************************
	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
	
    call    init_cmp1       ; setup comparators for input



	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)

	ERRORLEVEL -306, -302
	BSF     STATUS,RP0      ; OPTION & TRIS & VRCON ARE IN BANK 1
	MOVWF   OPTION_REG

    ; set port B for 4 inputs (config jumpers) and 4 outputs
    ; rb3 is pwm output
	MOVLW   B'11110000'     ; 0=OUTPUT
	MOVWF   TRISB

	; set port A as inputs
	MOVLW   B'11111111'     ; 1=INPUT
	MOVWF   TRISA

	BCF     STATUS,RP0      ; BACK TO LO REGS
	ERRORLEVEL +306, +302	

	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_timer1
    call    init_pwm1

	; init any i/o that needs to behave on powerup
	; add application specific inits here
    clrw                ; read display mode from eeprom at offset 0
    call    read_eeprom
    movwf   bmode       ; power up in last selected mode

    ; check to see how long since power was removed
    ; we use the fact that if power glitches for less than several seconds,
    ; the comparator input should be high still due to C1 storged charge.
    ; if its high, we bump to next display mode     
    btfsc   CMCON,6         ; sample comparator 1 output
    goto    start_1

    incf    bmode,W         ; bump lamp to next mode
    andlw   b'00000011'     ; modes 0..3
    movwf   bmode
    call    write_eeprom0   ; save new mode
    

start_1
    ; reset voltage comparator for low battery detection
	ERRORLEVEL -306, -302
	BSF     STATUS,RP0      ; OPTION & TRIS & VRCON ARE IN BANK 1
    ;need to set up internal vref here using VRCON
	movlw	b'10000000'+14		; vref enabled, vref not on RA2,high range
    movwf	VRCON
	BCF     STATUS,RP0      ; BACK TO LO REGS
	ERRORLEVEL +306, +302	
    
    call    init_offtimer           
    
; ====== MAIN ====== dispatch to correct light mode
main  

jmptab
    movlw	HIGH jmptab		; setup to index into jump table
	movwf	PCLATH
	movf	bmode,w
	andlw	B'00000011'		; force to 0,1,2,3 for case
	addwf	PCL,F			; jump into state table
	goto	level_0			; 0) off
	goto	level_1 		; 1) dim
	goto	level_2			; 2) med 
	goto	level_3			; 3) high
jtabend
	if ((jmptab & 0xff00 ) != (jtabend & 0xff00))
		error "Table must not cross page boundary - please movit!"
	endif


    
    

; ---------------------------------------------------------------------
; init_timer1
; set up registers associated with timer1 so that TMR1IF in PIR1 gets
; set at the interval we require ( 69.4ms )
; Sample timer calcs:
; XTAL_FREQ = 12mhz => .0000833 milliseconds period
; basic flash freq = 14.4hz => 69.4ms period
; .0000833ms * 4 * 8 = .0026656ms for 1 count of timer1 when using
; fosc/4 and prescaler of 8.
; we want a period of 69.4ms (72 flashes/min and 12 subsamples/flash)
; so the divider gets set to 69.4/.0026656 = 26035
; ---------------------------------------------------------------------
;
init_timer1
	; shut off the timer
	bcf		T1CON,TMR1ON

	; load the divider registers (clears prescaler counter)
	; divider calcs... scaled to use integer math.
	; to be totally correct, the preset value should be reduced
	; by the time it takes to call this routine and get the timer
	; restarted. In a polled environment detecting the TMR1IF is
	; going to vary, so the actual time interval will wander slightly.
base equ (XTAL_FREQ/4/8)
divi equ (10 * base/144)
divh equ (65536 - divi) >> 8
divl equ (65536 - divi) & 0x00ff
	movlw	divh
	movwf	TMR1H
	movlw	divl
	movwf	TMR1L

	; clr the interrupt flag
	bcf		PIR1,TMR1IF

;	see page 50 of data sheet for definition of T1CON
;	set timer 1 prescaler to /8
;	set timer 1 osc enable to off
;	dont care on sync bit
;	set timer 1 to use fosc/4 as input
;	enable timer 1
	movlw	(1<<T1CKPS1) + (1<<T1CKPS0) + (1<<TMR1ON)
	movwf	T1CON
	return

;
;-----------------------------------------------------------------------
;read_eeprom
;reads non volitile memory variable from eeprom at the offset
; in the w register. Returns the data in the W register.
;-----------------------------------------------------------------------
read_eeprom
	ERRORLEVEL -306, -302
    BANK1
    movwf   EEADR
    bsf     EECON1,RD
    movf    EEDATA,W    ;return eeprom data in W
    BANK0
	ERRORLEVEL +306, +302
    return
    
;
;-----------------------------------------------------------------------
; write_eeprom0
; writes data in W to eeprom offset 0
;-----------------------------------------------------------------------
write_eeprom0
	ERRORLEVEL -306, -302
    BANK1
    movwf   EEDATA
    clrf    EEADR       ; offset 0
    bsf     EECON1,WREN ; turn on write enable bit
    bcf     INTCON,GIE  ; shut off intr for now
    movlw   H'55'         ; magic sequence required
    movwf   EECON2
    movlw   H'AA'
    movwf   EECON2
    bsf     EECON1,WR   ; toggle on write bit
    bsf     INTCON,GIE  ; allow int again
    bcf     EECON1,WREN ; turn off write bit for safety
    BANK0
	; wait for the write to complete before we return
	btfss   PIR1,EEIF	; wait for interrupt flag to be set
	goto    $-1		 
	; clear interupt bit and write enable bit
	bcf    PIR1,EEIF	;clear eewrite irq flag
	ERRORLEVEL +306, +302
    return
    
;
; ---------------------------------------------------------------------
; init_pwm1
; set up registers associated with PWM
; ---------------------------------------------------------------------
;
init_pwm1

	ERRORLEVEL -306, -302
	BSF     STATUS,RP0      ; OPTION & TRIS & VRCON ARE IN BANK 1

	; pwm period =(pr2+1)*4*Tosc*TMR2prescale
	movlw	d'255'
	movwf	PR2		; bank 2 register

	bcf		TRISB,3	; make sure i/o pin is set as output

	BCF     STATUS,RP0      ; BACK TO LO REGS
	ERRORLEVEL +306, +302	

	movlw	h'4d'	;post scaler=/10, tmr2=on, prescale=16 
	movwf	T2CON   ;19.53khz/4 PWM @ 20 mhz 

	movlw	d'1'
	movwf	CCPR1L  ; 1/255% duty cycle,
                    ; must be < PR2
                    ; = PR2 is 100%

	movlw	h'0f'
	movwf	CCP1CON	; set CCP module to PWM mode

	return

;
; ---------------------------------------------------------------------
; init_cmp1
; set up registers associated with comparators
; internal vref used as common ref, ra0,3 as mux'd inputs
; status of comp read from CMCON register
; ---------------------------------------------------------------------
;
init_cmp1
    clrf    PORTA

    ; setup comparator 1 to read voltage on pin RA0
    ; non inverted
    movlw   b'00000010'    ;internal vref, i/p on ra0-3
    movwf   CMCON
    
	ERRORLEVEL -306, -302
	BSF     STATUS,RP0      ; OPTION & TRIS & VRCON ARE IN BANK 1

    movlw   b'00001111'     ; all 4 comparator inputs
    movwf   TRISA

    ;need to set up internal vref here using VRCON
	movlw	b'10000000'		; vref enabled, vref not on RA2,high range, 1.25v
    movwf	VRCON

	BCF     STATUS,RP0      ; BACK TO LO REGS
	ERRORLEVEL +306, +302	
	return


;
; ---------------------------------------------------------------------
; set_pwm1
; set the PWM rate using w value
; ---------------------------------------------------------------------
;
set_pwm1
;   	set duty cycle
;   	CCP1RL = write upper byte to set duty cycle
;   	CCP1X
;   	CCP1Y  = write 2 lsb to CCP1CON
	movwf	CCPR1L      ; 50% duty cycle,
                    	; must be < PR2
                    	; = PR2 is 100%
	return
    
            
;
;=======================================================================
;   level_0
;   set light output to light level 0 (off)
;=======================================================================
level_0
    clrf    delay1          ; reset 18 second timer
    
    ; code section dealing with delayed off mode
level_0a
	btfss	PIR1,TMR1IF		; check to see if time to do something
	goto	level_0a

	; if the timer is running correctly, we get here 14.4 times/sec
	call	init_timer1		; restart the timer for next time
    
    movlw   255             ; lights on bright
    call    set_pwm1

    movf    delay1,W
    andlw   b'00001111'
    btfss   STATUS,Z
    goto    level_0aa
    
    clrw    
    call    set_pwm1        ; blink lamp slow
    
level_0aa    
    decfsz  delay1,F        ; delay1 used for longer time delays
    goto    level_0a
    
    ; get here after 18 seconds
level_0b
	btfss	PIR1,TMR1IF		; check to see if time to do something
	goto	level_0b

	; if the timer is running correctly, we get here 14.4 times/sec
	call	init_timer1		; restart the timer for next time
    
    movlw   255             ; lights on bright
    call    set_pwm1

    movf    delay1,W
    andlw   b'00000111'
    btfss   STATUS,Z
    goto    level_0bb
    
    clrw    
    call    set_pwm1        ; blink lamp slow
    
level_0bb    
    decfsz  delay1,F        ; delay1 used for longer time delays
    goto    level_0b

    ; get here after 36 seconds
    movlw   128
    movwf   delay1
    
level_0bbb
	btfss	PIR1,TMR1IF		; check to see if time to do something
	goto	level_0bbb

	; if the timer is running correctly, we get here 14.4 times/sec
	call	init_timer1		; restart the timer for next time

    movf    delay1,W
    call    set_pwm1

    decfsz  delay1,F
    goto    level_0bbb    

    clrw
    call    set_pwm1
    
    
    ; code section dealing with lamp is off, but still want low battery alarms
level_0d    
	btfss	PIR1,TMR1IF		; check to see if time to do something
	goto	level_0d

	; if the timer is running correctly, we get here 14.4 times/sec
	call	init_timer1		; restart the timer for next time
    
    decfsz  delay1,F        ; delay1 used for longer time delays
    goto    level_0d

    btfsc   CMCON,6         ; sample comparator 1 output
    call    low_batt        ; handle low battery condition

    goto    level_0d

;
;=======================================================================
;   level_1
;   set light output to light level 1 (dim)
;   check for low battery every 17.8 sec
;=======================================================================
level_1
	btfss	PIR1,TMR1IF		; check to see if time to do something
	goto	level_1

	; if the timer is running correctly, we get here 14.4 times/sec
	call	init_timer1		; restart the timer for next time
    
    movlw   31
    call    set_pwm1

    decfsz  delay1,F        ; delay1 used for longer time delays
    goto    level_1
    
    call    tick_offtimer
    
    btfsc   CMCON,6         ; sample comparator 1 output
    call    low_batt        ; handle low battery condition

    goto    level_1

    
;
;=======================================================================
;   level_2
;   set light output to light level 2 (medium)
;   check for low battery every 17.8 sec
;=======================================================================
level_2
	btfss	PIR1,TMR1IF		; check to see if time to do something
	goto	level_2

	; if the timer is running correctly, we get here 14.4 times/sec
	call	init_timer1		; restart the timer for next time
    
    movlw   127
    call    set_pwm1

    decfsz  delay1,F        ; delay1 used for longer time delays
    goto    level_2
    
    call    tick_offtimer
    
    btfsc   CMCON,6         ; sample comparator 1 output
    call    low_batt        ; handle low battery condition

    goto    level_2
    
;
;=======================================================================
;   level_3
;   set light output to light level 3 (bright)
;   check for low battery every 17.8 sec
;=======================================================================
level_3
	btfss	PIR1,TMR1IF		; check to see if time to do something
	goto	level_3

	; if the timer is running correctly, we get here 14.4 times/sec
	call	init_timer1		; restart the timer for next time
    
    movlw   255
    call    set_pwm1

    decfsz  delay1,F        ; delay1 used for longer time delays
    goto    level_3
    
    call    tick_offtimer
    
    btfsc   CMCON,6         ; sample comparator 1 output
    call    low_batt        ; handle low battery condition

    goto    level_3

;

;
;=======================================================================
; low_batt
; handle low battery condition
;=======================================================================
low_batt
    btfsc   PORTB,7         ; no battery checks done if jumper off
    return

    clrw                    ; blink 1
    call    set_pwm1
    call    wait_short
    movlw   255
    call    set_pwm1
    call    wait_short

    clrw
    call    set_pwm1        ; blink 2
    call    wait_short
    movlw   255
    call    set_pwm1
    call    wait_short
    
    clrw                    ; blink 3
    call    set_pwm1
    call    wait_short
    movlw   255
    call    set_pwm1
    call    wait_short
    
    clrw                    ; exit with leds off
    call    set_pwm1

    return
    

;
;=======================================================================
; wait_short
; delay for a visable amount of time approx 1/14.4 seconds
;=======================================================================
wait_short

	btfss	PIR1,TMR1IF		; check to see if time to do something
	goto	wait_short

	; if the timer is running correctly, we get here 14.4 times/sec
	call	init_timer1		; restart the timer for next time

    return

;
;=======================================================================
; wait_long
; delay for a approx 18 seconds
;=======================================================================
wait_long
    clrf    delay1

wait_l1    
	btfss	PIR1,TMR1IF		; check to see if time to do something
	goto	wait_l1

	; if the timer is running correctly, we get here 14.4 times/sec
	call	init_timer1		; restart the timer for next time

    decfsz  delay1,F        ; delay1 used for longer time delays
    goto    wait_l1

    return

;
;=======================================================================
; init_offtimer
; reads option jumpers and sets up auto off function for 1/2 hour,
; or 2.5 hours.
;=======================================================================
; 101 = 31 minutes of 17.8 second ticks
#define THIRTYMIN 101
init_offtimer
    movlw   1               ; init with 1 so first decr makes 0
    movwf   off_timerh
    movlw   THIRTYMIN       ; 101 = 31 minutes of 17.8 second ticks
    movwf   off_timer

    btfsc   PORTB,6         ; check 2.5 hour jumper
    return

    ; get here if 2.5 hour jumper installed
    movlw   4
    movwf   off_timerh
    
    return
    
;
;=======================================================================
; tick_offtimer
; decrement downcounters and if the get to 0, set the bmode to 0 so that
; light turns off with 45 seconds warning.
; you must call this every 17 seconds when light is on. (mode 1,2,3)
;=======================================================================
tick_offtimer
    btfsc   PORTB,5     ;see if auto off is enabled
    return
    
    decfsz  off_timer,F     ; decr counter
    return
    
    ; get here if counter hit 0 (after half an hour)
    ; reload counter and decr high order counter
    movlw   THIRTYMIN       ; 101 = 31 minutes of 17.8 second ticks
    movwf   off_timer
    decfsz  off_timerh,F
    return
    
    ; get here if both high and low timers went to 0
    clrf    bmode
    clrw
    call    write_eeprom0   ; save new mode
    goto    main            ; restart in new mode
                            ; yes... stack is bogus


	END                     ; directive 'end of program'



