I am using an attiny85 to control an LED. I want the LED to blink every 8s to check if the system is alive, and to also turn on if an pin change is detected on PB4. I am aiming at minimizing power consumption as much as possible, since I am running the system with a very small solar panel. The interrupt pin is connected to a IR sensor.
A simpler version of the same script is working fine overnight and on cloudy days and the charge lasts for days at a time. However, on sunny days the IR sensor gets triggered by the Sun (the behaviour goes away if I cast shadow on the sensor) and the system consumes significantly more power.
To reduce the issue via software, I would like to disable the Pin Change Interrupt for slightly less than one second every time the Pin Change is triggered, so that the trigger can only happen roughly once per second even if the Sun is hitting the IR sensor. I cannot manage to make this part work correctly. Testing on a breadboard with a jumper the LED lights up every time the jumper touches the ground pin.
Any idea? Thanks in advance
Code:
#include <avr/sleep.h> // Sleep Modes
#include <avr/power.h> // Power management
const byte LED = 3; // pin 2
const byte SWITCH = 4; // pin 3 / PCINT4
volatile byte flag = 0;
ISR (PCINT4_vect) {
cli(); // disable interrupts
flag = 1;
GIMSK &= ~(1<<PCIE); // disable Pin Change Interrupts
sei(); // Enable interrupt
}
//This runs each time the watch dog wakes us up from sleep
ISR(WDT_vect) {
//watchdog_counter++;
}
void setup () {
pinMode(0, INPUT_PULLUP); // pull unused pins to reduce power consumption
pinMode(1, INPUT_PULLUP);
pinMode(2, INPUT_PULLUP);
pinMode(5, INPUT_PULLUP);
pinMode (LED, OUTPUT);
pinMode (SWITCH, INPUT_PULLUP);
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
ADCSRA = 0; // turn off ADC to reduce power consumption
PCMSK |= bit (PCINT4); // want pin D4 / pin 3
GIFR |= bit (PCIF); // clear any outstanding interrupts
GIMSK |= bit (PCIE); // enable pin change interrupts
} // end of setup
void loop () {
digitalWrite (LED, HIGH);
delayMicroseconds(250);
digitalWrite (LED, LOW);
if (flag==1) { // an pin change was detected. Use watchdogs to keep interrupt disabled for 990ms
setup_watchdog(5); //Setup watchdog to go off after 500msec
sleep_mode(); //Go to sleep! Wake up with watchdog
setup_watchdog(4); //Setup watchdog to go off after 250msec
sleep_mode(); //Go to sleep! Wake up with watchdog
setup_watchdog(3); //Setup watchdog to go off after 128msec
sleep_mode(); //Go to sleep! Wake up with watchdog
setup_watchdog(2); //Setup watchdog to go off after 64msec
sleep_mode(); //Go to sleep! Wake up with watchdog
setup_watchdog(1); //Setup watchdog to go off after 32msec
sleep_mode(); //Go to sleep! Wake up with watchdog
setup_watchdog(0); //Setup watchdog to go off after 16msec
sleep_mode(); //Go to sleep! Wake up with watchdog
flag = 0; // remove flag
GIMSK |= (1<<PCIE); // enable pin change interrupts
PCMSK |= (1<<PCINT4); // Set PCINT4 in the PCMSK register
}
setup_watchdog(9); //Setup watchdog to go off after 8sec
sleep_mode(); //Go to sleep! Wake up with watchdog/PinChange
} // end of loop
//Sets the watchdog timer to wake us up, but not reset
//0=16ms, 1=32ms, 2=64ms, 3=128ms, 4=250ms, 5=500ms
//6=1sec, 7=2sec, 8=4sec, 9=8sec
//From: http://interface.khm.de/index.php/lab/experiments/sleep_watchdog_battery/
void setup_watchdog(int timerPrescaler) {
//if (timerPrescaler > 9 ) timerPrescaler = 9; //Limit incoming amount to legal settings
byte bb = timerPrescaler & 7;
if (timerPrescaler > 7) bb |= (1<<5); //Set the special 5th bit if necessary
//This order of commands is important and cannot be combined
MCUSR &= ~(1<<WDRF); //Clear the watch dog reset
WDTCR |= (1<<WDCE) | (1<<WDE); //Set WD_change enable, set WD enable
WDTCR = bb; //Set new watchdog timeout value
WDTCR |= _BV(WDIE); //Set the interrupt enable, this will keep unit from resetting after each int
}
Normally, it should only be activated by the IR signal every second. During daylight, it seems like the Sun IR light triggers the phototransistor, even if reflected and not direct. If I cover the sensor well enough to keep it in the shadow, the system works fine
I have easier access to Pin 3 (PCINT4), but I could use Pin 7 if I need to (I use Pin 7 to reprogram the attiny while I trouble shoot, while Pin 3 is not used for re-progamming. I used that Pin to simplfy things for me a bit)
Until the next IR trigger (so slightly less than 1 sec). If no IR triggers arrives within 8s, I use the watchdog to blink once
The phototransistor pushes the pin to ground through a 1k resistor (I can probably remove the resistor, but don't want to risk pulling too much current from the attiny in case I configure the pin to output by mistake).
1. Configure WDT for System Reset Mode and NOT for Interrupt Mode. On WDT System Reset Mode, the LED may blink once every 8 sec interval with 500 ms duration in the setup() function.
2. When interupting signal will arrive at PCINT4-pin, the MCU will wakeup and will reset the WDT Timer in the ISR() and will just blink LED with 100 ms duration in the loop() function. If iinterrupting signal does not arrive within 8-sec, the MCU will reboot from location 0x0000.
3. You can test your sketch by injecting 1-Hz signal (from a 555-Timer or from a FG or from a UNO) on the PCINT4-pin.
Thanks for the suggestion. I will try to implement the System Reset Mode.
However, I do not understand how this can solve my original problem. If sunlight triggers my sensor randomly (or always active), the PCINT4 pin might see several activations in a single second, activating the MCU multiple times in a second (or the MCU remains inside the Pin Change ISR the whole time if sensor is always active? This would be consistent with the rate of discharge I measured).
I would like to disable the PCINT4 interrupt every time it is called for 990ms (so it can only activate once per second), This would be the purpose of the GIMSK &= ~(1<<PCIE); // disable Pin Change Interrupts
line in the Pin Change ISR. But it does not seem to work as I would expect
@river727
The other day, I have ruined my two ATtiny85 while experimenting with Fuse Bits and the ArduinoISP. I am waiting to receive a relatively robust Attiny85 Breakout Board.
In the meantime, I have experimented your idea on Arduino UNO; where, the 1 Hz test signal (I have made by NANO) is injected on PCINT20-pin of UNO.
Task: Design an experiment to put the MCU of the UNO Board into Power-down sleep mode. It will wake up every time the low level of a 1 Hz signal arrives at PCINT20-pin and L (built-in LED of UNO) will flash for 50 ms as an indication. If the wake-up signal does not arrive within 8 sec, the WDT timeout will force the MCU to reboot and will blink L three times at 400 ms interval to indicate system reset.
Sketch (tested)
#include <avr/sleep.h>
#include<avr/wdt.h>
#define LED 13
volatile bool flag = false;
void setup()
{
pinMode (LED, OUTPUT);
//-------system reset indication-----
for (int i = 0; i < 3; i++)
{
digitalWrite(LED, HIGH);
delay(200);
digitalWrite(LED, LOW);
delay(200);
}
//---- sleep mode initi-------------
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
//-------WDT intialization----------
WDTCSR |= (1 << WDCE) | (1 << WDE);
WDTCSR = (0 << WDIE) | (1 << WDE) | (1 << WDP3) | (1 << WDP0); //WDT system reset 8-sec time out
//----- PCINT20 intializatin---------
pinMode(4, INPUT_PULLUP); //PCINT20-pin
bitSet(PCICR, PCIE2);
bitSet(PCMSK2, PCINT20);
bitSet(SREG, 7); //global interrupt enable bit is active
//-------------------------------------------------
}
void loop()
{
if (flag == true)
{
noInterrupts();
digitalWrite(LED, HIGH);
delay(50);
digitalWrite(LED, LOW);
delay(50);
//--------------------
while(digitalRead(4) != HIGH)
{
; //wait until PCIN20-pin goes HIGH to avoid multiple interrupts
}
interrupts();
flag = false;
}
sleep_enable();
sleep_cpu(); //asm: sleep; the MCU sleeps
sleep_disable();
}
ISR (PCINT2_vect)
{
wdt_reset(); //reset the WDT Timer before it expires 8-sec delay
flag = true;
}
Thanks!
I was also trying your test using a ìn Arduino to generate the 1Hz signal. With your latest sketch (changing pin numbers) it seems to work fine on my attiny85v.
In the meantime, I also realised that my original issue was probably instead caused by the unpredictable behaviour of the attiny when powering up from a slowly charging capacitor and I have now set the fuses to enable the brown out detection (I still need to understand if that significantly affects the overall power consumption). I found this link quite useful and simple to manually setting the fuses, in case it can be useful for your future experiments:
So, on the plus side, I will likely be able to revert to my previous version of my sketch. The IR sensor will not work in strong daylight, but it will start working after the light goes down a bit (which is ok for now).
On the minus side, I still did not understand how to temporarily disable the Pin Change interrupt from my script
Ah... didn't notice that, even while testing. But I think I did not explain myself properly, sorry for that.
Your solution does not solve my problem because while in the "while" loop, the MCU is on. I want to turn it off as quickly as possible and as long as possible to save power.
My original plan was to disable the Pin Change interrupt for 990ms every time a Pin Change interrupt occurs, and send the MCU to sleep in the meantime. That's because I expect to receive a signal every 1 second at most, so I can ignore the sensor input for 990ms every time I detect a signal.
While testing I did not notice because I am pushing a buttom a few times per second, and each time my led turns on. I would like it to turn on max once per second.
A way to rephrase the problem would be to increase the arduino 1Hz signal to a 10Hz signal (or 100Hz signal) and rewrite the scirpt in such a way that the attiny85 will wake up from sleep no more than once per second (not only it should not blink the LED more than once per second, but it should ignore all other interrupt signals until 1s elapses and stay asleep as long as possible).
Here is a possible solution that I have tested in UNO setup by injecting 1 Hz test signal at the INT0-pin instead of PCINT20-pin. Now, there is no need to wait to see that interrupting signal has gone to HIGH state. The trigger takes place just at the falling edge of the interrupting signal. There is absolutely no multiple interrupts within 1 sec duration of the test signal. Use of push button to inject interrupting signal is not recommended due to bounces of the button.
For ATtiny85, you use INT0-pin (Pin-7) to receive interrupting signal from IR sensor. The use of INT0-pin will not disturb the sketch uploading process using ISP Port. First, you upload the sketch using INT0-pin as SCK-pin and then you connect IR's output at the INT0-pin.
#include <avr/sleep.h>
#include<avr/wdt.h>
#define LED 13
#define INT0 2
volatile bool flag = false;
void setup()
{
pinMode (LED, OUTPUT);
pinMode(INT0, INPUT_PULLUP);
//-------system reset indication-----
for (int i = 0; i < 3; i++)
{
digitalWrite(LED, HIGH);
delay(200);
digitalWrite(LED, LOW);
delay(200);
}
//---- sleep mode initi-------------
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
//-------WDT intialization----------
WDTCSR |= (1 << WDCE) | (1 << WDE);
WDTCSR = (0 << WDIE) | (1 << WDE) | (1 << WDP3) | (1 << WDP0); //WDT system reset 8-sec time out
/* //----- PCINT20 intializatin---------
pinMode(4, INPUT_PULLUP); //PCINT20-pin
bitSet(PCICR, PCIE2);
bitSet(PCMSK2, PCINT20);
bitSet(SREG, 7); //global interrupt enable bit is active
//-------------------------------------------------*/
//-------INT0 intialization--------
attachInterrupt(digitalPinToInterrupt(INT0), ISRINTZ, FALLING);
}
void loop()
{
if (flag == true)
{
//noInterrupts();
digitalWrite(LED, HIGH);
delay(10);
digitalWrite(LED, LOW);
delay(10);
//--------------------
/* while(digitalRead(4) != HIGH)
{
;
}*/
//interrupts();
flag = false;
}
sleep_enable();
sleep_cpu(); //asm: sleep; the MCU sleeps
sleep_disable();
}
void ISRINTZ()//ISR (PCINT2_vect)
{
wdt_reset(); //reset the WDT Timer before it expires 8-sec delay
flag = true;
}
I tested your script and it works as expected. The only downside is that the attiny can still wakes up more than once per second, increasing the power consumption. Still a bit of power is saved by not powering the LED. That's probably going to be good enough for me.
In parallel, I figured out my issue was instead likely caused by unstable MCU operations at low voltage. I modified the fuses to activate brownout detection and added a script to disable it while sleeping to avoid additional power consumption.
Brownout detection should kick in as soon as the MCU wakes up and ideally solve the problem. Seemed to have worked fine today, but I will need a few days to test different illumination conditions.
In the meantime, thank you very much for your help!!