An alternative type of delay
One common question here that seems to gets asked every day, is about mitigating the blocking effects of the delay function. Once a delay has been called the Arduino can do nothing until this delay has been finished. This is often called blocking code and getting round this can be quite baffling for a beginner.
Often a beginner here is pointed to the blink without delay example code in the IDE. The problem is that this sort of pseudo multitasking is often a large step up from the sort of code a beginner is use to writing, but this is the universal answer which will work for any situation. This is done by implementing what is called a state machine.
Other times a beginner might get the advice about using the millis() timer, but often doesn't know what to do with it.
However, in some circumstances it is not necessary to go into a full state machine. There is a much simpler alternative technique which I have called a "commutable delay". When you commute something you stop it.
So imagine you have put together a sequence of effects for a display on an addressable LED strip, and have a push button that changes what pattern is currently showing. Using the normal delay function will not always work because your code will only look at the push button when the running pattern ends.
If these patterns are short this will appear to work, but the pattern could take many seconds to finish, and you would have to hold the push button down for a long time, until the pattern sequence finishes, and only then does your code gets a chance to look at the change button. Most beginners think this has stopped working but you "only" need to hold down the pattern change button until the current sequence ends. But, by writing an alternative delay function, you can get the delay to end early when it sees a button pressed. This is what a commutable delay function can do.
I prefer to show you how to do this yourself and not just hide things in a library. That way you can understand it and use the concept in a number of different ways.
First a few rules to observe when using the millis() timer.
All variables should be of the "unsigned long" type, or to make applicable to not only the 5V but also the newer 3V3 Arduinos you should use the uint32_t type, which specifically creates a 32 bit unsigned integer number.
Do not attempt to reset the millis() timer, just take the value it give and subtract from it the start time.
Let's start by looking this code:-
void altDelay(uint32_t delayTime){
uint32_t startTime = millis();
while(millis() - startTime <= delayTime) { } // do nothing
}
This uses the millis() timer but it is blocking code, that is it will only end when the delay time has expired. To make it into a commutable delay, the function also has to look at the state of some pin to see if it is time to end the delay early, like this:-
void commutableDelayPin(uint32_t delayTime, uint8_t changePin, bool commuteState){ // this will return when the delay time has expired or the change button is pushed.
uint32_t startTime = millis(); // the millis count when we start;
// now hold until either time is up or commute pin is pressed
while((millis() - startTime < delayTime) && (commuted != true)) { // keep the loop going until time is up or delay is commutated
if ( digitalRead(changePin) == commuteState){ // if you see the button being held down abandon the delay
commuted = true;
}
}
}
Notes on this code
This uses a global boolean variable called commuted, which is set to true, once the commute condition has been detected. This also stops the delay looking for the timeout condition to occur naturally and so the delay ends.
This specific example requires the change pin to have been set up in the setup function to be set to a :- pinMode(pin, INPUT_PULLUP) wired between the changePin and ground, and should be called with the commuteState as a LOW.
So what happens is, when you change all the delay() function calls in your code to generate a pattern, into a commutableDelayPin, no matter how deep a program in delays they all get short circuited, one after the other, in an instant.
The down side of this, is that there is nothing to stop all the delays of everything being in effect set to zero when the code reaches to point where your code looks at this push button. So to get round this you need something at the top of your loop function to catch this commuted state, reset the global commuted flag, and wait until the change button becomes not pressed. You can do this by creating another function to catch the fact that the delays have been commutated and wait for them not to be, and then increment your pattern number
void commutableCatchPin(uint8_t changePin, bool commuteState) { // check to see if the delay has been commuted and take action if it has been
if(commuted){ // we have had a commute state deal with it
commuted = false; // reset the global flag
//Serial.println("button pressed");
while(digitalRead(changePin) == commuteState) {
delay(50); // do nothing until button is released
}
//Serial.println("button released");
delay(200); // allow any button bouncing to stop
// now take action for when a delay has been commutated
// this could be to wipe an addressable LED string or to turn off the LED
digitalWrite(LED_BUILTIN, LOW); // turn LED off
// move on the pattern to produce
patternCount += 1 ; // move onto next pattern
if(patternCount >= patternCountLimit) {
patternCount = 0; // wrap round patternCount
}
//Serial.print("count on button ");Serial.println(patternCount);
} // end of dealing with a commute
}
So let's see how this all looks in a complete program.
/* commutableDelay with change state detection example by Grumpy_Mike Nov 2022
example assumes change button is wired correctly - that is between input pin and ground
note - remove the // to enable printing so you can see what is happening
to extend this to more options alter the variable "patternCountLimit"
and add extra cases to the switch statement
This example code is in the public domain.
*/
uint8_t changePin = 8; // or any other pin you want
uint8_t patternCount = 0; // initial pattern value
uint8_t patternCountLimit = 2; // how many patterns are they
bool commuted = false;
void setup() {
Serial.begin(9600); // for debug only
pinMode(changePin, INPUT_PULLUP); // for change pin wired between input and ground
pinMode(LED_BUILTIN, OUTPUT); // LED to flash
//Serial.println("code starting");
}
void loop() {
//Serial.println("start of loop");
commutableCatchPin(changePin, LOW);
// now see what function we want to call depending on the value in count
// Serial.println("Pattern number "); Serial.println(patternCount);
switch(patternCount) {
case 0:
flash1();
break;
case 1:
flash2();
break;
default:
Serial.print("no such pattern as "); Serial.println(patternCount);
break;
}
}
// flash functions - these are just oversimplified examples to flash LED_BUILTIN
void flash1(){ // slow one second on & off
for(int i = 0; i<20; i++){
digitalWrite(LED_BUILTIN, HIGH); // turn LED on
commutableDelayPin(1000, changePin, LOW); // one second delay
digitalWrite(LED_BUILTIN, LOW); // turn LED off
commutableDelayPin(1000, changePin, LOW); // one second delay
}
}
void flash2(){
for(int i = 0; i<20; i++){ //fast 200mS on & one second off
digitalWrite(LED_BUILTIN, HIGH); // turn LED on
commutableDelayPin(200, changePin, LOW); // delay
digitalWrite(LED_BUILTIN, LOW); // turn LED off
commutableDelayPin(1000, changePin, LOW); // delay
}
}
void commutableDelayPin(uint32_t delayTime, uint8_t changePin, bool commuteState){ // this will return when the delay time has expired or the change button is pushed.
uint32_t startTime = millis(); // the millis count when we start;
// now hold until either time is up or commute pin is pressed
while((millis() - startTime < delayTime) && (commuted != true)) { // keep the loop going until time is up or delay is commutated
if ( digitalRead(changePin) == commuteState){ // if you see the button being held down abandon the delay
commuted = true;
}
}
}
void commutableCatchPin(uint8_t changePin, bool commuteState) { // check to see if the delay has been commuted and take action if it has been
if(commuted){ // we have had a commute state deal with it
commuted = false; // reset the global flag
//Serial.println("button pressed");
while(digitalRead(changePin) == commuteState) {
delay(50); // do nothing until button is released
}
//Serial.println("button released");
delay(200); // allow any button bouncing to stop
// now take action for when a delay has been commutated
// this could be to wipe an addressable LED string or to turn off the LED
digitalWrite(LED_BUILTIN, LOW); // turn LED off
// move on the pattern to produce
patternCount += 1 ; // move onto next pattern
if(patternCount >= patternCountLimit) {
patternCount = 0; // wrap round patternCount
}
//Serial.print("count on button ");Serial.println(patternCount);
} // end of dealing with a commute
}
This test code just flashes the LED built into your Arduino in two ways, either a slow one second on one second off way, or by a quick flash of the LED followed by a second off. These are just two simple functions that would take a long time to complete due to the fact that they are inside a for loop, that in the case of flash1 would take 20 * 2 = 40 seconds to complete. Yes I know you could get the same effect by not using a for loop, but these functions just illustrate a pattern that would normally take a long time to finish.
In practice these could be any patterns produced by an addressable LED library. Most example patterns in an addressable LED library are blocking code that will take a long time to finish.
If for some totally insane reason you want to detect a pin that is wired up incorrectly, that is with an external pull down resistor and the button connecting the pin input to the positive processor supply voltage, that will be 5V or 3V3 depending on the processor.
(note NOT THE INPUT VOLTAGE TO THE INTERNAL REGULATOR otherwise you will destroy your Arduino)
Then in the setup function the changePin needs to be set to a :- pinMode(changePin, INPUT) and pass a HIGH commuteState when calling both the commutableCatchPin() and commutableDelayPin() functions.
Other commutable states.
While this example uses a push button wired to a pin to act as the commutate trigger, other things also could be used. For example when the serial input buffer has a certain number of bytes in it, that could trigger the commutation, by looking at the value returned by Serial.available() method call. Of course kit would be better if you then did not call the two functions commutableCatchPin() and commutableDelayPin() but something like commutableCatchSerial() and commutableDelaySerial() as well as changing the code to detect the commutable conditions.
If you have questions about this tutorial, or difficulty in making it work for you then please start a specific thread about this in the "Project Guidance" or "LEDs and Multiplexing section" of the forum.
Only reply to this thread if you spot errors in spelling or concepts or have some other constructive comments. These will be edited into this thread so there should be no need to read all the replies.