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.
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.
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).
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
.
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.
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);
}
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.
If we measure the temperature every 2 minutes we need about:
0.007 mA·3600 s = 25.2 mAs = 0.007 mAh
(4.5 mA·0.68 s)30 = 91.8 mAh = 0.0255 mAh
Together we need about 0.026 mAh
in one hour. This gives us 228 mAh per year (8766 h).
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.