Getting ADC readings after waking a ATTiny85

arduinoattinysleep

I have this little project of mine that involves putting a ATTiny85 to sleep to save power. The main idea is to:

1- Putting ATTiny85 to sleep, including deactivation of the ADC
2- Wake-up MC on pin change
3- Activating ADC
4- Reading the voltage from the same pin that detected the change

The problem is that I'm unable to get a decent reading on the ADC after waking up the MC. It seems be to HIGH all the time.

Here is the code:

#include <avr/sleep.h>
#include <avr/interrupt.h>

// These constants won't change.  They're used to give names
// to the pins used:
const int analogInPin = 2;
const int LED = 3;

volatile int outputValue = 0;

void setup() {

  pinMode(LED, OUTPUT);
  pinMode(analogInPin, INPUT);

  digitalWrite(LED, LOW);
  digitalWrite(analogInPin, HIGH);

}

void MCSleep() {

  GIMSK |= _BV(PCIE);                     // Enable Pin Change Interrupts
  PCMSK |= _BV(PCINT2);                   // Use PB2 as interrupt pin
  ADCSRA &= ~_BV(ADEN);                   // ADC off
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);    // replaces above statement

  sleep_enable();                         // Sets the Sleep Enable bit in the MCUCR Register (SE BIT)
  sei();                                  // Enable interrupts
  sleep_cpu();                            // sleep

  cli();                                  // Disable interrupts
  PCMSK &= ~_BV(PCINT2);                  // Turn off PB2 as interrupt pin
  sleep_disable();                        // Clear SE bit
  ADCSRA |= _BV(ADEN);                    // ADC on

  sei();                                  // Enable interrupts
} // sleep

// Whatever the pin this IRS should always point to the PCINT0_vect, because is the only capable of
// providing the interrupt. The ISR must be provided otherwise, the MC just resets instead of resuming.
ISR(PCINT0_vect) {

  // This is called when the interrupt occurs, but I don't need to do anything in it
  delay(100);

}

void loop() {

  MCSleep();

  delay(100);

  outputValue = analogRead(analogInPin);

  if (outputValue > 100){
    digitalWrite(LED, HIGH); // Door is open.
  }
  else{
    digitalWrite(LED, LOW); // Door is closed.
  }

  delay(200);
}

From the ATTiny85 specs it seems that after reactivating the ADC there is some extended conversion, so I tried to add a delay after waking up (which didn't work) and later on
I've tried to implement some other ideas like saving the ADCSRA value before sleeping and recover it after waking up, but unfortunately, that hasn't work either.

#include <avr/sleep.h>
#include <avr/interrupt.h>

// These constants won't change.  They're used to give names
// to the pins used:
const int analogInPin = 2;
const int LED = 3;
byte keep_ADCSRA;

volatile int outputValue = 0;

void setup() {

  pinMode(LED, OUTPUT);
  pinMode(analogInPin, INPUT);

  digitalWrite(LED, LOW);
  digitalWrite(analogInPin, HIGH);

}

void MCSleep() {

  GIMSK |= _BV(PCIE);                     // Enable Pin Change Interrupts
  PCMSK |= _BV(PCINT2);                   // Use PB2 as interrupt pin
  keep_ADCSRA = ADCSRA;
  ADCSRA = 0;
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);    // replaces above statement

  sleep_enable();                         // Sets the Sleep Enable bit in the MCUCR Register (SE BIT)
  sei();                                  // Enable interrupts
  sleep_cpu();                            // sleep

  cli();                                  // Disable interrupts
  PCMSK &= ~_BV(PCINT2);                  // Turn off PB2 as interrupt pin
  sleep_disable();                        // Clear SE bit
  ADCSRA = keep_ADCSRA;

  sei();                                  // Enable interrupts
} // sleep

// Whatever the pin this IRS should always point to the PCINT0_vect, because is the only capable of
// providing the interrupt. The ISR must be provided otherwise, the MC just resets instead of resuming.
ISR(PCINT0_vect) {

  // This is called when the interrupt occurs, but I don't need to do anything in it
  delay(100);

}

void loop() {

  MCSleep();

  delay(100);

  outputValue = analogRead(analogInPin);

  if (outputValue > 100){
    digitalWrite(LED, HIGH); // Door is open.
  }
  else{
    digitalWrite(LED, LOW); // Door is closed.
  }

  delay(200);
}

Any ideas?

Thanks in advance.


So, apparently, if I use pin 3 (PB3) instead of 2 (PB2) as the interrupt pin, I get decent readings after reactivating ADC. Still have to find out why!

Best Answer

So I ended up by finding myself the answer on the ATTiny85 specs.

As I was trying out the code I found out that each time I used pin 3 (PCINT3) as the interrupt pin and analog reading, the code actually worked. So I started digging the a little bit more the details of each pin and I found out the following:

Port B, Bit 3 – XTAL1/CLKI/ADC3/OC1B/PCINT3 • XTAL1: Chip Clock Oscillator pin 1. Used for all chip clock sources except internal calibrateble RC oscillator. When used as a clock pin, the pin can not be used as an I/O pin. • CLKI: Clock Input from an external clock source, see “External Clock” on page 26. • ADC3: Analog to Digital Converter, Channel 3 . • OC1B: Inverted Output Compare Match output: The PB3 pin can serve as an external output for the Timer/Counter1 Compare Match B when configured as an output (DDB3 set). The OC1B pin is also the inverted output pin for the PWM mode timer function. • PCINT3: Pin Change Interrupt source 3.

Port B, Bit 2 – SCK/ADC1/T0/USCK/SCL/INT0/PCINT2 • SCK: Master Clock output, Slave Clock input pin for SPI channel. When the SPI is enabled as a Slave, this pin is configured as an input regardless of the setting of DDB2. When the SPI is enabled as a Master, the data direction of this pin is controlled by DDPB2. When the pin is forced by the SPI to be an input, the pull-up can still be controlled by the PORTB2 bit. • ADC1: Analog to Digital Converter, Channel 1 . • T0: Timer/Counter0 counter source. • USCK: Three-wire mode Universal Serial Interface Clock. • SCL: Two-wire mode Serial Clock for USI Two-wire mode. • INT0: External Interrupt source 0. • PCINT2: Pin Change Interrupt source 2.

The problem was that I assumed that the pin number was the same as the analogue channel, so I ended up by always reading something else.

Here's the working code (in the meanwhile I recoded it)

#include <avr/sleep.h>
#include <avr/interrupt.h>

const int switchPin = A1;
const int door = 3;
const int sleep = 4;

int outputValue = 0;

void setup() {

    //pinMode(switchPin, INPUT);
    //digitalWrite(switchPin, HIGH);

    pinMode(door, OUTPUT);
    digitalWrite(door, LOW);

    pinMode(sleep, OUTPUT);
    digitalWrite(sleep, HIGH);

    // Flash quick sequence so we know setup has started
    for (int k = 0; k < 10; k = k + 1) {
        if (k % 2 == 0) {
            digitalWrite(door, HIGH);
            }
        else {
            digitalWrite(door, LOW);
            }
        delay(250);
        } // for
    } // setup

void MCsleep() {

    GIMSK |= _BV(PCIE);                     // Enable Pin Change Interrupts
    PCMSK |= _BV(PCINT2);                   // Use PB2 as interrupt pin
    ADCSRA &= ~_BV(ADEN);                   // ADC off
    //digitalWrite(switchPin, LOW);
    set_sleep_mode(SLEEP_MODE_PWR_DOWN);    // replaces above statement

    sleep_enable();                         // Sets the Sleep Enable bit in the MCUCR Register (SE BIT)
    sei();                                  // Enable interrupts
    sleep_cpu();                            // sleep

    cli();                                  // Disable interrupts
    PCMSK &= ~_BV(PCINT2);                  // Turn off PB2 as interrupt pin
    sleep_disable();                        // Clear SE bit
    //digitalWrite(switchPin, HIGH);
    ADCSRA |= _BV(ADEN);                    // ADC on

    sei();                                  // Enable interrupts
    } // sleep

// Whatever the pin this IRS should always point to the PCINT0_vect, because is the only capable of
// providing the interrupt. The ISR must be provided otherwise, the MC just resets instead of resuming.
ISR(PCINT0_vect) {
    // This is called when the interrupt occurs, but I don't need to do anything in it
    }

void loop() {

  MCsleep();
  delay(20);

  outputValue = analogRead(switchPin);

  if(outputValue > 200){
    digitalWrite(sleep, LOW);
    delay(1000);
    digitalWrite(door, HIGH);
    delay(2000);
    digitalWrite(sleep, HIGH);
  }
  else{
    digitalWrite(sleep, LOW);
    delay(1000);
    digitalWrite(door, LOW);
    delay(2000);
    digitalWrite(sleep, HIGH);
  }
}