'*****************************************************************************
' 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