Using Digital Inputs

programming

Let's make our traffic light a bit more interesting by adding a button to it. Ready to learn how you can use a digital input of your Arduino for this?

A New Goal

In the last tutorial we explored how to use pins as a digital output to control LEDs and build our own miniature traffic light. We also discovered how to use the pinMode procedure to configure pins as an output. Remember that there were to other modes available? These are the ones we will explore this time when extending our traffic light with a button.

What is our new goal? Well, in more simple situations where a traffic light is only used to let pedestrians safely cross the street, it is not required that cars need to stop when nobody is there. For this scenario we will need a button. Once the pedestrian presses this button the traffic light will turn red to tell the cars to stop and the pedestrian can pass the street. If nobody is required to pass the street the traffic light just stays green. This is the goal we want to achieve by adding the button and adjusting our code. To get this working we need to wire up a button and replace the delay of 10 s we currently use in the green phase with something that checks whether a pedestrian presses the button.

Wiring up the Button

Wiring up the Button

Let's start by wiring up the button. We will place the button on the breadboard and connect one side to 5 V and the other side to pin 2 of the Arduino. Please make sure to put in the button in the right direction. These buttons usually have four pins and always two of them are permanently connected together forming a pair. When you press the button the two pairs will be connected to each other. If you put in the button in the wrong direction you will permanently connect pin 2 to 5 V, we want them to be only connected when you press the button. You can use a multi meter to check which pins are always connected and which not.

What have we achieved until now? Once you press the button it will connect pin 2 to 5 V and you should be able to detect a HIGH voltage level on pin 2. But what happens when you do not press the button? Nothing will be connected to pin 2 and you have a floating pin. The Arduino will randomly read HIGH or LOW. This is not what we want. We want the Arduino to read LOW when we do not press the button. We can achieve this by adding a 10 kΩ resistor to connect pin 2 to GND. When the button is not pressed this resistor will pull the voltage level to 0 V. and we read LOW. Once we press the button we will still read HIGH and everything works as expected. This is because there is no resistor between pin 2 and 5 V. This way the connection to 5 V will have a much stronger influence on the voltage level on pin 2 than the connection to 0 V. Note that I picked a pretty high resistor of 10 kΩ. It makes sure that there will not be a huge current flowing between GND and 5 V and we get a clear HIGH level on pin 2 when the button is pressed.

We call the resistor we used a pull-down resistor, as it pulls the voltage level of pin 2 down to a stable 0 V, if nothing else is connected to the pin. There exists also a pull-up resistor. It pulls the voltage level to the 5 V, but that's not what we need here.

Adjusting the Code

Now we need to adjust the code. The first step is to configure pin 2 as an input pin in the setup procedure. Do you still remember how its done?
We will use the following procedure for it:

  • pinMode(pin, mode)
    • pin: Number of the digital pin (0 to 13) or A1 to A5 for the analog pins
    • mode: OUTPUT, INPUT, INPUT_PULLUP

For our button we need to set the mode to INPUT. This is what our setup procedure will look like, after applying the change:

void setup() {
  pinMode(8, OUTPUT);  // Green LED
  pinMode(9, OUTPUT);  // Yellow LED
  pinMode(10, OUTPUT); // Red LED

  pinMode(2, INPUT); // Button
}

Up for the hard part? We need to detect a button press and wait for it in the green phase. How can we detect, if the button is pressed? Arduino provides a function called digitalRead for this purpose. As you learned last time a function will have a return value. The return value of digitalRead is either HIGH or LOW depending on the voltage level at the pin. In our case we expect to read HIGH when the button is pressed.
Here is the quick summary of this function and its argument the pin number:

  • digitalRead(pin)
    • pin: Number of the digital pin (0 to 13) or A1 to A5 for the analog pins
    • Returns HIGH or LOW corresponding to the voltage level at the pin

You might have noticed that this is only one part of the solution. We need to compare the output of this function with what we expect and wait in the phase until the button is pressed. For this purpose we need control structure. A control structure controls under which conditions and in which order statements are executed. We will learn new control statements step-by-step. At this point I want to introduce the while loop to you. The while loop executes a single statement or a code block while a certain condition is met. In our case for as long as the button is not pressed we need to wait. Thus, the while loop will fit our purpose.

Let's first look at how we express conditions. Conditions are so-called boolean expressions, they either evaluate to true or false. Take the expression 3 is equals 4 as an example. It will always evaluate to false since the claim that 3 is equals `4 is simply not true. In this claim we use a comparison operator. The 'is equals' operator to compare the to numbers to each other and form the expression.
In C++ we have the following comparison operators:

Operator in words C++ Syntax Boolean result
3 is equals 4 3 == 4 false
3 is not equals 4 3 != 4 true
3 is greater than 4 3 > 4 false
3 is greater or equals than 4 3 >= 4 false
3 is smaller than 4 3 < 4 true
3 is smaller or equals than 4 3 <= 4 true

In addition to comparison operators we also have logical operators. These allow us to combine multiple boolean expression into one. In C++ we have the following logical operators which can be combined as you want to:

Operator C++ Syntax Boolean result
AND Operator: Both expressions are true 3 < 4 && 3 > 4 false
OR Operator : One of the expressions is true 3 < 4 || 3 > 4 true
NOT Operator: The expression is not true !(3 > 4) true

How do we use them together with the while loop. The syntax for the while loop is the following:

while(condition) statement;

If you want to loop over multiple statements aka for a whole code block, we use this form:

while(condition) {
  statement1;
  statement2;
}

We will most commonly see the second option. You can use it even with only one statement inside the code block, therefore you can always use it and keep a consistent style throughout your entire program.

In our case we need to ask ourselves a different question: What statement do we want to execute while the button is not pressed? You might think about using a delay inside the loop. This would be certainly possible, but we would need to be careful to not miss the button press while the delay procedure executes. For this reason we would either have to use a tiny delay or just don't use it at all. There is actually no advantage in using delay at this point. The answer is we don't want to execute anything, we just want to wait until the button is pressed. Luckily the while loop does not force us to executed something inside the loop. We can full fill the syntax by just supplying an empty code block. Together with our condition, this is how it looks like:

while(digitalRead(2) == LOW) {}

As an alternative we can also put an empty statement behind the while loop. To do this we just write a semicolon to end a statement without actually having anything to execute in front of it. It will look like this:

while(digitalRead(2) == LOW);

You can use whatever you like most. Functionally both lines will be identical. If we put everything together, we get the following for our loop procedure:

void loop() {
  // Green
  digitalWrite(8, HIGH);
  while(digitalRead(2) == LOW);
  digitalWrite(8, LOW);

  // Yellow
  digitalWrite(9, HIGH);
  delay(2000);
  digitalWrite(9, LOW);

  // Red
  digitalWrite(10, HIGH);
  delay(10000);

  // Red and Yellow
  digitalWrite(9, HIGH);
  delay(1000);
  digitalWrite(9, LOW);
  digitalWrite(10, LOW);
}


Looking at the Result

Once we finished coding and upload the code to the Arduino Uno, we will see a green LED that just stays on. Once we press the button the traffic light will turn yellow. Everything works as expected. As you see we need quite some space for our button, the external resistors and all the wires. For such a common use case there has to be a simpler solution without external components. This solution is called internal pull-up resistor. Let's step forward and learn how to use it.

Traffic light with button

Using the Internal Pull-up Resistor

There is an alternative input mode called INPUT_PULLUP and as you might have guessed already it has something to do with pull-up resistors. The Arduino has an embedded pull-up resistor for each pin. This makes it a lot easier to connect things to digital inputs, as we don't need an external resistor. But, wait! We were using a pull-down resistor before, weren't we? Yes, your are correct. Sadly, the Arduino Uno only has pull-up resistors. There are other microcontrollers that incorporate pull-down resistors as well. How can we get along with this?

Well, there is a quite simple solution. Till now, we connected pin 2 to 5 V when pressing the button. This allowed us to read back HIGH with the Arduino when the button is pressed. HIGH or 1 which we can only use to check for a high voltage level fit well to the button pressed state in our minds. Button pressed being equals a HIGH level, is however not a god given thing. Using the internal pull-up pin 2 will be connected to 5 V if nothing else is connected. We can use the button to connect pin 2 to GND when it is pressed. This way we will read back HIGH when the button is not pressed and LOW when it's pressed. This inversion might be a bit confusing at first, but it allows us to use the internal pull-up like a charm. By the way, what's the value of the internal pull-up you might ask. In the data sheet of the microcontroller it described to have a value between 20 kΩ and 50 kΩ. So, it is actually a little bigger than my 10 kΩ resistor we used as an external pull-down before, but this is of no harm for our use case.

Let's change our wiring according to the new scheme:

Wiring up the Button with Internal Pull-up

We also need to adjust our code. In the setup-procedure just replace pinMode(2, INPUT) with pinMode(2, INPUT_PULLUP). Next we have to adjust the condition the used to check if the button is pressed. As we will now read LOW once the button is pressed we need to loop as long as we read HIGH and the button is not pressed. Just change digitalRead(2) == LOW to digitalRead(2) == HIGH, upload it your Arduino and everything should work fine in this new configuration.

void setup() {
  pinMode(8, OUTPUT);  // Green LED
  pinMode(9, OUTPUT);  // Yellow LED
  pinMode(10, OUTPUT); // Red LED

  pinMode(2, INPUT_PULLUP); // Button
}

void loop() {
  // Green
  digitalWrite(8, HIGH);
  while(digitalRead(2) == HIGH);
  digitalWrite(8, LOW);

  // Yellow
  digitalWrite(9, HIGH);
  delay(2000);
  digitalWrite(9, LOW);

  // Red
  digitalWrite(10, HIGH);
  delay(10000);

  // Red and Yellow
  digitalWrite(9, HIGH);
  delay(1000);
  digitalWrite(9, LOW);
  digitalWrite(10, LOW);
}

Working as Expected?

Let's look at our result. You should get something similar to the behavior shown in the video below. If you do nothing the traffic light will just stay green and if you press the button it immediately turns yellow and then red afterwards. Goal accomplished? Yes, kind of, but let's be honest this would be a quite annoying traffic light for drivers. Most normal traffic lights will not immediately react to a pedestrian pressing the button. Actually we have one in our town that does – but only if no one has pressed the button for a long time before. If we look at our traffic light it will never turn green if we press the button constantly, so we need to improve this and add some sort of cooldown. This will be our task in the next tutorial. How would you solve this?

Previous Post Next Post