Excessive current consumption

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
}

Hi,
Would just like to add that I have made a significant breakthrough by using:

digitalWrite(WB_IO2, 0); delay(1000);

after I have set the GPS position. I assume the IO2 pin is used as some sort of enable?
With this in place, I’m able to get my sleeping current below 9mA which is much more acceptable.
I welcome any critiques or pointers on my code otherwise. I am not an experienced coder and would love some feedback.

Cheers,
Tommi

IO2 is designed to cutoff supply of most of the sensors and modules that is mounted on the sensor slots of WisBlock Baseboards.

You can see it in the datasheet of RAK19003. That’s why you will see that WB_IO2 is always set to high on module quick start guide.

image

1 Like

Thanks,
Am I right in understanding that 3V3 is not toggled at the same time as 3v3_S? i,.e, I can still use the rtc module as normal with IO2 set to low.

Thanks,
Tommi

If you mean RAK12002 RTC module, that’s correct. It will not be affect since it is getting supply directly from the 3V3 and not 3V3_S.

1 Like

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.