SOLVED: Alternative to function with switch statements for low-RAM indexing

I am struggling to understand how the compiler is causing my RAM memory budget to get blown when I include a default case in a switch statement.

How come uncommenting
#define INCLUDE_DEFAULT_CASE
in the following test program decreases program memory compiled size by 598 bytes and increases global RAM usage by 200 bytes?

Is there an alternative way of adding a default case that will not increase global RAM usage?

Test program is attached.

And here are the compiled sizes of the program with and without the default case:

With default case:
progmem = 2158
global RAM = 582

Without default case:
progmem = 2756
global RAM = 382

here is the Arduino IDE build output dump:

C:\Program Files (x86)\Arduino\arduino-builder -dump-prefs -logger=machine -hardware C:\Program Files (x86)\Arduino\hardware -hardware C:\Users\Boompy\AppData\Local\Arduino15\packages -tools C:\Program Files (x86)\Arduino\tools-builder -tools C:\Program Files (x86)\Arduino\hardware\tools\avr -tools C:\Users\Boompy\AppData\Local\Arduino15\packages -built-in-libraries C:\Program Files (x86)\Arduino\libraries -libraries C:\Users\Boompy\Documents\Arduino\libraries -fqbn=MiniCore:avr:328:bootloader=uart0,variant=modelPB,BOD=2v7,LTO=Os_flto,clock=8MHz_internal -ide-version=10805 -build-path C:\Users\Boompy\AppData\Local\Temp\arduino_build_724911 -warnings=all -build-cache C:\Users\Boompy\AppData\Local\Temp\arduino_cache_990899 -prefs=build.warn_data_percentage=75 -verbose C:\Users\Boompy\Desktop\ArduinoSandbox\big_case_default_RAM\big_case_default_RAM.ino
C:\Program Files (x86)\Arduino\arduino-builder -compile -logger=machine -hardware C:\Program Files (x86)\Arduino\hardware -hardware C:\Users\Boompy\AppData\Local\Arduino15\packages -tools C:\Program Files (x86)\Arduino\tools-builder -tools C:\Program Files (x86)\Arduino\hardware\tools\avr -tools C:\Users\Boompy\AppData\Local\Arduino15\packages -built-in-libraries C:\Program Files (x86)\Arduino\libraries -libraries C:\Users\Boompy\Documents\Arduino\libraries -fqbn=MiniCore:avr:328:bootloader=uart0,variant=modelPB,BOD=2v7,LTO=Os_flto,clock=8MHz_internal -ide-version=10805 -build-path C:\Users\Boompy\AppData\Local\Temp\arduino_build_724911 -warnings=all -build-cache C:\Users\Boompy\AppData\Local\Temp\arduino_cache_990899 -prefs=build.warn_data_percentage=75 -verbose C:\Users\Boompy\Desktop\ArduinoSandbox\big_case_default_RAM\big_case_default_RAM.ino
Using board '328' from platform in folder: C:\Users\Boompy\AppData\Local\Arduino15\packages\MiniCore\hardware\avr\2.0.3
Using core 'MCUdude_corefiles' from platform in folder: C:\Users\Boompy\AppData\Local\Arduino15\packages\MiniCore\hardware\avr\2.0.3
Detecting libraries used...
"C:\Users\Boompy\AppData\Local\Arduino15\packages\arduino\tools\avr-gcc\5.4.0-atmel3.6.1-arduino2/bin/avr-g++" -c -g -Os -w -std=gnu++11 -fno-exceptions -ffunction-sections -fdata-sections -fno-threadsafe-statics  -w -x c++ -E -CC -mmcu=atmega328pb -DF_CPU=8000000L -DARDUINO=10805 -DARDUINO_AVR_ATmega328 -DARDUINO_ARCH_AVR -Wextra -flto -g  "-IC:\Users\Boompy\AppData\Local\Arduino15\packages\MiniCore\hardware\avr\2.0.3\cores\MCUdude_corefiles" "-IC:\Users\Boompy\AppData\Local\Arduino15\packages\MiniCore\hardware\avr\2.0.3\variants\pb-variant" "C:\Users\Boompy\AppData\Local\Temp\arduino_build_724911\sketch\big_case_default_RAM.ino.cpp" -o "nul"
Generating function prototypes...
"C:\Users\Boompy\AppData\Local\Arduino15\packages\arduino\tools\avr-gcc\5.4.0-atmel3.6.1-arduino2/bin/avr-g++" -c -g -Os -w -std=gnu++11 -fno-exceptions -ffunction-sections -fdata-sections -fno-threadsafe-statics  -w -x c++ -E -CC -mmcu=atmega328pb -DF_CPU=8000000L -DARDUINO=10805 -DARDUINO_AVR_ATmega328 -DARDUINO_ARCH_AVR -Wextra -flto -g  "-IC:\Users\Boompy\AppData\Local\Arduino15\packages\MiniCore\hardware\avr\2.0.3\cores\MCUdude_corefiles" "-IC:\Users\Boompy\AppData\Local\Arduino15\packages\MiniCore\hardware\avr\2.0.3\variants\pb-variant" "C:\Users\Boompy\AppData\Local\Temp\arduino_build_724911\sketch\big_case_default_RAM.ino.cpp" -o "C:\Users\Boompy\AppData\Local\Temp\arduino_build_724911\preproc\ctags_target_for_gcc_minus_e.cpp"
"C:\Program Files (x86)\Arduino\tools-builder\ctags\5.8-arduino11/ctags" -u --language-force=c++ -f - --c++-kinds=svpf --fields=KSTtzns --line-directives "C:\Users\Boompy\AppData\Local\Temp\arduino_build_724911\preproc\ctags_target_for_gcc_minus_e.cpp"
Compiling sketch...
"C:\Users\Boompy\AppData\Local\Arduino15\packages\arduino\tools\avr-gcc\5.4.0-atmel3.6.1-arduino2/bin/avr-g++" -c -g -Os -Wall -Wextra -std=gnu++11 -fno-exceptions -ffunction-sections -fdata-sections -fno-threadsafe-statics -MMD -mmcu=atmega328pb -DF_CPU=8000000L -DARDUINO=10805 -DARDUINO_AVR_ATmega328 -DARDUINO_ARCH_AVR -Wextra -flto -g  "-IC:\Users\Boompy\AppData\Local\Arduino15\packages\MiniCore\hardware\avr\2.0.3\cores\MCUdude_corefiles" "-IC:\Users\Boompy\AppData\Local\Arduino15\packages\MiniCore\hardware\avr\2.0.3\variants\pb-variant" "C:\Users\Boompy\AppData\Local\Temp\arduino_build_724911\sketch\big_case_default_RAM.ino.cpp" -o "C:\Users\Boompy\AppData\Local\Temp\arduino_build_724911\sketch\big_case_default_RAM.ino.cpp.o"
C:\Users\Boompy\Desktop\ArduinoSandbox\big_case_default_RAM\big_case_default_RAM.ino: In function 'void loop()':

C:\Users\Boompy\Desktop\ArduinoSandbox\big_case_default_RAM\big_case_default_RAM.ino:326:13: warning: value computed is not used [-Wunused-value]

       *ptr++;

             ^

C:\Users\Boompy\Desktop\ArduinoSandbox\big_case_default_RAM\big_case_default_RAM.ino: In function 'uint16_t* dumbFunc(uint8_t)':

C:\Users\Boompy\Desktop\ArduinoSandbox\big_case_default_RAM\big_case_default_RAM.ino:315:1: warning: control reaches end of non-void function [-Wreturn-type]

 }

 ^

Compiling libraries...
Compiling core...
Using precompiled core
Linking everything together...
"C:\Users\Boompy\AppData\Local\Arduino15\packages\arduino\tools\avr-gcc\5.4.0-atmel3.6.1-arduino2/bin/avr-gcc" -Wall -Wextra -Os -Wl,--gc-sections -mmcu=atmega328pb -w -flto -g -o "C:\Users\Boompy\AppData\Local\Temp\arduino_build_724911/big_case_default_RAM.ino.elf" "C:\Users\Boompy\AppData\Local\Temp\arduino_build_724911\sketch\big_case_default_RAM.ino.cpp.o" "C:\Users\Boompy\AppData\Local\Temp\arduino_build_724911/..\arduino_cache_990899\core\core_MiniCore_avr_328_bootloader_uart0,variant_modelPB,BOD_2v7,LTO_Os_flto,clock_8MHz_internal_a3be4008cf22b28f5690fc62bd869695.a" "-LC:\Users\Boompy\AppData\Local\Temp\arduino_build_724911" -lm
"C:\Users\Boompy\AppData\Local\Arduino15\packages\arduino\tools\avr-gcc\5.4.0-atmel3.6.1-arduino2/bin/avr-objcopy" -O ihex -j .eeprom --set-section-flags=.eeprom=alloc,load --no-change-warnings --change-section-lma .eeprom=0  "C:\Users\Boompy\AppData\Local\Temp\arduino_build_724911/big_case_default_RAM.ino.elf" "C:\Users\Boompy\AppData\Local\Temp\arduino_build_724911/big_case_default_RAM.ino.eep"
"C:\Users\Boompy\AppData\Local\Arduino15\packages\arduino\tools\avr-gcc\5.4.0-atmel3.6.1-arduino2/bin/avr-objcopy" -O ihex -R .eeprom  "C:\Users\Boompy\AppData\Local\Temp\arduino_build_724911/big_case_default_RAM.ino.elf" "C:\Users\Boompy\AppData\Local\Temp\arduino_build_724911/big_case_default_RAM.ino.hex"
Sketch uses 2756 bytes (8%) of program storage space. Maximum is 32256 bytes.
Global variables use 382 bytes (18%) of dynamic memory, leaving 1666 bytes for local variables. Maximum is 2048 bytes.

big_case_default_RAM.ino (8.6 KB)

It's compiler optimization at work. Your program doesn't do any useful work, so what is your concern other than knowing that?

I have developed a comms stack as part of a larger app that allows a client MCU to query the values of the global variables in a host MCU that is controlling an off grid battery/lighting system setup.

Part of how I ended up building the host comms stack (running on an ATmega328PB) was to use functions with large switch statements to return indexed properties of the registers (e.g. what eeprom backup address is associated with them, how many bytes the registers are, whether they are read only or read/write, etc) as shown in the example sketch in my original post. I did this instead of using const arrays because through testing/compiling I found that const arrays always seemed to use RAM for some reason (shouldn't const arrays be stored in program memory?), while switch statements in functions used progmem (unless I provided a default case in the switch statement, at which point I'm guessing the compiler "optimizes" the switch statement out of progmem and into RAM, thus using up my RAM budget).

In other words instead of this:

#include "EEPROM.h"
uint16_t myVar1 = 0;
uint16_t myVar2 = 4;
uint16_t myVar3 = 8;//deprecated
uint16_t myVar4 = 12; 
enum my_registers {
   REG_1,
   REG_2,
   REG_3,
   REG_4, //Deprecated 
   NUM_REGISTERS
};
const uint16_t eeprom_addresses[NUM_REGISTERS] = {
   0,
   123,
   456,//deprecated
   789
};
uint16_t * const getRegister[] = {
  &myVar1,
  &myVar2,
  &myVar3,//deprecated
  &myVar4 
};
void setup() {
}

void loop() {
  //Access random register
  uint8_t reg_to_access = 0;
  if(digitalRead(1)) reg_to_access++;
  if(digitalRead(2)) reg_to_access++;
  if(digitalRead(3)) reg_to_access++;
  if(digitalRead(4)) reg_to_access++;
  EEPROM.write(eeprom_addresses[REG_1], *getRegister[reg_to_access]); 
}

I am doing this:

#include "EEPROM.h"
uint16_t myVar1 = 0;
uint16_t myVar2 = 4;
uint16_t myVar3 = 8;
uint16_t myVar4 = 12; //deprecated
enum my_registers {
   REG_1,
   REG_2,
   REG_3,
   REG_4, // deprecated
   NUM_REGISTERS
};
const uint16_t eeprom_addresses(uint8_t targReg){
   switch(targReg){
      case REG_1:
         return 0;
      case REG_2:
         return 123;
      case REG_3:
         return 456;
      case REG_4:
         return 789;
//      default:
//        return NULL;
   }
};
uint16_t * const getRegister(uint8_t targ_reg){
   switch(targ_reg){
      case REG_1:
         return &myVar1;
      case REG_2:
         return &myVar2;
      case REG_3:
         return &myVar3;
      case REG_4:
         return &myVar4;
//      default: 
//        return NULL;
   }
}
void setup() {
}

void loop() {
  //Access random register
  uint8_t reg_to_access = 0;
  if(digitalRead(1)) reg_to_access++;
  if(digitalRead(2)) reg_to_access++;
  if(digitalRead(3)) reg_to_access++;
  if(digitalRead(4)) reg_to_access++;
  //Check register exists
  if(getRegister(reg_to_access)){
    EEPROM.write(eeprom_addresses(reg_to_access), *getRegister(reg_to_access)); 
  }
}

As I'm refactoring the code base, I am interested in getting rid of some old legacy register that I no longer require in order to free up space for further development (running out of progmem and RAM).

But, to deal with legacy client requests for values of variables that no longer, I am now looking at getting rid of the case statements for the legacy variables, and catching all legacy requests in a default case. But it seems that doing so blows up what little remaining RAM budget I have.

In the second code example above, if I comment out myVar3 and all case handlers for it, and then add a default case handler to the switch statements, the memory use goes up by 6 bytes, which defeats the purpose of getting rid of the register to begin with:

#include "EEPROM.h"
uint16_t myVar1 = 0;
uint16_t myVar2 = 4;
//uint16_t myVar3 = 8;
uint16_t myVar4 = 12; //deprecated
enum my_registers {
   REG_1,
   REG_2,
   REG_3,
   REG_4, // deprecated
   NUM_REGISTERS
};
const uint16_t eeprom_addresses(uint8_t targReg){
   switch(targReg){
      case REG_1:
         return 0;
      case REG_2:
         return 123;
//      case REG_3:
//         return 456;
      case REG_4:
         return 789;
      default:
        return NULL;
   }
};
uint16_t * const getRegister(uint8_t targ_reg){
   switch(targ_reg){
      case REG_1:
         return &myVar1;
      case REG_2:
         return &myVar2;
//      case REG_3:
//         return &myVar3;
      case REG_4:
         return &myVar4;
      default: 
        return NULL;
   }
}
void setup() {
}

void loop() {
  //Access random register
  uint8_t reg_to_access = 0;
  if(digitalRead(1)) reg_to_access++;
  if(digitalRead(2)) reg_to_access++;
  if(digitalRead(3)) reg_to_access++;
  if(digitalRead(4)) reg_to_access++;
  //Check register exists
  if(getRegister(reg_to_access)){
    EEPROM.write(eeprom_addresses(reg_to_access), *getRegister(reg_to_access)); 
  }
}

That is why I'd like to know if there are ways of:

  • Adding some kind of low-RAM legacy case handling other than a default in a switch statement
  • Alternative low-RAM ways of indexing a bunch of eeprom addresses to an enumerated register list

You should look at PROGMEM.

The reason the const arrays take up ram is so that the 'usual' methods of reading values works - as a Harvard architecture, reading values from flash is different.

By explicitly using PROGMEM and related functions, you can have those constant arrays stay in flash, not using precious RAM.

macdonaldtomw:

  • Adding some kind of low-RAM legacy case handling other than a default in a switch statement

Can you make the default case the initial value, and set it to something else if needed?

something like

return_val = default_case value;

switch
{
case 1:
    return_val = case_1_value;
    break;
[blah blah blah]
//  No default case needed now!
}

return return_val;

MHotchin:
Can you make the default case the initial value, and set it to something else if needed?

something like

return_val = default_case value;

switch
{
case 1:
return_val = case_1_value;
break;
[blah blah blah]
// No default case needed now!
}

return return_val;

Okay, I tried this and still end up getting a compile RAM usage of 23 bytes (6 bytes larger than base case) and progmem 1046 (56 bytes smaller than base case prior to deprecation). See code testing below.

I also tried a bunch of variations on your suggestion. See attached sketch_mar06a.ino file

MHotchin:
You should look at PROGMEM.

The reason the const arrays take up ram is so that the ‘usual’ methods of reading values works - as a Harvard architecture, reading values from flash is different.

By explicitly using PROGMEM and related functions, you can have those constant arrays stay in flash, not using precious RAM.

OK, let me try that.

The base case (no legacy register request handling) stays at RAM = 17 bytes but progmem goes down 42 bytes (nice!)

The legacy case handling version has RAM go down 2 bytes and progmem go down 2 bytes from base case (sweet!)

This is the answer. Also, the code is very nice to look at compared to massive switch statements:

THANK YOU!

#include "EEPROM.h"

/*=============================================>>>>>
= BASE CASE:  no deprecated myVar3 =

Sketch uses 1060 bytes (3%) of program storage space. Maximum is 32256 bytes.
Global variables use 17 bytes (0%) of dynamic memory, leaving 2031 bytes for local variables. Maximum is 2048 bytes.
===============================================>>>>>*/

/*=============================================>>>>>
= DEPRECATED CASE: get rid of myVar3 and free up 2 bytes RAM and 2 bytes progmem! =

Sketch uses 1058 bytes (3%) of program storage space. Maximum is 32256 bytes.
Global variables use 15 bytes (0%) of dynamic memory, leaving 2033 bytes for local variables. Maximum is 2048 bytes.
===============================================>>>>>*/

#define DEPRECATE_REG_3

uint16_t myVar1 = 0;
uint16_t myVar2 = 4;
#ifndef DEPRECATE_REG_3
uint16_t myVar3 = 8;//deprecated --> uncomment when compiling base case!
#endif
uint16_t myVar4 = 12;
enum my_registers {
   REG_1,
   REG_2,
   REG_3, // deprecated
   REG_4,
   NUM_REGISTERS
};
uint16_t const eeprom_addresses[] PROGMEM = {
   0,
   123,
   #ifdef DEPRECATE_REG_3
   0xFF,
   #else
   456,
   #endif
   789
};
uint16_t * const getRegister[] PROGMEM = {
   &myVar1,
   &myVar2,
   #ifdef DEPRECATE_REG_3
   NULL,
   #else
   &myVar3,
   #endif
   &myVar4
};
void setup() {
}
void loop() {
   //Access random register
   uint8_t reg_to_access = 0;
   if(digitalRead(1)) reg_to_access++;
   if(digitalRead(2)) reg_to_access++;
   if(digitalRead(3)) reg_to_access++;
   if(digitalRead(4)) reg_to_access++;
   //Check register exists
   if(getRegister[reg_to_access]){
      EEPROM.write(eeprom_addresses[reg_to_access], *getRegister[reg_to_access]);
   }
}

sketch_mar06a.ino (4.54 KB)