Commented No-block Button and Led example

2 sketches are examples for beginners.
The first shown is the latest. The second sketch of 2.
It does not show what would bury the main lesson in details.
It does some entangling of tasks, save that for later.

The second sketch here is the delay version just to show the difference in code and how it acts. The top sketch is a conversion of the delay version to a non-blocking version.

// NonBlockedSketchLedAndButton v1.1 by GoForSmoke 03/15/24
// compiles, untested so far
// expect: ground pin 7 to toggle blink off/on

const byte blinkPin = 13; // Uno board LED pin13

const byte buttonPin = 7; // a jumper from pin 7 to GND = pressed
byte buttonStateNow, buttonStatePrev; // to compare what is to what was
const byte debounce = 10; // ms delay while the contacts settle, dirty signal
unsigned int debounceStart; // interval is debounce above

byte blinkState;  // led13 0=OFF, not-0=ON
unsigned long blinkStart, blinkInterval = 500; // ON/OFF times are equal
const unsigned long interval = 500; // so blinkInterval can reset after change


void setup()
{
  pinMode( blinkPin, OUTPUT ); // LOW by default
  // blinkState is 0 by default
  pinMode( buttonPin, INPUT_PULLUP ); // pin reads 0 when the button to ground is down
  buttonStateNow = buttonStatePrev = 1;  // start with button up as last pin read
}

void blink()  // only blinks when blinkInterval > 0
{
  static unsigned long waitStart, waitMs; // time values, always unsigned
  if ( blinkInterval == 0 ) // here is where the blink is stopped if true
  {
    return; // let loop() run so that this can check later.
  }

  if ( waitMs > 0 ) // if-timer runs when set then turns itself off
  {
    // working with time, End Time - Start Time = Elapsed Time
    // that formula always works even across rollover (no problem).
    if ( millis() - waitStart >= waitMs )  // see if wait is over
    {
      waitMs = 0; // wait is over, turn wait off!
    }
  }
  else
  {
    blinkState = !blinkState; // != is NOT =, toggle 0 <==> 1
    digitalWrite( blinkPin, blinkState ); 
    waitMs = blinkInterval; 
    waitStart = millis();
  }
  return;
}


void button()  // this works, it's not the quickest but is simpler than quicker code!
{
  static byte State = 0;  //  local variable only the function uses
  static unsigned long waitStart, waitMs;  // local vars

  if ( waitMs > 0 ) // if-timer for delays set in this function
  {
    if ( millis() - waitStart >= waitMs ) // check if elapsed time >= wait time
    {
      waitMs = 0;
    }
  }
  else // no timer delay this time
  {
    switch ( State )  // now we run the state machine!
    {
      case 0 : // looking for change from button WAS up and is NOW down.
        buttonStateNow = digitalRead( buttonPin );
        if ( buttonStateNow == 0 && buttonStatePrev == 1 ) // press detected
        {
          waitMs = debounce;  // time to spend ingnoring button bounce
          waitStart = millis();  // setting start time to NOW
          State = 1;
        }
        else if ( buttonStateNow == 1 && buttonStatePrev == 0 ) // release detected
        {
          waitMs = debounce; // setting the interval of ignoring the pin
          waitStart = millis();  // when the blink is to start... is NOW
        }
        buttonStatePrev = buttonStateNow;  // done with the read, make it previous
        break; // leave the switch-case

      case 1 :
        if ( blinkInterval > 0 )  // if the blink is still active
        {
          blinkInterval = 0; // stop blinking
          digitalWrite( blinkPin, LOW ); // the led may be ON or OFF, make sure it's OFF
        }
        else // the blink must be OFF
        {
          blinkInterval = interval; // blink at interval, restarts blinking
        }
        State = 0; // so now it goes back to waiting for button press
        break; // exit the switch-case
    }
  }
  return;
}

void loop() // this will run so fast the order of functions makes no difference
{
  button();
  blink();
}

This is the Blocked version, a before picture the above was made from. Together they may help beginners get non-blocking code.

// BlockedSketchLedAndButton v1.1 by GoForSmoke 03/13/24 
// for 1.1 a lot of comments have been added.

// This sketch is not PERFECT. 
// It explores doing 2 things "at once" with delay-code.
// How responsive the button acts says how well it works.
// this began with a simple Blink With Delay Sketch.
// This sketch adds an ON/OFF toggle button with delay debounce.

// compiles, untested so far
// expect: ground pin 7 to toggle blink off/on
// expect this to take a while to notice button action.
// expect the button response to be quicker when blink is OFF.
// this is a Before Sketch. The After Sketch does not Block Execution.

// note for Beginners
// Arduino variable type byte can be 0 to 255, easy to type.
// variable type unsigned int can be 0 to 65535, Arduino name is word.
// Where an int uses 16 bits of RAM, Arduino byte uses only 8.
// When I only need a byte, I don't use more on Arduinos.

const byte debounce = 10; // ms delay to ignore contact bouncing.
const byte blinkPin = 13; // LED pin to blink
const byte buttonPin = 7; // the name says it

byte buttonStateNow;  // pin state may be 0 (LOW) or not-0 (HIGH).
byte buttonStatePrev; // see if pin state changed from Prev to Now.
byte blinkState;  // the LED is 0 (LOW) or not-0 (HIGH)
// using 0 for LOW and not-0 for HIGH gives options not used here
// and that is the way the chip works. 
// When you have 8 bits for one T/F... they can ALL be used.

unsigned int blinkInterval = 500; // so far the blink ON time = OFF time
const unsigned int interval = 500; // to restore the working value
// the button works by making blinkInterval = 0 or = interval

void setup() 
{
  pinMode( blinkPin, OUTPUT ); // LOW by default
  // blinkState = 0; // by default
  pinMode( buttonPin, INPUT_PULLUP );  // up is 1, down is 0
  buttonStateNow = buttonStatePrev = 1; // start values initialized
}

void blink() // can be turned OFF and ON through blinkInterval
{
  if ( blinkInterval > 0 ) // button toggled to ON by default
  {
    blinkState = !blinkState; // 0 <==> 1
    digitalWrite( blinkPin, blinkState ); // led blink
    delay( blinkInterval );
  }
}


void button() // works by changing blinkInterval, OFF turns LED OFF.
{
  buttonStateNow = digitalRead( buttonPin );

  // button press detect, state was 1, now 0
  if (( buttonStateNow == 0 ) && ( buttonStatePrev == 1 )) // press detected
  {
    delay( debounce ); // contact switch changes are briefly DIRTY

    if ( blinkInterval > 0 )  // if blink is ON, turn it OFF
    {
      blinkInterval = 0; // stop blinking
      blinkState = 0;
      digitalWrite( blinkPin, blinkState );
    }
    else    // if blink is OFF, turn it ON
    {
      blinkInterval = interval; // blink interval restored
    }
  }
  // button release detect, state was 0, now 1
  else
  {
    if (( buttonStateNow == 1 ) && ( buttonStatePrev == 0 )) // release detected
    {
      delay( debounce ); // DIRTY on both press and release, a few ms.
    }
  }

  buttonStatePrev = buttonStateNow;  // button done, make Prev = Now
}

void loop()
{
  button();  // which comes first doesn't really matter
  blink();   // try running blink first if yer not sure!
}
    digitalWrite( blinkPin, blinkState ); // led ON

The comment is close, but no cigar :grinning:

1 Like

Dammit, that's twice and I thought I fixed it the first time!
fixed in thread and source

Am I even close taking this approach of giving a blocking version then converting to unblocked? I could start with delay-blink then add the button before showing no-block.. my motive is to eventually show that what can do one thing with delay can be converted and run along with other sketches that were converted or written non-blocking.

It is about combining sketches and having sketch-toolboxes.

1 Like

What is the purpose of this, another thread which seems like the one you had some days ago? What was the end of that thread, are these all new examples or, well, you know.

Who is the intended audience? Do you plan to write about it for them outside the comments in the code?

And I have to ask again why you detect separately a press from a release? I can't recall seeing many examples code that does not detect a change, then within that use the current state to deduce whether it was a press or a release.

Of course either way works. I think the more like what ppl will see elsewhere, the better. I would say the same thing about using word as a type. I am unable to remember what that is every time. You use unsigned long, what would be bad about using int or unsigned int, whichever may be word?

a7

This is the fully commented release.
Last time there were too few.
Last time is why I started this with qualifications about purpose, tldr?

Looking for review, the material is for beginners but not total noobs.

Both press and release BOUNCE so I debounce the release before I go watching for the next press and I debounce the press same why.

The button handler is a task that runs, not an integrated section of a code monolith. It expects asynchrous events.

I want to show combining sketches without having to restructure any of it, a sure road to spaghetti mountain. Been there, got smarter.

In Arduino-verse, programs are sketches not pages of greenbar code and fittingly, word takes 4 keystrokes while unsigned int and uint16_t take more. Having differences to PC coding keeps me on the lookout for old habits in a new environment. Your calcification may vary...

I use word for Arduino beginners and my own poor, old, fingers.

:smiley:

For me, intN_t and uintN_t immediately make clear how big the variable is. That can be important when coding for 8-bit and 32-bit platforms :wink: An int on an ESP or ARM is 4 bytes, on AVR it's 2 bytes.

When I started Arduino I had to stop my fingers from typing int when I wanted an integer just due to old reflexes... that no new coder has.

I broke that habit for this new to me system. I looked at it for what it's supposed to be instead of turning it into what i already knew.

Using Arduino definitions helps me to remember "where I am".

Maybe i should change that just so beginners learn that as rote.
Then they won't have to be told they were wrong later on even if the hex will be the same.

I will likely change the source. Company here now so laters.

So I have changed type word to type unsigned and edited the first post to reflect that.

And here is the non-blocking example with a loop counter task added.
LoopCounterLT only runs loop() over 370KHz.
With the button and led idle blinking loop() runs over 89KHz.
That is how often the button gets checked and blink gets serviced.

// NonBlockedSketchLedAndButton v1.1 by GoForSmoke 03/15/24
// compiles, untested so far
// expect: ground pin 7 to toggle blink off/on

// thise example has loop counter added, loop() speed averages ~89057 Hz 

const byte blinkPin = 13; // Uno board LED pin13

const byte buttonPin = 7; // a jumper from pin 7 to GND = pressed
byte buttonStateNow, buttonStatePrev; // to compare what is to what was
const byte debounce = 10; // ms delay while the contacts settle, dirty signal
unsigned int debounceStart; // interval is debounce above

byte blinkState;  // led13 0=OFF, not-0=ON
unsigned long blinkStart, blinkInterval = 500; // ON/OFF times are equal
const unsigned long interval = 500; // so blinkInterval can reset after change

extern volatile unsigned long timer0_millis; // might be faster than millis()
void LoopCounter() // tells the average response speed of void loop()
{ // inside a function, static variables keep their value from run to run
  static unsigned long count; // only this function sees this
  static bool lastBit10Set; // only this function sees this
  word millis16 = timer0_millis;

  count++; // adds 1 to count after any use in an expression, here it just adds 1.

  bool currentBit10Set = millis16 & 0x0400; // leverage integral to bool implicit promotion
  if (currentBit10Set != lastBit10Set) // 1 second
  {
//    Serial.print( millis16 ); // 16-bit binary into decimal text, many micros
//    Serial.write('\t');
    Serial.println( count ); // 32-bit binary into decimal text, load of cycles!
    count = 0; // don't forget to reset the counter
    lastBit10Set = currentBit10Set; //  changes 0<==>1 
  }
}

void setup()
{
  Serial.begin( 115200 );
  pinMode( blinkPin, OUTPUT ); // LOW by default
  // blinkState is 0 by default
  pinMode( buttonPin, INPUT_PULLUP ); // pin reads 0 when the button to ground is down
  buttonStateNow = buttonStatePrev = 1;  // start with button up as last pin read
}

void blink()  // only blinks when blinkInterval > 0
{
  static unsigned long waitStart, waitMs; // time values, always unsigned
  if ( blinkInterval == 0 ) // here is where the blink is stopped if true
  {
    return; // let loop() run so that this can check later.
  }

  if ( waitMs > 0 ) // if-timer runs when set then turns itself off
  {
    // working with time, End Time - Start Time = Elapsed Time
    // that formula always works even across rollover (no problem).
    if ( millis() - waitStart >= waitMs )  // see if wait is over
    {
      waitMs = 0; // wait is over, turn wait off!
    }
  }
  else
  {
    blinkState = !blinkState; // != is NOT =, toggle 0 <==> 1
    digitalWrite( blinkPin, blinkState ); 
    waitMs = blinkInterval; 
    waitStart = millis();
  }
  return;
}


void button()  // this works, it's not the quickest but is simpler than quicker code!
{
  static byte State = 0;  //  local variable only the function uses
  static unsigned long waitStart, waitMs;  // local vars

  if ( waitMs > 0 ) // if-timer for delays set in this function
  {
    if ( millis() - waitStart >= waitMs ) // check if elapsed time >= wait time
    {
      waitMs = 0;
    }
  }
  else // no timer delay this time
  {
    switch ( State )  // now we run the state machine!
    {
      case 0 : // looking for change from button WAS up and is NOW down.
        buttonStateNow = digitalRead( buttonPin );
        if ( buttonStateNow == 0 && buttonStatePrev == 1 ) // press detected
        {
          waitMs = debounce;  // time to spend ingnoring button bounce
          waitStart = millis();  // setting start time to NOW
          State = 1;
        }
        else if ( buttonStateNow == 1 && buttonStatePrev == 0 ) // release detected
        {
          waitMs = debounce; // setting the interval of ignoring the pin
          waitStart = millis();  // when the blink is to start... is NOW
        }
        buttonStatePrev = buttonStateNow;  // done with the read, make it previous
        break; // leave the switch-case

      case 1 :
        if ( blinkInterval > 0 )  // if the blink is still active
        {
          blinkInterval = 0; // stop blinking
          digitalWrite( blinkPin, LOW ); // the led may be ON or OFF, make sure it's OFF
        }
        else // the blink must be OFF
        {
          blinkInterval = interval; // blink at interval, restarts blinking
        }
        State = 0; // so now it goes back to waiting for button press
        break; // exit the switch-case
    }
  }
  return;
}

void loop() // this will run so fast the order of functions makes no difference
{
  LoopCounter(); // the function runs as a task, the optimizer will inline the code.
  button();
  blink();
}

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