Explorer in Low power mode¶
Paper¶
Read the paper. to learn about the deep sleep of the SODAQ board.
Sketches¶
Low power sketch¶
This sketch will turn all components off or into sleep.
#include <Arduino.h>
#include <SPI.h>
#include <Sodaq_RN2483.h>
#include <Sodaq_wdt.h>
#include <RN487x_BLE.h>
// Specific library dependencies:
// RN487x_BLE.h -> Microchip_RN487x library -> version 1.0.2 +
// Sodaq_wdt.h -> Sodaq_wdt library
// Sodaq_RN2483.h -> Sodaq_RN2483
/*
* This sketch as-is sets an ExpLoRer down to 20.5 uA / 76 uW after start-up phase
* Lower is possible but requires shutting down the watchdog timer and loses functionality
*
* Possible improvement: setting the ATECC508a/608a watchdog timer to 10s vs default 1.3s
*/
#define bleSerial Serial1
#define loraSerial Serial2
void setup()
{
// setup watchdog timer -- we want to wake up
sodaq_wdt_enable(WDT_PERIOD_8X);
sodaq_wdt_reset();
sodaq_wdt_safe_delay(5000);
// LoRa module connection and sleep
loraSerial.begin(LoRaBee.getDefaultBaudRate());
LoRaBee.init(loraSerial, LORA_RESET);
LoRaBee.sleep();
sodaq_wdt_safe_delay(5); // not essential but seems to smooth things up
// BLE module sleep
rn487xBle.hwInit();
bleSerial.begin(rn487xBle.getDefaultBaudRate());
rn487xBle.initBleStream(&bleSerial);
rn487xBle.enterCommandMode();
rn487xBle.dormantMode();
bleSerial.end();
// set FLASH to deep sleep & reset SPI pins for min. energy consumption
DFlashUltraDeepSleep();
SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk; // set sleep mode -> deep sleep
SerialUSB.flush();
SerialUSB.end();
USBDevice.detach();
USB->DEVICE.CTRLA.reg &= ~USB_CTRLA_ENABLE; // Disable USB
}
void loop()
{
sodaq_wdt_reset();
systemSleep();
}
/**
Powers down all devices and puts the system to deep sleep.
*/
void systemSleep()
{
// Disable systick interrupt
SysTick->CTRL &= ~SysTick_CTRL_TICKINT_Msk;
__WFI(); // SAMD sleep
// Enable systick interrupt
SysTick->CTRL |= SysTick_CTRL_TICKINT_Msk;
}
// FLASH chip sleep functions
void DFlashUltraDeepSleep()
{
static const uint8_t SS_DFLASH = 44 ;
SPI.begin();
pinMode(SS_DFLASH, OUTPUT);
digitalWrite(SS_DFLASH, HIGH);
// transmit(0xAB);
// sodaq_wdt_safe_delay(10);
transmit(0xB9);
SPI.end();
resetSPIPins();
}
void transmit(uint8_t val)
{
SPISettings settings;
digitalWrite(SS_DFLASH, LOW);
SPI.beginTransaction(settings);
SPI.transfer(val);
SPI.endTransaction();
digitalWrite(SS_DFLASH, HIGH);
delayMicroseconds(1000);
}
void resetSPIPins()
{
resetPin(MISO);
resetPin(MOSI);
resetPin(SCK);
resetPin(SS_DFLASH);
}
void resetPin(uint8_t pin)
{
PORT->Group[g_APinDescription[pin].ulPort].
PINCFG[g_APinDescription[pin].ulPin].reg=(uint8_t)(0);
PORT->Group[g_APinDescription[pin].ulPort].
DIRCLR.reg = (uint32_t)(1<<g_APinDescription[pin].ulPin);
PORT->Group[g_APinDescription[pin].ulPort].
OUTCLR.reg = (uint32_t) (1<<g_APinDescription[pin].ulPin);
}
Sending over LoRa and Sleeping¶
#include <Arduino.h>
#include <Sodaq_RN2483.h>
#include <Sodaq_wdt.h>
#include <SPI.h>
#include <RN487x_BLE.h>
#include <RTCTimer.h>
#include <RTCZero.h>
#define CONSOLE_STREAM SERIAL_PORT_MONITOR
#define debugSerial SerialUSB // debug on USB
#define bleSerial Serial1 // Bluetooth module Serial
#define loraSerial Serial2 // LoRa module Serial
#define LORA_BAUD 57600
#define DEBUG_BAUD 57600
#define NIBBLE_TO_HEX_CHAR(i) ((i <= 9) ? ('0' + i) : ('A' - 10 + i))
#define HIGH_NIBBLE(i) ((i >> 4) & 0x0F)
#define LOW_NIBBLE(i) (i & 0x0F)
RTCZero rtc;
RTCTimer timer;
volatile bool minuteFlag;
//
// setup your constants here !!
//
const uint8_t records_to_send = 2; // set this to change the amount of records to send
const uint8_t record_every_x_minutes = 1; // set this to the desired interval in minutes
const uint8_t spreading_factor = 7; // set this to the desired LoRa spreading factor
// *****************************************************************************************
// LoRa communication setup !
// true : use OTAA
// false : use ABP
bool OTAA = false;
// ABP setup (device address)
// USE YOUR OWN KEYS!
const uint8_t devAddr[4] =
{
0x00, 0x00, 0x00, 0x00
};
// application session key
// USE YOUR OWN KEYS!
const uint8_t appSKey[16] =
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
// network session key
// USE YOUR OWN KEYS!
const uint8_t nwkSKey[16] =
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
// OTAA (device EUI)
// With using the GetHWEUI() function the HWEUI will be used
static uint8_t DevEUI[8]
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
const uint8_t AppEUI[8] =
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
const uint8_t AppKey[16] =
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
// *****************************************************************************************
// setup
bool LoRa_sleeps = false;
uint8_t message [records_to_send * 2];
void setup();
void loop();
void BT_powerdown();
void sleep_setup();
void systemSleep();
void sleep_LoRa();
void wake_LoRa();
void DFlashUltraDeepSleep();
void transmit(uint8_t val);
void resetSPIPins();
void resetPin(uint8_t pin);
void setupLoRa();
void setupLoRaABP();
void setupLoRaOTAA();
static void getHWEUI();
void send_message(uint8_t* val, size_t val_size);
void getTemperature();
void initRtc();
void rtcAlarmHandler();
void initRtcTimer();
void resetRtcTimerEvents();
uint32_t getNow();
void measureTemperature(uint32_t now);
void setup()
{
sodaq_wdt_enable(WDT_PERIOD_8X); // Enable the wdt at maximum interval
sodaq_wdt_reset();
sodaq_wdt_safe_delay(5000);
pinMode(TEMP_SENSOR, INPUT);
initRtc();
SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk; // sets SAMD sleep mode to deep sleep
// networks
loraSerial.begin(LoRaBee.getDefaultBaudRate());
LoRaBee.init(loraSerial, LORA_RESET);
setupLoRa();
initRtcTimer(); //timerinterrupt > 1 minute interval
sodaq_wdt_reset();
SerialUSB.flush();
SerialUSB.end();
USBDevice.detach();
USB->DEVICE.CTRLA.reg &= ~USB_CTRLA_ENABLE; // Disable USB
sleep_setup();
}
// *****************************************************************************************
// loop
// array for temperature we wish to send;
// here we’re taking a 6-value (12 bytes) array to send at once
// intended to measure every n*60 seconds, send every x messages.
const uint8_t measurements_to_send = 6 ;
int temperature_array [measurements_to_send];
uint8_t list_iter = 0; //variable to keep track of array index and when to send
void loop()
{
if (sodaq_wdt_flag) {
sodaq_wdt_reset();
sodaq_wdt_flag = false;
}
if (minuteFlag) {
timer.update();
minuteFlag = false;
}
systemSleep();
}
// *****************************************************************************************
// Sleep commands
void BT_powerdown()
{
rn487xBle.hwInit();
bleSerial.begin(rn487xBle.getDefaultBaudRate());
rn487xBle.initBleStream(&bleSerial);
rn487xBle.enterCommandMode();
rn487xBle.dormantMode();
bleSerial.end();
}
void sleep_setup()
{
// set FLASH to deep sleep & reset SPI pins for min. energy consumption
DFlashUltraDeepSleep();
sleep_LoRa();
// RN4871 BT/BLE module sleep
BT_powerdown();
}
void systemSleep() // Since only LoRa and MCU awake, only set those to sleep
{
// Skip if LoRa is asleep
if (!LoRa_sleeps) {
sleep_LoRa();
}
noInterrupts();
if (!(sodaq_wdt_flag||minuteFlag)) {
interrupts();
debugSerial.println("Sleeping");
__WFI(); // SAMD sleep
}
interrupts();
}
void sleep_LoRa()
{
loraSerial.flush();
LoRaBee.sleep();
LoRa_sleeps=true;
sodaq_wdt_safe_delay(5); // without this, it doesn’t sleep.. don’t know why
}
void wake_LoRa()
{
LoRa_sleeps = false;
LoRaBee.wakeUp();
}
// *****************************************************************************************
// SST25PF040C Flash functions (SPI)
void DFlashUltraDeepSleep()
{
static const uint8_t SS_DFLASH = 44;
// SPI initialization
SPI.begin();
// Initialize the CS pin for the data flash
pinMode(SS_DFLASH, OUTPUT);
digitalWrite(SS_DFLASH, HIGH);
transmit(0xB9);
SPI.end();
// Resets the pins used
resetSPIPins();
}
void transmit(uint8_t val)
{
SPISettings settings;
digitalWrite(SS_DFLASH,LOW);
SPI.beginTransaction(settings);
SPI.transfer(val);
SPI.endTransaction();
digitalWrite(SS_DFLASH,HIGH);
delayMicroseconds(1000);
}
void resetSPIPins()
{
resetPin(MISO);
resetPin(MOSI);
resetPin(SCK);
resetPin(SS_DFLASH);
}
void resetPin(uint8_t pin)
{
PORT->Group[g_APinDescription[pin].ulPort].
PINCFG[g_APinDescription[pin].ulPin].reg=(uint8_t)(0);
PORT->Group[g_APinDescription[pin].ulPort].
DIRCLR.reg=(uint32_t)(1<<g_APinDescription[pin].ulPin);
PORT->Group[g_APinDescription[pin].ulPort].
OUTCLR.reg=(uint32_t)(1<<g_APinDescription[pin].ulPin);
}
// *****************************************************************************************
// RN2483 / RN2903 LoRa commands
// for RN2903 : uncomment the setFsbChannels line.
void setupLoRa()
{
getHWEUI();
if (!OTAA) {
setupLoRaABP(); // ABP setup
} else {
setupLoRaOTAA(); // OTAA setup
}
// Uncomment the following line to for the RN2903 with the Actility Network.
// For OTAA, update the DEFAULT_FSB in the library
// LoRaBee.setFsbChannels(1);
LoRaBee.setSpreadingFactor(spreading_factor);
}
void setupLoRaABP()
{
if (LoRaBee.initABP(loraSerial, devAddr, appSKey, nwkSKey, true)) {
debugSerial.println("Communication to LoRaBEE successful.");
} else {
debugSerial.println("Communication to LoRaBEE failed!");
}
}
void setupLoRaOTAA()
{
if (LoRaBee.initOTA(loraSerial, DevEUI, AppEUI, AppKey, true)) {
debugSerial.println("Network connection successful.");
} else {
debugSerial.println("Network connection failed!");
}
}
/**
* Gets and stores the LoRa module's HWEUI/
*/
static void getHWEUI()
{
uint8_t len = LoRaBee.getHWEUI(DevEUI, sizeof(DevEUI));
}
void send_message(uint8_t* val, size_t val_size)
{
wake_LoRa();
// since the debug port is not enabled in this example, the debug message is not printed
switch (LoRaBee.send(1, (uint8_t*)val, val_size)) // send(port, payload, length)
{
case NoError:
debugSerial.println("Successful transmission.");
break;
case NoResponse:
debugSerial.println("There was no response from the device.");
break;
case Timeout:
debugSerial.println("Connection timed-out. Check your serial connection to the device! Sleeping for 20sec.");
delay(20000);
break;
case PayloadSizeError:
debugSerial.println("The size of the payload is greater than allowed. Transmission failed!");
break;
case InternalError:
debugSerial.println("Oh No! This shouldn't happen. Something is really wrong! The program will reset the RN module.");
setupLoRa();
break;
case Busy:
debugSerial.println("The device is busy. Sleeping for 10 extra seconds.");
delay(10000);
break;
case NetworkFatalError:
debugSerial.println("There is a non-recoverable error with the network connection. The program will reset the RN module.");
setupLoRa();
break;
case NotConnected:
debugSerial.println("The device is not connected to the network. The program will reset the RN module.");
setupLoRa();
break;
case NoAcknowledgment:
debugSerial.println("There was no acknowledgment sent back!");
break;
default:
break;
}
sleep_LoRa();
}
//*****************************************************************************************
// Temperature Sensor functions
// output: 2-byte int centi-celsius (divide by 100 to type float for correct value).
void getTemperature()
{
int int_temp;
uint8_t negativeFlag;
float mVolts = (float)analogRead(TEMP_SENSOR)*3300.0/1024.0;
float temp = (mVolts-500.0)/10.0;
temp*=100;
if (temp<0) {
negativeFlag = 0x80;
} else {
negativeFlag = 0x00;
}
int_temp = abs((int)temp);
message[list_iter*2] = (int_temp >> 8) | negativeFlag;
message[list_iter*2+1] = int_temp & 0xFF;
}
// *****************************************************************************************
// RTC functions
// Initializes the RTC
void initRtc()
{
rtc.begin();
// Schedule the wake-up interrupt for every minute
// Alarm is triggered 1 cycle after match
rtc.setAlarmSeconds(59);
rtc.enableAlarm(RTCZero::MATCH_SS); // alarm every minute
// Attach handler
rtc.attachInterrupt(rtcAlarmHandler);
// This sets the time to 2000-01-01
rtc.setEpoch(0);
}
// Runs every minute by the rtc alarm.
void rtcAlarmHandler()
{
minuteFlag=true;
}
// Initializes the RTCTimer
void initRtcTimer()
{
debugSerial.println("init rtc timer");
timer.setNowCallback(getNow); // set how to get the current time
timer.allowMultipleEvents();
resetRtcTimerEvents();
}
void resetRtcTimerEvents()
{
// Schedule the default fix event (if applicable)
timer.every(record_every_x_minutes * 60, measureTemperature);
debugSerial.println("event set");
}
// Returns current date time in seconds since epoch
uint32_t getNow()
{
return rtc.getEpoch();
}
// Default event parameter
void measureTemperature(uint32_t now)
{
getTemperature();
list_iter++;
if (!(list_iter<records_to_send)) {
send_message((uint8_t*)&message,sizeof(message));
list_iter=0;
}
}
Reprogramming the board¶
Double press the reset button to manually put the device into bootloader mode.