Timer counter state change toggle FSM - all at once

There is a recurring theme of people new to Arduino and/or microcontrollers not grasping the concept of putting what I call ‘building blocks’ of code together to achieve some goal. These short sketches are an attempt to offer some examples showing how small pieces can be used in concert to build up larger, more complex sketches.

Thanks to @PerryBebbington and @ballscrewbob for support and input. You made it better.

The examples will illustrate:

• Doing ‘several things at once
• pitfalls of blocking code using delay() and while()
• Operating millis()† timers
• Enabling and disabling sections of code
• Using ‘state change detection’ < IDE -> file/examples/digital/statechangedetection to…
• increment a counter which…
• toggles a boolean which enables/disables…
• a time-driven Finite State Machine (FSM) to blink an LED

Also touched on: local scope variables, calling functions with and without arguments, using analog pins for digital input and output1, enums, static.

Gound Rules:
These sketches were developed with the Arduino IDE version 1.8.13.

(dagger) denotes a footnoted link to the Arduino Reference Page where an item is explained and often an example is shown.

The main text will cover ‘just the facts’. Supercripted numerals 1, 2, 3... denote footnotes to a more detailed explanation of something in the main text.

Variables2 used in the sketch(es) are colored blue.

Although it’s not strictly correct, for our purposes here the terms true, HIGH, and 1 (or, one) are synonymous. Likewise for false, LOW, and 0 (or, zero).

An editing tip: Some of the sketches will need minor changes and certain code disabled by commenting out.  On a PC you can comment out a line by placing the editing cursor anywhere on the line and using the sequence <ctrl> forward slash ( / ).  If several lines need commented highlight as if to copy/cut and then forward slash.  Don’t forget to upload the changes.

We’ll start with a basic pushbutton-operated timer and add on to it.  If you don’t have a pushbutton handy a simple breadboard jumper, or failing that, a piece of solid 22 ga. hookup wire, will suffice.  Push one end of the wire into the Arduino GND socket and touch the other end to the input pin, that’s all the switch is doing anyway.  The switch is connected as S3 is to D3 A31 and the LED is connected as D2 is connected to D7 A0 are in the drawing.  If all you’ve got is the Arduino and no breadboard the external LED can be omitted, it just makes seeing the variable status easier.

To begin, select, copy, and paste the code below into a new IDE sketch and upload it to the processor.  Start the serial monitor by clicking on the magnifying glass icon in the IDE upper right corner.  Insure the baud rates in the sketch Serial.begin(115200) and the serial monitor match.

serial monitor controls.png

// Runs on an UNO

// A basic timer that runs when enable is true, else is reset.

// Demonstrates:
// millis() timer operation
// sensing/indicating timer completion
// doing more than one thing at a time

// THIS Version puts the starting display in loop() for variable passing example


uint32_t timer1AccValue;
uint32_t const timer1Preset = 1500; // 1.5 seconds
uint32_t timer1PreviousMillis;
uint32_t timer1CurrentMillis;
bool isTimer1TimedOut;
bool isFirstPass;

const byte enablePin = A3; // A0-A5 can be used as digital inputs
const byte externalLED = A0;
//
//-----------------------------
//
void setup() {
  Serial.begin(115200);
  pinMode(enablePin, INPUT_PULLUP); // INPUT_PULLUP avoids the need for a discrete resistor.
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(externalLED, OUTPUT);
  isFirstPass = true;
while(digitalRead(enablePin));
}

void loop() {
  while (isFirstPass) {
    Serial.print("\n\nTimer  Timer\n");
    Serial.println("Value  Done");
    displayFunction(timer1CurrentMillis - timer1PreviousMillis, isTimer1TimedOut);
    delay(6000); // Pause for user to see column headings
    isFirstPass = false;
  }
  bool isButtonPressed = (bool) !digitalRead(enablePin); // check button state before proceeding
//  digitalWrite(LED_BUILTIN, isButtonPressed);
//  digitalWrite(externalLED, isButtonPressed);

  timer1CurrentMillis = millis(); // Take a snapshot of millis() to use for processing.

  if (isButtonPressed) { // If input is grounded, enable timer to run.
    // Set the timer's completion status
    if (timer1CurrentMillis - timer1PreviousMillis >= timer1Preset) { // If time elapsed set isTimer1TimedOut
      isTimer1TimedOut = true;
    }
  }
  else {  // If timer not enabled it is reset
    timer1PreviousMillis = timer1CurrentMillis; // Reset the timer.
    isTimer1TimedOut = false;
  }
  //
  // End of timer1 update activities
  //
  digitalWrite(LED_BUILTIN, isTimer1TimedOut);  // Send timer1's completion status to the onboard LED
  digitalWrite(externalLED, isTimer1TimedOut);

  displayFunction(timer1CurrentMillis - timer1PreviousMillis, isTimer1TimedOut);
} // end of loop

//--------------------------------------------

void displayFunction(uint32_t timer1, bool timedOut) {

  // Display runtime values passed from caller
  Serial.print(timer1);
  Serial.print("\t");
  Serial.print(timedOut);
  Serial.println();
}

1 The keywords and functions for the digital I/O pins apply also to the analog pins.  This makes it perfectly legal and harmless to connect our button to an analog input defined by enablePin and configure it in setup() via pinMode and control or access it with digitalRead/Write.  Just substitute A0-A5 in place of the usual pin number.

2 A piece of advice: Variables, constants, functions, etc. should be given descriptive, meaningful names so others, including your six-months-from-now self, can more easily determine what’s going on with the code.  Cryptic, one- or two-letter names are a false economy and the compiler doesn’t care about name length.  Don’t imagine you’ll remember a few weeks from now what px = py + ( ry * 673) means without scuffling through the code trying to find a declaration.  Besides, the names have no existence when the code does go down to the chip anyway.  Giving things names also avoids magic numbers and reduces the need to comment the code.

Arduino Reference Page

Notice that the serial monitor hasn’t shown us anything yet even though there are some Serial.print statements in loop().  Look at the last statement in setup(), it’s a while()1.  Meaning, enablePin is blocking progress because it’ll be HIGH until the button is pressed (it was declared with INPUT_PULLUP).  while() evaluates this as true and so continues its loop.  This is blocking code in action.  Or, inaction.  Press the button and the while() loop condition goes false.  We exit setup() and drop into loop().  Try it.

The while() can be commented out at this point.

Alright, since isfirstPass is set true in setup() we execute the block of statements following the while().  On the way out of this block we set isFirstPass false so we won’t enter it again ‘til next restart.

Next we digitalRead() the button2 input into isButtonPressed.  If you’re pressing on the normally open switch3, the logical state of isButtonPressed will be true.  We digitalWrite isButtonPressed to the LED as confirmation that the switch is pressed.

With the serial monitor running, press the reset button on the Arduino then operate the pushbutton several times.  What’s going on? The LED doesn’t follow the button state (column two) and nothing happens on the serial monitor after the column headings and initial values are printed!  No, not right away because we can’t know the button’s state, let alone do anything about it, until the delay(6000)4 in the firstPass block finishes.  We’re stuck there, essentially vegetating, for six seconds.  This is the way delay() acts as blocking code.  However, once the delay ends and isfirstPass is set false this block of code will be skipped so delay(6000)4 isn’t a problem anymore.

Before continuing, edit the sketch to decrease the first pass delay to something more reasonable.

The timer works5 by comparing the reference value timer1PreviousMillis to the ever increasing timer1CurrentMillis.  If the button is held long enough the difference between these two will reach preset and isTimer1TimedOut will be set true.  Conversely, when the button is not pressed the timer is reset6 and isTimer1TimedOut is set to false.

isTimer1TimedOut can now do something useful for us, namely, we’ll digitalWrite it to the on-board LED to give a visual indication of timer status.  You can see this if you press and hold the button for one-and-a-half seconds.  It’s not terribly sophisticated but this simple action is a basic part of many a sketch.

Lastly, displayFunction is passed two arguments and called to display them on the serial monitor so we can see the timer value and the boolean value in real time.  If you look at the declarations at the top of the sketch and the arguments in the function call you can see the types of variables passed match their counterparts in the function definition. displayFunction doesn’t return anything so it gets the keyword void in the definition.

Looking at the serial monitor you’d think all this was happening at once but now you know that’s not true – it’s just that everything described above is happening so blazingly fast it only appears that way on the human time scale.


  1. The Arduino reference page tells us: “A while() loop will loop continuously, and infinitely, until the expression inside the parenthesis, () becomes false. Something must change the tested variable, or the while loop will never exit. This could be in your code, such as an incremented variable, or an external condition, such as testing a sensor.

  2. isButtonPressed is declared as type bool since the button can have only two states, on or off .

  3. Grounding the input causes digitalRead to return a LOW which will be inverted by the ! operator.

  4. Press the reset button on the Arduino and hold the pushbutton pressed. Watch the timer value in the serial monitor while pressing the button. Nothing happens with the switch - millis() however, driven by an interrupt generated from the processor’s internal T0 (timer 0), continually increments in the background. So, by the time we escape the isfirstPass code millis() is already six thousand greater than timer1PreviousMillis, which was set to zero by the compiler. If the button is being pressed the timer code will register a finished state immediately upon exiting the isfirstPass block , isTimer1TimedOut (serial monitor column three) is set true and the ending digitalWrite() turns on the LED.

  5. The current value of millis() is copied to timer1CurrentMillis to lock down the value so it won’t change in the middle of things. It’s more efficient, too than calling millis() every time we need it.

    Now isButtonPressed is checked. That is, the question is asked, “Is the expression inside the parentheses after the keyword if logically true? This is identical to if(isButtonPressed == true) but it saves a few keystrokes. If it is true we check to see if the current (copy of) millis() minus the millis() value recorded the last time we went through this code is greater than or equal to timer1Preset. If so, isTimer1TimedOut is set true and other parts of the code can test this to know the timer’s completion state. If the if() statement conditions seem unclear scroll down to the C++ operator precedence rules.

  6. When the button is not pressed timer1PreviousMIllis is set equal to timer1Currentmillis - making the difference between them zero, effectively resetting the timer - and isTimer1TimedOut is set false. When isButtonPressed is true it stops the else clause from being executed. When this happens timer1PreviousMillis is frozen and the difference between millis() – and, therefore timer1CurrentMillis - and timer1PreviousMillis begins to increase.

Arduino Reference Page

Now the original timer is going to do something more useful, we’ll set it up to count button presses and the sketch will display the number of presses on the serial monitor. Upload the sketch, open the serial monitor and press the button.

// Runs on an UNO

// A timer-driven counter

// Demonstrates:
// millis() timer operation
// counting
// sensing/indicating timer completion
// doing more than one thing at a time

uint32_t const timer1Preset = 1500; // 1.5 seconds
uint32_t timer1PreviousMillis;
uint32_t timer1CurrentMillis;
bool isFirstPass;
bool isTimer1TimedOut;

int counter1AccValue;

const byte enablePin = A3; // A0-A5 can be used as digital inputs
const byte externalLED = A0;
//
//-----------------------------
//
void setup() {
  Serial.begin(115200);
  pinMode(enablePin, INPUT_PULLUP); // INPUT_PULLUP avoids the need for a discrete resistor.
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(externalLED, OUTPUT);
  Serial.print("\n\nCounter  Timer\n");
  Serial.println("Acc Val  Done");
  displayFunction(counter1AccValue, isTimer1TimedOut);
  delay(3000); // Pause for user to see column headings
}

void loop() {

  bool isButtonPressed = (bool) !digitalRead(enablePin); // check button state before proceeding
  timer1CurrentMillis = millis(); // Take a snapshot of millis() to use for processing.

  //
  // Begin timer1 update activities
  //
  if (isButtonPressed) { // If input is grounded, enable timer to run.
    // Set the timer's completion status
    if (timer1CurrentMillis - timer1PreviousMillis >= timer1Preset) { // If time elapsed set isTimer1TimedOut
      isTimer1TimedOut = true;
    }
  }
  else {  // If timer not enabled it is reset
    timer1PreviousMillis = timer1CurrentMillis; // Reset the timer.
    isTimer1TimedOut = false;
  }
  //
  // Start counter increment activities
  //
  if (isTimer1TimedOut) {
    counter1AccValue += 1;;
  }
  //
  // End counter increment
  //
  digitalWrite(LED_BUILTIN, isTimer1TimedOut);  // Send timer1's completion status to the onboard LED
  digitalWrite(externalLED, isTimer1TimedOut);

  displayFunction(counter1AccValue, isTimer1TimedOut);
} // end of loop

//--------------------------------------------

void displayFunction(unsigned long intValue, bool timedOut) {

  // Display runtime values passed from caller
  Serial.print(intValue);
  Serial.print("\t  ");
  Serial.print(timedOut);
  Serial.println();
}

Wait! This is not what we wanted! The timer counts all the time when the timer is timed out. Yep, that’s what we asked for – when isTimer1TimedOut is true increment the counter. isTimer1TimedOut is true once timer preset is reached so every pass through the code the counter will increment.

What to do?

Go to the next post to find out.

Arduino Reference Page

The answer is to run the timer status through some state change detection code1 (SCD) and use the fact that the timer has become timed out to increment the counter. Upload the next sketch and press the button as before.

// Runs on an UNO

// A timer-driven counter

// Demonstrates:
// millis() timer operation
// counting
// sensing/indicating timer completion
// doing more than one thing at a time

uint32_t const timer1Preset = 1500; // 1.5 seconds
uint32_t timer1PreviousMillis;
uint32_t timer1CurrentMillis;

bool isTimer1TimedOut;
bool timer1TimedOutLastTime;

bool isTimeToBumpCounter;

int counter1AccValue;

const byte enablePin = A3; // A0-A5 can be used as digital inputs
const byte externalLED = A0;
//
//-----------------------------
//
void setup() {
  Serial.begin(115200);
  pinMode(enablePin, INPUT_PULLUP); // INPUT_PULLUP avoids the need for a discrete resistor.
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(externalLED, OUTPUT);
  Serial.print("\n\nCounter  Timer\n");
  Serial.println("Acc Val  Done");
  displayFunction(counter1AccValue, isTimer1TimedOut);
  delay(3000); // Pause for user to see column headings
}

void loop() {
 
  bool isButtonPressed = (bool) !digitalRead(enablePin); // check button state before proceeding
  timer1CurrentMillis = millis(); // Take a snapshot of millis() to use for processing.

  //
  // Begin timer1 update activities
  //
  if (isButtonPressed) { // If input is grounded, enable timer to run.
    // Set the timer's completion status
    if (timer1CurrentMillis - timer1PreviousMillis >= timer1Preset) { // If time elapsed set isTimer1TimedOut
      isTimer1TimedOut = true;
    }
  }
  else {  // If timer not enabled it is reset
    timer1PreviousMillis = timer1CurrentMillis; // Reset the timer.
    isTimer1TimedOut = false;
  }
  //
  // The one-shot section
  //
  if (isTimer1TimedOut and not timer1TimedOutLastTime) { // <- These two lines
    // if (isTimer1TimedOut && !timer1TimedOutLastTime) // <- are equivalent
    isTimeToBumpCounter = true;
  }
  else {
    isTimeToBumpCounter = false;
  }

  timer1TimedOutLastTime = isTimer1TimedOut;
  //
  // End one-shot
  //
  // Start counter increment activities
  //
  if (isTimeToBumpCounter) {
    counter1AccValue += 1;
  }
  //
  // End counter increment
  //
  digitalWrite(LED_BUILTIN, isTimer1TimedOut);  // Send timer1's completion status to the onboard LED
  digitalWrite(externalLED, isTimer1TimedOut);

  displayFunction(counter1AccValue, isTimer1TimedOut);
} // end of loop

//--------------------------------------------

void displayFunction(unsigned long intValue, bool timedOut) {

  // Display runtime values passed from caller
  Serial.print(intValue);
  Serial.print("\t  ");
  Serial.print(timedOut);
  Serial.println();
}

By now you should be able to see the chain of events. The pushbutton is pressed and held: after some time the timer reaches preset value; isTimer1TimedOut triggers a bit of state change detection code which increments the counter once because isTimeToBumpCounter is only true for one pass when isTimer1TimedOut becomes true.


1 The key to SCD is remembering the past. Examine the code under ‘The one-shot section’ comment. The expression in the if() statement performs a logical and† between the current data, isTimer1TimedOut, and the inverted/notted value we recorded the last time through this code, timer1TimedOutLastTime. On the first pass through loop() isTimer1TimedOut is false, set so by the preceding digitalRead, and timer1TimedOutLastTime is also false, via the compiler, but, by the action of the not operator it’s evaluated as true for the purposes of this comparison. The total expression will therefore be false since both sub-expressions must be true for the complete expression to be true. Setting retrigger true is skipped and instead the else† clause sets retrigger false. To wrap things up timer1TimedOutLastTime is set equal to isTimer1TimedOut. This final statement is what establishes our memory of events past and these conditions prevail until isTimer1TimedOut goes true.

If isTimer1TimedOut does eventually go true the next time we hit the if() both expressions within the parentheses are evaluated as true (we haven’t updated timer1TimedOutLastTime with the new state of isTimer1TimedOut yet) so retrigger is set true. As always, before we leave this section we remember for next time by setting timer1TimedOutLastTime equal to isTimer1TimedOut , ie., true. The rest of the sketch is processed and loop() starts over and we arrive at ‘The one-shot section’ again. isTimer1TimedOut is still true – nobody’s fast enough to get a finger off the switch before multiple passes have been made , but even if you were timer1TimedOutLastTime (actual state true) is evaluated as false due to the ! (not) operator - so the if() is false and we drop to the else where retrigger is set false. The result is that retrigger is true for only one pass through the code for each press of the switch. We have sensed a false-to-true transition of the if() condition.

Sometime later the button is released, the timer resets, and both expressions inside the parentheses will be false and once more timer1TimedOutLastTime is set equal to isTimer1TimedOut returning us to the initial conditions.

This example is acting on isTimer1TimedOut going false-to-true but one can rearrange things to get the SCD on true-to-false or, even on both of these.

SCD can be used on any value or expression that can be evaluated as true or false. A relationship between numbers or even an analog value can generate the one-scan-true as done above. The only requirement is an if() with the correct expression(s) for a test.

Arduino Reference Page

In this sketch the counter code will be modified so it resets itself at some number we choose and that resetting will also cause a boolean variable to toggle between true and false. Copy/paste the sketch below and upload.

// Runs on an UNO

// A timer-driven counter toggles a boolean

// Demonstrates:
// millis() timer operation
// counting
// sensing timer completion
// toggle a boolean variable on and off
// doing more than one thing at a time

uint32_t const timer1Preset = 1500; // 1.5 seconds
uint32_t timer1PreviousMillis;
uint32_t timer1CurrentMillis;
bool isFirstPass;
bool isTimer1TimedOut;
bool timer1TimedOutLastTime;

bool isTimeToBumpCounter;

int counter1AccValue;
int counter1Preset = 3;

const byte enablePin = A3; // A0-A5 can be used as digital inputs
const byte externalLED = A0;
//
//-----------------------------
//
void setup() {
  Serial.begin(115200);
  pinMode(enablePin, INPUT_PULLUP); // INPUT_PULLUP avoids the need for a discrete resistor.
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(externalLED, OUTPUT);
  Serial.print("\n\nCounter  toggle\n");
  Serial.println("Acc Val  Bool");
  displayFunction(counter1AccValue, 0 );
  delay(3000); // Pause for user to see column headings
}

void loop() {
  static bool toggleBool; // static so variable retains its value
  bool isButtonPressed = (bool) !digitalRead(enablePin); // check button state before proceeding
  timer1CurrentMillis = millis(); // Take a snapshot of millis() to use for processing.

  //
  // Begin timer1 update activities
  //
  if (isButtonPressed) { // If input is grounded, enable timer to run.
    // Set the timer's completion status
    if (timer1CurrentMillis - timer1PreviousMillis >= timer1Preset) { // If time elapsed set isTimer1TimedOut
      isTimer1TimedOut = true;
    }
  }
  else {  // If timer not enabled it is reset
    timer1PreviousMillis = timer1CurrentMillis; // Reset the timer.
    isTimer1TimedOut = false;
  }
  //
  // The one-shot section
  //
  if (isTimer1TimedOut and not timer1TimedOutLastTime) { // <- These two lines
    // if (isTimer1TimedOut && !timer1TimedOutLastTime) // <- are equivalent
    isTimeToBumpCounter = true;
  }
  else {
    isTimeToBumpCounter = false;
  }
  timer1TimedOutLastTime = isTimer1TimedOut;
  //
  // End one-shot
  //
  // Start counter increment activities
  //
  if (isTimeToBumpCounter) {
    counter1AccValue += 1;
  }
  //
  // End counter increment
  //
  //
  // When the counter reaches preset toggle a boolean and reset the counter.

  if (counter1AccValue == counter1Preset) {
    toggleBool = !toggleBool; // toggleBool is loaded with its opposite state.
    counter1AccValue = 0;
  }

  digitalWrite(LED_BUILTIN, isTimer1TimedOut);  // Send timer1's completion status to the onboard LED
  digitalWrite(externalLED, toggleBool);

  displayFunction(counter1AccValue, toggleBool);
} // end of loop

//--------------------------------------------

void displayFunction(unsigned long intValue, bool booleanValue) {

  // Display runtime values passed from caller
  Serial.print(intValue);
  Serial.print("\t  ");
  Serial.print(booleanValue
  );
  //  Serial.print(00);

  Serial.println();
}

Start the serial monitor and press the pushbutton repeatedly, holding it pressed long enough for the timer to complete. To assist with this the current sketch sends isTimer1TimedOut to the onboard LED rather than toggleBool. On the third press and timeout if (counter1AccValue == counter1Preset) will be true. Then the value of toggleBool is retrieved, complemented, and stored back in toggleBool. If it was false it becomes true and vice versa. The counter is then set equal to zero - as seen on the serial monitor - causing the if() test to be false next time. Call this a one-shot in disguise. By this means toggleBool is only allowed to change on that one pass when the timer resets. You can see a variation of this idea in IDE -> file/examples/digital/blink without delay.

Before going on to the next sketch take a look at the line where toggleBool is declared and see the use of keyword static. Without static added the variable will be created anew when the function is entered and destroyed upon exit - and loop() is a function. This means that if static is omitted we lose our memory of what happened on the last pass through the code. Edit out static and upload the sketch and try it out.

So now we’ve got button sensing, timing, state change detection, counting, boolean toggling, and displaying these things on the serial monitor in one sketch and all of this executing on every pass through the code. We're doing more than one thing at a time.

We’ve got a lot going on now but there’s one more big step in this progression, the Finite State Machine mentioned at the top. Briefly, an FSM can be in only one of multiple states at a given time. Imagine a traffic signal cycling through green, amber, red at an intersection. Each time a light goes on or off is a change from one state to another, a transition, and the states will be found somewhere in a list of possible states for the signal. Our FSM will operate very similarly.

There is more than one way to realize an FSM but this one will use the C/C++ switch/case construct. There are two essential features of a state machine. The switch statement resolves its expression to some integer value then selects one of its case statements with a matching value.

The case statements contain additional statements which perform some action(s) and/or can monitor some condition or set of conditions to determine when it’s time to go to a new state. Changing the switch expression isn’t restricted to within the switch/case, it can be altered from the outside, too. When a case has completed its little bit of the task a break statement is used to exit the switch and continue with the succeeding code. However, a break statement is not a requirement, and can be omitted if it serves your purpose – which it does here.

However, people expect to see a break at the conclusion of a case. The compiler does too, and a warning may be generated if one is not found. To confirm that this is intentional and to suppress warnings [[fallthrough]] is used in this sketch. If [[fallthrough]] is not used be sure to include a comment to the effect of "Fallthrough is deliberate" so the intent is clear.

If no matching case is found the code will go to a default case if one is provided. Think of the default as an error handler.

Now, one can use integer literals to select the various cases to move the FSM along but this can easily become confusing and error prone. To improve readability we’ll use enums. Instead of case: 1 we’ll have case: blinkerOnCycle and so forth.
Running the sketch below may help in visualizing how this works:

// enum prover
// blinker function state variables
enum : byte {initializeBlinker, blinkerOnPhase, blinkerOffPhase, blinkerOnTtransition, blinkerOffTransition};

void setup() {
  Serial.begin(115200);
  Serial.print("initializeBlinker\t= ");
  Serial.println(initializeBlinker);

  Serial.print("blinkerOnPhase\t\t= ");
  Serial.println( blinkerOnPhase);

  Serial.print("blinkerOffPhase\t\t= ");
  Serial.println(blinkerOffPhase);

  Serial.print("blinkerOnTtransition\t= ");
  Serial.println(blinkerOnTtransition);

  Serial.print("blinkerOffTransition\t= ");
  Serial.println(blinkerOffTransition);
}

void loop() {

}

The explanation and code for the sketch combining all that has come before is too long to be added here so it’s in the following post.

blinkerFunction has been added to handle blinking the LED and we control it by passing in toggleBool. Local variable runBlinker assumes the value of toggleBool and if true execution passes into the block of code under the if() and the state machine1 blinks the LED using its own timer. If runBlinker is false the LED is turned off.

Here’s the sketch.

// Runs on an UNO

uint32_t timer1AccValue;
uint32_t timer1Preset = 1000; // 1.0 seconds
uint32_t timer1PreviousMillis;
uint32_t timer1CurrentMillis;

const byte enablePin = A3; // A0-A5 can be used as digital inputs
const byte blinkLED = A0; // and digital outputs.

const byte counter1Preset = 3;
byte counter1AccValue;

bool isTimer1TimedOut, timer1TimedOutLastTime;

// blinker function state variables
enum : byte {initializeBlinker, blinkerOnPhase, blinkerOffPhase, blinkerOnTtransition, blinkerOffTransition};
byte blinkState;

unsigned long blinkTimer;

// For symmetrical on/off times just use the same time value for both states.
unsigned long blinkerOnTime = 400;
unsigned long blinkerOffTime = 1500;

void setup() {
  Serial.begin(115200);
  pinMode(enablePin, INPUT_PULLUP); // INPUT_PULLUP avoids the need for a discrete resistor.
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(blinkLED, OUTPUT); // Common anode connection
  Serial.print("\n\nTimer  Timer  Counter Toggle\n");
  Serial.println(" Acc   Done    Acc     Bit");
  displayFunction(timer1AccValue, isTimer1TimedOut, counter1AccValue, 0);
  blinkState = initializeBlinker;
  delay(3000); // Pause for user to see column headings
}

void loop() {
  static bool toggleBool;
  static bool isTimeToBumpCounter;

  bool isButtonPressed = (bool) !digitalRead(enablePin); // check button state before proceeding
  timer1CurrentMillis = millis(); // Take a snapshot of millis() to use for processing.
  //
  // Begin timer1 update activities
  //
  if (isButtonPressed) { // If input is grounded, enable timer to run.
    // Set the timer's completion status
    if (timer1CurrentMillis - timer1PreviousMillis >= timer1Preset) { // If time elapsed set isTimer1TimedOut
      isTimer1TimedOut = true;
    }
  }
  else {  // If timer not enabled it is reset
    timer1PreviousMillis = timer1CurrentMillis; // Reset the timer.
    isTimer1TimedOut = false;
  }
  //
  // The one-shot section
  //
  if (isTimer1TimedOut and not timer1TimedOutLastTime) { // <- These two lines
    // if (isTimer1TimedOut && !timer1TimedOutLastTime) // <- are equivalent
    isTimeToBumpCounter = true;
  }
  else {
    isTimeToBumpCounter = false;
  }
  timer1TimedOutLastTime = isTimer1TimedOut; //Condition one-shot for next pass
  //
  // End one-shot
  //
  // Start counter increment activities
  //
  if (isTimeToBumpCounter) {
    counter1AccValue += 1;
  }
  //
  // End counter increment
  //
  // When the counter reaches preset toggleBool changes state and the counter is reset.
  //
  if (counter1AccValue == counter1Preset) {
    toggleBool = !toggleBool; // toggleBool is loaded with the complement of itself.
    counter1AccValue = 0;
  }

  digitalWrite(LED_BUILTIN, isTimer1TimedOut);  // The onboard LED indicates timer status
  blinkerFunction(toggleBool);
  displayFunction(timer1AccValue, isTimer1TimedOut, counter1AccValue, toggleBool);
} // end of loop

//--------------------------------------------

void displayFunction(uint32_t timer1, bool timedOut, int counter, bool toggle) {

  // Display runtime values passed from caller
  Serial.print(timer1);
  Serial.print("\t");
  Serial.print(timedOut);
  Serial.print("\t");
  Serial.print(counter);
  Serial.print("\t");
  Serial.println(toggle);
}

void blinkerFunction(bool runBlinker) {

  // State machine to blink an LED.

  if (runBlinker) { // blink
    switch (blinkState)
    {
      case initializeBlinker:
        blinkState = blinkerOnTtransition; // Set the next state value
        [[fallthrough]]; // Prevents compiler warnings about falling through
      //                    due to lack of break; statement.

      case blinkerOnTtransition:
        // This way doesn't constantly hit the Serial print and digital write statements
        Serial.println("on cycle");
        digitalWrite(blinkLED, HIGH);
        blinkTimer = millis(); // Reset the timer
        blinkState = blinkerOnPhase; // Set the next state value
        [[fallthrough]];

      case blinkerOnPhase:
        if (millis() - blinkTimer < blinkerOnTime) { // Which it will be for awhile since the
          //                                            previous state set timer = millis()
          break;
        }
        // When on time expires
        blinkState = blinkerOffTransition; // Set the next state value
        [[fallthrough]];

      case blinkerOffTransition:
        Serial.println("off cycle");
        digitalWrite(blinkLED, LOW);
        blinkTimer = millis(); // Reset the timer
        blinkState = blinkerOffPhase; // Set the next state value
        [[fallthrough]];

      case blinkerOffPhase:
        if (millis() - blinkTimer < blinkerOffTime) {
          break;
        }
        blinkState = blinkerOnTtransition; // Set the next state value
        break;

      default:
        blinkState = initializeBlinker; // If invalid state occurs, start at the beginning
    }
  }
  else {
    digitalWrite(blinkLED, LOW);
  }
}

1 initializeBlinker insures we start from a known point then falls through to blinkerOnTransition where some setup type actions take place: a message is printed to the serial monitor; the LED is set HIGH (on); blinkTimer is set to millis – this performs a reset the same way the previous timers did; and blinkState is set to the next value.

We then fall through to blinkerOnPhase and start timing. By now the time comparison should look familiar so you will know if we haven’t reached blinkerOnTimePreset execution will drop into the break statement and we exit the switch to continue processing. This maintains the non-blocking character desired so whatever other code the sketch contains is not ignored while we wait for the next state to arrive. This is another instance
where using delay() would hold us up and nothing else would get done for X milliseconds.

So, eventually time expires and the if() goes false allowing us to pass on to the next statement, which adjusts blinkState to blinkerOffTransition. We fall through and again perform some initialization things for the LED off phase then fall through to time the LED off. When the LED off phase ends blinkState is set to blinkerOnTransition and the process repeats.

else if runBlinker is false the LED is shut off, a message is printed and the state variable is set to initializeBlinker in preparation for the next time runBlinker is true.

Arduino Reference Page

A number of posts back we made a timer we can control with a pushbutton and see when the timer has done its job via isTimer1TimedOut. This basic version meets the needs of many timer applications but we can add some simple code to make it even more versatile.

It’s often desirable to know if a timer is enabled and not yet finished. That is, running, in progress toward its preset value. An example might be to open a ‘window of opportunity’ for a user to press a button or to generate a pulse for some output device.

Holding the pushbutton down shows the action of the new variable isTimer1Running1 on the serial monitor and the LEDs. Here’s another point of difference with a delay() timer - if you release the button before the 1.5 seconds are up the timer resets and isTimer1Running goes false. If delay() had been used, once the LED was on it would be on the entire time designated because the code cannot break out of delay() to check the switch status.

// A basic timer that runs when enable is false (INPUT_PULLUP button configuration)
// else is reset.  isTimer1TimedOut true only when timer1AccValue >= timer1Preset,
// false otherwise.  A timer running output is provided to signify the timer is
// enabled to run but has not yet reached its preset value.

uint32_t const timer1Preset = 1500; // 1.5 seconds
uint32_t timer1PreviousMillis;
uint32_t timer1CurrentMillis;
bool isTimer1TimedOut, isTimer1Enabled, isTimer1Running;

const byte enablePin = A3; // A0-A5 can be used as digital inputs
const byte externalLED = A0;
//
//-----------------------------
//

void setup() {
  pinMode(enablePin, INPUT_PULLUP); // INPUT_PULLUP avoids the need for a discrete resistor.
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(externalLED, OUTPUT); // development only - remove for publishing
  Serial.begin(115200);
  Serial.println(__FILE__);
  Serial.print("\n\nTimer  Timer Timer\n");
  Serial.println("Value  Done  Running");
  displayFunction(timer1CurrentMillis - timer1PreviousMillis, isTimer1TimedOut, LOW);
  delay(3000);
}

void loop() {

  bool isButtonPressed = (bool) !digitalRead(enablePin); // update button state before proceeding

  timer1CurrentMillis = millis(); // Take a snapshot of millis() to use for processing.
  //
  // ------------------  Start of timer1 update activities  ------------------------
  //
  // If input is grounded, enable timer to run.
  if (isButtonPressed) {
    isTimer1Enabled = true;
    // Test the timer's completion status
    if (timer1CurrentMillis - timer1PreviousMillis >= timer1Preset) { // If time elapsed set isTimer1TimedOut
      isTimer1TimedOut = true;
    }
  }
  else {  // If timer not enabled it is reset
    timer1PreviousMillis = timer1CurrentMillis; // Reset the timer.
    isTimer1TimedOut = false;
    isTimer1Enabled = false;
  }
  // Timer running and not finished
  isTimer1Running = isTimer1Enabled and !isTimer1TimedOut;
  //
  // ------------------  End of timer1 update activities  --------------------------
  //
  
  digitalWrite(LED_BUILTIN, (isTimer1Running));
  digitalWrite(externalLED,isTimer1TimedOut);
  displayFunction(timer1CurrentMillis - timer1PreviousMillis, isTimer1TimedOut, isTimer1Running);

} // end of loop

void displayFunction(uint32_t timer1, bool timedOut, bool ledState) {

  // Display runtime values passed from caller
  Serial.print(timer1);
  Serial.print("\t");
  Serial.print(timedOut);
  Serial.print("\t");
  Serial.print(ledState);
  Serial.println();
}

++++++++++++++++++++++++++++++

1 If you examine the timer code you can see the original structure is still there. You can also see that the sketch has two new bools named isTimer1Enabled and isTimer1Running. The first tells us whether or not the timer run condition is met - for now it’s just a pushbutton but the conditions could be as complex as you want. The timer enabled to run and not yet timed is the same as it being somewhere between reset and finished. The logical combination of isTimer1Enabled and !isTimer1TimedOut gives us this information in the form of the single boolean variable isTimer1Running. Although isTimer1Running isn’t strictly necessary - here it’s a convenience to aid understanding – if this were used multiple times throughout the sketch it would make sense to use it for brevity and clarity’s sake.

Arduino Reference Page

There are times you want to know if something’s happening on a regular basis. Typically, this would be some digital sensor going on and off at some regular interval. An example would be checking a switch to know if some rotating mechanism is doing that – rotating. Maybe a pump failed. Maybe the water stopped flowing. Whatever the cause, if the pulses come in below some threshold rate an alarm is to be raised. The concept to realize this is called a retriggerable or watchdog timer. Here’s a link to a hardware version with a timing diagram.

Where the retriggerable timer, or one-shot, differs from the previous timers is that it needs a change on its input. A false-to-true transition will cause a single operation but, after the timer delivers its output it remains idle until another input transition of the correct direction – it could be true-to-false or even trigger on both transitions. An input state change of the correct type delivered before the current cycle ends will restart the timing cycle and the output stays on1. You can prove this by repeatedly pressing and releasing the pushbutton at half-second intervals.

// Runs on an UNO

// A retriggerable one-shot timer that runs when pulsed.

// Demonstrates:
// millis() timer operation
// state change detection
// sensing/indicating timer completion
// doing more than one thing at a time

uint32_t timer1AccValue;
uint32_t const timer1Preset = 1500; // 1.5 seconds
uint32_t timer1PreviousMillis;
uint32_t timer1CurrentMillis;
bool isTimer1Active;
bool isFirstPass;

const byte enablePin = A3; // A0-A5 can be used as digital inputs
const byte externalLED = A0;
//
//-----------------------------
//
void setup() {
  Serial.begin(115200);
  pinMode(enablePin, INPUT_PULLUP); // INPUT_PULLUP avoids the need for a discrete resistor.
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(externalLED, OUTPUT);
  Serial.print("\n\nTimer  Timer\n");
  Serial.println(" Acc   Done");
  displayFunction(timer1CurrentMillis - timer1PreviousMillis, isTimer1Active);
  delay(1000); // Pause for user to see column headings
}

void loop() {
  bool newTriggerPulse;
  static bool buttonWasPressedLastTime;

  bool isButtonPressed = (bool) !digitalRead(enablePin); // check button state before proceeding
  timer1CurrentMillis = millis(); // Take a snapshot of millis() to use for processing.
  //
  // The one-shot section
  //
  if (isButtonPressed and not buttonWasPressedLastTime) { // <- These two lines
    // if (isButtonPressed && !buttonWasPressedLastTime) // <- are equivalent
    newTriggerPulse = true;
  }
  else {
    newTriggerPulse = false;
  }

  buttonWasPressedLastTime = isButtonPressed;
  //
  // End one-shot
  //
  //
  // Begin timer update activities
  //
 
  if (newTriggerPulse) // Imagine a pulse like ____-____
  {
    timer1PreviousMillis = timer1CurrentMillis; // Reset the timer.
    isTimer1Active = true;
  }
  else {  // If newTriggerPulse is false, timing cycle runs
    if (timer1CurrentMillis - timer1PreviousMillis >= timer1Preset) { // If time elapsed set isTimer1Active
      isTimer1Active = false;
    }
  }
  //
  // End of timer1 update activities
  //
  digitalWrite(LED_BUILTIN, isTimer1Active);  // Send timer1's completion status to the onboard LED
  digitalWrite(externalLED, isTimer1Active);

  displayFunction(timer1CurrentMillis - timer1PreviousMillis, isTimer1Active);
} // end of loop

//--------------------------------------------

void displayFunction(unsigned long timer1, bool timedOut) {

  // Display runtime values passed from caller
  Serial.print(timer1);
  Serial.print("\t");
  Serial.print(timedOut);
  Serial.println();
}

+++++++++++++++

1 If the button has just changed state, the test of newTriggerPulse at the next if() statement ‘Begin timer update activities ‘ is true so the timer value is set equal to millis(), resetting the difference to zero and setting isTimer1Active true. The next time this code is evaluated newTriggerPulse will be false so we drop to the else and check to see if time is expired. If the button is pressed again before the cycle ends newTriggerPulse does its thing and the time value is reset and isTimer1Active remains true. If you stop pressing the button or, if you hold it pressed, 1500 milliseconds later when the state if (timer1CurrentMillis - timer1PreviousMillis >= timer1Preset) finally does become true isTimer1Active will be set false ending the timer cycle.

Arduino Reference Page

Thanks dougp !

:smiley:

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.