1
\$\begingroup\$

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!

\$\endgroup\$
  • \$\begingroup\$ are you applying pulse signal for pin change interrupt, circuit important here since you using adc and interrupt signal in same pin \$\endgroup\$ – masternone Dec 11 '15 at 10:34
  • \$\begingroup\$ Hi Vicky, actually it's basically the output of a reel switch to ground which is in parallel with a 1M ohm resistor. So, when the reel switch is closed, I get 0V at ADC and when it's open I get 3.3V. If it helps, I'll edit the post to add the circuit as soon as I get home. But basically it's what I've described. \$\endgroup\$ – cvicente Dec 11 '15 at 10:57
  • \$\begingroup\$ did you check with the multimeter when adc value was high \$\endgroup\$ – masternone Dec 11 '15 at 11:43
  • \$\begingroup\$ Yes I did. I can see the voltage is actually changing from 0-3.3V as I open and close the switch, but the ADC value never drops. I've already tried the same configuration without putting the MC to sleep and it works fine. So it's probably something I'm missing while activating the ADC again after waking. \$\endgroup\$ – cvicente Dec 11 '15 at 12:14
1
\$\begingroup\$

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);
  }
}
\$\endgroup\$
  • \$\begingroup\$ Yes, I think this is an annoyingly poor API design, just using number which is easy to use for people with little or no programming experience as opposed to require a specific enum value which could eliminate such problems at compile time... You might as well start a habit of always using symolic names for digital pins as well, e.g. door = PB3. \$\endgroup\$ – hlovdal Apr 5 '16 at 20:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.