Arduino UNO, sleep modes and interrupt, strange behaviour

Hi to all,
I am trying to write a "simple" sketch to improve the following task:
I have an instrument that sometimes, during the day heats up at more than 100 °C for a few minutes and the rest of the day stays at ambient temperature. I need a simple, and battery powered , circuit to read the amount of time the instrument is hot and save that duration (ex. minutes) in EEPROM.
My idea is to use a ATMEGA 328p programmed to run at 8MHz internal clock without any other components outside (crystal etc.) in order to save power.
The firmware i am writing simply keep the CPU always SLEEP_MODE_PWR_DOWN, until an interrupt is made on pin 2 (FALLING) . The interrupt is done using a simple mechanical termostat calibrated at 100 °C (those used inside domestic devices, like flat irons, for security reasons). It can be seen as a mechanical button connected to pin2 kept high by INPUT_PULLUP.
When the CPU is awaked from sleep it keeps the value of millis() and go to sleep in SLEEP_MODE_IDLE until the termostat opens (RISING). At that time the CPU calculate the time elapsed and write inside the EEPROM, after that goes to SLEEP_MODE_PWR_DOWN again until next event.

My problems (2):

  1. When i put the CPU in PWR_DOWN mode the arduino sleeps, but when it awakes i have strange characters coming from the serial port (strange bytes)
  2. When i call IDLE sleep the CPU doesn't go to sleep.. I am usind IDLE because it seems the only way to keep millis() running, otherwise i cannot take durations.

Below i attach only the "core" part of the code, in wich i implement the sleep/awake tasks.

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

// VARS
#define int_pin 2
#define led_pin 8
#define debounce_time 10
uint8_t int_type = 0;
long lastMillis = millis();
long elapsedTime = 0;
int counter = 0;

// ================================================================================
// ISR
// ================================================================================
void isr()
{
    // do nothing
}

// ================================================================================
// Blink led
// ================================================================================
void blink_led(int time)
{
    digitalWrite(led_pin, HIGH);
    delay(time);
    digitalWrite(led_pin, LOW);
}

// ================================================================================
// Setup
// ================================================================================
void setup()
{
    // Declare pins
    pinMode(int_pin, INPUT_PULLUP);
    pinMode(led_pin, OUTPUT);

    // Begin serial
    Serial.begin(115200);

    // Disable unused peripherals to reduce power consumption
    power_adc_disable();
    power_spi_disable();
    power_twi_disable();

    // Attach interrupt
    attachInterrupt(digitalPinToInterrupt(int_pin), isr, CHANGE);

    // Set the sleep mode to Power-down
    set_sleep_mode(SLEEP_MODE_PWR_DOWN;
}

// ================================================================================
// Loop
// ================================================================================
void loop()
{
    // Go to sleep
    sleep_mode();

    // ----------- sleeping . . . ----------------

    Serial.println("AWAKE!");

    // debounce
    delay(debounce_time);

    // Flush serial
    Serial.flush();

    // Detect INT type
    int_type = digitalRead(int_pin);

    // HOT condition
    if(int_type == 0)
    {
        Serial.println("FALLING, HOT");
        blink_led(10);
        lastMillis = millis();
        set_sleep_mode(SLEEP_MODE_IDLE);
    }

    // COLD condition
    else
    {
        Serial.println("RISING, COLD");
        blink_led(10);
        elapsedTime = lastMillis;
        Serial.print("Time elapsed: ");
        Serial.println(elapsedTime);
        set_sleep_mode(SLEEP_MODE_PWR_DOWN);
    }
}

to know the time you need to read it from somewhere. if I were to do something like this, my project would be on a wall outlet, with constant power and current monitoring of the heater, and the changes would be stored on an SD card.

Hi koala,
my circuit has to be used far from any power source and must last months. the good news is that i only need the full amount of time the temperature in about 100 °C with poor precision.
Updates:

  1. about serial port, i just added a delay(10) before putting to sleep and the strange characters has disappeared.
  2. Reading around it seems that in IDLE sleep the CPU in awake every 1 ms by timer0 (millis() timer). The way to deny tmr0 interrupt is to disable tmr0 with command power_timer0_disable(), but doing this will stop the millis() count during HOT time...

Read the datasheet for 328P, 10.1. There is a table for power modes.
Mess from serial port is probably combination of buffered data and clock loss in power down mode.

Welcome and thanks for using code tags in our first post :+1:

As your topic is not specific to the IDE, it has been moved to a more suitable location on the forum.

Yes, you have to have the clock running for millis to work. So you can only have light sleep. The typical solution for this is to use a separate RTC (real time clock) which can be powered by its own coin cell for years. Then when the thermostat wakes up the Arduino, you just read the current time from the RTC, do whatever else needs to be done, then go back into power_down sleep mode with all clocks off. A properly configured 328P with no regulator or LEDs running can deep sleep at less than 1uA.

The typical DS3231 RTC module, known as the ZS-042, also has a 4KB EEPROM. But since you don't really need an alarm function that could wake up the 328P, any RTC would work fine. However, if it's important that the time of day be correct, rather than just the elapsed time, then the DS3231 would be the most accurate option.

You could use the watchdog timer to wake yourself up every 8 seconds or so and then read the state of the pin. Then count the number of 8 second sleeps that occur with the temperature above 100C. When the temperature goes back below 100C, write the count to the next free EEPROM location.

If you use 1 byte per time period above 100C, then the 8 second WDT counter will give you 255 x 8s period (or 2040 seconds, 34 minutes).

A 328P has 1024 bytes of EEPROM so that will allow you to record 1024 time periods. At 1 per day, that's > 2.5 years of values.

That would be the micro going to sleep before the serial buffer has emptied, as @Budvar10 says.

I did experiment some time ago regarding the "accuracy" of the WDT and it's not that bad and likely perfectly good enough for your application, give that the WDT isn't meant to be used for any form of accurate timing.

See

Nick Gammon on low power

for more than you need to know and several many ways to do things like this.

If it was a book, I'd say this guy wrote the book on low power.

Worth the time I promise. :wink:

a7

2 Likes

1. If you have bought an ATmega328P from a shop, then the MCU will be running on 8 MHz internal oscillator and without external reset circuit.

2. Configure triggering mode of INT0 at LOW and enable internal pull-up. The thermostat (N/O) will remain connected at INT0-pin.

3. Configure TC1 to run in normal mode at 8 MHz driving clock and keep it in OFF state.

4. Put the MCU into sleep using Power-down mode.
5. When MCU wakes up at the event thermostat closes at 1000C, start TC1 and keeps polling INT0-pin for HIGH state.

6. When INT0-pin assumes HIGH state (thermostat is open and temperature has gone below 1000 C), stop TC1. Read the content of TCNT1 as count and convert it into time to know how long temperature has sustained above 1000C. Save the time into the EEPROM of the MCU.

7. Goto Section-4.

Note: The above algorithm could be easily tested using Arduino UNO Platform.

First I want to thank you all for your help,

(alto777) I know that guide, and i agree it is really well done, altought i can follow it until a certain point... from where i am lost :wink:

(ShermanP) Yes, this can be a way, but for a series of reasons i have to take the hardware as simple (and cheap) as it is possible. As i said precision and absolute timings are not mandatory for me.

(markd833) This is the way i am currentrly exploring, i did not arrived yet to use WDT, but my next improve will be in that direction, sure.

I arrived at the code below, were i keep the CPU oways power_down, until the thermostat close, during the thermostat is close i go to IDLE sleep and each time the CPU is awaken by the TMR0 (1 ms) i increase a counter by 1 ms + the debounce time. This way i get the duration of the "HOT" time.
Then when the thermostat opens, i print the value (and i will save into EEPROM), i reset the counter , and i go to sleep.
It works !, the only problem is that during PWR_DOWN the circuit absorbs about 0.1 mA (measured using a simple tester.. maybe more or less..) but during IDLE time i have about 3.5 mA that is not good..
At this point i will try putting to PWR_DOWN also during "HOT" time and awaking the CPU using WDT with 8seconds time, and icrementing the counter by 8 seconds each time. The rest will be the same.

What i don't know is if there is a way to use WDT to awake the CPU without to reset it completly, otherwise my solution cannot work...

I will try tonight (the best moment of the day for a programmer hahaha)
Thank you again to all

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

// VARS
#define int_pin 2
#define led_pin 8
#define debounce_time 10
uint8_t int_state = 0;
int counter = 0;

// ================================================================================
// ISR
// ================================================================================
void isr()
{
    // Do nothing
}

// ================================================================================
// Blink led
// ================================================================================
void blink_led(int time)
{
    digitalWrite(led_pin, HIGH);
    delay(time);
    digitalWrite(led_pin, LOW);
}

// ================================================================================
// Setup
// ================================================================================
void setup()
{
    // Declare pins
    pinMode(int_pin, INPUT_PULLUP);
    pinMode(led_pin, OUTPUT);

    // Begin serial
    Serial.begin(115200);

    // Disable unused peripherals to reduce power consumption
    power_adc_disable();
    power_spi_disable();
    power_twi_disable();

    // Attach interrupt
    attachInterrupt(digitalPinToInterrupt(int_pin), isr, CHANGE);

    // Set the sleep mode to Power-down
    set_sleep_mode(SLEEP_MODE_PWR_DOWN);
}

// ================================================================================
// Loop
// ================================================================================
void loop()
{
    // Go to sleep
    sleep_mode();

    // ----------- sleeping.. ----------------

    // debounce
    delay(debounce_time);

    // Detect INT type
    int_state = digitalRead(int_pin);

    // OVEN START condition
    if(int_state == 0)
    {
        counter += debounce_time + 1;        // debounce time + 1 ms TMR0 interrupt
        set_sleep_mode(SLEEP_MODE_IDLE);
    }

    // OVEN STOP condition
    else
    {
        Serial.println("RISING, OVEN STOP");
        blink_led(10);
        Serial.print("Time elapsed: ");
        Serial.println(counter);
        counter = 0;
        delay(10);
        set_sleep_mode(SLEEP_MODE_PWR_DOWN);
    }
}

Yes there is. I thought the library had an example where the code simply carried on executing from the point where it went to sleep.

I looked at the LowPower Lab code here, and one of the examples called powerDownWakePeriodic.ino demonstrates it - I hope!

I am really near my goal !
This code work perfectly, It uses 0.1 mA when sleeping and 0.2 mA during heated periods, and it measures accurately enought the time. It seems that WDT require 0.1 mA to work.
Now i will save the duration time of each heating period in a byte of the EEPROM, and implement a method to sento to serial data on for example interrupt on pin 3.
I tried to use the function pwr_all_disable() to lower more the power drained, but it breaks something somewere, the code blocks. Making some tests it seems i cannot disable timer0, ie i cannot use pwr_timer0_disable().
Someone knows why ?
Thank you all.

#include <Arduino.h>
#include <avr/sleep.h>
#include <avr/power.h>
#include <avr/wdt.h>

// VARS
#define int_pin 2
#define led_pin 8
#define debounce_time 10
uint8_t int_state = 0;
int counter = 0;

// ================================================================================
// ISR thermostat
// ================================================================================
void isr()
{
    // Do nothing
}

// ================================================================================
// ISR WDT
// ================================================================================
ISR(WDT_vect) {
    Serial.println("WDT");
}

// ================================================================================
// Blink led
// ================================================================================
void blink_led(int time)
{
    digitalWrite(led_pin, HIGH);
    delay(time);
    digitalWrite(led_pin, LOW);
}

// ================================================================================
// Setup
// ================================================================================
void setup()
{
    // Used to avoid strange behaviour of WDT
    wdt_disable();

    // Declare pins
    pinMode(int_pin, INPUT_PULLUP);
    pinMode(led_pin, OUTPUT);

    // Begin serial
    Serial.begin(115200);

    // Disable unused peripherals to reduce power consumption
    power_adc_disable();
    power_spi_disable();
    power_twi_disable();
    power_timer1_disable();
    power_timer2_disable();

    // Attach interrupt
    attachInterrupt(digitalPinToInterrupt(int_pin), isr, CHANGE);

    // Set the sleep mode to Power-down
    set_sleep_mode(SLEEP_MODE_PWR_DOWN);
}

// ================================================================================
// Loop
// ================================================================================
void loop()
{
    // Go to sleep
    sleep_mode();

    // ----------- sleeping.. ----------------

    // debounce
    delay(debounce_time);

    // Detect INT type
    int_state = digitalRead(int_pin);

    // OVEN START condition
    if(int_state == 0)
    {
        wdt_enable(WDTO_1S);                    // Enable WDT
        WDTCSR |= (1 << WDIE);                  // WDT in interrupt mode (no reset)
        blink_led(10);                          // Blink
        counter += debounce_time + 1000;        // debounce time + WDT time
        set_sleep_mode(SLEEP_MODE_PWR_DOWN);    // Go to sleep PWR DOWN
    }

    // OVEN STOP condition
    else
    {   
        wdt_disable();                          // Disable WDT during sleep
        Serial.println("RISING, OVEN STOP");
        blink_led(10);
        Serial.print("Time elapsed: ");
        Serial.println(counter);
        counter = 0;                            // Reset the counter
        delay(10);                              // delay to prevent serial buffer corruption
        set_sleep_mode(SLEEP_MODE_PWR_DOWN);    // Go to sleep PWR DOWN
        //
        //  Save to EEPROM (to do..)
        //
    }
}

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