AtTiny Advent Wreath

An advent wreath in late spring, you ask? Yes, the timing is a bit off, and that’s not just because the coldest spring in ages has not finished yet. While browsing for the topic of my last post, I discovered a nice little one-evening-project: Geeky advent from tinkerlog.
I had all the required parts here, so I just gave it a try. The adaptation from the AtTiny13 to an AtTiny45 was straight forward. But finding the right threshold value for the ambient light sensor was a bit trickier. Especially, as the ADC didn’t work at first. That was probably a difference between the two AtTiny’s. But once I configured the ADC properly for the AtTiny45, I flashed it a couple of times with different values, and turned the room light on an off, until I had a good threshold value.
It’s interesting how the flickering is done with the random values and the manual PWM. And especially, how one of the LED’s is used to sense the ambient light was intriguing. To save battery power during the day, it goes to sleep and waits for the watchdog timer to wake it up. It then senses the ambient light. If it is bright, it goes straight back to sleep. If it’s dark, it lights up the LED’s. Going through the four modes for the four weeks of advent is done by resetting, or just quickly disconnecting the power from the battery.

But now I look forward for the summer to come, before we can put the mini advent wreath to use…

As my modified code is so similar to the original, it’s not really worth to create a project on github. So, I just pasted the code below.

/* -----------------------------------------------------------------------
 * Title:    advent.c
 *           Flicker 4 LEDs
 * Author:   Alexander Weber
 *           http://tinkerlog.com/2009/12/12/geeky-advent/
 * Date:     22.11.2009
 * Hardware: ATtiny13v
 * Software: CrossPack-AVR-20090415
 *
 * Modified to use an AtTiny45 by Richard Ulrich <richi@paraeasy.ch> 11.06.2013
 *
 * Credits:
 * This code is based heavily on sritesmods version.
 * Find the original at http://spritesmods.com/?art=minimalism&f=gpl
 *
 * Changes:
 * - support 4 LEDs
 * - added a bit of sampling for light detection
 * - moved the "power down" out of the ISR, was always resetting
 * - removed callibration, replaced by hardwired value.
 */

#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <avr/eeprom.h>
#include <avr/pgmspace.h>
#include <avr/sleep.h>
#include <avr/wdt.h>

#define LED1  PB4
#define LED2  PB3
#define LED3  PB2
#define LED4  PB1
#define ADC2 2
#define AMBIENT_LIGHT 55

#define TRUE 1
#define FALSE 0

//Bunch o' random numbers: I'm too lazy to write or port a real random number
//generator.
//generated using bash:
//for x in `seq 0 255`; do echo -n $(($RANDOM%256)),; done
const uint8_t randomvals[] PROGMEM = {
	234,191,103,250,144,74,39,34,215,128,9,122,144,74,137,105,123,218,158,175,205,
	118,149,13,98,7,173,179,194,97,115,110,213,80,220,142,102,102,36,152,90,135,
	105,176,173,49,6,197,48,140,176,122,4,53,83,216,212,202,170,180,214,53,161,
	225,129,185,106,22,12,190,97,158,170,92,160,194,134,169,98,246,128,195,24,
	198,165,156,77,126,113,136,58,156,196,136,41,246,164,84,138,171,184,42,214,
	203,128,89,39,198,85,140,148,149,36,215,78,170,234,131,124,152,239,154,214,
	130,194,49,3,69,248,120,179,101,163,131,124,184,148,213,118,213,81,177,149,
	58,213,33,201,63,10,195,215,190,7,86,245,128,9,8,40,102,51,125,94,92,5,159,
	75,253,158,40,4,6,178,241,92,124,73,248,1,157,61,50,86,136,113,22,16,171,209,
	230,144,240,14,188,2,167,22,88,57,50,86,171,73,114,175,34,226,245,57,180,111,
	220,186,170,242,141,229,49,158,30,82,161,49,124,65,139,24,95,14,133,65,238,
	116,180,190,49,130,30,30,59,93,173,139,19,187,2,163,102,26,255,23,239,196,19,
	6,162
};

uint8_t mode_ee EEMEM = 0;                      // stores the mode in eeprom
static volatile uint8_t sleep = 0;

// Gets a semi-random number between 0 and 255
uint8_t getRandom(void)
{
	//This'll probably give a warning because we use it uninitialised. Little
	//does the compiler know: that's actually what we _want_ 🙂
	static uint8_t random1, random2;
	random1++;
	if(random1 == 0)
		random2 += 0x41;
	return pgm_read_byte(randomvals + random1) ^ random2;
}

uint16_t getLight(void)
{
	uint16_t val = 0;
	uint8_t i;

	ADMUX = (0 << REFS0) 	// Set ADC reference to AVCC
		  | (0 << REFS1) 	// Set ADC reference to AVCC
		  | (1 << MUX1);    // Use ADC2 or PB4 pin for Vin
//		  | ADC2;        	// use ADC2 on PB4

	ADCSRA = (1 << ADEN)    // enable ADC
		   | 3; 			//  prescaler 8

	// kill all leds
	PORTB &= ~((1 << LED1) | (1 << LED2) | (1 << LED3) | (1 << LED4));
	_delay_ms(5);

	DDRB &= ~(1 << LED1); // let led generate some voltage
	_delay_ms(5);

	// warm up the ADC, discard the first conversion
	ADCSRA |= (1 << ADSC);
	while(ADCSRA & (1 << ADSC))
		; // wait for the measurement to complete

	for(i = 0; i < 4; i++)
	{
		_delay_ms(5);
		ADCSRA |= (1 << ADSC);
		while(ADCSRA & (1 << ADSC))
			; // wait for the measurement to complete
		val += ADC;
	}
	val >>= 2; // divide by 4

	ADCSRA = 0;             // disable adc
	DDRB |= (1 << LED1);    // re-enable led

	return val;
}

ISR(WDT_vect)
{
  //check if it's still dark
  sleep = (getLight() > AMBIENT_LIGHT) ? TRUE : FALSE;
}

int main(void)
{
	uint8_t lval1, lval2, lval3, lval4;
	uint8_t i, x, y;
	uint8_t mode;

	// set up wdt
	wdt_enable(WDTO_2S);

	WDTCR |= 0x40; // WDT generates interrupts instead of resets now.
				   // We want interrupts because a reset clears our nice random
				   // seeds, and an interrupt doesn't.

	for(i = 0; i < 10; i++)
	{
		_delay_ms(100);
	}

	// retrieve mode from eeprom and write back mode + 1
	mode = eeprom_read_byte(&mode_ee);
	mode = mode % 4;
	eeprom_write_byte(&mode_ee, mode + 1);

	sei(); // enable interrupts

	// go directly into sleep mode and lets wake up by the wdt
	sleep = TRUE;

	// enable leds
	DDRB = (1 << LED1) | (1 << LED2) | (1 << LED3) | (1 << LED4);

	while(1)
	{
		WDTCR |= 0x40; // make sure wdt keeps generating an int instead of a reset

		if(sleep)
		{
			// switch off all LEDs and power down
			PORTB &= ~((1 << LED1) | (1 << LED2) | (1 << LED3) | (1 << LED4));
			set_sleep_mode(SLEEP_MODE_PWR_DOWN);
			sleep_mode();
		}
		else
		{
			// get a random value for the leds intensity
			lval1 = getRandom();
			lval2 = getRandom();
			lval3 = getRandom();
			lval4 = getRandom();
			// Manually do some pwm
			for(x = 0; x < 20; x++)
			{
				if(mode == 0)
					PORTB |= (1 << LED1);
				else if(mode == 1)
					PORTB |= (1 << LED1) | (1 << LED2);
				else if(mode == 2)
					PORTB |= (1 << LED1) | (1 << LED2) | (1 << LED3);
				else if(mode == 3)
					PORTB |= (1 << LED1) | (1 << LED2) | (1 << LED3) | (1 << LED4);

				for(y = 0; y != 255; y++)
				{
					if(y == lval1)
						PORTB &= ~(1 << LED1);
					if(y == lval2)
						PORTB &= ~(1 << LED2);
					if(y == lval3)
						PORTB &= ~(1 << LED3);
					if(y == lval4)
						PORTB &= ~(1 << LED4);
					_delay_us(5);
				}
			}
		}
	}
}

Leave a Reply

Your email address will not be published. Required fields are marked *