Anda di halaman 1dari 8

Vinicio Coletti Site - PIC Projects

Electronic dice

Click the picture to see a movie

PIC Projects Index IK0WRB Home page

Description
Some friends asked me to do an electronic dice, just for fun. I thought this was the occasion to write a simple yet interesting program for the usual PIC 16F84 chip. Thus I wrote such a program and decided to publish the entire project, including the assembler source, for educational purposes. The dice will simply show a random number, 1 to 6, using 7 leds put in the same shape as the dots on a dice face. When the user push a button, a new random number is generated and showed. After about ten seconds of inactivity the leds are switched off, to save the battery. Since in this application the timing is not critical, we can save the crystal and run the chip on the internal RC oscillator. The frequency of oscillation will depends on the resistor and capacitor used, as well as the voltage and the ambient temperature. At 25 and the rated 4.5 V, the clock in this circuit will be about 620 kHz, giving an istruction rate of 155 kHz (6.45 s per istruction). Port A of the chip will not be used at all, while the first pin of port B (RB0/INT) will be used to connect a push button, normally open. The other seven pins of port B (RB1 - RB7) will drive the seven leds. The power to the circuit will be given by 3xAA cells, so 4.5 V, but you can try also with three or four NiMH rechargable cells (3.6 - 4.8 V). The clock will change depending on the voltage used: the higher the voltage, the slower the clock. This will only change slightly the timeout used to switch off the leds, all other features being exactly the same.

The dice circuit

The components list


1 Microchip PIC 16(L)F84(A), in the 18 pins PDIP package 7 standard leds, of any color and size you like (20 mA current) 7 resistors of 150 W 1 resistor of 10 k W 1 capacitor of 100 pF 15 VL 1 pushbutton, normally open 1 switch 1 battery holder for 3 AA cells 1 box to contain the circuit and the batteries and on the outside leds, switch and button

The program
The are several ways to generate a random number to simulate the rolling of a dice. I simply make the main program increment a variable at the maximum possibile speed. This infinte loop requires 3 istruction cycles for every increment, thus we will have about 52,000 increments per second. When we push the button, we generate an interrupt and the interrupt service routine will read the current counter value and will use it to generate a random number in

the range 0 to 5. This number will be showed on the leds as a number in the range 1 to 6. To generate the random number, the program will look at the 3 least significant bits of the counter (value 0 to 7). If we have an invalid value (6 or 7), the program will look at the 3 bits on the left and if we have another invalid value, another byte is used. So the tmr0 register is taken (it is the timer counter) and here also we look for the 3 least significat bits and then, if needed, to the 3 immediately on their left. A wrong generation has thus a probability of about 0.4% and in this case we will simply need to push again the button to have a valid number. Of course we could make a better algorythm, but I think this is good enough for an aducational project and beware of the counters manipulations, because it is very easy to obtain a not uniform distribution of the six numbers! To signal a wrong generation, I make the seven leds blink all at the same time. Another problem, very secondary however, is that pushbuttons tend to give many pulses when we press them once, until the mechanical parts stabilize. One can decide to simply let the button give all these pulses, so that many random generations would occur within a few milliseconds (only the last stable one will be visibile on the leds) but I decided to provide a protection for this: when we press the button, all subsequent pulses are disabled for about 300 ms. Since there are three different operations linked to time passing (blinking the leds, re-enabling the button and switching off the leds), we need also that Timer 0 generate an interrupt. This timing interrupt will increment a second counter, used to know when we have to do the different operations. Thus, basically all the program is contained in the interrupt service routine, called either by a button pressure or by a timeout of timer 0. You can find further details on the assembler code, that I commented heavily for educational purpose. I think it is possibile to learn a lot of things about PICs from this simple code.

The Assembler source code


What follows is the commented source code for the dice program (dice.asm). You can compile it with the freeware suite MPLAB that you can download from the Microchip site. When I tested the program, I fixed a bug and made some other small changes. I had used blt instead of bnc because usually I use a set of macros and definitions created by myself. Changes are in violet.
; DICE.ASM - Electronic dice with a PIC16F74A and 7 leds - (c) Vinicio Coletti 2005 ; First of all we must inform the assembler program and the development environment ; about what chip we are using in this source LIST P=16F84A ; then we load an include file with many symbolic definitions that simplify ; the writing of the program; for example if we want to stop all interrupts ; we must clear the most significant bit in the interrupt control register ; writing: bcf 11,7 but this is quite obscure, whilst using the mnemonic ; definitions we could write: bcf INTCON,GIE that is much more easy to remember INCLUDE <p16f84a.inc> ; the other important thing to do initially is to define the bits ; of the configuration word, which are stored on the chip in the

; programming phase and are not accessible by the program; ; here we say: the power on delay is ON, code protect is OFF, ; watchdog timer is OFF, the oscillator we use is RC __CONFIG _PWRTE_ON & _CP_OFF & _WDT_OFF & _RC_OSC ; this is somewhat optional: we say we want to be informed of all errors ; during compile (this is a message to the compiler) ERRORLEVEL 1 ; another information to the compiler: the numbers we write in the source ; are to be considered in decimal notation, if not otherways stated RADIX DEC ; ; ; ; ; ; now we begin to define the variables used by the program the user RAM bank in the 16f84a begins at address 0x0c and ends at address 0x4f for a total of 68 bytes we define several variables, all of 1 byte each (the default) simply listing their names; the first will be at address 0x0c the second at 0x0d and so on; we need only seven bytes! cblock 0x0c count ; counter used to generate the random number flag ; contains several 1 bit flags nint ; timer interrupts counter, each unit about 82 ms save_w ; save w register during interrupts save_status ; save status register during interrupts num ; stores the generated random number 05 try ; counter of the generation tries endc ; we need two different 1 bit flags and to simplify their access ; we define two symbolic names for them #define f_ledon flag,0 ; it is 1 if leds are ON #define f_blink flag,1 ; it is 1 if leds must blink ; ; ; ; ; ; ; ; ; at this point we can begin to write the real program istructions! but first we need to say to the compiler which is the address of the first istruction; it will be ZERO because on power on reset the program counter is set to 0; this is the start of all PIC programs org 0 now lets begin the program; we can put here only 4 istructions at addresses 0,1,2,3 because at address 4 there is the interrupt vector! we begin by clearing the register of timer0 and the two i/o ports this is not mandatory, but I like it :-) clrf TMR0 clrf PORTA clrf PORTB then we must jump over the interrupt vector and continue to main program goto begin this is the interrupt vector! whenever an interrupt occurs, the program counter is loaded with the address 4, so the program execution jumps here; I put a goto to the real interrupt service routine goto inter

; ; ; ; ; ;

; here there is the continuation of the main program ; first of all we must configure some important ; registers and we begin with the i/o ports ; since the special registers TRISA and TRISB are in the high bank of RAM ; we need first to set the bit RP0 in the STATUS register begin bsf STATUS,RP0 ; in every port definition 0=output pin and 1=input pin ; port A is not used and set to all inputs movlw 0xff movwf TRISA ; port B is all outputs, except the first pin, used for the button movlw 1 movwf TRISB ; now we set the OPTION register, where each bit sets a different feature: ; - internal pull-up resitors on port B are enabled ; - external interrupt in on falling edge (1 to 0 transition) ; - istruction clock is from internal oscillator ; - prescaler is assigned to timer 0 ; - prescaler value is 64 (1 timeout every about 105 ms) ; for clarity the value is specified in binary notation movlw B'00000101' movwf OPTION_REG ; now we go back to bank 0 of RAM, where we have our variables bcf STATUS,RP0 ; we clear some variables, as needed clrf count clrf flag clrf nint ; now we enable the interrupts, setting three bits in the INTCON register ; they are the external interrupt enable (INTE), the timer 0 interrupt ; enable (T0IE) and the general interrupt enable (GIE) ; to be shorter we do it loading a single value in INTCON movlw B'10110000' movwf INTCON ; now we are finished with the initialitazion and we can begin the ; main program, which in this case consists simply in an infinite ; loop where the variable count in incremented 0 to 255 and then ; it rolls back to 0 and so on ad libitum... ; the value of count will be used to generate the random numbers ; and all the processing will happen in the interrupt service routine loop incf count,f goto loop ; here is the interrupt service routine! all things will happen here ; first of all we MUST save the current STATUS and W registers ; the istruction MOVF must be avoided, because in alters the status! inter movwf save_w swapf STATUS,W movwf save_status ; since there are two possibile interupt source in this program, ; first of all we must know who generated this interrupt; ; if it was the button, we jump to its routine btfss INTCON,T0IF goto intbutt ; if we are here, it's not the button! so it's the timer

; first of all we increment the number-of-interrupts variable incf nint,f ; then we clear the timer interrupt flag, to enable it for the next time bcf INTCON,T0IF ; now we should know what we have to do: enabling the button? ; switching off the leds? blinking them? but if the leds are ; already switched off, we must do nothing and exit the routine ; this state is signaled by the f_ledon flag btfss f_ledon goto endint ; here the leds are ON so we should see if the button is already enabled btfsc INTCON,INTE goto int1 ; here the button is disabled, so we check if about 300 ms are passed ; from the last random number extraction, that is 3 timer interrupts movlw 3 subwf nint,w ; if the time passed is shorter, we don't enable the button and ; jump also the led switching routine, that will happen later bnc int2 ; here about 300 ms are passed, so we re-enable the button, if released btfss PORTB,0 goto int1 bcf INTCON,INTF bsf INTCON,INTE ; either the button was already enabled or it was enabled now, here we are! ; where we check if about 10 seconds are passed; if so, we switch off the leds int1 movlw 100 subwf nint,w ; if less than 10 seconds, we jump to the next routine bnc int2 ; otherwise we clear all port B, where the leds are connected ; and we clear also f_ledon to remember that leds are now off clrf PORTB bcf f_ledon ; since we have switched off the leds, they must not blink either ; so we jump directly to the end of the interrupt routine goto endint ; if time was shorter than 10 seconds we arrive here to see ; if leds are in a blinking state, if not we jump to the end int2 btfss f_blink goto endint ; here we must blink the leds; we do so linking the state of bit ; number 1 in the number-of-interrupts variable to the state of ; the leds (1=on 0=off); since this bit changes every 2 ; timer 0 interrupts, this will produce about 210 ms withs leds ON ; then about 210 ms with leds OFF (about 2.4 blinks per second) btfsc nint,1 goto blinkon clrf PORTB goto endint blinkon movlw B'11111110' movwf PORTB goto endint ; this ends the timer interrupt routine

; here we arrive if the interrupt was generated by a button pressure ; first of all we disable further button interrupts ; they will be re-enabled by the timer about 300 ms later intbutt bcf INTCON,INTE ; then we clear the counter of timer interrupts, this is zero time for us! clrf nint ; we set the flag telling that the leds are on (either normal or blinking) bsf f_ledon ; we initialize a variable to make two tries of random number generation movlw 2 movwf try ; this is the generation loop, we look at the 3 least significant bits of count gen movlw 7 andwf count,w movwf num ; if the number is less than 6, it's all ok movlw 6 subwf num,w bnc ok ; if not, we try shifting count by 3 bits to the right rrf count,f rrf count,f rrf count,f ; than we get the 3 least significant bits again movlw 7 andwf count,w movwf num movlw 6 subwf num,w bnc ok ; here we are still with a wrong number, so we make a second try ; getting the timer 0 counter value, from TMR0 register ; to repeat the try, we put it inside count variable movf TMR0,w movwf count ; we decrement the try variable, first time it will be different ; from zero, so we will go on to the "goto" that brings us back ; to the generation routine; the second time the goto will be jumped decfsz try goto gen ; here both the two generations failed, so we set the led blink flag ; then we exit the routine; the timer will blink the leds bsf f_blink goto endint ; here the random number is OK, in the range 0 to 5, we reset the blink flag ok bcf f_blink ; then we get the generated number and decode it to the value needed for output movf num,w call decod movwf PORTB ; clear the counter, to avoid correlation with tmr0 value clrf count ; this is the common end for the two interrupt routines ; we restore the STATUS and W registers, then we end the interrupt

endint swapf movwf swapf swapf retfie

save_status,w STATUS save_w,f save_w,w

; this routine decodes the random number 0-5 to the value needed to ; switch on the seven output leds as if they were on the face of a dice ; these values depend on the circuit wiring; ; to decode, we will add the input value in W to the program counter PCL ; in fact, this is a computed-goto; the "retlw" istruction is a "return" ; loading also a value into the W register; so we simply list ; the six needed values and the routine will return with the selected ; one loaded in the W register decod addwf PCL,f retlw B'00010000' retlw B'00101000' retlw B'10010010' retlw B'10101010' retlw B'10111010' retlw B'11101110' ; and finally we must tell the compiler that the source code ends here end ; and this is the end of the source code of DICE.ASM by Vinicio Coletti (c) 2005

PIC Projects Index IK0WRB Home page accesses from March 2nd 2005 Crea il tuo counter

Anda mungkin juga menyukai