Intro | Mechanical stuff | Electronics | Software | Who we are? + useful links |
Software
Several programs were written to test all the different parts of the set-up:
The different parts were combined (and adapted), resulting in our final program:
Now we'll take a closer look at all the parts of the final program.
Getting numbers on the display
This is done with the INSERTDIGITS routine. In this routine, we communicate with the two shift registers (74HC595). They are connected with the pins of port A:
To show a number, the correct bit combination has to be shifted in the register. These combinations are stored in SH_REG1 and SH_REG2 (somewhere in the program). The INSERTDIGITS routine is then responsible for shifting SH_REG1 and SH_REG2. This goes as follows:
First, port A is cleared. This ensures that the SH_CP and ST_CP (the clock pulses) are low. Then we place the first bits of SH_REG1 and SH_REG2 on the corresponding output pins (respectively RA0 and RA1). To do this, they are "right rotated", which copies the first bit to the carry bit (STATUS, C). By right rotating them, we also ensure that in the next loop cycle, the second bit will become the first bit, and so on... After setting RA0 and RA1 to the right value, a pulse is given on the SH_CP, that way, the bits are shifted in the register.
This is repeated 8 times.
After that, a pulse is given on the ST_CP. This puts the bits that were shifted inside the shift registers on the output pins of the shift register, which are connected to the 7-segment displays. (See Electronics for more information.)
Showing or not showing anything on the display
This is quite easy to do: if you put a high on the OE of the shift registers (connected with RA2), the output pins of the shift registers are cleared (put in high-impedance state). (However, the shift register still contains its data!) If these outputs are high-impedance, the displays won't work. If you put a low on the OE, the displays will show the data that is in the shift registers.
In assembler:
BCF PORTA,H'2' ; Output enable
BSF PORTA,H'2'
; Output disable
Showing the correct numbers on the display
To get the display to show what we want him to show, we simply have to check how we connected it. Well... this is how:
These are the 'names' of the leds in a 7-segment display.
This is how the leds are connected to the shift register.
So you can see: to display number 4, a high is needed on connections f, g, b and c. This means we need to load the combination 01101100 in the shift register. Similar, we can define for each of the numbers 0... 9 which combination needs to be loaded in the shift register. This is done in the beginning of the program:
MOVLW B'01111011'
MOVWF DIG_0
MOVLW B'01100000'
MOVWF DIG_1
MOVLW B'00110111'
MOVWF DIG_2
MOVLW B'01110110'
MOVWF DIG_3
MOVLW B'01101100'
MOVWF DIG_4
MOVLW B'01011110'
MOVWF DIG_5
MOVLW B'01011111'
MOVWF DIG_6
MOVLW B'01110000'
MOVWF DIG_7
MOVLW B'01111111'
MOVWF DIG_8
MOVLW B'01111110'
MOVWF DIG_9
These DIGs will be used in the CONVERTSCORE routine. To see how this happens... read on!
Score handling is done with three routines: INCSCORE, SHOWSCORE, CONVERTSCORE.
How INCSCORE works:
Three things are done: increasing the score, checking if the score hasn't reached 99 yet, and making the game run a bit faster. (Each time a cowboy is hit, the game should get a bit faster).
We keep track of the score in two decimals: SCORE_DEC2 for the multitudes of 10 (= left number), SCORE_DEC1 for the multitudes of 1(= right number). When a hit on a cowboy is detected, the score should increase, so we increase SCORE_DEC1. If SCORE_DEC1 is 10, it's set back to zero, and SCORE_DEC2 is increased.
Doing it this way ensures that we can easily convert the score from binary to decimal system, to load it in the display.
How CONVERTSCORE works:
We compare one of the two decimals (SCORE_DEC1 or SCORE_DEC2) with binary numbers from 00000000 to 00001001. When they're the same, the corresponding 'DIG' is loaded in SH_REG1 or SH_REG2. Because we don't want to write this routine twice, we simply run it twice, using general names (A_SCORE_DEC and A_SH_REG). CONVERTSCORE is only called from the SHOWSCORE routine.
How SHOWSCORE works:
Showscore first puts SCORE_DEC1 in A_SCORE_DEC and SH_REG_1 in A_SH_REG. Then it calls CONVERTSCORE, which converts the decimal score in an appropriate 'DIG' to show. Then the same is done for the SCORE_DEC2 and SH_REG2.
This way, in both the SH_REG variables, the right digit is stored, and ready for displaying. Then we display them by calling INSERTDIGITS.
To make the game harder, the cowboys appear randomly. To achieve this, we used the PRBS method (Pseudo Random Binary Sequence). In this method, two bits of a random sequence are EXORed (= XOR ), the whole sequence is shifted to the right, and the result of the EXOR is added on the left.
(For those who didn't remember:
Bit 1 | Bit 2 | Bit 1 EXOR Bit 2 |
0 | 0 | 0 |
1 | 1 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
)
BUT... this method generates a random series of 2n - 1 bits (with n being the number of bits in the sequence, we used 8 bits).
BUT... this method does not work with every start number and with every combination of bits. We used as a start number 10100111, and we EXORed bits zero and five (as shown in the image). To test possible combinations and start numbers, we wrote a simulation in MATLAB: behold prbs.m!!
To get it working in assembler:
RANDOM EQU H'30'
BIT_0 EQU H'32'
BIT_5 EQU H'33'
...
MOVLW B'10100111' ; The random start number... (arbitrary chosen)
MOVWF RANDOM
...
BTFSC RANDOM, H'0'
BSF BIT_0, H'0'
BTFSC RANDOM, H'5'
BSF BIT_5, H'0'
MOVF BIT_0, 0
XORWF BIT_5, 1
BCF STATUS, C
BTFSC BIT_5, H'0'
BSF STATUS, C
RRF RANDOM, 1
BCF BIT_0, H'0'
BCF BIT_5, H'0'
A short explanation: bit zero and bit five of the register RANDOM are copied in the 'bit zeros' of register BIT_0 and register BIT_5. Then these two registers are EXORed, and the result is placed back in BIT_5. Then bit zero of the result is placed in the carry bit (STATUS, C), and the RANDOM register is shifted one place to the right.
More information on PBRS can be found in the book 'The Art Of Control Engineering', p. 418.
How to show the cowboys at random?
(This is done in the TRIGGER_COWBOYS routine. It is quite a long routine, so I'm going to explain all the parts separately.)
We can now generate a series of random 1's and 0's, but how do we get a random choice between the cowboys? This works as follows: the random method runs three times. Now the three first bits of RANDOM are 'refreshed'. Since it's random, there are 8 different possible combinations for these three bits. Here's what we are going to do, for each combination:
1 | 000 | Show cowboy 1 |
2 | 001 | Show cowboy 2 |
3 | 010 | Show cowboy 3 |
4 | 011 | Show cowboy 4 |
5 | 100 | Show cowboy 5 |
6 | 101 | Show cowboy 6 |
7 | 110 | Do nothing |
8 | 111 | Do nothing |
Now, how do we get the job done in assembler?
MOVF RANDOM, 0
MOVWF DECIDE
MOVLW B'00000111'
ANDWF DECIDE, 1
If you are reading this document top-to-bottom, the time has come to discuss the main loop, so you can understand the stuff below a bit better. (If you are reading this document bottom-to-top, you will now understand what you just read below.).
The main loop is separated in two loops: preloop and mainloop. Before starting the game, preloop shows our names: 'A3' and 'robin' on the display. The original idea was to add a huge start-button, which you could press to start the game. Until the button was pressed, the preloop loop should loop. However, the huge start-button is not yet implemented, so now the program just shows the preloop with our names once, and then jump over to the mainloop. Here's a diagram of the mainloop.
As you can see, there two clocks that decrease, in a construction similar to the DELAY routine (not explained here). This causes the call to TRIGGER_COWBOYS to happen only once in a while. Also, the LDRs (light sensors) are only checked when there is a 1 on port D somewhere. Why all that trouble? Why not simply add a call to the DELAY routine? The answer is that we must constantly check if a cowboy is hit. If you would hit a cowboy during a DELAY, the signal would appear, but the pic would not read it because it's busy doing nothing.
Checking the reset bit is done to see if the game has ended yet and if we have to go back to the preloop.
Each time the TRIGGER_COWBOYS routine is called, the CLOCK2 is being reset (using DA_CLOCK_NUMBAH). For more info on this: see 'Making the game faster' (below).
In the main loop, the routine CHECK_LDRS is called. This routine performs a check on the 6 cowboys if he/she was hit. Here's how it works for one cowboy:
For the last cowboy, we don't have to check the first line: if none of the other cowboys was hit, and the CHECK_LDRS routine was called, it must be that the last one was hit. In the end of CHECK_LDRS, the SHOWSCORE is called to put the (increased) score in the displays.
Then there is one last aspect that needs discussing: the COWBOY_AGE register. We want to show the cowboys a certain time, but not too long. That's why, the first time the TRIGGER_COWBOY routine is called, for the visible cowboys, we set the correct bit in COWBOY_AGE. (Visible cowboys are cowboys that have their corresponding COWBOY_STATS bit set.)
Then, the second time TRIGGER_COWBOY is called, we check if their COWBOY_AGE-bit is set. If so, we clear it, and also clear their COWBOY_STATS bit. This means the cowboys withdraw (without being shot by the player).
Since there are 6 cowboys, this has to be done 6 times each time the TRIGGER_COWBOY routine runs.
Here's an example how it can be accomplished in assembler...
...
check_mr2
BTFSS COWBOY_STATS, H'1'
GOTO check_mr3
BTFSS COWBOY_AGE, H'1'
GOTO its_time_to_go_2
BSF COWBOY_AGE, H'1'
GOTO check_mr3
its_time_to_go_2
BCF COWBOY_STATS, H'1'
BCF COWBOY_AGE, H'1'
check_mr3
...
Et voila....
We want to make the game faster as the player hits more cowboys, to create some sort of 'tetris'-effect. That's why, each time a cowboy is hit, and INCSCORE is called, DA_CLOCK_NUMBAH is decreased a bit. Then, when CLOCK2 is being reset (in the TRIGGER_COWBOYS), using DA_CLOCK_NUMBAH, it will be a bit smaller, so the time until the next TRIGGER_COWBOYS will decrease also.
Well, I guess that's about all there is to say about the software part of the project...