The great advantage of programmable electronics over classic electronics now becomes evident: I will show you how to implement many different “behaviours” using the same electronic circuit as in the previous section, just by changing the software.
As I’ve mentioned before, it’s not very practical to have to hold your finger on the button to have the light on. You therefore must implement some form of “memory”, in the form of a software mechanism that will remember when you have pressed the button and will keep the light on even after you have released it.
To do this, you’re going to use what is called a variable. (You have used one already, but we haven’t explained it.) A variable is a place in the Arduino memory where you can store data. Think of it like one of those sticky notes you use to remind yourself about something, such as a phone number: you take one, you write “Luisa 02 555 1212” on it, and you stick it to your computer monitor or your fridge. In the Arduino language, it’s equally simple: you just decide what type of data you want to store (a number or some text, for example), give it a name, and when you want to, you can store the data or retrieve it. For example:
int val = 0;
int
means that your variable will store an integer number, val
is the name of the variable, and = 0
assigns it an initial value of zero.
A variable, as the name intimates, can be modified anywhere in your code, so that later on in your program, you could write:
val = 112;
which reassigns a new value, 112, to your variable.
NOTE
Have you noticed that in Arduino, every instruction ends with a semicolon? This is done so the compiler (the part of Arduino that turns your sketch into a program that the microcontroller can run) knows your statement is finished and a new one is beginning. If you forget a semicolon where one is required, the compiler won’t be able to make sense of your sketch.
In the following program, the variable val
stores the result of digitalRead()
; whatever Arduino gets from the input ends up in the variable and will stay there until another line of code changes it. Notice that variables use a type of memory called RAM. It is quite fast, but when you turn off your board, all data stored in RAM is lost (which means that each variable is reset to its initial value when the board is powered up again). Your programs themselves are stored in flash memory—this is the same type used by your mobile phone to store phone numbers—which retains its content even when the board is off.
Let’s now use another variable to remember whether the LED has to stay on or off after we release the button. Example 4-3 is a first attempt at achieving that.
Example 4-3. Turn on LED when the button is pressed and keep it on after it is released
const int LED = 13; // the pin for the LED
const int BUTTON = 7; // the input pin where the
// pushbutton is connected
int val = 0; // val will be used to store the state
// of the input pin
int state = 0; // 0 = LED off while 1 = LED on
void setup() {
pinMode(LED, OUTPUT); // tell Arduino LED is an output
pinMode(BUTTON, INPUT); // and BUTTON is an input
}
void loop() {
val = digitalRead(BUTTON); // read input value and store it
// check if the input is HIGH (button pressed)
// and change the state
if (val == HIGH) {
state = 1 - state;
}
if (state == 1) {
digitalWrite(LED, HIGH); // turn LED ON
} else {
digitalWrite(LED, LOW);
}
}
Now go test this code. You will notice that it works…somewhat. You’ll find that the light changes so rapidly that you can’t reliably set it on or off with a button press.
Let’s look at the interesting parts of the code: state
is a variable that stores either 0 or 1 to remember whether the LED is on or off. After the button is released, we initialise it to 0 (LED off).
Later, we read the current state of the button, and if it’s pressed (val == HIGH
), we change state
from 0 to 1, or vice versa. We do this using a small trick, as state
can be only either 1 or 0. The trick I use involves a small mathematical expression based on the idea that 1 – 0 is 1 and 1 – 1 is 0:
state = 1 - state;
The line may not make much sense in mathematics, but it does in programming. The symbol = means “assign the result of what’s after me to the variable name before me”—in this case, the new value of state
is assigned the value of 1 minus the old value of state
.
Later in the program, you can see that we use state
to figure out whether the LED has to be on or off. As I mentioned, this leads to somewhat flaky results.
The results are flaky because of the way we read the button. Arduino is really fast; it executes its own internal instructions at a rate of 16 million per second—it could well be executing a few million lines of code per second. So this means that while your finger is pressing the button, Arduino might be reading the button’s position a few thousand times and changing state
accordingly. So the results end up being unpredictable; it might be off when you wanted it on, or vice versa. As even a broken clock is right twice a day, the program might show the correct behaviour every once in a while, but much of the time it will be wrong.
How do you fix this? Well, you need to detect the exact moment when the button is pressed—that is the only moment that you have to change state
. The way we like to do it is to store the value of val
before we read a new one; this allows you to compare the current position of the button with the previous one and change state
only when the button changes from LOW to HIGH.
Example 4-4 contains the code to do so.
Example 4-4. New and improved button press formula!
const int LED = 13; // the pin for the LED
const int BUTTON = 7; // the input pin where the
// pushbutton is connected
int val = 0; // val will be used to store the state
// of the input pin
int old_val = 0; // this variable stores the previous
// value of "val"
int state = 0; // 0 = LED off and 1 = LED on
void setup() {
pinMode(LED, OUTPUT); // tell Arduino LED is an output
pinMode(BUTTON, INPUT); // and BUTTON is an input
}
void loop(){
val = digitalRead(BUTTON); // read input value and store it
// yum, fresh
// check if there was a transition
if ((val == HIGH) && (old_val == LOW)){
state = 1 - state;
}
old_val = val; // val is now old, let's store it
if (state == 1) {
digitalWrite(LED, HIGH); // turn LED ON
} else {
digitalWrite(LED, LOW);
}
}
You’ll notice something new in the if
statement: there are two comparisons separated by a new symbol: &&
. This symbol performs the logical AND operation, meaning that the compound statement is true only if both of the two simple statements are true.
Now test this code: you’re almost there!
You may have noticed that this approach is not entirely perfect, due to another issue with mechanical switches.
As we explained earlier, pushbuttons are just two bits of metal kept apart by a spring, that come into contact when you press the button. This might seem like the switch should be completely on when you press the button, but in fact what happens is the two pieces of metal bounce off each other, just like a ball bounces on the floor.
Although the bouncing is only for a very small distance and happens for a fraction of a second, it causes the switch to change between off and on a number of times until the bouncing stops, and Arduino is quick enough to catch this.
When the pushbutton is bouncing, the Arduino sees a very rapid sequence of on and off signals. There are many techniques developed to do debouncing, but in this simple piece of code, it’s usually enough to add a 10- to 50-millisecond delay when the code detects a transition. In other words, you just wait a bit for the bouncing to stop.
Example 4-5 is the final code.
Example 4-5. Another new and improved formula for button presses—with simple debouncing!
const int LED = 13; // the pin for the LED
const int BUTTON = 7; // the input pin where the
// pushbutton is connected
int val = 0; // val will be used to store the state
// of the input pin
int old_val = 0; // this variable stores the previous
// value of "val"
int state = 0; // 0 = LED off and 1 = LED on
void setup() {
pinMode(LED, OUTPUT); // tell Arduino LED is an output
pinMode(BUTTON, INPUT); // and BUTTON is an input
}
void loop(){
val = digitalRead(BUTTON); // read input value and store it
// yum, fresh
// check if there was a transition
if ((val == HIGH) && (old_val == LOW)){
state = 1 - state;
delay(10);
}
old_val = val; // val is now old, let's store it
if (state == 1) {
digitalWrite(LED, HIGH); // turn LED ON
} else {
digitalWrite(LED, LOW);
}
}
One reader, Tami (Masaaki) Takamiya, wrote in with some extra code that may give you better debouncing:
if ((val == LOW) && (old_val == HIGH)) {
delay(10);
}
1LEDs are included in the kit mentioned in the preface.
2All of these parts are included in the kit mentioned in the preface.
Leave a Reply