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;
}
}
// 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.
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.
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)
/*
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 ...
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.
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.
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.
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?