Millis-based code to replace servo delay?

First, a big thank you to Robin2 for his excellent ‘Several things at once’ tutorial. I’m working my way through that whole thread and finally getting the hang of millis.

Based on that I successfully coded a circuit to flash two LEDs with different OFF intervals. I now want to operate two servos with similar non-blocking independence. I’m hoping that for any simple code like the example four lines in loop() there’s a relatively simple equivalent using millis. So that I can use it as a sort of template.


#include <VarSpeedServo.h>
VarSpeedServo myservo;

void setup()
{
myservo.attach(9);
myservo.write(outPos);
}

void(loop)
{
myservo.write(inPos);
delay(waitBeforeRelease);
myservo.write(outPos);
delay(waitAfterRelease);
}

Terry

Why doesn't this look like setup?

Post that code so we can see how you are doing that. It might be very close to being able to handle your new goal. At the very least it will help us know how far you’ve gone on what path - lotta ways to skin a cat.

a7

one solution is to use a switch-case state-machine.

The mutually exclusive execution of always only a part of the code
saves a lot of flag-variables that would be nescessary otherwise


const byte myLED1_Pin = 12;
const byte myLED2_Pin = 13;

unsigned long currentMillis = 0;

unsigned long StartOfLED1On  = 0;
unsigned long StartOfLED2On  = 0;

unsigned long waitBeforeInterval = 1000;

unsigned long waitAfterInterval = 2000;

const byte LED1On = 1;
const byte waitBeforeRelease = 2;
const byte LED2On = 3;
const byte waitAfterRelease = 4;

byte myStateVar;

void timingStateMachine() {
  currentMillis = millis();
  
  switch (myStateVar) {
    case LED1On:
      digitalWrite(myLED1_Pin,HIGH);
      StartOfLED1On = currentMillis;
      myStateVar = waitBeforeRelease;
      break; // <== break makes execution mutually exclusive !!

    case waitBeforeRelease:
      if (currentMillis - StartOfLED1On >= waitBeforeInterval) {
        myStateVar = LED2On;
      }
      break;

    case LED2On:
      digitalWrite(myLED2_Pin,HIGH);
      StartOfLED2On = currentMillis;
      myStateVar = waitAfterRelease;
      break;

    case waitAfterRelease:
      if (currentMillis - StartOfLED2On >= waitAfterInterval) {
        digitalWrite(myLED1_Pin,LOW);
        digitalWrite(myLED2_Pin,LOW);
        myStateVar = LED1On;                       
      }
      break;
  }    
}


void setup() {
  Serial.begin(115200);
  Serial.println( F("Setup-Start") );

  digitalWrite(myLED1_Pin,LOW);
  digitalWrite(myLED2_Pin,LOW);
  pinMode(myLED1_Pin,OUTPUT);
  pinMode(myLED1_Pin,OUTPUT);
  myStateVar = LED1On;
}


void loop() {
  // do other things in parallel to LED1/2 On/off
  timingStateMachine();
  // do more other things in parallel to LED1/2 On/off
}

best regards Stefan

Thanks a7. Here's that code:

// Monday 11 October 2021, tested OK
// Based on @Robin2's tutorial 'Starting SeveralThingsAtTheSameTimeRev1.ino'
// Edited to reduce scope to just two external LEDs
// Changed pins to suit me, plus some formatting preferences etc
// Using millis(), blinks two LEDs independently, with different Off times
// (but same On times; not yet tried to extend to varying those)

//========================================

// CONSTANTS
const int redLEDpin = 8; // Red LED
const int blueLEDpin = 7; // Blue LED
const int redLED_Interval = 500; // Experimented with wide range
const int blueLED_Interval = 800;
const int blinkDuration = 500; // How long both LEDs are on

// VARIABLES
byte redLED_State = LOW;
byte blueLED_State = LOW;

unsigned long currentMillis = 0; // Time at start of each iteration of loop()
unsigned long previousredLED_Millis = 0;
unsigned long previousblueLED_Millis = 0;

//========================================

void setup()
{
  Serial.begin(115200);
  Serial.println("SeveralThings-BlinkRedBlue-2");
  pinMode(redLEDpin, OUTPUT);
  pinMode(blueLEDpin, OUTPUT);
}

//========================================

void loop()
// As recommended, using FUNCTIONS, even for relatively trival tasks
{
  currentMillis = millis(); // Latest value of millis() at start of this iteration
  updateredLED_State();
  updateblueLED_State();
  switchLeds();
}

//========================================

// FUNCTIONS

void updateredLED_State()
{
  // If red LED is off, wait for specified interval to expire before turning it on
  if (redLED_State == LOW)
  {
    if (currentMillis - previousredLED_Millis >= redLED_Interval)
    {
      // Red LED OFf interval has expired, so change the state to HIGH
      redLED_State = HIGH;
      // Save the time of that state change
      previousredLED_Millis += redLED_Interval;
      // NOTE: The previous line could alternatively be
      // previousredLED_Millis = currentMillis
      // which is the style used in the BlinkWithoutDelay sketch
      // Adding on the interval ensures that succesive periods are identical
      // Usually? Always? I need to study that more to fully grasp this.
    }
  }
  // Else it must be on, so need to wait before turning it off
  else
  {
    if (currentMillis - previousredLED_Millis >= blinkDuration)
    {
      redLED_State = LOW;
      previousredLED_Millis += blinkDuration; // Using this more accurate option
    }
  }
}

//========================================

void updateblueLED_State()
{
  if (blueLED_State == LOW)
  {
    if (currentMillis - previousblueLED_Millis >= blueLED_Interval)
    {
      blueLED_State = HIGH;
      previousblueLED_Millis += blueLED_Interval;
    }
  }
  else
  {
    if (currentMillis - previousblueLED_Millis >= blinkDuration)
    {
      blueLED_State = LOW;
      previousblueLED_Millis += blinkDuration;
    }
  }
}

//========================================

void switchLeds()
{
  // Simply switches the LEDs on and off
  digitalWrite(redLEDpin, redLED_State);
  digitalWrite(blueLEDpin, blueLED_State);
}


//========================================

Terry

Sorry, don't understand the question!

Thanks Stefan, I'll later try the switch/case approach (which I saw discussed in the long thread). But my immediate aim is to try getting millis-based code for servos.

Terry

Clearer now?

1 Like

Yes thanks! Was using my iPad in shed workshop, as PC there has no internet. Now back on my house office PC.

Terry

replace

digitalWrite(LED1,HIGH);

with

myservo.write(inPos);

and 

digitalWrite(LED2,HIGH);


with 

myservo.write(outPos);


and you are done 
best regards Stefan

In @StefanL38 #4 code, this line in case waitAfterRelease

should be moved to case LED20n. Otherwise, LED1 is off for only a matter of microseconds, certainly invisible to the human eye.

That's a cosmetic thing, but helps to see the state changes. As pointed out, the servo steps can replace the LED commands, &c.

I must put in the plug for wokwi.com makes looking at posted code easy. And the build in logic analyser is quite useful.

a7

Yes the WOKWI-simulator is great. I have played a little bit with it
but I'm not too familiar with it. Is there na introduction to the "logic analyser" on how to use it?
I prefer text combined with screenshots over a video because with text/pictures I can move back/forth like I want.
But if there is only a video I'll take that too.

best regards Stefan

consider


// define Servos
// define servo positions

#define N   2

const unsigned long Interval [N] = { 500, 800 };
const byte          PinLed   [N] = { 13, 12 };

byte          state  [N];
unsigned long msecLst [N];

unsigned long msec;

// -----------------------------------------------------------------------------
void
chkPin (
    int  n )
{
    if ( (msec - msecLst [n]) >= Interval [n])  {
        msecLst [n] = msec;
        state [n] = ! state [n];
        digitalWrite (PinLed [n], state [n]);
        // write() servo: value based on state
    }
}

// -----------------------------------------------------------------------------
void loop ()
{
    msec = millis ();

    for (unsigned n = 0; n < N; n++)
        chkPin (n);
}

// -----------------------------------------------------------------------------
void setup()
{
    Serial.begin(115200);
    Serial.println("SeveralThings-BlinkRedBlue-2");

    for (unsigned n = 0; n < N; n++)  {
        digitalWrite (PinLed [n], state [n]);
        pinMode      (PinLed [n], OUTPUT);
        // attach servos
    }
}

In the wokwi hardware window, sometimes clicking on an added component reveals a question mark button that takes you to a doc page for the device.

That works for an added logic analyser. It's very easy to use - wire it up, run you program, then use PulseView open source logic data presentation tool to look at the generated vcd file.

You can find the documentation for this and other components, it does take a bit of digging. But the discourse channel is a true shortcut when you can't. Find something or figure it out.

Now the two solutions handed out here do different things, both reasonable interpretations or extrapolations from the OP's description.

So @Terrypin what do you want?

  • A servo moving IN and OUT alternately with adjustable gaps

OR

  • two servos pulsing asynchronously, each with an adjustable gap between pulses.

The latter wouldn't exactly be like LEDs, because once you move a servo to a position, that's where it stays, so the LED turning off would need to be the servo turning back to a home position.

In either program and your own, doing nothing for a while is a perfectly good state for a state machine to be in. Finally your delay() that does not block!

Both solutions are ready to go in the wokwi. <- Daily plug there.

Do it and see the difference. But make the change I recommend to @StefanL38 version.

a7

Here's some code that does exactly what you want using the nodelay.h library. The library removes the complexity of millis() and, I think, is easier to use.
There is not a bit of blocking code in the loop.

/*
   NoDelay-Function-Experiments.ino

   This test blinks two LEDs in random, asynchronous patterns.
   Well, almost. The yellow_ON function resets the green LED intensity
   which then dims on it's own schedule.
*/


/* To create a NoDelay object:
   noDelay object(1000);                    //Makes an object of given time in ms
   noDelay object(1000, function_to_call);  //Makes an object in ms that will call the given function
   noDelay object(1000,false);              //Makes an object of given time in ms but keeps it from running
   noDelay object(1000, function, false);   //Makes object in ms that will call function but keep it from running

    Methods
    update() : When called it will check if the amount of time set has passed.
    setdelay() : This function is used to adjust the amount of time it takes for NoDelay to wait for.
    start() : Start is used to reset the timer or to start NoDelay again when it has been stopped.
    stop() : This function will keep NoDelay from returning true or running a function when calling update().
*/

#include<NoDelay.h>

//---------- prototypes ----------
void yellow_ON();
void yellow_OFF();
void green_ON();
void green_OFF();
void green_DIM();


//Create noDelay objects
noDelay yellowLED_onTime(1000, yellow_ON , false);
noDelay yellowLED_offTime(1000, yellow_OFF, false);
noDelay greenLED_onTime(1000, green_ON , false);
noDelay greenLED_offTime(1000, green_OFF, false);
noDelay greenLED_dim(20, green_DIM , true);           //How fast to dim the LED. Lower is faster

const int LED_ON = 1;
const int LED_OFF = 0;
const int LED_YELLOW_PIN = D2;
const int LED_GREEN_PIN = D1;


int greenVal = 0;
const int GREEN_MIN = 5;            //Green will dim to this value in loop
const int GREEN_MAX = 100;          //Yellow_ON will bring green back to this level.


// ---------- setup ----------
void setup() {
  Serial.begin(115200);
  while (!Serial);
  Serial.println();
  Serial.println(F("NoDelay-Function-Experiments.ino"));
  Serial.println();

  pinMode(LED_YELLOW_PIN, OUTPUT);
  pinMode(LED_GREEN_PIN, OUTPUT);
  digitalWrite(LED_YELLOW_PIN, LED_OFF);
  digitalWrite(LED_GREEN_PIN, LED_OFF);


  //Start the LED timers
  yellow_ON();                     //The ON functions will start OFF functions.
  green_ON();
}


// ---------- loop ----------
void loop() {
  yellowLED_onTime.update();        //If time has past, run set function
  yellowLED_offTime.update();
  greenLED_onTime.update();
  greenLED_offTime.update();        //Blink green
  greenLED_dim.update();            //Dim green

}


void yellow_ON() {
  //The yellow ON timer has dinged.
  //Turn on the LED, stop the ON timer then start the OFF timer.
  digitalWrite(LED_YELLOW_PIN, LED_ON);                   //Turn on the yellow LED
  yellowLED_onTime.stop();                                //Stop the ON timer
  yellowLED_offTime.setdelay(random(1000, 5000));         //LED will be on for this time.
  yellowLED_offTime.start();                              //Start the OFF timer
  greenVal = GREEN_MAX;
}

void yellow_OFF() {
  digitalWrite(LED_YELLOW_PIN, LED_OFF);                 //Turn off the yellow LED
  yellowLED_offTime.stop();                              //Stop the OFF timer
  yellowLED_onTime.setdelay(random(1000, 6000));         //LED will be off for this time.
  yellowLED_onTime.start();                              //Start the ON timer
}

void green_ON() {
  analogWrite(LED_GREEN_PIN, greenVal);
  greenLED_onTime.stop();                                //Stop the ON timer
  greenLED_offTime.setdelay(random(1500, 4000));         //LED will be on for this time.
  greenLED_offTime.start();                              //Start the OFF timer
}

void green_OFF() {
  digitalWrite(LED_GREEN_PIN, LED_OFF);                   //Turn off the green LED
  greenLED_offTime.stop();                                //Stop the OFF timer
  greenLED_onTime.setdelay(random(100, 150));             //LED will be off for this time.
  greenLED_onTime.start();                                //Start the ON timer
}

void green_DIM() {
  //Fade the greenVal
  if (greenVal > GREEN_MIN) {
    greenVal -= 1;
    analogWrite(LED_GREEN_PIN, greenVal);
  }
}

Hi SteveMann,

cool library. I did a quick look and understand the basic concept,
but I'm yet unable to apply it myself to a new code due to I took just a quick look.

A lot of Coders tend to use too similr names for (n reality) different things.

example there is this line of code

//Create noDelay objects
noDelay yellowLED_onTime(1000, yellow_ON , false);

creating objects it has a parameter "yellow_ON" without parenthesis

then there is a function named void yellow_ON() with parenthesis.

is the parameter in the line noDelay yellowLED_onTime(1000, yellow_ON , false);

the further down written function just without the parenthesis ?
or is this parameter yellow_ON something different?

If this is something different the name should reflect it

If it is the same there should be a comment explaining this

best regards Stefan

noDelay object(1000, function_to_call);  //Makes an object in ms that will call the given function

What part of that is the least bit ambiguous? It is very often that a callback function is provided as a parameter in this manner - the name of the function.

Asides, if there was any ambiguity about the meaning or interpretation of the supplied symbol, the compiler would make an error about it…

a7

1 Like

@SteveMann did you test the code you posted?

Here the green LED never comes on. To get the green LED to function, I had to strip out the whizzy fading thing it looks like was attempted.

What part of the OP's problem is exactly like fading an LED whilst it blinks anyway?

Libraries are nice sometimes, but this is a case where @Terrypin would be better served by coding this from scratch, or reading a solution that does not bring in a library.

To learn what really needs be done when eliminating delay() calls.

a7

1 Like

simply depends on the knowledge-level somebody has. I'm coming from delphi and rarely used call-backs-functions with dephi.

My former experience with call-back-functions is
this pretty different design which I have not touched or modified but just used it

//   Callbacks related to BLE connection
class BleKeyboardCallbacks : public BLEServerCallbacks {

    void onConnect(BLEServer* server) {
      isBleConnected = true;

      // Allow notifications for characteristics
      BLE2902* cccDesc = (BLE2902*)input->getDescriptorByUUID(BLEUUID((uint16_t)0x2902));
      cccDesc->setNotifications(true);

      Serial.println("Client has connected");
    }

    void onDisconnect(BLEServer* server) {
      isBleConnected = false;

      // Disallow notifications for characteristics
      BLE2902* cccDesc = (BLE2902*)input->getDescriptorByUUID(BLEUUID((uint16_t)0x2902));
      cccDesc->setNotifications(false);

      Serial.println("Client has disconnected");
    }
};

and this one which looks different again

  MyEmailAdressCard.attachCallback([&](String MyEmailStr){
    MyEmail = MyEmailStr.c_str();
    Serial.print( F("Button Triggered: ") );
    Serial.println(MyEmail_SS);
    writeData();
    
    MyEmailAdressCard.update(MyEmail); //Make sure we update our value and send update to dashboard */
    //mySerial.println(MyEmail_SS);
    MyDashboard.sendUpdates();
  });

a longer time ago I did things like this

  attachInterrupt(digitalPinToInterrupt(2), checkPin, CHANGE);

which has a similarity to your examplecode

Some of the comments I do not yet understand in all details.

void yellow_ON() {
  //The yellow ON timer has dinged.
  //Turn on the LED, stop the ON timer then start the OFF timer.
  digitalWrite(LED_YELLOW_PIN, LED_ON);                   //Turn on the yellow LED
  yellowLED_onTime.stop();                                //Stop the ON timer
  yellowLED_offTime.setdelay(random(1000, 5000));         //LED will be on for this time.
  yellowLED_offTime.start();                              //Start the OFF timer
  greenVal = GREEN_MAX;
}

I try to describe in my own words what I assume to have understood about the noDelay-library

There is first non-blocking timerobject for switching the yellow LED on
and a second non-blocking timerobject for switching the yellow LED off

you create two timer-objects for the one Yellow-LED to have different times for the LED to be switched on and switched off

  yellowLED_onTime.stop();                                //Stop the ON timer

not nescessary to call this inside the callback-function
used here as a specialty of this functionality here to make two LEDs blink at randomly changing times.

  yellowLED_offTime.setdelay(random(1000, 5000));         //LED will be on for this time.

does what the name says. set the time that has to pass by until the timer-object with the name yellowLED_offTime "fires" its "expiration-signal"
where the expiration-signal is calling (=executing) the callbackfunction

  yellowLED_offTime.start();                              //Start the OFF timer

here start means activate timer in the sense of start the timer to count-down from the milliseconds set with the .setdelay() function until this countdown reaches zero.

If timer reaches zero execute the callbackfunction

The methodname "update" does some "updating" under the hood.
But this method-name does not really reflect that the user has to call this function very often and at least repeatedly to make the timer-object work.
I'm still thinking about a function name that does explain that thing better.

The function name "start" does start the timer. Yes. But IMHO this name does not reflect all things that are important for the user.
Something like "StartCountingDown" describes it more precise.
I have carefully chosen Counting instead of simply "CountDown" as Counting clearly expresses what is happening.

similar to function "stop" StopCountingDown.

I understand the intention of the demo-code. Show what is possible to do with the noDelay-library.
I'm thinking about derivating a new library from it which has even a different library-name

Including demos that start at the most simple task of a regular blink working up from there to more complex things.

best regards Stefan

1 Like

It's working fine here on my bench.
The OPs problem was making two timing functions operate asynchronously. This was just an example of how I did it without millis().