Using Digital Outputs

programming

The Blink example was a nice program to test the Arduino board, but how does it work and how can we make something bigger? Let's get started with controlling the digital output pins of your Arduino.

In the last example we used the Blink example to test if our Arduino IDE installation works correctly. But how does this simple program work and what are the takeaways for our own projects?

Let's start with the overall structure of the example code. As every Arduino program, it consists of two blocks: The setup and the loop procedure.

// the setup function runs once when you press reset or power the board
void setup() {
  // initialize digital pin LED_BUILTIN as an output.
  pinMode(LED_BUILTIN, OUTPUT);
}

// the loop function runs over and over again forever
void loop() {
  digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(1000);                       // wait for a second
  digitalWrite(LED_BUILTIN, LOW);    // turn the LED off by making the voltage LOW
  delay(1000);                       // wait for a second
}

A procedure is just a group of statements grouped together under a human-readable name. To group these statements we use curly braces { } and create a code block. The name is used to later call and execute this code block. In the world of Arduino there are two procedures that are called automatically: The setup and the loop procedure. So what is the purpose of this two procedures? The idea of the Arduino creators is that you use the setup procedure to configure everything you need for your actual program to run. It is the initialization routine of your program and will only be run once when the Arduino gets reset by reprogramming it, pressing the button or turning the power on and off. After the setup procedure has run the Arduino will run the loop procedure in a never ending loop - just like the name says. I depicted this behavior in the little diagram below.

[flow] st=>start: Start of Execution setup=>operation: setup|success loop=>operation: loop|success

st->setup->loop loop->loop [/flow]

Why this never ending loop? Well, most embedded systems will never really finish their task. Once one thing is done, they are usually waiting for user input and then continue with whatever the user wants them to do. The execution never ends until you turn off the power. This is why this loop scheme is a perfect fit here.

By the way, you have probably noticed that there are also explanatory parts in the code that tell you what these two procedures do. These parts are called comments and can contain an arbitrary text that means absolutely nothing to the compiler. Comments are just for other human beings to help them understand the code. A comment is introduced by two slashes: // comment text here.
Everything that comes after this two slashes int this line will be ignored by the compiler. There is an alternative way of creating comments that also supports multiple lines. It looks like this:

/*
   Comment text here
 */

If you look at the example code in the Arduino IDE, this type of comment is used for the long text part right at the beginning of the file. Everything in between /* and */ is ignored by the compiler.

You already know that there are two blocks of code and a bunch of explanatory comments, but what's in them and why are there empty braces after the name and this strange void in front of the name. This void is called a return type and just means that there is nothing returned by this two procedures. That does not seem to be very useful, you might think, and what does it mean to return something. In math, we only have functions. A functions takes one or more parameters like x and produces a value which is called the return value in programming. Take for example the simple function f(x) = 2x - if you put in 3 for x the result will be 6. In programming, we have functions too, but we do not only have calculations but also commands which say 'do this or that'. In the case of just running commands, there won't be a result to return. This is exactly the case for our two procedures setup and loop. Procedures are functions that return nothing and thus have the return type void. Just like our mathematical function, functions in programming can have parameters too. In our case this is not the case and this is why we have the empty braces after the name of our procedures. If our procedures required parameters these would need to be declared in between these braces. Let's not get hung up here for too long. We will have an in depth look at data types, return types and functions in a later tutorial anyway.

Now let's look at the content of our two code blocks. There is a bunch of so-called statements in there. There are different types of statements that are allowed in a procedure or function. In this example we only have one type: Procedures calls. Just like we have defined the two procedures setup and loop in the example code, there are a bunch of other procedures which are already predefined.
Before we get into what these procedures do and how the example code is able to produce the blinking LED with them, I want to point you to an important detail: Each statement is ended with a semicolon. This is really important as the compiler does not care about the line breaks in our code but instead uses this semicolon to determine where a statement ends. If you don't believe me, just try it yourself and remove one of the line breaks. The code will still work after that. If it doesn't, you probably forgot to remove the comment. Remember everything behind the two slashes will be completely ignored by the compiler.

What predefined procedures are used in the Blink example? Well, there a three different procedures pinMode, digitalWrite and delay called by the example code. Let's start with the delay procedure which meaning should be not hard to guess and is given in the comment as well. Let's look at the procedure call:

  delay(1000);

A procedure can be called by putting down its name and then supplying the parameter values in braces directly after the name. If a procedure does not require any parameters, you just put a pair of empty braces after the name. Multiple parameters can be separated using a comma, as we see in the other procedure calls. The delay procedure has only a single parameter: The time to wait in milliseconds.

The two other procedures are used to control the digital outputs of our Arduino Uno. The pinMode procedure is used tell the Arduino Uno that we want to use the pin connected to the LED as output. As parameter values LED_BUILTIN, which is a placeholder for pin 13 to which the internal LED is connected to, and OUTPUT, to change the pin mode to digital output, are given. You can use pins as a digital input too, at which we will have a look at in the next tutorial. The pin configuration needs to be done only once and is thus put in the setup procedure.
The second procedure is called digitalWrite and changes the actual voltage level of the pin. This can be either 5 V (HIGH) or 0 V (LOW). The voltage level is the second parameter supplied to the digitalWrite procedure, the first one is again the number of the pin to use.

What happens in the loop procedure? The first call to digitalWrite turns the LED on by supplying 5 V voltage through pin 13 to it. Then there is a call to delay and the program waits for 1 s. After this there is another call to digitalWrite to turn the LED off and a call to delay to wait 1 s again. After this the whole loop procedure is executed over and over again, the LED gets continuously turned on and off. It's that simple, once you understand the syntax.

To finish up, here is a quick summary of all the predefined procedures used in the example:

  • 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
  • digitalWrite(pin, value)
    • pin: Number of the digital pin (0 to 13) or A1 to A5 for the analog pins
    • value: HIGH or LOW
  • delay(ms)
    • ms: Time in milliseconds

A more complex Example

Now that I explained the Blink example to you, it's time to adopt the knowledge you gained to a different problem. For the sake of simplicity, let's not get to fancy and keep using LEDs. Of course, you can use the digital outputs of your Arduino Uno for a totally different thing to.

For this tutorial I picked an example that probably everybody knows well. Traffic lights. You have three lamps colored red, green and yellow, that tell you whether you are allowed to pass a crossing or not. To build our own miniature traffic light we need to adopt the code to this new problem and create a circuit on a breadboard, as wee need three LEDs of different colors instead of the one LED that the Arduino Uno provides.

Building the Circuit

Wiring up the LEDs

The circuit required for our simple traffic light is really simple: Three LEDs each in series with a resistor are hooked up to pins 8 to 10 of the Arduino Uno. I used 220 Ω resistors to limit the current flowing through each LED. These resistors are needed to protect the LED. I will explain why this is needed and how you calculate the resistance value yourself in the upcoming electronic basics series. For now just stick to the 220 Ω or at least choose a similar value. You should not pick one under 180 Ω in order to keep your LEDs safe. Remember that LEDs need to be connected in the correct polarity. In the build-up plan the negative sides or cathodes of the LEDs are connected to the resistors and the positive side is connected directly to the Arduino pins. You can determine which side of the LED is which by searching for the flattened side. This is the negative side of the LED.

I chose the following pin assignment for the LEDs:

LED Pin
Green LED 8
Yellow LED 9
Red LED 10

Defining our Goal

Before we start writing the code for our traffic light, let's think a moment about what we want to achieve. A normal traffic light (at least here in Europe) has 4 phases. We want to have a long phase where only the green LED is lit and the cars are allowed to pass. The next phase will be a shorter period of only the yellow LED, where cars have time to brake, before the traffic light turns red finally. These are the three phases everyone is thinking of, but there is one more phase. Just before the traffic light becomes green, there will be a really short period where both the red and the yellow light will turn on. Why not use another phase of yellow? Well, there is an obvious reason. Car drivers should be able to differentiate between a traffic light that becomes red and one that becomes green, so that they know whether they need to brake or not.
Now that we know what our traffic light should do, let's start writing the code.

Writing the Code

As explained at the beginning the program execution starts with the setup procedure. What are the things we need to do only once at the beginning of our program? We need to initialize the pins to which we connected our LEDs:

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

In the code listing above you can see that we set the mode of each pin we use. We need to tell our Arduino that we want to use them as output pins. By default, all LEDs will be turned off when the Arduino starts. There is nothing more we need to do here, so let's continue with the actual loop.

The loop procedure is repeated over and over again. This means we only have to describe one complete cycle to get a fully functional traffic light. We need to use the correct combination of the functions digitalWrite and delay to recreate the four phases of real traffic lights.
Let's have a look at the required code:

void loop() {
  // Green
  digitalWrite(8, HIGH);
  delay(10000);
  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);
}

What have I done here? Let's look at it step by step.

The first thing I have done is turning on the green LED, by using the digitalWrite function to set pin 8 to HIGH. After that a used the delay function to sleep for 10000 ms aka 10 s. Once the 10 s have passed the program execution continues and the LED will be turned off again by setting pin 8 to LOW.

void loop() {
  // Green
  digitalWrite(8, HIGH);
  delay(10000);
  digitalWrite(8, LOW);

Just like I did it for the green LED, I turn on the yellow LED on pin 9 now. This time I picked a smaller delay of just 2 s.

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

Finally, we get to the red LED on pin 10. I used 10 s as delay for this phase as well, but there is something special here: After the 10 s the red LED won't be turned off, because we need it to be lit for the next phase.

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

In this next phase, we additionally turn on the yellow LED on pin 9 and wait for only 1 s and then turn off both LEDs before this whole cycle will start again with the green LED.

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

Final Result

If you followed the steps correctly, they will result in your own Arduino traffic light. You can see it in action in the video below. Play around with your solution and experiment with the delays and if you want to, you can design your own totally random phases and create a disco traffic light. Stay tuned for the next part, when we will add a button to the traffic light.

Previous Post Next Post