	list	n=96
;**********************************************************************
;
;	Filename:	dice.asm
;	Date:		29 July 2002
;	File Version:	01.00  (set in ID locs)
;
	__IDLOCS 0x0100
;
;	Author:		Steve Russell
;	Company:	
;
;	Copyright:	(C) Steve Russell 2002
;			This code is available at no charge to individuals for
;			personal, non-profit, use, and may be used as is or
;			modifed as desired. Further distribution of this code
;			or a derivative work is allowed as long as this
;			copyright notice is not removed or modified. Changes
;			to the original code should be clearly identified.
;			Any commercial use of this code, or a derivative of it,
;			may only be made with the written agreement of the
;			copyright holder.
;
;**********************************************************************
;
;	Files required: PIC12C508A.INC
;
;**********************************************************************
;
;	DESCRIPTION
;
;	This program creates a number of electronic dice. The dice are 
;	displayed using seven LEDs driven by the 12C508A I/O ports, arranged
;	in the following way:
;
;		2		5	GP0 drives LED 1
;					GP1 drives LEDs 2&3
;		6	1	7	GP2 drives LEDs 4&5
;					GP4 drives LEDs 6&7
;		4		3
;
; 	A logic low on an output illuminates the LEDs driven by that
;	output. A single push-to-make SPST switch is the only input
;	and is connected to GP3. Up to seven simulated dice can be 
;	rolled at one time (see USER OPERATION).
;
;	All timings are calculated assuming a 4MHz clock, so 1 cycle takes 1us.
;	The configuration fuses are set to use the internal oscillator as more
;	accurate timing is not required.
;
;	USER OPERATION
;
;	Normal mode (assume the device is in sleep mode, with all LEDs off)
;	* Pressing the switch wakes up the device and it displays the dice 
;	  values from the previous roll.
;	* Releasing the switch puts the device into the normal, powered-up,
;	  state where it loops showing the dice values for the last roll. 
;	* Pressing the switch in this normal mode rolls the dice. The number
;	  of dice thrown is selected in setup mode.
;	* When power is first applied, two dice are selected, both showing 1.
;
;	Setup Mode
;	* Holding the switch down for more than about 2s puts the device in
;	  setup mode, in which you select the number of dice (1-7).
;	* Each press of the switch in setup mode changes the number of dice
;	  to roll, cycling through 1-7. The LEDs show how many dice will be 
;	  rolled by flashing that number on the LEDs.
;	* Releasing the switch for more than about 2 seconds exits setup mode
;	  and rolls the current number of dice.
;
;	Sleep mode/shutdown
;	* If the switch is not pressed and doesn't change state for five
;	  display cycles, the device goes to sleep.
;
;	DISPLAY
;
;	When displaying the result of a throw, the LEDs fade up from off to
;	show each die consecutively. After all dice have been displayed, the 
;	display flashes between 2 (\) and reverse 2 (/) for about 1s before
;	starting to display the dice again. This continues until the switch
;	is pressed to roll the dice, or the dice have been displayed five 
;	times, and the device goes to sleep with the LEDs off.
;
;	While the dice are rolling, the display flashes between 3 (\) and 
;	reverse 3 (/) for about 1s before starting to display the dice again.
;	The roll actually takes place very quickly, but the display is held 
;	flashing for a while to simulate a real roll.
;
	page
;**********************************************************************
; ASSEMBLER DIRECTIVES
;**********************************************************************
	list	p=12c508a, r=dec
	#include <p12c508a.inc>

	__CONFIG _CP_OFF & _WDT_OFF & _MCLRE_OFF & _IntRC_OSC 

; 	IDLOCS set above (see File version) 


;**********************************************************************
; MACROS
;**********************************************************************
mSET_ONTIME	macro	val
  if val == 0
	clrf	ontime
  else
	movlw	val
	movwf	ontime
  endif
	endm
	
;**********************************************************************
; CONSTANTS
;**********************************************************************
SETIO		equ	0x28	; --101000 GP0-2,4 o/p, GP3,5 i/p
SWITCH		equ	3	; GP3 bit for switch 1=up, 0=down

; Options: Enable wakeup on pin change, internal pullups, internal timer
;          and assign prescaler to timer, set to 1:128.
SETOPT		equ	0x06

I_NUMDICE	equ	2	; Initial number of dice
MAXDICE		equ	7	; Maximum number of dice allowed

; ontime determines at what time during the 16ms inner loop the LEDs go on
; Initial ontime=0x80 so LEDs are off for first 16 ms
; Important: 	ontime must be +ve after subwf in not2syet to avoid moving
; 		to the next die at the following test
I_ONTIME	equ	16*8	
DELTA_ONTM	equ	2	; Ontime change each time round loop (0.25ms)
ONTMFLSH	equ	12*8	; Ontime value for flash (about 12ms)

FLSHTIME	equ	0x07	; Low order 3 bits (mask for multiples of 8)
FLSHDONE	equ	6	; Done with flashing when bit6 of flashtmr set
				; ...about 1s
DEBTIME		equ	10*8	; Debounce time in ms*8 (TMR0 clock is 128us)

SETTIME		equ	7	; Bit7 of setuptmr indicates about 2s elapsed
SLEEPCNT	equ	5	; Device goes to sleep after this many display
				; cycles without a switch press
; Display values for LEDs
DISP_1		equ	0xfe	; DISP_x used by GetGPIO to return
DISP_2		equ	0xfd	; corresponding port value for die value
DISP_3		equ	0xfc
DISP_4		equ	0xf9
DISP_5		equ	0xf8
DISP_6		equ	0xe9
DISP_7		equ	0xe8
LEDOFF		equ	0xff	; GPIO port value to turn LEDs off
FLSHROLL	equ	DISP_3	; Start with 3 when flashing a roll
FLSHNORM	equ	DISP_2	; Start with 2 when flashing end of display
FLSHTOGL	equ	0x06	; Mask to toggle display \ / \ / etc...


;**********************************************************************
; STATUS BITS for dicestat
;**********************************************************************
S_ROLL		equ	7	; Rolling the dice?	1=yes, 0=no
S_PRESS 	equ	6	; Switch pressed	1=pressed, 0=released
S_DBNCE		equ	5	; Debounce in process	1=debounce, 0=normal
S_SETUP		equ	4	; Setup mode		1=setup, 0=normal
S_FLASH		equ	3	; Need to flash LEDs	1=flash, 0=normal
				; - used at end of display sequence, rolling.
S_NU2		equ	2	; Not used
S_NU1		equ	1	; Not used
S_NU0		equ	0	; Not used

	page
;**********************************************************************
; VARIABLES
;**********************************************************************
	CBLOCK 0x07
dicestat			; Status byte (see bit values above)

current				; Current switch state read from GPIO
dbncstrt			; Start time for debounce

dice:0				; The seven dice
  die1,die2,die3,die4,die5,die6,die7

numdice				; Current number of dice
dispdie				; Dice currently being displayed (1-7)
rollctr				; Loop counter for dice currently being rolled
dispctr				; Count display cycles - sleep after SLEEPCNT

feedback			; Feedback bit for shift reg
shiftreg:0			; Random number shift reg
  sr_hi				; - high byte
  sr_lo				; - low byte

ontime				; Defines when (during 16ms cycle) LEDs are on
ledval				; Value to write to port to display die value

setuptmr			; Timer for entering/leaving setup mode
flashtmr			; Timer for LED sequence at end of display/roll

saveFSR				; To save FSR while calling lfsr to randomise
dummydie			; Somewhere to point FSR at while calling lfsr
	ENDC

	page
;**********************************************************************
; Internal RC calibration value is placed at location 0x1FF by Microchip
; as a movlw k, where the k is a literal value. Not required for OTP chips
	ORG     0x1FF		; processor reset vector
;	movlw	0x??		; VALUE FOR EPROM PIC12C508A.  
				; Uncomment and enter calibration value
				; ...before programming

        ORG     0x00		; Coding begins here
        movwf   OSCCAL		; Set factory cal value
	goto	init
	
;**********************************************************************
; SETDICE
;
;	Set the dice pointer and the counters for roll and display
;	
;	Cycle count: 9 (inc 2 for call)
;
;**********************************************************************
setdice
	movlw	dice		; Get address of first dice
	movwf	FSR		; ...and set indirect addressing reg
	movf	numdice,w	; Get the number of dice we're rolling
	movwf	rollctr		; ...and save in the roll counter
	movwf	dispdie		; ...and dice being displayed
	retlw	0		; 7 cycles total (+2 for call)

;**********************************************************************
; GETGPIO
;
;	Get value needed to display dice throw
;
;	Lookup table for driving LEDs
;
;		2		5	GP0 drives LED 1
;					GP1 drives LEDs 2&3
;		6	1	7	GP2 drives LEDs 4&5
;					GP4 drives LEDs 6&7
;		4		3
;
; 	A logic low on an output illuminates the LEDs driven by that
;	output. 
;
;	INPUT:	W contains the die value on entry.
;
;	OUTPUT:	W contains the corresponding GPIO value on exit.
;
;	Die variable	Die displayed	GPIO
;	0		1		11111110
;	1		2		11111101
;	2		3		11111100
;	3		4		11111001
;	4		5		11111000
;	5		6		11101001
;	6		7		11101000 (used for setup mode)
;
;	Cycle count: 6 (inc 2 for call)
;
;**********************************************************************
getgpio	addwf	PCL,f
	dt	DISP_1,DISP_2,DISP_3,DISP_4,DISP_5,DISP_6,DISP_7

	page
;**********************************************************************
; SWITCH
;
; 	Test for a change in the switch status. One of two things needs to
;	happen if the switch is pressed:
;	* In normal mode (S_SETUP=0), the user wants to roll the dice.
;	* In setup mode (S_SETUP=1), the user is changing the number of dice
;
;	In both cases, we're just going round the inner loop and we have to 
;	test for a switch change. If a change is detected, we start the 
;	debounce timer. After debounce is complete, the routine handles
;	the change in the switch state. There are four cases:
;	* Switch unchanged, Pressed->Pressed (P>P)
;	  - primarily to handle debounce during wake from sleep (P>P), but 
;	    also avoids problems from unexpected glitches. No action.
;	* Switch unchanged, Released->Released (R>R)
;	  - avoids problems from unexpected glitches. No action.
;	* Switch changed, Released->Pressed (R>P)
;	  - roll dice (normal mode), change number of dice (setup mode).
;	* Switch changed, Pressed->Released (P>R)
;	  - no action.
;	
;	Cycle counts (inc 2 for call):
;	1. Just looping through, no switch change		13 cycles
;	2. Switch change, start debounce			20 cycles
;	3. Debouncing, switch hasn't bounced			20 cycles
;	4. Debouncing, switch changed				17 cycles
;	5. Debounced, switch unchanged (P>P, R>R)		26 cycles
;	5. Debounced, switch released (P>R)			29 cycles
;	6. Debounced, inc numdice in setup (no wrap)		38 cycles
;	7. Debounced, inc numdice in setup (wrap)		41 cycles
;	8. Debounced, press (R>P) to roll			48 cycles
;
;**********************************************************************
switch
	btfsc	dicestat,S_DBNCE ; [1/2] Have we started debounce?
	goto	debounce	; [3] YES: Test if debounce complete

; NO: Not debouncing, check if switch has changed
	clrf	current		; [3] Assume switch is not pressed
	btfss	GPIO,SWITCH	; [4/5] Test current switch state(1=up, 0=down)
	bsf	current,S_PRESS ; [5] Set to pressed if necessary

; S_PRESS in current = 0 if switch up, 1 if pressed, see if state has changed
	movf	dicestat,w	; [6] Get status byte
	andlw	1<<S_PRESS	; [7] ...and isolate switch status
	xorwf	current,w	; [8] Test saved and read states for equality
	btfsc	STATUS,Z	; [9/10] If result 0, saved state=read state
	retlw	0		; [11] ...so no change, return

; Switch transition detected, start debounce timing 
	bsf	dicestat,S_DBNCE ; [11] Set the debounce flag
	goto	gettime		; [13a] ...and get the time for debounce

debounce
	movf	current,w	; [4] Get last read state
	clrf	current		; [5] Assume switch is not pressed
	btfss	GPIO,SWITCH	; [6/7] Test current switch state(1=up, 0=down)
	bsf	current,S_PRESS ; [7] Set to pressed if necessary
	xorwf	current,w	; [8] w=current^previous = 0 if the same
	btfsc	STATUS,Z	; [9/10b] If result not 0, switch changed again
	goto	nobounce	; [11] ...=0 so no bounce occurred this time

; Switch transition detected: clear the setup timer and get debounce start time
gettime
	clrf	setuptmr	; [14a][11b] Switch changed, clear setup timer
	movf	TMR0,w		; [15a][12b] Get timer value
	movwf	dbncstrt	; [16a][13b] ...and save for later
	retlw	0		; [18a][15b]

	page
;***********
; Switch hasn't changed since last transition, see if debounce time has elapsed
nobounce	
	movlw	DEBTIME		; [12] Get debounce time
	addwf	dbncstrt,w	; [13] w=debounce start time+debounce time
	andlw	0x7f		; [14] Handle timer wrap at 128
	subwf	TMR0,w		; [15] Subtract timer value
	btfss	STATUS,Z	; [16/17] If 0, switch not changed for DEBTIME
	retlw	0		; [18] Not debounced: return

;*********
; We have debounced, so set flags
	bcf	dicestat,S_DBNCE ; [18] Clear the debounce flag
	movf	dicestat,w	; [19] Get status byte
	andlw	1<<S_PRESS	; [20] Isolate previous switch status
	xorwf	current,w	; [21] Test if status has changed
	btfsc	STATUS,Z	; [22/23] Has status changed? (Z=0)
	retlw	0		; [24] NO: Return (P>P, or R>R)

	bcf	dicestat,S_PRESS ; [24] YES: Assume switch not pressed
	btfss	current,S_PRESS	; [25/26] Test last read value
	retlw	0		; [27] Return if switch released (P>R)

;**********
; Switch was off and has been pressed - handle it
	bsf	dicestat,S_PRESS ; [27] Set the status flag
	btfsc	dicestat,S_SETUP ; [28/29] Test if in setup mode
	goto	setup		; [30] YES: Go to setup code
;***********
; NO: Switch pressed in normal mode
;***********
; NOTE: This entry point is also called as a subroutine when leaving setup mode
setroll
	bsf	dicestat,S_ROLL	; [30] Set roll flag
	bsf	dicestat,S_FLASH ; [31] Flash the LEDs so not "instant" roll
	clrf	flashtmr	; [32] Clear the timer for the flash sequence
	movlw	FLSHROLL	; [33] Get initial flash LED pattern
	movwf	ledval		; [34] ...and put it in ledval for the loop
	mSET_ONTIME ONTMFLSH	; [35] Set ontime for flash	
	call	setdice		; [44] Make sure pointers correct for the roll
	retlw	0		; [46] ...and return

;*******
; Switch pressed in setup mode
setup
	incf	numdice,f	; [31] Increment the number of dice
	movlw	MAXDICE+1	; [32] Test if numdice>MAXDICE
	subwf	numdice,w	; [33] ...by subtracting
	btfss	STATUS,Z	; [34/35] If Z flag clear...
	retlw	0		; [36] ...we're OK, so return
; numdice has overflowed
	movlw	1		; [36] Otherwise, set numdice to 1
	movwf	numdice		; [37]
	retlw	0		; [39] ...and return

	page
;**********************************************************************
; LFSR
;
;	This subroutine uses a 16-bit linear feedback shift register to
;	generate a pseudo-random sequence of bits. The feedback has been
;	selected to give a maximal length sequence.
;
;	INPUT:	FSR points to a register which will receive the next
;		random bit. 
;
;	OUTPUT:	The register pointed to by FSR is shifted left. Bit7
;		is shifted into the C flag, bit0 receives the new bit.
;		Effect on shiftreg:
;		new bit15 = old bit15 XOR old bit14 XOR old bit12 XOR oldbit3
;		new bit14:0 = old bit15:1
;
;	Cycle count: 18 (inc 2 for call)
;
;**********************************************************************
lfsr	clrf	feedback	; [1] Initialise feedback bit
	movlw	1		; [2] Bit 0 of W used to XOR
	btfsc	sr_hi,7		; [3/4] xor in bit15
	xorwf	feedback,f	; [4]
	btfsc	sr_hi,6		; [5/6] xor in bit14
	xorwf	feedback,f	; [6]
	btfsc	sr_hi,4		; [7/8] xor in bit12
	xorwf	feedback,f	; [8]
	btfsc	sr_lo,3		; [9/10] xor in bit3
	xorwf	feedback,f	; [10]
	rrf	feedback,f	; [11] Feedback bit to C flag, bit 7 is x
	rrf	sr_hi,f		; [12] Shift it into the high byte
	rrf	sr_lo,f		; [13] ...and the low byte
	rlf	INDF,f		; [14] Shift o/p bit into FSR->dice reg
	retlw	0		; [16]

;**********************************************************************
; ROLLDIE
;
;	Rolls a die pointed to by FSR.
;
;	To roll a die, this routine calls lfsr three times to generate a number
;	between 0 and 7. If a 6 or 7 is rolled, the FSR reg and rollctr are
;	not adjusted, so that the same die is rolled next time. If the roll is
;	good, then FSR is increased to point at the next die, and the roll
;	counter (rollctr) is decreased. If rollctr gets to zero, all dice have
;	been rolled, so we clear the S_ROLL flag and set variables for the
;	next display sequence.
;       
;	INPUT:	FSR points to the die being rolled
;		rollctr has to be set to the total number of dice to roll
;
;	OUTPUT:	FSR->die receives a number between 0 and 7
;		0 and 5, representing dice throws of 1 through 6.
;
;	Cycle counts (inc 2 for call):
;	1. Roll 0-5, more dice to roll				65 cycles
;	2. Roll 0-5, last die					78 cycles
;	3. If the roll is a 6 or a 7				62 cycles
;
;**********************************************************************
rolldie
	clrf	INDF		; [1] Clear the die register
	call	lfsr		; [19] Shift in the first bit
	call 	lfsr		; [37] ...and the second bit
	call	lfsr		; [55] ...and the third bit
	movlw	6		; [56] Maximum value we allow is 5
	subwf	INDF,w		; [57] ...so subtract 6 and test if -ve
	btfsc	STATUS,C	; [58/59] Test carry flag
	goto	display		; [60] If set, rolled 6 or 7 so go round again
; Roll was good, so move to next die	
	incf	FSR,f		; [60] Rolled 0-5 so bump FSR
	decfsz	rollctr,f	; [61/62] Set rollctr=rollctr-1
	goto	display		; [63] Done? NO: Go round again if not done
;*********
; All dice have been rolled, set up for display	
	bcf	dicestat,S_ROLL	; [63] YES: Clear the roll flag
	call	setdice		; [72] Reset pointer and counters
	movlw	SLEEPCNT	; [73] Get the number of display cycles
	movwf	dispctr		; [74] ...before sleep and set dispctr
	goto	display		; [76]

	page
;**********************************************************************
; INIT
;	
;	Start of program proper. Initialise ports, variables, etc.
;
;**********************************************************************
init
	movlw	SETOPT		; Set OPTION register (inc. time prescale)
	option
	movlw	LEDOFF		; Get value to switch all LEDs off
	movwf	GPIO		; ...and do so while we set things up
	movlw	SETIO		; Set I/O to drive LEDs and read switch
	tris	GPIO
	clrf	dicestat	; Clear the status register
	btfss	STATUS,GPWUF	; Test to see if this is a reset from sleep
	goto	pwrreset	; NO: Go to power on from cold code

;**********
; YES: Handle special requirements of reset from sleep
; We only go to sleep with switch released, so switch must have been pressed.
	bsf	dicestat,S_PRESS ; Set switch state
	bsf	dicestat,S_DBNCE ; Set debounce flag to avoid spurious changes
	clrf	dbncstrt	; Set dbnc start time (TMR0 cleared at rsttmr)
	goto	initdisp	; Now do final initialisation for display

;**********
; Power on reset - load start values for first roll
pwrreset
	movlw	0x5a		; Set seed for random number generator
	movwf	sr_hi
	movwf	sr_lo
	clrf	die1		; Ensure all dice have valid data
	clrf	die2		; With this number of dice, it's easier
	clrf	die3		; ...and quicker than using a loop
	clrf	die4
	clrf	die5
	clrf	die6
	clrf	die7
	movlw	I_NUMDICE	; Get initial number of dice
	movwf	numdice		; ...and set numdice

;**********
; Last initialisation steps are common to power on and wake from sleep
initdisp
	clrf	setuptmr	; Clear the setup timer
	mSET_ONTIME I_ONTIME	; Set ontime for normal operation
	movlw	SLEEPCNT	; Get the # of display cycles before sleep
	movwf	dispctr		; ...and set dispctr
	movf	die1,w		; Get value of first die
	call	getgpio		; Convert to port value to drive LEDs
	movwf	ledval		; ...and set ledval for display
	call	setdice		; Set the dice pointer and counters

;**************
; OUTER LOOP
;
; Loop back to here when the 16ms or so taken by the inner loop have elapsed
; and the inner loop exit code has made any changes for setup, flash etc. 
;
;	Cycle count:	25 cycles
;
;	Taking the worst case path through inner loop exit code, the MCU is
;	outside the inner loop for 70 cycles max.
;
;**************
rsttmr
	movf	FSR,w		; [1] Get the current FSR value
	movwf	saveFSR		; [2] ...and save it
	movlw	dummydie	; [3] Get the address of the dummy die
	movwf	FSR		; [4] ...and point FSR at it
	call	lfsr		; [22] Spin the shift register to randmomise
	movf	saveFSR,w	; [23] Get the original FSR value
	movwf	FSR		; [24] ...and restore FSR
	clrf	TMR0		; [25] Clear the timer

	page
;**************
; INNER LOOP
;
; 	This main loop is repeated over and over for about 16ms. Loop execution
;	time creates latency in detecting the end of the 16ms period, so keep 
;	it as short as possible. TMR0 is clocked each 128us, so the path MUST
;	be shorter than this to avoid missing timer comparisons.
;	
;	Cycle counts:
;
;	1.	Longest path through loop code is 88us.
;	2.	Shortest path through loop code is 24us.
;
;	Worst case timing error in exiting loop is therefore 88/16384, 
;	or about 0.5%.
;
;**************
loop
	btfsc	dicestat,S_ROLL ; [1/2] Test if we're rolling the dice
	goto	rolldie		; [79a max] YES: Roll die (skip switch testing)
	call	switch		; [50b max] NO: Poll the switch

;**********
; Test if it's time to turn the LEDs on
display
	movf	ontime,w	; [+1] Get the time the LEDs should go on
	subwf	TMR0,w		; [+2] Subtract w from timer
	movlw	LEDOFF		; [+3] Assume we're switching the LEDs off
	btfsc	STATUS,C	; [+4/+5] Carry flag=1 if ontime=<timer
	movf	ledval,w	; [+5} ...so time to switch LEDs on, get value
	movwf	GPIO		; [+6] Switch LEDs on/off as reqd.

;*****************
; End of inner loop
loopend
	btfss	TMR0,7		; [+7/+8] bit7=1 after 128 counts 
	goto	loop		; [88a/59b] 128 * 128us => 16ms approx 

;******************
; This is the section where we decide what will be displayed, and when.
;
; 	Cycle counts (to go back to rsttmr):	
;	1.	Worst case: 	Exiting setup mode	45 cycles
;	2.	Best case:	Same die, no timeout	13 cycles
;
	incf	setuptmr,f	; [1] 16ms, so inc setup counter
	btfss	setuptmr,SETTIME ; [2/3]  2s since setuptmr cleared?
	goto	not2syet	; [4] NO: We don't need to mess with setup state

;************
; YES: 2s has passed since the last switch state change or the last 2s timeout
; If S_PRESS = S_SETUP, then the latter and no action is required apart from
; clearing the timer. If S_PRESS != S_SETUP then we also have to enter/exit
; setup mode as appropriate.
	clrf	setuptmr	; [4] Clear the timer
	movf	dicestat,w	; [5] Get the status byte
	andlw	(1<<S_PRESS)+(1<<S_SETUP) ; [6] Isolate bits we want to check
	btfsc	STATUS,Z	; [7/8] Both zero?
	goto	not2syet	; [9] YES: Setup state doesn't change
	btfss	dicestat,S_PRESS ; [9/10]  NO: Is S_PRESS set?
	goto	exitsu		; [11] NO: S_SETUP must be, so exit setup mode
	btfsc	dicestat,S_SETUP ; [11/12] YES: Is S_SETUP set?
	goto	not2syet	; [13] YES: Both bits set, so no change in state

;**********
; NO: But S_PRESS is set, so enter setup mode
;
; 	Cycle counts:	23 cycles
;
entersu				; YES: Enter setup mode
	bsf	dicestat,S_SETUP ; Set flag for setup mode
	mSET_ONTIME ONTMFLSH	; Set ontime for flash
	goto	not2syet 	; ...and continue

	page
;**********
; We're leaving setup mode
;
; 	Cycle counts:	34 cycles	Takes shortest route through flash
;
exitsu
	bcf	dicestat,S_SETUP ; Clear flag for setup mode
	mSET_ONTIME I_ONTIME	; Set ontime for normal operation
	call	setroll		; Set flags etc. to roll dice


;**********
; All routes through the inner loop exit code pass through this point
;
; 	Cycle counts:
;	1.	Not changing to next die		9 cycles
;	2.	Changing to next die			24 cycles
;	3.	End of this display cycle (start flash)	29 cycles
;	4.	Flashing: Going through			13 cycles
;	5.	Flashing: Toggling flash patter		16 cycles
;	6.	Flashing: Done flashing			31 cycles
;	7.	In setup mode				18 cycles
;
not2syet
	btfsc	dicestat,S_SETUP ; Are we in setup mode?
	goto	dspsetup	; YES: Goto setup display code

;**********
; NO: Not in setup, check if we need to flash (end of display cycle or rolling)
	btfsc	dicestat,S_FLASH ; Test if we need to flash
	goto	flash		; YES: Go and do it...
; NO: We're displaying, set values for next 16ms
	movlw	DELTA_ONTM	; Move ontime earlier
	subwf	ontime,f	; ontime=ontime-DELTA_ONTM
	btfss	ontime,7	; Is ontime <0?
	goto	rsttmr		; NO: Go back for next 16ms loop
	
; ontime is now negative, so time to display the next die
	incf	FSR,f		; YES: Bump pointer to next die
	decfsz	dispdie,f	; See if we've displayed the last die
	goto	nextdie		; NO: Keep going
; YES: Last die has been displayed, see if time to sleep or display again
	decfsz	dispctr,f	; Test if last display cycle complete
	goto	nxtdisp		; NO: Flash for end of this cycle
; YES: We have displayed SLEEPCNT times.... time to sleep	
	movlw	LEDOFF		; We want LEDs to be switched off while asleep
	movwf	GPIO		; ...so set outputs to switch them off
	movf	GPIO,w		; Read port before sleeping (as per Microchip)
	sleep			; ... and goodbye!

;***********
; Set flag and timer for end of display flash
;
; 	Cycle count:	14 cycles	Takes shortest route through flash
;
nxtdisp
	bsf	dicestat,S_FLASH ; Flash for end of display cycle
	clrf	flashtmr	; Clear the timer for the flash sequence
	movlw	FLSHNORM	; Get initial flash LED pattern
	movwf	ledval		; ...and set ledval
	mSET_ONTIME ONTMFLSH	; Set ontime for flash	

	page
;***********
; In normal and rolling mode (not setup), handle flash
;
; 	Cycle counts:	
;	1.	Just going through, no change		8 cycles	
;	2.	When pattern toggles			11 cycles
;	3.	When done flashing			26 cycles
;
flash
	incf	flashtmr,f	; Bump flashtmr
	btfsc	flashtmr,FLSHDONE; See if finished flashing
	goto	doneflsh	; YES: Need to start displaying again
	movf	flashtmr,w	; NO: Get flashtmr
	andlw	FLSHTIME	; Test if multiple of 8 (8*16=128ms)
	btfss	STATUS,Z	; Test if time to toggle LEDs
	goto	rsttmr		; NO: Go back for next 16ms loop
; Time to toggle the LEDs
	movlw	FLSHTOGL	; YES: Get mask
	xorwf	ledval,f	; ...and toggle pattern (\ / \ ...)
	goto	rsttmr		; Go back for next 16ms loop

;***********
; Done flashing, clear flag and reset pointers for next display or roll cycle
;
; 	Cycle count:	22 cycles
;
doneflsh
	bcf	dicestat,S_FLASH ; Clear the flag
	call	setdice		; Set the dice pointer and counters

;********
; Get LED values for next die
;
; 	Cycle count:	12 cycles
;
nextdie
	movf	INDF,w		; Get dice value
	call	getgpio		; Convert to value to drive LEDs
	movwf	ledval		; ...and set ledval
	mSET_ONTIME I_ONTIME	; Set ontime for normal operation
	goto	rsttmr		; ...and start again

;**********
; Display number of dice being rolled while in setup mode.
; Ontime is set to a fixed value when setup mode is entered. We invert it every
; eight timer cycles. When bit7 of ontime is set, this turns the LEDS off, so 
; the LEDs flash. ledval is set to the correct value for the LEDS to display
; numdice, so the display shows flashing representation of the number of dice
; selected.
;
; 	Cycle count:	15 cycles
;
dspsetup
	incf	flashtmr,f	; Bump flashtmr
	movf	flashtmr,w	; Get flashtmr
	andlw	FLSHTIME	; Test if multiple of 8
	btfsc	STATUS,Z	; Is it time to toggle LEDs?
	comf	ontime,f	; YES: ontime=!ontime (Bit7=1 turns LEDs off)
	decf	numdice,w	; Get number of dice into W (-1 for getgpio)
	call	getgpio		; Convert to value to drive the GPIO port
	movwf	ledval		; ...and set ledval
	goto	rsttmr		; Go back for next 16ms loop

	END			; directive 'end of program'
