This is the most basic example from the low power library:
// **** INCLUDES *****
#include "LowPower.h"
void setup()
{
// No setup is required for this library
}
void loop()
{
// Enter power down state for 8 s with ADC and BOD module disabled
LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);
// Do something here
// Example: Read sensor, data logging, data transmission.
}
Clearly it doesn't do very much on the surface, but it is the cornerstone of my low power setup. If you just want to sleep for an extended period, then this is my routine that does that:
// ==================================================================
// Power down the system for the desired number of seconds. Note that
// the time interval is approximate because the watchdog oscillator
// frequency is not meant to be accurate.
// ==================================================================
void longSleep( uint16_t sleepInSeconds )
{
if ( sleepInSeconds & 0x01 ) LowPower.powerDown(SLEEP_1S, ADC_OFF, BOD_OFF);
if ( sleepInSeconds & 0x02 ) LowPower.powerDown(SLEEP_2S, ADC_OFF, BOD_OFF);
if ( sleepInSeconds & 0x04 ) LowPower.powerDown(SLEEP_4S, ADC_OFF, BOD_OFF);
while ( sleepInSeconds & 0xFFFFFFF8 ) {
sleepInSeconds = sleepInSeconds - 8;
LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);
}
}
and the defined sleep periods I have are:
// 1 hour = 3600 seconds
const uint16_t delayOneHour = 3600;
const uint16_t delay5Mins = 300;
const uint16_t delay10Mins = 600;
const uint16_t delay15Mins = 900;
const uint16_t delay30Mins = 1800;
const uint16_t delay10Seconds = 10;
On reflection, probably should have called the constants sleepXXMins rather than delayXXMins!
Just in case anybody wants to see the complete sketch, here it is:
/*
* XRobotiX Board - RFM69 Node sketch based on the LowPowerLab gateway demo code.
*
* The sketch supports ACK, encryption, and Automatic Transmission Control.
* The sketch uses a simple implementation like MySensors to talk to the gateway.
**********************************************************************************
* SETUP TO SIMPLY REPORT BATTERY VOLTAGE & TEMPERATURE
* Uses the Moteino clone board - badged as XRobotiX LoRa development board.
*
* READING VCC - 3 x AA BATTERY VERSION
* Reading the supply voltage on the LoRa Development Board using external resistors
* Resistor setup is Vin to 2M2 to a 560K to Gnd + a 100n cap in parallel with the 560K.
* The centre tap of the potential divider goes to ADC7.
*
* Vin --[ 2M2 ]-+-[ 560K ]-- GND
* +-[ 100n ]-- GND
* A7 ---'
*
* TEMPERATURE - DS18B20
* Simple read of the DS18B20 sensor connected to D4 (PD4)
*/
#include <OneWire.h>
#include <DallasTemperature.h>
#include <mshaCore.h>
#include <RFM69.h>
#include <LowPower.h>
const char sketchVersion[] PROGMEM = {"1.0a"};
#if defined (ARDUINO_AVR_XROBOTIX_DEV_BOARD)
// only the XRobotiX boards use the HCW variant
#define USING_RFM69HW_HCW 1
#else
#define USING_RFM69HW_HCW 0
#endif
#define SERIAL_BAUD 9600
#define CHILD_ID1 10 // Id of the #1 sensor child - motion sensor
// Data wire is plugged into port pin PD4 (digital pin 4 on the Arduino UNO)
#define ONE_WIRE_BUS 4
// Setup a oneWire instance to communicate with any OneWire device
OneWire oneWire(ONE_WIRE_BUS);
// Pass oneWire reference to DallasTemperature library
DallasTemperature sensors(&oneWire);
// 1 hour = 3600 seconds
const uint16_t delayOneHour = 3600;
const uint16_t delay5Mins = 300;
const uint16_t delay10Mins = 600;
const uint16_t delay15Mins = 900;
const uint16_t delay30Mins = 1800;
const uint16_t delay10Seconds = 10;
// configuration data stored in internal EEPROM
struct mshaEEConfigType eeConfig;
// char buffer for general use
char chBuff[ 32 ];
void setup() {
bool eeReadResult;
// enable the internal pullup resistor for the motion sensor input
pinMode( 4, INPUT_PULLUP );
Serial.begin(SERIAL_BAUD);
delay(500);
Serial.println(F("# myHomeAutomation RFM69 Serial Node - MySensors Protocol."));
Serial.print(F("# myRFM69PeriodicReporting.ino v"));
Serial.println( (__FlashStringHelper*)sketchVersion );
Serial.println(F("# Built on " __DATE__ " at " __TIME__ ));
Serial.println(F("# Configured for hourly voltage & temperature reports."));
#if defined (ARDUINO_AVR_XROBOTIX_DEV_BOARD)
Serial.println(F("# Compiled for XRobotiX LoRa development board"));
#elif defined (ARDUINO_AVR_UNO)
Serial.println(F("# Compiled for Arduino UNO + HS Electors shield"));
#else
Serial.println(F("# Compiled for unknown hardware <---- !!!"));
#endif
// get our stored settings from internal EEPROM
eeReadResult = getEESettings();
// uncomment the lines below to force a specific Node ID
// eeConfig.param[ MSHA_NODE_ID ] = 3;
// mshaEEWriteSettings( (uint8_t *)&eeConfig, sizeof(eeConfig) );
// eeReadResult = true;
// hardware reset the radio to get back to a known state
resetRadio();
if ( mshaInitRadio( eeConfig.param[MSHA_NODE_ID], USING_RFM69HW_HCW ) == false ) {
Serial.println(F("HALT: Setup failed."));
while(1);
}
delay(100);
// if the EEPROM settings are invalid, then we need a new node ID, so get one
// and stay here till we do
if ( eeReadResult == false ) {
Serial.println(F("# INFO: Attempting to get new node ID."));
while ( mshaGetNewNodeID( &eeConfig.param[ MSHA_NODE_ID ] ) == false ) {
delay( 5000 );
}
// save the settings now we have a node ID
mshaEEWriteSettings( (uint8_t *)&eeConfig, sizeof(eeConfig) );
Serial.print(F("# INFO: Got & saved new node ID "));
Serial.println( eeConfig.param[MSHA_NODE_ID] );
}
// if we get this far, then the radio module is setup ready to go.
presentation();
// Start up the 1-wire library
sensors.begin();
// put the RFM69 into ultra low power mode
mshaRadioSleep();
}
//****************************************************************************
// presentation
//----------------------------------------------------------------------------
// Announce to the controller via the gateway what sensors we have.
// This needs to succeed before we can carry on so it won't return until it
// succeeds in getting the information across.
//****************************************************************************
void presentation()
{
// each of these messages has to succeed before we can continue, so keep
// trying until they all arrive - i.e ACK recevied from gateway
// Send the sketch version information to the gateway
Serial.print("\nSN ");
while ( mshaSendSketchName(F("Periodic Reporter")) == false ) delay(2000);
delay(500);
Serial.print("\nSV ");
while ( mshaSendSketchVersion( (__FlashStringHelper*)sketchVersion ) == false ) delay(2000);
delay(500);
// Register all our INTERNAL sensors to the controller via the gateway (they will be created as child devices)
Serial.print("\nC1 ");
while ( mshaPresent( 1, S_MULTIMETER, "Battery" ) == false ) delay(2000);
delay(500);
Serial.print("\nC2 ");
while ( mshaPresent( 2, S_TEMP, "DS18B20" ) == false ) delay(2000);
delay(500);
// if the gateway detects child sensor #3 and it's defined as S_SOUND, then
// it will automatically generate an S_SOUND message to the controller for
// every received message and populate it with the RSSI of that message!
Serial.print("\nC3 ");
while ( mshaPresent( 3, S_SOUND, "RSSI" ) == false ) delay(2000);
delay(500);
}
//****************************************************************************
// loop
//****************************************************************************
void loop() {
bool result;
// short delay after waking from SLEEP mode for things to settle down
delay(10);
// Send the command to get temperatures
sensors.requestTemperatures();
dtostrf( sensors.getTempCByIndex(0), 5, 2, chBuff );
Serial.print( chBuff ); Serial.println( " C" );
mshaSend( 2, V_TEMP, chBuff );
delay( 1000 );
dtostrf( readVcc(), 5, 3, chBuff );
Serial.print( chBuff ); Serial.println( " V" );
mshaSend( 1, V_VOLTAGE, chBuff );
delay( 1000 );
// put the RFM69 back into ultra low power mode
mshaRadioSleep();
longSleep( delay30Mins );
}
//============================================================================
// RETRIEVE EEPROM SETTINGS
// Retreve stored settings from internal EEPROM.
// Returns TRUE if settings are valid, otherwise returns FALSE.
// If the settings are invalid, then all the settings are zero'd and the
// NODE ID setting is set to 255.
//============================================================================
bool getEESettings()
{
bool eeReadResult;
char buff[8];
// retrieve the stored settings from internal EEPROM
eeReadResult = mshaEEReadSettings( (uint8_t *)&eeConfig, sizeof(eeConfig) );
Serial.print(F("# "));
// print them out
for(int i=0; i<MSHA_EE_PARAM_COUNT; i++) {
sprintf(buff, "%02X ", eeConfig.param[i] );
Serial.print( buff );
}
Serial.println();
if ( eeReadResult == false ) {
for (int i=0; i<sizeof(eeConfig); i++ ) eeConfig.param[i] = 0;
eeConfig.param[MSHA_NODE_ID] = MSHA_DEFAULT_NODE_ID;
Serial.println(F("# WARN: EEPROM settings invalid - default to Node 255."));
}
return eeReadResult;
}
// ==================================================================
// Power down the system for the desired number of seconds. Note that
// the time interval is approximate because the watchdog oscillator
// frequency is not meant to be accurate.
// ==================================================================
void longSleep( uint16_t sleepInSeconds )
{
if ( sleepInSeconds & 0x01 ) LowPower.powerDown(SLEEP_1S, ADC_OFF, BOD_OFF);
if ( sleepInSeconds & 0x02 ) LowPower.powerDown(SLEEP_2S, ADC_OFF, BOD_OFF);
if ( sleepInSeconds & 0x04 ) LowPower.powerDown(SLEEP_4S, ADC_OFF, BOD_OFF);
while ( sleepInSeconds & 0xFFFFFFF8 ) {
sleepInSeconds = sleepInSeconds - 8;
LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);
}
}
//============================================================================
// RESET THE RADIO
// UNO + HS Electros Shield + nRF24L01->RFM69 adapter - uses PB1 (pin 9)
// AA Node + nRF24L01->RFM69 adapter - uses PB1 (pin 9)
// XRobotiX RFM69/96 dev board - uses PB0 (pin 8)
//============================================================================
void resetRadio()
{
#if defined (ARDUINO_AVR_UNO) || defined (ARDUINO_AVR_AANODE)
// If the board is an UNO, then we're using the HS Electros shield
// reset the radio - via PB1 - pin 9
pinMode( 9, OUTPUT );
digitalWrite( 9, LOW );
delay( 10 );
digitalWrite( 9, HIGH );
delay( 10 );
digitalWrite( 9, LOW );
delay( 100 );
Serial.println(F("# Radio reset."));
#endif
#if defined (ARDUINO_AVR_XROBOTIX_DEV_BOARD)
// XRobotiX board has reset mod to reset the radio - via PB0 - pin 8
pinMode(8, OUTPUT); // set PB0 as an OUTPUT pin
digitalWrite(8, HIGH); // RFM69 RESET pin HIGH = RESET
delay(1); // delay 1000us
digitalWrite(8, LOW); // RFM69 RESET pin LOW
delay(10); // wait 10ms
Serial.println(F("# Radio reset."));
#endif
}
// ==================================================================
// Read the supply voltage and return a voltage in millivolts
// ==================================================================
// XRobotiX Board:
// There is a potential divider across the battery supply with a 2M2
// to +ve and a 560K to Gnd. There is a 100n across the 560K.
// This generates about 1.014V with 5V coming from the batteries.
// ==================================================================
// AA-Node Board:
// The AA node method does not use any external components.
// The voltatge is calculated as ( vRef x 1024 x 1000 ) / ADCReading
// which is 1.1 x 1024 x 1000 (=1126400) / ADCReading.
// ==================================================================
float readVcc()
{
uint32_t result;
#if defined (ARDUINO_AVR_XROBOTIX_DEV_BOARD)
// XRobotiX specific code
// select 1.1v internal reference
analogReference( INTERNAL );
// read ADC channel 7 twice, ignore the first result
analogRead(7);
result = analogRead(7);
// adc reading x 5.28 - multiply by 0xA8F5 and shift right 13 times
// to get the voltage in millivolts - roughly!
result = result * 0xA8F5;
result = result>>13;
#endif
#if defined (ARDUINO_AVR_AANODE)
// AA-Node specific code
// enable the ADC - in case it's turned off!
ADCSRA |= (1 << ADEN);
// set the reference voltage to be AVcc and the signal to measure, the internal 1.1V reference
ADMUX = (1 << REFS0) | (1 << MUX3) | (1 << MUX2) | (1 << MUX1);
delay(10); // Wait for Vref to settle down
// start a conversion
ADCSRA |= (1 << ADSC);
// and wait for it to complete - ADSC goes low on completion
while ( (ADCSRA & (1 << ADSC)) != 0 );
// read the result of the conversion
result = (uint32_t)ADC;
result = 1126400L / result; // Calculate Vcc (in mV)
#endif
// result is in millivolts - need volts for Domoticz
return ((float)result) / 1000.0;
}
It's mainly for an XRobotiX board which is a clone of a Moteino board - consisting of a 328P, crystal, regulator, and a few discretes. I've added an RFM69HCW module to the rear and a daughter board that holds the DS18B20 temperature sensor as well as the potential divider to measure the battery voltage.
I couldn't get the MySensors library to work with my RFM69 boards, so I rolled my own version of it for the nodes and gateway that connects to a re-purposed thin client running Linux Lite and Domoticz.
Some boards run off 3x AA batteries and last over 6 months on one set. One board currently on month 11! Other boards are built into a cheap solar garden PIR light and are wired straight across the rechargeable battery. They just keep going as long as the battery is good and there's enough sunlight to recharge them.