Am I running out of UNO dynamic memory?

I was getting on well with my project that controls an audio processor and displays details on an OLED screen but I think I am running out of memory of some type.

I want to display some names on the OLED driven by a int value. So I made an array char inputName[10] to hold the names and displayed it on the OLED by calling the array element from int newInputNumber. It was working well, so I repeated the same for another array char tiltName[15] with more elements but it simply will not run, although it does compile.

Sketch uses 20292 bytes (62%) of program storage space. Maximum is 32256 bytes.
Global variables use 967 bytes (47%) of dynamic memory, leaving 1081 bytes for local variables. Maximum is 2048 bytes.

Simply reducing the number of elements in the char arrays it runs, but displays some weird mess in the bottom left of the screen. Reducing the number of elements further it will run as expected.

This compile works as expected:

Sketch uses 20122 bytes (62%) of program storage space. Maximum is 32256 bytes.
Global variables use 797 bytes (38%) of dynamic memory, leaving 1251 bytes for local variables. Maximum is 2048 bytes.

This is the weird mess on the screen:

Is there an easy way I can reduce memory usage without limiting my program function? Is this actually a memory issue?

#include <Wire.h> // I2C library.
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

// OLED screen setup.
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
#define OLED_RESET     4 // Reset pin # (or -1 if sharing Arduino reset pin)
#define SCREEN_ADDRESS 0x3C /// Screen I2C address, 7-bit.
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// Rotary encoder variables
const byte inputCLK = 3; // Arduino pin the rotary encoder CLK pin is connected to.
const byte inputDT = 2; // Arduino pin the rotary encoder DT pin is connected to.
int currentStateCLK; // Current state of rotary encoder pin CLK. Used to identify rotation.
int previousStateCLK; // Current state of rotary encoder pin CLK. Used to identify rotation.
boolean encCCW; // Rotary encoder flag to denote counter-clockwise rotatation by one pulse.
boolean encCW; // Rotary encoder flag to denote clockwise rotatation by one pulse.

// Push button variables
const byte buttonPin = 4; // Arduino pin the push button is connected to.
byte previousButtonState = HIGH;  // Assume switch open because of pull-up resistor.
byte currentButtonState; // Will be populated by reading button pin satae.
const unsigned long debounceTime = 5;  // Milliseconds of debounce.
unsigned long buttonPressTime;  // Time since button last changed state. Used to differentialte button bounce vs. intentional press.
boolean buttonPressed = 0; // A flag variable to identify button presses. Returned from button debounce code. 

// Menu variables
int mode = 1;  // Mode variable used to run switch case. 1 = volume, 2 = audio input, 3 = tone control.
int currentInputNumber = 0; // Audio input the system is currently set to.
int newInputNumber = 0; // Audio input user is in the process of choosing by rotary control.
int currentTiltValue = 6; // Current tone control setting.
int newTiltValue = 6; // Tone control setting the user is in the process of choosing by rotary control.
char inputName[][10] = {"ANALOG 1", "ANALOG 2", "ANALOG 3", "DIGITAL 1", "DIGITAL 2", "DIGITAL 3"};
char tiltName[][15] = {"-3.0dB  +3.0dB", "-2.5dB  +2.5dB", "-2.0dB  +2.0dB", "-1.5dB  +1.5dB", "-1.0dB  +1.0dB", "-0.5dB  +0.5dB", "0.0dB  0.0dB", "+0.5dB  -0.5dB", "+1.0dB  -1.0dB", "+1.5dB  -1.5dB", "+2.0dB  -2.0dB", "+2.5dB  -2.5dB", "+3.0dB  -3.0dB"};

// Gain variables 
float volume; // Volume level in dB.
float linGain; // Volume level as a linear gain value between 0 (no level) and 1 (full level).
uint32_t vol32; // Linear gain as a 32 bit word to populate hex array.
byte volArray[4]; // 4 byte array to hold volume hex values ready to send over I2C to the audio processor.

// Audio processor safeload addresses.
byte safeDataAddr0[2]; // 2-byte address to send first data word during a safe load.
byte safeDataAddr1[2]; // 2-byte address to send second data word during a safe load.
byte safeDataAddr2[2]; // 2-byte address to send third data word during a safe load.
byte safeDataAddr3[2]; // 2-byte address to send fourth data word during a safe load.
byte safeDataAddr4[2]; // 2-byte address to send fith data word during a safe load.
byte safeTargetAddr[2]; // 2-byte address to send target address during a safe load.
byte safeTrigAddr[2]; // 2-byte address to send number of words to write and trigger the safe load.
byte safeTrig1[4]; // Trigger safe load of 1 data word.

int dspAdd = 59; // Address of audio processor.
byte volAddress[4]; // 4-byte address of volume control.
unsigned long lastSafeWrite = -100; // Time at which the last safeload write was completed.

void setup() { 

  //Below addresses are fixed for safe load operations, do not change.
  safeDataAddr0[0] = 0x60;
  safeDataAddr0[1] = 0x00;
        
  safeDataAddr1[0] = 0x60;
  safeDataAddr1[1] = 0x01; 
     
  safeDataAddr2[0] = 0x60;
  safeDataAddr2[1] = 0x02;
        
 
  safeDataAddr3[0] = 0x60;
  safeDataAddr3[1] = 0x03;
 
  safeDataAddr4[0] = 0x60;
  safeDataAddr4[1] = 0x04;
 
  safeTargetAddr[0] = 0x60;
  safeTargetAddr[1] = 0x05;

  safeTrigAddr[0] = 0x60;
  safeTrigAddr[1] = 0x06;

  // Below variables are user configurable.
  volAddress[0] = 0x00; // Address of the volume module. Check in Sigma Studio.
  volAddress[1] = 0x00;
  volAddress[2] = 0x00;
  volAddress[3] = 0x19;
       

  safeTrig1[0] = 0x00; // Safeload trigger for 1 data word.
  safeTrig1[1] = 0x00;
  safeTrig1[2] = 0x00;
  safeTrig1[3] = 0x01;

    
  // Set encoder and push button pins as inputs.  
  pinMode (inputCLK,INPUT);
  pinMode (inputDT,INPUT);
  pinMode (buttonPin, INPUT_PULLUP);
  encCCW = 0; // Initial state for rotation flag not rotated.
  encCW = 0; // Initial state for rotation flag not rotated.
  previousStateCLK = digitalRead(inputCLK); // Read initial state of rotary encoder CLK pin. Assign to previousStateCLK so we can check for state changes.
   
  // Setup Serial Monitor
  Serial.begin (9600);

  // Setup I2C.
  Wire.begin(); // join i2c bus
  Wire.setClock(400000); // Set 400KHz frequency

  
  // Begin display. SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
    if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
        Serial.println(F("SSD1306 allocation failed"));
    for(;;); // Don't proceed, loop forever
    }

  volume = -50; // Set default volume level to -50dB.    
  sendVolume(); // Send volume to audio processor.

 
  display.clearDisplay(); // Clear the display.
  sendDisplay(); // Update the display with initial settings.

} 

void loop() {

  // Rotary encoder code.
  currentStateCLK = digitalRead(inputCLK); // Read the current state of inputCLK pin.

  if (currentStateCLK != previousStateCLK) { // If the previous and the current state of inputCLK are different, then the encoder and been rotated.

    if (currentStateCLK != digitalRead(inputDT)) { // If the inputCLK state is different than the inputDT state then the rotation is counterclockwise.
      encCCW = 1; // Set rotation flag counter-clockwise.
    }

    else { // If inputCLK and inputDT are the same then encoder is not rotating CCW.
      encCW = 1; // Set rotation flag clockwise.
    }

  }

  // Button press code with debounce.
  currentButtonState = digitalRead (buttonPin); 

  if (currentButtonState != previousButtonState){ // If the button has changed state since the last loop.

    if (millis () - buttonPressTime >= debounceTime){ // If the current time minus the time when the switch was pressed (i.e. how long since the switch chagned state) is more than the debounce period. (This statement acts on the leading edge of a switch press, but rejects any state changes that happen within 10ms after that).
      buttonPressTime = millis ();  // Set switch press time = now.
      previousButtonState = currentButtonState;  // Save switch state for next loop.

      if (currentButtonState == LOW){ // When debounce criteria are met and button is still pressed.
        buttonPressed = 1; // Set button flag to 1.
      }

      else { // When debounce criteria are not met because boutton is not pressed or it is bouncing.
        buttonPressed = 0;  // Set button flag to 0.
      }
      
    }

  }

  // Limit Mode (menu page) and loop back to main menu.
  if (mode >= 4){ // If mode is more than 3.
   mode = 1; // Loop back to 1.
  }
   

  switch (mode) { // Each menu page is a case operated by the 'mode' variable.
   
    case 1: // Main menu page, volume control.
     
      if (encCCW == 1) { // If the rotary encoder has been rotated counter-clockwise.
        volume = volume - 2; // Decrement volume level by one. 

        if (volume <= -60){ // Limit volume level. If volume is less than -60dB.
          volume = -60; // Make it -60dB.
        }  

        sendVolume(); // Send volume over i2C.
        updateDisplay(); // Print to serial monitor.
        sendDisplay();
      }

      if (encCW == 1) { // If the rotary encoder has been rotated clockwise.
        volume = volume + 2; // Increment volume level by one.
      
        if (volume >= 0){ // Limit volume level. If volume is greater than 0dB.
         volume = 0; // Make volume 0dB.
        }   
        sendVolume(); // Send volume over i2C.
        updateDisplay(); // Print to serial monitor.
        sendDisplay();
      }

      if (buttonPressed == 1) { // If button is pressed.
        mode = mode + 1 ; // Progresses to the next page (case 2).
        updateDisplay();  // Print to serial monitor.
                sendDisplay();
      }

      buttonPressed = 0; // Reset button press for next loop.
    break;  // End of case 1.
  
      
    case 2:
     
      if (encCCW == 1) { // If the rotary encoder has been rotated counter-clockwise.
        newInputNumber -= 1; // subtract 1 from input number.
        
        if (newInputNumber < 0){ // Limit input selection. If input number is less than 1.
          newInputNumber = 5; // Loop back to 5.
        }  
        
        updateDisplay();  // Print to serial monitor.
        sendDisplay();
      }

      if (encCW == 1) { // If the rotary encoder has been rotated clockwise.
        newInputNumber += 1; // Add 1 to the input number. 

        if (newInputNumber > 5){ // Limit input selection. If input number is more than 5.
          newInputNumber = 0; // Loop back to 1.
        }

        updateDisplay();  // Print to serial monitor.
        sendDisplay();
      }

      if (buttonPressed == 1) { // If button is pressed.

        if (newInputNumber == currentInputNumber){ // Without changing input selection.
         mode = mode + 1 ; // Increment mode by 1 to progress to the next menu page.
         updateDisplay();  // Print to serial monitor.
         sendDisplay();
        }

        else { // A new audio input is being selected.
         currentInputNumber = newInputNumber; // Set the current audio input to the new selection.
         mode = 1; // Go back to the main menu page (case 1)
         updateDisplay();  // Print to serial monitor.
         sendDisplay();
        }

      }

      buttonPressed = 0; // Reset button press for next loop.
    break;  // End of case 2.

    
    case 3:
     
      if (encCCW == 1) { // If the rotary encoder has been rotated counter-clockwise.
        newTiltValue -= 1; // Subtract 1 from tilt value.

        if (newTiltValue < 0){ // Limit tilt value. If tilt value is less than 1.
          newTiltValue = 0; // Make it 1.
        }   

        updateDisplay();  // Print to serial monitor.
        sendDisplay();
        
      }

      if (encCW == 1) { // If the rotary encoder has been rotated clockwise.
        newTiltValue += 1; // Add 1 to the tilt value. 

        if (newTiltValue > 12){ // Limit tilt value. If tilt value is more than 9.
          newTiltValue = 12; // Make it 9.
        }

        updateDisplay();  // Print to serial monitor.
        sendDisplay();
      }

        if (buttonPressed == 1) { // If button is pressed.

            if (newTiltValue == currentTiltValue){ // Without changing tilt control value.
              mode = 1 ; // Go back to the main menu page, because this is the last menu.
              updateDisplay();  // Print to serial monitor.
              sendDisplay();
            }

            else{ // A new tilt value has been selected.
                currentTiltValue = newTiltValue; // Set the current tilt value to the new tilt value.
                mode = 1; // Go back to the main menu page (case 1).
                updateDisplay();  // Print to serial monitor.
                sendDisplay();
            }

        }

      buttonPressed = 0; // Reset button press for next loop.
   
    break; // End of case 3.

  } // End of switch.

  // Rotary encoder reset values for next loop.  
  previousStateCLK = currentStateCLK; // Update previousStateCLK with the current state of inputCLK.
  encCCW = 0; // Reset encoder counter-clockwise flag.
  encCW = 0; // Reset encoder clockwise flag.

} // end of loop.

void updateDisplay(){ // Display the menu mode, volume level and current audio input.

  Serial.print("Mode:");
  Serial.println(mode);
  Serial.print("Volume: ");
  Serial.print(volume);
  Serial.println("dB");
  Serial.print("Current Input Number:");
  Serial.println(currentInputNumber);
  Serial.print("New Input Number:");
  Serial.println(newInputNumber);
  Serial.print("Current Tilt Control Value:");
  Serial.println(currentTiltValue);
  Serial.print("New Tilt Control Value:");
  Serial.println(newTiltValue);
  Serial.println();
  
}

void sendVolume(){  // Convert dB volume level to hex array and send by I2C to the audio processor.
 
 // Convery dB level to hex array.
  linGain = pow(10, volume/20); // Convert dB level to linear gain value.
  vol32 = linGain * 16777216; // Convert linear gain value stored as a float variable to a 32 bit integer.
  volArray[0] = (vol32 >> 24) & 0xFF; // Populate first byte of array with most significant 8 bits of the 32 bit volume word, by shifting word right by 24 bits. Adding 0xFF denotes it as a hex value.
  volArray[1] = (vol32 >> 16) & 0xFF; // Populate second byte of array with next most significant 8 bits of the 32 bit volume word, by shifting word right by 16 bits. Adding 0xFF denotes it as a hex value.
  volArray[2] = (vol32 >> 8) & 0xFF; // Populate third byte of array with next most significant 8 bits of the 32 bit volume word, by shifting word right by 8 bits. Adding 0xFF denotes it as a hex value.
  volArray[3] = vol32 & 0xFF; // Populate fourth byte of array with least significant 8 bits of the 32 bit volume word, naturally the first 8 bits of the word. Adding 0xFF denotes it as a hex value.

  // Send hex array by I2C safeload proceedure to audio processor.
    if (millis () - lastSafeWrite >= 1){ // Safeload should only occur once per audio frame (1ms is pleanty). 
      Serial.println("Sending Volume Now");  
      Wire.beginTransmission(dspAdd); // Begin I2C transmission to 7-bit address of audio processor.
      Wire.write(safeDataAddr0, 2); // Prepare to write safe load data bank 1.
      Wire.write(volArray, 4); // Write hex array holding linear volume level.
      Wire.endTransmission(); // Send data queue and end transmission with stop bit.
      Wire.beginTransmission(dspAdd); // Begin I2C transmission to 7-bit address (0x3B) adds R/W bit automatically. 
      Wire.write(safeTargetAddr, 2); // Prepare to write target address.
      Wire.write(volAddress, 4); // Write aadress of volume control module.
      Wire.endTransmission(); // Send data queue and end transmission with stop bit.
      Wire.beginTransmission(dspAdd); // Begin I2C transmission to 7-bit address (0x3B) adds R/W bit automatically.
      Wire.write(safeTrigAddr, 2); // Prepare to writing number of data banks used (1-5) and trigger safe load.
      Wire.write(safeTrig1, 4); // Trigger safe load writing 1 data bank.
      Wire.endTransmission(); // Send data queue and end transmission with stop bit.
      lastSafeWrite = millis (); // Store time when the latest write occured.
    }
}

void sendDisplay(){

    int16_t x1, y1;
    uint16_t width, height;
    display.setTextSize(2); // Draw 2X-scale text
    display.getTextBounds(inputName[newInputNumber], 0, 0, &x1, &y1, &width, &height);
  
  if (mode == 1) {
    display.clearDisplay(); // Clear the display.
    display.setTextSize(1); // Draw 1X-scale text
    display.setTextColor(SSD1306_WHITE);
    display.setCursor(2, 0); // Position.
    display.print(F("CLIP   ")); // Text.
    display.print(F("DRC   ")); // Text.
    display.println(F("AMP 34c")); // Text.
    display.print(F("---------------------")); // Text.
    display.setTextSize(4); // Draw 4X-scale text
    display.setCursor(5, 17); // Position.
    display.print(volume, 0); // Text.
    display.print(F("dB"));
    display.setTextSize(2); // Draw 4X-scale text
    display.setCursor((SCREEN_WIDTH - width) / 2, 50);
    display.print(inputName[newInputNumber]); // Text.
    display.display();      // Send to display.
  }

 if (mode == 2) {
    display.clearDisplay(); // Clear the display.
    display.setTextSize(1); // Draw 1X-scale text
    display.setTextColor(SSD1306_WHITE);
    display.setCursor(2, 0); // Position.
    display.print(F("CLIP   ")); // Text.
    display.print(F("DRC   ")); // Text.
    display.println(F("AMP 34c")); // Text.
    display.print(F("---------------------")); // Text.
    display.setTextSize(3); // Draw 3X-scale text
    display.setTextColor(SSD1306_WHITE);
    display.setCursor(20, 20); // Position.
    display.println(F("INPUT")); // Text.
    display.setTextSize(2); // Draw 2X-scale text
    display.setCursor((SCREEN_WIDTH - width) / 2, 50);
    display.print(inputName[newInputNumber]); // Text.
    display.display();      // Send to display.
  }

if (mode == 3) {
   
    display.clearDisplay(); // Clear the display.
    display.setTextSize(1); // Draw 1X-scale text
    display.setTextColor(SSD1306_WHITE);
    display.setCursor(2, 0); // Position.
    display.print(F("CLIP   ")); // Text.
    display.print(F("DRC   ")); // Text.
    display.println(F("AMP 34c")); // Text.
    display.print(F("---------------------")); // Text.
    display.setTextSize(2); // Draw 2X-scale text
    display.setTextColor(SSD1306_WHITE);
    display.setCursor(10, 17); // Position.
    display.println(F("Tilt Tone")); // Text.
    display.setTextSize(2); // Draw 2X-scale text
    display.setCursor(5, 50);
    display.print(tiltName[newTiltValue]); // Text.
    display.display();      // Send to display.
  }
 
}

Scope for more use of F() here

Thanks!

I've been reading about the progmem function. Seems a bit complicated :frowning:

It's not at all complicated to use the F() macro...in fact, it's already dotted around your code.

I understand how to use the F() macro to simply put a directly entered string in to progmem such as display.println(F("INPUT")); but how can I make use of this for my char array and then call it?

newTiltValue = 6;
const char tiltName[][16]  = {"-3.0dB  +3.0dB", "-2.5dB  +2.5dB", "-2.0dB  +2.0dB", "-1.5dB  +1.5dB", "-1.0dB  +1.0dB", "-0.5dB  +0.5dB", "0.0dB  0.0dB", "+0.5dB  -0.5dB", "+1.0dB  -1.0dB", "+1.5dB  -1.5dB", "+2.0dB  -2.0dB", "+2.5dB  -2.5dB", "+3.0dB  -3.0dB"};
display.print(tiltName[newTiltValue]);

these fixed texts should not need RAM space

For arrays that are const, the format is:

unsigned char logo[] PROGMEM = { 
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,

Reserve in memory arrays for data to be populated as the program runs.

Thank you, I got that far, but struggled with recalling the data in the needed way.

The compiler knows where the data is located.

void loop()
{
  for (int i = 0; i < 128 * 8; i++) // display Logo
    SendChar(pgm_read_byte(logo + i));
    delay(8000);
    displayOn();
    clear_display();

You are definitely low on dynamic memory (ram), the adafruit library allocates 1024 bytes of memory for the display buffer. The garbage you are seeing in the corner of the display is where the code is overwriting the buffer area.

Grab Adafruit's freeMemory function - it'll tell you just how bad the problem is.

PROGMEM is not that easy. At least not for me.
Admiring the F() macro I have problems with this one:

// const char t1[] PROGMEM {"Hello PROGMEM"}; // cannot be printed directly
const char tram[] {"Text in RAM ..... !"};

void setup() {
Serial.begin (9600);
Serial.println(tram);
// Serial.println(t1);
const __FlashStringHelper* t2 = F("Text in Flash via F() ");
Serial.println(t2);
}

void loop() {}

This works, but requires t2 to be a local one. If I move it outside setup(), I get

error: statement-expressions are not allowed outside functions nor in template-argument lists
 #define F(string_literal) (reinterpret_cast<const __FlashStringHelper *>(PSTR(string_literal)))
                                                                          ^

Any hints ?

Yes, PSTR must be inside a function. But, you could make the pointer global. That would allow you to use the PROGMEM string in any function:

const __FlashStringHelper* t2;

void setup() {
  Serial.begin(115200);
  t2 = F("Text in Flash via F() ");
  Serial.println(t2);
}

void loop() {
  Serial.println(t2);
  delay(1000);
}
1 Like

Now we need an array of F()

to do something like display.print(tiltName[newTiltValue]);

const __FlashStringHelper* tarr[] = {F("Text 1 in Flash via F() "), F("Text2")} ;
Serial.println(tarr[1]);

locally it's fine, but if tarr is global, the ambiguity with = as assignment or initializer comes up.

Since all your text is nearly the same length, use a global 2-dimensional array (or if you prefer to declare the array within a function, declare it as "static"):

const char tiltName[][15] PROGMEM = { //reformatted a bit for readability
  "-3.0dB  +3.0dB",
  "-2.5dB  +2.5dB",
  "-2.0dB  +2.0dB",
  "-1.5dB  +1.5dB",
  "-1.0dB  +1.0dB",
  "-0.5dB  +0.5dB",
  "0.0dB  0.0dB",
  "+0.5dB  -0.5dB",
  "+1.0dB  -1.0dB",
  "+1.5dB  -1.5dB",
  "+2.0dB  -2.0dB",
  "+2.5dB  -2.5dB",
  "+3.0dB  -3.0dB"
};

Then to print the text cast the char* to __FlashStringHelper*:

    display.print((__FlashStringHelper*)tiltName[newTiltValue]); // Text.
1 Like

Sure. Thanks. I was sitting too close to the problem. (Or the problem was me myself)
The only reason for the invention of FlashStringHelper was that cast.

Never wanted to have the texts in a function, that was what PSTR forced me to do.

The main reason I would declare a PROGMEM array in a function is readability and convenience - if it is ONLY used within that function, then it is easier to understand the code with all the information in one place. If you don't declare the array as static inside a function, then the compiler basically ignores the PROGMEM and makes it a local variable:

static const char inputName[][10] PROGMEM = {

Use this.

Documentation.

Bonus points if you know the original name of the phantom type.

Amazing, thank you! It seems odd that FlashStringHelper is built in to current Arduino IDE yet my googling of PROGMEM and F() didn't turn up any mention of this!

P.S. I had read that when you add PROGMEM to an char array it only puts the array in memory, not the strings. So each string needs to be separately declared with PROGMEM.

This is what I read: Do you know Arduino? – PROGMEM demystified – E-Tinkers

Storing array of strings in Program Space
Sometime you have an array of strings (a string itself is an array in C), and it is often take up relatively large amounts of memory, so you'd want to put them in Program Space.
~~
const char *instructions =
{
"Connecting to WiFi...",
"WiFi connected",
"Establishing TCP Connection...",
"TCP established",
"Connection terminated"
};
~~
However, it is not sufficient to just declare the istructions array with PROGMEM, because AVR GCC only treats the declaration that it attached to as as PROGMEM variable. So in this case, we successfully put the instructions variable, the array itself, in the program memory by adding PROGMEM modifier. This DOES NOT put the actual strings themselves into program memory. You will have to declare each string with PROGMEM.
~~
const char string_1 PROGMEM = "Connecting to WiFi...";
const char string_2 PROGMEM = "WiFi connected";
const char string_3 PROGMEM = "Establishing TCP Connection...";
const char string_4 PROGMEM = "TCP established";
const char string_5 PROGMEM = "Connection terminiated";
~~

This does not seem to be the case with the code David_2018 kindly wrote.

const char tiltName[][15] PROGMEM = { //reformatted a bit for readability
  "-3.0dB  +3.0dB",
  "-2.5dB  +2.5dB",
  "-2.0dB  +2.0dB",
  "-1.5dB  +1.5dB",
  "-1.0dB  +1.0dB",
  "-0.5dB  +0.5dB",
  "0.0dB  0.0dB",
  "+0.5dB  -0.5dB",
  "+1.0dB  -1.0dB",
  "+1.5dB  -1.5dB",
  "+2.0dB  -2.0dB",
  "+2.5dB  -2.5dB",
  "+3.0dB  -3.0dB"
};

You'll notice that the strings in the example are of differing lengths, and the array is of type char pointer, which is not the case in the example provided by david_2018, where the strings are all of the same length (15 characters,including terminator)

1 Like