global/local variables

Hi

I’m coding my first ever arduino project and stumbled upon something weird.
As far as I know, the variables inside a function are not global. But when I verify a sketch with an extra function (other than setup and loop), the global variables ge up.

When I comment out the code inside that extra function, the global variables go down again.

It’s problematic because the function first gets a random number between 0-20, and then outputs text to a 16x2 LCD, with the text being different for every number. Getting rid of that function saves me 30% of global variables.

Am i misunderstanding global/local variants or is it just the IDE software that’s being wrong.

Thanks!

This code uses: 5514 bytes (17%) program memory, global variables use 547 bytes (26%) of dynamic memory, 1501 bytes left for local variables.

#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4 ,5, 6, 7, 3, POSITIVE);
 void RandomizerThingy();

void setup(){
  lcd.begin(16,2);
  lcd.clear();
  Serial.begin(115200);
}


void loop(){
//code here

 RandomizerThingy();

// OTHER CODE

}


void RandomizerThingy() {
randomSeed(analogRead(0));
int randNumber = random(20); 
switch (randNumber) {
  
  case 0:
  Msg(lcd, "Something", "Something", 5000);
  break;

  case 1:
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print ("     S");
  delay(500);
  lcd.print ("O");
  delay(500);
  lcd.print ("M");
  delay(500);
  lcd.print ("E");
  delay(500);
  lcd.print ("T");
  delay(500);
  lcd.print ("H");
  delay(500);
  lcd.setCursor(0, 0);
  lcd.print ("     I");
  delay(500);
  lcd.print ("N");
  delay(500);
  lcd.print ("G");
  delay(2000);
  break;
  
  case 2:
 Msg(lcd, "NOPE NOPE NOPE", "  NOPE NOPE NOPE", 5000);
  break;

  case 3:
 Msg(lcd, "Something", "Something", 5000);
  break;  
  
  case 4:
 Msg(lcd, "Something", "Something", 5000);
  break;

  case 5:
 Msg(lcd, "Something", "Something", 5000);
  break;  
  
  case 6:
 Msg(lcd, "Something", "Something", 5000);
  break;

  case 7:
 Msg(lcd, "Something", "Something", 5000);
  break; 
   
  case 8:
 Msg(lcd, "Something", "Something", 5000);
  break;

  case 9:
 Msg(lcd, "Something", "Something", 5000);
  break;  
  
  case 10:
 Msg(lcd, "Something", "Something", 5000);
  break;

  case 11:
 Msg(lcd, "Something", "Something", 5000);
  break;
  
  case 12:
 Msg(lcd, "Something", "Something", 5000);
  break;

  case 13:
  Msg(lcd, "Press button", "when light is on", 0);
randNumber = random(20);
  delay(randNumber);
  int LEDpin = 1;
  pinMode (LEDpin, OUTPUT);
  digitalWrite(LEDpin, HIGH);
  delay(100);
  digitalWrite(LEDpin, LOW);
  Msg(lcd, "Too slow", "!!!", 5000);
  break;  
 
  case 14:
 Msg(lcd, "Something", "Something", 5000);
  break;

  case 15:
 Msg(lcd, "Something", "Something", 5000);
  break;  
  
  case 16:
 Msg(lcd, "Something", "Something", 5000);
  break;

  case 17:
 Msg(lcd, "Something", "Something", 5000);
  break;  
  
  case 18:
 Msg(lcd, "Something", "Something", 5000);
  break;

  case 19:
 Msg(lcd, "Something", "Something", 5000);

  case 20:
 Msg(lcd, "Something", "Something", 5000);
  break;  
}
return;
}







//  A helper function to display messages of a specified duration
void Msg(LiquidCrystal_I2C &lcd, const char *top, const char *bottom, unsigned long del)
{ 
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print(top);
  lcd.setCursor(0, 1);
  lcd.print(bottom);
  delay(del);   
}

This code (when commented out all code in the randomizer function) uses: 4038 bytes (12%) program memory, global variables use 439 bytes (21%) of dynamic memory, 1609 bytes left for local variables.

#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4 ,5, 6, 7, 3, POSITIVE);
 void RandomizerThingy();

void setup(){
  lcd.begin(16,2);
  lcd.clear();
  Serial.begin(115200);
}


void loop(){
//code here

 RandomizerThingy();

// OTHER CODE

}


void RandomizerThingy() {
/*

randomSeed(analogRead(0));
int randNumber = random(20); 
switch (randNumber) {
  
  case 0:
  Msg(lcd, "Something", "Something", 5000);
  break;

  case 1:
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print ("     S");
  delay(500);
  lcd.print ("O");
  delay(500);
  lcd.print ("M");
  delay(500);
  lcd.print ("E");
  delay(500);
  lcd.print ("T");
  delay(500);
  lcd.print ("H");
  delay(500);
  lcd.setCursor(0, 0);
  lcd.print ("     I");
  delay(500);
  lcd.print ("N");
  delay(500);
  lcd.print ("G");
  delay(2000);
  break;
  
  case 2:
 Msg(lcd, "NOPE NOPE NOPE", "  NOPE NOPE NOPE", 5000);
  break;

  case 3:
 Msg(lcd, "Something", "Something", 5000);
  break;  
  
  case 4:
 Msg(lcd, "Something", "Something", 5000);
  break;

  case 5:
 Msg(lcd, "Something", "Something", 5000);
  break;  
  
  case 6:
 Msg(lcd, "Something", "Something", 5000);
  break;

  case 7:
 Msg(lcd, "Something", "Something", 5000);
  break; 
   
  case 8:
 Msg(lcd, "Something", "Something", 5000);
  break;

  case 9:
 Msg(lcd, "Something", "Something", 5000);
  break;  
  
  case 10:
 Msg(lcd, "Something", "Something", 5000);
  break;

  case 11:
 Msg(lcd, "Something", "Something", 5000);
  break;
  
  case 12:
 Msg(lcd, "Something", "Something", 5000);
  break;

  case 13:
  Msg(lcd, "Press button", "when light is on", 0);
randNumber = random(20);
  delay(randNumber);
  int LEDpin = 1;
  pinMode (LEDpin, OUTPUT);
  digitalWrite(LEDpin, HIGH);
  delay(100);
  digitalWrite(LEDpin, LOW);
  Msg(lcd, "Too slow", "!!!", 5000);
  break;  
 
  case 14:
 Msg(lcd, "Something", "Something", 5000);
  break;

  case 15:
 Msg(lcd, "Something", "Something", 5000);
  break;  
  
  case 16:
 Msg(lcd, "Something", "Something", 5000);
  break;

  case 17:
 Msg(lcd, "Something", "Something", 5000);
  break;  
  
  case 18:
 Msg(lcd, "Something", "Something", 5000);
  break;

  case 19:
 Msg(lcd, "Something", "Something", 5000);

  case 20:
 Msg(lcd, "Something", "Something", 5000);
  break;  
}
return;
*/
}







//  A helper function to display messages of a specified duration
void Msg(LiquidCrystal_I2C &lcd, const char *top, const char *bottom, unsigned long del)
{ 
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print(top);
  lcd.setCursor(0, 1);
  lcd.print(bottom);
  delay(del);   
}

This is just a small part of my code as an example. In my (almost) final code with more functions, it’s more like 88% of dynamic memory that’s used by global variables.

Just from a quick glance, you are commenting out library function calls. It may be that none of that code gets loaded.
Are there globals involved in that situation? I don’t know for sure. Not going to look at library code and I can’t see the rest of your code either.

Might be all the string literals. Take a look at the F() Macro.

gfvalvo:
Might be all the string literals. Take a look at the F() Macro.

Oh, that might be it. But then again, it shouldn't be a global variable if the literal strings are in a function, right?

The F literals (no matter where they are used) always exist hence get charged against “global” storage.

Regular variables within functions exist only when the function is running, they come and go and use the leftover space as the compiler notes.

You can have a variable in a function that retains its value, the keyword is ‘static”. These always exist hence also add to global storage usage.

a7

The F literals (no matter where they are used) always exist hence get charged against "global" storage.

Rubbish. The F() macro prevents the compiler from generating code to copy the literal from program memory to SRAM. Literals in the parens DO NOT OCCUPY SRAM.

If you are not anywhere near using all of your memory it is not something you need to be worried about at this point.

Any string literals (except those wrapped in an F() macro or otherwise stored in PROGMEM) will be copied to SRAM on initialization and hence use SRAM.

Make msg take a _flashStringHelper (or whatever it's called, too lazy to get the exact name/cases of that datatype) instead of a char array, and pass it strings in F() macro - I've done a similar thing with arrays of strings in PROGMEM here (note the FLASH macro DriftAnimate/DriftAnimatePlus.ino at master · SpenceKonde/DriftAnimate · GitHub )

DrAzzy:
Any string literals (except those wrapped in an F() macro or otherwise stored in PROGMEM) will be copied to SRAM on initialization and hence use SRAM.

Make msg take a _flashStringHelper (or whatever it's called, too lazy to get the exact name/cases of that datatype) instead of a char array, and pass it strings in F() macro - I've done a similar thing with arrays of strings in PROGMEM here (note the FLASH macro DriftAnimate/DriftAnimatePlus.ino at master · SpenceKonde/DriftAnimate · GitHub )

EDIT: This worked very well, my SRAM usage got from 91% down to 43%. This was very helpful, thank you!

Thanks, that's exactly what I'm trying to do now. It's going to take a while to change everything though.
I do understand now why you use F() macros, but I still don't understand why the compiler takes those literals strings as a global variable. Since they only exist in that function and get deleted out of SRAM when that function is quit. I do like to now why something is happening, understanding something is a bit more useful than only knowing how to do things.

Or is this what you are saying: all the literal strings in the whole program get loaded on the SRAM before the program is run and stay there forever, even when they are inside different functions?

but I still don't understand why the compiler takes those literals strings as a global variable. Since they only exist in that function

The second part of your statement is incorrect. The string literal exists. It is used only by that one function, but it is NOT local to that function. String literals are global in scope.

You could use the same string literal somewhere else, and the compiler would NOT make another copy of it.