Let’s Build An LED Lantern! Part 3: Controlling It Part 1: Too Many Colons

Hi all, welcome back! In part two, I built the lantern’s LED light source. Now that it’s finished, I’m ready to build and program the microcontroller device that manages it. This article is going to be pretty theory-heavy, concentrating more on concepts than the actual build.

The usual CYA stuff still applies: While no dangerous voltages are directly present in this project, it will be using large lithium-ion battery packs, and those can be very dangerous if mishandled. It also involves soldering, which can expose you, obviously, to high temperature molten metal, but also to potentially toxic vapors from burning flux. If you do decide to make one of these yourself, you’re doing so at your own risk.

The Arduino Uno microcontroller board, which will act as the lantern’s brain, needs to perform a few basic functions in this application: It needs to read the state of the buttons and react to button presses, it needs to switch the colors on & off, and it needs to dim them independently. To figure out how to pull this all off, a good place to start is with the Uno’s datasheet:

According to the specs, the Uno uses an Atmel ATmega328P microcontroller, which sports a total of 14 digital input and output pins. ‘Digital’ in this case means the pins can either detect or source two voltage states: HIGH (+5v) or LOW (0v/ground). (The 328p also has six analog input pins, but we’re not going to be using them in this project.) Of particular interest are the six pins capable of something called PWM output. PWM is short for Pulse Width Modulation, a feature that is critical to this project and something I’ll go into in detail a little later.

Now that we know the 328P’s basic features, let’s look more closely at the Uno board’s layout:

The Atmel microcontroller is the large DIP chip in the socket on the lower right corner of the board. Directly below it are the six analog input pins, labeled A0-A5. To the left of those are pins concerned primarily with providing power to and from the board: Vin is voltage in, one of the many ways you can feed power to the Uno itself. GND is ground, basically zero volts with respect to every other voltage on the pins. 5v and 3.3v are output pins that output +5 volts and +3.3 volts respectively. They are always HIGH, meaning there’s always a voltage present on them as long at the Uno is powered on.

Above the micro is the second set of pinouts. These are the 14 universal digital input/output pins, labed 0-13 from right to left. The oh-so-critical PWM-capable pins are marked with a tilde (~).

Finally, there’s a USB-B connecter and a barrel jack on the left-side top & bottom of the board. As the datasheet indicates, the Uno will accept an input voltage from 6 to 20 volts, which can be fed to it through the barrel jack. The Uno has onboard power regulation which keeps the voltage to the Atmel micro capped at 5v. It has an internal loss of ~1 volt, hence the need for at least 6 volts. The USB port provides both power from and communication with a host PC when it’s connected.

Now that we know what an Uno board looks like, let’s look at how one is programmed. Arduino provides a special development platform for its devices, called the Arduino IDE. It can be downloaded from just about any device’s app store or directly from their website, https://www.arduino.cc/en/main/software

Note: The Arduino development platform uses a custom version the ANSI C programming language, which is a subject way outside the scope of this dumb little article. Tutorials on Arduino programming are available all over the net, like here.

Once it’s installed and running, and a board is connected, you’re presented with this big blank canvas, called a sketch:

Remember this GIF from part 2?

Let’s talk about how to wire and program the Uno to accomplish this astounding feat of engineering. First, we connect the wires from the led module to the various pins on the Uno board like so:

As it was in part 2, this LED package is wired in a common anode configuration. The black wire runs from the +5 volt power output pin, through a 1 k-ohm resistor to the positive pads on all four colors. The individual negative leads are plugged into four digital I/O pins on the Uno: Green goes to pin 2, red to pin 3, blue to pin 4 and white to pin 5.

The sketch that I’ve written to get the LED colors flashing in sequence is here:

Let’s break it down into delicious bite-sized chunks: The very first part of the sketch creates our global variables and assigns them their initial values.

const int green = 2;
const int red = 3;
const int blue = 4;
const int white = 5;
int del = 1000;

‘Const int’ stands for constant integer; an integer that, once it has been declared and stored in the micro’s RAM can’t be changed. Here, these four constants are used to specify which color is attached to which pin on the board.

‘Int’ creates an integer variable in memory. It’s similar to an integer constant, but its value can be changed by another line of code within the program. Here, del is a variable I use to specify a delay in milliseconds to be used later in the program.

The next block of code is the Setup block. The micro executes this block only once when it’s first powered on or rebooted. Its purpose is to set the initial state of the micro, prior to executing its main code:

void setup() {
// put your setup code here, to run once:

pinMode(green, OUTPUT);
pinMode(red, OUTPUT);
pinMode(blue, OUTPUT);
pinMode(white, OUTPUT);

digitalWrite(green, HIGH);
digitalWrite(blue, HIGH);
digitalWrite(red, HIGH);
digitalWrite(white, HIGH);
}

pinMode is a function used to set the state of the pin to be either an input pin or an output pin. The normal syntax is pinMode(pin, mode); where pin is an integer corresponding to the physical pin number on the board, and mode is either INPUT, OUTPUT or INPUT_PULLUP. Since I already declared that green = 2, red = 3, etc., the compiler is smart enough to substitute in the value contained in the constant, so pinMode(green, OUTPUT) becomes pinMode(2, OUTPUT) and so on.

digitalWrite is a function that tells the corresponding output pin which state to be in, either HIGH or LOW. Again, HIGH in this case is +5 volts, while LOW is 0 volts/ground. Since the anode (positive side) of the LED is already connected to +5 volts, there’s no voltage difference between it and the cathode. No current flows through the LED and no light is emitted. Essentially, I’m telling the arduino to turn the LED completely off on boot.

Incidentally, properly initializing your variables is very important in a C program like this. When you declare a new variable, you should always assign it an initial value according to its type. It’s entirely possible in C programming to create a variable without an initial value by just specifying its name and type, ie int A;. When you declare a variable without a value, you’re telling the compiler to only set aside some RAM to accommodate the variable. As a result, you could end up with weird, unpredictable behavior when your program acts on whatever random garbage data happened to already be stored in the RAM allocated for your improperly initialized variable. Some compilers are smart enough to null out the contents of that variable’s RAM after it has been allocated, but others aren’t, and I have no idea whether the Arduino IDE is. ANSI C is also case-sensitive and sensitive to the placement of white space. digitalWrite(green, LOW); is a valid function declaration, but digitalwrite(green, LOW); is not. Neither are digitalWrite(green, Low); or digitalWrite(green,LOW);

Anywho, let’s move on to the meat of the program, the loop block. Code in the loop block does what the name implies: It runs over and over in an endless loop.

void loop() {
// put your main code here, to run repeatedly:
digitalWrite(green, LOW);
delay(del);
digitalWrite(green, HIGH);

digitalWrite(red, LOW);
delay(del);
digitalWrite(red, HIGH);

digitalWrite(blue, LOW);
delay(del);
digitalWrite(blue, HIGH);

digitalWrite(white, LOW);
delay(del);
digitalWrite(white, HIGH);

}

As you can see, it ain’t a whole lot of meat. Its first step is to run the digitalWrite(green, LOW); function to pull the green pin LOW. This sets pin 2 (remember, we declared pin 2 to be green earlier in the sketch) to ground, thus creating a voltage difference between it and the +5 volts pin. Current flows through the +5v pin to pin 2 through the green LED, and the green LED lights up.

The next line, delay(del); tells the micro to pause executing anything else for the duration of the variable del in milliseconds. Since we initialized it to 1000, the micro waits for 1000 ms, or one second. Then it executes digitalWrite(green, HIGH); which, as it does in the Setup function, turns the green LED back off.

The loop block then does the same for each other LED color in the sequence, and when it reaches the end of the loop, denoted by the final “}”, it goes right back to the beginning and does it all over. Forever. Such is the tedious life of a microcontroller.

OK, now that we know how to write a basic sketch and how to switch stuff on and off with it, let’s move on to reading button presses. Remember that the digital pins on the Uno can function as output OR input, so our next step is to get one pin to read the state of a button or switch. (I’m going to use button & switch interchangeably going forward because, to the Uno, they’re functionally identical.) Let’s look at a new version of the sketch:

I’ve added a new global variable, button, initialized it to HIGH, and, under Setup, added PinMode(button, INPUT);. This sets pin 6 on the Uno to INPUT mode. In the main loop, I’ve added readButton = digitalRead(button);, a command that checks if pin 6 is HIGH or LOW and sets the button var accordingly. I’ve also added this while (readButton == LOW) loop, which constantly checks if pin 6 has been pulled LOW, (connected to ground) and if it has, turn the LEDs on. (I’ve also set the delay to 0, so they all essentially turn on/off at the same time.) Finally, I’ve attached a piece of dangly wire to pin 6 to simulate what you would have in-circuit with an open switch. Let’s see what happens:

First, the LEDs are on when they shouldn’t be, since the “button” isn’t pressed, and the circuit is open. Second, just bringing my big meaty hand near the wire connected to pin 6 is enough to cause the micro to detect a change of state. So what’s going on? The micro is looking at the voltage across pin 6 to determine whether it’s HIGH or LOW. Intuitively, you might think that, since there’s no connection, the voltage is zero/LOW, but that’s not the case. Voltage is a relative value; it is always measured across two points. Since pin 6 is just flapping in the breeze, it has nothing to compare itself to, so its voltage is actually unknown. This is called a floating input, and it’s bad for your project, since anything that induces a tiny current in the input pin, such as a hand moving near it, can provide it a false voltage reference, trip that on/off threshold, and cause the micro to think a state change has occurred.

So how do we fix floating inputs? We make sure that second point of reference is always available, whether the switch is closed or open. And we do that by adding a resistor in circuit with the switch. According to Ohm’s Law, V=I*R. If we keep current (I) constant, a change in resistance (R) results in a change in voltage (V). When the switch is open, enough current will flow through the resistor to/from the input pin to give it a constant reference, but not provide a large enough voltage to pull the pin fully high or low. The switch has a lower resistance than the resistor, so when it’s closed, electricity flows through it instead, following the path of least resistance. The input pin goes definitively high or low, and the micro registers a state change.

There are two ways to wire these resistors, in a pull-down or pull-pull-up configuration. In a pull-down configuration, the resistor is connected between the input pin and ground, while the switch is connected between the input pin and +5 volts:

Closing the switch raises the voltage level of the input pin HIGH to +5v, which usually translates to a logic level 1.

In a pull-up configuration, the two components are swapped. The resistor is connected between input and +5v, while the switch is connected between input and ground:

Closing the switch here pulls the input pin LOW, to zero volts, which usually translates to a logic level 0, something you have to be aware of when writing your code.

So now we know how to wire our micro to make sure it always accurately reads button presses. But remember, I said there are two input modes that the pinMode function can use. It turned out that the good people at Atmel added internal pull-up resistors to the 328P, saving us the trouble of wiring our own. All we have to do to take advantage of them in our project is change one line of code: PinMode(button, INPUT); needs to become PinMode(button, INPUT_PULLUP);. Boom. Done. That’s it. With that one little change, all our floating input problems are now gone!

As you can see, touching the input pin’s wire to ground (Ground in this case is the metal shield around the USB connector) causes the input pin to go LOW. This passes the while (readButton == LOW) check and causes the micro to turn the LEDs on. Lifting the wire causes the pin to return to HIGH and the check to fail, turning the LEDs back off. We now have a micro that can reliably tell when a button is pressed.

Now that we have a micro that can both switch LEDs on/off and react to button presses, we’ve conquered two of the three major micro tasks, and it’s time to move on to dimming. In part 2, I mentioned that LEDs are current-driven devices, meaning that small changed is the current driving them can have big impacts on their brightness. Indeed, I’ve used this technique to build dimmable LED lights in the past. But the Uno doesn’t have a particularly easy or effective method of regulating current flow through its I/O pins, so I’m going to use that handy feature I mentioned earlier, pulse width modulation. In a nutshell, PWM is nothing more than switching an electrical signal on and off at regular intervals. Imagine turning a light switch on for one second, waiting one second, then turning it back off for one second: You’ve just pulse-width-modulated that light and you did it at a frequency of 1 Hz.

PWM signals are visualized as square waves, with the peak being ‘on’ and the trough being ‘off’, but in addition to amplitude and frequency/period, they have a third component called duty cycle. Duty cycle is the ratio of how long the signal is on vs how long it’s off. In our previous example, the light was switched on for 50% of the time, and off for 50%, so the duty cycle is 50%.

Now instead of leaving the light on for one full second, imagine you flicked the light on and then immediately back off. Say it takes you 1/10th of a second to do that. Your square wave frequency is still 1 Hz because the full on/off cycle still takes the same one second to finish. However, since the light is now only on for 1/10th of a second, your duty cycle is only 10%. Here’s a visualization of the concept of PWM duty cycles shamelessly ripped off from Wikipedia:

It’s worth noting that a 0% duty cycle means your circuit is just off, while a 100% duty cycle means the circuit is constantly on.

The reason why PWM switching is so handy here is that the Uno can do it very quickly. The PWM pins can be switched on and off hundreds of times per second, and the duty cycle can be changed in single-digit increments from 0 (0%) to 255 (100%). This fast switching feature combines with our own persistence of vision to give the illusion that the light is being dimmed by the controller. PWM also has the added advantage of power-efficiency: A lot of current control methods rely on converting excess current into heat, thus wasting it. Since a PWM-controlled circuit is either fully-on or fully-off at any given moment, it doesn’t regulate away any amount of power as waste heat.

One thing PWM is not, though is truly analog. It can only simulate an analog voltage, with varying degrees of accuracy depending on the hardware.

So let’s check PWM out on an Arduino Uno:

Again, along the top of the board are the digital I/O pins. The ones that support PWM, marked with a ~, are 3, 5, 6, 9, 10, and 11. With those in mind, let’s build a new sketch:

This sketch deals only with the red LED. It sets the appropriate pin to OUTPUT and sets up a new integer variable, brightness, to set the brightness of the LED. The Loop is very simple:

analogWrite(red,brightness);
delay(del);
brightness--;
if (brightness == 0) {
brightness = 255;
}

Using the incorrectly-named analogWrite function, it sets the brightness of the red LED to the initial PWM value, 255. It then waits for the delay time del, and decrements the brightness variable by one (brightness–; is shorthand for brightness = brightness – 1;) Finally, it checks to see if brightness is equal to 255. If it is, brightness gets set back to 0, and the whole loop starts over. Let’s see what that looks like:

The eagle-eyed among you probably already noticed a strange issue: If we’re starting at 255, which is supposed to be 100% duty cycle, and decrementing down to zero, why does the LED start dim, then get brighter? It has to do with the ass-backwards way this LED is wired. I’m not using pin 3 as a current source, I’m using it as a sink. When that pin is on/HIGH, the voltage on both sides of the LED is +5, and no current flows. When it switches off/LOW, pin 3 is pulled to ground, the current from the black wire, which is connected to +5 volts, flows, and the LED lights. So in this case, the greater the duty cycle, the dimmer the LED. Part 2 covered some of the reasons why I had to wire it this way, and a full explanation will be forthcoming in a future article.

That’s almost all there is to PWM, but I’m not done boring you with it just yet. First, let’s check out what the Uno’s PWM output looks like on an oscilloscope, and then we’ll go over some of the issues that the Uno has in its implementation:

This is a look at PWM pin 6 on the Uno, set to a PWM value of 64:

As expected, we’re getting 4.96v amplitude, which is close enough to the 5 volts we’d expect. We’re also seeing a positive duty cycle of 25.4%, and a negative duty cycle of 74.6%. That jives with a PWM value of 64 since 64 is very nearly one quarter of 255. Incidentally, we’re also seeing a frequency of 976.7 Hz. Keep that in mind.

Changing the duty cycle to 128 has this effect:

As expected, duty cycle is now roughly 50/50. Amplitude is the same, and so is frequency. Now let’s look at what happens when you set the duty cycle to the extremes. Here’s 254:

And here’s 1:

It’s very nearly a flat line, with just the spikiest of spikes still visible, yet amplitude and frequency are still unchanged.

Now let’s try 255. Remember that 255 is fully on:

Frequency is now some bogus value, as is amplitude. Duty cycle isn’t even measured anymore. This is because oscilloscopes nominally plot change voltage over time, but now the voltage is no longer changing over time, so the concepts of frequency and amplitude no longer apply. Oscilloscopes can be used to measure static voltages, though: That little purple arrow on the left side points to what’s called the zero crossing, i.e. the point at which the measured voltage crosses above or below 0v. On the right side of the screen we can see our Y-axis scale, which is set to 2.00 volts per division; a division being defined as the area between the dark grey grid marks on the screen. If we count up from the zero crossing, we see we have about two and a half divisions from there to the line, or about 5 volts.

We can also just turn measurements on:

Here, we can see our max voltage pegged out at 5.12, our average is 4.88, RMS is 4.88, yadda yadda.

OK, that was a fun, pointless little diversion. Now let’s get back to talking about the Arduino’s PWM problems: Here are PWM pins 3 and 6 on the scope. Both are set to 50% duty cycle:

The purple trace on the bottom is showing pin 6, and the yellow trace is showing pin 3. Notice its much lower frequency: 488.4 Hz vs pin 6’s 976.8. What’s the deal? After all, the only parameters we have control over with analogWrite are the pins and their duty cycle. Turns out the answer lies in Arduino’s documentation for the analogWrite function:

According to that snippet, the PWM frequencies for the Uno are both fixed and determined by the pins you use. Pins 5 & 6 pulse at 980 Hz, while the rest pulse at 490 Hz. And those specs jive with what we’re seeing on the scope. This is something of a problem because lower PWM frequencies mean there’s a greater likelihood that you’ll notice the flicker as the LEDs are being pulsed. After all, a 10% duty cycle at 490 Hz means the LEDs are on for only 0.02 milliseconds every 2 milliseconds, and unlike old-school light bulbs, they have no filament to remain hot & keep emitting light for those couple of milliseconds that they’re off. I did some entirely subjective eyeball testing and found out that flicker is perceptible to me at both 490 Hz and 980 Hz when the duty cycle is low. But as the duty cycle rises, the perceptible flicker goes away for the 980 Hz PWM channels first. Not a huge deal, but it is something that’s outside my ability to fix, given the way the rest of this project will come together.

OK, I think that should do it for this theory-heavy slog of an article. If you’ve managed to stay awake to the end, I thank you for reading, and I hope you stick around for the next installment.