One Circuit, A Thousand Behaviours

The great advantage of digital, 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. We therefore must implement some form of "memory", in the form of a software mechanism that will remember when we have pressed the button and will keep the light on even after we have released it.

To do this, we're going to use what is called a variable. (We have used one already, but I 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, with one exception (#define), ends with a semicolon? This is done so that the compiler (the part of Arduino that turns your sketch into a program that the microcontroller can run) knows that your statement is finished and a new one is beginning. Remember to use it all the time, excluding any line that begins with #define. The #defines are replaced by the compiler before the code is translated into an Arduino executable.

In the following program, val is used to store 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

#define LED  13  // the pin for the LED
#define 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 we fix this? Well, we need to detect the exact moment when the button is pressed—that is the only moment that we have to change state. The way I like to do it is to store the value of val before I read a new one; this allows me to compare the current position of the button with the previous one and change state only when the button becomes HIGH after being LOW.

Example 4-4 contains the code to do so:

Example 4-4. Turn on LED when the button is pressed and keep it on after it is released Now with a new and improved formula!

#define LED  13   // the pin for the LED
#define 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);
  }
}

Test it: we're almost there!

You may have noticed that this approach is not entirely perfect, due to another issue with mechanical switches. Pushbuttons are very simple devices: two bits of metal kept apart by a spring. When you press the button, the two contacts come together and electricity can flow. This sounds fine and simple, but in real life the connection is not that perfect, especially when the button is not completely pressed, and it generates some spurious signals called bouncing.

When the pushbutton is bouncing, the Arduino sees a very rapid sequence of on and off signals. There are many techniques developed to do de-bouncing, but in this simple piece of code I've noticed that it's usually enough to add a 10- to 50-millisecond delay when the code detects a transition.

Example 4-5 is the final code:

Example 4-5. Turn on LED when the button is pressed and keep it on after it is released including simple de-bouncing Now with another new and improved formula!!

#define LED  13   // the pin for the LED
#define 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);
  }
}

Get Getting Started with Arduino now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.