Why local variables are avoided in Arduino?

17

In several code examples for the Arduino, I note that there is almost no use of locally scoped variables. One of the examples in the IDE: Analog > AnalogInput :

int sensorPin = A0;
int ledPin = 13;
int sensorValue = 0;

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

void loop() {
  sensorValue = analogRead(sensorPin);
  digitalWrite(ledPin, HIGH);
  delay(sensorValue);
  digitalWrite(ledPin, LOW);
  delay(sensorValue);
}

The variable sensorValue is global, whereas its use is only within the loop function. Another case is the Moving-Average-Filter library. Part of the code:

#define MAX_DATA_POINTS 20

class MovingAverageFilter {
public:
  MovingAverageFilter(unsigned int newDataPointsCount);
  float process(float in);

private:
  float values[MAX_DATA_POINTS];
  int k;
  int dataPointsCount;
  float out;
  int i;
};

Here the out and i members are only used in process , and should be local:

float MovingAverageFilter::process(float in) {
  out = 0;

  values[k] = in;
  k = (k+1) % dataPointsCount;

  for (i=0; i<dataPointsCount; i++) {
    out += values[i];
  }

  return out/dataPointsCount;
}

Using variables in this way seems absurd to me. Is this intentional? If so, what is the reason?

The only possibility I can imagine is that the address of local variables would be known at compile time, so they can be accessed without taking into account the stack's register. Does it really make a difference?

But in the case of the class, I can not see how it could be faster to read an object through the this pointer than to read it in the stack relative to the registered.

Another explanation might be to avoid having to allocate a stack frame to the function. But this allocation should be as simple as incrementing a registrant, I do not understand why it should be avoided. In addition, functions that take arguments will have a stack frame anyway.

    
asked by anonymous 24.12.2013 / 14:21

3 answers

15

I researched the subject and found no good answer about it. The reasons I managed to raise are as follows:

  • Programs written for the Arduino are usually quite simple and there is very little memory available. As a result, few people care much about modularisation and encapsulation in Arduino programs.

  • Arduino does not use a single main() method to run the program. Instead, it uses two independent setup() and loop() methods. The result is that for you to be able to use within loop() what was set in setup() , you end up being forced to use global variables.

  • Often the variables used in the loop() method must be remembered between one iteration and another. This causes you to be forced to use global variables.

  • Arduino is simple and too limited for you to use events, callbacks, and messaging effectively.

  • Most of the examples are written for beginners who know little about C, so everything is somewhat simplified. In addition, most users of Arduino do not have much interest, practice, or training in software programming because their focus is on hardware.

Useful but inconclusive discussions can be found here and here .

In conclusion: I recommend following good traditional programming practices. Constants can be easily optimized by the compiler, so you can declare them in global scope without problems (the problem of global variables is when the value is changed, which does not occur with constants). Anything that can change value, it's best to stay in a local scope, unless you have no choice. If you are going to use global scope, remember the static modifier to make the variable private and make it available to getters and setters functions if you need to export it.

    
25.12.2013 / 19:30
5

See what the Arduino website says:

  

"When programs start to get bigger and more complex,   Local variables are a useful way of ensuring that only the   have access to their own variables. This prevents programming errors when a function inadvertently modifies variables used by another function.   Sometimes it is convenient to declare and initialize variables within a for (...). "   ( link )

The indiscriminate use of global variables is not advisable; should be used only when it is necessary to access their values from different blocks. Use constants or local variables whenever possible.

    
29.01.2014 / 16:11
2

What happens is that variables defined within a scope are allocated in the frame of the function (in the stack).

Variables defined globally go straight to the data segment.

The difference is that while accessing a data from the data segment is used direct addressing, to access data allocated in the frame are accessed by indexed indirect addressing.

For those who understand ASM Z80, it's something like (AVR uses RISC, that's not exactly what happens in AVR):

Direct addressing:

LD A, (VAR_ADDRESS)

Indexed indirect addressing:

LD HL, stack_pointer
LD A, (HL),VAR_INDEX

Obviously the first form is faster, ie it consumes fewer processor cycles. Even if there is an AVR instruction that does everything at once, it still hits the memory twice: one to pick up the base, another to pick up the die (whose address is calculated from the base, which in itself also spends processor cycles).

So if you are programming a time critical algorithm, each processor cycle counts and you will want to generate the fastest instruction possible. Most of the time, this gain is marginal but sometimes it makes a difference.

An alternative to global variables is to use "static" scope variables. They are allocated in the data segment equally, but only the scope where it was defined is able to address it:

float MovingAverageFilter::process(float in) {
  static float out = 0;

  values[k] = in;
  k = (k+1) % dataPointsCount;

  static int i;
  for (i=0; i<dataPointsCount; i++) {
    out += values[i];
  }

  return out/dataPointsCount;
}

A side effect is that instead of just having an "i" in the universe, each function that declares its static int i to its counters will allocate an address in the data segment - 10 functions, 10 variables allocating space. / p>

So if your RAM space is getting short, it's best to declare globally even to save space on the data segment.

Sometimes we need to give up best practices when dealing with limited hardware. When you have only 2K of RAM, spending 40 bytes with 10 32-bit counters and doing the same service with only 4 bytes can be the difference between using a cheaper AVR, or having to use a more expensive one.

If you want to produce thousands of units of your product, a 1 USD more expensive chip will cost you thousands of dollars more to do the same service.

EDIT: Please note that I have solemnly ignored compiler optimizations. A compiler could, if the counter was declared within the scope of the function (and use for (int i = 0 ....) is a way to help the compiler to pull this out), it could go straight to an AVR it has 32!), and this saves memory and processor cycles - nothing is faster than using the register!

    
17.10.2015 / 03:06