Running out of memory on an Arduino Nano

I am running out of memory with the below code. Why is that? I have had much larger programs without even getting close to running out of memory.

Secondly, if I need a board with a larger memory and a linear 5V ADC, which should I go for? I plan to add a lot more code to this, planning to install a LCD to it too.

#include <Arduino.h>

int readPin1 = A0;
int readingA0;
float V1 = 0;

// CD4066 switch control pins
int switchPin1 = 2;
int switchPin2 = 3;
int switchPin3 = 4;
int switchPin4 = 5;

// Switching frequency variables
unsigned long switchInterval = 3; // Switching interval in microseconds for 300 kHz
unsigned long lastSwitchMillis = 0;
bool switchState = true; // Initial state

#define NUM_READINGS 600 // Change this to 1000

int readings[NUM_READINGS];
int currentReadingIndex = 0;
float movingAverage = 0;

void setup()
{
 pinMode(readPin1, INPUT);

 // Set CD4066 switch control pins as OUTPUT
 pinMode(switchPin1, OUTPUT);
 pinMode(switchPin2, OUTPUT);
 pinMode(switchPin3, OUTPUT);
 pinMode(switchPin4, OUTPUT);

 Serial.begin(9600);

 // Initialize movingAverage to the initial value of readingA0
 for (int i = 0; i < NUM_READINGS; ++i)
 {
   readings[i] = analogRead(readPin1);
   movingAverage += readings[i];
 }
 movingAverage /= NUM_READINGS;
}

void loop()
{
 readingA0 = analogRead(readPin1);

 // Switch electrodes at a rate of 300 kHz
 if ((micros() - lastSwitchMillis) >= switchInterval)
 {
   lastSwitchMillis = micros();
   switchState = !switchState;

   if (switchState)
   {
     // Connect the first electrode to 5V and the second electrode to A0
     digitalWrite(switchPin1, HIGH);
     digitalWrite(switchPin2, LOW);
     digitalWrite(switchPin3, LOW);
     digitalWrite(switchPin4, HIGH);
   }
   else
   {
     // Connect the first electrode to A0 and the second electrode to 5V
     digitalWrite(switchPin1, LOW);
     digitalWrite(switchPin2, HIGH);
     digitalWrite(switchPin3, HIGH);
     digitalWrite(switchPin4, LOW);
   }
 }

 // Update the moving average
 movingAverage += (readingA0 - readings[currentReadingIndex]) / static_cast<float>(NUM_READINGS);

 // Store the new reading
 readings[currentReadingIndex] = readingA0;

 // Increment the current reading index
 currentReadingIndex = (currentReadingIndex + 1) % NUM_READINGS;

 // Print the average every 1000 readings
 if (currentReadingIndex == 0)
 {
//    Serial.print("Moving average: ");
   Serial.println(movingAverage);
 }
}

There goes around 60% of the available dynamic memory.

I am actually not even happy with 600. I'd like to average a thousand values. FIrstly because my analog serial print is still not a straight line, secondly for fun sake.

Besides the memory problem, you can't switch at 300kHz. Micros only has a resolution of 4usec.
You need a faster board with more RAM

I've got an esp32, but I hear its ADC isn't linear. I even have an STM32 (blue pill), is that good?

The ESP32 ADC has to be calibrated like any other ADC. However it's input range is restricted to 150mV ~ 3100mV (does not go to zero) but is supposedly "linear" in that range.

Know nothing about the STM32

What do you mean by calibration? I've never calibrated arduino adc. Also since the CD4066 switches at logic 5V i think I'd need a 5V board, else I'd have to add up more hardware.

Why keep 600 or 1000 readings - what good does it do you? If all you really need is the average value, look into 'leaky integrator' postings on this forum.

2 Likes

readingA0 = analogRead(readPin1);
Highly recommend you put that in a loop, all by itself, and calculate how quickly it can run. I think you'll find you're not going to reach 300 kHz. You might get beyond 1kHz, depending on how you approach the rest of your code.

Yes. @thekinginthenorth might stumble upon

for example.

a7

2 Likes

I wrote the below code to find out what was the actual frequency. Its around 5.5KHz only. I need to be at "at least 10KHz".

#include <Arduino.h>

int readPin1 = A0;
int readingA0;
float V1 = 0;

// CD4066 switch control pins
int switchPin1 = 2;
int switchPin2 = 3;
int switchPin3 = 4;
int switchPin4 = 5;

// Switching frequency variables
unsigned long switchInterval = 3; // Switching interval in microseconds for 300 kHz
unsigned long lastSwitchMillis = 0;
bool switchState = true; // Initial state

#define NUM_READINGS 600 // Change this to 1000

int readings[NUM_READINGS];
int currentReadingIndex = 0;
float movingAverage = 0;

unsigned long analogReadCounter = 0; // Counter for analogRead operations
unsigned long startTime = 0;         // Time at the start of the measurement interval

void setup()
{
  pinMode(readPin1, INPUT);

  // Set CD4066 switch control pins as OUTPUT
  pinMode(switchPin1, OUTPUT);
  pinMode(switchPin2, OUTPUT);
  pinMode(switchPin3, OUTPUT);
  pinMode(switchPin4, OUTPUT);

  Serial.begin(115200);

  // Initialize movingAverage to the initial value of readingA0
  for (int i = 0; i < NUM_READINGS; ++i)
  {
    readings[i] = analogRead(readPin1);
    movingAverage += readings[i];
  }
  movingAverage /= NUM_READINGS;
}

void loop()
{
  // Record the start time of the measurement interval
  if (analogReadCounter == 0)
  {
    startTime = micros();
  }

  readingA0 = analogRead(readPin1);

  // Increment the counter for analogRead operations
  analogReadCounter++;

  // Switch electrodes at a rate of 300 kHz
  if ((micros() - lastSwitchMillis) >= switchInterval)
  {
    lastSwitchMillis = micros();
    switchState = !switchState;

    if (switchState)
    {
      // Connect the first electrode to 5V and the second electrode to A0
      digitalWrite(switchPin1, HIGH);
      digitalWrite(switchPin2, LOW);
      digitalWrite(switchPin3, LOW);
      digitalWrite(switchPin4, HIGH);
    }
    else
    {
      // Connect the first electrode to A0 and the second electrode to 5V
      digitalWrite(switchPin1, LOW);
      digitalWrite(switchPin2, HIGH);
      digitalWrite(switchPin3, HIGH);
      digitalWrite(switchPin4, LOW);
    }
  }

  // Update the moving average
  movingAverage += (readingA0 - readings[currentReadingIndex]) / static_cast<float>(NUM_READINGS);

  // Store the new reading
  readings[currentReadingIndex] = readingA0;

  // Increment the current reading index
  currentReadingIndex = (currentReadingIndex + 1) % NUM_READINGS;

  // Print the average every 1000 readings
  if (currentReadingIndex == 0)
  {
    Serial.print("Moving average: ");
    Serial.println(movingAverage);

    // Calculate and print the actual frequency
    unsigned long elapsedTime = micros() - startTime;
    float actualFrequency = static_cast<float>(analogReadCounter) / (elapsedTime / 1e6); // Convert to Hz
    Serial.print("Actual Frequency: ");
    Serial.print(actualFrequency);
    Serial.println(" Hz");

    // Reset counters for the next measurement interval
    analogReadCounter = 0;
  }
}

An ADC measures a voltage relative to a reference voltage. Most people use the Vcc (5V) for the reference voltage on the UNO and Nano. The 5V has a nominal accuracy of +/- 1% and will vary a little depending on the load and digital noise. So the accuracy of your ADC reading will never be any bettor than +/- 1%. That's usually acceptable for most people and you really can't calibrate out that error.

If you need more accuracy, you should use an external reference. The ADC has a reference input (AREF) where you can supply a more accurate and cleaner reference to the ADC or you can use the internal reference but that does require calibration.

1 Like

@thekinginthenorth
That code could be improved, but 10kHz is attainable only if you do very little else.

Better explore calibration with @jim-p.

For maximum sampling speed, directly program the ADC and use interrupts to read the data, analogRead() is slower and blocking.

Then why not start with better hardware?

Sure, if you're into that. I sense the OP isn't into assembly.

I actually don't much understand what programming the adc means.

I think the simplest thing for me would be to try the ESP32, as in any case I am going to need more memory, since a touch screen is to be added yet.

1 Like

The AVR chip docs tell ADC precision is +/- 2 of actual.

That's even less than 1% for sure but it seems to me that if I want to trust reads I'd >> 2 and treat them as 8 bit reads, eliminate dither.

How analog sensors are wired makes a difference. Short/long, shielded, paired, EM noise... all change what can be got.

If want a 5V linear ADC then the ESP32 is the wrong choice.
You might consider an external ADC like the ADS1015 with the ESP32

1 Like