;*******************************************************************
;	8 LED scanning eye (like Knight Rider).
;
;	ver 1.01
;
;	History:
;	ver 1.01 1999-04-06
;		Exponential intensity decay done in ISR
;		Exponential decay when turning off.
;		/Per Magnusson
;	ver 1.00 1999-04-05
;		Things work, features like fixed pattern, two speeds,
;		and LEDs off implemented.
;		/Per Magnusson
;	ver 0.01 1999-04-04
;		Original version.
;		/Per Magnusson
;
;
; Copyright Per Magnusson. You may use and modify this code 
; for any purpose. But don't claim you wrote it yourself unless 
; you have substantially modified it.
; 
;*******************************************************************

	list P=16F84, r=dec		; Select processor

	include <p16f84.inc>

	; Configure processor for:
	;  No code protection
	;  Watch dog timer enable
	__config _CP_OFF & _PWRTE_ON & _WDT_ON & _XT_OSC

	title	"Scanning eye"


fosc = 4000000			; Oscillator frequency in Hz
tmrval = 255 - (fosc/(4*128*90)) ; Interrupt timer set value 
dlyval1 = 8			; Update delay for low speed
dlyval2	= 4			; Update delay for high speed


;*******************************************************************
;	Register usage
;
	cblock	0ch

	reg0		; Intensity register for LED 0
	reg1		; Intensity register for LED 1
	reg2		; Intensity register for LED 2 
	reg3		; Intensity register for LED 3
	reg4		; Intensity register for LED 4
	reg5		; Intensity register for LED 5
	reg6		; Intensity register for LED 6
	reg7		; Intensity register for LED 7
	dir		; 1 = scan up, 0 = scan down
	pwm_ramp	; Used by PWM interrupt routine
	cnt0		; LSB of interrupt counter
	cnt1		; MSB of interrupt counter
			; The interrupt counter is incremented each
			; time the ISR is executed. It is used by the
			; main routine to keep track of the time.
			; The main routine resets this byte.
	cnt1b		; MSB of extra interrupt counter
			; The extra interrupt counter is used by the 
			; ISR. It is not reset by the main routine.
	w_save		; Used by ISR
	status_save	; Used by ISR
	endc


;*******************************************************************
;	Bit definitions
;

; Pins in PORTA
STILL	equ	0	; 1 -> generate fixed pattern, 0 -> scan 
SPEED	equ	1	; 1 -> lower speed
OFF	equ	2	; 1 -> turn LEDs off


; Pins in PORTB
; Connect LEDs between PORTB pins and resistor to positive supply
; Pins are active low PWM modulated
LED0	equ	0		
LED1	equ	1		
LED2	equ	2		
LED3	equ	3		
LED4	equ	4		
LED5	equ	5		
LED6	equ	6		
LED7	equ	7		

;*******************************************************************
;	Macros
;


; Name:	      	page0
; Function:   	Selects page 0
; Parameters: 	-
; Alters:     	-
page0	macro
	bcf	STATUS,RP0
	endm


; Name:	       	page1
; Function:   	Selects page 1
; Parameters: 	-
; Alters:     	-
page1	macro
	bsf	STATUS,RP0
	endm


; Name:		skip
; Function:	always skip next instruction
skip	macro
	btfsc	PCLATH,7	; bit 7 of PCLATH is not implemented and reads as 0
				; it exists in both memory banks
	endm


; Name:		skipneg
; Function:	skip next instruction if result is negative (carry = 0)
skpneg	macro
	btfsc	STATUS,0	; bit 0 of STATUS is carry
	endm


; Name:		skippos
; Function:	skip next instruction if result is positive or 0 (carry = 1)
skppos	macro
	btfss	STATUS,0	; bit 0 of STATUS is carry
	endm


; Name:		pwmtest
; Function:	Test one pwm channel and set it low if it is less than
;		pwm_ramp
; Parameters:	channel number (0-7)
; Alters:	W, PORTB
pwmtest	macro	n
	movf	pwm_ramp,W		; Calculate reg#n - pwm_ramp
	subwf	reg#v(n),W		; "
	skpneg
	 bcf	PORTB,LED#v(n)		; pwm_ramp < reg, set LED on
	skppos
	 bsf	PORTB,LED#v(n)		; pwm_ramp >= reg, set LED off
	endm


; Name:		decr_int
; Function:	Decrease intensity of LED n
; Parameters:	n (LED number)
; Alters:	W, w_save, status_save
decr_int macro n
	clrc
	rrf	reg#v(n),F		; Decrease by 0.75
	clrc				; "
	rrf	reg#v(n),W		; "
	addwf	reg#v(n),F		; "
	endm


; Name:		push
; Function:	Save W and status registers at beginning of ISR
; Parameters:	-
; Alters:	W, w_save, status_save
push	macro
	movwf	w_save
	swapf	STATUS,W
	movwf	status_save
	endm


; Name:		pop
; Function:	Restore W and status registers after ISR
; Parameters:	-
; Alters:	W, STATUS
pop	macro
	swapf	status_save,W
	movwf	STATUS
	swapf	w_save,F
	swapf	w_save,W
	endm


;**********************************************************************
;	Code
;

	goto	init		; 16F84 reset vector
	

;**********************************************************************
; Interrupt routine that performs the PWM pulsing of the LED outputs
; to set their intensity.
;
; This routine may not take longer than 255-tmrval inctruction cycles
; to execute.

	org	0x4		; interrupt vector
interrupt
	push
	bcf	INTCON,T0IF	; clear timer interrupt flag
	movlw	tmrval		; reset timer
	movwf	TMR0		; "
	clrwdt
	incf	pwm_ramp,F	; Increment ramp
	incf	pwm_ramp,F	; (2 times)
	pwmtest	0		; Test and set all PWM channels
	pwmtest	1
	pwmtest	2
	pwmtest	3
	pwmtest	4
	pwmtest	5
	pwmtest	6
	pwmtest	7

	movlw	2		; Increment cnt0 by 2
	addwf	cnt0,F		; "
	skpz	
	 goto	isr_end
	; We come here every 128 calls to the ISR
	incf	cnt1,F		; Increment high byte of counter
	incf	cnt1b,F		; Increment high byte of extra counter
	btfss	cnt1b,0		; Test bit 2
	 goto	isr_end
	; We come here every 256 calls to the ISR
	call	decr_intensity	; Exponential intensity decay
	; The call to decr_intensity takes a long time, so we will
	; probably miss the next timer wrap around. However, the wrap 
	; will set the T0IF flag anyway, so we will get a new interrupt 
	; immediately after retfie. Thus the average interrupt
	; frequency will not be affected.
isr_end
	pop
	retfie
	

;********************************************************************
;	Main program
;

init
	page1
	 movlw	0	
	 movwf	TRISB		; Set all pins in PORTB as outputs
	 movlw	(1<<STILL) | (1<<SPEED) | (1<<OFF)
	 movwf	TRISA		; Set some pins of PORTA as inputs

	 clrwdt
	 ; NOT_RBPU, INTEDG, T0CS, TOSE, PSA, PS2, PS1, PS0
	 movlw	(1<<NOT_RBPU) | (1<<PSA)	; prescaler -> wdt, no pullup, prescale=1:1
	 movwf	OPTION_REG	; "
	page0

	clrf	reg0		; Clear all PWM timers
	clrf	reg1
	clrf	reg2
	clrf	reg3
	clrf	reg4
	clrf	reg5
	clrf	reg6
	clrf	reg7
	movlw	1		; Set dir = 1 (up)
	movwf	dir		
	movlw	reg0		; Initialize FSR
	movwf	FSR		; "
	clrf	cnt0		; Initialize cnt0
	clrf	cnt1		; Initialize cnt1
	clrf	cnt1b		; Initialize cnt1b

	movlw	1		; Initialize pwm_ramp
	movwf	pwm_ramp	; "
	movlw	tmrval		; Set timer
	movwf	TMR0		; "

	movlw	(1<<GIE) | (1<<T0IE)	; global IRQ en. and timer IRQ en.
	movwf	INTCON

main_loop
	btfsc	PORTA,OFF	; Test if we should turn off all LEDs
	 goto	main_loop	; Yes, just wait and let ISR decay LEDs
	btfsc	PORTA,STILL	; Test if we should generate fixed pattern
	 goto	leds_still	; Yes, don't update

	call	wait		; Wait until it is time to change
				; bright LED. Also set bright LED to
				; full intensity.	
	; Update which LED should be bright
	btfss	dir,0		; dir==up?
	 goto	down		; no
	; yes, dir==up
	movlw	reg7		; calculate fsr - reg7
	subwf	FSR,W		; "
	skpnz			; result = 0?
	 goto	updec		; yes
	incf	FSR,F		; no, continue to increase FSR
	goto	main_loop
updec
	decf	FSR,F		; decrement FSR instead
	bcf	dir,0		; dir = down
	goto	main_loop
down
	movlw	reg0		; calculate fsr - reg0
	subwf	FSR,W		; "
	skpnz			; result = 0?
	 goto	dninc		; yes
	decf	FSR,F		; no, continue to decrease FSR
	goto	main_loop
dninc
	incf	FSR,F		; increment FSR instead
	bsf	dir,0		; dir = up
	goto	main_loop
	

leds_off			; Turn all LEDs off
	clrf	reg0		; Clear all PWM timers
	clrf	reg1
	clrf	reg2
	clrf	reg3
	clrf	reg4
	clrf	reg5
	clrf	reg6
	clrf	reg7
leds_off2
	btfsc	PORTA,OFF	; Still clear?
	 goto	leds_off2	; Yes
				; No, continue
	clrf	cnt1		; Clear delay counter
	goto	main_loop	


leds_still			; Generate fixed pattern
	btfsc	PORTA,STILL	; Test if we should generate fixed pattern
	 goto	leds_still	; Yes, don't update
				; No, continue
	clrf	cnt1		; Clear delay counter
	goto	main_loop	



;**********************************
; Subroutines

;**********************************
; Name:		decr_intensity
; Function:	Decrease the intensity of all pwm registers
decr_intensity
	decr_int 0
	decr_int 1
	decr_int 2
	decr_int 3
	decr_int 4
	decr_int 5
	decr_int 6
	decr_int 7
	return


;**********************************
; Name:		wait
; Function:	Wait until cnt1 is big enough (depending on SPEED bit)
wait
	movlw	0xff		; Set (FSR) to full intensity
	movwf	INDF		; so that the decay will not affect
				; the brightest LED
	movf	cnt1,W		; Has cnt1 reached dlyvalX?
	btfss	PORTA,SPEED	; ", test speed bit
	 goto	wait_speed2	; ", use dlyval2
	sublw	dlyval1		; ", use dlyval1
	goto	wait_test	; "
wait_speed2
	sublw	dlyval2		; "
wait_test
	skpneg			; "
	 goto	wait		; No, continue to wait
	clrf	cnt1		; Clear cnt1
	return			; No more waiting

	END


