;
;	Zuki eye animation
;
; Eye blink circuit for fursuit with LED eyes.
;
; (c) 2007, 2008 David Cooke.
;
; This source code is licensed under a Creative Commons Attribution
; Share Alike Licence.
;
; You are free:
;	* to copy, distribute, and perform the work
;	* to make derivative works
;
; Under the following conditions:
;	* Attribution.  You must give the original author credit.
;	* Share alike.  If you alter, transform, or build upon this
;	  work, you may distribute the resulting work only under a licence
;	  identical to this one.
;
; * For any reuse or distribution, you must make clear to others the
;   licence terms of this work.
; * Any of these conditions can be waived if you get permission from
;   the copyright holder.
; * Nothing in this license impairs or restricts the author's moral rights.
;
; For more information see: http://creativecommons.org/licenses/by-sa/2.0/uk/
;

	list	p=12f683

#include p12f683.inc

;
; Device configuration
;

	; Fail safe clock monitor disabled (no external clock anyway)
	; Internal/external switch over disabled
	; Internal oscillator with clock pins free for I/O
	;
#define	clk_cfg		_FCMEN_OFF & _IESO_OFF & _INTOSCIO

	; Code protect off
	; Data protect off
	;
#define	prot_cfg	_CPD_OFF & _CP_OFF

	; Brown out detect except when sleeping
	; Watchdog timer off
	; Master clear off
	; Power-up timer enabled
	;
#define	rst_cfg		_BOD_NSLEEP & _WDT_OFF & _MCLRE_OFF & _PWRTE_ON

	__CONFIG	clk_cfg & prot_cfg & rst_cfg

;
; Timer configuration
;
; Timer 1:
;	clk = 1MHz
;	prescale = 1:256
;	count = 256
;	period = 65.536ms
;
; Timer 2: PWM mode
;	period = (PR2 + 1) * 4 * TOsc * TMR2prescale
;		= 256 * 4 * 250ns * 16
;		= 4.096ms (244Hz)
;



; -----------------------------------------------------------------------------
; I/O bits
; -----------------------------------------------------------------------------

#define	debug	GPIO,0	; Debug output

; -----------------------------------------------------------------------------
; RAM usage
; -----------------------------------------------------------------------------
	cblock	020h

	temp		; For tight loops

	; Base animation variables
	;
	basestyle	; The basic style (0: solid, 1: throb)
	basecount	; The basic style counter
	onindex		; Index value into cos table
	onvalue		; Value to use for eyes lit

	; Short animation variables
	;
	anistyle	; The animation style being displayed (0: none)
	anistate	; The state within the animation style
	anidelay	; The delay within the animation style
	nextani		; The next animation to display
	nextdelay	; Time until the next style in quarter seconds

	; Random number generator
	;
	randlo		; Low byte of random number generator
	randmi		; Middle byte of random number generator
	randhi		; High byte of random number generator
	randcy		; Carry bit of random number generator
	randct		; Random number generator count

	endc


	;
	; These general purpose registers are in the same location for each
	; register page.  This is useful for the interrupt handler storage
	; which can be invoked with the page select bits in any state.
	;
	cblock	0x70

	intW		; Working register 
	intS		; Status register
	intP		; PC Latch High order bits

	endc



; -----------------------------------------------------------------------------
; Program code
; -----------------------------------------------------------------------------

	org	000h
	banksel	GPIO
	clrf	GPIO	; Set outputs low before enabling drivers.
	goto	Start

	; Interrupt entry point.
	;
	; NOTE: Swapf has to be used in place of movf as movf conditionally
	; sets the zero flag of the status register.
	;
	org	0x0004
Int	
	movwf	intW		; Save working register
	swapf	STATUS, w	; Status:0-3 -> w:7-4, s:7-4 -> w:0-3
	clrf	STATUS		; Clear the status register, access page 0
	movwf	intS		; Save the nibble swapped status register
	movf	PCLATH, w	; Move the PC High Latch register value
	movwf	intP		; Save the PC High Latch

	bsf	debug		; Set the interrupt debug bit

	; Check for timer0 interrupt
	;
	btfsc	INTCON,T0IF
	goto	Tick
TickRet
	;
	; Restore registers
	;
IntRet
	bcf	debug		; Clear the interrupt debug bit

	movf	intP, w		; Fetch the old PC High Latch value
	movwf	PCLATH		; Restore it
	swapf	intS, w		; Fetch the status register value and unswap
	movwf	STATUS		; Restore the flags and register pages
	swapf	intW, f		; Swap the nibbles in the saved version of W
	swapf	intW, w		; Restore the working register
	retfie			; Return from interrupt, and enable interrupts
	


	; ---------------------------------------------------------------------
	; Timer 0 interrupt handling
	; ---------------------------------------------------------------------
Tick	;
	; Prepare for the next interrupt.
	;
	bcf	INTCON,T0IF

	; Determine which style is operative.  An animation style is a very
	; short animation such as a blink.
	;
	tstf	anistyle
	bnz	Animate

	; For base styles, check for an update every 4 ticks, or ~1/4 sec.
	;
	incf	basecount,f
	movlw	0x04
	subwf	basecount,w
	bnc	TickRet
	clrf	basecount

	; Increment the on index.  When this is zero the base style can
	; change.
	;
	incf	onindex,f
	bnz	TickB1

	; Generate a random bit, and use it to set the base style.
	;
	movlw	0x01
	call	random
	andlw	0x01
	movwf	basestyle

TickB1	; Solid or throbing?
	;
	tstf	basestyle
	bnz	TickB2
	movlw	0xff
	goto	TickB3

TickB2	; Lookup the new on value.
	;
	movfw	onindex
	call	cos

TickB3	; Set the PWM value and record it.
	;
	movwf	onvalue
	movwf	CCPR1L

	; Test to see if a new animation is due.
	;
	decf	nextdelay,f
	bnz	TickRet

	; Initialise the animation state.
	;
	clrf	anistate
	clrf	anidelay
	movf	nextani,w
	movwf	anistyle
	goto	TickRet

Animate	; Determine which animation applies.
	; 	0: no animation
	;	1: double blink
	;	2+: blink
	;
	decf	anistyle,w
	bnz	Blink
	goto	DBlink

Blink	; Perform blink animation.
	;
	tstf	anistate
	bz	Blink1
	decf	anidelay,f	; Decrement the animation timer
	bnz	TickRet
	goto	AniEnd

	; Set eyes shut.
	;
Blink1	clrf	CCPR1L
	movlw	0x05
Blinke	movwf	anidelay	; Set delay to 1/3rd sec
	incf	anistate,f	; Move on to next state
	goto	TickRet

	; Perform double blink animation
	;
DBlink	movf	anistate,w
	bz	DBlink1
	decf	anidelay,f
	bnz	TickRet
	;
	; Choose the animation state.
	;
	movwf	temp
	decf	temp,f
	bz	DBlink2
	decf	temp,f
	bz	DBlink1
	goto	AniEnd

	; Set eyes shut
	;
DBlink1	clrf	CCPR1L
	movlw	0x05
	goto	Blinke

	; Set eyes open again
	;
DBlink2	movf	onvalue,w
	movwf	CCPR1L
	movlw	0x05
	goto	Blinke

	; End animation cycle
	;
AniEnd	clrf	anistyle
	movf	onvalue,w
	movwf	CCPR1L

	; Choose the next style
	;
	movlw	0x02
	call	random
	andlw	0x03
	addlw	0x01
	movwf	nextani

	; Choose the delay until the next style.
	; This results in a delay from 3 sec to 19 sec.
	;
AniEnd2	movlw	0x06
	call	random
	andlw	0x3f
	movwf	temp
	movlw	0x06
	call	random
	andlw	0x3f
	addwf	temp,w
	addlw	0x0c
	movwf	nextdelay

	goto	TickRet



; -----------------------------------------------------------------------------
; -- Subroutines
; -----------------------------------------------------------------------------

	;
	; Random number generator routine
	;
	; On entry, w should countain the number of bits to generate.
	; The result can be read from randlo onwards, randlo is returned
	; in w.
	;
random	movwf	randct		; Store the number of iterations to perform.

	;
	; These next eight instructions are adapted from an EDN Design Idea
	; "Single IC form pseudorandom-noise source", EDN, March 21, 2002, pg 98
	;
rndbit	rlf	randcy,f	; Perform shift
	rlf	randlo,f
	rlf	randmi,f
	rlf	randhi,f
	movlw	0x1a		; Polynomial
	skpnc			; Test carry
	xorwf	randlo,f	; XOR if set
	rrf	randcy,f	; Preserve carry bit

	decfsz	randct,f	; Loop around for each bit needed.
	goto	rndbit

	movfw	randlo
	return


	;
	; Lookup cos curve values.
	;
	; On entry, w should be the offset into the table.
	; The result is returned in W.
	;
cos
	movwf	temp
	btfsc	temp,5		; Test bit 5 and complement if set
	comf	temp,f		; - this folds the table
	movlw	0x1f
	andwf	temp,w		; Limit to 5 bit index
	addwf	PCL,f		; Branch into data table and return.
costab
	dt	0xff, 0xfd, 0xfa, 0xf6
	dt	0xef, 0xe8, 0xe0, 0xd7
	dt	0xcd, 0xc4, 0xba, 0xb1
	dt	0xa8, 0xa0, 0x98, 0x90
	dt	0x89, 0x82, 0x7c, 0x76
	dt	0x71, 0x6c, 0x68, 0x64
	dt	0x60, 0x5e, 0x5b, 0x59
	dt	0x58, 0x56, 0x56, 0x55

	if ((high($)) != (high(costab)))
	    error "Cos table crosses page boundary"
	endif


; -----------------------------------------------------------------------------

Start	;
	; Initialise device
	;

	;
	; Set the oscillator configuration
	;	Internal oscillator is 4Mhz (default)
	;
	banksel	OSCCON
	movlw	0x60
	movwf	OSCCON

	;
	; Configure 0,1,2 as digital outputs, 3 as digital input.  Turn
	; off the comparator module.
	;
	banksel	ANSEL
	clrf	ANSEL		; (Bank 1) Analogue inputs off

	banksel	CMCON0
	movlw	0x07		; Comparator off, lowest power mode
	movwf	CMCON0		; (Bank 0)

	banksel	TRISIO
	movlw	0x28		; GP2, GP1, GP0 as outputs, others as inputs.
	movwf	TRISIO		; (Bank 1)

	banksel	WPU
	movlw	0x30		; Weak pull ups on GP5, GP4
	movwf	WPU		; (Bank 1)

	;
	; Set up PWM mode to control CCP1 pin (GP2)
	; Uses:
	;	TMR2
	;	CCPR1L	duty cycle
	;	PR2	period
	;	CCP1CON	control

	banksel	PR2
	movlw	0xff		; Period of 256 ticks	
	movwf	PR2		; (Bank 1)

	banksel	CCPR1L
	movlw	0x80		; Duty cycle of 50%
	movwf	CCPR1L		; (Bank 0)

	banksel	T2CON
	movlw	0x06		; Timer 2 enabled, prescale 16, postscale 1
	movwf	T2CON		; (Bank 0)

	banksel	CCP1CON
	movlw	0x0c		; PWM mode, active high
	movwf	CCP1CON		; (Bank 0)

	;
	; Set up timer 0 for 15th sec interrupts
	; - Pull ups enabled
	; - Interrupt on fallin edge on INT (not used)
	; - Timer 0 source is internal clock (1MHz), rising edge
	; - Timer 0 has prescaler (not WDT), divide by 256
	;
	banksel	OPTION_REG
	movlw	0x05
	movwf	OPTION_REG	; (Bank 1)

	banksel	TMR0
	clrf	TMR0		; (Bank 0)

	;
	; Program state initialisation
	;
	movlw	0xff		; Seed the random number generator.  It won't
	movwf	randhi		; function from an all-zero starting state.

	clrf	anistyle	; No animation.
	clrf	basestyle	; Solid on.
	incf	basestyle	; Throb

	; Choose the delay until the first animation style.
	; This results in a delay from 3 sec to 19 sec.
	;
	movlw	0x06
	call	random
	andlw	0x3f
	addlw	0x0c
	movwf	nextdelay

	;
	; Enable interrupts
	;
	bsf	INTCON,T0IE
	bsf	INTCON,GIE

Loop	;
	; Main loop
	;


	;
	; The end of the world.  Don't fall off.
	;
DedEnd
	goto	DedEnd

	end

