Microcontroller Tutorial Part 10: Creating a Game (The Button Game)
by RobotDictionary in Circuits > Microcontrollers
3085 Views, 27 Favorites, 0 Comments
Microcontroller Tutorial Part 10: Creating a Game (The Button Game)
Now we're ready to apply what we know and make an actual game. The game will consist of a couple of push buttons and two sets of LEDs (2 rows of 7). The objective of the game is to see who between two people can push their button the most times in the smallest amount of time. As the button is pressed, the LEDs light up one after the other until the last LED is lit and that side wins. The winner is indicated by the LEDs on their side flashing.
The circuit consists of two rods of LEDs, 7 on each row. Since there are seven on each side, a single port is able to be used for each set with ample room for a push button on each port. One push button and seven LEDs can be wired to eight pins or one port of pins. In the video we use Port B for one player's button and LEDs and port D for the other player's button and LEDs.
Programming Fundamentals
Three fundamental programming features have been illustrated in our video: arrays, encapsulation and additional info on variables. Arrays are used to simplify the variables used for each player. Instead of generating unique variables for button debouncing, button press and LEDs to be lit, a single variable was used for each containing an array of "2", one for each player. This also made encapsulation and repetitive code use much more simplified. If code is to be repurposed in the program more than once, it makes sense to put that code in the special place called a function (method) so it can be labeled whenever it is needed. Since there were two players, we used an identical code that was originally going to be used twice when the button press and release is tested and the code that is used to illuminate the LEDs.
Specifically, in the program, you will see a few new things. With the creation of new functions, we can essentially create what looks like new commands that the program will undestand. These functions are called ProcessPressedButton and ProcessReleasedbutton. With the help of the arrays, all that needs to be done is the array number (player number) be passed into these functions. In the program, the first thing you will notice is that there are a couple of statements with "void" at the beginning. These are called function prototypes. The language of C, or C++ requires these function prototypes to inform the compiler that these are used in the program before they are actually defined. Notice that the functions are used in the program (within the main function) before the actual functions and code (after the main function). An alternative to this would be to actually put the main function at the end of the program, and remove the prototypes, but I personally like keeping the main function at the beginning for clarity. There is even a better alternative: to create a couple of library files that contain these new functions, but we have not gotten to this level of programming yet at this point in the tutorial series.
You will also notice that there are integers (variables) outside of the main function (ignore the brackets and number 2 for now). This will force these variables to have a global scope. Scope is where the declared variables will be used. If a variable is declared within a specific code block, like in a function, then the variable will live and die in this code block, and any code blocks within this block. For instance, if the variables are declared within the main function, these variables cannot be used within another function that resides outside the main function. If the variables are defined at the bottom most level (outside of any block), they become global, and any code block can use them.
Now the arrays... notice the [2] at the end of the global variables. This allows for two of these same variables to be created and differentiated by using a [0] or [1]. Why "0"? The numbers are indexed from 0, not 1. So, take the Pressed[2] as an example. This creates a variable called Pressed[0] and a Pressed[1]. Notice in the functions that I have a variable within the brackets. Encapsulation and arrays can provide some really cool features in programming.
The Code
#include <avr/io.h> #include <util/delay.h> void ProcessPressedButton(int ButtonPressed); void ProcessReleasedButton(int ButtonReleased); int Pressed_Confidence_Level[2]; int Released_Confidence_Level[2]; int Pressed[2]; int LEDNumber[2]; int main(void) { DDRB = 0b01111111; DDRD = 0b01111111; PORTB = 0b10000000; PORTD = 0b10000000; while (1) { if (bit_is_clear(PINB, 7)) { ProcessPressedButton(0); } else { ProcessReleasedButton(0); } if (bit_is_clear(PIND, 7)) { ProcessPressedButton(1); } else { ProcessReleasedButton(1); } } } void ProcessPressedButton(int ButtonPressed) { Pressed_Confidence_Level[ButtonPressed] ++; if (Pressed_Confidence_Level[ButtonPressed] > 500) { if (Pressed[ButtonPressed] == 0) { Pressed[ButtonPressed] = 1; if (ButtonPressed == 0) PORTB |= 1 << LEDNumber[ButtonPressed]; if (ButtonPressed == 1) PORTD |= 1 << LEDNumber[ButtonPressed]; LEDNumber[ButtonPressed] ++; if (LEDNumber[ButtonPressed] >6) { for(int i=0;i < 10;i++) { if (ButtonPressed == 0) PORTB = 0b11111111; if (ButtonPressed == 1) PORTD = 0b11111111; _delay_ms(10); if (ButtonPressed == 0) PORTB = 0b10000000; if (ButtonPressed == 1) PORTD = 0b10000000; _delay_ms(10); } LEDNumber[0] = 0; LEDNumber[1] = 0; PORTB = 0b10000000; PORTD = 0b10000000; } } Pressed_Confidence_Level[ButtonPressed] = 0; } }void ProcessReleasedButton(int ButtonReleased) { Released_Confidence_Level[ButtonReleased] ++; if (Released_Confidence_Level[ButtonReleased] > 500) { Pressed[ButtonReleased] = 0; Released_Confidence_Level[ButtonReleased] = 0; } }
Creating a Button Library
This is the program that contains all of the code that determines the button state and keeps track of the confidence levels for the button press and release for eliminating the debouncing effect. This library file is named: "ButtonPress.h" which you can see in the include file of the main program. If you copy and paste this code and save it as another name, you will need to change the include file for the main program.
#ifndef ButtonPress
#define ButtonPress
include <avr/io.h> char ButtonPressed(int buttonNumber, unsigned char pinOfButton, unsigned char portBit, int confidenceLevel);
char Pressed[numberOfButtons];
int Pressed_Confidence_Level[numberOfButtons]; //Measure button press cofidence
int Released_Confidence_Level[numberOfButtons]; //Measure button release confidence
char ButtonPressed(int buttonNumber, unsigned char pinOfButton, unsigned char portBit, int confidenceLevel)
{
if (bit_is_clear(pinOfButton, portBit)) { Pressed_Confidence_Level[buttonNumber] ++; //Increase Pressed Conficence Released_Confidence_Level[buttonNumber] = 0; //Reset released button confidence since there is a button press if (Pressed_Confidence_Level[buttonNumber] > confidenceLevel) //Indicator of good button press { if (Pressed[buttonNumber] == 0) { Pressed[buttonNumber] = 1; return 1;
} //Zero it so a new pressed condition can be evaluated Pressed_Confidence_Level[buttonNumber] = 0;
}
} else { Released_Confidence_Level[buttonNumber] ++; //This works just like the pressed Pressed_Confidence_Level[buttonNumber] = 0; //Reset pressed button confidence since the button is released if (Released_Confidence_Level[buttonNumber] > confidenceLevel) { Pressed[buttonNumber] = 0; Released_Confidence_Level[buttonNumber] = 0;
}
} return 0;
}
#endif
The Actual Program That We Would Write Using the Button Press Library
#define numberOfButtons 2
include <avr/io.h>
#include"ButtonPress.h"
int main(void)
{
DDRB = 0b00001100;
PORTB = (1 << PINB0)|(1 << PINB1);
while (1) { if (ButtonPressed(0, PINB, 0, 100)) PORTB ^= (1 << PINB2);
if (ButtonPressed(1, PINB, 1, 100)) PORTB ^= (1 << PINB3);
}
}
Notice how short the main program is now. Most of the code for the button presses and software debouncing is abstracted out in a library. All you need to do is include the ButtonPress.h file and use a define statement at the beginning of the program to inform the compiler how many buttons you wish to use. To determine if a button is pressed, just make an "if" statement with the information relating to the specific button like the button number, the pin and port and the threshold of the confident level for the software debouncing.