Posted by
| Nick Gammon
Australia (23,121 posts) Bio
Forum Administrator |
Message
| A few nights ago the lights went out (power failure). I have torches scattered around the house, but the trick is to find them in the pitch darkness.
It occurred to me that it would be nice if there was a flashing light on or near a torch, so you could head for the light, and find the torch. Hence this project was born.
Design goals
- Keep power consumption low, so the flashing light could be relied on for a few years.
- To save power, detect when it was dark, and only flash the light then.
- Use a lithium "coin" battery because they are compact, and have a long shelf life.
Photos of device
View of processor (battery on table):
Back view showing LED and LDR:
Upside-down, showing battery:
Circuit side, showing parts:
Device sitting on a torch, ready for action:
Schematic
Design notes
To minimize power consumption, the processor sleeps in "power down" mode for two seconds, until it is woken by the watchdog timer. Then it does a ADC read (analog read) of the LDR (light dependent resistor) to see if it dark or not. If it is not dark it goes back to sleep.
To read the LDR it briefly applies power to the voltage divider (does a digitalWrite to D3 (that is, pin 2 on the chip). The ADC reading is taken in sleep mode SLEEP_MODE_ADC, which lets the processor sleep during the reading.
If it is dark, then the code turns timer 0 back on (so that millis() will work) and then turns on the LED, waits 2 mS, and turns it off again. This brief flash is plenty to spot the LED in the dark.
Construction notes
I soldered the whole thing together "dead bug" style on a 8-pin chip socket. This lets you remove the processor and reprogram it if necessary.
I tried to keep components to small sizes to keep the size down, and have less leads that might accidentally break off.
For the LED I used a Adafruit LED Sequin - Warm White which I had to hand. This is a compact LED, with an inbuilt 100 ohm resistor, saving the need to have an extra current-limiting resistor. Also as it turned out, the LED board fitted exactly between pins 4 and 5 of the chip, so all I had to to was drop it onto the pins and solder in place. The polarity is marked on the board with a "+" symbol.
LED "sequins" with built-in resistor:
For the light-detector I used a "50k to 100k" LDR which I had previously purchased from eBay. To measure the resistance you need a voltage-divider so I used a 56k surface-mounted resistor, carefully soldered between pin 2 and pin 5 (ground).
With the LED, LDR and resistor in place, all that remained was to solder Vcc to the + side of the button coin holder, and Gnd to the - side of the holder.
Alternative parts
Of course, you can use any suitable LED with a suitable current-limiting resistor (eg. 100 ohms or thereabouts), and you don't have to use SMD parts if you don't want to.
You can probably use an ATtiny25 or ATtiny45, it is not as if the code is particularly large:
Binary sketch size: 1,396 bytes (of a 8,192 byte maximum)
Prices
(At time of writing)
- From DigiKey: ATtiny25: $1.56, ATtiny45: $1.45, ATtiny85: $1.69
- LED Sequins: $3.95 for a pack of 5.
- 8-pin socket: around 20 cents
- LDR: around 1 cent
- Resistor: around 1 cent
- Button battery holder: around 12 cents
- CR2032: around 50 cents
Total cost: under $US 4.
Code
// ATtiny85 torch detector
// Author: Nick Gammon
// Date: 25 February 2015
// ATMEL ATTINY 25/45/85 / ARDUINO
// Pin 1 is /RESET
//
// +-\/-+
// Ain0 (D 5) PB5 1| |8 Vcc
// Ain3 (D 3) PB3 2| |7 PB2 (D 2) Ain1
// Ain2 (D 4) PB4 3| |6 PB1 (D 1) pwm1
// GND 4| |5 PB0 (D 0) pwm0
// +----+
/*
Pin 2 (PB3) <-- LDR (GL5539) --> Pin 7 (PB2) <----> 56 k <----> Gnd
Pin 5 (PB0) <---- LED ---> 100 R <-----> Gnd
*/
#include <avr/sleep.h> // Sleep Modes
#include <avr/power.h> // Power management
#include <avr/wdt.h> // Watchdog timer
const byte LED = 0; // pin 5
const byte LDR_ENABLE = 3; // pin 2
const byte LDR_READ = 1; // Ain1 (PB2) pin 7
const int LIGHT_THRESHOLD = 200; // Flash LED when darker than this
// when ADC completed, take an interrupt
EMPTY_INTERRUPT (ADC_vect);
// Take an ADC reading in sleep mode (ADC)
float getReading (byte port)
{
power_adc_enable() ;
ADCSRA = bit (ADEN) | bit (ADIF); // enable ADC, turn off any pending interrupt
// set a2d prescale factor to 128
// 8 MHz / 128 = 62.5 KHz, inside the desired 50-200 KHz range.
ADCSRA |= bit (ADPS0) | bit (ADPS1) | bit (ADPS2);
if (port >= A0)
port -= A0;
#if defined(__AVR_ATtiny85__)
ADMUX = (port & 0x07); // AVcc
#else
ADMUX = bit (REFS0) | (port & 0x07); // AVcc
#endif
noInterrupts ();
set_sleep_mode (SLEEP_MODE_ADC); // sleep during sample
sleep_enable();
// start the conversion
ADCSRA |= bit (ADSC) | bit (ADIE);
interrupts ();
sleep_cpu ();
sleep_disable ();
// reading should be done, but better make sure
// maybe the timer interrupt fired
// ADSC is cleared when the conversion finishes
while (bit_is_set (ADCSRA, ADSC))
{ }
byte low = ADCL;
byte high = ADCH;
ADCSRA = 0; // disable ADC
power_adc_disable();
return (high << 8) | low;
} // end of getReading
// watchdog interrupt
ISR (WDT_vect)
{
wdt_disable(); // disable watchdog
} // end of WDT_vect
#if defined(__AVR_ATtiny85__)
#define watchdogRegister WDTCR
#else
#define watchdogRegister WDTCSR
#endif
void setup ()
{
wdt_reset();
pinMode (LED, OUTPUT);
pinMode (LDR_ENABLE, OUTPUT);
ADCSRA = 0; // turn off ADC
power_all_disable (); // power off ADC, Timer 0 and 1, serial interface
} // end of setup
void loop ()
{
// power up the LDR, take a reading
digitalWrite (LDR_ENABLE, HIGH);
int value = getReading (LDR_READ);
// power off the LDR
digitalWrite (LDR_ENABLE, LOW);
// if it's dark, flash the LED for 2 mS
if (value < LIGHT_THRESHOLD)
{
power_timer0_enable ();
delay (1); // let timer reach a known point
digitalWrite (LED, HIGH);
delay (2);
digitalWrite (LED, LOW);
power_timer0_disable ();
}
goToSleep ();
} // end of loop
void goToSleep ()
{
set_sleep_mode (SLEEP_MODE_PWR_DOWN);
noInterrupts (); // timed sequence coming up
// pat the dog
wdt_reset();
// clear various "reset" flags
MCUSR = 0;
// allow changes, disable reset, clear existing interrupt
watchdogRegister = bit (WDCE) | bit (WDE) | bit (WDIF);
// set interrupt mode and an interval (WDE must be changed from 1 to 0 here)
watchdogRegister = bit (WDIE) | bit (WDP2) | bit (WDP1) | bit (WDP0); // set WDIE, and 2 seconds delay
sleep_enable (); // ready to sleep
interrupts (); // interrupts are required now
sleep_cpu (); // sleep
sleep_disable (); // precaution
} // end of goToSleep
There is a bit of conditional code to allow for testing on a Uno, before migrating to the Attiny85.
Power budget
It is nice to calculate how long this should last before running down the battery.
First, for 99% of the time, it will be sleeping in sleep mode "power down" with the watchdog timer enabled. According to the datasheet that should use 4.4 µA at 3V supply which agrees with what I measured.
Then, every two seconds, it wakes up and does the ADC measurement. This takes around 430 µS, thus it it awake for this fraction of time:
According to the datasheet the ADC uses 85 µA at 3V (and 4 MHz). Even though we are running at 8 MHz we can probably use that as a ballpark figure. So that 85µA can be averaged out by multiplying by the amount of time it is awake.
So on average the ADC conversions adds another 0.018 µA to our budget.
In addition to this, if it is dark (say for a third of each day) then we flash the LED for 2 mS every 2 seconds.
Thus the average time the LED is on is for:
I measured the current consumption as being 7 mA, so therefore the average consumption will be:
So our total power budget is:
- Continuous: 4.4 µA
- ADC conversions: 0.018 µA
- Flashing LED: 2.31 µA
All up: 6.728 µA
As an estimate, let's assume a CR2032 battery has a capacity of 210 mAH.
Therefore it should run for:
210 / 0.006728 = 31212.84 hours
Divide by 24 to get days, and 365 to get years and we have:
31212.84 / 24 / 365 = 3.56 years
There you are! The device should light the way to your torch for over 3 years.
Other applications
If you make up a couple of these, you could deploy them in other places. For example:
- Emergency exit light - over a door
- Burglar deterrent - a light flashing in your front room, visible from the street, suggests some sort of monitoring system is active
- Inside a dummy TV camera. If you mount a TV camera over your front door, pointing at visitors, having a flashing LED makes it look "alive".
Simpler version
Also see Simpler torch flasher which I posted on the Arduino Forum.
// ATtiny85 sleep mode, wake on pin change interrupt or watchdog timer
// Author: Nick Gammon
// Date: 12 October 2013
// ATMEL ATTINY 25/45/85 / ARDUINO
//
// +-\/-+
// Ain0 (D 5) PB5 1| |8 Vcc
// Ain3 (D 3) PB3 2| |7 PB2 (D 2) Ain1
// Ain2 (D 4) PB4 3| |6 PB1 (D 1) pwm1
// GND 4| |5 PB0 (D 0) pwm0
// +----+
#include <avr/sleep.h> // Sleep Modes
#include <avr/power.h> // Power management
#include <avr/wdt.h> // Watchdog timer
const byte LED = 3; // pin 2
// watchdog interrupt
ISR (WDT_vect)
{
wdt_disable(); // disable watchdog
} // end of WDT_vect
void resetWatchdog ()
{
// clear various "reset" flags
MCUSR = 0;
// allow changes, disable reset, clear existing interrupt
WDTCR = bit (WDCE) | bit (WDE) | bit (WDIF);
// set interrupt mode and an interval (WDE must be changed from 1 to 0 here)
WDTCR = bit (WDIE) | bit (WDP2) | bit (WDP1) | bit (WDP0); // set WDIE, and 2 seconds delay
// pat the dog
wdt_reset();
} // end of resetWatchdog
void setup ()
{
resetWatchdog (); // do this first in case WDT fires
pinMode (LED, OUTPUT);
} // end of setup
void loop ()
{
digitalWrite (LED, HIGH);
delay (1);
digitalWrite (LED, LOW);
goToSleep ();
} // end of loop
void goToSleep ()
{
set_sleep_mode (SLEEP_MODE_PWR_DOWN);
ADCSRA = 0; // turn off ADC
power_all_disable (); // power off ADC, Timer 0 and 1, serial interface
noInterrupts (); // timed sequence coming up
resetWatchdog (); // get watchdog ready
sleep_enable (); // ready to sleep
interrupts (); // interrupts are required now
sleep_cpu (); // sleep
sleep_disable (); // precaution
power_all_enable (); // power everything back on
} // end of goToSleep
|
- Nick Gammon
www.gammon.com.au, www.mushclient.com | Top |
|