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
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):

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:
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); }