list n=96 ;********************************************************************** ; ; Filename: dice.asm ; Date: 29 July 2002 ; File Version: 01.00 (set in ID locs) ; __IDLOCS 0x0100 ; ; Author: Steve Russell (pic.projects@ntlworld.com) ; 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 __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<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= 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<