Detecting button time held

Not sure if this is the right place, please move me if I am in the wrong spot.

I'm sure this has been asked a lot before, but even with all my research on the subject I cannot quite get the coin to drop on it's side.

I'm working on my first large project, a pinball machine driven by arduino uno.

I got quite a lot done, but I need to know for how long a button is pushed, in order to protect solenoids from overheating. I seem to be able to detect single button push, but even if I release the button instantly I still see button held in the console. I don't know if this is because it's working correctly and just being super duper quick.

Can someone help me verify if this is working correctly?

Here is my code:

// whenever we start our arduino, because the switches are connected to a schmitt trigger,
// when the button is not pressed the low signal becomes a high signal. 
// therefore, initially you can see in the serial monitor, that the buttons were held.

// What happens so far:
/* 
When I press my button(s) only for a short tap, the console outputs 1 trigger but also 4 or 5 or more button held returns.
This could become a problem, once I attach solenoids to the trigger. (it may activate for too short) cause there should be some power to initially shoot a ball away.
after the button has been pressed for a while (still looking for how long) lets say 100ms, the solenoid should be ran with less power to prevent solenoid overheating.
I'm looking for 1 trigger to drive the solenoid high
and then looking for how long the button was held to allow for driving the solenoid with lower power.
*/

// define variables
int Led1pin = 7;
int Led2pin = 8;
int Led3pin = 12;

// switch 1
int state_s1 = 0; // In what state is the switch. (0-5)
int state_prev_s1 = 0; // What was the previous state. (0-5)
int pin_s1 = 2; // right flipper button is connected to this pin on the board.
int val_s1 = 0; // is the button on or off.
unsigned long t_s1 = 0; //  timing value for debouncing.
unsigned long t_0_s1 = 0; // timing value for debouncing.
unsigned long bounce_delay_s1 = 5; // delay some time for debouncing.
// variable only for debugging purposes. the other switches should behave the same way so that might be overkill. (it's only temp variable removed when not having to debug any more.)
unsigned long s1_times_pressed = 0; // let us know how many times the button was pressed.
// switch 2
int state_s2 = 0;
int state_prev_s2 = 0;
int pin_s2 = 3; // left flipper button
int val_s2 = 0;
unsigned long t_s2 = 0;
unsigned long t_0_s2 = 0;
unsigned long bounce_delay_s2 = 5;
// switch 3
int state_s3 = 0;
int state_prev_s3 = 0;
int pin_s3 = 4; // enter button
int val_s3 = 0;
unsigned long t_s3 = 0;
unsigned long t_0_s3 = 0;
unsigned long bounce_delay_s3 = 5;

void setup() {
  // put your setup code here, to run once:
  pinMode(pin_s1, INPUT);

  Serial.begin(9600);

}

void loop() {
  // put your main code here, to run repeatedly:

  // sense the switches.
  SM_s1();
  SM_s2();
  SM_s3();

  // handle the outcome (let us know what has been done)
  if (state_s1 == 4) {
    Serial.print("right flipper triggered!!! ");
    Serial.print("This many times: ");
    Serial.println(s1_times_pressed);
  }

  if (state_s1 == 1) {
    Serial.println("right flipper held!");
  }

  if (state_s1 != state_prev_s1) {
    Serial.print("right flipper state = ");
    Serial.println(state_s1);    
  }

  if (state_s2 == 4) {
    Serial.println("left flipper triggered");
  }

  if (state_s2 == 1) {
    Serial.println("left flipper held!");
  }

  if (state_s2 != state_prev_s2) {
    Serial.print("left flipper state = ");
    Serial.println(state_s2);    
  }

  if (state_s3 == 4) {
    Serial.println("enter button triggered");
  }

  if (state_s3 == 1) {
    Serial.println("enter button held!");
  }

  if (state_s3 != state_prev_s3) {
    Serial.print("enter button state = ");
    Serial.println(state_s3);    
  }

}

// for debugging purposes we want some blinking leds. 
void blink_s1() {
    digitalWrite(Led1pin, !digitalRead(Led1pin)); // no need to check anything, just write to the pin the value opposite to what the value is. (so 0 becomes 1, and 1 becomes 0)
    // this gets called quite a few times during the sensing of the switch, causing a blink to happen.
}

void blink_s2() {
    digitalWrite(Led2pin, !digitalRead(Led2pin));
}

void blink_s3() {
    digitalWrite(Led3pin, !digitalRead(Led3pin));
}

// detecting switch input cases.
void SM_s1() {

  state_prev_s1 = state_s1;

  switch (state_s1) {
    case 0: // start
      // code here      
      state_s1 = 1;
    break;

    case 1: // read pin
      // next case code
      val_s1 = digitalRead(pin_s1);
      blink_s1();
      if (val_s1 == LOW) {state_s1 = 2;}
    break;

    case 2: // GO
      // next case code      
      t_0_s1 = millis();
      state_s1 = 3;
    break;

    case 3: // wait debounce
      // next case code
      val_s1 = digitalRead(pin_s1);
      t_s1 = millis();
      if(val_s1 == HIGH) {state_s1 = 0;}
      if(t_s1 - t_0_s1 > bounce_delay_s1) {
        state_s1 = 5;
      }
    break;

    case 4: // pressed!
      // next case code
      state_s1 = 0;      
    break;

    case 5: // armed!
      val_s1 = digitalRead(pin_s1);
      digitalWrite(Led1pin, 0);
      if(val_s1 == HIGH) {
        state_s1 = 4;
        s1_times_pressed++;
        }
  }
}

void SM_s2() {

  state_prev_s2 = state_s2;

  switch (state_s2) {
    case 0: // start
      // code here  
      state_s2 = 1;
    break;

    case 1: // read pin
      // next case code
      val_s2 = digitalRead(pin_s2);
      blink_s2();
      if (val_s2 == LOW) {state_s2 = 2;}
    break;

    case 2: // GO
      // next case code
      t_0_s2 = millis();
      state_s2 = 3;
    break;

    case 3: // wait debounce
      // next case code
      val_s2 = digitalRead(pin_s2);
      t_s2 = millis();
      if(val_s2 == HIGH) {state_s2 = 0;}
      if(t_s2 - t_0_s2 > bounce_delay_s2) {
        state_s2 = 5;
      }

    break;

    case 4: // pressed!
      // next case code
      state_s2 = 0;
    break;

    case 5: // armed!
      val_s2 = digitalRead(pin_s2);
      digitalWrite(Led2pin, 0);  
      if(val_s2 == HIGH) {state_s2 = 4;}
  }
}

void SM_s3() {

  state_prev_s3 = state_s3;

  switch (state_s3) {
    case 0: // start
      // code here  
      state_s3 = 1;
    break;

    case 1: // read pin
      // next case code
      val_s3 = digitalRead(pin_s3);
      blink_s3();
      if (val_s3 == LOW) {state_s3 = 2;}
    break;

    case 2: // GO
      // next case code
      t_0_s3 = millis();
      state_s3 = 3;
    break;

    case 3: // wait debounce
      // next case code
      val_s3 = digitalRead(pin_s3);
      t_s3 = millis();
      if(val_s3 == HIGH) {state_s3 = 0;}
      if(t_s3 - t_0_s3 > bounce_delay_s3) {
        state_s3 = 5;
      }

    break;

    case 4: // pressed!
      // next case code
      state_s3 = 0;
    break;

    case 5: // armed!
      val_s3 = digitalRead(pin_s3);
      digitalWrite(Led3pin, 0);   
      if(val_s3 == HIGH) {state_s3 = 4;}
  }
}

I also have a video on youtube of the code in action. here: https://www.youtube.com/shorts/9fS7Qp7z7gw

I do not think that you need to detect how long a button is pressed.

You can detect when a button becomes (not: is) pressed and next start a millis() based timer to control the duration that the solenoid will be activated.

1 Like

so I got to add a variable say s1_time_held and put millis() in the value at case 5? or 4? then check vs the initial millis assigned after the debounce has passed into case 5?

I think I got a desirable outcome. I added a counter for s1_held_time. this is the console readout:

right flipper triggered!!! This many times: 1
right flipper state = 4
right flipper state = 0
right flipper held!
This long: 2
right flipper state = 1
right flipper held!
This long: 3
right flipper held!
This long: 4
right flipper held!
This long: 5
right flipper state = 2
right flipper state = 3
right flipper state = 5
right flipper triggered!!! This many times: 2
right flipper state = 4
right flipper state = 0
right flipper held!
This long: 2
right flipper state = 1
right flipper held!
This long: 3
right flipper held!
This long: 4
right flipper held!
This long: 5
right flipper state = 2
right flipper state = 3
right flipper state = 5

So a player can't hold the ball with the flipper like in a real pin-ball machine.

No this is not what I want, but solenoids cannot be driven high for long without burning out. You have to protect them by running them lower if the user is holding the button down for a while. This is the reason I want to know for how long the button is pressed. If I got it right this time, the code adds 1 to the value of held button. over time this returns a higher and higher value. When the value reaches my limit for driving the solenoid at max power, we can then drive it lower with a PWM pulse that I still need to find so that the flipper stays up to hold the ball without burning the solenoids. The plan is to use a potentiometer to find this magic number.

Seems like you have all this figured out.

Perhaps a better mounting structure for your solenoids would help. Dissipate the heat rather than let it build up.

I can encase it in some aluminum sheet and stick a heatsink on it? I got some old PC parts that have passive cooling blocks I can salvage for that task, but still, better be safe and send a PWM signal after the user is holding the button down for a while?

You are barking up the wrong tree if you think solenoids can only be driven for short period of time.
More likely your hardware choice is incorrect for the application.

It has been years since I worked on pinball machines, but I recall some flipper coils had a tapped winding. A high current on part of it to rapidly pull in the plunger , while the whole winding had a lower current to hold the plunger. The high current was switched in for only a short period.

A quick search came up with this.

https://www.reddit.com/r/pinball/comments/ubpu6k/flipper_coil_wiring_i_bought_a_fl11629_flipper/

Some later ones had a single coil and had a momentary high voltage for pull in and a lower voltage for hold. These were controlled by transistors rather than directly by switches.

I'm quite sure that my KK-1039B 12v solenoids have two wires coming out of them, not three. This would indicate single wound coil.

Maybe don't power the flippers beyond continuous rate? The ball landing on a held flipper shouldn't deflect it much.

But you do have elapsed time code in that sketch where the buttons are debounced and that leaves me wondering how much was copied from elsewhere and how close you are to knowing it complete... so we can get you there!

If you wrote the state machines that your posts tell me you do have a grasp of, KUDOS for knowing to use them!

Using unsigned integers, time is always
elapsed = end - start

if ( now - start >= desired ).....

Maybe make flipper functions that use Full Power for a short desired time then Sustained Power until the button stops?

When I do buttons, each one has a state value that can translate as OFF, ON, OFF-ON transition detected, ON-OFF transition detected, and BOUNCING.
Only how I do it is read every short interval (when millis != prev_millis is always 1024 micros, always) and keep the reads as bits in a read-history byte (left-shift history then read the pin and add that to the shifted history) that by value is button state with debounce built in.
Using 8 bits of history and pinmode INPUT_PULLUP; binary 10000000 is Press Detected, 00000000 is Held Down. 01111111 is Release, 11111111 is Up and everything else is Bounce.
A shorter debounce uses fewer bits, like 4 bits (history & 15) makes Press 1000 and Held 0000, Release 0111, Up 1111 and everything else is Bounce.
Each button has a history and other functions in void loop() do the if () with the history byte. My do-action logic doesn't have to include the button function, just the constantly updated history.

You are really doing well, just bye the bye.

Well the learning curve for me it's pretty steep. I always seem to punch above my weight class.

Yes some code is seen elsewhere, adapted where I needed it to. This is what I got so far. (be aware it contains code not related to this question, just to be complete. but it seems to operate now like I expected.)

/*
  ****************************************************
  ** ARDUINO PINBALL MACHINE By CRAZYBITE ver. 0.09 **
  ****************************************************
*/

/*
  ****************************************************
  ** Version history:                               **
  ** 0.01 Detecting & Releasing button input        **
  ** 0.02 Button debouncing                         **
  ** 0.03 Holding buttons                           **
  ** 0.04 Pulse Width Modulation                    **
  ** 0.05 Front LED attract mode                    **
  ** 0.06 Clockcycle counter for various timers     **
  ** 0.07 Pinball Machine Logic v 0.1               **
  ** 0.08 IR ball drain sensor (calibration)        **
  ** 0.09 TV Out custom number "font" arrays        **
  ****************************************************
*/

// Custom Number "font" arrays.
// '0', 11x18px
const unsigned char number_0 [] PROGMEM = { 11, 18,
	0xff, 0xe0, 0xf0, 0xe0, 0xe0, 0x60, 0xd0, 0xa0, 0x8f, 0x00, 0x8f, 0x00, 0x8f, 0x00, 0x8f, 0x00, 
	0xdf, 0xa0, 0xff, 0xe0, 0xdf, 0xa0, 0x8f, 0x00, 0x8f, 0x00, 0x8f, 0x00, 0x8f, 0x00, 0xd0, 0xa0, 
	0xe0, 0x60, 0xf0, 0xe0
};
// '1', 11x18px
const unsigned char number_1 [] PROGMEM = { 11, 18,
	0xff, 0xe0, 0xff, 0xe0, 0xff, 0xe0, 0xff, 0xa0, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 
	0xff, 0xa0, 0xff, 0xe0, 0xff, 0xa0, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0xa0, 
	0xff, 0xe0, 0xff, 0xe0
};
// '2', 11x18px
const unsigned char number_2 [] PROGMEM = { 11, 18,
	0xff, 0xe0, 0xf0, 0xe0, 0xe0, 0x60, 0xf0, 0xa0, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 
	0xf0, 0xa0, 0xe0, 0x60, 0xd0, 0xe0, 0x8f, 0xe0, 0x8f, 0xe0, 0x8f, 0xe0, 0x8f, 0xe0, 0xd0, 0xe0, 
	0xe0, 0x60, 0xf0, 0xe0
};
// '3', 11x18px
const unsigned char number_3 [] PROGMEM = { 11, 18,
	0xff, 0xe0, 0xf0, 0xe0, 0xe0, 0x60, 0xf0, 0xa0, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 
	0xf0, 0xa0, 0xe0, 0x60, 0xf0, 0xa0, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xf0, 0xa0, 
	0xe0, 0x60, 0xf0, 0xe0
};
// '4', 11x18px
const unsigned char number_4 [] PROGMEM = { 11, 18,
	0xff, 0xe0, 0xff, 0xe0, 0xff, 0xe0, 0xdf, 0xa0, 0x8f, 0x00, 0x8f, 0x00, 0x8f, 0x00, 0x8f, 0x00, 
	0xd0, 0xa0, 0xe0, 0x60, 0xf0, 0xa0, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0xa0, 
	0xff, 0xe0, 0xff, 0xe0
};
// '5', 11x18px
const unsigned char number_5 [] PROGMEM = { 11, 18,
	0xff, 0xe0, 0xf0, 0xe0, 0xe0, 0x60, 0xd0, 0xe0, 0x8f, 0xe0, 0x8f, 0xe0, 0x8f, 0xe0, 0x8f, 0xe0, 
	0xd0, 0xe0, 0xe0, 0x60, 0xf0, 0xa0, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xf0, 0xa0, 
	0xe0, 0x60, 0xf0, 0xe0
};
// '6', 11x18px
const unsigned char number_6 [] PROGMEM = { 11, 18,
	0xff, 0xe0, 0xf0, 0xe0, 0xe0, 0x60, 0xd0, 0xe0, 0x8f, 0xe0, 0x8f, 0xe0, 0x8f, 0xe0, 0x8f, 0xe0, 
	0xd0, 0xe0, 0xe0, 0x60, 0xd0, 0xa0, 0x8f, 0x00, 0x8f, 0x00, 0x8f, 0x00, 0x8f, 0x00, 0xd0, 0xa0, 
	0xe0, 0x60, 0xf0, 0xe0
};
// '7', 11x18px
const unsigned char number_7 [] PROGMEM = { 11, 18,
	0xff, 0xe0, 0xf0, 0xe0, 0xe0, 0x60, 0xf0, 0xa0, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 
	0xff, 0xa0, 0xff, 0xe0, 0xff, 0xa0, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0xa0, 
	0xff, 0xe0, 0xff, 0xe0
};
// '8', 11x18px
const unsigned char number_8 [] PROGMEM = { 11, 18,
	0xff, 0xe0, 0xf0, 0xe0, 0xe0, 0x60, 0xd0, 0xa0, 0x8f, 0x00, 0x8f, 0x00, 0x8f, 0x00, 0x8f, 0x00, 
	0xd0, 0xa0, 0xe0, 0x60, 0xd0, 0xa0, 0x8f, 0x00, 0x8f, 0x00, 0x8f, 0x00, 0x8f, 0x00, 0xd0, 0xa0, 
	0xe0, 0x60, 0xf0, 0xe0
};
// '9', 11x18px
const unsigned char number_9 [] PROGMEM = { 11, 18,
	0xff, 0xe0, 0xf0, 0xe0, 0xe0, 0x60, 0xd0, 0xa0, 0x8f, 0x00, 0x8f, 0x00, 0x8f, 0x00, 0x8f, 0x00, 
	0xd0, 0xa0, 0xe0, 0x60, 0xf0, 0xa0, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xf0, 0xa0, 
	0xe0, 0x60, 0xf0, 0xe0
};

// declare constants.
const int N_BUTTONS = 4;
const int N_SOLENOIDS = 4;
// Build an array for the buttons Right 15, Left 16, Start 17. and an array for the solenoids. 3,6 are left. 5,10 are right. 18 is the ball drain IR sensor.
int buttonPin[N_BUTTONS] = { 17, 16, 15, 18 };
int solenoidPin[N_SOLENOIDS] = { 3, 6, 5, 10};
int ledPin = 2; // the LED at the front.
// busyPin MP3 is on 4.

int buttonState[N_BUTTONS] = {0}; // in what state are the buttons? 0 is off 1 is on.
int buttonPState[N_BUTTONS] = {0}; // in what state were the buttons?

unsigned long lastDebounceTime[N_BUTTONS] = {0}; // what was the last debounce time for each button.
unsigned long debounceTimer[N_BUTTONS] = {0}; // current time in debouncing.
int debounceDelay = 10; // this is the delay for debounced button detection.
int maxLoops = 6; // this is the ammount of clock cycles that are allowed.
int minPower = 64; // 64 = 25% PWM. 128 = 50% PWM. 192 = 75% PWM. 255 = 100% PWM. (any value between 0 and 255 is legal.)

unsigned long loopsPressed[N_BUTTONS] = {0}; // store how many clock cycles the button is pressed.

int state_pinball = 0; // state of the pinball machine.
int prev_state_pinball = 0; // previous state of the pinball machine.

unsigned long score = 0; // score indicator.
unsigned long points = 0; // points indicator.
int balls = 0; // how many balls.

// some things depend on delays, but we don't want delays. create unique timers using unsigned long. (take care of the overflow at some point.)
unsigned long clockcycles = 0;
unsigned long prev_led_time = 0;

void setup() {

  // This runs once after power on.

  Serial.begin(9600); // begin serial communication with USB connected PC. (to display console output, debugging information etc.)
  Serial.println("");
  Serial.println("PINBALL MACHINE STARTING UP.");


  for (int i = 0; i < N_BUTTONS; i++) {
    // set pinmodes for each button. (they are input_pullup)
    pinMode(buttonPin[i], INPUT_PULLUP);
  }
  for (int j = 0; j < N_SOLENOIDS; j++) {
    // set pinmodes for each solenoid. (they are output)
    pinMode(solenoidPin[j], OUTPUT);
  }
  // set pinmode for LED. (it's output)
  pinMode(ledPin, OUTPUT);
}

void loop() {

  // this runs in a loop after setup was completed.

  for (int i = 0; i < N_BUTTONS; i++) {

    buttonState[i] = digitalRead(buttonPin[i]); // read the buttonpin(s)

    debounceTimer[i] = millis() - lastDebounceTime[i]; // set the debounce timer for read buttonpin.

    if (buttonState[i] != buttonPState[i]) {
      // buttonState is different than before.
      lastDebounceTime[i] = millis(); // store the last debounce time.

      if(debounceTimer[i] > debounceDelay) {
        // the debounce timer has passed the delay time! if the state reads low now, the button was really pressed!
        if(buttonState[i] == LOW) {
          // button was pressed.
          digitalWrite(ledPin, !buttonState[i]); // we switch the LED. !buttonState[i] = the other value of what it is now. 0 = 1 and 1 = 0.
          // print some debug to the console, to see what happened.
          Serial.print("button ");
          Serial.print(i);
          Serial.print(" ... ");
          Serial.print("buttonState: ");
          Serial.println(!buttonState[i]);
          Serial.print("loopsPressed: ");
          Serial.println(loopsPressed[i]);     
          
          if (buttonState[0] == LOW) {
            // start button pressed. no function yet.
            // start pinball machine if case state is 0 (state_pinball)
            if(state_pinball == 0) {
              state_pinball = 1;
            } else {
              // state_pinball = 2;
            }
          }
          if (buttonState[1] == LOW) {
            // flipper button pressed, send full power to first pair solenoids.
            analogWrite(solenoidPin[0], 255);
            analogWrite(solenoidPin[1], 255);
          }
          if (buttonState[2] == LOW) {
            // flipper button pressed, send full power to second pair solenoids.
            analogWrite(solenoidPin[2], 255);
            analogWrite(solenoidPin[3], 255);
          }
          if (buttonState[3] == LOW) {
            //
            if(state_pinball == 3) {
              state_pinball = 2;
            } else {
              // state_pinball = 2;
            }
          }
          loopsPressed[i]++; // since we pressed our button, count up for how many loops duration.
        } else {
          // button was released.
          digitalWrite(ledPin, !buttonState[i]); // we switch the LED. !buttonState[i] = the other value of what it is now. 0 = 1 and 1 = 0.
          // print some debug to the console, to see what happened.
          Serial.print("button ");
          Serial.print(i);
          Serial.print(" ... ");
          Serial.print("buttonState: ");
          Serial.println(!buttonState[i]);
          Serial.print("loopsPressed: ");
          Serial.println(loopsPressed[i]);          
          if (buttonState[0] == HIGH) {
            // start button released. but no function yet.
            loopsPressed[0] = 0; // button released, reset the value to 0.
          }
          if (buttonState[1] == HIGH) {
            // flipper button released, send 0 power to first pair solenoids.
            loopsPressed[1] = 0; // button released, reset the value to 0.
            analogWrite(solenoidPin[0], 0);
            analogWrite(solenoidPin[1], 0);
          }
          if (buttonState[2] == HIGH) {
            loopsPressed[2] = 0; // button released, reset the value to 0.
            // flipper button released, send 0 power to second pair solenoids.
            analogWrite(solenoidPin[2], 0);
            analogWrite(solenoidPin[3], 0);
          }
        }
        buttonPState[i] = buttonState[i]; // update the buttonPState.
      }
    } else {
      // No change in buttonstate. check if it's still low.
      if(buttonState[i] == LOW) {
        // the button is still pressed.
        digitalWrite(ledPin, !buttonState[i]); // we switch the LED. !buttonState[i] = the other value of what it is now. 0 = 1 and 1 = 0.
        // print some debug to the console, to see what happened.
        Serial.print("button ");
        Serial.print(i);
        Serial.print(" ... ");
        Serial.print("buttonState: ");
        Serial.println(!buttonState[i]);
        Serial.print("loopsPressed: ");
        Serial.println(loopsPressed[i]);        
        loopsPressed[i]++; // we keep counting.
        if (loopsPressed[i] >= maxLoops) {
          // loopsPressed for the button was more or equal to the maxloops value, send less power to solenoids now.
          if (buttonState[0] == LOW) {
            // we do nothing for now.
            }
          if (buttonState[1] == LOW) {
            // send min power to first pair solenoids.
            analogWrite(solenoidPin[0], minPower);
            analogWrite(solenoidPin[1], minPower);
            }
          if (buttonState[2] == LOW) {
            // send min power to second pair solenoids.
            analogWrite(solenoidPin[2], minPower);
            analogWrite(solenoidPin[3], minPower);
          }
        }
      }
    }
  }

  // PINBALL MACHINE LOGIC

  if(state_pinball >= 0) {
    Serial.print("State Pinball: ");
    Serial.println(state_pinball);     
  }

  // Serial.print("Clockcycles: ");
  // Serial.println(clockcycles);

  // Still heavily unfinished.
  switch (state_pinball) {

    case 0: //  attract mode.
      // code here
      // do attract mode stuff here, blink leds, show high score, do sounds etc.
      front_led_attract();
      
    break;

    case 1: // start pinball game.

      // next case code
      // mp3.volume(30);
      // mp3.play(3);
      state_pinball = 3;
      balls = 5;
      score = 0;
      points = 0;
    break;

    case 2: //  (re)start game & count pinballs left.
      // next case code
      // mp3.play(46);
      if(balls > 1) {
        // more than one ball left to play turn motor to feed next ball in launcher.
        balls = balls - 1;
        state_pinball = 3;
      }
      else
      {        
        state_pinball = 4;
      }      
    break;

    case 3: // wait ball drain
      // next case code
      // if ball drain is passed, count multiplier to score & pass to state 2 after some delay for tunes etc.
      // state_pinball = 2;
      // mp3.play(20); // ball drain sound.
    break;

    case 4: // No more balls! It's game over!
      // next case code
      // see if the player got a high score , if not pass to state 0
      // mp3.play(22); // game over sound.
      state_pinball = 5;
    break;

    case 5: // game over, but high score! enter name here, then pass over to state 0
      
      state_pinball = 0;
      // mp3.play(49); // hi score sound.
  }

  prev_state_pinball = state_pinball;
  // take care of clockcycles overflow.
  if (clockcycles >= 4000000000) {
    // almost overflowing, reset all unique timers to 0.
    clockcycles = 0;    
    prev_led_time = 0;
  }
  else {
    // not overflowing, keep counting.
    clockcycles++;  
  }
  
}

/*  
    ***************************************************************************
    FUNCTIONS LIST
    ***************************************************************************
*/

void front_led_attract() {
  // LED will blink every 5 clockcycles..
  int difference = clockcycles - prev_led_time;
  if (difference >= 12) {
    prev_led_time = clockcycles;
    digitalWrite(ledPin, !digitalRead(ledPin));
    // find a good sound effect for this LED effect.
  }
}

There is more than one way to write code and between easiest and hardest is about debugging.

The easiest way to debug is to have the fewest possible unknown parts at a time and the worst is to have a whole mess together and insist to trying to fix it all at once.

To that end, the way most experienced programmers write a program is in stages of little things, incrementally.

Beginners, on the other hand see finished examples and tryy to re-create that by writing what looks good to them using a lot of parts they copy and piecing a whole together and THEN go about trying to debug it all... which can work, don't get me wrong as I started out like that, but it takes a larger chunk of life longer to get that to work and chances are it may never work quite right.

For that reason I am loathe to commit to fixing a large sketch and I have seen others here post the same message.

How WE work with time, rollover is not special and does not need an if ( check ) { handler }.

How WE use Serial is at high baudrate, typically 115200 so that the print buffer gets emptied faster of chars because when that gets full, execution sticks until the print buffer has all the chars to be printed.

If you use an int where a byte would do, like with pin numbers then those numbers take twice as long to process and use up twice as much RAM which there isn't lots to waste.

When you use millis() or micros() code right you can break the sketch down into pieces that run in parallel so they don't have to get structured into a big block/knot that makes it harder to debug.

Punching above your weight class? Start small and work up, get help before you build. There are lots of helpers here but warning, some are not far ahead of you.

Kudos for trying though! A lot of that uses good technique, especially the state machine. What shows best about you is that you're willing to work at it!

Thank you for your effort in commenting. I was thinking that my counter version running on an unsigned long eventually reaches that point where it goes negative, and I didn't know if that was going to be a problem so I wrote that in as a precaution just to be certain we're working with positive values all the time.

The serial communication baud rate is just plucked from the sky (started low, increased the value untill I got output that made sense on the monitor.) I'm sure when I'm done debugging everything I will delete those lines (// comment them away or just backspace them)

edit: and in the first attempt at using millis() to time something twice it failed to work. Perhaps that was because I simply wrote it the wrong way?

How fast are you counting to get to 4294967296?

And anyway, no matter how long you count at any rate an

unsigned long myCounter;

will never go negative. By definition. You may mean roll over, in which case it mightn't be the problem you think it is.

I haven't yet your code. Maybe you could write a tiny sketch where you try to use millis() and fail; isolating a difficulty will make it easier for anyone to see where you went wrong.

a7

I'm saying loops++; inside the main loop. so it counts as fast as the uno can loop?

I think I got it confused with signed. after 4294967296 comes 0.

Yes, this is the smart approach. I will do more testing with tiny sketch to learn behaviour.

How does an unsigned integer go negative?

Look at a round clock and replace 12 with 0. If it is 3 and the start time was 8, move the hand on 3, 8 hours to the left (3 subtract 8) and you get 7... 7 hours difference.

end time - start time = elapsed time, always to the limit of the clock - 1, in this case 11 hours limit.

A loop incrementing an unsigned long and testing it would run for a bit under three hours. The loop is going at something like 400,000 Hz.

Careful if you test this: the compiler is quite capable of seeing that you aren't doing anything that really needs to have all that counting going on.

Qualify the variable as volatile to make sure you are timing the actual work of incrementing.

a7