Unexpected activation after long (months) run

Hey everyone, looking for a little help with my first project. I have a linear actuator that goes up and down with the push of a single button. This code has been running for over two years and the same pattern occurs. Everything runs as expected for months but then the actuator starts moving without the button being pressed. It will then activate (up/down) somewhat randomly every 1-4 hrs. I'm assuming this is code/memory/overflow related as it goes away if I power cycle the arduino. BTW, I started with a simple two button code and tried state change detection but ended up with the code below.

Code:

// constants won't change. They're used here to set pin numbers:
int RPWM = 10;  //connect Arduino pin 10 to IBT-2 pin RPWM
int LPWM = 11;  //connect Arduino pin 11 to IBT-2 pin LPWM
int buttonPin = 2;  // the number of the pushbutton pin

// variables will change:
byte Speed = 0; // Intialize Varaible for the speed of the motor (0-255);
byte Move = 0; // Intialize Varaible for the speed of the motor (0-255);
int buttonState = 0;  // variable for reading the pushbutton status
int direction = 1;

void setup() {
 // setup code runs once:
pinMode(10, OUTPUT); // Configure pin 10 as an Output
pinMode(11, OUTPUT); // Configure pin 11 as an Output
pinMode(buttonPin, INPUT_PULLUP); // Configure button
}

void loop() {
//------------------------------------------------------------------------
//------------------------------------------------------------------------
  // Check For Button Push
  buttonState = digitalRead(buttonPin);
  if (buttonState == LOW) {
    Move = 1; //set actuator to move state
  }
    else { //if no button is pushed, remain stationary
    Move = 0;
  }

 // Extend  Actuator ------------------------------------------------------
  if (Move == 1 && direction == 1) {
 
  // fade in from min to max in increments of 5 points:
  for (int fadeValue = 100; fadeValue <= 255; fadeValue += 5) {// sets the value (range from 0 to 255):
    analogWrite(10, fadeValue);
    analogWrite(11, 0);
    // wait for 85 milliseconds to see the dimming effect
    delay(85);
  }

  Move = 0; //Actuator reaches end of extension
  
      // Stop Actuator
  analogWrite(RPWM, 0);
  analogWrite(LPWM, 0);
  direction = 2; // Set to retract
  }
    
    else{ //if no button is pushed, remain stationary
      analogWrite(RPWM, 0);
      analogWrite(LPWM, 0);
      Move = 0;
  }

//-----------------------------------------------------------------------------------------
//-----------------------------------------------------------------------------------------
  // Check For Button Push
  buttonState = digitalRead(buttonPin);
  if (buttonState == LOW) {
    Move = 1; 
  }
    else { //if no button is pushed, remain stationary
    Move = 0;
  }

  // Retract  Actuator ----------------------------------------------------------------------
  if (Move == 1 && direction == 2) {

  // fade out from max to min in increments of 5 points:
  for (int fadeValue = 50; fadeValue <= 255; fadeValue += 5) {// sets the value (range from 0 to 255):
    // sets the value (range from 0 to 255):
    analogWrite(10, 0);
    analogWrite(11, fadeValue);
    // wait for 84 milliseconds to see the dimming effect
    delay(84);
  }
  
  Move = 0;
    // Stop Actuator
  analogWrite(RPWM, 0);
  analogWrite(LPWM, 0);
    direction = 1; // Set back to extend
  }
    
    else{ //if no button is pushed, remain stationary
      analogWrite(RPWM, 0);
      analogWrite(LPWM, 0);
      Move = 0;
  }

}

I see no reasons for overflow problems.

This will not solve your problems, but I recommend to declare constants as const...

And things that can only be high/low or true/false as boolean...

Your comments are misleading. Move is a state variable, it does not hold a 0-255 value...

  // Check For Button Push
  buttonState = digitalRead(buttonPin);
  if (buttonState == LOW) {
    Move = 1; 
  }
    else { //if no button is pushed, remain stationary
    Move = 0;
  }

  • Highly recommend that you revisit the method you are using to read a switch input.

  • Adopt a proper de-bounce method for handling switches.
    Look at input changes in state rather than a input level.
    In fact, look for sequential changes that are to the same level (say 10 times in a row) before you validate a change in state.

A “class” to manage switches including proper de-bounce to ignore noise.


//================================================^================================================
//                                  c l a s s    m a k e I n p u t
//================================================^================================================
//
//a class to define "Input" objects for switches and/or sensors
 
//================================================
class makeInput
{
#define NOTvalidated       0
#define VALIDATED          1
#define NOchange           2
 
  private:
 
  public:
 
    static byte s_filter;
    //say the above validating "s_filter" variable is set to 10
    //if we scan "inputs" every 5ms
    //i.e. we sample our inputs every 5ms looking for a change in state.
    //5ms * 10 = 50ms is needed to validate a switch change in state.
    //i.e. a switch change in state is valid "only after" 10 identical changes are detected.
    //This technique is used to filter out EMI (spikes), noise, etc.
    //i.e. we ignore switch changes in state that are less than 50ms.
   
    byte lastState;            //the state the input was last in, HIGH/LOW
    unsigned long switchTime;  //the time the switch was closed
    byte counter;              //a counter used for validating a switch change in state
 
    //these "members" are needed to define an "Input"
    byte pin;                  //the digital input pin number, any valid input pin
    byte closedLevel;          //the level on the pin when the switch is closed, HIGH/LOW
                               //HIGH  +5V---[Switch]---PIN---[10k]---GND
                               //LOW   +5V---[10k]---PIN---[Switch]---GND
                               //LOW   +5V---[Internal 50k]---PIN---[Switch]---GND
 
    //================================================
    //constructor with parameters
    makeInput(byte _pin, byte _closedLevel)
    {
      pin = _pin;
      closedLevel = _closedLevel;
     
      lastState = digitalRead(pin);
      switchTime = 0;
      counter = 0;
 
      //do we need a pull_up needed ?
      if (closedLevel == LOW)
      {
        pinMode(pin, INPUT_PULLUP);
      }
    }
 
    //================================================
    //condition returned: NOTvalidated (0), VALIDATED (1) or NOchange (2)
    //check to see if the input object has had a valid state change
    byte validChange()
    {
      byte currentState = digitalRead(pin);
 
      //===================================
      //has there been an input change in state ?
      if (lastState != currentState)
      {
        //we have had another similar change in state
        counter++;
 
        //is the "change in state" stable ?
        if (counter >= s_filter)
        {
          //an input change has been validated
          //get ready for the next scanning sequence
          counter = 0;
 
          //update to this new state
          lastState = currentState;
 
          //is this switch closed ?
          if (currentState == closedLevel)
          {
            //capture the time when the switch closed
            switchTime = millis();
          }
 
          return VALIDATED;
        }
 
        return NOTvalidated;
      }
 
      //===================================
      //there has not been an input change in state
      counter = 0;
 
      return NOchange;
 
    } //END of   validChange()
 
}; //END of   class makeInput
 
//================================================
//a change in state is confirmed/validated when 10 identical state changes in a row are seen
byte makeInput::s_filter = 10;

An example:


//INPUTS
//================================================
//
//See the "makeInput" Class.
//We have access to:
//object.validChange() - checks for a change in state  NOTvalidated(0), VALIDATED(1) or NOchange(2)
//object.pin           - input hardware pin number                  a valid input pin #
//object.lastState     - the state the input was/is in              HIGH/LOW
//object.closedLevel   - the pin level when the switch is closed    HIGH/LOW
//object.switchTime    - the millis() value when the switch closed
//
//Note: .closedLevel refers to the voltage on the input when the switch is closed,
//HIGH  +5V---[Switch]---PIN---[10k]---GND
//LOW   +5V---[10k]---PIN---[Switch]---GND
//LOW   +5V---[Internal 50k]---PIN---[Switch]---GND
 
 
//============                  GPIO 2
makeInput mySwitch1 =
{
  //.pin, .closedLevel
  2, LOW
};


. . .

Call the **checkSwitches()** function every 5ms to get 50ms (10 samples) worth of de-bounce testing.
. . .

//                                   c h e c k S w i t c h e s ( )
//================================================^================================================
//
//We have access to,where "object" is the name of our switch:
//object.validChange()    - checks to see if there was a valid state change
//object.pin              - input hardware pin number
//object.lastState        - the state the input was/is in
//object.closedLevel      - the level on the pin when the switch is closed
//object.switchTime       - the millis() value when the switch closes
 
void checkSwitches()
{
  //========================================================================  mySwitch1
  //was there a valid change in state for this switch input ?
  //condition returned: NOTvalidated (0), VALIDATED (1) or NOchange (2)
  if (mySwitch1.validChange() == VALIDATED)
  {
    //========================
    //was this switch closed ?
    if (mySwitch1.lastState == mySwitch1.closedLevel)
    {
      Serial.print("Switch went closed.\n");
     
      //Do something . . .
     
    }
 
    //========================
    //this switch opened ?
    else
    {
      Serial.print("The switch was closed for ");
      Serial.print(millis() - mySwitch1.switchTime);
      Serial.println("ms.");
 
      //================================================  Short Push
      //was this a short push ?
      if (millis() - mySwitch1.switchTime <= shortPushTime)
      {
        //do something
        Serial.println("Short Push\n");
      }
 
      //================================================  Long Push
      //was this a long push ?
      else if (millis() - mySwitch1.switchTime >= longPushTime)
      {
        //do something
        Serial.println("Long Push\n");
      }
 
      //================================================  Regular Push
      //this was a regular push
      else
      {
        //do something
        Serial.println("Regular Push\n");
      }
    }
 
  } //END of mySwitch1

} //END of   checkSwitches()

Does this code produce the anomaly you describe?
A debounce strategy definitely needs to be added (a simple 100nF capacitor between the input and GND might be enough). But that still doesn’t explain why it worked fine for months.

Which Arduino are you using?
How is everything powered?
How is the actuator controlled?
Are all connections soldered?
Is this device indoors?

Since the problem is probably not in your code, it would be a good idea to post a photo of your setup.

if you are using a mechanical switch which may have contact bounce problems maybe worth moving to a hall effect switch

You mean it goes away for months, and then after months it starts false-triggering every few hours?

How many cycles of several months have you seen this behaviour repeating?

It is hard to imagine how lack of debouncing could produce this strange behaviour.

There's not much to go wrong with your code - no dynamic memory usage that could slowly fill the memory, for example.

I could suggest various changes to the code, but they would only be to remove the repetition and make the code more readable. It would take months before we knew if it had make any difference.

Oh, please Auto-Format the code before you post it again.

Hi @davissc44 ,

as already posted by other members there is no obvious reason in the code for a misbehavior of the sketch after running properly for a long time. It might be necessary to post more information about your hardware setup ....

I ported your application to Wokwi (using leds at the PWM pins)

Original @ https://wokwi.com/projects/432557652667267073

and created (quickly) a modified version using constants and a proper debouncing of the button.

Modified @ https://wokwi.com/projects/432557680640130049

Modified sketch
/*
  Forum: https://forum.arduino.cc/t/unexpected-activation-after-long-months-run/1385981
  Wokwi: https://wokwi.com/projects/432557680640130049
  Original @ Wokwi: https://wokwi.com/projects/432557652667267073

  ec2021
  2025/06/01

*/

constexpr byte RPWM {10};  // Arduino pin to IBT-2 pin RPWM
constexpr byte LPWM {11};  // Arduino pin to IBT-2 pin LPWM
constexpr byte buttonPin {2};  // pushbutton pin
constexpr int rpwmFadeStart {100};
constexpr int lpwmFadeStart {50};
constexpr int fadeEnd {255};
constexpr unsigned long rpwmFadeDelay {85};
constexpr unsigned long lpwmFadeDelay {84};

struct moveStruct {
  byte fadePin;
  byte zeroPin;
  int Start;
  unsigned long Delay;
};

moveStruct movement[2] = {
  {RPWM, LPWM, rpwmFadeStart, rpwmFadeDelay},
  {LPWM, RPWM, lpwmFadeStart, lpwmFadeDelay}
};

byte direction = 0;

void setup() {
  pinMode(RPWM, OUTPUT); // Configure RPWM as an Output (actually not required for analogWrite!)
  pinMode(LPWM, OUTPUT); // Configure LPWM as an Output (actually not required for analogWrite!)
  pinMode(buttonPin, INPUT_PULLUP); // Configure button as Input
  analogWrite(RPWM, 0);
  analogWrite(LPWM, 0);
}

boolean buttonPressed() {
  constexpr unsigned long debounceTime {50}; // ms
  static unsigned long lastChange = 0;
  static byte state = HIGH;
  static byte lastState = LOW;
  byte actState = digitalRead(buttonPin);
  if (actState != lastState) {
    lastState = actState;
    lastChange = millis();
  }
  if (state != actState && millis() - lastChange >= debounceTime) {
    state = actState;
    return !state;
  }
  return false;
}

void move() {
  analogWrite(movement[direction].zeroPin, 0);
  for (int fadeValue = movement[direction].Start; fadeValue <= fadeEnd; fadeValue += 5) {// sets the value (range from 0 to 255):
    analogWrite(movement[direction].fadePin, fadeValue);
    delay(movement[direction].Delay);
  }
  analogWrite(movement[direction].fadePin, 0);
  if (direction == 0) {
    direction = 1;
  } else {
    direction = 0;
  }
}

void loop() {
  if (buttonPressed()) {
    move();
  }
}

It won't solve your issue but feel free to check it out ... :wink:

ec2021

P.S.: I did not take care of making the sketch non-blocking during the movement as there seems to be no need for a stop function ... If yes, the sketch could easily be changed to non-blocking to allow for further buttons ...

You guys are AMAZING!! Thanks for all of the very thoughtful responses. I'm going to test some code shortly but as stated this will be a slow troubleshooting process over several months.

Here are some more details.

  1. Linear actuator
  2. Arduino Uno Rev3
  3. High Current DC Motor Drive
  4. Tutorial and wiring diagram used
  5. Located inside
  6. No solder but I know I should (this could be a problem point)

I moved the setup location a little over a year ago and this has happened 5 times in 13 months. If I leave it to continue, the actuator cycling does get more frequent. I left it for a couple of weeks and by the end it was cycling almost constantly, with sporadic breaks. Also, power cycling does not fix the problem immediately but unplugging for an extend duration does. I need to let it sit for more than 15 min to get it to reset. I'm currently one week in from the last reset with exactly zero false triggers.

Including my workbench setup (it is currently installed deep in the back of a cabinet so photos will not be possible.

Button function video (before sound insulation)

Thanks again, your combined responses have blown me away.

Where are your pullup resistors?

  • Please provide us with a proper schematic showing all the component connections.

This certainly contributes to the problem. But there’s more to it: the connectors highlighted in the image are absolutely not suitable in the long term.

How is the Uno powered?
12V?
What current?

Do you have power outages or AC power problems at your location?
Is everything inside an enclosed space without air flow that could get hot?

Code uses built in pull-up, I'm assuming that this works rather than including an extra resistor.

pinMode(buttonPin, INPUT_PULLUP); // Configure button as Input

Using the official Uno 9v power supply at 1A

No known AC issues, we do loose power 3-4 times per year and do have a whole house generator.

Area is mostly enclosed but I provided a 3" x 6" opening (top and bottom) for airflow. I live in the PNW and temps in the house stay cool year round. Heat is not an issue.

While I know that this is not suitable I did install my test setup (embarrassed to admit). This is the weakest part of the whole project and I need to fix it. What is the ideal connection method I can use to replace this?

  • Oregon, Washington, B.C. ?

Here is the full schematic along with the actual setup.


On an island near Seattle