Correct way to store 360 doubles that need to be read fast on Arduino Nano

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.

Type "double" is not supported on AVR type Arduinos, instead it is treated exactly like "float", 32 bits with 6-7 decimal digits of precision.

Which Arduino are you using?

You can store tables of values in flash memory (see keyword PROGMEM), and retrieving will be much, much faster than calculating them.

I'm using an Arduino Nano. The code compiled so the compiler probably just changed it to a float without giving me an error. Does it matter?

Also is reading from PROGMEM slower than how I'm doing it?

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.

When the compiler sees "56" then it sees an integer.
When the compiler sees "56.0" then it sees a float.

How should 'w' be calculated ?

const float w1 = 2*PI/(23+56/60)/3600;
const float w2 = 2.0 * PI / ((23.0 + (56.0/60.0)) / 3600);
const float w3 = 2.0 * M_PI / ((23.0 + (56.0/60.0)) * 3600);
const float w4 = 2.0 * M_PI / (23.0 + (56.0/60.0)) * 3600;

void setup() 
{
  Serial.begin( 9600);
  Serial.println( w1);
  Serial.println( w2);
  Serial.println( w3);
  Serial.println( w4);
}

void loop() 
{
}

The calculation takes about 350 µs on a Arduino Uno:

const float w4 = 2.0 * M_PI / (23.0 + (56.0/60.0)) * 3600;
const int delta0 = 318.917;

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

void loop()
{
  float f;

  unsigned long t1 = micros();
  f = delta0 * sq( cos (w4 * float(millis()) / 1000.0));
  unsigned long t2 = micros();

  Serial.print( t2 - t1);
  Serial.print( " µs (f=");
  Serial.print( f);
  Serial.print( ")");
  Serial.println();
  delay( 500);
}

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.

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

Koepel:
When the compiler sees "56" then it sees an integer.
When the compiler sees "56.0" then it sees a float.

How should 'w' be calculated ?

const float w1 = 2*PI/(23+56/60)/3600;

const float w2 = 2.0 * PI / ((23.0 + (56.0/60.0)) / 3600);
const float w3 = 2.0 * M_PI / ((23.0 + (56.0/60.0)) * 3600);
const float w4 = 2.0 * M_PI / (23.0 + (56.0/60.0)) * 3600;

void setup()
{
 Serial.begin( 9600);
 Serial.println( w1);
 Serial.println( w2);
 Serial.println( w3);
 Serial.println( w4);
}

void loop()
{
}




The calculation takes about 350 µs on a Arduino Uno:


const float w4 = 2.0 * M_PI / (23.0 + (56.0/60.0)) * 3600;
const int delta0 = 318.917;

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

void loop()
{
 float f;

unsigned long t1 = micros();
 f = delta0 * sq( cos (w4 * float(millis()) / 1000.0));
 unsigned long t2 = micros();

Serial.print( t2 - t1);
 Serial.print( " µs (f=");
 Serial.print( f);
 Serial.print( ")");
 Serial.println();
 delay( 500);
}




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?

The Arduino Serial.println() does not print such a small number.
I have changed my sketch to print it in scientific notation.
So which one is it ?

const float w1 = 2*PI/(23+56/60)/3600;
const float w2 = 2.0 * PI / ((23.0 + (56.0/60.0)) / 3600);
const float w3 = 2.0 * M_PI / ((23.0 + (56.0/60.0)) * 3600);
const float w4 = 2.0 * M_PI / (23.0 + (56.0/60.0)) * 3600;

char buffer[40];

void setup()
{
  Serial.begin( 9600);
  dtostre( w1, buffer, 8, 0);   Serial.println( buffer);
  dtostre( w2, buffer, 8, 0);   Serial.println( buffer);
  dtostre( w3, buffer, 8, 0);   Serial.println( buffer);
  dtostre( w4, buffer, 8, 0);   Serial.println( buffer);
}

void loop()
{
}

The result is:

7.5883880e-05
9.4510315e+02
7.2924624e-05
9.4510315e+02

I suppose that it is w1 or w3, but perhaps it is something else :confused:
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.

Note: A sidereal day is 86164.0905 seconds.

johnwasser:
A sidereal day is 86164.0905 seconds.

Thank you. It seems that there is also a Wikipedia page for it: Sidereal time - Wikipedia.

The right way to do it would be this then ?

const float siderealDay = 86164.0905;
const float w5 = 2.0 * M_PI / siderealDay;

char buffer[40];

void setup()
{
  Serial.begin( 9600);
  dtostre( w5, buffer, 10, 0);   Serial.println( buffer);
}

void loop()
{
}

Result: 7.2921161e-05

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!

To keep the timer with millis() in sync with the time, you have to use millis in a different way.

Old:

if(time_now - time_last > delta){
  time_last = millis();
  ...

New:

if(time_now - time_last >= delta){
  time_last += delta;
  ...

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.

Wouldn't the compiler see the first float and then multiply them all as floats? Or is that not how it works?

"float*unsigned_long / int" becomes "float/int" becomes "float"?

Also I don't know if that 86164,0 vs 86164,1 will make a huge difference.

2*pi/ 86164,1 would be 0.00007292115

2*pi/ 86164,0 would be 0.00007292123

I ran the numbers and the error after a full hour of opereration would be
2pi/ 86164,03600180/pi - 2pi/ 86164,13600180/pi = 0.00001745632 degrees off.

I don't know if that would show up in a 1-minute exposure but I doubt it.

float*unsigned_long / int

In this case the compiler promotes or converts all three variables to type float then performs the operations left to right.

However, it does not hurt to make it clear what should be done by casting.

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 :slight_smile:

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.

The rules for C/C++ are very complicated and hard to remember. You can always look them up.

They are strictly adhered to by the compiler.