Atiny84 Help with PCI Wake up From Power Down Sleep mode

Hi
I am trying to have two push buttons generate PCI to wake up ATtiny84 from Power Down sleep mode.

I think I understand the principle, I’ve done it before on other MCU but I can’t see the error in my code below - I stripped out most of the code to focus on my PCI issue.

The ISR routines capture the interrupts generated - without these I get a reset on PCI interrupt.

My mistake must be in how I set up the PCMSK0 PCMSK1 and then the GIMSK to enable the PCI for the pins in question.

I appreciate my buttons, SW1 and SW2 are on PCINT5 and PCINT6 respectively and hence in the MCMSK0.

The buttons may be read for a brief period from start up before the inactivity timeout causes sleep to be invoked.

Periodically the WDT is successfully able to wake the device from POWER DOWN sleep.

I have a natty LED for debug to indicate when the unit is awake.

What I want is to have the buttons generate a PCI and for this to wake up the unit from POWER DOWN sleep.

Can you see what I have done wrong as I can’t?

I’ve tried setting PCMSK0 and GIMSK just before I dive into sleep just in case some Arduino code is changing things but this has no affect.

My code snippet:

// ATtiny84_PCI_Help
// cSpell:includeRegExp CStyleComment
// written for ATtiny84
// Peter Charles-Jones

#ifdef F_CPU
#undef F_CPU
#define F_CPU 8000000UL    	    // Main clock is internal 32kHz oscillator / 8- required for delay function
#endif

#include <Arduino.h>
#include <avr/sleep.h>				  // useful macros for putting the unit into sleep mode
#include <avr/wdt.h>		        // used to wake up MCU periodically from Sleep

#define SW1_bp PA5              // PCINT5
#define SW2_bp PA6              // PCINT6
#define LED1_bp PB1
#define LED2_bp PB0
#define LED3_bp PA7
#define ADC_bp PA0
// PA1 Serial TXD               
// PA2 Serial RXD               // PCINT2
// PA3 Software Serial TXD - Tx 
// PB2 Software Serial RXD - Rx // PCINT10

/*  Pin definitions and use
//                  			ATtiny84
//                              VCC 1| u |14 GND
//            PCINT8/XTAL1/CLKI PB0 2|   |13 PA0 ADC0/AREF/PCINT0
//                 PCINT9/XTAL2 PB1 3|   |12 PA1 ADC1/AIN0/PCINT1
//           PCINT11/~RESET~/dW PB3 4|   |11 PA2 ADC2/AIN1/PCINT2
//      PCINT10/INT0/OC0A/CKOUT PB2 5|   |10 PA3 ADC3/T0/PCINT3
//         PCINT7/ICP/OC0B/ADC7 PA7 6|   |9  PA4 ADC4/USCK/SCL/T1/PCINT4
// PCINT6/OC1A/SDA/MOSI/DI/ADC6 PA6 7|   |8  PA5 ADC5/DO/MISO/OC1B/PCINT5

This core uses a simple scheme for assigning the Arduino pin numbers: 
Pins are numbered starting from the the I/O pin closest to Vcc as pin 0 
and proceeding counterclockwise, skipping the (mostly) non-usable RESET pin. `
See Dr Azzy link <https://europe1.discourse-cdn.com/arduino/original/4X/3/a/5/3a50e2c98f4f87265f182d2e8e1adf5fcaa2376d.jpeg>

//  ARDUINO digital pin numbers

//                     VCC  1| u |14  GND
//             (D  0)  PB0  2|   |13  AREF (D 10)
//             (D  1)  PB1  3|   |12  PA1  (D  9) 
//                     PB3  4|   |11  PA2  (D  8) 
//  PWM  INT0  (D  2)  PB2  5|   |10  PA3  (D  7) 
//  PWM        (D  3)  PA7  6|   |9   PA4  (D  6) 
//  PWM        (D  4)  PA6  7|   |8   PA5  (D  5)        PWM
//
//  PA1 TXD Arduino pin 9
//  PA2 RXD Arduino pin 8

//  ISP header

//            MISO  1 2 VCC
//              SCK 3 4 MOSI
//             ~RST 5 6 GND   
//
*/  

#define buttonDebounce 10     // button debounce time (ms)
#define BAUD 9600           // define the serial baud rate

#define Prtln(x) Serial.println(x)
#define Prt(x) Serial.print(x)

volatile uint8_t INT_FLAG;      // ISR flags
#define WDT_INT_bp 0	

#define WDT_Prescaler (1<<WDP3)|(0<<WDP2)|(0<<WDP1)|(1<<WDP0) // every 8 seconds

// put function declarations here:
void goToSleep();
void enableWDT();
void sysInit();
int pinChange (unsigned long* last_time, int pinID, char tPIN, char hPIN, char* pins);
int pinHIGH (int pinID, char* pins);

void setup() {
  // put your setup code here, to run once:
  sysInit();
  Serial.begin(BAUD);
  Prtln(F("\n\r\n\rStarting serial"));
    
  enableWDT();  
  //goToSleep();
}

void loop() {
  // put your main code here, to run repeatedly:
  
  static char PINS = PINA;                                      // debounced copy of the PIND register
  static unsigned long inactivity_time  = millis();     // inactivity timer
  static unsigned long lastSW1_time = millis();                 // debounce timer
  static unsigned long lastSW2_time = millis();                 // debounce timer
  static char tempPINA;                             // used as snapshot for PIND status if it changes mid process
  static char pinAHistory;                          // the last read PIND status

  // check buttons
  tempPINA = PINA;                                  // get latest PINA setting
  if (pinChange(&lastSW1_time, SW1_bp, tempPINA, pinAHistory, &PINS)){
    inactivity_time = millis();
    if (pinHIGH(SW1_bp,&PINS)){                                              // SW1 is HIGH - OC - button released
    }
    else{                                                                   // SW1 is LOW - CC - button pressed
      Prt (F("\n\rSW1 presed "));
    }
  }

  if (pinChange(&lastSW2_time, SW2_bp, tempPINA, pinAHistory, &PINS)){
    inactivity_time = millis();
    if (pinHIGH(SW2_bp,&PINS)){                                              // SW1 is HIGH - OC - button released
    }
    else{                                                                   // SW1 is LOW - CC - button pressed
      Prt (F("\n\rSW2 presed "));
    }
  }
  pinAHistory = tempPINA;     

  if (INT_FLAG & (1<<WDT_INT_bp)){
    INT_FLAG &= ~(1<<WDT_INT_bp);
    WDTCSR |= (1<<WDIE);			    // required to prevent a reset on next timeout
    Prtln(F("WDT"));
    inactivity_time = millis();   // keeps teh unit awake a bit on each WDT
  }

  if ((millis() -  inactivity_time) > (1000)){ // to to POWER DOWN sleep mode after inactivity
    PORTB &= ~(1<<LED1_bp);
    goToSleep();
  }
  else{
    PORTB |= (1<<LED1_bp);
  }

  //goToSleep();
}

// interrupt service routines


ISR (PCINT0_vect){										     // pin change interrupt - used to wake up unit from PowerDown sleep mode
} // end of pin change interrupt

ISR (PCINT1_vect){										    // pin change interrupt - used to wake up unit from PowerDown sleep mode
} // end of pin change interrupt

ISR (WDT_vect){									          // WDT interrupt
	INT_FLAG |= (1<<WDT_INT_bp);						// set flag so that main knows to execute some code
}

// put function definitions here:
void goToSleep(void){
  //PCMSK0 |= (1<<PCINT5)|(1<<PCINT6)|(1<<PCINT2); // set PCI on SW1, SW2 and Serial RXD
  //GIMSK |= (1<<PCIE1)|(1<<PCIE0);             // enable PCI
	char oldSREG = SREG;												// grab a copy of the current value of the system register
	cli();																			// turn off interrupts
	sleep_enable();
	SREG = oldSREG;															// enable interrupts if they were enabled before
	sleep_cpu();																// this is the point where the MCU shuts down
	// zzZZzz
	sleep_disable();														// code restarts here on wake up
}

void enableWDT(void){							            // turn on WDT 
	wdt_reset();                                // reset watchdog - don't want it timing out mid change
  unsigned char oldSREG = SREG;				        // store SREG state specially for global interrupt enable
	cli();                                      // turn off interrupts for timed sequence
	MCUSR &= ~(1<<WDRF);                        // clear the WDT restarted flag
 	WDTCSR |= (1<<WDCE)|(1<<WDE);               // start timed sequence enabling it to be edited
  WDTCSR = (1<<WDIE)|(1<<WDE)|WDT_Prescaler;	// Interrupts enabled, WDT enabled, prescaler set
	SREG = oldSREG;                             // restore SREG (will also re-enable interrupts if they were enabled before) 
}

int pinChange (unsigned long* last_time, int pinID, char tPIN, char hPIN, char* pins) { // has the pin changed state?
  if ((tPIN & (1<<pinID))!= (hPIN & (1<<pinID) )){          // last pin state different
    *last_time = millis();
  }

  if ((millis() - *last_time > buttonDebounce)){            // debounce time has elapsed
    if ((tPIN & (1<<pinID))!=(*pins & (1<<pinID))){          // is the current pin state different from the current one
      *pins &= ~(1<<pinID);                                 // mask out the bit position
      *pins |= (tPIN & (1<<pinID));                         // get the bit value at bit location and OR into PINS
      return 1;                                             // pin has changed
    }
  }
  return 0;                                                 // pin has not changed state
}

int pinHIGH (int pinID, char* pins){
  if (*pins & (1<<pinID)){                                  // pin value is 1.. switch / button is Open Circuit
    return 1;                                               // HIGH
  }
  else{                                                     // pin value is 0 .. switch / button is Closed to ground
    return 0;                                               //LOW
  }
}

// initialization functions here:

void sysInit(){
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);

 	//PCMSK0 |= (1<<SW1_bp)|(1<<SW2_bp)|(1<<PA2); // set PCI on SW1, SW2 and Serial RXD
  PCMSK0 |= (1<<PCINT5)|(1<<PCINT6)|(1<<PCINT2); // set PCI on SW1, SW2 and Serial RXD

  //PCMSK1 |= (1<<PB2);                       // set PCI for Software Serial RXD
  PCMSK1 |= (1<<PCINT10);                     // set PCI for Software Serial RXD
	
  GIMSK |= (1<<PCIE1)|(1<<PCIE0);             // enable PCI
  
	DDRB |= (1<<LED1_bp)|(1<<LED2_bp);          // define output ports
	PORTB &= ~((1<<LED1_bp)|(1<<LED2_bp));      // set outputs LOW
	
  DDRA |= (1<<LED3_bp);                       // define output ports
  DDRA &= ~((1<<SW1_bp)|(1<<SW2_bp));         // make sure thse are inputs
  PORTA &= ~((1<<LED3_bp));                   // set outputs LOW
	PORTA |= (1<<SW1_bp)|(1<<SW2_bp);		        // define internal pull up on digital inputs 

	sei ();	
}

Here’s a further stripped down piece of code and this works as expected. I get PCI interrupts waking up from POWER DOWN for both the buttons SW1 and SW2 but also for the data lines I will be using for Serial and SoftwareSerial (a pass through piece of code) but I can’t see what in my original code tripped me up. I’ll keep looking, but if you can spot the problem, please put me out of my misery.

// cSpell:includeRegExp CStyleComment
#include <Arduino.h>
#include <avr/sleep.h>
#include <avr/wdt.h>		        // used to wake up MCU periodically from Sleep

#define SW1_bp PA5              // PCINT5
#define SW2_bp PA6              // PCINT6
#define LED1_bp PB1
#define LED2_bp PB0
#define LED3_bp PA7
#define ADC_bp PA0
// PA1 Serial TXD               
// PA2 Serial RXD               // PCINT2
// PA3 Software Serial TXD - Tx 
// PB2 Software Serial RXD - Rx // PCINT10    
/*  Pin definitions and use
//                  			ATtiny84
//                              VCC 1| u |14 GND
//            PCINT8/XTAL1/CLKI PB0 2|   |13 PA0 ADC0/AREF/PCINT0
//                 PCINT9/XTAL2 PB1 3|   |12 PA1 ADC1/AIN0/PCINT1
//           PCINT11/~RESET~/dW PB3 4|   |11 PA2 ADC2/AIN1/PCINT2
//      PCINT10/INT0/OC0A/CKOUT PB2 5|   |10 PA3 ADC3/T0/PCINT3
//         PCINT7/ICP/OC0B/ADC7 PA7 6|   |9  PA4 ADC4/USCK/SCL/T1/PCINT4
// PCINT6/OC1A/SDA/MOSI/DI/ADC6 PA6 7|   |8  PA5 ADC5/DO/MISO/OC1B/PCINT5

This core uses a simple scheme for assigning the Arduino pin numbers: 
Pins are numbered starting from the the I/O pin closest to Vcc as pin 0 
and proceeding counterclockwise, skipping the (mostly) non-usable RESET pin. `
See Dr Azzy link <https://europe1.discourse-cdn.com/arduino/original/4X/3/a/5/3a50e2c98f4f87265f182d2e8e1adf5fcaa2376d.jpeg>

//  ARDUINO digital pin numbers

//                     VCC  1| u |14  GND
//             (D  0)  PB0  2|   |13  AREF (D 10)
//             (D  1)  PB1  3|   |12  PA1  (D  9) 
//                     PB3  4|   |11  PA2  (D  8) 
//  PWM  INT0  (D  2)  PB2  5|   |10  PA3  (D  7) 
//  PWM        (D  3)  PA7  6|   |9   PA4  (D  6) 
//  PWM        (D  4)  PA6  7|   |8   PA5  (D  5)        PWM
//
//  PA1 TXD Arduino pin 9
//  PA2 RXD Arduino pin 8

//  ISP header
cccc
//            MISO  1 2 VCC
//              SCK 3 4 MOSI
//             ~RST 5 6 GND   
//
*/  


// put function declarations here:
void sysInit();
void goToSleep();

void setup() {
  // put your setup code here, to run once:
  sysInit();
  goToSleep();

}
void loop() {
  // put your main code here, to run repeatedly:

  PORTB ^= (1<<LED1_bp);
  delay(100);
  PORTB ^= (1<<LED1_bp);
  delay(100);
  PORTB ^= (1<<LED1_bp);
  delay(100);
  PORTB ^= (1<<LED1_bp);
  
  goToSleep();
}

// put function definitions here:

// interrupt service routines
ISR(PCINT0_vect){										     // pin change interrupt - used to wake up unit from PowerDown sleep mode
  //PORTB ^= (1<<LED1_bp);
} // end of pin change interrupt

ISR(PCINT1_vect){										    // pin change interrupt - used to wake up unit from PowerDown sleep mode
  PORTB ^= (1<<LED1_bp);
} // end of pin change interrupt

void goToSleep(){
  char oldSREG = SREG;												// grab a copy of the current value of the system register
	cli();																			// turn off interrupts
	sleep_enable();
	SREG = oldSREG;															// enable interrupts if they were enabled before
	sleep_cpu();																// this is the point where the MCU shuts down
	// zzZZzz
	sleep_disable();														// code restarts here on wake up
}

void sysInit(){
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);

 	PCMSK0 |= (1<<PCINT5)|(1<<PCINT6)|(1<<PCINT2); // set PCI on SW1, SW2 and Serial RXD

  PCMSK1 |= (1<<PCINT10);                     // set PCI for Software Serial RXD

  GIMSK |= (1<<PCIE1)|(1<<PCIE0);             // enable PCI
  
	DDRB |= (1<<LED1_bp);          // define output ports
	DDRB |= (1<<LED1_bp)|(1<<LED2_bp);          // define output ports
  
  PORTB &= ~((1<<LED1_bp));      // set outputs LOW
	PORTB &= ~((1<<LED1_bp)|(1<<LED2_bp));      // set outputs LOW
	
  DDRA |= (1<<LED3_bp);                       // define output ports

  DDRA &= ~((1<<SW1_bp)|(1<<SW2_bp));         // make sure thse are inputs
  PORTA |= (1<<SW1_bp)|(1<<SW2_bp);		        // define internal pull up on digital inputs 
	sei ();	

}

Solved -
But I’m still picking over the pieces. I think it has to do with an interrupt on the button up event and somehow putting my device back to sleep before it had done its task.
I sometimes think writing issues to a forum helps even if no one comments.
Happy hacking.

1 Like