Microcontroller projects

Sleep tips and tricks

last updated: 2020-10-25

For my LoRa projects I need a deep sleep of the controller, so that battery life will be maximised. The problem with breakout boards is that they contain many components that consume energy an finally are not needed. So here are some basic tests to get a feeling on minimal power consumption.

Voltage

The higher the voltage, the higher the current. For the tests I will use 3 V (2*1.5 V alkaline battery or lithium primary cell). With lithium-ion batteries we can use a diode in series to loose 0,6 V. One reason to do so is that the LoRa chip used has a maximum voltage of 3.7 V and a newly charged battery could get up to 4.1 V and destroy the chip.

ATmega328p (Arduino Uno chip)

Arduinos use a 16 MHz external crystal. This crystal will only operate if the voltage is over 2.7 V. The minimum voltage for the Lora module is 1.8 V and for the mega chip the same if we stay below 4 MHz. So to use the batteries to a maximum, we have to run the mega328 with max. 4 MHz.

The less components we use, the less power is needed. So for the test we could use a breadboard. I decided to use a prototyping PCB. The circuit needs in a first step only a connector for an external programmer (6 pin AVRISP) to burn the bootloader. As programmer you can use an Arduino board (look here) or e.g. an AVRISP mk2. The connections needed are MISO (chip pin 18), MOSI (17), SCK(19), Reset (1), VCC (7) and GND (8).

arduino on a breadboard circuit

Now we could use the board with only the external programmer connected (without bootloader) and change the fuse bits with avrdude. But a bootloader is a cool thing and the chance to have an USB/TTL converter by hand is bigger than having a programmer. So we will use a bootloader.

We can't use the Arduino bootloader, because it's programmed for an 16 MHz external crystal. So we are very happy that MCUdude (Hans) helps with it's Cores. In our case we need the MiniCore. Add the following json text line to to File > Preferences > Additional Boards Manager URLs: (you can add multiple URLs, separating them with commas).

https://mcudude.github.io/MiniCore/package_MCUdude_MiniCore_index.json

After adding the json strings, we need to install the files. For this open the Boards Manager from Tools > Board > Boards Manager.... Scroll down and click on install. After installation is complete, restart Arduino.

Now we can select our Board from Tools > Board.

Screenshot Arduino board

We choose a bootloader with internal RC oscillator and 8 MHz. The libraries for my temperature sensor (DS18B20) won't work with 1 MHz. Fortunately with the help of the WR`register we can change the frequency during runtime.

Click on Burn Bootlader to install the bootloader.

Now we don't need the AVRISP header anymore, but a new header to connect the 3.3 V !! TTL/USB converter. The important connections are TxD and RxD naturally crossed and ground. As it is more comfortable to let the Arduino IDE set the chip in programming mode with the help of an RTS or DTR signal we use an appropriate cable or tweak a converter with an CH340 chip by adding pin 13 through an 100 nF capacitor to the header.

ATmega328p with DS18B20 temperature sensor

We are ready to test a little sketch with an DS18B20 temperature sensor. The data is sent every 2 minutes through the serial port. We use the LowPower library from rocketscream (Add the zip file with Sketch > Include Library > Add .ZIP library...). In PowerDown mode the watchdog timer will wake the CPU up. Maximum time is 8 s.

    /*   mega328_mini_core_deep_sleep_ds18b20.ino by weigu.lu
     *   
     *   We use an mega328p (Arduino) on a breadboard with a minicore bootloader with
     *   8MHz internal clock (https://github.com/MCUdude/MiniCore).
     *   Most of the time we sleep and go down to 1MHz by dividing the clock.
     *   An DS18B20 is connected with VCC to pin D3 and with the data input to pin D4.
     *   The DS18B20 library needs 4MHz! The temperature is send by serial to the monitor.
     *   
     *   RESET  PC6  1  |  |  28 PC5 A5
     *   D0 RX  PD0  2  |  |  27 PC4 A4
     *   D1 TX  PD1  3  |  |  26 PC3 A3
     *   D2     PD2  4  |  |  25 PC2 A2
     *   D3 PWM PD3  5  |  |  24 PC1 A1
     *   D4     PD4  6  |  |  23 PC0 A0
     *   VCC         7  |  |  22 GND
     *   GND         8  |  |  21 AREF
     *   ctal   PB6  9  |  |  20 AVCC
     *   ctal   PB7 10  |  |  19 PB5 D13 SCK
     *   D5 PWM PD5 11  |  |  18 PB4 D12 MISO
     *   D6 PWM PD6 12  |  |  17 PB3 D11 MOSI PWM
     *   D7     PD7 13  |  |  16 PB2 D10 PWM
     *   D8     PB0 14  |  |  15 PB1 D9 PWM 
     *   
     *   If we get the temperature every 2 minutes we need about
     *   Always: 0.007mA*3600s = 25.2mAs = 0.007mAh
     *   Temp: (4.5mA*0.68)*30 = 91.8mAh = 0.0255mAh
     *   Voltage: 2.5mA*0.008s = 0.02mAs **forget it**  
     *   All together gives 0.026mAh per hour or 228mAh per year
     */

    //#define DEBUG

    #include "LowPower.h"
    #include <OneWire.h>
    #include <DallasTemperature.h>

    long send_interval = 15;        // interval between sends, multiple of 8s (15 = 2min)

    const byte PIN_DS18B20_POW = 3;
    const byte PIN_DS18B20_DATA = 4;
    float temperature;
    unsigned int voltage;

    OneWire oneWire(PIN_DS18B20_DATA);
    DallasTemperature DS18B20(&oneWire);

    void setup() {
      pinMode(PIN_DS18B20_POW,OUTPUT);
      digitalWrite(PIN_DS18B20_POW,LOW);  
      DS18B20.begin();
      set_clock_to_1MHz();
      delayMicroseconds(10); 
    }

    void loop() {
      set_clock_to_4MHz(); // DS18B20 lib needs min. 4MHz 
      delayMicroseconds(1); 
      temperature = get_temperature_DS18B20();
      set_clock_to_1MHz(); // back to 1MHz  
      delayMicroseconds(1);
      voltage = get_voltage();
      #ifdef DEBUG
        Serial.begin(38400); // 38400/8 = 4800, set monitor to 4800 bit/s   
        Serial.print(String(temperature) + "°C  ");
        Serial.println(String(voltage) + "V");
        Serial.end();
      #endif  
      for (int i=0; i<send_interval; i++) {
        LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);
      }
    }

    float get_temperature_DS18B20() {
      digitalWrite(PIN_DS18B20_POW,HIGH);
      delayMicroseconds(1);
      DS18B20.requestTemperatures();    
      float temp = DS18B20.getTempCByIndex(0);
      digitalWrite(PIN_DS18B20_POW,LOW); 
      return temp; 
    }  

    unsigned int get_voltage() { // in mV
      ADCSRA = 0x85;                   // ADEN, div = 32 = 125kHz
      ADCSRB &= 0xF8;                  // free running mode  
      ADMUX = 0x6E;                    // 01 Ref = Vcc 1 ADLAR (left 8 Bit) 
      delayMicroseconds(750);          // wait for Vref to settle  6ms = 750*8!
      ADCSRA |= (1 << ADSC);           // start 1 conversion
      while (bit_is_set(ADCSRA,ADSC)); // measure and forget 1 conversion
      delayMicroseconds(10);
      ADCSRA |= (1 << ADSC);           // start 2 conversion
      while (bit_is_set(ADCSRA,ADSC)); // measuring  Vcc (in mV); 281600=1.1*256*1000      
      return 275000L / ADCH;    // adjusted to real multimeters!
    }

    void set_clock_to_1MHz() {
      CLKPR = 0x80; // enable change of CLKPR (4 CLPS bits must be 0)
      CLKPR = 0x03; // set the CLKDIV to 8 (0b0011 = div by 8)
      delayMicroseconds(1000);
    }

    void set_clock_to_2MHz() {
      CLKPR = 0x80; // enable change of CLKPR (4 CLPS bits must be 0)
      CLKPR = 0x02; // set the CLKDIV to 2 (0b0001 = div by 2)
      delayMicroseconds(1000);
    }

    void set_clock_to_4MHz() {
      CLKPR = 0x80; // enable change of CLKPR (4 CLPS bits must be 0)
      CLKPR = 0x01; // set the CLKDIV to 2 (0b0001 = div by 2)
      delayMicroseconds(1000);
    }

    void set_clock_to_8MHz() {
      CLKPR = 0x80; // enable change of CLKPR (4 CLPS bits must be 0)
      CLKPR = 0x00; // set the CLKDIV to 0 (0b0000 = div by 1)
      delayMicroseconds(1000);
    }
Energy consumption

In the following screenshots we see the current in µA (5 mV for 5 µA) and in mA (5 mV for 5 mA). In deep sleep we need about 7 µA. The peak during measuring needs another range to be captured and is about 4.5 mA during 0.68 s.

Screenshot Osci 2    Screenshot Osci 1
Click on the pictures for a sharp version!

If we measure the temperature every 2 minutes we need about:

Together we need about 0.026 mAh in one hour. This gives us 228 mAh per year (8766 h).

With a 1200mAh battery (2xAAA) we get more than 5 years! for our battery life.

ATmega328p with DS18B20 temperature sensor and a LoRa chip

To send the temperature value (or other data) to my house automation system I want to use low power LoRa radio communication with batteries. The continuation of my efforts are documented under microcontroller/lora.

Interesting links: