Struggling to nail down power draw issues with an Arduino UNO automated feeding trough project

I am building a 2ft (6.56m) x 4ft (13.12m) feeding trough made of HDPE plastic. It's lid is very light, and it has a 12v linear actuator that is pushing it from 10 degrees "closed" to 90 degrees "open".

It is designed to open at 7:00 am my local time, and close at 5:30pm my local time. (Thereabouts. I tweak the time occasionally).

It draws far more power than it has any business drawing. Around 0.12 amps. This is with sleep logic that I have to try to manage power draw. There is a 15ah battery and this should last weeks or months with how much power the system should need. But it would drain in around 3 days at this rate.

It is set up with the following key components:
Arduino Uno REV3
BTS7960
LM2596 Multi-Channel Buck Converter w/ Adj/5v/5v/3.3v
Button module
DS3231 AT24C32 IIC RTC Clock w/ LIR2032 battery installed
Terminal block for splicing button wires into dupont connections
12V 15Ah LiFePO4 Battery - Bioenno Power

The battery has an inline fuse attachment. It then has a voltage meter for tracking the voltage and amp usage rate at any given reading + overall usage since last plugged into the battery. The battery splits on a WAGO connector to go directly to the motor driver board directly with 16g wires in B+/B-, and into the buck converter.

The buck converter plugs into the Arduino via the ADJ outputs, which are set to approximately 9v, and plug into the Vin on the Arduino. I did this instead of the 5v as I need to be able to plug in my computer to the USB port without causing problems, and it was my understanding that the 5v would potentially cause problems when power also comes along the USB cord.

I had it powered this way previously, and didn't notice as large of a power draw, but this was also before a buck converter was added, or an RTC, and I was using a small 2ah test battery. The whole system looked different then.

The motor driver is connected via these pins to the Arduino. It is grounded with an extra Dupont ground wire to the buck converter:
RPWM = 9;
LPWM = 10;
REN = 6;
LEN = 7;

And this was, again, powered directly via the 16g battery power coming off of the WAGO splicer.

Then, I have a button, which will allow someone to override the current open/close of the trough lid with a button. It is currently plugged into the D3 port, though I also had it plugged into A4 when I didn't have wakeup/sleep logic in my code. It is connected to an Arduino ground and this D3 port. The button is connected to the Arduino via a terminal block that takes the ~22g wire and converts it into Dupont wires.

Also, the RTC module is connected to A4/A5 and 3.3v and GND on the Arduino. I've had some problems with it. It still does not seem to properly maintain the time when the system is powered off. I ordered a new, better RTC module as the one I had was very cheap. Will see if that helps. The rechargeable coin batteries are definitely good though, and what it calls for.

And of course, the motor driver then connects M+/M- to my 12v linear actuator (8 inch). An important note here! I step down the wire gauge from the battery's 12g wire, to a 16g wire, using a WAGO lever connector. It is impossible to fit the 12g wire tip into the corral for the B+/B- in the motor driver. It forced me to do a wire gauge step down to fit. I made the 16g portion as short as possible/about 4 inches.

Here is my entire code. I have never programmed in C++ before, so I apologize for any horrid code. I am a C# and Js/Nodejs guy.

Would an Arduino Mini be a reasonable step down to save on energy cost?

#include <Wire.h>
#include "RTClib.h"
#include <LowPower.h>

#define DEBUG_SERIAL 1         // Set to 0 for field deployment
#define SET_RTC_FROM_COMPILE 0 // Set to 0 when you do NOT want to change the time keeping modules time reference

// Time keeping when disconnected from development machine.
RTC_DS3231 rtc;
DateTime now;
DateTime sunrise;
DateTime sunset;
const int sunriseHourLocalUtc = 7;
const int sunriseMinuteLocalUtc = 0;
const int sunsetHourLocalUtc  = 17;
const int sunsetMinuteLocalUtc = 20;

// How long to sleep after doing the scheduled action
const uint32_t afterSunriseSleepSeconds = 35280UL; // 9.8 hours
const uint32_t afterSunsetSleepSeconds  = 49680UL; // 13.8 hours

// Pins BTS7960 (Motor Driver)
const int RPWM = 9;
const int LPWM = 10;
const int REN  = 6;
const int LEN  = 7;

// Volatile awake D3 Arduino pin for movement button.
const int openCloseButton = 3;

// Motion timings
const unsigned long actuatorExtendRetractTime   = 15000UL;   // 15 seconds
const unsigned long overrideTimeoutMs           = 1800000UL; // 30 minutes

// Ignore button noise for first 2 minutes after boot
const unsigned long startupIgnoreButtonPressTimeout = 120000UL;

// ISR debounce (ms)
const unsigned long isrDebounceMs = 250UL;

// -----------------------------
// Feed Trough System States
// -----------------------------
unsigned long lastOverridenTimestamp = 0;
uint32_t currentSleepCycleTimeRemaining = 0;

bool isOpen = false;
bool isDaytimeAndLidShouldBeOpen = false;
bool isOpenStateOverriden = false;
bool needsInitializing = true;
bool actuatorIsActive = false;

volatile bool wakeEvent = false; // Set by ISR ALWAYS (means "something happened, wake up").
volatile unsigned long lastInterruptTime = 0; // Last time button reports press (may be false report in first two minutes).
bool buttonPressed = false; // Latched by loop when it decides a wake event is a valid press.

unsigned long currentMillis = 0;


void setup() {
#if DEBUG_SERIAL
  Serial.begin(9600);
  while (!Serial) {}
  Serial.print("\nReset cause (MCUSR): ");
  Serial.println(MCUSR, BIN);
  MCUSR = 0;
#endif

  Wire.begin();

  if (!rtc.begin()) {
#if DEBUG_SERIAL
    Serial.println("Couldn't find RTC");
#endif
  }

#if SET_RTC_FROM_COMPILE
    // One-time convenience: set RTC from compile time on purpose.
    rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
  #if DEBUG_SERIAL
    Serial.println("RTC adjusted from compile time (SET_RTC_FROM_COMPILE=1).");
  #endif
  #else
    // Normal operation: do NOT adjust every boot.
    if (rtc.lostPower()) {
  #if DEBUG_SERIAL
      Serial.println("RTC lost power (OSF set). Clock needs to be set once.");
  #endif
      // rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
    }
#endif

  pinMode(RPWM, OUTPUT);
  pinMode(LPWM, OUTPUT);
  pinMode(REN, OUTPUT);
  pinMode(LEN, OUTPUT);

  pinMode(openCloseButton, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(openCloseButton), buttonISR, FALLING);

  digitalWrite(REN, HIGH);
  digitalWrite(LEN, HIGH);

  stopActuator();
}

void loop() {
  currentMillis = millis();
  now = rtc.now();
  // TODO: Use timing module (when it works reliably) to determine "next day", and set sunrise and sunset just once a day.
  sunrise = DateTime(now.year(), now.month(), now.day(), sunriseHourLocalUtc, sunriseMinuteLocalUtc, 0);
  sunset  = DateTime(now.year(), now.month(), now.day(), sunsetHourLocalUtc,  sunsetMinuteLocalUtc,  0);
  isDaytimeAndLidShouldBeOpen = (now >= sunrise && now < sunset);

  // Convert ISR wake into a "real" press if appropriate
  if (wakeEvent) {
    noInterrupts();
    wakeEvent = false;
    interrupts();

    // Ignore the first N ms after boot to avoid bogus wake noise
    if (currentMillis >= startupIgnoreButtonPressTimeout) {
      // Only accept a new press if button is currently released
      // (prevents "holding down for too long" from retriggering)
      if (digitalRead(openCloseButton) == LOW)
        buttonPressed = true;
    }
  }

  if (needsInitializing) {
#if DEBUG_SERIAL
    Serial.println("\nInitializing: forcing lid CLOSED at startup.");
#endif
    needsInitializing = false;
    isOpenStateOverriden = false;
    buttonPressed = false;

    // Force known state: close lid
    moveClose();
  }

  // If actuator is moving and a button press comes in, ignore it for now
  if (actuatorIsActive)
    buttonPressed = false;

  const bool forceCancelOverride = isOpenStateOverriden &&
      (currentMillis - lastOverridenTimestamp > overrideTimeoutMs);

  // Manual button OR forced override timeout
  if (buttonPressed || forceCancelOverride)
  {
    buttonPressed = false;

    // If override timeout happened, we are cancelling override
    if (forceCancelOverride) {
  #if DEBUG_SERIAL
      Serial.println("\nOverride timeout: returning lid to intended position.");
  #endif
      isOpenStateOverriden = false;
    } else {
      // Toggle override state on manual press
      isOpenStateOverriden = !isOpenStateOverriden;
      if (isOpenStateOverriden) {
        lastOverridenTimestamp = currentMillis;
  #if DEBUG_SERIAL
        Serial.println("\nManual override ON.");
  #endif
        toggleLid();
      } else {
  #if DEBUG_SERIAL
        Serial.println("\nManual override OFF.");
  #endif
      }
    }
  }

  // Scheduled behavior only when NOT overridden
  if (!isOpenStateOverriden && isDaytimeAndLidShouldBeOpen && !isOpen) {
#if DEBUG_SERIAL
    Serial.println("\nScheduled: OPEN lid.");
#endif
    moveOpen();
    Serial.println("\nScheduled: OPEN lid.");
    sleepForSeconds(afterSunriseSleepSeconds);
  } else if (!isOpenStateOverriden && !isDaytimeAndLidShouldBeOpen && isOpen) {
    moveClose();
    sleepForSeconds(afterSunsetSleepSeconds);
  }

  delay(25);
}

// IMPORTANT: ISR should be tiny. No Serial. No long logic.
void buttonISR() {
  unsigned long ms = millis();
  if (ms - lastInterruptTime < isrDebounceMs) 
    return;
  lastInterruptTime = ms;

  // Always set wake event so sleep can break
  wakeEvent = true;
}

void sleepForSeconds(uint32_t seconds) {
  if (seconds > 0) 
    currentSleepCycleTimeRemaining = seconds;

  wakeEvent = false;
#if DEBUG_SERIAL
    Serial.print("Sleeping for seconds: ");
    Serial.println(currentSleepCycleTimeRemaining);
#endif
  while (currentSleepCycleTimeRemaining >= 2) {
    // If anything woke us, stop sleeping immediately
    if (wakeEvent) 
      break;

    LowPower.powerDown(SLEEP_2S, ADC_OFF, BOD_OFF);

    if (wakeEvent) 
      break;

    currentSleepCycleTimeRemaining -= 2;
  }
}

// -----------------------------
// ACTUATOR CONTROL
// -----------------------------
void toggleLid() {
  if (isOpen) 
    moveClose();
  else 
    moveOpen();
  sleepForSeconds(0);
}

void moveClose() {
  actuatorIsActive = true;

  analogWrite(RPWM, 255);
  analogWrite(LPWM, 0);

  // Keep state consistent
  isOpen = false;

#if DEBUG_SERIAL
  printVariableStates();
#endif

  delay(actuatorExtendRetractTime);
  stopActuator();
}

void moveOpen() {
  actuatorIsActive = true;

  analogWrite(RPWM, 0);
  analogWrite(LPWM, 255);

  isOpen = true;

#if DEBUG_SERIAL
  printVariableStates();
#endif

  delay(actuatorExtendRetractTime);
  stopActuator();
}

/*
 Actuator has internal limit switches, but these can fail - especially with cheaper units. 
 We minimize chance of motor burnout by explicitly calling this about 2 seconds after open/close time should take.
 Also, debris falling on lid, stopping extension or slowing it - we want it to give up if it meets resistance.
*/
void stopActuator() {
  analogWrite(RPWM, 0);
  analogWrite(LPWM, 0);
  actuatorIsActive = false;

#if DEBUG_SERIAL
  Serial.println("Motor STOP.");
#endif
}

void printVariableStates() {
  Serial.println("\n----------------------");
  Serial.println("DEBUG STATE");

  Serial.print("Now: ");
  Serial.println(now.timestamp());

  Serial.print("Sunrise: ");
  Serial.println(sunrise.timestamp());

  Serial.print("Sunset: ");
  Serial.println(sunset.timestamp());

  Serial.print("isDaytimeAndLidShouldBeOpen: ");
  Serial.println(isDaytimeAndLidShouldBeOpen);

  Serial.print("isOpen: ");
  Serial.println(isOpen);

  Serial.print("isOpenStateOverriden: ");
  Serial.println(isOpenStateOverriden);

  Serial.print("lastOverridenTimestamp(ms): ");
  Serial.println(lastOverridenTimestamp);
  Serial.println("----------------------");
}

Welcome to the forum

Why are the calls to the stopActuator() function commented out in the code ?

Sorry, I forgot to undo that! That was part of testing I was doing recently around the Serial Monitor seeming to stop output, when it definitely had more output to show. Commenting out stopActuator() didn't end up affecting that. But it was the last thing called before Serial communication ceased. But then when it had more logs to post later, the logs that should have shown earlier also appear at the top of the message stack. So I suspected it was an Arduino IDE issue or something.

Long story short, that should not be commented out. Editing it now. Don't worry, it has an internal limit switch. I added these as backup stoppage in case something goes wrong with the actuator limit switches, to prevent a motor burnout.

Also, thanks for the welcome!

Schematics and close data to the electricity consumers would help. A lot larger auto motor batteries drain out during some 12 hours if the wrong consumer is left on.

Sure! I will get all the schematics for those asap. (Hopefully this evening or early tomorrow).

An estimation of the number of operations per day and night tells a lot about the battery capacity needed.

An Uno R3 without sleep code draws about 50mA@5volt, independent of work.
When powered with a 12>5 buck converter, that should be about 25mA from a 12volt supply.
Most parts on an Uno R3 can't be put to sleep, so sleep code is sort of useless.

You should first investigate which part is drawing that 120mA.

Wrong Arduino and wrong buck converter choice for low power.
A proMini 3.3volt and a micro-power buck converter to 3V3 could drop battery drain to <1mA.

A solar library could give you sunrise/sunset from the RTC's UTC and your lat/long.
No more time adjustments for the feeder throughout the year.
Leo..

The most important schematic is the annotated schematic showing exactly how you built your project. Links to technical information on the hardware items will be a big step in getting the information you need.
Arduino Pro Mini Out of box:

  • 1–5 mA (too high)

After removing:

  • Power LED
  • Onboard regulator

Sleep current:

  • ~1–5 uA typical

This is probably your best bet. Take a look at the Analog Devices LT8609S (buck, up to 42V in) It gives you very low quiescent current (uA-range)

Thanks! How do I connect all the duponts I need to that lower power converter your linked? I want to order it asap, but I also want to know that it has the outputs I need.

I need to connect several grounds to the system on the buck converter. Right now I have the power for the arduino board, along with its ground to battery. But then I have an additional ground connected to the current buck converter, as there isn't enough gnd connectors on the Arduino board that I have.

And do you have a link to a specific proMini 3.3volt you recommend. I am getting some... less obviously the "right board" results when I search for that versus the UNO.

I intended to do a solar initially, but this trough will very likely be in shade, and it will be in an area where brush will grow around it, potentially blocking good solar detection - along with debris potentially falling onto the sensor and entirely blocking it. Which is why I chose a set open/close time.

(Schematics coming shortly)

The ONLY things it needs to do are open the lid once a day, and close it once a day - and be able to wake up and do the lid open/close on demand when the button is pressed (maybe twice a month).

For a start you don't use Dupont connectors for a permanent installation

I cannot for the life of me nail down which brand this is. EC Buying is probably just a Chinese company that sells this, and doesn't make this (for all I know).
Link to Buck Converter I purchased on Amazon

Here is the linear actuator I am using

Here are the motor driver schematics

I am replacing the RTC module shortly with an Adafruit DS3231 Precision RTC.

Here is a link to the batteries for the RTC module

Here is my REV3 board's schematics

This is my Bioenno Power battery

What do I do then? I am completely new to any of this. I've never used Arduino's or built anything like this before. I am researching online and have gotten to where I am purely based on things I've read online.

For a permanent installation you would normally use soldered connections. If you need to make multiple connections to say GND then you can use perf board or more conveniently strip board (aka Veroboard)

Measure the current and time for one cycle, open and close. Calculate the amount of mAh used. Multiply with the number of operations You need. That tells what capacity the battery needs to have.

Have respect for numbers! They quickly sums up!

The A4 / A5 pins are pulled up to 5V and the RTC I/O pins are 3V? How much current is flowing backwards through the RTC's protection diodes? Post a link to the RTC module's datasheet.

That's sort of the problem. I bought a seemingly gibberish Amazon brand RTC.

No idea how to find its data sheet (beyond Amazon page data), much less schematics.

Hopefully that link provides something useful. But I just purchased a new RTC module a few days ago that I am replacing it with.

It's unlikely that a different RTC is going to fix that 120mA draw.
Better track that down first by measuring the supply current of each individual item.

If the feeder is within WiFi range, then I would have choosen for a WiFi board, with time extracted from the internet (no RTC). That also opens the possibility to control things with a smartphone.
Leo..

Thanks, I am replacing the RTC module because it's not functioning properly. Not due to the draw issue - or not directly due to that. If it had somehow fixed it, that'd be nice though.

But this will be on a large farm, nowhere near wifi. And I don't want to set up some sort of wifi system like the ones that "trail cams" have.

Do you know how to solder?
If not, it may not be possible to do what you want with premade off-the-shelf modules.