Introduction: Beginning Microcontrollers Part 7: Revising the First Program to Make the LED Blink

About: I like robots!

You've written the first program, which turned on an LED. Yeah, that was spectacular! Well, not really, but let's introduce a little bit of craziness to the LED. We'll give it a "bi-polar" personality by making it blink. Then we'll step it up a notch and make it blink really fast. The program is surprisingly concise and easy to implement.

See for yourself:

#include <avr/io.h>
#include <util/delay.h>

int main(void)
{
DDRB |= 1 << PINB0;
while (1)
{
PORTB ^= 1 << PINB0;
_delay_ms(100);
}
}

HEY!! What are those crazy symbols!! Crazy seems to be a common theme here. Well, I'll explain. But, I promise, it's pretty easy. You just need to memorize a few things. I will get into a little bit of detail, but if you don't get it, don't worry, just memorize what the entire statement accomplishes and you will be fine. I will give you a hint along the way. Here comes the detail of bitwise operations:

There are logical operators called AND, OR, NOT and XOR that we are concerned with at this point. They really do mean what they say. This is all for the sake of comparing two bits, "1" and "0". See? How can just comparing two numbers be difficult! "1" AND "1" is True or "1". And yes, you guessed it, "0" AND "0" is False or "0". But what is "0" AND "1" and vice versa? Well, False or "0", obviously. Those guys are different, so, truely, they are false.

Example AND binary number operation (the "&" is used in C programming for AND):

01001011 &
10001101
equals
00001001

Easy, right? Well, OR is even easier! "1" OR "1" is "1", obviously. "0" OR "0" is also "0". It's starting to look very similar to AND, but here is the difference. If the two numbers are different, it will result in "1". So, "1" OR "0" is "1".

Let's use the same binary number for the example (that funny character "|" is the OR, sometimes located above the "\" onthe keyboard):

01001011 |
10001101
equals
11001111

Hmm, that turned on all of the places where there were missing ones! This is where the magic happens. Yes, it kept the ones in the first number and where there are ones in the second binary number, but not the first, it changed those to ones. Very simple. NOT simply takes all of the bits and flips them. For example, for this binary number: 01001101 will turn into 10110010. It turned the 0's into 1's and 1's into 0's, but don't get this confused with the XOR.

XOR is similar to OR but the "1" XOR "1" is actually "0". I'll just show the example and you can figure out what happenes.

01001011 ^
10001101
equals
11000110

Yep, you guessed it, the "^" that is above the "6" is the XOR symbol. Hmmm... The "|" and the "^" are in the program. Cool. Well, since we are thinking program, let's disect the stuff we don't know yet!

#include <util/delay.h>

You already know what the <avr/io.h> does, so I won't waste your time with that one, but there is a new #include statement. The delay.h will provide us a couple of convenient methods for us to use. Just like the name implies, the delay.h will provide us with a way to create delays in our program.

Skipping over the "main" since we know that already, we see the DDRB changed. Don't be scared! Here is the process from where we were to where we are:

This is what we had before. This is not a very good way of minipulating the pins because we just changed all of the pins from 1 to 7 as input, but "what if" we had a larger program that used those pins for other things, like for instance, pin 2 allpies brake pressure control for the anti-lock braking system. You wouldn't want to just arbitrarily set that as an input. That would render your brakes useless (which would be REALLY bad).

DDRB = 0b00000001;

We need a way to ONLY affect one bit, the pin 0 bit. Well, if you look above at the "OR", we can do this with a mask (not the carnival mask you are thinking, but a binary mask.

DDRB = DDRB | 0b00000001;

This will takes it's former self and "OR" it to a mask. The mask is: 0b00000001. Yes this looks like the actual binary number, but if the previous DDRB was, say, 0b01001010, then doing an OR to that with our mask would be: 0b01001010 | 0b00000001 = 0b01001011. What is different in the result. That's right, only the pin 0 bit is changed!

This statememt can be further compressed in C++:

DDRB |= 0b00000001;

But that is not what is in the program. Even though this is perfectly valid, why don't we take advantage of some of the definitions in the io.h header file. I mean, it's there for our convenience, isn't it? Notice my use of contractions! This is the same in C++: "it's" is really "it is", just like "DDRB |= 0b00000001" is the same as "DDRB = DDRB | 0b00000001". I sink in my chair with that bad analogy. Whataver, helps!

So why "DDRB |= 1 << PINB0"?

1 << PINB0 is the act of creating the mask. The "1" represents what will be inserted into the mask, the << is a left shift operator. It does EXACTLY what it says, and PINB0 is a number of positions that the "1" will shift left. In essence, PINB0 is just equal to 0. So, you start with a 0b00000000, and you add a "1" to make 0b00000001 and then you shift it left 0 positions. So you are left with 0b00000001, the same number from above. So, what if it was PINB4? The statement would be: 1 << PINB4. The "1" would be left shifted 4 times resulting in: 0b00010000. Remember, we are using a zero index, so there is four 0s after the 1.

Let's move on to the While loop. You're right, we didn't have anything in the "infinite loop" before. Well, now we need the microcontroller to show some action. This is only possible within the loop. The loop is where the action is repeated over and over. If the action was located before the loop, then the action would only happen once, like setting the direction of the pin, which is appropriate for this program. But to create forever blinking, we need to turn the PINB0 on and off within the loop. Here is also where the delays come in. If we didn't have delays, we would not see the LED blinking at all, it would look like it is just on, since the blinking would occur faster than the eye could perceive, so we need to slow it down.

We know how to set a specific bit in the binary number, but we don't know how to make a specific bit "0" if it is a "1" yet. The following code does just this, but you will notice that it is not what the program shows. The first couple of lines turns the bit to "1" (5 volts, light), and pauses for 100 miliseconds (by the way, you can use microseconds by changing the "ms" to "us"). The second two lines turns the PINB0 bit to "0" (0 volts, no light). No, the AND comparison cannot just make a "0" from the bit, but if you NOT "~" the binary number mask, it will turn all of the 0s to 1s and all of the 1s to 0s. This will permit you to only affect the PINB0 bit and turn it into a "0". I added the parenthesis just to contain the masking operation so the NOT could NOT the whole maks and not just the "1" before the left shift "<<".

PORTB |= 1 << PINB0;
_delay_ms(100);
PORTB &= ~(1 << PINB0);
_delay_ms(100);

If the delay is going to be the same for on and off, we could shorten the previous four lines to only two and take advantage of the XOR operation. Remember, the XOR will turn our specific pin to a 1 if it is 0 and vice versa. This instruction will only affect the PINB0. Every time that the instruction is executed, it will flip the bit to the opposite.

PORTB ^= 1 << PINB0;
_delay_ms(100);

That's it. See that was not painful at all.