1

I currently know 3 ways to do a program. Which one of them is the most memory friendly. Note that the example code is simple (blinking a led) and the final code will be VERY VERY complex. Also, the way I code is not the best, so suggest any suggestions in the way to write things or where to put them please.

1- All in the loop. The most basic way of programming.

const int led = 2;
const int interval = 500;
bool ledState = false;

void setup() {
  pinMode(led, OUTPUT);
}

void loop() {
  ledState = !ledState;
  digitalWrite(led, ledState);
  delay(interval);
}

2- Functions. Similar to the loop but a little neither. Can be splitted into many tabs.

const int led = 2;
const int interval = 500;
bool ledState = false;

void setup() {
  pinMode(led, OUTPUT);
}

void loop() {
  blinkLed();
}

void blinkLed()
{
  ledState = !ledState;
  digitalWrite(led, ledState);
  delay(interval);
}

Then finnaly, objects. The most supreme of them all (and the one that I know the least). I like objects and classes because multitasking with it is much easier. It is more flexible too. Code taken from here.

class Flasher
{
  // Class Member Variables
  // These are initialized at startup
  int ledPin;      // the number of the LED pin
  long OnTime;     // milliseconds of on-time
  long OffTime;    // milliseconds of off-time

  // These maintain the current state
  int ledState;                 // ledState used to set the LED
  unsigned long previousMillis;   // will store last time LED was updated

  // Constructor - creates a Flasher 
  // and initializes the member variables and state
  public:
  Flasher(int pin, long on, long off)
  {
  ledPin = pin;
  pinMode(ledPin, OUTPUT);     

  OnTime = on;
  OffTime = off;

  ledState = LOW; 
  previousMillis = 0;
  }

  void Update()
  {
    // check to see if it's time to change the state of the LED
    unsigned long currentMillis = millis();

    if((ledState == HIGH) && (currentMillis - previousMillis >= OnTime))
    {
      ledState = LOW;  // Turn it off
      previousMillis = currentMillis;  // Remember the time
      digitalWrite(ledPin, ledState);  // Update the actual LED
    }
    else if ((ledState == LOW) && (currentMillis - previousMillis >= OffTime))
    {
      ledState = HIGH;  // turn it on
      previousMillis = currentMillis;   // Remember the time
      digitalWrite(ledPin, ledState);   // Update the actual LED
    }
  }
};


Flasher led(2, 500, 500);

void setup()
{
}

void loop()
{
  led.Update();
}

So, which is the best out of those 3.

  • Which board are you targeting?Which version of the hardware core for that board are you using? – per1234 Oct 17 '16 at 12:50
  • Arduino Pro Mini by Sparkfun (Atmega328P_smd) – Dat Ha Oct 17 '16 at 12:52
  • Which version of Arduino AVR Boards? You can check at Tools > Board > Boards Manager... – per1234 Oct 17 '16 at 12:55
  • its version 1.6.14 – Dat Ha Oct 17 '16 at 13:00
  • Compile each variant and present the memory foot-print together with basic code analysis (LOC, complexity). And please define what you think is better. For instance, the OOP Flasher variant can be reduced in footprint and still keep the abstraction by rewriting to a template class. – Mikael Patel Oct 17 '16 at 13:25
7

That is an incredibly hard question to give a definitive answer to.

In general using OO consumes more memory, but that is the price you pay for greater flexibility and ease of use. The difference is usually minor though. Variables within an object take the same amount of space as variables outside an object. It's just the overhead of instantiating the object itself that adds to the memory usage, and that can be quite small, depending of course on what the object does.

Calling functions increases memory use on the stack. Every time you call a function the return address and a number of the internal CPU registers, along with some of the parameters, get placed on the stack. The deeper your function calls go the bigger the stack gets. But that is all temporary - as you leave the functions the stack shrinks again.

However, this is where it gets more complex:

The compiler performs optimizations. One of them is to consider functions for inlining. This replaces the function call with the content of the function that is being called. It makes for a slightly larger program, but it's a program that runs faster and uses less stack.

In your first two examples, because there is only one call to the blinkLed() function, the compiler will see that as a good candidate for using inline. So your first and second programs may well both end up exactly the same.

There are times, as well, where variables that you declare don't actually ever exist. For instance, if you use a couple of temporary variables to do a few calculations in a function and then return the result the compiler may instead just use CPU registers to store the temporary values instead of allocating variable space in memory.

So it is very hard to say "this is better" or "that is better". Instead you should pay more attention to how and where you declare and use your variables.

In general there are two rules:

  1. Keep your variables the smallest size possible for the contents (use 8 bit variables instead of int for storing smaller numbers).
  2. Keep the scope of variables as small as possible.

The latter is often overlooked. The scope of a variable is the block (delimited by { and } for example) where the variable is declared. Anything inside that block, including sub-blocks, can see the variable. By keeping the variable in as small a scope as possible you increase the chances that the compiler will choose to use CPU registers instead of allocating memory. For example:

int i;

for (i = 0; i < 1000; i++) {
    .... do something ....
}

.... other things ....

for (i = 30; i < 2000; i++) {
    .... do something else ....
}

That is pretty much guaranteed to allocate memory for i. However:

for (int i = 0; i < 1000; i++) {
    .... do something ....
}

.... other things ....

for (int i = 30; i < 2000; i++) {
    .... do something else ....
}

With that method the variable i will only exist within the first loop, after which it disappears. It is then recreated as an entirely new variable for the second loop. It doesn't need to stay in memory in between. The compiler may just place it in internal registers, or it may reuse the same block of memory, etc.

Also by keeping scope as small as possible you increase the ability of the compiler to re-use the same bits of memory for different purposes. 10 integers in different scopes doesn't mean 20 bytes of memory - it may mean just 2 bytes for all 10 integers.

So as you can see managing memory effectively can be a bit of a black art at times.

  • One other note not directly related to the code in the question which may be relevant to your final project - keep an eye on your use of the standard libraries. e.g. std::string will take more memory than using a c style char* for a string, lists/vectors will take up more memory than an array etc... – Andrew Oct 17 '16 at 15:11
  • Optimizing your first example into the second is one of the basic optimizations automatically done by the compiler... provided the two loops belong to the same function. – Edgar Bonet Oct 17 '16 at 20:13
  • @EdgarBonet You're making assumptions about the compiler. Yes, avr-gcc 4.9.2 might do that. Other versions might not. Other compilers for other chips might not. It is highly likely that it might, but it's not something you should rely 100% on happening if you can give the compiler a helping hand with just simple things like keeping the scope to the absolute minimum. – Majenko Oct 17 '16 at 20:17
  • If yours doesn't do this basic stuff, you should probably get a better one. – Edgar Bonet Oct 17 '16 at 20:19
  • @EdgarBonet Unless a better one isn't available. Not all compilers are GCC. Some are incredibly basic. Some are little more than a direct conversion to equivalent assembly. – Majenko Oct 17 '16 at 20:21
0

There are very good observations about coding with limited resources in this book:

https://www.amazon.com/Programming-Arduino-Next-Steps-Sketches/dp/0071830251

It covers the items you ask about and more besides, including some brought up in the comments.

As others indicate- there are some 'best' practices to consider but often the most efficient method is context-dependent.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.