Hello,
I’ve finally got my entire code working really well (almost) and I’ve now just been racking my brain trying to get the current consumption on my set up to be much lower than it’s current 50-60mA in “sleep” mode. I have a couple of components that I know will consume a considerable amount of current even in their most efficient settings; RAK12500 GNSS (9mA in low power mode), my own arduino-based sensor (5mA measured), and the rtc module which should use negligible current. From this I would expect my overall current consumption to be well less than 20mA. I’ve attached my code if anyone cares to take a look and see if I’ve missed anything obvious.
Cheers,
Tommi
#define DEVICE_ID 1
// ******** Library Includes ******** //
// LoRa
#include <Arduino.h>
#include <SPI.h>
#include <SX126x-RAK4630.h>
// GNSS (I2C)
#include <Wire.h>
#include <SparkFun_u-blox_GNSS_Arduino_Library.h>
long lat = 0;
long lon = 0;
// RTC
#include "Melopero_RV3028.h"
Melopero_RV3028 rtc;
// ******** Pin Definitions ******** //
#define RTCINTPIN WB_IO4 // RTC Interrupt input
#define ARD_INTERRUPT_PIN WB_IO6 // Connected to Arduino (INT0)
#define LED_CONN LED_BUILTIN
// ******** Battery Level ******** //
#define PIN_VBAT WB_A0
uint32_t vbat_pin = PIN_VBAT;
#define VBAT_MV_PER_LSB (0.73242188F) // 3.0V ADC range and 12 - bit ADC resolution = 3000mV / 4096
#define VBAT_DIVIDER_COMP (1.73) // Compensation factor for the VBAT divider, depend on the board
#define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB)
// ******** Lora Parameters ******** //
#define RF_FREQUENCY 916100000
#define TX_OUTPUT_POWER 22
#define LORA_BANDWIDTH 0
#define LORA_SPREADING_FACTOR 7
#define LORA_CODINGRATE 1
#define LORA_PREAMBLE_LENGTH 8
#define TX_TIMEOUT_VALUE 3000
#define WAKE_DELAY_MS 2000
#define RESPONSE_TIMEOUT_MS 2000
#define MESSAGE_GAP_MS 50
#define LORA_IQ_INVERSION_ON false
#define LORA_SYMBOL_TIMEOUT 0
#define LORA_FIX_LENGTH_PAYLOAD_ON false
// ******** Globals ******** //
SemaphoreHandle_t taskEvent = NULL;
SFE_UBLOX_GNSS gnss;
long lastSendTime = 0;
static RadioEvents_t RadioEvents;
static uint8_t txBuffer[64];
static uint8_t RcvBuffer[128];
bool ACK = false;
struct SlotEntry {
uint16_t id;
uint16_t TIMESLOT;
};
const SlotEntry slot_table[] = {
{1, 21},
{2, 23},
{3, 25},
{4, 27},
{5, 29}
};
const size_t num_entries = sizeof(slot_table) / sizeof(slot_table[0]);
uint16_t TIMESLOT = 0;
// ******** Function Prototypes ******** //
void OnTxDone(void);
void OnTxTimeout(void);
void send();
void sendString(const String &message);
String getJSON();
String requestSensorData();
void interruptCallback() {
xSemaphoreGiveFromISR(taskEvent, pdFALSE);
}
void setup() {
taskEvent = xSemaphoreCreateBinary(); // Init semaphore
xSemaphoreGive(taskEvent);
// Start Debug UART
Serial.begin(115200);
while (!Serial && millis() < 5000) delay(100);
// ***** Determine time slot for sending ***** //
for (size_t i = 0; i < num_entries; i++) {
if (slot_table[i].id == DEVICE_ID) {
TIMESLOT = slot_table[i].TIMESLOT;
break;
}
}
Serial.println("Lora Logger");
// ***** LoRa setup ***** //
lora_rak4630_init();
RadioEvents.TxDone = OnTxDone;
RadioEvents.TxTimeout = OnTxTimeout;
RadioEvents.RxDone = OnRxDone;
RadioEvents.RxTimeout = OnRxTimeout;
Radio.Init(&RadioEvents);
Radio.SetChannel(RF_FREQUENCY);
Radio.SetTxConfig(MODEM_LORA, TX_OUTPUT_POWER, 0, LORA_BANDWIDTH,
LORA_SPREADING_FACTOR, LORA_CODINGRATE, LORA_PREAMBLE_LENGTH,
false, true, 0, 0, false, TX_TIMEOUT_VALUE);
Radio.SetRxConfig(MODEM_LORA, LORA_BANDWIDTH, LORA_SPREADING_FACTOR,
LORA_CODINGRATE, 0, LORA_PREAMBLE_LENGTH,
LORA_SYMBOL_TIMEOUT, LORA_FIX_LENGTH_PAYLOAD_ON,
0, true, 0, 0, LORA_IQ_INVERSION_ON, true);
// ***** Arduino interrupt setup ***** //
pinMode(ARD_INTERRUPT_PIN, OUTPUT);
digitalWrite(ARD_INTERRUPT_PIN, HIGH); // idle high
// ***** Battery Voltage initialisations ***** //
analogReference(AR_INTERNAL_3_0); // Set the resolution to 12-bit (0..4095)
analogReadResolution(12);
delay(1);
readVBAT();
delay(50);
// Initialise GNSS
pinMode(WB_IO2, OUTPUT); // GNSS Wakeup pin
delay(10);
wakeGPS();
delay(10);
Wire.begin();
Serial.println("GPS Init (ZOE-M8Q via I2C)");
if (!gnss.begin()) {
Serial.println(F("GNSS not found! Halting."));
while (1);
}
gnss.setI2COutput(COM_TYPE_UBX);
gnss.saveConfigSelective(VAL_CFG_SUBSEC_IOPORT);
gnss.setUART1Output(0);
gnss.setUART2Output(0);
// RTC initialisation
rtc.initI2C();
rtc.set24HourMode();
setUTCTime();
Serial.println("RTC set to UTC time");
delay(50);
//Set RTC Alarm
rtc.setDateModeForAlarm(false);
rtc.enableAlarm(0, 0, TIMESLOT, false, false, true, true);
Serial.println("RTC alarm set.");
//RTC interrupt
pinMode(RTCINTPIN, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(RTCINTPIN), interruptCallback, CHANGE);
//Start Arduino Serial Comms
Serial1.begin(38400);
delay(50);
//Test Gateway message
String declare = "\n\"ID\": " + String(DEVICE_ID);
sendString(declare);
delay(100);
sendString("\nDevice initialised Successfully\n");
Serial.println("Setup Complete");
//Turn of Radio
Radio.Sleep();
delay(2000);
//Put to sleep
xSemaphoreTake(taskEvent, 10);
}
void loop() {
if (xSemaphoreTake(taskEvent, portMAX_DELAY) == pdTRUE) {
rtc.clearInterruptFlags();
//wakeGPS(); // wake and give time to initialise before using
const int maxRetries = 5;
if (millis() - lastSendTime > 1000) {
lastSendTime = millis();
String json = getJSON();
for (int attempt = 1; attempt <= maxRetries; attempt++) {
Serial.printf("Sending attempt %d", attempt);
ACK = false; // Clear flag before send
sendString(json);
Radio.Rx(5000); // Wait for ACK
unsigned long start = millis();
while ((millis() - start) < 5000 && !ACK) {
delay(10);
}
if (ACK) {
Serial.println("message delivered");
break;
}
Serial.println("No ACK, retrying...");
delay(357); // Short pause before retry
}
}
if(!ACK){
Serial.println("message delivery failed");
}
ACK = false;
if(rtc.getDate()==1 && rtc.getHour()==1){
setUTCTime();
delay(100);
}
//gnss.powerOff(3540000); // power off GPS module for 59 minutes
Serial.printf("rtc: %02d:%02d:%02d\n", rtc.getHour(), rtc.getMinute(), rtc.getSecond());
TIMESLOT= TIMESLOT+30;
if(TIMESLOT>=60){
TIMESLOT-=60;
}
rtc.enableAlarm(0, 0, TIMESLOT, false, false, true, true);
digitalWrite(LED_CONN, HIGH);
delay(500);
digitalWrite(LED_CONN, LOW);
Radio.Sleep();
delay(50);
xSemaphoreTake(taskEvent, 10);
}
}
// ****** Radio Functions ***** //
void OnTxDone() {
Serial.println("LoRa TX complete");
}
void OnTxTimeout() {
Serial.println("LoRa TX timeout.");
}
void OnRxDone(uint8_t *payload, uint16_t size, int16_t rssi, int8_t snr) {
memcpy(RcvBuffer, payload, size);
RcvBuffer[size] = '\0';
String received = String((char *)RcvBuffer);
received.trim(); // Remove any trailing \r, \n, or spaces
String expectedACK = "ACK" + String(DEVICE_ID);
if (received == expectedACK) {
ACK = true;
//Serial.println("Correct ACK received.");
} else {
ACK = false;
}
}
void OnRxTimeout(){
//Serial.println("onRxTimeout");
}
void OnRxError(void){
Serial.println("OnRxError");
}
void sendString(const String &message) {
const int MAX_CHUNK = 60;
int len = message.length();
for (int i = 0; i < len; i += MAX_CHUNK) {
int chunkLen = min(MAX_CHUNK, len - i);
Radio.Send((uint8_t*)message.c_str() + i, chunkLen);
delay(300); // gap between chunks
}
}
// ***** Get and compile data ***** //
String getJSON() {
char timeStr[32];
sprintf(timeStr, "\"%04d-%02d-%02d %02d:%02d:%02d\"",
rtc.getYear(), rtc.getMonth(), rtc.getDate(),
rtc.getHour(), rtc.getMinute(), rtc.getSecond());
String data = requestSensorData();
int batPercent = batteryLevel();
delay(50);
return "{\n\"ID\":" + String(DEVICE_ID) + ",\n\"UTC\":" + timeStr +
",\n\"lat\":" + String(lat / 1e7, 7) +
",\n\"lon\":" + String(lon / 1e7, 7) +
",\n\"RAKBat\":"+ String(batPercent) + ",\n" + data + "}";
}
String requestSensorData() {
const int maxRetries = 3;
const int retryDelayMs = 1000;
String receivedMessage = "";
for (int attempt = 1; attempt <= maxRetries; attempt++) {
// Wake Arduino via interrupt
digitalWrite(ARD_INTERRUPT_PIN, LOW);
delay(5);
digitalWrite(ARD_INTERRUPT_PIN, HIGH);
delay(WAKE_DELAY_MS); // Let Arduino wake
// Wait for data with timeout
unsigned long start = millis();
while (!Serial1.available() && millis() - start < RESPONSE_TIMEOUT_MS);
if (Serial1.available()) {
unsigned long lastByteTime = millis();
while (millis() - lastByteTime < MESSAGE_GAP_MS) {
if (Serial1.available()) {
char c = Serial1.read();
receivedMessage += c;
lastByteTime = millis(); // Reset timer on each byte
}
}
// Look for valid JSON message
int jsonStart = receivedMessage.indexOf('{');
int jsonEnd = receivedMessage.indexOf('}');
if (jsonStart != -1 && jsonEnd != -1 && jsonEnd > jsonStart) {
return receivedMessage.substring(jsonStart + 1, jsonEnd); // exclude outer {}
}
Serial.println(receivedMessage);
Serial.println("Invalid format received. Retrying...");
} else {
Serial.printf("Attempt %d: No response from Arduino.\n", attempt);
}
delay(retryDelayMs);
}
// All attempts failed
Serial.println("All sensor data retries failed.");
return "\n\"run\":null,\n\"watertemp\":null,\n\"airtemp\":null,\n\"waterheight\":null,\n\"batvol\":null,\n\"valid\":false";
}
// ***** Wake up GPS module ***** //
void wakeGPS(){
// GPS reset and init
digitalWrite(WB_IO2, 0); delay(1000);
digitalWrite(WB_IO2, 1); delay(1000);
}
// ***** Set UTC time from GPS to RTC ***** //
void setUTCTime(){
int powermode = gnss.getPowerSaveMode();
if(powermode == 1 ){
gnss.powerSaveMode(false);
}
//wait for valid time
while (gnss.getTimeValid() == false) {
Serial.println("Waiting for GNSS time...");
delay(4000);
}
// Set RTC time to GPS time
rtc.setTime(
gnss.getYear(), gnss.getMonth(), gnss.getTimeOfWeek(),
gnss.getDay(), gnss.getHour(), gnss.getMinute(), gnss.getSecond()
);
Serial.printf("rtc: %02d:%02d:%02d\n", rtc.getHour(), rtc.getMinute(), rtc.getSecond());
//wait for good GPS fix
while(gnss.getFixType()!=3){
Serial.println("getting GPS fix");
delay(4000);
}
//Set coordinates
lat = gnss.getLatitude();
lon = gnss.getLongitude();
delay(1000);
gnss.powerSaveMode();
if(gnss.getPowerSaveMode()== 1){
Serial.println("GNSS Low Power Mode Enabled");
}
else{
Serial.println("GNSS Low Power Mode Failed");
}
}
// ******* Battery Functions ******* //
float readVBAT(void){
float raw;
// Get the raw 12-bit, 0..3000mV ADC value
raw = analogRead(vbat_pin);
return raw * REAL_VBAT_MV_PER_LSB;
}
int batteryLevel(void){
float raw;
float mvolts;
// Get the raw 12-bit, 0..3000mV ADC value
raw = analogRead(vbat_pin);
mvolts = raw * REAL_VBAT_MV_PER_LSB;
if (mvolts < 3300)
return 0;
if (mvolts < 3600){
mvolts -= 3300;
return mvolts / 30;
}
mvolts -= 3600;
return 10 + (mvolts * 0.15F); // thats mvolts /6.66666666
}
