Help Needed - Wake up Arduino from sleep using capacitive touch sensors

I am building a project that requires sleep modes and is controlled by capacitive touch sensing pins. My goal is to have no moving parts (ie. pushbuttons, slide switches, etc). I have used the Capacitive Sensor library but I moved to using ADCTouch since it requires no hardware. I can initialize sleep using the sensor pin attached to A0 but the chip immediately tries to turn back on and seems to stall out at this point.

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

int LEDPin = 4;  
int pwrPin = 2;
int sensorPin0 = A0; //Enter, or sleep
int sensorPin1 = A1; //Down
int sensorPin2 = A2; //Up

int maxModes = 3;
int mode = 3;
int count = 0;
int ref0, ref1, ref2;       //reference values to remove offset

void setup()                    
{
   Serial.begin(9600);
   Serial.println("Turning on.");
   ref0 = ADCTouch.read(sensorPin0, 500);    //create reference values to .. 
   ref1 = ADCTouch.read(sensorPin1, 500);  
   ref2 = ADCTouch.read(sensorPin2, 500);     
   pinMode(pwrPin,INPUT);
   digitalWrite(pwrPin,HIGH);   
   pinMode(LEDPin,OUTPUT);
   digitalWrite(LEDPin,HIGH);
    // pin change interrupt (example for A0)
   PCMSK1 |= bit (PCINT8);  // want pin A0
   PCIFR  |= bit (PCIF1);   // clear any outstanding interrupts
   PCICR  |= bit (PCIE1); 
}

 ISR (PCINT1_vect)
 {
  wakeUpNow();
 }

void sleepNow()
{ 
  sleep_enable();
  set_sleep_mode(SLEEP_MODE_IDLE);
  Serial.println("Going to sleep");
  digitalWrite(LEDPin,LOW);
  delay(1000);
  attachInterrupt(0, wakeUpNow, LOW); //for debugging, there is a pushbutton that will also wake the chip.
  sleep_cpu(); // Go to sleep                              
  sleep_disable(); // Return from sleep
}

void wakeUpNow(){
  detachInterrupt(0);
  cli;
  wakeUpMsg();
}

void wakeUpMsg(){
    delay (00);
    Serial.println("Waking up.");
    Serial.print("Now on mode ");
    Serial.println(mode);
    sei;
}

void loop(){ 
    int value0 = ADCTouch.read(sensorPin0);   //no second parameter
    int value1 = ADCTouch.read(sensorPin1);
    int value2 = ADCTouch.read(sensorPin2);
    value0 -= ref0;     //remove offset
    value1 -= ref1;
    value2 -= ref2;    
    
    if (value0 > 60){
      digitalWrite(LEDPin,LOW);//Flash to indicate button recognition
      delay(100);
      digitalWrite(LEDPin,HIGH);
      delay(100);
      count ++;
    }
    
    if ((value1 > 40) && (mode == 0)) {   //mode down
      mode = maxModes;
      Serial.println("Mode Down");
      Serial.print("Now on mode ");
      Serial.println(mode);
      delay (200);
    }
        else if ((value1 > 40) && (mode > 0)) { 
      mode --;
      Serial.println("Mode Down");
      Serial.print("Now on mode ");
      Serial.println(mode);
      delay (200);
    }
    
    if ((value2 > 40) && (mode == maxModes)) {   //mode down
      mode = 0;
      Serial.println("Mode Up");
      Serial.print("Now on mode ");
      Serial.println(mode);
      delay (200);
    }
        else if ((value2 > 40) && (mode < maxModes)) { 
      mode ++;
      Serial.println("Mode Up");
      Serial.print("Now on mode ");
      Serial.println(mode);
      delay (200);
    }
    
    if (count == 10){
      Serial.println("Trying to sleep.");
      sleepNow();
      count = 0;
    }
    
    if ((value0 < 60) && (count < 10)){
      count = 0;
    }
    
    switch (mode) {
      case 0:
          digitalWrite(LEDPin,LOW);
          delay (1000);
          digitalWrite(LEDPin,HIGH);
          delay (1000);
          break;
      case 1:
          digitalWrite(LEDPin,LOW);
          delay (500);
          digitalWrite(LEDPin,HIGH);
          delay (500);
          break;      
      case 2:
          digitalWrite(LEDPin,LOW);
          delay (200);
          digitalWrite(LEDPin,HIGH);
          delay (200);
          break;
       case 3:
          digitalWrite(LEDPin,LOW);
          delay (50);
          digitalWrite(LEDPin,HIGH);
          delay (50);
          break;
     }
    
    digitalWrite(LEDPin,HIGH);
}

The loop in this code handles several LED flash modes controlled by A1 and A2. I have pin 2 connected to a pushbutton right now for debugging purposes. void setup is where I try to initialize pin A0 as an interrupt, and right below that I have the ISR call on the wake command. I followed Nick Gammon's instructions the best I could but I seem to be either messing up somewhere, or it isn't possible to wake up using a capsense pin (the former I believe).

I am aware of the Atmel touch sensor chip, and I am prepared to resort to that, but if this can be done without using extra harware, I would be a happy camper. Any help would be greatly appreciated.

Thank you,

Atrius129

and seems to stall out at this point.

Don't do serial prints from an ISR. Or a function called from an ISR.

    delay (00);

?

Don't call cli() or sei() from inside an ISR. Or delay().

You read my page: Gammon Forum : Electronics : Microprocessors : Interrupts

Then you broke all the rules in it.

Thank you for the quick reply!

I have the ISR calling for wakeUpNow which doesn't have any serial. I have wakeUpNow() then call wakeUpMsg() which then does the serial. I did this specifically because I saw you say to keep the ISR code simple and without things like delays. Is that what you mean when you say not to have a function inside the ISR?

The (00) is the result of accidentaly hitting ctrl+z while copying the code. It should be 100, sorry.

Let me give you an analogy. Say that I tell you not to open the airplane door while it is flying. So you get your son to do it (function call). The door opens, and everyone dies. You can't say "but I didn't open the door".

When I said not do to a serial print in an ISR, you can't take the curse off it by writing a function, calling that, and having that function do the serial print.

The (00) is the result of accidentaly hitting ctrl+z while copying the code. It should be 100, sorry.

Don't do delays in an ISR.

You don't have to take my word for it. From the official documentation: http://arduino.cc/en/Reference/AttachInterrupt

Inside the attached function, delay() won't work ...

Thank you, I will remember that (sorry, I will definitely take your word, I am very new to Arduino programming so it's a lot to take in). I took the functions out, as per your instructions, which does seem to have fixed the stalling issue. Now it triggers sleep, and immediately pops back into awake mode. Do you have any suggestions for ensuring that it stays asleep until the pin is touched again?

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

int LEDPin = 4;  
int pwrPin = 2;
int sensorPin0 = A0; //Enter, or sleep
int sensorPin1 = A1; //Down
int sensorPin2 = A2; //Up

int maxModes = 3;
int mode = 3;
int count = 0;
int ref0, ref1, ref2;       //reference values to remove offset

void setup()                    
{
   Serial.begin(9600);
   Serial.println("Turning on.");
   ref0 = ADCTouch.read(sensorPin0, 500);    //create reference values to .. 
   ref1 = ADCTouch.read(sensorPin1, 500);  
   ref2 = ADCTouch.read(sensorPin2, 500);     
   pinMode(pwrPin,INPUT);
   digitalWrite(pwrPin,HIGH);   
   pinMode(LEDPin,OUTPUT);
   digitalWrite(LEDPin,HIGH);
    // pin change interrupt (example for A0)
   PCMSK1 |= bit (PCINT8);  // want pin A0
   PCIFR  |= bit (PCIF1);   // clear any outstanding interrupts
   PCICR  |= bit (PCIE1); 
}

 ISR (PCINT1_vect)
 {
 }

void sleepNow()
{ 
  sleep_enable();
  set_sleep_mode(SLEEP_MODE_IDLE);
  Serial.println("Going to sleep");
  digitalWrite(LEDPin,LOW);
  delay(1000);
  attachInterrupt(0, wakeUpNow, LOW); //for debugging, there is a pushbutton that will also wake the chip.
  sleep_cpu(); // Go to sleep                              
  sleep_disable(); // Return from sleep
}

void wakeUpNow(){
  detachInterrupt(0);
}


void loop(){ 
    int value0 = ADCTouch.read(sensorPin0);   //no second parameter
    int value1 = ADCTouch.read(sensorPin1);
    int value2 = ADCTouch.read(sensorPin2);
    value0 -= ref0;     //remove offset
    value1 -= ref1;
    value2 -= ref2;    
    
    if (value0 > 60){
      digitalWrite(LEDPin,LOW);//Flash to indicate button recognition
      delay(100);
      digitalWrite(LEDPin,HIGH);
      delay(100);
      count ++;
    }
    
    if ((value1 > 40) && (mode == 0)) {   //mode down
      mode = maxModes;
      Serial.println("Mode Down");
      Serial.print("Now on mode ");
      Serial.println(mode);
      delay (200);
    }
        else if ((value1 > 40) && (mode > 0)) { 
      mode --;
      Serial.println("Mode Down");
      Serial.print("Now on mode ");
      Serial.println(mode);
      delay (200);
    }
    
    if ((value2 > 40) && (mode == maxModes)) {   //mode down
      mode = 0;
      Serial.println("Mode Up");
      Serial.print("Now on mode ");
      Serial.println(mode);
      delay (200);
    }
        else if ((value2 > 40) && (mode < maxModes)) { 
      mode ++;
      Serial.println("Mode Up");
      Serial.print("Now on mode ");
      Serial.println(mode);
      delay (200);
    }
    
    if (count == 10){
      Serial.println("Trying to sleep.");
      sleepNow();
      count = 0;
    }
    
    if ((value0 < 60) && (count < 10)){
      count = 0;
    }
    
    switch (mode) {
      case 0:
          digitalWrite(LEDPin,LOW);
          delay (1000);
          digitalWrite(LEDPin,HIGH);
          delay (1000);
          break;
      case 1:
          digitalWrite(LEDPin,LOW);
          delay (500);
          digitalWrite(LEDPin,HIGH);
          delay (500);
          break;      
      case 2:
          digitalWrite(LEDPin,LOW);
          delay (200);
          digitalWrite(LEDPin,HIGH);
          delay (200);
          break;
       case 3:
          digitalWrite(LEDPin,LOW);
          delay (50);
          digitalWrite(LEDPin,HIGH);
          delay (50);
          break;
     }
    
    digitalWrite(LEDPin,HIGH);
}

There is a flag which can be cleared - I don't recall its exact name this moment. Without clearing it the pin change interrupt might be caused by some event prior to you enabling it.

...

Actually you seem to clear it in setup:

   PCIFR  |= bit (PCIF1);   // clear any outstanding interrupts

Try doing that before you sleep.

The correct code is (no bitwise-or)...

PCIFR = bit (PCIF1);   // clear any outstanding interrupts

Yep, I wondered about that. I agree with Coding Badly.

That last bit helped a little bit. I tried what both of you said. Mr. Gammon, I took the code from your interrupt tutorial, changed a couple things around and added ADCTouch. It has no trouble sleeping, but it still comes back on automatically. I can sometimes trigger it to come back on via sensor but with no input, within about 10 seconds it automatically wakes.

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

int sensorPin0 = A0; //Enter, or sleep
int ref0;   //calibration variable

const byte LEDWAKE = 4;

ISR (PCINT1_vect)
 {
 // handle pin change interrupt for A0 to A5 here
 }  // end of PCINT1_vect

void setup () 
  {
  pinMode (LEDWAKE, OUTPUT);
  ref0 = ADCTouch.read(sensorPin0, 500); //calibrate the sensor
  // pin change interrupt
  PCMSK1 |= bit (PCINT8);  // want pin A0
  PCIFR  |= bit (PCIF1);   // clear any outstanding interrupts
  PCICR  |= bit (PCIE1);   // enable pin change interrupts for A0 to A5
  
  }  // end of setup

void loop () 
{
  int value0 = ADCTouch.read(sensorPin0);   //read capsense pin.
  value0 -= ref0;     //remove offset

  if (value0 > 60){        //capacitiance needs to be above 60 to trigger
  digitalWrite(LEDWAKE,LOW);
  delay (1000);
  set_sleep_mode (SLEEP_MODE_PWR_DOWN);  
  PCIFR = bit (PCIF1);   // clear any outstanding interrupts, I tried both ways you guys said
  sleep_mode ();
  }  

  // flash to indicate we got out of sleep
  else{
  digitalWrite (LEDWAKE, HIGH);
  delay (100);
  digitalWrite (LEDWAKE, LOW);
  delay (100);
  }
  } // end of loop

I think that ADCTouch isn't being properly handled once the chip goes into sleep. It seems that you loose the ability to set any kind of sensitivity for the sensor once this happens. (value0 > 60) ensures the sensor is not triggered unless capacitance measured on the pin is over 60 units, so going into sleep mode is fine. Without that, I think that the pin just reacts to random noise, so it has a hard time waking up. Any thoughts, or see any mistakes I made?

I'm a little doubtful about the touch sensors working when it is asleep. As I recall, it is looking for changes to analog inputs, however the pin change interrupts are either off or on.

Without that, I think that the pin just reacts to random noise, so it has a hard time waking up

Quite possibly.

Hi,

I have the same question. I think that we can use the sleep_mode_ADC. Arduino will waking from an ADC interrupt, however I am doubtful for the program. When I enter in a sleep mode, how can I wake up via an ADC interrupt? like external pin, we use attachInterrupt, maybe LOW, maybe HIGH. How can I do it in the ADC interrupt, it should be make a comparation of the threshold for the analogical value.

Thank you

See my page: ADC conversion on the Arduino - in particular the section starting: "Read with an interrupt when done".

Also my page on power saving - there is code there (Low-power temperature monitor) that sleeps while taking an ADC reading.

Thanks for your reply.

I did a very simple test, it works. However, there is another probleme. When it's in the sleep mode, that is to say the analogical value is lower than the reference value, the LED blinks, with a very low current. I have no idea for this probleme.

ADC-INTERRPUT.ino (1.38 KB)

You don't need to attach tiny sketches like that. Yours was:

#include <ADCTouch.h>
#include <avr/sleep.h>
#include <avr/power.h>
int ref0;       //reference values to remove offset



const int LED = 12;
const int adcPin = 5;

// when ADC completed, take an interrupt
EMPTY_INTERRUPT (ADC_vect);

// Take an ADC reading in sleep mode (ADC)
void getReading (const byte port)
{
  ADCSRA = bit (ADEN) | bit (ADIF);  // enable ADC, turn off any pending interrupt
  ADCSRA |= bit (ADPS0) | bit (ADPS1) | bit (ADPS2);   // prescaler of 128
  ADMUX = bit (REFS0) | (port & 0x07);  // AVcc

  noInterrupts ();
  set_sleep_mode (SLEEP_MODE_ADC);

  // sleep during sample
  sleep_enable();


  ref0 = ADCTouch.read(A5, 500);    //create reference values to
  //account for the capacitance of the pad


  if (ref0 > 950)
  {
    interrupts ();
    sleep_cpu ();
    sleep_disable ();



  }
  while (bit_is_set (ADCSRA, ADSC))
  { }


  // awake again, reading should be done, but better make sure
  // maybe the timer interrupt fired

}  // end of getReading

void setup ()
{

  pinMode(LED, OUTPUT);
  digitalWrite(LED, LOW);
  ref0 = ADCTouch.read(A5, 500);    //create reference values to
  //account for the capacitance of the pad

}  // end of setup

void loop () {

  digitalWrite(12, HIGH);
  delay(2000);

  digitalWrite(12, LOW);
  delay(1000);
  getReading(adcPin);

}

What is your schematic? I don't see how that would affect the brightness of the LED.

Thank you for your reply.

In fact, the objective of my project is waking the AirBoard from a capacitive touch sensor. In order to add the sleep mode, I have chosen the library of ADCTouch , which is the only way with just one AD input for the capacitive touch sensor. Considering the detection of the ADC convertor in performance, I have been using the sleep_mode_ADC, and ADC interrupt for the wake up. I did a very simple test, just control a LED, when it’s in the sleep mode, it will do nothing, then it will waking from the touch, and it lights. But problem appears, when I am in the sleep mode, LED blinks with weak current. I don’t know why it appeared this current or maybe it even doesn’t entre in the sleep mode.

Yiling

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

// when ADC completed, take an interrupt
EMPTY_INTERRUPT (ADC_vect);

// Take an ADC reading in sleep mode (ADC)
void sleepNow ()
{
  ADCSRA = bit (ADEN) | bit (ADIF);  // enable ADC, turn off any pending interrupt
  ADCSRA |= bit (ADPS0) | bit (ADPS1) | bit (ADPS2);   // prescaler of 128
  ADMUX = bit (REFS0) | (A5 & 0x07);  // AVcc

  noInterrupts ();
  set_sleep_mode (SLEEP_MODE_ADC);

 
  sleep_enable();


  int ref0 = ADCTouch.read(A5, 500);   

  if (ref0 > 950)
  {
    interrupts ();
    sleep_cpu ();
    sleep_disable ();
  }
  
  while (bit_is_set (ADCSRA, ADSC))
  { }


}  
void setup ()
{
}  // end of setup

void loop () {
  sleepNow();
}

Why is there a 10k resistor between 3.3V and Gnd?