'***************************************************************************** ' Jotto aka Mastermind with four places and seven "colors" 3.8 - Strobl '***************************************************************************** ' Copyright (C) 1998 by Wolfgang Strobl, all rights reserved
' 3.0 changed from RS232 to LED display 25.3.1998 ' 3.5 3.9.98 power saving implemented ' 3.7 4.9.98 Cleanup. ' 3.8 4.9.98 keyb during "success". ' 3.81 30.10.98 documentation cleanup ' ' Programming notes ' ------------------ ' ' WDT must be disabled in order to enable powersaving mode. ' ' Source language is a mixture of PicBasic (compatible to Parallax ' Basic Stamp I) and inline assembler. The relevant and time-critical ' parts are coded in asssembler, some as macros. ' ' ' Part list ' --------- ' 1 PIC16F84/4 ' 1 8MHz ceramic resonator (or 2*22pF + 8MHz Quartz) ' 4 HDSP 5503 seven segment LED display, common cathode ' 1 Resistor, 4.7K ' 2 buttons ' ' ' Structure of the circuit: ' ------------------------ ' ' Display ' ------- ' ' 4 * 7-Segment LED type HDSP 5503 1,42mm, a surplus part bought from ' Conrad/Germany.
' 1. Anodes
' connected to port B, as follows: ' ' +--0--+ ' | | ' 5 1 ' | | ' +--6--+ ' | | ' 4 2 ' | | ' +--3--+ (7) ' ' ' i.e. the upper segment of all four HDSP5503 connected to PB0, the decimal point ' of all four displays connected to PB7, etc. ' ' 2. Cathodes ' connected to port A, Bit A0, A1, A2 and A3. A4 is still free, ' but connected to ground for lowest power consumption during SLEEP. ' ' ' There are no external pullup or pulldown resistors, current ' adjustment/limitation is done by software (PWM), as a side effect of ' multiplexing. ' ' For the encoding, see function "convert". ' ' ' Buttons ' ------- ' ' First switch between PB7 (pin 13) and ground, second switch between PB6 (pin 12) ' and ground. Internal weak pullups are used, so no external pullup are necessary. ' ' ' IMPORTANT ' Never drive a segement continuously, it might destroy both the display and ' your processor. ' Immediately stop driving a segment when a key is pressed, because PB7/6 are ' shared between display and keys.
' Operation ' ' All four displays are multiplexed round robin, by pulling PA0 .. PA3 low ' in turn, and then pulling some of the segments up for a short time, two ' of the segments at a time, at max, because of the current limitation ' of a single PIC pin.
' During every display multiplexing cycle, the buttons get checked by ' first changing both port A and port B to input (== three-state), ' enabling the weak pullup, and then checking PB7 and PB6. When a ' connection is detected (by reading a low level), the program waits ' until the key is released, in order to avoid having an output ' shortcircuited to ground. ' ' Using about 5 V from a 4 cell, 110Ah Nicad battery pack, I measure between ' 25 and 30 mA during operation, and about 2 µA in sleep mode. It was ' necessary to ground PA4, in order to archive this. That power consumption ' amounts to about three hours continous operation, or many years of SLEEP ' mode. For that reason, I didn't consider a power switch necessary. The ' device goes into power saving mode immediately after the display is ' darkened. When the left button is pressed, it wakes up again. '
asm lall endasm
'************************************************************** ' Data, ordered by memory layout. '**************************************************************
' There are four places for storing an uncompressed combination: ' a, b, c and d. Utility subroutines are available for copying, ' see below.
' a und c are temporary combinations, to be compared ' b is for counting. symbol a0=b4 symbol a1=b5 symbol a2=b6 symbol a3=b7 symbol c0=b14 symbol c1=b15 symbol c2=b16 symbol c3=b17
' stored answer and actual answer ' "schwarz" == "black", "weiss" == white
symbol antw_schwarz=b8 ' no of correct positions symbol antw_weiss=b9 ' no of correct "colors" symbol schwarz=b10 symbol weiss=b11
' Answers get stored in an array starting with w11, using n as ' high water mark (B22 ... B38 == 18 Bytes = 18/3 = space for 6 tries )
symbol n=b12 ' Number of answers so far ' w11[0] to w11[n] are defined symbol i=b13 ' running index
symbol answer=b20 ' packed answer
symbol temp=b21 ' temporary storage, byte
symbol count=w9 ' b18 b19
symbol rdm=w20 ' well ..
symbol tempw=w21 ' temporary storage, word
symbol d0=b46 ' w23 symbol d1=b47 symbol d2=b48 ' w24 symbol d3=b49
symbol gcount=w25 ' b50, b51
Symbol PortA = 5 ' PortA address Symbol TrisA = $85 ' PortA data direction register symbol zaehler=b1 symbol wert=b0
'********************************************************************************************* ' Code starts here ' ' we need to jump across the interrupt handler ' The interrupt handler isn't really necessary (wakeup from sleep through ' interrupt on change works with interrupts disabled, too!), but we might need ' it during further development, so I just let it there ...
asm goto _start ; not to be confused with "start" ! ;********************************************************************************************* inthandler movwf i0 ; save w swapf status,w ; save status movwf i1 movf fsr,0 movwf i2 ; save fsr
; now the real work bcf rbif ; clear interrupt on change bit movf portb,1 ; clear portb mismatch
; end of real work
returnfromint movf i2,0 ; restore fsr movwf fsr swapf i1,0 movwf status ; and restore swapf i0,1 ; restore w swapf i0,0 bcf t0if retfie
;-end of inthandler
;*********************************************************************************************** ; convert a digit in _wert to a LED pattern. ; Implements digits 0 to 7, only, high order bit controls the decimal point. ; (There is obviously room for improvement, here). convert movf _wert,0 ; load _wert to W andlw 0fh addwf pcl,1 ; d6543210 retlw 00111111b ; 0 retlw 00000110b ; 1 retlw 01011011b ; 2 retlw 01001111b ; 3 retlw 01100110b ; 4 retlw 01101101b ; 5 retlw 01111101b ; 6 retlw 00000111b ; 7
retlw 10111111b ; 0 retlw 10000110b ; 1 retlw 11011011b ; 2 retlw 11001111b ; 3 retlw 11100110b ; 4 retlw 11101101b ; 5 retlw 11111101b ; 6 retlw 10000111b ; 7 endasm '-------------------------------------------------------- ' utility subroutines b2c: ' copy b to c w7=w0 w8=w1 return b2d: ' copy b to d w23=w0 w24=w1 return d2a: ' copy d to a w2=w23 w3=w24 return
'-------------------------------------------------------- asm compare macro a,b,color
; compares a and b, when both are equal and nonzero, both ; get zeroed and color gets incremented
local fl movf a,0 ; 1 load to w, 0? btfsc z ; 2 (1) skip goto, when zeroflag not set goto fl ; 2 goto, when zeroflag set subwf b,0 ; 1 =b? btfss z ; 2 (1) skip goto, when zeroflag set goto fl ; 2 goto, when zeroflag not set gesetzt (!=) incf color ; 1 count clrf a ; 1 and zero both candidates clrf b ; 1 ; 9 / 8 / 5 fl endm endasm '-------------------------------------------------------- compare: ' compare a with c (destroys a and c) ' (2401 x 145 µs = 0.34 s) asm ; 16*9+2 cycles = 146 µS clrf _schwarz clrf _weiss compare _a0,_c0,_schwarz compare _a1,_c1,_schwarz compare _a2,_c2,_schwarz compare _a3,_c3,_schwarz compare _a0,_c1,_weiss compare _a0,_c2,_weiss compare _a0,_c3,_weiss compare _a1,_c0,_weiss compare _a1,_c2,_weiss compare _a1,_c3,_weiss compare _a2,_c0,_weiss compare _a2,_c1,_weiss compare _a2,_c3,_weiss compare _a3,_c0,_weiss compare _a3,_c1,_weiss compare _a3,_c2,_weiss endasm return
asm NextColor macro ok,overflow ; ca. 16 decfsz _b3,1 jmp ok movlw 7 movwf _b3 decfsz _b2,1 jmp ok movlw 7 movwf _b2 decfsz _b1,1 jmp ok movlw 7 movwf _b1 decfsz _b0,1 jmp ok movlw 7 movwf _b0 jmp overflow endm
endasm
'*************************************************************** display: ' already converted pattern in wert, to pins
zaehler=200 asm call convert ; from _wert to W movwf _wert ; from W to _wert dl movf _wert,0 andlw 11000000b movwf portb movf _wert,0 andlw 00110000b movwf portb movf _wert,0 andlw 00001100b movwf portb movf _wert,0 andlw 00000011b movwf portb decfsz _zaehler,1 jmp dl movlw 0 movwf portb
endasm return asm ;------------------------------------------------------------------------ ; W11 is the first adress behind the block of internal variables
; cstore var und cload var macros
; pack four nibbles from a0...a4, and answer into a triple of bytes, ; and store (load) them to (from) w11[var]. Changes W and FSR.
; --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ; Store combination a0...a3 and answer to w11[i]. ; Three bytes, 2 x combi, 1 x answer
; answer ist already packed
; 0000 1111 2222 3333 ssss wwww
cstore macro i ; i is a variable! Must! movlw _w11 ; load adress of w11 addwf i,0 ; add index addwf i,0 ; | addwf i,0 ; | movwf fsr ; and access indirectly movf _a0,0 ; load value andlw 00001111b movwf indf ; store to w11 swapf indf,1 ; 0000 xxxx ; and to LSN of w11/b22 movf _a1,0 andlw 00001111b iorwf indf,1 ; 0000 1111
incf fsr,1 movf _a2,0 andlw 00001111b movwf indf swapf indf,1 ; 2222 xxxx movf _a3,0 andlw 00001111b iorwf indf,1 ; 2222 3333 incf fsr,1 movf _answer,0 ; store answer, too movwf indf endm
; --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ; Load combination a0..a3 from w11[i] ; 0000 1111 2222 3333 cload macro i movlw _w11 addwf i,0 addwf i,0 addwf i,0 movwf fsr movf indf,0 ; load first byte andlw 00001111b movwf _a1 swapf indf,0 andlw 00001111b movwf _a0 incf fsr,1 movf indf,0 ; load second Byte andlw 00001111b movwf _a3 swapf indf,0 andlw 00001111b movwf _a2 incf fsr,1 movf indf,0 ; load stored answer movwf _answer ; back into original var endm
pack macro x,y,nach ; x:0000xxxx y:0000yyyy -> nach: yyyyxxxx movf x,0 ; load one half andlw 00001111b movwf nach ; swapf y,0 ; second half andlw 11110000b iorwf nach,1 ; or in endm
unpack macro von,x,y ; from: yyyyxxxx -> x:0000xxxx y:0000yyyy movf von,0 ; load andlw 00001111b movwf x swapf von,0 andlw 00001111b movwf y endm
endasm '--------------------------------------------------------------------
getrandom: random rdm temp = rdm & %111 if temp>0 then okreturn temp=1 okreturn: return
displaykombi: poke TrisA, 0 ' Set PortA to all output poke portA,$7 wert=d0 gosub display
poke portA,$b wert=d1 gosub display
poke portA,$d wert=d2 gosub display
poke portA,$e wert=d3 gosub display return
displayweiss: poke TrisA, 0 ' Set PortA to all output poke portA,$7 wert=antw_weiss gosub display ' falltru displayschwarz: poke TrisA, 0 ' Set PortA to all output poke portA,$b wert=antw_schwarz gosub display
return
keyb: poke TrisA,255 ' port A to all input pins=0 ' latches auf 0 dirs=%00111111 ' port B auf input auf 7/6 asm bsf rp0 ; option register bcf rbpu ; pull up enable 0=an bcf rp0 endasm pause 1 ' little bit of charging time temp=pins ' read port B ' temp = temp | %00111111
if temp=$c0 then keybok ' wait for keyup waitk: pause 100 if pins=$c0 then keybok goto waitk keybok: dirs=255 ' port B output pins=0 ' latches auf 0 poke TrisA,0 ' port A to all output return '---------------------------------------------------- ' Power saving mode schlafe: dirs=%01111111 ' port B auf input auf 7 poke TrisA,0 ' Port A to all output poke portA,%00001111 ' Port A low == no current through LED possible ' and powersaving pins=%10000000 ' hmmm asm ; enable Interrupts movf portb,1 ; clear portb mismatch bsf rbie ; enable port chg interr bsf gie ; global interrupts on bsf rp0 ; option register bcf rbpu ; pull up enable 0=on bcf rp0 ; back to first bank sleep ; as it says endasm goto restart '-------------------------------------------------------- ' First wait for a keypress, then generate a random question start: restart: dirs=255 asm bcf rbie ; disable port B change on interrupt bsf rp0 ; option register bcf rbpu ; pull up enable 0=on bcf rp0 endasm
startl: pause 9 ' wait 10 ms (incl keyb) gosub keyb ' is a key pressed? rdm = rdm + 1 ' increment random seed if temp=$c0 then schlafe ' no key -> sleep
random rdm ' seed random number generator
gcount=0 count=0
n=0
' compute a random question (four digits)
gosub getrandom d0=temp gosub getrandom d1=temp gosub getrandom d2=temp gosub getrandom d3=temp
outerloop:
'------------
il: gosub displaykombi ' display the actual question gosub keyb ' check keys if temp = $c0 then il ' no key? try again
antw_schwarz=0 ' left digit (== black, correct position) initialized with 0 antw_weiss=0 ' and so the right digit (== white, correct color) il1: gosub displayschwarz ' first display the left digit, only gosub keyb ' and wait for a key if temp=$40 then incschwarz ' left key? cycle "black" if temp=$80 then il2 ' right key? store "black" and process "white" goto il1 ' no key? try again
incschwarz: ' cycle black antw_schwarz=antw_schwarz+1 if antw_schwarz<5 then il1 ' 0-4 allowed antw_schwarz=0 il1a: goto il1
il2: ' process right digit ("white", correct color) gosub displayweiss ' displays both digits gosub keyb ' check keys if temp=$40 then incweiss ' left key= cycle "white" if temp=$80 then il9 ' right key? store "white" and continue goto il2 incweiss: antw_weiss=antw_weiss+1 if antw_weiss<5 then il2 ' we could take "black" into account, but we don't do that antw_weiss=0 il2a: goto il2 il9:
'------------ ' Now pack the question and its answer into the array
gosub d2a
asm pack _antw_schwarz,_antw_weiss,_answer cstore _n endasm
n = n + 1
count=0
' we are counting backwards. b0=7 b1=7 b2=7 b3=7
middleloop:
i=0 innerloop: ' loops over all stored answers if i=n then innerloop_succ asm cload _i ; to a ; now we have a and answer. endasm gosub b2c
' compare actual combination a with computed (counted) combination c(b)
gosub compare asm
pack _schwarz,_weiss,_temp ; temp is the answer to b
endasm if answer=temp then innerloop_next goto innerloop_fail innerloop_next: ' next stored answer i=i+1 goto innerloop
innerloop_succ: ' possible combination found, possible question
count = count + 1 ' zählen
' We want to make a random choice of 1/n, without knowing n beforehand, without ' computing the set twice. ' DONT copy with a probability of 1/(count+1), -- (count+1) before incrementing. ' Nice trick, eh?
random rdm ' 0 to 65535 tempw= 65535/count
if tempw<rdm then innerloop_fail gosub b2d ' memorize ' fallthrough!!
innerloop_fail: ' Die Kombi ist es nicht: Nächste Zählung gcount=gcount+1 asm NextColor _Middleloop,_Middleloop_out endasm ' notreached Middleloop_out: ' Alles durchgezählt if count=0 then Fehler if count=1 then Erfolg ' serout rout,N9600,(#n,". Noch ",#count," Moeglichkeiten") ' gosub crlf goto OuterLoop
Erfolg: d0 = d0 ^ $08 for tempw=0 to 10 gosub displaykombi next gosub keyb if temp<>$c0 then start d1 = d1 ^ $08 for tempw=0 to 10 gosub displaykombi next gosub keyb if temp<>$c0 then start d2 = d2 ^ $08 for tempw=0 to 10 gosub displaykombi next gosub keyb if temp<>$c0 then start d3 = d3 ^ $08 for tempw=0 to 10 gosub displaykombi next gosub keyb if temp<>$c0 then start goto Erfolg
Fehler: d0=0 d1=0 d2=0 d3=0 goto Erfolg ' ha
Ende: end