[SOLVED] ATtiny85, Timer, and Sleep

Hi Guys,

I have made a light sensitive switch using an ATtiny85 to turn on battery powered leds when it gets dark and leave them on for 3 hours, switch it off for 9 hours and go back to waiting for it to get dark again.

Version 1: lasted 3 days.... 1066 mA use per day and cooked 2 of my 18650 batteries. So I then took some time out to learn about power reduction.

Version 2: I managed to reduce the power consumption to 56.1 mA per day and with 2x 18650 in parallel. It has a theoretical 104 day endurance. I also built in a weak low voltage cut off, it will turn off the led at 3v and not allow it to come back on. It draws 1.7mA in this state, so I should have time to notice when the lights stop coming on to recharge the batteries before any damage occurs.

Version 3: I have been playing around with sleep functions and I can get the consumption down to 0.6mA, led off, which would push my endurance out to 180 days theoretically, at which stage I’ll probably just install 2 more 18650s and get a whole year out of it.

But what I can’t do is get sleep and my program working together. I know ADC has gone to sleep and the millis count is off. But I can’t fix it, I’ve tried many sleep examples to no avail.

Here is my working code without any sleep, 1.7mA led off, 6.8mA led on.

enum STATE {
  WAIT_LOW_LIGHT,
  LED_ON,
  LED_OFF,
};

STATE state = WAIT_LOW_LIGHT;

int ledPin =  0;              // the number of the LED pin
unsigned long previousMillis = 0;        // will store last time LED was updated
long OnTime = 5000;           // milliseconds of on-time
long OffTime = 5000;          // milliseconds of off-time


void setup ()
{
  pinMode(ledPin, OUTPUT);
  pinMode(1, INPUT_PULLUP); //saves power on unused pins
  pinMode(2, INPUT_PULLUP); //saves power on unused pins
  pinMode(4, INPUT_PULLUP); //saves power on unused pins
  pinMode(5, INPUT_PULLUP); //saves power on unused pins
}  // end of setup

void loop () {

  int sensorValue = analogRead(3); // Read the light sensor

  long voltage = readVcc(); //Get the Vcc reading
  double decimalVoltage = doubleMap(double(voltage), 0, 6000, 0, 6); //convert to double digits

  if (decimalVoltage <= 3.00) {
    digitalWrite(ledPin, LOW);
  }

  unsigned long currentMillis = millis();

  switch (state)
  {
    case WAIT_LOW_LIGHT:
      if (sensorValue <= 300)
      {
        digitalWrite(ledPin, HIGH);       // Update the actual LED
        previousMillis  = currentMillis;  // Remember the ON time
        state = LED_ON;
      }
      break;

    case LED_ON:
      if (currentMillis - previousMillis >= OnTime)
      {
        digitalWrite(ledPin, LOW);        // Update the actual LED
        previousMillis  = currentMillis;  // Remember the OFF time
        state = LED_OFF;
      }
      break;

    case LED_OFF:
      if (currentMillis - previousMillis >= OffTime)
      {
        state = WAIT_LOW_LIGHT;
      }
      break;
  }

}  //end of loop

double doubleMap(double x, double in_min, double in_max, double out_min, double out_max) {
  return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}

long readVcc() {

#if defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)   // Read 1.1V reference against AVcc, set the reference to Vcc and the measurement to the internal 1.1V reference
  ADMUX = _BV(MUX3) | _BV(MUX2);
#endif

  delay(2); // Wait for Vref to settle
  ADCSRA |= _BV(ADSC); // Start conversion
  while (bit_is_set(ADCSRA, ADSC)); // measuring

  uint8_t low  = ADCL; // must read ADCL first - it then locks ADCH
  uint8_t high = ADCH; // unlocks both

  long result = (high << 8) | low;

  result = 1125300L / result; // Calculate Vcc (in mV); 1125300 = 1.1*1023*1000
  return result; // Vcc in millivolts
}

Here is my closest code to working, it works up until when the led is ment to turn off, it just stays on.

#include <avr/sleep.h>
#include <avr/power.h>
#include <avr/wdt.h>

enum STATE {
  WAIT_LOW_LIGHT,
  LED_ON,
  LED_OFF,
};

STATE state = WAIT_LOW_LIGHT;

int ledPin =  0;              // the number of the LED pin
unsigned long previousMillis = 0;        // will store last time LED was updated
long OnTime = 5000;           // milliseconds of on-time
long OffTime = 5000;          // milliseconds of off-time
volatile int f_wdt = 1;

//Watchdog Interrupt Service. This is executed when watchdog timed out.
ISR(WDT_vect) {
  if (f_wdt == 0) {
    f_wdt = 1;
  }
  else  {
  }
}

void enterSleep(void)
{
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);   //Millis not working
  sleep_enable();

  sleep_mode();   //Now enter sleep mode

  // The program will continue from here after the WDT timeout
  sleep_disable(); //First thing to do is disable sleep

  power_all_enable();   //Re-enable the peripherals
}

void setup() {
  pinMode(ledPin, OUTPUT);
  pinMode(1, INPUT_PULLUP); //saves power on unused pins
  pinMode(2, INPUT_PULLUP); //saves power on unused pins
  pinMode(4, INPUT_PULLUP); //saves power on unused pins
  pinMode(5, INPUT_PULLUP); //saves power on unused pins

  // Setup the WDT
  MCUSR &= ~(1 << WDRF);  //Clear the reset flag

  WDTCR |= (1 << WDCE) | (1 << WDE);

  WDTCR = 1 << WDP0 | 1 << WDP3; //8.0 seconds  //set new watchdog timeout prescaler value

  WDTCR |= _BV(WDIE);   //Enable the WD interrupt
}

void loop() {


  if (f_wdt == 1) {

    int sensorValue = analogRead(3); //Read the light sensor

    unsigned long currentMillis = millis();

    switch (state) {
      case WAIT_LOW_LIGHT:
        if (sensorValue <= 300) {
          digitalWrite(ledPin, HIGH);       // Update the actual LED
          previousMillis  = currentMillis;  // Remember the ON time
          state = LED_ON;
        }
        break;

      case LED_ON:
        if (currentMillis - previousMillis >= OnTime) {
          digitalWrite(ledPin, LOW);        // Update the actual LED
          previousMillis  = currentMillis;  // Remember the OFF time
          state = LED_OFF;
        }
        break;

      case LED_OFF:
        if (currentMillis - previousMillis >= OffTime) {
          state = WAIT_LOW_LIGHT;
        }
        break;
    }

    f_wdt = 0;     // clear the flag

    enterSleep();  // Re-enter sleep mode
  }
  else  {
    //do nothing
  }
}  //end of loop

I haven't added my low voltage code to this until I get the sleep up and running.

edit: grammer

Light_Switch.png

Hi gogreenpower2,

I´m not sure if this is what you are asking for, but... :slight_smile:

You can calculate the time the CPU is off by incrementing a counter each time it wakes up, say every 8 secs:

LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);

You just wake up briefly, do the Counter++, and if it´s no time to do anything, go back to sleep.

Even in sleep mode, the timer for PowerDown is accurate.

Hope this helps. Regards!

So get rid of the Millie timers and just use the power down cycle count.

Interesting.

So something like, wake up, if dark, led on, if
led on and count is 3 hours worth of 8s, turn led off, sleep, wake, if led off count is 9 hours worth of 8s, wait for dark.

Best way to achieve would be using elseif?

gogreenpower2:
So something like, wake up, if dark, led on, if
led on and count is 3 hours worth of 8s, turn led off, sleep, wake, if led off count is 9 hours worth of 8s, wait for dark.

Exactly! You only have to wake up for a few milliseconds every 8 secs, so low power usage.

You can translate your own pseudocode above to code, and we can check it here if it helps!

Ok, so while trying to get my count and sleep working I came across the library tinysnore.h, and it does all the heavy lifting. Just use

snore(time in milli seconds);

goes to sleep and wakes up at that point in the loop.

My code works and it ended up being much easier than what I was working on.

#include "tinysnore.h"

const byte ledPin =  0;                   //the number of the LED pin
const byte pullupPins[] = { 1, 2, 4, 5 }; //list of unused pins

void setup() {
  pinMode(ledPin, OUTPUT);                //setting the led pin 0 as an output

  for (auto pin : pullupPins) {
    pinMode (pin, INPUT_PULLUP);          //saves power on unused pins
  }
}

void loop() {

  int sensorValue = analogRead(3);        //read the light sensor

  if (sensorValue <= 300) {               //night time. Range 0(black hole dark) - 1023(surface of sun bright)
    digitalWrite(ledPin, HIGH);           //turn led on
    snore(9000000);                       //go to sleep for 2.5 hours(9,000,000ms), then resume from here
    digitalWrite(ledPin, LOW);            //turn the led off
    snore(43200000);                      //go to sleep for 12 hours(43,200,000ms), then resume from here
  }

  else (sensorValue > 300); {             //day time. Range 0(black hole dark) - 1023(surface of sun bright)
    digitalWrite(ledPin, LOW);            //turn the led off
  }
  snore(600000);                          //to save power go to sleep for ten minute, then resume from here
  
  delay(100);                             //let the program settle
}                                         //end of loop

worked out it should run for around 175 days.

Can't add a new post but the batteries lasted way longer than 175. Just pulled them out and measured them, still at 3.5v, I recon it would have lasted the whole year! But I needed to update the LDR, 300 was coming on too early, so now I'll try 200 and a run time of 4 hours. Back next year!

Good!!

Very interesting library. Thanks for sharing.

See you in 174 days with the results :slight_smile: