In this project you will create a circuit with three buttons and three LEDs as shown in Fig. 1. When controlling lights, typically you are used to associating a switch with a given light or set of lights—if you change the position of a switch, the same lights always turn off or on. However, in this project the chipKIT™ board comes between the “switches” (buttons) and the lights (LEDs). In this way we can use software (a sketch) to dictate the ways in which the buttons control the LEDs. The interaction of lights and switches is no longer “hardwired” (i.e., no longer a direct result of the physical connections in the circuit). Instead, if we want to change the way the buttons control the lights, we can simply change the sketch. To accomplish what is needed for this project, we must further explore the “condition” on which if-statements make decisions. Specifically, we will consider logical expressions using and and or. Along the way we will also consider how we can specify the behavior of a circuit using a truth table.
Qty | Description | Typical Image | Schematic Symbol | Breadboard Image |
---|---|---|---|---|
3 | Two-port buttons | ![]() |
||
3 | LEDs | ![]() |
||
3 | 220 Ω resistors | ![]() |
||
3 | 10 kΩ resistors (10 kΩ = 10,000 Ω) |
![]() |
When a button is pressed, it supplies 3.3V to the corresponding digital I/O pin. The chipKIT Max32™ and Uno32 boards are designed to recognize a range of voltages—from a minimum of 2.4V to a maximum of 5.5V—as a HIGH input for the digital I/O pins. That is, when the function digitalRead() is called, it returns HIGH if the voltage on the pin being read is within this range. If the voltage is below 2.4V, the digitalRead() function returns LOW. The digitalRead() function will be discussed later in this project.
At this point, you should now have all the buttons and LEDs in the breadboard as shown in Fig. 4. The next step is to write the sketch that controls the circuit. Because this sketch uses if statements and comparison operators, we will first introduce those programming constructors.
The schematic diagram of the circuit is shown in Fig. 4.
A PDF version of this schematic is available here.
We now need to specify how the three buttons will control the three LEDs. The most “obvious” approach is almost undoubtedly to have button A control LED A, button B control LED B, and button C control LED C. In other words, if button A is pressed, then LED A is illuminated regardless of the state of the other buttons. Let's get a bit more complicated than that.
In our circuit we have three buttons and thus there eight ($2^3=8$) possible combination of button states. We have the freedom to specify what each combination of inputs translates to in terms of illuminating the LEDs. Let us express the relationship between the “inputs” (button states) and the “outputs” (LED states) in the form of a truth table. A truth table shows all the possible input states and the output states that correspond to those input states—each row of the table corresponds to a particular combination of inputs and the corresponding output for those inputs. We will use a 0 (zero) to indicate that a button is not pushed and a 1 to indicate that it is pushed. If truth tables are new to you, please click on the tab on the right to learn more about them.
First, before creating the truth table, let's describe the behavior of the circuit in narrative form. If one button is pushed, the corresponding LED should be illuminated. If two buttons are pushed, the LED corresponding to the non-pushed button should be illuminated. And, finally, if none of the buttons are pushed or all of the buttons are pushed, then none of the LEDs should be illuminated. The truth table corresponding to this system is (outputs are indicated with a yellow background):
BTN A | BTN B | BTN C | LED A | LED B | LED C |
---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 |
0 | 0 | 1 | 0 | 0 | 1 |
0 | 1 | 0 | 0 | 1 | 0 |
0 | 1 | 1 | 1 | 0 | 0 |
1 | 0 | 0 | 1 | 0 | 0 |
1 | 0 | 1 | 0 | 1 | 0 |
1 | 1 | 0 | 0 | 0 | 1 |
1 | 1 | 1 | 0 | 0 | 0 |
Note the conditions that cause LED A to illuminate. In English we might say, “Illuminate LED A if button A is pushed and buttons B and C are not pushed, or illuminated it if button A is not pushed and buttons B and C are pushed. Otherwise, do not illuminate LED A.” Now the questions becomes: How do we translate this statement into C/C++? To do so, we need to use logical operators which are the subject of the next section.
Sometimes we need to check if two conditions are simultaneously true. Conversely, sometimes we need to check if one or another condition is true. We can use the logical “and” operator (written &&) to test if two conditions are simultaneously true. If we want to test if one or another condition is true, i.e., test if either condition is true, we can use the “or” operator (written ||). If these operators are new to you, please read more about them via the tab on the right.
For example, assuming that the pins btnPinA and btnPinB have been properly initialized to correspond to the states of button A and button B, respectively, the following expression will evaluate to true if both buttons are pressed:
digitalRead(btnPinA) == HIGH && digitalRead(btnPinB) == HIGH
Read “&&” as “and.” Thus, this expression essentially evaluates to the answer of: is button A pressed and button b pressed? Or, said another way, are both button A and B pressed? This expression would be used, for instance, as the condition in an if-statement if we wanted to execute a particular block of code when both buttons are pressed.
In contrast, if we wanted to execute a particular block of code when one or the other button is pressed (or both are pressed), the expression that would be used as the condition in an if-statement would be:
digitalRead(btnPinA) == HIGH || digitalRead(btnPinB) == HIGH
Read “||” as “or.” This expression evaluates to true if either or both buttons are pressed.
We can construct logical expressions that involve multiple “and” and “or” operations to form compound logical expressions. More details concerning such expressions can be found via the tab on the right. As an example, assume we want to check if button A is pressed or buttons B and C are pressed (so, we want to obtain true if A is pressed [independent of B and C] or if both B and C are pressed [independent of A]). The appopriate expression is:
digitalRead(btnPinA) == HIGH || digitalRead(btnPinB) == HIGH && digitalRead(btnPinC) == HIGH
This expression relies on the fact that the “and” operator has higher precedence than the “or” operator. If we are unsure of the order of precedence, we can always add parentheses. The following expression is equivalent to the one above.
digitalRead(btnPinA) == HIGH || (digitalRead(btnPinB) == HIGH && digitalRead(btnPinC) == HIGH)
If we wanted the expression to evaluate to true if either button A or B were pressed while, at the same time, button C were pressed, we would need to use parentheses as follows:
(digitalRead(btnPinA) == HIGH || (digitalRead(btnPinB) == HIGH) && digitalRead(btnPinC) == HIGH
Provided you understand these logical constructs, you now have all the software tools at your disposal to implement the entire sketch.
The following sketch implements the control system described above, where the push of a single button will illuminate the corresponding LED, but the push of two buttons will illuminate the LED corresponding to the unpushed button. There are a couple of things you should note about this sketch. First, all the variables are declared const (i.e., to be constant). This seems okay for the variables that correspond to the input and output pins, but declaring the button states as const may seem incorrect because clearly the button states can change. However, for each execution of the loop() function, the button states are read once and do not change throughout the execution of the function. When the function has completed execution, the variables are “discarded” and read in anew when the function is executed again. The other thing to note is that in this implementation we check if all the buttons are pressed or none of the buttons are pressed. You should ask yourself: Given the way the rest of the sketch is written, is this truly necessary? (The answer is no, it isn't truly necessary! But there is no harm in doing things this way. In fact, one could actually make arguments that this is the best way to implement the controller if the typical state is to have all or none of the buttons pressed—this implementation would then typically have fewer conditions to test for each execution of the loop() function.)
/* The input from three buttons is used to control the
* the illumination of three LEDs. If a single button
* is pushed, the associated LED is illuminated. If two
* buttons are pushed, only the LED associated with the
* unpushed LED is illuminated. If all of the buttons
* are pushed or if none of the buttons are pushed, then
* none of the LEDs are illuminated.
*/
const int btnPinA = 7;
const int btnPinB = 6;
const int btnPinC = 5;
const int ledPinA = 12;
const int ledPinB = 11;
const int ledPinC = 8;
void setup() {
// Configure the buttons for input.
pinMode(btnPinA, INPUT);
pinMode(btnPinB, INPUT);
pinMode(btnPinC, INPUT);
// Configure the LEDs for output.
pinMode(ledPinA, OUTPUT);
pinMode(ledPinB, OUTPUT);
pinMode(ledPinC, OUTPUT);
}
void loop() {
// Obtain the states of all three buttons.
const int btnStateA = digitalRead(btnPinA);
const int btnStateB = digitalRead(btnPinB);
const int btnStateC = digitalRead(btnPinC);
// If all or none of the buttons are pushed,
// then all the LEDs should be off.
if ((btnStateA == LOW && btnStateB == LOW && btnStateC == LOW) ||
(btnStateA == HIGH && btnStateB == HIGH && btnStateC == HIGH)) {
digitalWrite(ledPinA, LOW);
digitalWrite(ledPinB, LOW);
digitalWrite(ledPinC, LOW);
}
else {
// Determine whether or not LED A should be on.
if ((btnStateA == LOW && btnStateB == HIGH && btnStateC == HIGH) ||
(btnStateA == HIGH && btnStateB == LOW && btnStateC == LOW)) {
digitalWrite(ledPinA, HIGH);
}
else {
digitalWrite(ledPinA, LOW);
}
// Determine whether or not LED B should be on.
if ((btnStateA == HIGH && btnStateB == LOW && btnStateC == HIGH) ||
(btnStateA == LOW && btnStateB == HIGH && btnStateC == LOW)) {
digitalWrite(ledPinB, HIGH);
}
else {
digitalWrite(ledPinB, LOW);
}
// Determine whether or not LED C should be on.
if ((btnStateA == HIGH && btnStateB == HIGH && btnStateC == LOW) ||
(btnStateA == LOW && btnStateB == LOW && btnStateC == HIGH)) {
digitalWrite(ledPinC, HIGH);
}
else {
digitalWrite(ledPinC, LOW);
}
}
}