Tuesday, February 2, 2010

Jar-O-Fireflies (Mark II)


I really enjoyed the process of making my first 'Jar-O-Fireflies' but I was a little disappointed with the performance (i.e. the software), especially the obvious flicker when the lights were pulsing on and off. I've learned a lot more about how to program the AVR uCs over the last few months (but still just the basics really). So, I decided to re-write the software and make a smaller, battery powered, version. I'm going to make another and put it in an empty Nuttela jar for the girls to use as a night light (I'm sure the girls will help me empty the jar).

The software took a little while to sort out but the hardware, on the other hand, only took about 2 hours to put together. Most of that was due to the fiddlyness of using magnet wire for the LED leads.

Below is a little video of it working. Yes, we have 'pet' ants at the moment - at least it was interested in what was going on...



The parts:
  • some magnet wire
  • coin cell holder (and coin cell to go with it)
  • perf board - I used a board with a semi breadboard layout (see picture below), but it wasn't much more help than a standard one other than having a common power and ground rail to connect everything to.
  • switch (one that fits onto a PCB/perf board)
  • 8-pin dip
  • 5 green LEDs (3mm)
  • ATtiny13

This is the layout I used (for the battery holder, DIP socket and switch):



The trickiest part was soldering the magnet wire onto the LEDs. I used a third-hand to hold all the pieces together. It helps that the magnet wire is quite stiff and bendable even though it's thin.


As you can see from the photo. After soldering on the wire I twisted the two strands together. After doing the 5 LEDs I realised that it'd be really nice to have 2 different colours of wire so that the positive and negative leads were obvious... oops. I ended up testing each pair of wires with my LED tester to determine which lead was positive and which was negative.

Then it's was just a matter of soldering on the LED leads to the board:


Then I glued everything onto the jar lid (using a hot glue gun):



Et voila!



Code
:

I've just started using code.google.com as a code repository. The google site supports subversion source control (and there's a good plug-in for eclipse called subversive). I'd been going back and forth between computers and it was turning into quite a headache trying to get the code in sync. Now I can just push and pull from the code repository.

Here's the location of the firefly code:
http://code.google.com/p/pboardman-avr/source/browse/firefly_jar/firefly_jar.c

Google code is no-more.  I've moved all the code to github.

https://github.com/paulboardman/avr/blob/master/firefly_jar/firefly_jar.c

This may change in the future as I tinker a bit more, so I'm including below the version of the code that was running when I wrote this post. This version required setting the fuse bits for the ATtiny13 to make it run at 8MHz. I also wrote a better software PWM routine (see the Atmel technote: "Low-Jitter Multi Channel Software PWM". Warning: PDF link).

There are a few enhancements that I have in mind like actually using a random number generator for generating random numbers... The reason I didn't was that I had trouble coming up with a method for generating a seed (without a differing seed, the pseudo random number generator would give the same sequence each time the AVR was switched on). My latest project (post forthcoming) saves the seed in EEPROM and increments it each time the AVR is booted up - this way the random number generator produces a different sequence each time. Anyway, here's the code:

/*
 * firefly_jar.c
 *
 * Distributed under Creative Commons 3.0 -- Attib & Share Alike
 *
 *  Created on: Feb 2, 2010
 *      Author: PaulBo
 */
#import <util/delay.h>
#import <avr/io.h>
#import <avr/interrupt.h>

#ifndef F_CPU
    #define F_CPU 8000000UL
#endif

//LED pin definitions
#define LED1    PB0
#define LED2    PB1
#define LED3    PB2
#define LED4    PB3
#define LED5    PB4
#define N_LEDS  5
#define ALL_LEDS (1 << LED1) | (1 << LED2) | (1 << LED3) | (1 << LED4) | (1 << LED5)

//Max value for LED brightness
#define MAX     100

#define PULSE_UP   1
#define PULSE_DOWN 0

volatile unsigned char buffer[N_LEDS];

//counter for use in updateLedState() and pulseLeds()
unsigned char i;

//structure for storing data on each LED
struct ledData {
    unsigned char mBrightness;
    unsigned int  mTime;
    unsigned char mPin;
    unsigned char mPulseDirection;
};

//set up some initial values
struct ledData led_data[] = {
        {0, 1000,  LED1, PULSE_DOWN},
        {0, 10, LED2, PULSE_DOWN},
        {0, 500, LED3, PULSE_DOWN},
        {0, 50, LED4, PULSE_DOWN},
        {0, 150,  LED5, PULSE_DOWN}
};

//return a random time interval from 0 to 255
int getTime()
{
    return TCNT0;//just read the current timer/counter value
}

void updateLedState()
{
    for(i = 0; i < N_LEDS; i++)
    {
        switch(led_data[i].mBrightness)
        {
            case MAX://led is on
                if(--led_data[i].mTime == 0)
                {
                    //decrement the brightness, this puts the LED in a
                    //pulse state
                    led_data[i].mBrightness--;

                    //specify the "down" direction for pulsing
                    led_data[i].mPulseDirection = PULSE_DOWN;
                }
                break;
            case 0://led is off
                if(--led_data[i].mTime == 0)
                {
                    //increment the brightness,this puts the LED in
                    //a pulse state
                    led_data[i].mBrightness++;

                    //specify the "up" direction for pulsing
                    led_data[i].mPulseDirection = PULSE_UP;

                    //set the ON time
                    led_data[i].mTime = getTime() + 1;
                }
                break;
            default: //pulse state
                if(led_data[i].mPulseDirection == PULSE_UP)
                {
                    led_data[i].mBrightness++;
                }
                else
                {
                    if(--led_data[i].mBrightness == 0)
                    {
                        //set the OFF time - make this longer than the on time
                        led_data[i].mTime = (getTime() + 1) * 5;
                    }
                }
                break;
        }
    }
}

void init_timers()
{
    TIMSK0 = (1 << TOIE0);         // enable overflow interrupt
    TCCR0B = (1 << CS00);          // start timer, no prescale

    //enable interrupts
    sei();
}

void init_io()
{
    //set all LED pins as outputs
    DDRB |= ALL_LEDS;
    PORTB &= ~(ALL_LEDS); //off to start
}

void setup()
 {
    init_io();
    init_timers();
}

int main(void)
{
    setup();

    //infinite loop
    while(1)
    {
        updateLedState();
        _delay_ms(10);
    }
}

/*
 * Timer/Counter overflow interrupt. This is called each time
 * the counter overflows (255 counts/cycles).
 */
ISR(TIM0_OVF_vect)
{
    //static variables maintain state from one call to the next
    static unsigned char sPortBmask = ALL_LEDS;
    static unsigned char sCounter = 255;

    //set port pins straight away (no waiting for processing)
    PORTB = sPortBmask;

    //this counter will overflow back to 0 after reaching 255.
    //So we end up adjusting the LED states for every 256 overflows.
    if(++sCounter == 0)
    {
        for(i = 0; i < N_LEDS; i++)
        {
            buffer[i] = led_data[i].mBrightness;
        }
        //set all pins to high
        sPortBmask = ALL_LEDS;
    }
    //this loop is considered for every overflow interrupt.
    //this is the software PWM.
    if(buffer[0] == sCounter) sPortBmask &= ~(1 << led_data[0].mPin);
    if(buffer[1] == sCounter) sPortBmask &= ~(1 << led_data[1].mPin);
    if(buffer[2] == sCounter) sPortBmask &= ~(1 << led_data[2].mPin);
    if(buffer[3] == sCounter) sPortBmask &= ~(1 << led_data[3].mPin);
    if(buffer[4] == sCounter) sPortBmask &= ~(1 << led_data[4].mPin);
}

18 comments:

Kelsey said...

Very cool. It would make a nice night light.

In a similar project, I was able to use the hardware PWM for 6 LEDs with an ATtiny45/85, but they can't all be operated independently. I also tried to give my fireflies realistic behavior for our common species of firefly.

My project is here here.

PaulBo said...

@Kelsey:

I just took a look at your project. I love the tiny hardware footprint and the realistic simulated behaviour.

One note: from your todo list, you mention adding a light sensor - you can actually use the LEDs themselves as light sensors (something I've been reading about and playing with recently).

Here's the first example of this that I saw: http://spritesmods.com/?art=minimalism

And here's a page with some more details: http://www.sensorsynergy.com/helpfulhints.htm

Kelsey said...

@PaulBo:

Thanks, that is an interesting idea, I had heard that was possible but not looked into details.

The other thing I've learned recently is that AVRs, including Tinies, have an internal temperature sensor. It's not very accurate in an absolute sense (e.g., could be 10 deg C off), but can register relative temperature changes of a degree. So with a software-only upgrade, I could conceivably add both light and temperature sensing.

Brandon said...

HI!

I am just getting started with ATs and trying to squeak back into C code (I'm a bit rusty). Anyway, I can decode most of what you are doing, but I can't understand the purpose of the PWM code. Can you explain a bit? I read the appnote but didn't get a bunch from it.

Thanks, and love the site!

PaulBo said...

What part of the PWM code is causing issues? The reason it's there is that I'm controlling 5 LEDs but there are only 2 PWM outputs on the uC so you have to do it in software.

I have the uC running at 8MHz and we use timer0 running at full speed (no prescale). This is an 8-bit timer so the associated counter counts to 255 and then overflows back to 0. When the timer overflows an interrupt routine is called (this is where our software PWM happens).

The sCounter variable in the interrupt routine is incremented each call to the routine - this is an 8-bit integer so it will increment up to a value of 255 and then 'overflow' back to 0.

When the counter is set to 0 the LED buffer is set to the values from the main program and all the LEDs are switched on. For each call to the interrupt we check the value of sCounter against the value/setting for each LED, if they're the same then we switch that LED off. This way each LED can have an independent duty cycle.

Note that you'll see some flicker from the LEDs if the uC isn't running at 8MHz.

I hope that helps!

seer said...

Hi, I just found your site when I was searching for my own post. Do you connect the led directly to the pin of the chip and drive them in 3V? I'm willing to know where it is possible to take out the resistors while keep or even enhance the efficiency.
here is my work:
http://www.thinkcreate.org/index.php/solar-firefly-jar/

PaulBo said...

Hi Seer,

Yes, I connected the LEDs directly to the uC pins. I define a maximum brightness for the LEDs (100 in the code on this page) which limits the average current the LEDs receive. I found it was hard to see the difference in brightness for the top 50% of the duty cycle values so using 100 (of 256) was fine. This has the added advantage of reducing power consumption.

I like your build! I also like the idea of using a super cap in-place of a battery. What are the expected life spans of those caps? Is it a lot better than using rechargeable batteries?

seer said...

@PaulBo:
Thanks for your reply, I once found Stevens'_power_law(http://en.wikipedia.org/wiki/Stevens'_power_law), the brightness we sense is not linear to the physical one. The exponent is 0.33 so it is not necessary to use large current on this kind of decoration powered by battery.
The super capacitor is easy to use. It is not necessary to worry about the overcharge or over discharge which is extremely harmful to rechargeable batteries.Besides, the lithium or NiMH batteries have hundreds of life cycles. But the capacitor has more than 20000 on 70% value according to the datasheet. more information(http://www.vina.co.kr/New_html/eng/product/info.asp?cate1=10&cate2=15). But the price is much higher and the voltage on it drops different to batteries.
PS: That's the best capacitor I can find on domestic online market.

Gregory said...

First of all I love all your projects...just starting down this road and these ideas are very inspiring...thanks for sharing!

This project in particular has caught my fancy...have looked over several different designs on different sites (including Kelsey's, which I also very much like)...wondering if you would mind sharing your schematic? I can't tell from the final wiring pic exactly how all the LEDs are connected...either a sketch or text of where each leg of each LED is connected would be most helpful to this beginner...

Greg said...

Sorry...I just read through your code, and I think that tells me how you are wiring (gee do that math...8 pins - VCC, Reset, and GND = 5 pins...one LED per pin...duh), but my assumption that I would like you to validate or correct is that the pin is wired to the anode side of the LED, and the cathode sides would all go to the GND pin (which would go to the battery)? Also, what are you doing with the reset pin?

Thanks again for your patience...I am a software guy...never tinkered with hardware at all...

Joby Taffey said...

Your fireflies look great. Thought you might be interested in my similar project using the TI Launchpad (MSP430)

http://blog.hodgepig.org/2010/09/23/fireflies-mk3/

Rather than soft-PWM, I'm using BCM which uses a few less cycles (http://blog.hodgepig.org/2010/09/12/577/)

I've also posted some information (and algorithm) for making fireflies synchronize over time (http://blog.hodgepig.org/2010/09/21/firefly-synchronisation/)

PaulBo said...

@Greg

You're right: I just have one LED per pin (hence only having 5 "fireflies in this jar". I have experimented a bit with charlieplexing (http://www.fangletronics.com/2010/02/charlieplexed-leds.html) which would let you have 20 fireflies controlled by 5 pins. It gets confusing to wire though.

You are correct about the wiring, the LED anodes are connected to the pins and the cathodes are all connected to GND. I didn't add resistors as the max brightness is set to less than 50% in the code so the LEDs are safe.

As far as the RESET pin goes, ATMELs application note 42 gives a good overview of what you *should* do (http://www.atmel.com/dyn/resources/prod_documents/doc2521.pdf). It looks like I neglected to activate the pull up resistors in the published code... oops. I'll fix that.

Often you'll see people using a physical resistor as well to pull up the RESET pin. I've not had any issues with my Jar just leaving the pin floating, but it's probably a bad habit to get into. These things are hard enough to debug when something goes wrong.

PaulBo said...

@Joby

Thanks for the links! I received my Launchpad the other day, but I've not had a chance to play with it (I'm still fiddling with the CNC machine...). I love your addition of a piezoelectric sensor for randomizing the fireflies, very cool :)

jimday said...

Hi,

Can you compile this code to .hex for us please?

PaulBo said...

@jimday

I've added the .hex file to the code repository:

http://code.google.com/p/pboardman-avr/source/browse/firefly_jar/?r=83#firefly_jar%2FRelease

Let me know if it works for you (I've not played with this in over a year). I'm rather busy making Christmas presents at the moment, but may be able to help out if this doesn't work (I just pulled the code and compiled with the settings I think are correct - internal oscillator at 8MHz).

ScottInNH said...

Sorry for the noob question, but for the tiny13a, what argument/value do you give to enable 8MHz?

This looks better than another firefly program I was playing with. PWM would be a nice unexpected bonus. :-)

PaulBo said...

@ScottInNH:

To enable 8MHz you have to set the fuse bits on the chip. This is something you have to do with the hardware programmer and isn't done via the software.

Take a look at these pages for more info (or search for "avr fuse programming"):
http://www.ladyada.net/learn/avr/fuses.html
http://www.ladyada.net/learn/avr/avrdude.html

I'm using the Eclipse IDE and the Eclipse AVR plugin for development and it contains a section for setting the fuse bits; these are then set at the same time as the code is uploaded to the chip.

The main warning here is that it's possible to set the fuses so that the AVR is no longer programmable with the simple/standard set up. So, be careful.

BLUEICE1371 said...

Kudos on the awesome project PaulBo.

I have zero experience on electronic circuitry and programing.

I am in way over my head on this project and would like to give it as a gift.

I would love to purchase a preprogramed micro controller.
PaulBo or anyone willing to sell me a preprogramed ATtiny13?