Unexpected behaviour of the program

Hello , i have been at this for a while now , in essence the code works but it shows some unexpected behaviour on the configbutton

so as it is , not every short press of the configbutton changes the firemode, most of the times it does but lets say 1in5 it doesnt do anything

when it is in programming mode , a short press doesnt allways changes the selected settings and it will exit programming mode on a short press randomly.

i case anybody was wondering this is a program to control a solenoid of a HPA airsoft engine

i am all out of ideas , if anybody can help me out here that would be greatly appreciated

#include <EEPROM.h>

// Define pin assignments
const int triggerPin = 2;    // Pin connected to trigger switch
const int solenoidPin = 3;   // Pin connected to solenoid valve
const int configButton = 5;  // Pin used for programming & fire mode selection
const int feedbackLED = 4;   // Pin connected to LED for feedback

// Define adjustable settings with predefined values
const int dwellTimes[] = {7, 6, 7, 8, 9, 10, 11, 12, 13, 14};  
const int fireRates[] = {20, 9, 12, 15, 18, 20, 23, 26, 29, 33};         
const int dmrCooldowns[] = {1000, 800, 600, 400, 200};               
const int burstLengths[] = {3, 3, 4, 5, 6, 7};                     

// Define adjustable parameter indexes (loaded from EEPROM at startup)
int dwellTimeIndex;
int fireRateIndex;
int dmrCooldownIndex;
int burstLengthIndex;

// Define fire mode types
enum FireMode { SEMI_AUTO, BURST_MODE, FULL_AUTO, DMR_MODE, BINARY_MODE };
FireMode fireMode = SEMI_AUTO;

// Define adjustable setting types
enum Setting { DWELL_TIME, FIRE_RATE, DMR_COOLDOWN, BURST_LENGTH };
Setting selectedSetting = DWELL_TIME;  // Default selection

// Define control variables
bool inProgrammingMode = false;  // Dedicated flag to track if we're in programming mode
bool modeactive = false;
bool buttonPressed = false;
bool triggerPressed = false;
bool triggerReleased = true;  // Tracks trigger release in Binary Mode
unsigned long buttonPressStart = 0;
unsigned long lastFireTime = 0;
unsigned long modeactivetime = 0;

void setup() {
    Serial.begin(9600);  // Initialize serial communication
    Serial.println("FCU Booting...");
    Serial.println("loading settings");
    
    //read eeprom and set values on first boot
    dwellTimeIndex = EEPROM.read(1);
    fireRateIndex = EEPROM.read(2);
    dmrCooldownIndex = EEPROM.read(3);
    burstLengthIndex = EEPROM.read(4);
    Serial.println(dwellTimeIndex);
    Serial.println(fireRateIndex);
    Serial.println(dmrCooldownIndex);
    Serial.println(burstLengthIndex);
    if (dwellTimeIndex == 255) {
        dwellTimeIndex = 0;
    }  
    if (fireRateIndex == 255) {
        fireRateIndex = 0;
    }  
    if (dmrCooldownIndex == 255) {
        dmrCooldownIndex = 0;
    }  
    if (burstLengthIndex == 255) {
        burstLengthIndex = 0;
    }  

    // Set pin modes
    pinMode(triggerPin, INPUT_PULLUP);
    pinMode(solenoidPin, OUTPUT);
    pinMode(configButton, INPUT_PULLUP);
    pinMode(feedbackLED, OUTPUT);

    // Default states
    digitalWrite(solenoidPin, LOW);
    digitalWrite(feedbackLED, LOW);


}

void loop() {
    unsigned long currentTime = millis();

    // Handle button press (Short press cycles fire mode, long press enters programming mode)
    if (digitalRead(configButton) == LOW) {
       // Serial.println("buttonreleased");
      
        if (!buttonPressed) {
         buttonPressed = true;
         buttonPressStart = millis();
        }

        // Long press (2 sec) enters programming mode
         if (buttonPressed && (millis() - buttonPressStart > 2000) && !inProgrammingMode) {
             inProgrammingMode = true;  // Keep this flag separate from button state
             Serial.println("Entered Programming Mode");
             modeactive = true;
             modeactivetime = millis();

             blinkLED(20, 50);
        }

     } 
         else {
        // Short press cycles **fire mode** if programming mode is NOT active
        if (buttonPressed && (millis() - buttonPressStart < 2000) && !inProgrammingMode) {
            fireMode = static_cast<FireMode>((fireMode + 1) % 5);
            Serial.print("Fire Mode Changed: ");
            switch (fireMode) {
                case SEMI_AUTO:
                    Serial.println("Semi-Auto");
                    blinkLED(1, 150);
                    break;
                case BURST_MODE:
                    Serial.println("Burst Mode");
                    blinkLED(2, 150);
                    break;
                case FULL_AUTO:
                    Serial.println("Full Auto");
                    blinkLED(3, 150);
                    break;
                case DMR_MODE:
                    Serial.println("DMR Mode");
                    blinkLED(4, 150);
                    break;
                case BINARY_MODE:
                    Serial.println("Binary Mode");
                    blinkLED(5, 150);
                    break;
            }
        }
                // Short press cycles settings **only if programming mode is active**
        if (buttonPressed && (millis() - buttonPressStart < 2000) && inProgrammingMode) {
            selectedSetting = static_cast<Setting>((selectedSetting + 1) % 4);
            Serial.println("Selected Setting: ");
            Serial.println(selectedSetting);
            modeactivetime = millis();
            blinkLED(selectedSetting + 1, 250);
        }

        // If programming mode is active and long press detected again, exit mode
        if (inProgrammingMode && (millis() - buttonPressStart > 2000)) {
            if (buttonPressed && (millis() - modeactivetime > 5000)){
                inProgrammingMode = false;
                Serial.println("Exiting Programming Mode");
                Serial.println("storing in EEPROM");
                EEPROM.write(1, dwellTimeIndex);
                EEPROM.write(2, fireRateIndex);
                EEPROM.write(3, dmrCooldownIndex);
                EEPROM.write(4, burstLengthIndex);
                blinkLED(3, 500);
        }
        }
        

        
    }
        // Adjust selected setting using trigger press in programming mode
    if (inProgrammingMode && digitalRead(triggerPin) == LOW && !triggerPressed) {
        Serial.println("adjusting setting"); \
       
        switch (selectedSetting) {
            case DWELL_TIME:
                dwellTimeIndex = (dwellTimeIndex + 1) % 10;
                Serial.println("dwellTimeIndex");
                Serial.println(dwellTimeIndex);
                blinkLED(dwellTimeIndex + 1, 250);
                break;
            case FIRE_RATE:
                fireRateIndex = (fireRateIndex + 1) % 10;
                Serial.println("fireRateIndex");
               Serial.println(fireRateIndex);
               blinkLED(fireRateIndex + 1, 250);
                break;
            case DMR_COOLDOWN:
                dmrCooldownIndex = (dmrCooldownIndex + 1) % 5;
                Serial.println("dmrCooldownIndex");
                Serial.println(dmrCooldownIndex);
                blinkLED(dmrCooldownIndex + 1, 250);
                break;
            case BURST_LENGTH:
                burstLengthIndex = (burstLengthIndex + 1) % 6;
               Serial.println("burstLengthIndex");
               Serial.println(burstLengthIndex);
               blinkLED(burstLengthIndex + 1, 250);
                break;
        }
        
    }

    // Solenoid control based on selected fire mode
    if (!inProgrammingMode && digitalRead(triggerPin) == LOW) {
        if (currentTime - lastFireTime >= 1000 / fireRates[fireRateIndex]) {
            lastFireTime = currentTime;

            switch (fireMode) {
                case SEMI_AUTO:
                    if (triggerReleased) {
                        fireShot(dwellTimes[dwellTimeIndex]);  // Fire on pull
                        triggerReleased = false;  // Reset after firing
                     }
                     break;

                case BURST_MODE:
                    if (triggerReleased) {  // Ensure trigger was released before firing
                        triggerReleased = false;  // Mark trigger as "not released"
        
                    for (int i = 0; i < burstLengths[burstLengthIndex]; i++) {
                        fireShot(dwellTimes[dwellTimeIndex]);
                        delay(1000 / fireRates[fireRateIndex]);  // Maintain fire rate timing
                         }
                     }
                     break;


                case FULL_AUTO:
                    fireShot(dwellTimes[dwellTimeIndex]);
                    break;

                case DMR_MODE:
                    if (triggerReleased) {  // Ensure trigger was released before firing
                        triggerReleased = false;  // Mark trigger as "not released"
                        fireShot(dwellTimes[dwellTimeIndex]);
                        delay(dmrCooldowns[dmrCooldownIndex]);
                    }
                    break;

                case BINARY_MODE:
                    if (triggerReleased) {
                        fireShot(dwellTimes[dwellTimeIndex]);  // Fire on pull
                        triggerReleased = false;
                    }
                    break;
            }
        }
           if (!inProgrammingMode && digitalRead(triggerPin) == HIGH && fireMode == BINARY_MODE && !triggerReleased) {
        fireShot(dwellTimes[dwellTimeIndex]);  // Fire on release
        triggerReleased = true;
    }
    
        
    }
    //Serial.println(modeactivetime);
    if (digitalRead(configButton) == HIGH) {
       buttonPressed = false;
      // Serial.println("buttonreleased");
        }

    if (digitalRead(triggerPin) == HIGH) {
                        triggerReleased = true;  // Set to true when trigger is released
                    }
}

// Function to activate solenoid
void fireShot(int dwell) {
    
    digitalWrite(solenoidPin, HIGH);
    delay(dwell);
    digitalWrite(solenoidPin, LOW);
    Serial.println("pew");
}

// Function to blink LED for feedback
void blinkLED(int times, int delayTime) {
    for (int i = 0; i < times; i++) {
        digitalWrite(feedbackLED, HIGH);
        delay(delayTime);
        digitalWrite(feedbackLED, LOW);
        delay(delayTime);
    }
}

You have lots of Serial.print() statements in the program. Add more track down your error.

oh believe me i have tried , im all out of ideas thats why i came here

Did you print all the variables relating to the unexpected behavior?

i have tried printing everything i deemed usefull but i havent been able to track down the cause of my issues

You will probably be more successful if you use a proven button library. You are most likely getting bounce with your code.

Please say what duration of press is long, and what is short. And if any other durations apply, I think > 2 is long, and < 2 is short.

Next increase the baud rate to 115200. This won't fix your problem, but it may make for more confidence in any experiments or fizxes that you or we wanna try.

Lastly, just for now, make the very first statement, or the very last, just so it def runs once per loop() pass this

  delay(20);   // poor man's global debouncing

it will remove for the moment any question that your logic is occasionally catching a transition on the button(s) that is not advertent. And I don't think it will negative impact the functionality.

FWIW I also note that some of your logic in if statements would be cleaner and have fewer terms if you recognized A and B are true and branch on C, rather than branching on A and B and C versus A and B and not C.

But leave that for the moment since it kinda sorta works except for, I think, your precise button handling and the timing involved.

HTH

I may try to build your project. Cou,d you say what it is? It reads to be something fun.

a7

You might also get better results if you built your code in the form of a state machine. I also didn't read the unformatted code in any detail but I suspect any logic involving more than 2 variables.

@alto777 i will try the global debounce and change the baud rate ,
indeed a buttonpress longer then 2sec to enter en exit programming mode

as to the project , i am making a 3d printed HPA engine for airsoft , this code is for the fire control unit what is used to control the solenoid wich controls the hpa engine if u are interested i can send u some more info in DM

@sonofcy your last comment has me scratching my hair , im doing this as a hobby and have no experience in coding or programming , i got this far by using AI to help me build the code piece by piece :slight_smile:

@alto777 i tried the global debounce , but that hasnt changed the behaviour of the program

i increased the delay to 30 and it seems to have removed the unwanted behaviour almost completely , it does seem to miss a buttonpress every now and then , the trigger however doesnt skip a beat though.

@sonofcy i looked for some button librarys and it seems that yes indeed i should have used something like that from the start when reading it a bit i saw things like longclickhandler etc that would have saved me a lot of trouble getting all that functionality out of 1 button

If you use the Tools/Auto Format menu item in the IDE your code will be reformatted to a standard. Most of us with decades of experience (40 hrs a week) are able to understand the code when the punctuation is done a certain way.
The last part should be self-explanatory, and with two variables is less likely to have issues than one with three. We had a person on her only last week or the week before, and his three variables were in fact the cause of his problem.
At 83 I don;t have the energy to re-write somebodies code but I do suspect it would benefit from some modularization.

no experience in coding or programming, i got this far by using AI

That explains a lot. As a beginner, you should start with something easier. Do you have a copy of the Arduino Cookbook, have you worked through the Arduino Tutorials.

OK, that puts the spotlight in the button handling.

I have some arcade switches that would need more than 50 milliseconds, no matter how that figured in a button handler algorithm.

So maybe you have similarly crappy switches…

Now the question is whether the 50 (or whatever) starts to impact the feel of the sketch.

Better button handling can make the effects less obvious, but crappy switches needing long debounce time constants will always have an impact, like limiting the speed with which you can squeeze off single rounds.

a7

@sonofcy other then some basic stuff i have not really done much in the arduino biome I must admit , thx for the tip on the auto format and the button library .
i did do the autoformat in hopes that maybe something springs out to u as being blatantly wrong

i understand something easier would be beneficial in the learning process however im am driven by results atm , i got the hardware of my project ready and now im just trying to get it working with a certain degree of customisability in the form of the modes and settings , due to the way its implemented i am limited in how many buttons i can facilitate . but i thank everybody that takes a little time of their day to help me any way ,shape or form .
muchos gracias

#include <EEPROM.h>

// Define pin assignments
const int triggerPin = 2;    // Pin connected to trigger switch
const int solenoidPin = 3;   // Pin connected to solenoid valve
const int configButton = 5;  // Pin used for programming & fire mode selection
const int feedbackLED = 4;   // Pin connected to LED for feedback

// Define adjustable settings with predefined values
const int dwellTimes[] = { 7, 6, 7, 8, 9, 10, 11, 12, 13, 14 };
const int fireRates[] = { 20, 9, 12, 15, 18, 20, 23, 26, 29, 33 };
const int dmrCooldowns[] = { 1000, 800, 600, 400, 200 };
const int burstLengths[] = { 3, 3, 4, 5, 6, 7 };

// Define adjustable parameter indexes (loaded from EEPROM at startup)
int dwellTimeIndex;
int fireRateIndex;
int dmrCooldownIndex;
int burstLengthIndex;

// Define fire mode types
enum FireMode { SEMI_AUTO,
                BURST_MODE,
                FULL_AUTO,
                DMR_MODE,
                BINARY_MODE };
FireMode fireMode = SEMI_AUTO;

// Define adjustable setting types
enum Setting { DWELL_TIME,
               FIRE_RATE,
               DMR_COOLDOWN,
               BURST_LENGTH };
Setting selectedSetting = DWELL_TIME;  // Default selection

// Define control variables
bool inProgrammingMode = false;  // Dedicated flag to track if we're in programming mode
bool modeactive = false;
bool buttonPressed = false;
bool triggerPressed = false;
bool triggerReleased = true;  // Tracks trigger release in Binary Mode
unsigned long buttonPressStart = 0;
unsigned long lastFireTime = 0;
unsigned long modeactivetime = 0;

void setup() {
  Serial.begin(115200);  // Initialize serial communication
  Serial.println("FCU Booting...");
  Serial.println("loading settings");

  //read eeprom and set values on first boot
  dwellTimeIndex = EEPROM.read(1);
  fireRateIndex = EEPROM.read(2);
  dmrCooldownIndex = EEPROM.read(3);
  burstLengthIndex = EEPROM.read(4);
  Serial.println(dwellTimeIndex);
  Serial.println(fireRateIndex);
  Serial.println(dmrCooldownIndex);
  Serial.println(burstLengthIndex);
  if (dwellTimeIndex == 255) {
    dwellTimeIndex = 0;
  }
  if (fireRateIndex == 255) {
    fireRateIndex = 0;
  }
  if (dmrCooldownIndex == 255) {
    dmrCooldownIndex = 0;
  }
  if (burstLengthIndex == 255) {
    burstLengthIndex = 0;
  }

  // Set pin modes
  pinMode(triggerPin, INPUT_PULLUP);
  pinMode(solenoidPin, OUTPUT);
  pinMode(configButton, INPUT_PULLUP);
  pinMode(feedbackLED, OUTPUT);

  // Default states
  digitalWrite(solenoidPin, LOW);
  digitalWrite(feedbackLED, LOW);
}

void loop() {
  delay(30);
  unsigned long currentTime = millis();

  // Handle button press (Short press cycles fire mode, long press enters programming mode)
  if (digitalRead(configButton) == LOW) {
    Serial.print("buttonpressed");

    if (!buttonPressed) {
      buttonPressed = true;
      buttonPressStart = millis();
    }

    // Long press (2 sec) enters programming mode
    if (buttonPressed && (millis() - buttonPressStart > 2000) && !inProgrammingMode) {
      inProgrammingMode = true;  // Keep this flag separate from button state
      Serial.println("Entered Programming Mode");
      modeactive = true;
      modeactivetime = millis();

      blinkLED(20, 50);
    }

  } else {
    // Short press cycles **fire mode** if programming mode is NOT active
    if (buttonPressed && (millis() - buttonPressStart < 2000) && !inProgrammingMode) {
      fireMode = static_cast<FireMode>((fireMode + 1) % 5);
      Serial.print("Fire Mode Changed: ");
      switch (fireMode) {
        case SEMI_AUTO:
          Serial.println("Semi-Auto");
          blinkLED(1, 150);
          break;
        case BURST_MODE:
          Serial.println("Burst Mode");
          blinkLED(2, 150);
          break;
        case FULL_AUTO:
          Serial.println("Full Auto");
          blinkLED(3, 150);
          break;
        case DMR_MODE:
          Serial.println("DMR Mode");
          blinkLED(4, 150);
          break;
        case BINARY_MODE:
          Serial.println("Binary Mode");
          blinkLED(5, 150);
          break;
      }
    }
    // Short press cycles settings **only if programming mode is active**
    if (buttonPressed && (millis() - buttonPressStart < 2000) && inProgrammingMode) {
      selectedSetting = static_cast<Setting>((selectedSetting + 1) % 4);
      Serial.println("Selected Setting: ");
      Serial.println(selectedSetting);
      modeactivetime = millis();
      blinkLED(selectedSetting + 1, 250);
    }

    // If programming mode is active and long press detected again, exit mode
    if (inProgrammingMode && (millis() - buttonPressStart > 2000)) {
      if (buttonPressed && (millis() - modeactivetime > 5000)) {
        inProgrammingMode = false;
        Serial.println("Exiting Programming Mode");
        Serial.println("storing in EEPROM");
        EEPROM.write(1, dwellTimeIndex);
        EEPROM.write(2, fireRateIndex);
        EEPROM.write(3, dmrCooldownIndex);
        EEPROM.write(4, burstLengthIndex);
        blinkLED(3, 500);
      }
    }
  }
  // Adjust selected setting using trigger press in programming mode
  if (inProgrammingMode && digitalRead(triggerPin) == LOW && !triggerPressed) {
    Serial.println("adjusting setting");

    switch (selectedSetting) {
      case DWELL_TIME:
        dwellTimeIndex = (dwellTimeIndex + 1) % 10;
        Serial.println("dwellTimeIndex");
        Serial.println(dwellTimeIndex);
        blinkLED(dwellTimeIndex + 1, 250);
        break;
      case FIRE_RATE:
        fireRateIndex = (fireRateIndex + 1) % 10;
        Serial.println("fireRateIndex");
        Serial.println(fireRateIndex);
        blinkLED(fireRateIndex + 1, 250);
        break;
      case DMR_COOLDOWN:
        dmrCooldownIndex = (dmrCooldownIndex + 1) % 5;
        Serial.println("dmrCooldownIndex");
        Serial.println(dmrCooldownIndex);
        blinkLED(dmrCooldownIndex + 1, 250);
        break;
      case BURST_LENGTH:
        burstLengthIndex = (burstLengthIndex + 1) % 6;
        Serial.println("burstLengthIndex");
        Serial.println(burstLengthIndex);
        blinkLED(burstLengthIndex + 1, 250);
        break;
    }
  }

  // Solenoid control based on selected fire mode
  if (!inProgrammingMode && digitalRead(triggerPin) == LOW) {
    if (currentTime - lastFireTime >= 1000 / fireRates[fireRateIndex]) {
      lastFireTime = currentTime;

      switch (fireMode) {
        case SEMI_AUTO:
          if (triggerReleased) {
            fireShot(dwellTimes[dwellTimeIndex]);  // Fire on pull
            triggerReleased = false;               // Reset after firing
          }
          break;

        case BURST_MODE:
          if (triggerReleased) {      // Ensure trigger was released before firing
            triggerReleased = false;  // Mark trigger as "not released"

            for (int i = 0; i < burstLengths[burstLengthIndex]; i++) {
              fireShot(dwellTimes[dwellTimeIndex]);
              delay(1000 / fireRates[fireRateIndex]);  // Maintain fire rate timing
            }
          }
          break;


        case FULL_AUTO:
          fireShot(dwellTimes[dwellTimeIndex]);
          break;

        case DMR_MODE:
          if (triggerReleased) {      // Ensure trigger was released before firing
            triggerReleased = false;  // Mark trigger as "not released"
            fireShot(dwellTimes[dwellTimeIndex]);
            delay(dmrCooldowns[dmrCooldownIndex]);
          }
          break;

        case BINARY_MODE:
          if (triggerReleased) {
            fireShot(dwellTimes[dwellTimeIndex]);  // Fire on pull
            triggerReleased = false;
          }
          break;
      }
    }
    if (!inProgrammingMode && digitalRead(triggerPin) == HIGH && fireMode == BINARY_MODE && !triggerReleased) {
      fireShot(dwellTimes[dwellTimeIndex]);  // Fire on release
      triggerReleased = true;
    }
  }
  //Serial.println(modeactivetime);
  if (digitalRead(configButton) == HIGH) {
    buttonPressed = false;
    // Serial.println("buttonreleased");
  }

  if (digitalRead(triggerPin) == HIGH) {
    triggerReleased = true;  // Set to true when trigger is released
  }
}

// Function to activate solenoid
void fireShot(int dwell) {

  digitalWrite(solenoidPin, HIGH);
  delay(dwell);
  digitalWrite(solenoidPin, LOW);
  Serial.println("pew");
}

// Function to blink LED for feedback
void blinkLED(int times, int delayTime) {
  for (int i = 0; i < times; i++) {
    digitalWrite(feedbackLED, HIGH);
    delay(delayTime);
    digitalWrite(feedbackLED, LOW);
    delay(delayTime);
  }
}

it still feels responsive enough to me , compared to the trigger delay of a conventional AEG airsoft rifle this will be sufficiently responsive and that might be a serious understatement :slight_smile:

im using microswitch XSS-5 for the inputs on both trigger and configbutton

Adopting a state machine solution and not using delay statements will dramatically increase responsiveness. Maybe leave that to Version 2?
At some point, check out the Projects category sub-category Tutorials article

A Demo-Code explaining the switch-case state-machine and how to do things (almost) in parallel

The code gets reviewed and critiqued; therefore, it's better to read the entire thread, which is 24 posts long, but you should be much wiser by the time you are done.
This is one of several tutorials that are a goldmine for new people. I spent two years learning 40 hours a week, but I did not have access to libraries. Programmers today have it much easier because of the libraries.

1 Like

im gonna try and get through that in the morning when my mind is fresh , this for sure could be the genesis of V2 . thx for the motivation

1 Like

That's interesting. My crappy arcade buttons use the same type of switch. No that exact switch, but the same mechanical design.

a7

OK, my sources from beyond the grave say that microswitches are crappy out of the box due to the design of the switching mechanism.

For fun, if you'd like, try this sketch and a few different kinds of buttons. Better switches will count fewer transition each time you press and release the button.

Wire a switch between pin two and ground, open the serial monitor and set the baud rate to 115200. Try a few different switches if you have any that are not microswitches.

const int buttonPin = 2;      // Button input pin
volatile int bounceCount = 0; // Count of every transition (bounces and all)

int lastState = HIGH;         // Previous button state
int lastPrintedCount = -1;    // Last printed count (so we only print on change)

void setup() {
  pinMode(buttonPin, INPUT_PULLUP);  // Using internal pull-up
  Serial.begin(115200);
}

void loop() {
  int currentState = digitalRead(buttonPin);

  // Every time the state changes (up or down), count it
  if (currentState != lastState) {
    bounceCount++;
    lastState = currentState;
  }

  // Only print if the count changed
  if (bounceCount != lastPrintedCount) {
    Serial.println(bounceCount);
    lastPrintedCount = bounceCount;
  }
}

a7