IO program control

Hello,

I am brand new to the forum. I am a PLC programmer but do not have any experience in C++ so I’m doing my best to research/learn more about the language and what I can do with it to program Arduino’s. If anyone has any good learning resources please send them my way. My question is very basic/entry level, I am trying to control a portion of a basic flashing program with a maintained switch. When I turn the maintained switch on my output will begin flashing according to the program, and when the maintained switch is turned off the output will stop flashing. Very simple I realize that. My question is, how can I control my output with a maintained switch? Is there a way to have my flashing portion of the program running in the background and just have the maintained switch control the output? Or is there a way to control the actual flashing sequence portion of the program?

Welcome. You can do whatever is needed :wink:

I don't have any resources for you except for the arduino reference and the web in general.

To read a switch, you can read up on digitalRead() - Arduino Reference.

In it's simplest form (loop() only, switch wired between pin and GND)

void loop()
{
  if (digitalRead(somePin) == LOW)
  {
    runPattern();
  }
}

void runPattern()
{
  ...
  ...
}

If you did not set up your code correctly from the beginning, you probably will sooner than later find out that if your pattern is running and takes a lot of time, your code will not react immediately on the switch because your code waits for the runPattern() to be finished before it returns to loop(). This is called blocking; you need to stay away from for-loops, while-loops etc. if they contain delays.

There are plenty of examples how to work around that. E.g. on the forum

  1. The blink-without-delay example in the IDE
  2. Using millis() for timing. A beginners guide
  3. Demonstration code for several things at the same time
  4. Flashing multiple LEDs at the same time
  5. State machines, a short tutorial

Because you asked about blocking the output, below demonstrates a way to do that.

const uint8_t switchPin = 2;
const uint8_t ledPin = LED_BUILTIN;

void setup()
{
  Serial.begin(115200);


  pinMode(switchPin, INPUT_PULLUP); // switch connected between pin and GND
  pinMode(ledPin, OUTPUT);
}

void loop()
{
  // simulate pattern
  uint8_t ledState = random(0, 2);
  
  // debug
  Serial.print("ledState if switch is pressed: ");
  Serial.println(ledState);
  // if switch is closed
  if (digitalRead(switchPin) == LOW)
  {
    Serial.println("Setting led");
    // set led state
    digitalWrite(ledPin, ledState);
  }

  // slow serial output down
  delay(2000);
}

look this over

const byte PinInp = A1;
const byte PinOut = LED_BUILTIN;    // 13

const unsigned long MsecPeriod = 500;   // Capitalize constants
      unsigned long msecLst;

void
loop (void)
{
    unsigned long msec = millis ();
    if (LOW == digitalRead (PinInp) && (msec - msecLst >= MsecPeriod)) {
        msecLst += MsecPeriod;
        digitalWrite (PinOut, ! digitalRead (PinOut));
    }
}

void
setup (void)
{
    Serial.begin (9600);
    pinMode (PinInp, INPUT_PULLUP);
    pinMode (PinOut, OUTPUT);
}

see The C Programming Language

Several years ago, I ran across a post from Robin2 with the title " many things at a time " or something similar to that. I am a hardware guy but am teaching myself programming. I found this post very informative and useful !! Am I now a programmer ?? HELL NO, but I can now test my hardware and also write some programs to perform various functions - still learning !!! Tom

Having some PLC experience, I'll throw in a comment here.

You will quickly move beyond the "see spot run" stage. You may find it useful to write your programs using the Input Process Output model, to enforce a structure similar to that of the PLC I/O scanning I remember.
In IPO, we scan inputs, execute all process logic, then output the results.
This avoids the 'scattershot' read/writes you will see in many program examples, here and elsewhere.

One thing to understand is when you set an output HIGH it's going to stay in that state 'til you explicitly set it LOW. Just making the logic which turned it on go false won't do it. Think of outputs as latches -(OTL)-, -(OTU)-.

You don't have to write flasher code right off the bat, there are myriad libraries out there that function more or less like a black box. Input goes true LED flashes, input goes false LED turns off.

The Arduino language already contains a timing function named millis(), which Arduino board are you using?

That is helpful, from playing around they are acting like latches but for some reason I just thought of it as an OTE. Thank you for the knowledge!

Arduino Uno

Your first example seems to make some sense to me but I am still having some issues executing it and I believe my issues are due to poorly written code by me. I will attach the part I am having an issue with, maybe you have a better way of writing it! I will do some reading on some of the resources you sent me but will sit tight on messing with the code until I receive some insight from you. Thank you :smiley:

void loop() {
  if (digitalRead(SWITCH) == LOW) {
    runPattern();
  }
}

void runPattern() {
  blinkRled(160);
}

void blinkRled(int milliseconds) {
  digitalWrite(RLED, HIGH);
  delay(milliseconds);
  digitalWrite(RLED, LOW);
  delay(milliseconds);
}

What kind of issues?

Run time issues are often not where you think they are; so please post your full sketch. I've guessed what the missing pieces are and that works as expected.

#define RLED 13
#define SWITCH 2

void setup()
{
  pinMode(RLED, OUTPUT);
  pinMode(SWITCH, INPUT_PULLUP);
}

void loop()
{
  if (digitalRead(SWITCH) == LOW) {
    runPattern();
  }
}

void runPattern()
{
  blinkRled(160);
}

void blinkRled(int milliseconds)
{
  digitalWrite(RLED, HIGH);
  delay(milliseconds);
  digitalWrite(RLED, LOW);
  delay(milliseconds);
}

Your program demonstrates the use of blocking code; the switch will only be read every (roughly) 320 ms.

You are real close.

As others have noted, the do many things at once lesson is critical and state machines combine to:
do many things one step at a time each at the same time.

We learn programming by crafting one block does everything as an approach... next step to IPO automation is to break that.

Inside of void loop() there should be multiple functions that interact as needed to do the job together. It is tasking cooperatively without an OS much like the PLC.

In void loop(), put

Input functions to watch Inputs that update global status variables, flags or more complex values. Maybe one for a button or set of buttons or serial channel or other Input.

Process functions to watch Input status/data and process data if wanted and decide what to do by setting flags or data like Input functions do.

Output functions that read flags/data and make the same and/or control leds/motors/serial output.

Amd they all work together as smoothly as none holds the rest up, aka block execution.
And loop() runs over and over. With an Uno it's not a stretch to run loop() at 50KHz on average which you can see using a loop counter function in void loop(). The code just has to make the steps run short enough to not block that speed, state machines may need to split state cases and the overall not try to ask more than there's cycles for in 20 microseconds on average which for an Uno could literally be 100's of things at once.
A 10x10 button matrix task can reasonably check all 100 buttons once a millisecond for instance.

I have posted examples of this many times starting 2012 here.


It's toolbox-ready to have functions that act as drivers for different Inputs and Outputs and general Processes to use in the main loop(). As long as none of them block they can run together at speed, none stuck inside of the if() of another but maybe waiting on a flag or data or set time to be triggered.

That is where "do many things at once" can getcha, fast and smooth tasking on a single thread without using interrupts and the overhead of an OS. BTW, engineers have been doing this since the 70's or before... Charles Moore was doing it to aim observatory telescopes in 1959.

It will take naby hours of practice to learn basic techniques into long term memory. Have patience and you can get there.

This forum always has helpers at different levels and archives a great many solutions that require some digging and sifting but should save time over figuring everything out for yourself. We even have PLC coders who know the C/C++ "dark side" too!

There are boards out and software that do program using ladder logic, some are screamers compared to the Uno! The RPi PICO if I recall has that, it's a 133Mhz dual-core AMD M0 that the low end model costs around $5, the WIFI version isn't too much more.

You have loads of options and more new ones all the time!

Is there any way we can get that switch to be read quicker? Using that first example you gave, would that one be a quicker method? 320 ms is quite slow. Also can you explain the difference between INPUT and INPUT_PULLUP ?

I will have to check the RPi PICO out since it uses ladder, however I would like to work on developing Arduino and related programming skills. Ladder, FBD, SFC, etc is something I already understand, I enjoy the challenge of learning something new.

Here's an example that is not by-the-rules IPO but does

use a button to control a led and has a loop counter to show average speed. Button letency includes software debounce.

Made on Uno.

// 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();
}

That sketch is to show a method of getting rid of delay.
This is the "before" example that uses delay to compare.
Un-blocking sketches can be done with cut-and-dry techniques, whatever runs alone using do-one-thing-at-a-time with delay and/or other blocking code can be converted to do-many code that can be combined with other non-blocking code iswhat the two sketches seek to demonstrate.

// 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!
}

If you use INPUT, the pin is in a high impedance state and can pick up noise from the environment resulting in random HIGH or LOW. You can use an external resistor to pull the pin to a defined level (either GND or Vcc) to prevent that random reading; the input will be LOW or HIGH unless you force it externally to another level. A number of microcontrollers have that pull-up resistor built-in and you can enable that when you use INPUT_PULLUP.

You can have a look at the below image (by @LarryD)
image

Note
Some microcontrollers also have a built-in pull-down resistor; you will have to consult the datasheet (to save you the time, the AVR microcontrollers like the n328P in the Uno don't).

The code shown by @GoForSmoke demonstrates how it can be done. It might be slightly overwhelming so I have taken your blinkRled() to describe the process.

That function has four steps (LED high, delay, LED low and delay). You can change that to two steps by keeping track of the state of the LED.

void blinkRled(int milliseconds)
{
  // remember the state of the LED; initial state HIGH
  static uint8_t ledState = HIGH;
  // set the LED to the current state
  digitalWrite(RLED, ledState);
  // change the LED state to the next state
  ledState = !ledState;
  // wait
  delay(milliseconds); 
}

This code will behave (nearly) the same as your code but it will now only block for 160ms. The difference is that if you release the switch when the LED is on or off, the LED will stay in that state.
Notes:

  1. uint8_t is the same as byte; I prefer the former as it will allow a little more consistency in my code as can be seen later.
  2. The variable ledState is local to the function. Local variables are only known inside the function and they will be lost when the function if finished. Use of the static keyword prevents that the value is lost when the function ends; from that perspective it is like a global variable that you declare outside a function.

In the next step you can get rid of the delay. We add a variable to keep track of the time that the last change happened.

void blinkRled(uint16_t milliseconds)
{
  // remember the state of the LED
  static uint8_t ledState = HIGH;

  // last time that the LED was updated
  static uint32_t lastUpdateTime;

  ...
  ...
}

Notes:

  1. All variables related to timing should be unsigned. uint32_t is the same as unsigned long but less typing :wink:
  2. The parameter for the function is now an unsigned 16-bit integer. The main reason to use the uint16_t instead of unsigned int is that the size of an int depends on the architecture; on an 8-bit processor it's 16 bits (two bytes), on a 32-bit processor it's 32 bits (4 bytes); the use of uint16_t (and int16_t) prevents confusion and possibly errors.
  3. The choice of 8-bit vs 16-bit vs 32-bit variables depends on your needs; if you have limited memory (as in the 328P on the Uno) you should limit the size of the variable to the minimum needed.
    If your delay will never exceed 255 milliseconds, you use an uint8_t, if your delay will never exceed 65535 milliseconds you use uint16_t and else you use an uint32_t (good for 49 days and a bit); if you need longer delays, it gets a bit more complicated because the millis() function that we're going to use can only handle the maximum of 49 days and a bit.

And next you use that variable to check if it's time to do something

void blinkRled(uint16_t milliseconds)
{
  // remember the state of the LED
  static uint8_t ledState = HIGH;

  // last time that the LED was updated
  static uint32_t lastUpdateTime;

  // check if it's time to update the LED
  if (millis() - lastUpdateTime >= milliseconds)
  {
    // set the LED to the current state
    digitalWrite(RLED, ledState);
    // change the LED state to the next state
    ledState = !ledState;

    // remember the time that the last update was done
    lastUpdateTime = millis();
  }
}

This again behaves nearly the same as the previous version but now will immediately react on the switch being released.

  1. If you press the switch within your 160 milliseconds, the LED will not immediately switch on because millis() is less than 160 ms
  2. If you release the switch somewhere in a 160 milliseconds period and press it again, the function will initially continue where it was.

You can solve the first problem by adding another variable so the function always will set the first state when initially entered.

void blinkRled(uint16_t milliseconds)
{
  // remember the state of the LED
  static uint8_t ledState = HIGH;

  // last time that the LED was updated
  static uint32_t lastUpdateTime;

  // flag to idnciate that the we need to execute the first time
  static bool isRunning = false;

  // check if it's the first time or that it's time to update the LED
  if (isRunning == false || millis() - lastUpdateTime >= milliseconds)
  {
    isRunning = true;
    
    // set the LED to the current state
    digitalWrite(RLED, ledState);
    // change the LED state to the next state
    ledState = !ledState;

    // remember the time that the last update was done
    lastUpdateTime = millis();
  }
}

Up to this point, the other parts of your code can stay unchanged.

The second problem (if it is a problem) can e.g. be solved as shown in @GoForSmoke's code by passing 0 for the milliseconds parameter and resetting the ledState and the isRunning flag.

The below full code demonstrates

#define RLED 13
#define SWITCH 2

// a global variable to remember the switch state
uint8_t switchState;

void setup()
{
  pinMode(RLED, OUTPUT);
  pinMode(SWITCH, INPUT_PULLUP);
}

void loop()
{
  // read the switch
  switchState = digitalRead(SWITCH);

  // run the pattern
  runPattern();
}

/*
   Run the pattern
*/
void runPattern()
{
  uint16_t redDelayTime;
  if (switchState == LOW)
  {
    redDelayTime = 160;
  }
  else
  {
    redDelayTime = 0;
  }

  blinkRled(redDelayTime);
}

/*
   Blink the red LED
   In:
    on time / off time
*/
void blinkRled(uint16_t milliseconds)
{
  // remember the state of the LED
  static uint8_t ledState = HIGH;

  // last time that the LED was updated
  static uint32_t lastUpdateTime;

  // flag to idnciate that the we need to execute the first time
  static bool isRunning = false;

  // if reset instructed
  if (milliseconds == 0)
  {
    // clear the flag so the next time the pattern is immediately started
    isRunning = false;
    // switch the LED off
    digitalWrite(RLED, LOW);
    // reset the ledState
    ledState = HIGH;
    // nothing else to do
    return;
  }

  // check if it's the first time or that it's time to update the LED
  if (isRunning == false || millis() - lastUpdateTime >= milliseconds)
  {
    isRunning = true;

    // set the LED to the current state
    digitalWrite(RLED, ledState);
    // change the LED state to the next state
    ledState = !ledState;

    // remember the time that the last update was done
    lastUpdateTime = millis();
  }
}

Note the change in blinkRled() and the use of a global variable for the switch state; the global variable allows you to use that variable in any function. loop() and runPattern() are slightly changed.

Notes on variable scope (do some research on the web):

  1. Local variables and static local variables can only be changed in that function.
  2. Global variables can be changed anywhere; which create the risk that you modify them by accident in a function where they are not supposed to be changed.
  3. The IDE counts global and static local variables to give you the amount of memory used; it does not count local variables. So if you have a lot of local variables in a function you might run out of (RAM) memory. The IDE also does not count dynamically allocated memory (as e.g. used in Adafruits NeoPixel library).

Oh. I'm a bit deaf there.

I'm used to taking 2 weeks to a month to 'get' an idea down solid!

You can easily do both. BUT! You need to have a clear idea of what you mean by "flashing". If it is simple on/off with time for on and time for off, then you can use millis() to clock the periods of on and off. Than means setting up your own timers and NOT using delay() to do the on/off.

I want the flashing around 35hz , in you opinion what would be the best way of doing that? Timers like you stated?

*uint_32 is pointer to 32-bit unsigned integer.
unsigned long is a 32-bit unsigned integer.

typing underscore takes using the shift key which I find a slowdown for my typing fingers.

Arduino millis low 8 bits never equals 255 (or 5 other values) and never exceeds 255 which makes catching >= 255 with uint8_t impossible. It's still good for >= 254 as long as the test is run once or more in that milli. I would suggest picking a low enough limit that it is never missed!
How do I know? Because that bug bit me before! Been there, fixed it in my own code!