Attiny85 AVR Arduino PIR Triggered Timed Night Light
by CScientific in Circuits > Electronics
661 Views, 1 Favorites, 0 Comments
Attiny85 AVR Arduino PIR Triggered Timed Night Light
This project is a programmable timed night light with very low idle (quiescent) power usage, a couple of micro amps. 3x C Cell alkaline batteries are used. Fresh C Cell alkaline batteries provide about 8000 milliAh. At two microamps in quiescent, in theory, the batteries would last over 450 years. Well past their serviceable life of course. The actual battery life is determined by how often the PIR is triggered. In testing, when the light strip was on, it drew around 100 milliamps.
A nice thing about using the PIR in this project is you never have to touch it to turn it on. And using the attiny85 as a timer combined with a small mosfet, you never have to touch it to turn it off. The PIR triggers easily though clear plastic. Note, the PIR will not trigger through glass (in case you thought to make one in a jar).
Programming the attiny85 for extremely low quiescent power usage is probably the most interesting part of this project.
Most of the supplies used were sourced through Amazon. Without precisely determining the cost of each item, it was probably a $20 project. A lot of the miscellaneous/consumable supplies were already on hand.
I ended up soldering everything together 'dead bug' style. I originally put one together using a small PCB project board. Unfortunately it did not fit in this project box with the batteries. I'm not particularly happy about how the soldering came out, but it works. I originally tried D Cell batteries in the project box for longer life, they didn't fit.
The project uses four of the attiny85 pins. VCC (pin 8), GND (pin 4), PIR interrupt (pin 5) and mosfet gate (pin 7). Unused pins were trimmed from the 8pin DIP socket. If I were to reprogram the chip specifically for dead bug programming I would change PIR interrupt to pin 2 and put mosfet gate on pin 5. This would make soldering easier. Note, the pin changes would be easy to make in the code.
The mosfet is required because the attiny85 is not designed to push enough current through its pins for the light strip. I have a semiconductor analyzer that I used to determine the DGS (drain, gate, source) of the mosfet. The mosfet product description on Amazon did not include the pinout. The test tool I use is very nice (PEAK Atlas DCA75), but a bit pricey ($160 on Amazon). Short of a specific test tool, the mosfet datasheet is probably available online and I believe there are techniques using a simple diode tester to determine the correct pinout. It would be frustrating to get it wrong though.
The pulldown resistor on the interrupt pin was required to reach minimum quiescent power usage in the attiny85. The pulldown resistor can be any sufficiently large value. It goes from the PIR interrupt pin to ground. Using too low of a resistor value will prevent the PIR from triggering an interrupt. I tried a 10K resistor and it didn't work. I jumped to 1M and it worked. There are undoubtedly values between those two (1M and 10K) that would work. If you wanted to attempt to calculate a minimum usable resistor value, the key point is that the resistor forms a voltage divider to GND with the PIR input voltage. If too much of the PIR trigger voltage goes to ground, there won't be sufficient voltage to trigger an interrupt.
The LED light strip came with a 3x AA switched battery holder attached. The battery holder was cut off for this project. Do not trust the markings on the LED light strip wires. In my case the wire with the '-' symbol was the positive. Getting this wrong is also frustrating. Once the correct polarity was identified by testing, some black heat shrink on the negative wire helps keep the polarity identifiable.
Supplies
- attiny85 DIP microprocessor running at 1 MHz
- 8pin DIP socket
- PIR (passive infrared) sensor (five for $12 through Amazon)
- 500mA N-channel mosfet (ten for $6.50 through Amazon)
- 1M ohm pulldown resistor
- 5 volt led light strip (about $10 through Amazon, used half of it in the project)
- 3x C Cell battery holder 4.5V (two for $8 through Amazon)
- The project box was $1 from Walmart
- Dupont crimp connectors male and female, three pin socket for the PIR
- 26 gauge stranded wire
- Small diameter clear and black heat shrink tubing
- Hot glue gun
Physical Construction 1 (hot Glue)
Using a hot glue gun, the LED light strip and the battery holder are affixed to the inside of the project box.
The LED light strip comes with adhesion, but for this project it is on the wrong side. The hot glue did not adhere very well to the front of the LED strip, but it was sufficient to get the lights wound into place. For maximum light output try not to get the glue directly above the LEDs, but this is not critical. Once all of the LED strip that will fit has been wound into the box, the LED strip can be cut. It is made for this, cut lines are marked. Save the unused portion of the light strip for another project.
The 3x C Cell battery holder was next glued into place. The fit is snug and helps to hold the LED light strip in place. Place the battery holder opposite of where the wires will connect. This will give more room for the 'circuit' and the PIR. There is not a lot of room, so push it up against the opposite end when hot gluing it in place.
Programming
The Arduino IDE and the SparkFun AVR programmer were used to program the attiny85.
The attiny85 bootloader was configured to run at 1Mhz using an internal clock.
The source code is below. There are some interesting AVR/attiny85 specific coding techniques.
A few notes.
This code is interrupt driven, no polling, this helps to get to very low quiescent power utilization.
The _delay_ms() function call does not work reliably with large values. To avoid this issue programmed delays were coded in loops starting with a one second delay. The code below will turn on for 90 seconds after a PIR trigger and then flash three times before going to sleep again. It is very easy to increase or decrease the amount of time the lights will stay on.
There is a lot of attiny85 specific register manipulation in the code below designed to minimize power usage. There are arduino power functions that incorporate most if not all of these techniques into one call. This is where I discovered the pulldown resister was essential to minimizing power usage. In spite of all the code optimization below, quiescent was still in the milliamp range until I added the pulldown resistor. Even after identifying the need for the pulldown resistor, I left all of the detail below in the code instead of returning to the arduino power functions because I think the register details are instructive for reference. The attiny85 datasheet was an essential reference as well as some generous/smart people's related online posts.
/*
Timed delay turn off
Using an attiny85 for low power down timer.
A push button will trigger an interrupt that turns on mosfet for hardcoded time limit.
Code set to run on an attiny85 at 1 Mhz internal clock.
Interrupt pin:
Arduino Attiny85 Attiny85 Attiny85
ref port Intrpt physical
0 PB0 PCINT0 5
Mosfet pin:
Arduino Attiny85 Attiny85 Attiny85
ref port Intrpt physical
2 PB2 PCINT2 7
*/
#include "avr/sleep.h"
// interrupt pin
int intPin = 0; // Attiny85 PB0 Physical pin 5
int inInterrupt=0;
int timedDelay = 90;
// _delay_ms() cannot use large arguments, breaking delays into loops
void delay_a_second(void) { _delay_ms(1000); }
void delay_seconds(int j) { for (int i = 0; i < j; i++) delay_a_second(); }
void delay_a_minute(void) { for (int i = 0; i < 60; i++) delay_a_second(); }
void delay_minutes(int j) { for (int i = 0; i < j; i++) delay_a_minute(); }
void setup() {
// initialize all pins as inputs
DDRB=0;
PORTB=0; // set all pins low
// Set 2 to be an output to turn on mosfet
DDRB |= (1<<PB2);
PORTB &= (0<<PB2); // turn off
// allow PIR a few seconds to stabilize
_delay_ms(10*1000);
// disable watchdog timer
WDTCR |= (1<<WDCE) | (1<<WDE); // two steps must be performed within four clock cycles
WDTCR &= ~(1<<WDE);
} // end setup
// configure an interrupt on PB0 (PCINT0)
void initDelayPinInterrupt(void)
{
GIMSK |= (1<<PCIE);
PCMSK |= (1<<PCINT0);
sei();
} // end initPIRInterrupt
// action performed on interrupt
ISR(PCINT0_vect)
{
if (digitalRead(intPin)==HIGH)
{
timerStart();
} // end if
GIFR |= (1<<PCIF); // clear the interrupt
} // end ISR
void timerStart(void)
{
inInterrupt=1;
PORTB |= (1<<PB2); // turn on mosfet
// main delay
delay_seconds(timedDelay);
// flash to alert that mosfet is about to turn off
for (int i=0;i<3;i++)
{
PORTB &= (0<<PB2); // turn off mosfet
delay_a_second();
PORTB |= (1<<PB2); // turn on mosfet
delay_a_second();
} // end if
PORTB &= (0<<PB2); // turn off mosfet
inInterrupt=0;
} // end timerStart
void loop()
{
volatile uint8_t dataFlush;
uint8_t byte1st,byte2nd;
initDelayPinInterrupt(); // initialize the interrupt pin
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
sleep_enable();
while (1)
{
// sleep mode will block sleeping until the interrupt pin is triggered
// disable components to reduce power usage while sleeping
ADCSRA &= ~(1<<ADEN); // Disable ADC set bit ADEN to zero
ACSR &= ~(1<<ACIE); // Clear Analog Comparator interrupt bit (set to zero)
ACSR |= (1<<ACD); // Disable Analog Comparator set bit ACD to one
byte2nd = MCUCR|(1<<BODS); // Disable Brown Out detector
byte2nd &= ~(1<<BODSE); // Make sure BODSE is zero on second assignment
byte1st=byte2nd|(1<<BODSE); // To enable BOD change.
dataFlush=byte1st+byte2nd; // dataFlush being volatile forces byte1st and byte2nd
MCUCR=byte1st; // Set with BODSE.
MCUCR=byte2nd; // And without within 4 cycles.
PRR |= (1<<PRTIM1) | (1<<PRTIM0) | (1<<PRUSI) | (1<<PRADC); // Use power reduction register
sleep_mode();
// delay until interrupt action is finished before returning to sleep
_delay_ms(100);
while (inInterrupt==1) { _delay_ms(100); } // end while
} // end while
} // end loop<br>
Physical Construction 2 (dead Bug)
This is the embarrassing part of this instructable. The circuit construction is really ugly. This is the third one I've built. The first was in a much larger box and the circuit was build on a PCB. When I tried that in this box there wasn't enough room to get everything inside. So I soldered everything together directly to the DIP socket. It's hard to be helpful here because it's a mess. As noted in the introduction, if I was doing this dead bug again, I'd use different pins.
I used dupont crimp pins on the battery, PIR and LED light strip wires. Adding clear heat shrink tubing to each pin helps prevent the connections from short circuiting when everything was shoved together in the tight space.
I cut unused pins off of the 8pin DIP socket to make it a bit easier to solder.
Use a good flux on each connection. And contrary to everything I've ever read, with these very small connections, carrying a bit of freshly melted solder on the iron to the fluxed connection usually works pretty well.
Solder to GND (pin 4):
- Battery '-'
- PIR '-'
- mosfet 'source'
- pulldown resistor
Solder to VCC (pin 8):
- Battery '+'
- PIR '+'
- LED strip '+'
Solder to PIR interrupt (pin 5):
- PIR output
- other end of pulldown resistor
Solder to mosfet gate pin (pin 7):
- mosfet 'gate'
Solder LED strip '-' to mosfet 'drain'
Then cross your fingers and hope it works.
It probably won't work correctly the first time, mine didn't. Don't give up and walk away yet. It's frustrating after all the work for it not to just fire up, but debugging is part of the process.
Debugging.
Check the voltage on VCC and GND, it should be ~4.5 volts. Mine wasn't. The C Cells needed to be reset in the battery holder.
Touch a wire from VCC to the PIR interrupt pin (pin 5), this should trigger the lights on regardless of whether the PIR is working. Mine didn't. Looking closer I saw that one mosfet pin had broken off, ugh. Clipped the broken mosfet off and soldered a new one in place.
Voila, that fixed it for me.
Other debugging could include testing the voltage on the PIR output (pin 5). It needs to be around 3 volts or over to trigger an interrupt. Also testing the output voltage on the mosfet gate pin (pin 7), it should go high when the PIR interrupt is triggered.
*NOTE*: There is a ten second delay built into the logic to give the PIR time to stabilize before setting the interrupt the first time. Don't confuse this for failure, just wait a bit before seeing if the lights come on or not.
Success
It works!
At the end of it all you should be pleasantly surprised to see the lights come on.
Place the PIR sensor facing out through a clear part of the plastic. It should be fairly sensitive to anyone passing by it or waving their hand over the sensor.
For maximum battery life this would be ideal in some dark space that isn't used much. The light is sufficient to softly illuminate a room and keep you from busting your toe or shin on some immovable object.
It would probably be useful as a night light for a young child as well, instead of an always on night light. This is if you could convince them not to take it apart. Battery usage in that scenario of course would vary.
Even if you don't try to make this project, I hope that you were able to find something useful in the code example.
Thanks for checking it out.
Update, Added a Light Sensor
Before the C cell version of the original instructable, I made a version of this project that used three D cell batteries in a larger plastic box.
The D cell version was in a location where it was continuously being triggered, the batteries only lasted a disappointing four months.
I've updated the project by adding a light sensor, so that the LED lights only come on if it is dark. To further preserve the battery life, the light sensor is only turned on for a half second whenever the PIR sensor is triggered. If the light sensor output remains low (indicating light) everything goes back to the PIR interrupt driven state without turning on the LED lights. If the light sensor output goes high (indicating dark) the LED lights are turned on.
The light sensor pulls a bit over 100 microamps when it is on. It's use is limited to only a half second whenever the PIR interrupt is triggered. A half second was long enough to turn it on (with a mosfet switch) and get a good reading.
This is the light sensor used: https://www.amazon.com/gp/product/B07XFZ99XL
It is inexpensive and worked well for the purpose. I tinkered with the potentiometer located on the sensor to bring the light level as dim as possible before indicating dark. I would have preferred it to go lower than it could be set before triggering dark (high), but for the price it is quite adequate.
The new mosfet is the same type as was already used in this project.
Updated code is below and an updated schematic is in the images. I used a bread board for this one. The ElectroCookie Mini Solderable Breadboard was used.
Thanks for checking the update out.
Thanks for having a look.
Any questions, feel free to inquire.
/*
Timed delay turn off
Using an attiny85 for low power down timer.
A push button will trigger an interrupt that turns on mosfet for hardcoded time limit.
Code set to run on an attiny85 at 1 Mhz internal clock.
Interrupt pin:
Arduino Attiny85 Attiny85 Attiny85
ref port Intrpt physical
0 PB0 PCINT0 5
LED Mosfet pin:
Arduino Attiny85 Attiny85 Attiny85
ref port Intrpt physical
1 PB1 PCINT1 6
Light Sensor pin:
Arduino Attiny85 Attiny85 Attiny85
ref port Intrpt physical
2 PB3 PCINT3 2
Light Sendor Mosfet pin:
Arduino Attiny85 Attiny85 Attiny85
ref port Intrpt physical
3 PB4 PCINT4 3
*/
#include "avr/sleep.h"
// interrupt pin
int intPin = 0; // Attiny85 PB0 Physical pin 5
// light sensor pin
int senPin = 3; // Attiny85 PB3 Physical pin 2
int inInterrupt=0;
int timedDelay = 90;
// _delay_ms() cannot use large arguments, breaking delays into loops
void delay_a_second(void) { _delay_ms(1000); }
void delay_seconds(int j) { for (int i = 0; i < j; i++) delay_a_second(); }
void delay_a_minute(void) { for (int i = 0; i < 60; i++) delay_a_second(); }
void delay_minutes(int j) { for (int i = 0; i < j; i++) delay_a_minute(); }
void setup() {
// initialize all pins as inputs
DDRB=0;
PORTB=0; // set all pins low
// Set 1 to be an output to turn on led mosfet
DDRB |= (1<<PB1);
PORTB &= (0<<PB1); // turn off
// Set 4 to be an output to turn on sensor mosfet
DDRB |= (1<<PB4);
PORTB &= (0<<PB4); // turn off
// allow PIR a few seconds to stabilize
_delay_ms(10*1000);
// disable watchdog timer
WDTCR |= (1<<WDCE) | (1<<WDE); // two steps must be performed within four clock cycles
WDTCR &= ~(1<<WDE);
} // end setup
// configure an interrupt on PB0 (PCINT0)
void initDelayPinInterrupt(void)
{
GIMSK |= (1<<PCIE);
PCMSK |= (1<<PCINT0);
sei();
} // end initPIRInterrupt
// action performed on interrupt
ISR(PCINT0_vect)
{
if (digitalRead(intPin)==HIGH)
{
timerStart();
} // end if
GIFR |= (1<<PCIF); // clear the interrupt
} // end ISR
void timerStart(void)
{
inInterrupt=1;
// check see if it there is light before turning on LED
// turn on mosfet to power light sensor
PORTB |= (1<<PB4); // turn on sensor mosfet
_delay_ms(500); // give sensor a half second before taking a reading
if (digitalRead(senPin)==LOW)
{ // there is light, return to waiting on interrupt
PORTB &= (0<<PB4); // turn off sensor mosfet
inInterrupt=0;
return;
} // end if
PORTB &= (0<<PB4); // turn off sensor mosfet
// TURN ON LED MOSFET
PORTB |= (1<<PB1); // turn on led mosfet
// main delay
delay_seconds(timedDelay);
// flash to alert that led mosfet is about to turn off
for (int i=0;i<3;i++)
{
PORTB &= (0<<PB1); // turn off led mosfet
delay_a_second();
PORTB |= (1<<PB1); // turn on led mosfet
delay_a_second();
} // end if
PORTB &= (0<<PB1); // turn off led mosfet
inInterrupt=0;
} // end timerStart
void loop()
{
volatile uint8_t dataFlush;
uint8_t byte1st,byte2nd;
initDelayPinInterrupt(); // initialize the interrupt pin
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
sleep_enable();
while (1)
{
// sleep mode will block sleeping until the interrupt pin is triggered
// disable components to reduce power usage while sleeping
ADCSRA &= ~(1<<ADEN); // Disable ADC set bit ADEN to zero
ACSR &= ~(1<<ACIE); // Clear Analog Comparator interrupt bit (set to zero)
ACSR |= (1<<ACD); // Disable Analog Comparator set bit ACD to one
byte2nd = MCUCR|(1<<BODS); // Disable Brown Out detector
byte2nd &= ~(1<<BODSE); // Make sure BODSE is zero on second assignment
byte1st=byte2nd|(1<<BODSE); // To enable BOD change.
dataFlush=byte1st+byte2nd; // dataFlush being volatile forces byte1st and byte2nd
MCUCR=byte1st; // Set with BODSE.
MCUCR=byte2nd; // And without within 4 cycles.
PRR |= (1<<PRTIM1) | (1<<PRTIM0) | (1<<PRUSI) | (1<<PRADC); // Use power reduction register
sleep_mode();
// delay until interrupt action is finished before returning to sleep
_delay_ms(100);
while (inInterrupt==1) { _delay_ms(100); } // end while
} // end while
} // end loop