Ok, I know using millis instead of delay() is a topic that has probably been done to death, but I hope someone here can point out the error of my ways in the following servo controlling code:
#include <Servo.h>
Servo myservo;
int pos = 0;
int period = 2;
unsigned long time_now = 0;
void setup() {
myservo.attach(2);
}
void loop() {
for (pos = 700; pos <= 2400; pos += 1) { // goes from 0 degrees to 180 degrees (servo limit to servo limit) in steps of Ms
if (millis() - time_now > period) {
time_now = millis();
myservo.writeMicroseconds(pos);
}
}
for (pos = 2400; pos >= 700; pos -= 1) { // goes from 180 degrees to 0 degrees (servo limit to servo limit) in steps of Ms
myservo.writeMicroseconds(pos);
delay (2);
}
}
The code above is not a finished sketch, but a working example of the problem I'm trying to solve.
You can see that I have a servo sweeping from left to right and then back from right to left and I have used two different methods of controlling the sweep speed (for demonstration purposes)
With the delay (2) in place between each step, the servo takes the time I'd like to make its sweep, but when I replace the delay (2) with
(where period == 2), then the servo sweeps at top speed (i.e. no delay at all between steps). I have tried varying the value of "period" in case 2ms was too low but with no affect on the end result.
I know that millis code fragment does function as I hoped because when used like this:
int period = 1000;
unsigned long time_now = 0;
void setup() {
Serial.begin(9600);
}
void loop() {
if(millis() - time_now > period){
time_now = millis();
Serial.println("Tick");
}
//Run other code
}
it produces the desired result, i.e. a delay of 1 second between serial prints.
Can anyone help me understand why the millis section doesn't actually give me 2ms between each servo step? I know its probably blindingly obvious to quite a few on here, but please just assume I'm dumb!!
Compiles but not tested. See if you can make heads or tails of it.
#include <Servo.h>
Servo myservo;
int pos = 0;
unsigned int period = 2; //when using millis you should use unsigned longs
unsigned long time_now = 0;
bool bDirection;
void setup()
{
myservo.attach(2);
pos = 700;
bDirection = true; //makes sense with us moving from 700 to 2400
}
void loop()
{
TwiddleServo();
//can do lots of stuff in here while servo times using millis
//.
//.
//.
}//loop
void TwiddleServo( void )
{
//has the period been met or exceeded?
if( (millis() - time_now) < period)
return;
//send pos to the servo
myservo.writeMicroseconds(pos);
//check the direction of travel; see if it's time to move the other direction
if( bDirection == true )
{
// goes from 0 degrees to 180 degrees (servo limit to servo limit) in steps of Ms
pos++;
if( pos > 2400 )
bDirection = false;
}//if
else
{
// goes from 180 degrees to 0 degrees (servo limit to servo limit) in steps of Ms
pos--;
if( pos == 0 )
bDirection = true;
}//else
time_now = millis();
}//TwiddleServo
Can anyone help me understand why the millis section doesn't actually give me 2ms between each servo step? I know its probably blindingly obvious to quite a few on here, but please just assume I'm dumb!!
for (pos = 700; pos <= 2400; pos += 1) { // goes from 0 degrees to 180 degrees (servo limit to servo limit) in steps of Ms
if (millis() - time_now > period) {
time_now = millis();
myservo.writeMicroseconds(pos);
}
}
In case you haven't realized why it wasn't working, the "for" loop continues to run when the "if" statement is false, so the value of pos goes up considerably between the servo write commands.
septillion:
Owww, in that case, not needed You only need an unsigned long for variables in which you store millis()
'Tis true in this case. But as a general practice it's smart to make a habit of using ULs for millis() stuff so that when one wants a period of, say, 2-minutes in the same code or in the future one is not struggling to figure out why the code isn't doing what it should "when it worked before."
Hopefully that doesn't put too much strain on the RAM resources
Better way, only one call to millis(), doesn't slip in time if a timer interrupt falls in the wrong place, catches up if the actions occasionally longer than the perod:
unsigned long next_time = 0L ;
#define PERIOD 2
void loop ()
{
if (millis() - next_time >= PERIOD)
{
next_time += PERIOD ; // move the target time a fixed amount
.....actions.....
}
}
I will be integrating some serial calls to a Robertsonic wavTrigger into the code next, so there's going to be more timing states to control. All the above advice is going to be handy!
MarkT:
Better way, only one call to millis(), doesn't slip in time if a timer interrupt falls in the wrong place, catches up if the actions occasionally longer than the perod:
unsigned long next_time = 0L ;
#define PERIOD 2
void loop ()
{
if (millis() - next_time >= PERIOD)
{
next_time += PERIOD ; // move the target time a fixed amount
.....actions.....
}
}
Don't think that works. millis() returns a free-counting unsigned long. A subtraction is only meaningful if "next_time" is referenced to the start of the timing period.
Unless you make next_time equal to millis at some point, it's possible that every single pass will result in the action being taken.
Better, for something that is continuously timed, would be:
Delta_G:
Where it might fail is if it takes longer than the interval to get to it the first time. So in my 20ms example, if it took 65ms to get to that the first time (maybe because setup was long) then it will run three times in a row to catch up. You can add an extra if statement to catch that if needed.
Not sure I see the benefit of adding the if statement when the case I showed needs no such logic.
Certainly, without it the logic is susceptible to failing.
If a delay is required some period of time after the millis() counter has started -- 10 seconds? an hour? whatever -- the logic as written will fail. It will drop through as many times as is required for next_time to catch up.