Conditionals keep running twice

//Code for Fuel Injector testing apparatus - Taranabas Sept 2017

#include <Bounce2.h>

int startButton = 7;    //Starts the 15 second test pulse
int startButtonState = 0;
int toneButton = 8;   //Starts the 15 second PWM run
int toneButtonState = 0;
int injPin = 9;   //Fuel injector Output
int injFrequency = 60;
unsigned long previousMillis = 0; // Timer for serial monitor interval
const long interval = 3000;   // interval to report to serial monitor
bool busy = LOW; //Test in progress

//use Bounce2 to debounce inputs. This instantiates the bounce objects.
Bounce debouncer1 = Bounce();
Bounce debouncer2 = Bounce();

void setup() {
  //configure the start button for the main test pulse
  pinMode(startButton, INPUT_PULLUP);
  debouncer1.attach(startButton); //setup Bounce instance
  debouncer1.interval(10); //5ms debounce
  
 //configure the second button for the pulse width test
  pinMode(toneButton, INPUT_PULLUP);
  debouncer2.attach(toneButton); //setup Bounce instance
  debouncer2.interval(10); //5ms debounce
  
  pinMode(injPin, OUTPUT);

  Serial.begin(9600);

}

void loop() {
  //Update the Bounce Instances
  debouncer1.update();
  debouncer2.update();

  //Store the Bounce instance values:
  startButtonState = debouncer1.read(); //Reads startButton and stores it in startButtonState
  toneButtonState = debouncer2.read();

  //Read and store the A0 input and map it to a range for the tone function to use
  //tone() cannot go lower than 31Hz, so I'll use 35-150Hz
  int potValue = analogRead(A0);
  int scaledPotValue = map(potValue, 0, 1023, 35, 150);
  injFrequency = scaledPotValue;
  
  //Serial reporting timer
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= interval) {
    previousMillis = currentMillis;
  Serial.print("Pot Value = ");Serial.println(potValue);
  Serial.print("Scaled Pot Value = ");Serial.println(scaledPotValue);
  Serial.print("InjFrequency = ");Serial.println(injFrequency);
  }

//Conditional Statements:
  if ( startButtonState == LOW && busy == LOW )
  {
    busy = HIGH;
    Serial.print("Injector test button pressed, beginning 15s pulse.");
    delay(500);
    digitalWrite(injPin, HIGH);
    delay(15000);
    digitalWrite(injPin, LOW);
    delay(500);
    Serial.print("Test Complete.");
    busy = LOW;
  }
  if ( toneButtonState == LOW && busy == LOW )
  {
    busy = HIGH;
    toneButtonState = HIGH;
    Serial.print("Dynamic test starting at Hz: ");Serial.println(injFrequency);
    analogWrite(injFrequency, scaledPotValue);
    delay(500);
    tone(injPin, injFrequency, 15000);
    delay(15000);
    Serial.print("Test Complete.");
    busy = LOW;
    
  }
  

}

So this code is for a little fuel injector tester I’m working on, and is my first serious use of Arduino. The idea is that when I ground pin 7 or pin 8, either a 15s on pulse, or 15s square wave is applied to pin 9. It mostly works perfectly, except that every time I trigger pin 7 or 8, the 15s test period runs twice, despite only having momentarily grounded the pin. I tried using the ‘busy’ bit to latch the test, locking out any tests from initiating while one is running. I also tried adding a debounce function to the inputs, and even asserting the input back to HIGH which you can see in the second conditional ‘toneButtonState = HIGH’. I also tried adding a pullup resistor to pin 8.

What am I missing here? is it a problem with my code, or am I missing something electrically?

Don't just 'check' the button state - check for the 'change' of state.
There are several (many) examples here on the forum.

// read buttonState
if (buttonState != prevButtonState) {
  // do your thing
  prevButtonState = buttonState;
}

Also worth looking into as you develop your code - remove the delay() calls, and replace with millis() timers.

As @lastchancename suggests, you want to look at button changes, and the best way to use the Bounce2 library to do that is to use the .rose() or .fell() functions.

Try your code like this

//Code for Fuel Injector testing apparatus - Taranabas Sept 2017

#include <Bounce2.h>

int startButtonPin = 7;    //Starts the 15 second test pulse
//int startButtonState = 0;
int toneButtonPin = 8;   //Starts the 15 second PWM run
//int toneButtonState = 0;
int injPin = 9;   //Fuel injector Output
int injFrequency = 60;
unsigned long previousMillis = 0; // Timer for serial monitor interval
const long interval = 3000;   // interval to report to serial monitor
bool busy = LOW; //Test in progress

//use Bounce2 to debounce inputs. This instantiates the bounce objects.
Bounce startButton = Bounce();
Bounce toneButton = Bounce();

void setup() {
  //configure the start button for the main test pulse
  pinMode(startButtonPin, INPUT_PULLUP);
  startButton.attach(startButtonPin); //setup Bounce instance
  startButton.interval(10); //10ms debounce

  //configure the second button for the pulse width test
  pinMode(toneButtonPin, INPUT_PULLUP);
  toneButton.attach(toneButtonPin); //setup Bounce instance
  toneButton.interval(10); //10ms debounce

  pinMode(injPin, OUTPUT);

  Serial.begin(9600);

}

void loop() {
  //Update the Bounce Instances
  startButton.update();
  toneButton.update();

  //Store the Bounce instance values:
 // startButtonState = debouncer1.read(); //Reads startButton and stores it in startButtonState
 // toneButtonState = debouncer2.read();

  //Read and store the A0 input and map it to a range for the tone function to use
  //tone() cannot go lower than 31Hz, so I'll use 35-150Hz
  int potValue = analogRead(A0);
  int scaledPotValue = map(potValue, 0, 1023, 35, 150);
  injFrequency = scaledPotValue;

  //Serial reporting timer
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= interval) {
    previousMillis = currentMillis;
    Serial.print("Pot Value = "); Serial.println(potValue);
    Serial.print("Scaled Pot Value = "); Serial.println(scaledPotValue);
    Serial.print("InjFrequency = "); Serial.println(injFrequency);
  }

  //Conditional Statements:
 // if ( startButtonState == LOW && busy == LOW )
 if(startButton.fell())
  {
    //busy = HIGH;
    Serial.print("Injector test button pressed, beginning 15s pulse.");
    delay(500);
    digitalWrite(injPin, HIGH);
    delay(15000);
    digitalWrite(injPin, LOW);
    delay(500);
    Serial.print("Test Complete.");
    //busy = LOW;
  }
 // if ( toneButtonState == LOW && busy == LOW )
 if(toneButton.fell())
  {
    //busy = HIGH;
    //toneButtonState = HIGH;
    Serial.print("Dynamic test starting at Hz: "); Serial.println(injFrequency);
    analogWrite(injFrequency, scaledPotValue);
    delay(500);
    tone(injPin, injFrequency, 15000);
    delay(15000);
    Serial.print("Test Complete.");
   // busy = LOW;
  }
}

busy looks like it should be a boolean variable, with a value of true or false. You are either busy (true) or not (false). HIGH or LOW do not make sense.

Thanks for the replies all, for some reason I had it in my head that 0/1, high/low, and true/false were interchangeable o.O whoops.

Checking transitions certainly makes more sense, I'll give that a go.

They are interchangeable as arguments to digitalWrite, they are different concepts though, if your
variable is truth-valued, voltage levels and numbers are not appropriate.

I could imagine a version of the Arduino runtime in which you might specify "ACTIVE_LOW" in pinMode,
for example, meaning that digitalWrite (true) would, for that pin, be interpreted as digitalWrite (LOW).

The Arduino system doesn't have this notion though, so digitalWrite (true) means digitalWrite (HIGH)
always. That's a property of the digitalWrite, digitalRead function really.

Thanks for the replies all, for some reason I had it in my head that 0/1, high/low, and true/false were interchangeable o.O whoops.

They are. But, in some contexts HIGH and LOW make sense. In other contexts, true and false make sense. In other contexts, 0 and 1 make sense.

   for(byte b=0; b<=100; b+=HIGH)
   {
   }

would cause a WTF moment, wouldn’t it?

Checking transitions certainly makes more sense, I'll give that a go.

Given the use of delay() in your program, the need for button debounce and transitional logic is not really needed. If you just used digitalRead() of the button and take your finger off within 15 seconds of when the pulse starts your code should work as planned.

The repeat of the pulse routine may have been an artifact of how you were using the Bounce2 library with .read(). and the requirement for stable readings between .update() calls. See

and