NerdKits - electronics education for a digital generation

You are not logged in. [log in]

NEW: Learning electronics? Ask your questions on the new Electronics Questions & Answers site hosted by CircuitLab.

Project Help and Ideas » Blink multiple LEDs?

December 25, 2011
by boxvic
boxvic's Avatar

Ok, so I'm an electronics technician with a degree in electronics technology. Unfortunately we never got to PICs or MCUs in school, and while I work with them on my job I never actually deal with their written programming. I do program several chips, but I just basically load the software and flash the chips; I never actually see any of the code. I run into issues at work though were the programming is the culprit and basically all I can do is reprogram a chip to reset it. I'd like to get a better understanding of the programming so that I can understand why specific glitches happen under certain circumstances.

Setting up the MCU circuit was no problem, and loading the pre-written code is just like another day at work. For the life of me though I cannot figure out the code. I thought I understood the LED blink program, and I tried to set other pins as blinking outputs but then I get this error:

make[1]: Entering directory mydirectory' make[1]: Nothing to be done forall'. make[1]: Leaving directory `mydirectory' avr-gcc -g -Os -Wall -mmcu=atmega168 -Wl,-u,vfprintf -lprintf_flt -Wl,-u,vfscan f -lscanf_flt -lm -o led_blink.o led_blink.c ../libnerdkits/delay.o ../libnerdki ts/lcd.o ../libnerdkits/uart.o led_blink.c: In function 'main': led_blink.c:37: warning: 'main' is normally a non-static function led_blink.c:58: error: expected declaration or statement at end of input make: *** [led_blink.hex] Error 1

So I obviously don't understand the statements like I thought, but I have no clue what I'm doing right and what I'm doing wrong. I would like to eventually set it up to flash multiple LEDs with a pull-down switch that changes the pattern (which is actually very similar to something I'm in charge of repairing at work). Right now though I would just love to get it to flash two LEDs. I understand how to change the single LED's flash rate, but I cannot figure out the code to enable a second port.

December 25, 2011
by boxvic
boxvic's Avatar

It never fails, ask for help and you shall figure it out for yourself. I have two leds blinking at different rates now. I don't completely understand the fundamentals for why this code works, but it does.

December 26, 2011
by Rick_S
Rick_S's Avatar

If you'd like input on your code, post it, and I'm sure someone will chime in. If you have specifics on what you don't understand show us the specific piece of code. Otherwise, we are pretty much in the dark to your question.

Rick

December 26, 2011
by boxvic
boxvic's Avatar

Here is the simple code that I'm using to flash four LEDs.

int main() {

  DDRC |= (1<<PC4);
  DDRC |= (1<<PC3);
  DDRC |= (1<<PC2);
  DDRC |= (1<<PC1);

   while(1) {

    PORTC |= (1<<PC4);
    delay_ms(100);
    PORTC &= ~(1<<PC4);
    delay_ms(100);

    PORTC |= (1<<PC4);
    delay_ms(50);  
    PORTC |= (1<<PC3);
    delay_ms(50);

    PORTC &= ~(1<<PC4);
    PORTC &= ~(1<<PC3); 
    delay_ms(50);

    PORTC |= (1<<PC2);
    delay_ms(100);
    PORTC &= ~(1<<PC2);
    delay_ms(100);

    PORTC |= (1<<PC2);
    delay_ms(50);
    PORTC |= (1<<PC1);
    delay_ms(50);
    PORTC &= ~(1<<PC1);
    PORTC &= ~(1<<PC2);
    delay_ms(50);
  }

  return 0;
}

Using this I understand which statements do what, but not entirely what they mean. I'm not sure what the starting "int main" statement means. This statement: "DDRC |= (1<<PC4)" declares | as a bit that turns the output on, correct?

I'm also not sure I understand the PORTC deceleration. I can set different ports to control the outputs right? So in this example I could set PORTB to flash the LEDs at a different rate after a button is pressed?

December 26, 2011
by treymd
treymd's Avatar

main() is a function and like any other unction it can take parameters, and return a value. int main, means essentially that main will return an integer value. the parameters normally passed to main are argc, and argv. argc is the argument count, argv being the arguments. These to variables represent the parameters given to a command line program.

In the world of microcontrollers, I don't believe that the parameters or the return value are particularly useful, unless of course you have multiple programs on your microcontroller, and then I guess one calling the other could possibly make use of these variables.

December 26, 2011
by boxvic
boxvic's Avatar

So how would I go about setting up multiple patterns? I've seen a few examples online, but none of them are programmed similar to what I've started with; so I don't see how to make it work.

December 26, 2011
by hevans
(NerdKits Staff)

hevans's Avatar

Hi boxvic,

I think you are getting stuck in the way you are thinking about programs on your chip. You are looking for a way to kick off a series of events, and then have control return to your main thread so you can kick off another series of events at the same time. In normal PC programming you would use a thread (or a separate process) to do something like this. On an MCU this isn't quite as easy to do since simple programs usually run in a single main loop.

In order to truly achieve the effect you want you will have to use timers and interrupts. Basically you want to set up an timer/interrupt to fire at some interval of time, and then toggle an LED. Once you do that you can setup a different timer/interrupt to blink a second LED. Your main loop in this situation would just sit there not doing anything, and you could use it in that case to listen for a button press to change the rates of blinking or something similar. Check out our tutorial on interrupts for more about this topic.

Humberto

December 26, 2011
by boxvic
boxvic's Avatar

Wow... this is a lot different than I anticipated. I know ladder logic and PLC programming, and I think I'm having trouble looking at this differently. I keep wanting to look at the pins as bits that I can manipulate with timers and stuff like in a PLC.

I guess I'm going to have to study some C or something.

December 26, 2011
by treymd
treymd's Avatar

I don't think your understanding is far off, as the the port pins are manipulated by changing bits. PORTA/B/C are 8 bit values representing the state of several pins, by manipulating these 8 bit values you can control the state of individual pins.

December 26, 2011
by treymd
treymd's Avatar

Sorry there is no PORTA on the 168, but there is a PORTD.

December 27, 2011
by pcbolt
pcbolt's Avatar

@boxvic

I wrote a little code that may shed some light on your project. Hope it helps. BTW the "int main()" statement is required for the program to know where to start. You can replace "int" with "void" if you want, but if you do, you have to change the last "return 0;" statement with just "return;" or else the compiler will puke up an error. Here's the code with some comments added:

    #define F_CPU 14745600

    #include <avr/io.h>
    #include <inttypes.h>

    #include "../libnerdkits/delay.h"
    #include "../libnerdkits/lcd.h"

    // PIN DEFINITIONS:
    //
    // PC4 -- LED anode Red
    // PC3 -- LED anode Yellow
    // PC2 -- LED anode Green

    int main() {
    // LED as output
    // DDRC is "Data Direction Register C"
    // DDRC is an 8-bit register and contains some value xxxxxxxx
    // Where x is either 1 or 0
    // (1<<PC4) is 00010000 in binary - 1 shifted right 4 times
    // when you OR it with DDRC it becomes xxx1xxxx no matter what
    // this tells the MCU that we want Pin PC4 to be an output pin
    // it also pulls the pin voltage up to 5v. 
    // you could write DDRC = 00010000 but this would turn off the output of all
    // the other pins.
     DDRC |= (1<<PC4);
     DDRC |= (1<<PC3);
     DDRC |= (1<<PC2);

     // Declare some variables 
     uint16_t i = 1;                                        // counter
     uint16_t min_time_interval = 200;  // time in msec
     uint8_t led4 = 2;                                  // # of intervals between blinks Red
     uint8_t led3 = 4;                                  // # of intervals between blinks Yellow
     uint8_t led2 = 8;                                  // # of intervals between blinks Green

    // turn on LEDs
        // PORTC register is what actually gets looked at when voltages 
        // are assigned to each pin - 1 high, 0 low

     PORTC |= (1<<PC4);
     PORTC |= (1<<PC3);
     PORTC |= (1<<PC2);

    // loop forever
  while(1) {
            i++;        // Increment i on each loop

        // using the modulus operator % (or remainder operator)
        // the LED4 will only change state when i is equally divisible by 2
        // using the XOR operator ^=, this toggles the value of the pin
        // because 1 XOR 0 equals 1 and 1 XOR 1 equals 0

            if (i % led4 == 0)   PORTC ^= (1<<PC4);
            if (i % led3 == 0)   PORTC ^= (1<<PC3);
            if (i % led2 == 0)   PORTC ^= (1<<PC2);

        // delay for smallest time unit
        delay_ms(min_time_interval);

        } // loop back around

        return 0;
    }
December 27, 2011
by boxvic
boxvic's Avatar

pcbolt, that actually helps a good deal. I understand all of the binary and operator stuff (thought I did have to look up the short hand for the operators). I think my biggest stumbling block is understanding the C that is used, and then grasping the idea that I have to use operators to enable and disable a pin instead of just setting their states like in a PLC. I understand that ORing an output will set it high, and then ANDing it will set it low. 1OR'd0 is 1 and 1AND'd0 is zero. I'm coming at this from a background though where I would explicitly set an output. With ladder logic you can define a specific bit; then I could tie that bit to an output on the PLC, and I could call that bit from different timers and counters, or just button inputs and energize it. All of my actions would be in the same scan chain, but that output would only react to the last action that energized the bit. So I could have an LED flash on for one second then off for one second until a button is pressed which then activated a new timer function and flashed at a new rate. At the end I could have the same switch reset it all.

I think that is my biggest struggle right now; just getting over the idea of defining multiple routines and then calling them on command.

Your code helps identify a few things that I didn't know what they meant, but I'm still about 50% lost. For starters... the last part of your code there, you XOR the remainder with the output. That is what does the actual on/off of the pin to blink the LED right? It starts off low then gets XOR'd against a high to turn on, and then that high gets XOR'd against a high and turns off. Right? 0Xr1=1 1Xr1=0 0Xr1=1 etc.

Why do you use the remainder function? I've only dealt with remainders when building flip-flop counters, and honestly I can't remember much of that stuff.

December 27, 2011
by boxvic
boxvic's Avatar

*correction:

I think that is my biggest struggle right now; just getting over the idea of not defining multiple routines and then calling them on command.

December 27, 2011
by pcbolt
pcbolt's Avatar

@boxvic

Actually you're pretty close to getting it. First, the remainder operator is easy to explain. In the code, every time the program loops, the variable 'i' is increased by 1 (with the i++; statement). So if 'i' is 1 and we do 'i % 2' the remainder is 1. Since this is not zero, we skip the toggling of the LED. When 'i' becomes 2 and we execute 'i % 2' the answer is 0 and we do execute the LED toggle. So LED4 toggles at i = 2,4,6,8... Same with the other LEDs. LED3 toggles at 4,8,12,16.... half as often as LED4.

Second, you can have complete control over each pin. Whenever you use OR with 1 the answer is always 1. 1 OR 1 = 1 and 1 OR 0 = 1, so you always end up setting a pin (ON state). Whenever you use the AND operator, you notice we are using the NOT operator first. So we set 1 in the right bit position then use NOT (~) to change it to 0. Now when you AND 0 with any value the result is always 0. 0 AND 0 = 0 and 0 AND 1 = 0, so you always end up with a 0 where you need it (OFF state).

As far as calling routines on command, you can still do that but just remember you have to let the program run in an infinite loop. Pushing a button to change the blinking rates can be done but you might have to use interrupts to do it. In the program above, we could check on the button's pin state in every loop (polling) and change the 'led4' variable if the button has been pressed. Problem is, you would most likely press the button during the 'delay' function and it would not register. Of course if you held the button down for the whole delay, it might register and then the program would know to change the LED4 interval. Even better would be to change the min_time_interval to 20ms and change the led4, led3, led2 variables to 20,40,80. That way you'd only have to hold it for just over 20 ms.

I know it's "thinking outside the box", but it works. The Nerdkit video about interfacing with a keyboard gives you a good heads up into interrupts, which I think is the direction you want to go.

Post a Reply

Please log in to post a reply.

Did you know that you can control 120 LEDs with just 17 microcontroller pins? Learn more...