Go Down

Topic: Re: using millis() for timing (Read 357 times) previous topic - next topic

wiifm

I have been using a different method...

Code: [Select]


unsigned long nextVoidNoArgsFunction = 0;


long returnedValue = 0;

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

}

void doFunctionAtInterval( void ( *callBackFunction )(), unsigned long *nextEvent, unsigned long interval ) {
  unsigned long now = millis();
  if ( now  >= *nextEvent ) {
    *nextEvent = now + interval;
    callBackFunction();
  }
}

void loop() {
  doFunctionAtInterval(voidNoArgsFunction, &nextVoidNoArgsFunction, 400);
}

void voidNoArgsFunction() {
  returnedValue = value = random(0,100);
  Serial.println(returnedValue);
}



This has worked great for me... as long as the callback function is void and has no arguments.

I'd like to be able to use code such as below in the loop():
Code: [Select]

doFunctionAtInterval(voidOneArgFunction(&returnedValue), &nextVoidOneArgFunction, 1000);


but I have not had any success getting things to work.  See below:
Code: [Select]


unsigned long nextVoidNoArgsFunction = 0;
unsigned long nextVoidOneArgFunction = 0;

long returnedValue = 0;

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

}

void doFunctionAtInterval( void ( *callBackFunction )(), unsigned long *nextEvent, unsigned long interval ) {
  unsigned long now = millis();
  if ( now  >= *nextEvent ) {
    *nextEvent = now + interval;
    callBackFunction();
  }
}

/*

// Trying to overload doFunctionAtInterval here, but get compiler error on callbackFunction(*alue); line
void doFunctionAtInterval( void ( *callBackFunction )(float *value), unsigned long *nextEvent, unsigned long interval ) {
  unsigned long now = millis();
  if ( now  >= *nextEvent ) {
    *nextEvent = now + interval;
    callBackFunction(*alue);
  }
}
*/

void loop() {
  doFunctionAtInterval(voidNoArgsFunction, &nextVoidNoArgsFunction, 400);
  /*
  // No success here with efforts so far, but this is the shape of how I'd like it to work
  doFunctionAtInterval(voidOneArgFunction(&returnedValue), &nextVoidOneArgFunction, 1000);
  */

  // This works, but feels like cheating
  doFunctionAtInterval(encapsulatingVoidOneArgFunction, &nextVoidOneArgFunction, 1000);

}

void voidOneArgFunction(long *value) {
  *value = random(0,100);
}

void voidNoArgsFunction() {
  Serial.println(returnedValue);
}

void encapsulatingVoidOneArgFunction() {
  voidOneArgFunction(&returnedValue);
}


Can anyone help please?

wiifm

Another way of 'cheating', but not as elegant as I would like...

Code: [Select]


long returnedValue = 0;

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

}

void doFunctionAtInterval( void ( *callBackFunction )(), unsigned long *nextEvent, unsigned long interval ) {
  unsigned long now = millis();
  if ( now  >= *nextEvent ) {
    *nextEvent = now + interval;
    callBackFunction();
  }
}

void doFunctionAtInterval( long ( *callBackFunction )(), long *value, unsigned long *nextEvent, unsigned long interval ) {
  unsigned long now = millis();
  if ( now  >= *nextEvent ) {
    *nextEvent = now + interval;
    *value = callBackFunction();
  }
}


void loop() {
  doFunctionAtInterval(voidNoArgsFunction, &nextVoidNoArgsFunction, 400);
  doFunctionAtInterval(noArgFunction, &returnedValue, &nextVoidOneArgFunction, 1000);
}

long noArgFunction() {
  return random(0,100);
}

void voidNoArgsFunction() {
  Serial.println(returnedValue);
}

gfvalvo

#2
Feb 22, 2019, 04:27 am Last Edit: Feb 22, 2019, 04:38 am by gfvalvo
I have been using a different method...

Code: [Select]


unsigned long nextVoidNoArgsFunction = 0;


long returnedValue = 0;

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

}

void doFunctionAtInterval( void ( *callBackFunction )(), unsigned long *nextEvent, unsigned long interval ) {
  unsigned long now = millis();
  if ( now  >= *nextEvent ) {
    *nextEvent = now + interval;
    callBackFunction();
  }
}

void loop() {
  doFunctionAtInterval(voidNoArgsFunction, &nextVoidNoArgsFunction, 400);
}

void voidNoArgsFunction() {
  returnedValue = value = random(0,100);
  Serial.println(returnedValue);
}


This has worked great for me...
It's unclear to me how that is so since that code doesn't even compile.
No technical questions via PM. They will be ignored. Post your questions in the forum so that all may learn.

MorganS

Did this get split off from another topic? It doesn't seem to start with a question.

There's two ways to attack this: write a function that does what you want, when you want or write a library with an object that can do what you want.

I prefer the first method. Each function that has an interval manages its own interval. Something like this...

Code: [Select]
void loop() {
  checkInputs();
  sendMonitorOutput();
  sendTelemetry();
  updateScreen();
}


Each one of those has different intervals. The Serial Monitor output happens at a different rate to the Serial data going to the telemetry radio.

The top of each of those functions looks like:
Code: [Select]
void sendTelemetry() {
  const unsigned long telemetryInterval = 50000; //milliseconds
  static unsigned long lastTelemetrySent=0;
 
  if(millis() - lastTelemetrySent < telemetryInterval) return;
  lastTelemetrySent = millis();

  ...
}


Making the last-sent variable local to the function means that no other function can mess with it. You could use the same name for that in every function and they don't interfere with each other. Making it static means that its value is preserved after the function exits.
"The problem is in the code you didn't post."

blh64

I have been using a different method...

Code: [Select]


void doFunctionAtInterval( void ( *callBackFunction )(), unsigned long *nextEvent, unsigned long interval ) {
  unsigned long now = millis();
  if ( now  >= *nextEvent ) {
    *nextEvent = now + interval;
    callBackFunction();
  }
}

Your method is flawed.  What happens when now is a value close to the upper limit such that now+interval rolls over?  Immediately, you next event will be triggered.

There is a reason why you can only use the 'now-startTime' type of calculations

Jiggy-Ninja

#5
Feb 22, 2019, 06:22 am Last Edit: Feb 22, 2019, 06:23 am by Jiggy-Ninja
I'd like to be able to use code such as below in the loop():
Code: [Select]

doFunctionAtInterval(voidOneArgFunction(&returnedValue), &nextVoidOneArgFunction, 1000);


but I have not had any success getting things to work.  See below:
A general solution to this is possible, but far from easy. It requires extensive use of overloading, templates, and virtual member functions to create a versatile function object that can be used in the same way as a function pointer. If you need to ask what any of those things are, you're nowhere near good enough to try rolling your own solution to this.

The ArduinoSTL library appears to have a workable implementation of the functional library, but I haven't personally used it.

The easier way to do this is to create a void(void) function in your sketch that wraps around the function you want to use, and use that as the timer's callback.
Hackaday: https://hackaday.io/MarkRD
Advanced C++ Techniques: https://forum.arduino.cc/index.php?topic=493075.0

Whandall

Code: [Select]
void doFunctionAtInterval( void ( *callBackFunction )(), unsigned long *nextEvent, unsigned long interval ) {
    callBackFunction();
  }
}

Why does the call compile and run ok at all?
I would have expected

Code: [Select]
    (*callBackFunction)();
Ah, this is obviously some strange usage of the word 'safe' that I wasn't previously aware of. (D.Adams)

wiifm

Did this get split off from another topic? It doesn't seem to start with a question.
I read the sticky at the top of this forum regarding millis();
I prefer the first method... snipped for brevity
Thanks MorganS.  Notwithstanding the flaws in the function I originally posted, sometimes I might call a function out of the regular timing sequence (especially updating displays etc).  The timing logic within the function would prevent this from happening.

Updated code thanks to those who pointed out flaws...
Code: [Select]

void doFunctionAtInterval( void ( *callBackFunction )(), unsigned long *lastEvent, unsigned long interval ) {
  
  unsigned long now = millis();
  if(now - *lastEvent < interval) return;
  *lastEvent = now;
  callBackFunction();

}



A general solution to this is possible, but far from easy. It requires extensive use of overloading, templates, and virtual member functions to create a versatile function object that can be used in the same way as a function pointer. If you need to ask what any of those things are, you're nowhere near good enough to try rolling your own solution to this.

The ArduinoSTL library appears to have a workable implementation of the functional library, but I haven't personally used it.

The easier way to do this is to create a void(void) function in your sketch that wraps around the function you want to use, and use that as the timer's callback.
Thanks Jiggy.  I was hoping there was just something relatively simple I was missing.

wiifm

Code: [Select]
void doFunctionAtInterval( void ( *callBackFunction )(), unsigned long *nextEvent, unsigned long interval ) {
    callBackFunction();
  }
}

Why does the call compile and run ok at all?
I would have expected

Code: [Select]
    (*callBackFunction)();

Code: [Select]
    (*callBackFunction)();

throws a compiler error - error: void value not ignored as it ought to be

maybe
Code: [Select]

void (*callBackFunction() );

?

Whandall

Code: [Select]
    (*callBackFunction)();

throws a compiler error - error: void value not ignored as it ought to be
Not if I compile the code.

Code: [Select]
unsigned long nextVoidNoArgsFunction = 0;
long returnedValue = 0;

void setup() {
  Serial.begin(250000);
}
void doFunctionAtInterval( void ( *callBackFunction )(), unsigned long *nextEvent, unsigned long interval ) {
  unsigned long now = millis();
  if ( now  >= *nextEvent ) {
    *nextEvent = now + interval;
    (*callBackFunction)();
    //callBackFunction();
  }
}

void loop() {
  doFunctionAtInterval(voidNoArgsFunction, &nextVoidNoArgsFunction, 400);
}

void voidNoArgsFunction() {
  returnedValue = random(0, 100); // returnedValue = value = random(0, 100);
  Serial.println(returnedValue);
}

Both version compile and 'work' (the addition flaw is still in).
Ah, this is obviously some strange usage of the word 'safe' that I wasn't previously aware of. (D.Adams)

Robin2

Thanks MorganS.  Notwithstanding the flaws in the function I originally posted, sometimes I might call a function out of the regular timing sequence (especially updating displays etc).  The timing logic within the function would prevent this from happening.
IMHO this suggests a flawed approach to program design.

As the creator you must know when writing the program which functions will need to operate at regular intervals and which ones may need to work at regular as well as irregular intervals.

The first type can have the timing code within them. The second type can be programmed to work whenever some control variable is true and that "truth" can be set by another function with regular timing and separately by some other part of the program that responds to some other event.

I have been writing a program that sends a standard response to the PC in reply to messages from the PC (which are regular) or whenever an LDR is triggered (which is asynchronous).

...R
Two or three hours spent thinking and reading documentation solves most programming problems.

Jiggy-Ninja

Code: [Select]
void doFunctionAtInterval( void ( *callBackFunction )(), unsigned long *nextEvent, unsigned long interval ) {
    callBackFunction();
  }
}

Why does the call compile and run ok at all?
I would have expected

Code: [Select]
    (*callBackFunction)();

Both syntaxes are allowed, and will do the same thing. There's no problem using either.
Hackaday: https://hackaday.io/MarkRD
Advanced C++ Techniques: https://forum.arduino.cc/index.php?topic=493075.0

Whandall

I've never seen or used the version without the dereferencing star, thanks for the info.

Any idea which C++ version started this behaviour?
Ah, this is obviously some strange usage of the word 'safe' that I wasn't previously aware of. (D.Adams)

christop

I've never seen or used the version without the dereferencing star, thanks for the info.

Any idea which C++ version started this behaviour?
It's been that way in C since just about forever, and probably the same in C++, because there's only one thing it could possibly mean (call the function pointed to by the function pointer). It's the same reason you don't need to use & to get the address of a function--a function name without parentheses already decays to a pointer to the function.

MorganS

I read the sticky at the top of this forum regarding millis();
Thanks MorganS.  Notwithstanding the flaws in the function I originally posted, sometimes I might call a function out of the regular timing sequence (especially updating displays etc).  The timing logic within the function would prevent this from happening.
Well, display code can be different. My displays usually look like...

Code: [Select]
  if(somethingChangedForTheDisplay) updateDisplay();

Some of my displays go right down to the individual characters, so that it doesn't need to redraw "12" when the displayed value changes from "123" to "124".
"The problem is in the code you didn't post."

Go Up