ATtiny84 wake on external interrupt

Firstly I am very new to this whole subject, just three days in.

The project I am working on is a diving torch switch. Due to the waterproof requirement it is switched using a Hall sensor. I have build a test board using a Hall sensor and inverter, with a Schmitt trigger input. The normal state is LOW rising to HIGH when a magnet is in proximity. The board works well.

There is very little real estate on the PCB so I am planning on using an ATtiny85. Currently I only have an ATtiny84 so am testing using that.

Since the torch spends most of its life in the off state and it is battery powered it is important that the MCU is put into sleep mode when the torch is off.

The code treats the rising edge from the hall sensor as an external trigger. Please see the code.

/* 
Code for the ATtiny84 to switch an LED on and off using a hall snesor.
The Hall switch used has an inverter with a Schmitt trigger input and produces a rising edge
when a magnet is in proximity.
Detect the rising edge from a Hall sensor and toggle the led on or off using a preset duty cycle.
After setting the pin out to 0 the ATtiny is put into sleep mode. It wakes on an external
interrupt and turns on the LED to a preset duty cycle.
*/

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

volatile boolean on_off = false;   // variable that defines LED state
const byte switchPin = 6;         // pin that drives the LED, physical pin 7
const byte intPin = 0;            // physical pin 5
int level = 500;                  // set the duty cycle 0 to 1000 where 1000 = 100%

int tOn = level;         // set the time on and time off such that f=1kHz
int tOff = 1000 -level;  // the values represent microseconds

void setup() 
{
//  setup the function for creating external interrupts at physical pin 5 on Rising (LOW to HIGH)
  attachInterrupt(intPin, hallInt, RISING);
  
  pinMode(switchPin, OUTPUT); //define the pin that drives the LED as an output pin
}

void loop() 
{
 // If the LED state is false set pin low
 
  if (on_off == false) 
  {
    digitalWrite(switchPin, LOW);
    enterSleep();

  }

  // If the LED state is true toggle pin at desired duty cycle.
  // The ATtiny is woken from the sleep state by the interrupt and the LED state is changed.
  
  while (on_off == true) 
  {
    digitalWrite(switchPin, HIGH);
    delayMicroseconds(tOn);
    digitalWrite(switchPin, LOW);
    delayMicroseconds(tOff);
  }
}

void enterSleep(void) 
{
// Function to put the ATtiny to sleep.
  sei();                                 //set Global Interrupt Enable
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);  
  sleep_enable();
  sleep_mode();                           // enter sleep mode

  // the programme will continue from here after the interrupt

  // first thing to do is to disable sleep mode

  sleep_disable();
}  

void hallInt()
{
// Interrupt function. Toggles the LED state.
  on_off = !on_off;
}

The problem I am having is that the MCU does not wake from sleep on the interrupt. If I comment out the call to the enterSleep function everything works fine.

I have checked the code examples on GitHub and other sites and cannot see what is wrong. If I replace the ATtiny84 with an Arduino UNO the Arduino works as expected. When put to sleep the power consumption drops and it wakes correctly from sleep.

With the ATtiny84 the power consumption is 80% lower if the call to the enterSleep routine is un-commented, so it certainly appears to be sleeping. Although the power consumption is higher than the datasheet.

Please assist with pointers as to the errors of my ways.

The (3-wire ?) hall sensor is continuously powered?
i am just curious why you decided for the hall sensor and not a magnetic reed switch. That would consume zero power when in open state.

[edit]
And what happens if you use:
const byte intPin = PB2; // physical pin 5

Table 7-1 of the datasheet shows that INT0 can only wake up from power down with level trigger (HIGH or LOW), not edge trigger (RISING, FALLING). You must change your attachInterrupt call to HIGH instead of rising, and adjust the rest of your code to not repeat trigger when the button is held down.

The edge detection circuitry in the interrupt requires the system clock, and that is turned off completely in power down sleep. That's why RISING and FALLING won't work.

screenshot.137.jpg

screenshot.137.jpg

tedbar:
With the ATtiny84 the power consumption is 80% lower if the call to the enterSleep routine is un-commented, so it certainly appears to be sleeping. Although the power consumption is higher than the datasheet.

How much current are you measuring? You are probably leaving the Brownout detection circuit and analog reference running.

Put PRR = 0xB; at the beginning of setup (PRR register is describing in datasheet section 7.5.2). This will turn off Timer1, USI, and ADC peripherals, but leaves Timer0 on so your delays work.

<avr/sleep> documentation will show you how to disable the brownout detector before sleep for maximum power saving.

hmeijdam:
The (3-wire ?) hall sensor is continuously powered?
i am just curious why you decided for the hall sensor and not a magnetic reed switch. That would consume zero power when in open state.

You are 100% correct. The simple truth is that I had some smd hall sensors lying around but no suitable switches. The hall sensor/inverter board draws around 15uA when not active and the battery is a 2000mAh battery. So I get 5000 days of standby and that seemed a reasonable compromise.

Jiggy-Ninja:
How much current are you measuring? You are probably leaving the Brownout detection circuit and analog reference running.

Thanks for the clear explanation. Time to do some reading and tinkering. I will report back when I have progressed. The power draw is around 200uA, including the 15uA the sensor board draws.

185 uA is quite a lot for power-down sleep mode. Does that include the torch circuitry too? A microcontroller output isn't strong enough to drive an illumination LED, so there must be some external switching circuitry involved. What's you're entire schematic, and where are you measuring current?

The led/torch circuit will be switched through a low side FET. At present I am triggering just the MCU on a bread board with the hall circuit. There is an LED on the bread board, through a 1K resistor, and I am measuring when it is off. The LED at 4.5V draws around 2.5mA. The 185uA is measured when just the MCU is powered up, a meter in the power supply line. I assume my sleep routine is only partially effective.

If it would help, or if anyone is interested I can post the schematic.

Work has kept me busy, hoping to start reading this evening.

Thanks for pointing me in the right direction. I have a working solution - may not be the most elegant and comments on how to better implement and code this are more than welcome.

Reading the datasheet again (was less daunting second time round) it became clear that the wake from sleep, when powered down, can only be done using a LOW on the external interrupt. Also that Port B has pull up resistors. This makes sense to me as holding the pin low causes a current drain through the pull up resistor and defeats the power saving, so triggering on a HIGH would be contradictory.

Here is my working code:

/* 
Code for the ATtiny84 to switch an LED/FET on and off using a hall snesor.

The Hall switch is configured to be normally HIGH and goes LOW when in the proximity of a magnet.

When the Hall switch goes low and the torch is off:
    1) an external interrupt wakes the ATtiny
    2) the LED/Fet is powered on.

the Hall switch is then polled and a flag (hasBeenHigh) is set when the switch is released. This 
avoids the torch toggling on and off while the button is held down.

The Hall switch is polled and when it goes LOW and hasBeenHigh is true the torch switches off and
the ATting is put to sleep.

*/

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

byte pressState = LOW;            // define the state of the torch on/off switch
byte hasBeenHigh = LOW;           // variable to avoid turning the LED off before the switch button has been released
const byte switchPin = 6;         // pin that drives the FET/LED, physical pin 7
const byte intPin = 0;            // physical pin 5, INT0, interrupt pin
const byte on_offPin = 7;         // Torch on/off switch. Physical pin 6.
int level = 500;                  // set the duty cycle 0 to 1000 where 1000 = 100%

int tOn = level;         // set the time on and time off such that f=1kHz
int tOff = 1000 -level;  // the values represent microseconds



void setup() 
{

  pinMode(on_offPin, INPUT);     // define the pin to poll to turn the torch on/off when it goes LOW
  
  pinMode(switchPin, OUTPUT);    //define the pin that drives the FET/LED as an output pin

  enterSleep();                  // put the MCU into sleep mode with the torch off
}

void loop() 
{
/*  The FET/LED is switched on/off at 1kHz. This is necessary as the power for the board is
 *  derived from the potential across the FET/LED during the off period. A capacitor is charged,
 *  through a diode during the off times and used to power the circuit during the on periods.
 */

    digitalWrite(switchPin, HIGH);
    delayMicroseconds(tOn);
    digitalWrite(switchPin, LOW);
    delayMicroseconds(tOff);

    pressState = digitalRead(on_offPin);   //poll the input pin to determine if the switch has been pressed

    if ((pressState == HIGH) && (hasBeenHigh == LOW))
    
    {
      hasBeenHigh = HIGH;                  // if the switch is released, the Hall goes high - set the flag to HIGH
    }

/* check if the hasBeenHigh flag is set HIGH and the Hall goes low. This indicates that he switch has been 
 * released and pressed again. The torch must then be turned off.
 */
 
    if ((hasBeenHigh == HIGH) && (pressState == LOW))
    {
      hasBeenHigh = LOW;
      enterSleep();                       // power off the torch and enter sleep
    }

}

void enterSleep(void) 
{
// Function to put the ATtiny to sleep.

digitalWrite(switchPin, LOW);            // turn the torch off and wait for the switch to be released

  while(pressState == LOW)
  {
    pressState = digitalRead(on_offPin); // poll to determine when the switch is released
  }
  sei();                                 //set Global Interrupt Enable
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);  
  sleep_enable();
  
  attachInterrupt(intPin, hallInt, LOW); // use the ISR to wake up when the interrupt pin goes LOW
  
  sleep_mode();                          // enter sleep mode

/* the programme will continue from here after the interrupt
 * first thing to do is to disable sleep mode
*/

  sleep_disable();
  detachInterrupt(0);                   // disable the interrupt pin so that hallInt is not called during normal run time (torch on)

}  

void hallInt()
{
/* Do nothing but resume execution */
}

All of the GPIO pins have pullup resistors that can be enabled or disabled. Since you never used pinMode on the intPin, the default state is INPUT with no pullups. current draining from the internal pullup wasn't contributing to your current consumption issue, which is why I didn't mention it as a possibility.

Adding pinMode(INPUT) shouldn't change your current consumption since the pin was INPUT to begin with.

Jiggy-Ninja:
All of the GPIO pins have pullup resistors that can be enabled or disabled. Since you never used pinMode on the intPin, the default state is INPUT with no pullups. current draining from the internal pullup wasn't contributing to your current consumption issue, which is why I didn't mention it as a possibility.

OK, lots to learn ....
You mentioned disabling BOD. It is not clear to me if the MCU I am using supports this, it appears to be revision dependent. However, the power consumption is very dependent on the supply voltage. Above 4.3V (supply) the power down consumption is around 200uA. Below 4.3V it drops rapidly, almost immediately to <1uA reaching 0.1uA by 4.2V (assuming my Fluke is reading correctly).
Could this be due to BOD detection?

I saw references to revision C being used in a 2012 post. If you bought the chip recently and it's not like 15 year old stock, software BOD disable should work.

There's 3 different level settings for the BOD circuit. The highest one is 4.1-4.5V, so 43.V is within that range. What board files are you using to program this chip in Arduino? The fuse byte setting will be in there somewhere, so we can check if it's turned on.

Are you sure about that current reading though? That you're reading 0.1 microamps and not 0.1 milliamps/ 1 uA is way too low for the chip being in RESET. According to the datasheet's Typical Characteristics it should be drawing about 0.75 mA at 4.5 V and something more like 0.6 mA at 4.0V (assuming 8MHz clock).

The reason for the huge current drain is that you left the ADC enabled.. Turn it off before sleeping; you can turn it on again after sleeping - but since I don't see you doing any analogRead()'s you can just turn it off in setup and be done with it there.

ADCSRA &= ~(1 << ADEN); // turn off ADC to save power.

ADCSRA |= (1 << ADEN); // turn on ADC to consume power.

One demerit to everyone who responded to yo and missed this. This is sleep mode 101 guys, when anyone has ~200uA of excess current draw in sleep, 9 times out of 10 it's because they didn't turn the ADC off before going to sleep, because most guides don't mention this. ADC burns around 200uA, and hardware doesn;t turn it off in sleep automatically, because of the analog noise reduction sleep mode (where you stage the ADC read, but instead of setting ADSC to start the conversion, you go to ADC sleep mode, with ADIE ,and it goes to sleep, reads the ADC in it's sleep, and the ADC conversion complete interrupt wakes it). It's not clear to me whether it's also possible to have it sleeping, trigger the start of a conversion without waking it, such that the ADC conversion completing is what ACTUALLY wakes it.

As an aside, you don't need the sei(); I don't see you disabling interrupts, and global interrupts are already reenabled when setup() is called.

Thank you all. 101 is just what I needed :o , turned off the ADC and the draw when put to sleep is 0.1uA at 5V.

I have attached a pic of the multimeter and the power draw from the datasheet using the 1MHz internal clock (which is what I am using). Seems to be right where it should be.

5 days into my MCU experience :slight_smile:

Doh! So here's the good lesson to read the datasheet carefully.

So, my second post here where I recommended writing to the PRR variable, this is the section of the datasheet I got that from. See if you can spot the part I didn't read.

++Karma for DrAzzy.

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.