I'm controlling a stepper motor and it needs to precisely adjust its speed based on how many seconds it has been running.
This is the equation to calculate the time between steps:
delta = delta0 * sq(cos(w*millis()/1000));
Because this is a fairly complex equation I think storing precomputed values should be faster. It's going to be running for up to 30 minutes so I decided to store 360 precomputed values per 5 seconds. The errors in a single loop will add up over time so speedy code is important.
Here is my code:
const int stepper = 9;
const int delta0 = 318.917;
const double b = 0.22;
const double w = 2*PI/(23+56/60)/3600;
const double arr[] = { 318.917, 318.91699830399926, 318.91679478395974, 358 more values...};
//the last value is 297.54198193001537. Not much difference
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
pinMode(stepper, OUTPUT);
}
void loop() {
double delta = arr[millis()/5000];
digitalWrite(LED_BUILTIN, HIGH);
digitalWrite(stepper,HIGH);
delay(delta*0.5);
digitalWrite(LED_BUILTIN, LOW);
digitalWrite(stepper, LOW);
delay(delta*0.5);
}
My question is: Is this the correct way to go about it? Will calculating the value every loop be faster than an array lookup? Am I better of storing the values in FLASH memory? Then I can calculate a value for every second and have 1800 instead of 360 values.
The compiler uses float. It doesn't matter, if the last ten digits of this constant don't matter:
318.91699830399926
Reading from PROGMEM is a few machine cycles slower than reading from RAM (62.5 nanoseconds per cycle). But 360 four byte constants take up 1440 of your 2048 bytes of RAM.
Your equation feels weird. Are you taking the root square of a possibly negative number (after t seconds, w.t will be above PI/2 and your cos() is negative)
Seems also you have plenty of free time since you use delay for hundreds of ms...
Use millis and non blocking code and you have plenty of time to do the maths... see blink without delay
Probably you can take advantage of the symmetry of the cos function every 90 degrees and only store 1/4 of those values. Or alternatively 4 times more gradual samples in the same space.
There are a number of problems in your code, mostly having to do with mixed float and integer math.
23 + 56/60th hours is one sidereal day.
My guess is that you are implementing a 'barn door' polar tracker for taking pictures of star fields. I bet it's been done before without double-precision math. When I typed "barn door star" into Google, one of the suggested searches was "barn door star tracker arduino". Try looking at some of those projects to see how they got around the problems.
J-M-L:
Your equation feels weird. Are you taking the root square of a possibly negative number (after t seconds, w.t will be above PI/2 and your cos() is negative)
Seems also you have plenty of free time since you use delay for hundreds of ms...
Use millis and non blocking code and you have plenty of time to do the maths... see blink without delay
Thank you that's a great tip. The value for w= 0.0000000001 or something. It's going to take a while for a full revolution. The first value is about 320 and after an hour the last value is about 280. It's not going to be nagative any time soon
pcbbc:
Probably you can take advantage of the symmetry of the cos function every 90 degrees and only store 1/4 of those values. Or alternatively 4 times more gradual samples in the same space.
The value for w= 0.0000000001 or something. It's going to take a while for a full revolution. The first value is about 320 and after an hour the last value is about 280. It's not going to be nagative any time soon
I think it is not important how long it is running, either 10 seconds or 10 weeks. I don't understand why the 30 minutes could be a problem.
When you do that calculation of 350 µs every second, then you still have 0.99965 seconds to do other things.
I'm building a barn door tracker for tracking the night sky. It already works well for a 200mm lens but I want to make it better. You're pushing open a hinge with a rod that's pointing straight up. The rod needs to move faster to maintain the same rotational speed of the hinge after 30 minutes. Here's a picture of the tracker
Yes but I used delay. So let's say you calculate the delay and you get a value of 300ms. The calculation took 10ms. Then the arduino is going to calculate for 10ms. Then wait for 300ms. Then move the stepper motor. Then repeat. The delay will be 310ms instead of 300 no?
But another user already suggested "non blocking code" I think that solves the issue entirely no?
I suppose that it is w1 or w3, but perhaps it is something else
When a line of code is a too long and not clear, then you can split it into multiple lines. You should tell the compiler which part should be calculated with integers and which part with float.
You wanted to reduce the calculation time of 350 µs to be able to use a very specific delay and keep those delays as accurate as possible. I think that is called a XY-problem: http://xyproblem.info/
With millis() all your problems are gone. My millis_clock.ino is an example of a clock that is just as accurate as the 16MHz crystal or resonator of the Arduino board.
The interval does not have to be 1 second, you can change the interval each time to a new value.
I suggest to use a board with a crystal for good accuracy. The Arduino Leonardo uses a crystal and some Arduino Uno clones have a crystal.
zwettekop:
Yes but I used delay. So let's say you calculate the delay and you get a value of 300ms. The calculation took 10ms. Then the arduino is going to calculate for 10ms. Then wait for 300ms. Then move the stepper motor. Then repeat. The delay will be 310ms instead of 300 no?
So, don't use delay(). Use the BlinkWithoutDelay example as an example.
Note that 'delay()' and 'millis()' work in integer milliseconds so your floating-point values with 14 places after the decimal point are:
(1) Going to be stored as 'float' with 6 or 7 total decimal places (three to four after the decimal point)
and
(2) Going to get truncated to integer when you use them for delay().
You might be better served using integer microseconds rather than truncating fractional milliseconds to integer milliseconds.
johnwasser:
So, don't use delay(). Use the BlinkWithoutDelay example as an example.
Note that 'delay()' and 'millis()' work in integer milliseconds so your floating-point values with 14 places after the decimal point are:
(1) Going to be stored as 'float' with 6 or 7 total decimal places (three to four after the decimal point)
and
(2) Going to get truncated to integer when you use them for delay().
You might be better served using integer microseconds rather than truncating fractional milliseconds to integer milliseconds.
Note: A sidereal day is 86164.0905 seconds.
Yup somebody else already mentioned this. Here's my updated code:
const int stepper = 9;
const float delta0 = 313.58805/2.0;
const float b = 0.22;
const long sidereal_day_in_sec = 86164;
const float w = 2.0*PI/ sidereal_day_in_sec;
float delta = delta0;
float time_now = 0;
float time_last = 0;
bool toggle = false;
/*Program to control a stepper motor: (NEMA17 39BYGL) with a A4988 driver.
22/05/2020 delta total calculated including microstepping = 313.58805
delta0 = delta total /2
This version compensates for tangential error
delta0: correct timing for t = 0 sec
delta: calculated time for t != 0
b: length between the pusher rod and the hinge in m
w: rotatoinal speed of the earth in rad/s
*/
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
pinMode(stepper, OUTPUT);
}
void loop() {
time_now = millis();
if(time_now - time_last > delta){
time_last = millis();
toggle = !toggle;
delta = delta0 * sq(cos(w*millis()/1000));
digitalWrite(LED_BUILTIN, toggle);
digitalWrite(stepper,toggle);
}
}
I haven't tested the tracking yet on long exposures at night but during the day this seemed to at least run. Can I post this code update here or should this go in a new question? I "think" I also fixed the integer errors you spotted!
I prefer that you compare it with a unsigned long value. The variable 'delta' is now a float.
Can you fix this:
delta = delta0 * sq(cos(w*millis()/1000));
millis() returns a unsigned long
1000 is a integer
w is a float
I prefer that you tell the compiler how to do that calculation.
Why do you throw away accuracy by truncating the 'sidereal_day_in_sec' to whole seconds and put it in a 'long', but then use it to calculate a 'float' value.
I see that makes sense! I'm studying compsci or the EU equivalent and we got those types of questions on our 1st-year exam. I guess just because you can doesn't mean that you should
Maybe doing it without casting is less frowned upon in other languages like C, C++, Java etc. I see it in example code form profs all the time.