User Tools

Site Tools


oak:tutorials:advanced-leds

Oak: Advanced LEDs

In this lesson, we will build on the blink and basic LED tutorials. We will connect three LEDs to the Oak and control them independently using analogWrite(). The use of a for() loop will also be introduced to show you how to cycle through a range of variables, in this case the brightness and blinking speed of the LEDs.

Components used

Part Quantity Identification
Oak with soldered headers 1
Breadboard1
Jumper wires7
220 ohm resistor3Red-Red-Brown
LEDs, 5mm3Any color

Notes: resistors ranging from 100-1,000 ohms are all in a good range. Lower resistance will be brighter, while 1k ohms will produce a slightly dimmer LED.

Any size LED is acceptable; 5mm is simply a recommendation.

Concepts

for() loops

A for() loop is a core function commonly used in microcontrollers, as well as programming in general. In layman's terms, the for() loop creates a temporary variable (often an integer, i, is used) with a starting value, a condition in order to know whether or not to keep running, and a way to modify that variable with each pass. Lastly, code is contained within the for() loop, typically based on the temporary variable's current value.

That might seem abstract, so here is a concrete example:

for(int i = 0; i < 5; i = i + 1)
{

  Serial.println(i);
  
}

The format of the for() line is always the same, with it having three parts:

  • int i = 0: the first part defines a new temporary integer variable called i and sets it's starting value to 0.
  • i < 5: the next part tells the for() loop how to know if it should continue running. This is usually a condition that needs to be TRUE; in our case we're using “less than” (<) 5.
  • i = i + 1: the last part tells the loop how to change i with each pass. In this case, we are defining the new value of i as the old value + 1. This is commonly written as i++, which does the same thing. You could also use i = i - 1, i = i + 10, i = i * 5 or any other equation you choose.

What does this loop do? It prints the value of i each time through the loop until the condition i < 5 is no longer met. Here is a table showing what will occur:

Time through loop Value of i Value printed
100
211
322
433
544

The last time through, i contains the value 4, the code is run (printing out 4 via serial), and then 1 is added to i, making it 5. When the loop goes back to the top i < 5 will be false and the loop will exit. Keep in mind that a for() loop is often contained within the loop() section, so once the for() loop exits, it will go back to the top of loop() and start over. This will begin the for() loop all over again with i reset to 0. Using the above in a loop() section would would print 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0 … again and again.

loop()
{

  for(int i = 0; i < 5; i++)
  {
    Serial.print(i);
  }

}

map()

The map() function takes arguments for a variable, the variable's range, and the output range:

map(var, var_min, var_max, out_min, out_max)

The function creates a linear mapping, just like converting from, say, Fahrenheit to Celsius. We would use map() to do so like this:

map(temp, 32, 212, 0, 100)

Some temperature in F is stored in temp, and we feed it into map() which understands the linear relationship between F and C based on the two pairs of min/max values we gave it. One other way to think of map is with two number lines:

                   var
                    |
|var_min|-----------|-----------------|var_max|
|out_min|-----------|-----------------|out_max|

We know how the two endpoints of the input and output scales match up since we've told map() the min/max of each. Thus, for some value on the variable's scale, map() can translate it to the output scale.

constrain()

What the map() function will not do is constrain the value. In fact, if you look at the code for map() (shown at the end of the function definition), you may realize that map() uses these min/max pairs to create the formula for a line! With the temperature example above, we could actually feed in 0 F (even though it's less than the min value, 32, we provided) and receive the output in degrees C.

This is where constrain() comes in. It's a pretty simple function: constrain(x, min, max). If x is less than min, the function returns min. If x is bigger than max, max is returned. We will use this below to set up a constrained output from 0-1023, the min/max used by analogWrite().

Circuit

We will connect this circuit, which is three copies of the basic LED circuit.

Steps:

  • Insert the 3 LEDs into the breadboard with enough space for their respective resistors as shown below
  • Insert the three resistors so that one lead is in the same row as the long lead of the LED, and the other end is in a row by itself
  • Connect a jumper from pins 6, 7, and 8 of the Oak to each of the rows containing both the LED and resistor leads
  • Connect a jumper from the short leg of each LED to the ground strip on your breadboard (or to the same row)
  • Connect a jumper from a ground pin on the Oak to the ground strip of the breadboard (or the row containing jumpers from each LED's short lead)

Here is an example of this setup in real life:

oak-3-leds-back.jpg

oak-3-leds-side.jpg

oak-3-leds-front.jpg

Code

Code: for() loop

We will begin by controlling 1 led, changing it's on and off time using a for() loop:

// create two integers to hold the on and off delay times
int on_time;
int off_time;

void setup() 
{                

  // set pin 6 to an OUTPUT
  pinMode(6, OUTPUT);

}

void loop()
{

  // the for() loop will initialize a new temporary variable, i
  // it runs until i >= 500, adding 50 each time
  for(int i = 50; i < 500; i = i+50)
  {

    // the on time will equal i (50, 100, 150 ...)
    on_time = i;
    
    // the off time will equal 500 - i (450, 400, 350 ...)
    off_time = 500 - i;

    digitalWrite(6, HIGH);
    delay(on_time);
  
    digitalWrite(6, LOW);
    delay(off_time);
  
  }
  
  delay(1000);
  
}

Here is what happens each time the loop runs:

time through loop i on_time off_time
15050450
2100100400
3150150350
....
....
1045045050

When you upload this code, you will see this (other LEDs shown removed for simplicity; we'll use them next!):

Code: a pulsing sequence

This next example will use all three LEDs, pulsing them from 0 to bright, and then back down… to accomplish this, we will use a for() loop, map(), and constrain(). Here is the code:

// these values will store the brightness for each of our LEDs
int led_1_bright = 0;
int led_2_bright = 0;
int led_3_bright = 0;

void setup() 
{                

  // set all LED pins to OUTPUT
  pinMode(6, OUTPUT);
  pinMode(7, OUTPUT);
  pinMode(8, OUTPUT);

}

void loop()
{

  // this first loop will run from i = 0 until i = 2000
  for(int i = 0; i <= 2000; i++)
  {

    // these lines map the value of i to the desired brightness range, 0-1023
    // because we've "offset" the min/max of i, we need to constrain
    // the output to make sure it's between 0-1023
    // Can you figure out why the input min/max's are not the same?
    led_1_bright = constrain(map(i, 0, 1000, 0, 1023), 0, 1023);
    led_2_bright = constrain(map(i, 500, 1500, 0, 1023), 0, 1023);
    led_3_bright = constrain(map(i, 1000, 2000, 0, 1023), 0, 1023);

    // each time through the loop, we write each brightness
    // value to it's corresponding LED
    analogWrite(6, led_1_bright);
    analogWrite(7, led_2_bright);
    analogWrite(8, led_3_bright);

    // we delay for 1 millisecond, otherwise this runs at
    // lightning speed!
    delay(1);

  }

  // now we repeat the same cycle of i from 0-2000, but with a twist!
  for(int i = 0; i < 2000; i++)
  {

    // this is the same code except that our output range in map()
    // is reversed! When i is 0, led_1_bright will be 1023;
    // when i is 1000, it will be 0. This let's us dim the LEDs
    led_1_bright = constrain(map(i, 0, 1000, 1023, 0), 0, 1023);
    led_2_bright = constrain(map(i, 500, 1500, 1023, 0), 0, 1023);
    led_3_bright = constrain(map(i, 1000, 2000, 1023, 0), 0, 1023);

    analogWrite(6, led_1_bright);
    analogWrite(7, led_2_bright);
    analogWrite(8, led_3_bright);

    delay(1);
    
  }
  
}

They key portion of this code are the two loops and the constrain(map(…)) lines. Did you figure out why each LED has a different input range for i? It allows us to offset the behavior of each LED.

i range LED 1 LED 2 LED 3
0-500 ramping to halfoffoff
500-1000ramping to fullramping to halfoff
1000-1500full onramping to fullramping to half
1500-2000full onfull onramping to full

The loop then re-runs, but reverses the mappings so that each LED gradually turns off slightly staggered from one another. Upload the code, and see this!

Conclusion

Congratulations for finishing another tutorial! You've completed a more complex circuit and learned some great tools for future use: for(), map(), and constrain(). These are extremely useful functions, and often used to match an input range to an output. We used a loop variable, i, however this would enable you to do something like this:

  • map the brightness of an LED to the value of a photocell
  • map the resistance of a potentiometer to the angle of a servo motor
  • map the color of an RGB LED to the output of a temperature sensor
oak/tutorials/advanced-leds.txt · Last modified: 2016/03/23 21:19 by jwhendy