Looping without Maxing out RAM

Hey,
I am working on a test bench that will test 8 magnets in a rotating fixture. I am able to read all the sensors and put the values into an arrays, but I can't do the calculations that I want to do. The programm will let me do the calculation on one array but I can't loop in through all the arrays without the compiler saying that I don't have enough RAM. The board that I am using is the Controllino Mega. Is it possible to make this loop work?

#include <Controllino.h>  // Usage of CONTROLLINO library allows you to use CONTROLLINO_xx aliases in your sketch. //


volatile int count = 0;       //sets the variable count to zero. A variable should be declared volatile whenever its value
volatile int countzero = 0;  //can be changed by something beyond the control of the code section in which it appears, such as a concurrently executing
//thread. In the Arduino, the only place that this is likely to occur is in sections of code associated with interrupts, called an interrupt service routine
int switchstate = 0;

int Gauss = 0;

int testArray[363];   //defines array for hall sensor 1
int testArray2[363];   //defines array for hall sensor 2
int testArray3[363];   //defines array for hall sensor 3
int testArray4[363];   //defines array for hall sensor 4
int testArray5[363];   //defines array for hall sensor 5
int testArray6[363];   //defines array for hall sensor 6
int testArray7[363];   //defines array for hall sensor 7
int testArray8[363];   //defines array for hall sensor 8

int result = 1;   //Result for strength   0 = ok, 1 =NOK
int result1 = 1;   //Result for angel error  0 for OK 1 for NOK
int approved = 1;
int tt = 0;
void setup() {

  Serial.begin(9600);  //Sets the data rate in bits per second (baud) for serial data transmission

  pinMode(CONTROLLINO_R0, OUTPUT);  //Configures the specified pin to behave either as an input or an output
  //R0 is a relay that Will start the motor
  pinMode(CONTROLLINO_D0, OUTPUT);  //output signal to robot if parts are ok


  pinMode(CONTROLLINO_IN0, INPUT);  //IN0 is an interupt and will read the pulse from the refernce point

  pinMode(CONTROLLINO_A0, INPUT);     //pin for the start switch

  pinMode(CONTROLLINO_A1, INPUT);     //pin for the hall sensor 1

  pinMode(CONTROLLINO_A2, INPUT);     //pin for hall sensor 2
  pinMode(CONTROLLINO_A3, INPUT);     //pin for hall sensor 3
  pinMode(CONTROLLINO_A4, INPUT);     //pin for hall sensor 4
  pinMode(CONTROLLINO_A10, INPUT);     //pin for hall sensor 5
  pinMode(CONTROLLINO_A9, INPUT);     //pin for hall sensor 6
  pinMode(CONTROLLINO_A7, INPUT);     //pin for hall sensor 7
  pinMode(CONTROLLINO_A8, INPUT);     //pin for hall sensor 8

  //allows external interrupts
  attachInterrupt(digitalPinToInterrupt(CONTROLLINO_IN0), zero, RISING);   //attachInterrupt(digitalPinToInterrupt(pin), ISR, mode) (recommended)   pin: the Arduino pin number.
  //ISR: the ISR to call when the.  mode: defines when the interrupt should be triggered. interrupt occurs

}

void loop() {

  switchstate = digitalRead(CONTROLLINO_A0);

  result = 0;
  result1 = 0;

  if (switchstate == HIGH)
  {


    digitalWrite(CONTROLLINO_R0, HIGH);

    delay(6000);



    for (int work = 0; work < 8; work++)
    {
      approved = Eval1();
    }


    if (approved == 0)
    {
      //digitalWrite(CONTROLLINO_D0, HIGH);
      Serial.println("OK");
    }
    else
    {
      //digitalWrite(CONTROLLINO_D1, HIGH);
      Serial.println("NOK");
    }

  }

  delay(30);
  countzero = 0;
  tt = 0;
}


//**********************************************************************Function: Count zero pulse************************************************************************

void zero()
{

  countzero += 1;   //add one to the count of zero pulses
  int x = countzero - 1;

  testArray[x] = analogRead(CONTROLLINO_A1) * 1.07;
  testArray2[x] = analogRead(CONTROLLINO_A2) * 1.07;
  testArray3[x] = analogRead(CONTROLLINO_A3) * 1.07;
  testArray4[x] = analogRead(CONTROLLINO_A4) * 1.07;
  testArray5[x] = analogRead(CONTROLLINO_A10);
  testArray6[x] = analogRead(CONTROLLINO_A7) * 1.07;
  testArray7[x] = analogRead(CONTROLLINO_A8) * 1.07;
  testArray8[x] = analogRead(CONTROLLINO_A9) * 1.07;

  if (x == 363)
  {
    digitalWrite(CONTROLLINO_R0, LOW);

    //for (int i=0;i<453;i++){
    //Serial.println(testArray8[i]);

    //}
    //Serial.println("done testArray");
  }
}




//*************************************************************************Function: Evaluate testArray*********************************************************
//**************************************************************************************************************************************************************

int Eval1()
{

  int calcArray[363];
  float tempA = 0;
  float maxMin = 0;

  tt += 1;




  if (tt == 1)
  {
    memmove(calcArray, testArray, sizeof(testArray));
  }

  if (tt == 2)
  {
    memmove(calcArray, testArray2, sizeof(testArray2));
  }

  if (tt == 3)
  {
    memmove(calcArray, testArray3, sizeof(testArray3));
  }

  if (tt == 4)
  {
    memmove(calcArray, testArray4, sizeof(testArray4));
  }

  if (tt == 5)
  {
    memmove(calcArray, testArray5, sizeof(testArray5));
  }

  if (tt == 6)
  {
    memmove(calcArray, testArray6, sizeof(testArray6));
  }

  if (tt == 7)
  {
    memmove(calcArray, testArray7, sizeof(testArray7));
  }

  if (tt == 8)
  {
    memmove(calcArray, testArray8, sizeof(testArray8));
  }



  //--------------------------------------------------------------------------------Smooth the values---------------------------------
  for (int t = 0; t < 363; t++)
  {
    calcArray[t] = 0.5 * calcArray[t + 3] + 0.3 * calcArray[t + 2] + 0.2 * calcArray[t + 1];
  }
  //for (int i=0;i<453;i++){
  //Serial.println(calcArray[i]);

  //}
  //Serial.println("done smoooth");
  //----------------------------------------------------------------------------average the two revolutions--------------------------------
  //for (int u = 0; u < 360; u++)
  //{
  //  calcArray[u] = (calcArray[u] + calcArray[u + 360]) / 2;
  // }
  //for (int i=0;i<360;i++){
  //Serial.println(calcArray[i]);

  //}
  //Serial.println("Done averae");
  //-----------------------------------------------------------------------find max, min and amplitude--------------------------------
  float maxTemp = calcArray[0];                           //max value place holder
  float minTemp = calcArray[0];                           //min value place holder

  for (int u = 1; u < 360; u++)
  {
    if (calcArray[u] > maxTemp)                           //find the maximum in the array.
    {
      maxTemp = calcArray[u];
    }

    if (calcArray[u] < minTemp)                           //find the minimum in the array.
    {
      minTemp = calcArray[u];
    }
  }

  maxMin = (maxTemp - minTemp) / 2;   //evaluate amplitude

  Serial.println(maxMin);
  Serial.println("next");

  //-----------------------------------------------------------------------convert to cos'---------------------------------------
  for (int v = 0; v < 360; v++)
  {
    calcArray[v] = (calcArray[v] - (maxTemp - maxMin)) / maxMin;
  }

  //for (int i=0;i<360;i++){
  //Serial.println(calcArray[i]);

  //}
  //Serial.println("Done cos'");

  //---------------------------------------------------------------------arctan2 plus conversion to degrees-----------------------------------------

  for (int w = 0; w < 360; w++)
  {
    calcArray[w] = atan2(calcArray[w], calcArray[w + 89]) * 180 / 3.1415;
  }
  //for (int i=0;i<360;i++){
  //Serial.println(calcArray[i]);

  //}
  //Serial.println("done angle");

  //----------------------------------------------------------------------shift array so that 180 degrees is first--------------------------------------------------------------------
  for (int z = 1; z < 361; z++)
  {
    if (calcArray[0] < calcArray[1])
    {

      float temp = calcArray[0];                        // save x[0] in temporary

      for (int from = 1; from < 361; from++)            // shift remaining values to the front of the array
      { int to = from - 1;
        calcArray[to] = calcArray[from];
      }

      calcArray[359] = temp;                            // put x[0] saved in temp into last entry
    }
  }
  //for (int i = 0; i < 360; i++) {
  //Serial.println(calcArray[i]);

  //}
  //Serial.println("done shift");

  //-------------------------------------------------------------------------check delta angle------------------------------------------------------------------------

  for (int pp = 0; pp < 360; pp++)
  {
    tempA = 0;

    float delta = calcArray[pp] - (180 - pp);

    if (abs(delta) > tempA)
    {
      tempA = abs(delta);
    }
  }


  //--------------------------------------------------------------------------Ok eller NOK parts----------------------------------------------------------------
  if (maxMin < 30)                                    //part not ok
  {
    //Serial.println(maxMin);
    result = 1;
  }

  if (tempA > 3.00)                                     //part not ok
  {
    //Serial.println(tempA);
    result1 = 1;
  }
  if (result == 2 || result1 == 2)
  {
    return (1);
    //break;
  }
  else
  {
    return (0);
  }
}

That is only a warning, which is generated whenever a sketch uses 75% or more of the available dynamic memory, not a definite indication you do not have enough memory. Even with the additional local array in your Eval1 function you appear to have a considerable amount of memory to spare.

Sketch uses 7884 bytes (3%) of program storage space. Maximum is 253952 bytes.
Global variables use 6201 bytes (75%) of dynamic memory, leaving 1991 bytes for local variables. Maximum is 8192 bytes.
Low memory available, stability problems may occur.

You do have some fairly obvious problems that the compiler should be warning you about, where you are exceeding the bounds of an array:

/home/Arduino/forumtest/forumtest.ino: In function 'Eval1()':
/home/Arduino/forumtest/forumtest.ino:187:41: warning: iteration 360 invokes undefined behavior [-Waggressive-loop-optimizations]
     calcArray[t] = 0.5 * calcArray[t + 3] + 0.3 * calcArray[t + 2] + 0.2 * calcArray[t + 1];
                          ~~~~~~~~~~~~~~~^
/home/Arduino/forumtest/forumtest.ino:185:21: note: within this loop
   for (int t = 0; t < 363; t++)
                   ~~^~~~~
/home/Arduino/forumtest/forumtest.ino:242:56: warning: iteration 274 invokes undefined behavior [-Waggressive-loop-optimizations]
     calcArray[w] = atan2(calcArray[w], calcArray[w + 89]) * 180 / 3.1415;
                                        ~~~~~~~~~~~~~~~~^
/home/Arduino/forumtest/forumtest.ino:240:21: note: within this loop
   for (int w = 0; w < 360; w++)
                   ~~^~~~~

A more serious problem is that you are doing several analog reads in an interrupt service routine. Analog reads are fairly slow, made even slower by the floating point math, and ISRs generally should be made fairly fast to avoid interfering with other interrupts. It is usually better to have the ISR set a flag, then test for the flag in the main code and do the slower processing there.

Variables used in both the ISR and other parts of the code should be declared as volatile.

1 Like

And …. You could look at alternative boards , Nano Every ? Which may or may not have more memory ( I’ll leave you to look up )

You can use pointer in Eval1 function. Do you need this large of sample size? Can you do this in parallel with multiple boards, as mentioned by @hammy? Can you serialize the pins?

There appears to be a protocol used by pins. I do not see the point or issue with this code using an interrupt, however it would depend a lot on this protocol. If you lose this protocol's feedback you lose one of your samples. You can miss multiple samples, however I am guessing the performance is not an issue.

I agree overall with @david_2018's point on this. I think you could use super loop for this. You do not need to do the floating point in the ISR, however you may not have other ISRs you care about. Printing from ISR can also be bad.

I meant alternative ( different ) boards not using boards in parallel serial or otherwise .

Hey David_2018,
Thank you for your comment. I will move the floating point math to the main body of the code as you have suggested. I don't have any other interrupts and I have not seen any problems with the reads. The interrupt is from a angle sensor where I have one pulse for every degree. One rotation takes approximately 3 seconds so I have 0,5 seconds to make the 8 reads.

My arrays are a mess like you pointed out. I had totally missed this. I will try and rewrite it so that everything meshes.

The Mega has 8k of static RAM, enough for 4096 ints (if nothing else is using the RAM).
You declare 2904 ints as arrays (8 x 363), using 5808 bytes for the arrays, leaving just over 2k for other stuff - so its not clear if you can make it fit, but its not immediately ruled out.

BTW why not declare a multi-dimensional array:

int testArrays [8][363];

I never thought of using a multi-dimensional array. I will give it a try and see what happens.

Multidimensional array will simplify parts of the code. Since the array is int, a lot of your floating point calculations get truncated to whole numbers, and could be calculated with integer math if speed is important.

A big problem I see is where you calculate the arctan and use 359 + 89 for the array index, might need more samples for that.

1 Like

Hello David_2018,

The multidimensional array has helped to simplify the code. It was a lot easier with the looping.

Since I am measuring a singel complete Cosinus curve i changed the code so that i jump back to the first position in the array when I run out of samples.

Thanks again to everyone for all the help. I think the code should work now!

Multiarray may limit performance. Check compiler settings or fix manually. The non multiarray logic may be better without optimization, but the multiarray can be the same.

Using memcpy will limit performance. You can fix this with multiarray or with pointer.

Unless you are using a microcontroller with hardware floating point this is a non-issue - emulated floating point operations will be the performance limiting factor by a factor of 100's.

Not if you build look up table. In that case it does not matter. Compiler did not likely do that. Technically you need two tables.

This case is very simple. Bound input and linear computation. Perfect for look up. The compiler in theory could do this for you. I just do not know if it does or not. In fact there are a couple different levels it could reach. Possibly fully automate the whole function. There is only so many values possible from analog read that it can compute them all.

This would be like combinational reduction using memory in hardware. Then memory accesses become more critical. However if the compiler is doing a decent job of automation this would not matter. Manual acceleration is possible here. However this is a little more advanced.

One major problem, its huge.

Hey,

Thanks to everyone that took time to answer my question. My code is now working with a 2D array. It uses 76% of available RAM but it works. In the future I will use a DAQ to do the sampling and the Controllino to do the motor steering.