Use less SRAM by converting strings to char arrays?

Hello all,

I have a project going on and it is getting pretty big, but I need it to run on an Uno. I have done everything in my knowledge to make the code as small as possible in terms of SRAM, things like storing serial outputs in flash instead of SRAM and using small data types, but it has a lot of strings that take serial data in, and compare it to things and I would like some help with converting those strings into character arrays. I can make a custom sized array to hold the data using Serial.available() but how do I put the whole serial input string into it and how do I compare it to see if the input is correct? I have included a function from the code below to give an example. This example is very similar elsewhere in the code, just different variables and the like:

void validateCard(uint8_t chipSelect) {
	Serial << F("Ensure the SD card is inserted.\n");

	boolean cardValid = true;
	String loopRequest;
	if (!SD.begin(chipSelect)) {
		Serial << F("The SD card is invalid!\nCorrect it and enter \"X\" to try again\n");
		tone(BUZZER, ERR, 800);
		cardValid = false;
	}
	else {
		Serial << F("The SD card is valid. ") << "\n";
		tone(BUZZER, READY, 250);
	}

	//If the above statements determined there was no card, loop until there is a valid SD card.
	while (cardValid == false) {
		loopRequest = Serial.readString();
		while (loopRequest == "") {
			loopRequest = Serial.readString();
		}

		if (loopRequest == "X" || loopRequest == "x") {
			if (SD.begin(chipSelect)) {
				Serial << F("The SD card is valid. ") << "\n";
				tone(BUZZER, READY, 250);
				cardValid = !cardValid;
			}
			else {
				Serial << F("The SD card is invalid! Try again.\n");
				tone(BUZZER, ERR, 800);
			}
		}
	}
}

Any help is needed.

If it's more than one character that you want to compare, use 'strcmp_P()':-

char myString[6]="hello";
if(strcmp_P(myString, PSTR("hello"))==0)
{
    // Do something here
}

If it's a single character as in your code, just use == :-

char loopRequest = Serial.read();
if(loopRequest == 'X' || loopRequest == 'x')
{
    // Do something here
}

With C strings, custom-sized arrays created on-the-fly, (dynamic memory allocation), aren't a good idea. It's better to create a suitably-sized buffer in advance.
If you must do it, use 'new' and 'delete':-

int iStringSize = 10;
char *pString = new char[iStringSize];
// Use the buffer here
delete[] pString;  // clean up afterwards

And don't forget to allow an extra element for the terminating nul, '\0'.

Edit: And to copy a C string to a char array, use strcpy_P():-

char myString[6];
strcpy_P(myString, PSTR("hello"));

Ok, I have implemented this into what I could in the code, but in another function, it expects a number up to 65,535; how would I put this number into an array and still be able to make sure that the array is under 65,535 and above zero. Even if this cannot be done, the changes in the other functions have reduced memory usage by about 4% so thank you for that.

Edit:

When I try to use this:

char useSer = Serial.read();
		while (useSer == "") {
			useSer = Serial.read();
		}

		boolean stringValid = true;
		useSer.toUpperCase();
		if (useSer == F('Y')) {
			//Save 'yes' to EEPROM.
			EEPROM.update(EPRM_USESER, 2);
			sensorConfig(true);
		}
		else if (useSer == F('N')) {
			//Save 'no' to EEPROM.
			EEPROM.update(EPRM_USESER, 1);
			sensorConfig(false);
		}
		else {
			Serial << F("Input is invalid! Try again.\n");
			tone(BUZZER, ERR, 800);
			stringValid = !stringValid;
		}

It will not compile this and it says that operand types are incompatible ("char" and "const char") inside the if statements

For one thing, you're using "String" class member functions on a char variable. 'toUpperCase()' is a member of the String class so can't be used on a 'char' variable. It'll throw an error on compilation.

This also won't work:-if (useSer == F('Y'))You'll get a compiler error. You don't need to use the F macro for a single byte anyway.

I already showed you how to do it:-if(loopRequest == 'X' || loopRequest == 'x')
If you really want to save more SRAM, you could probably do away with the "Streaming" library and just use 'Serial.print()' and 'Serial.println()'. You could do some testing, but I think that the extra library will use more SRAM and more flash program memory.
There's not much extra typing in this:-Serial.println(F("Input is invalid! Try again."));compared to this:-Serial << F("Input is invalid! Try again.\n");Edit: I take that back. :smiley:
I just downloaded the library and did a simple test.
Surprisingly, this:-Serial << F("Hello") << "\n";uses less flash and SRAM than this:-Serial.println(F("Hello"));

Here, you're comparing a 'char' to a string:-

char useSer = Serial.read();
while (useSer == "")
{
    useSer = Serial.read();
}

Maybe you should consider 'Serial.available()' if you want to check if a character is available at the serial port.

Mike44449:

char useSer = Serial.read();

while (useSer == "")
{
  useSer = Serial.read();
}

As OldSteve states, you can not compare characters with character arrays.

If you read the Serial.read() reference, you will see that Serial.read() returns -1 if no data is available. So you should test for that or use Serial.available()

Reading Serial Input Basics - updated might get you on the right track.

PS
If you post your full code, people might be able to suggest improvements.

Well, I got all the stuff with the chars out of the way, it does compile, but there are some changes that I could not make. I was unsuccessful in finding something understandable on the internet to tell me how to get multiple numbers into a char array, do I use an int instead, like "int sernum = Serial.read" and repeat it until all parts of the number are stored, or do I just keep the string? I also spent a while removing the Streaming library and it freed up 42 bytes of flash and using regular Serial.print added an extra few bytes to the SRAM usage so it's fair to say that Streaming and regular Serial utilities use the same amount of resources.

Edit: It's fine, It took a while to remove Streaming so I won't be putting it back unless I don't feel so lazy but using regular doesn't make a big enough difference in storage to worry me yet.

The full code is too big for this post but it is attached.

Uno_DAP.ino (12.3 KB)

Simple change

const PROGMEM char REVISION[] = "B4";

Saves 4 data bytes and 82 code bytes

Instead of this:-const PROGMEM boolean DEBUG_MODE = true;You could use a 'define':-#define DEBUG_MODEThen:-

#ifndef DEBUG_MODE
    delay(1500);
    String resetEEPROM = Serial.readString();
    resetEEPROM.toUpperCase();
    if (resetEEPROM == "R")
    {
        EEPROM.update(EPRM_INTVAL_H, 0);
        EEPROM.update(EPRM_INTVAL_L, 0);
        EEPROM.update(EPRM_BEEP, 0);
        EEPROM.update(EPRM_USESER, 0);
        Serial.print(F("EEPROM settings have been cleared.\n"));
    }
#endif

and:-

#ifdef DEBUG_MODE
    Serial.print(freeRam());
    Serial.print(F(" bytes available after setup.\n"));
#endif

and so on with the other instances.

I was unsuccessful in finding something understandable on the internet to tell me how to get multiple numbers into a char array, do I use an int instead, like "int sernum = Serial.read" and repeat it until all parts of the number are stored, or do I just keep the string?

The thread that sterretje linked, Serial Input Basics - updated, will show you everything you need to know about building a C string from serial input. In effect, in a loop you read each 'char', copy it to an indexed position in a char array, then increment the index before reading the next char. That thread will make it clear.
Don't use an 'int' variable as you describe - that will just overwrite the previous value on each loop.
Remember, they're 'chars' being read from serial, not 'numbers', (integers). Only one byte is sent and received at a time.
Get rid of those horrible Strings. :smiley:
It'll take more typing, and a little more learning, but use less SRAM and flash memory. (As sterretje just illustrated.)

In 'setConfigMethod()', I just noticed this:-

if (useSer == 'Y' || useSer == 'n')
{
    stringValid == true;
    return true;
}

Was it supposed to be this?:-

if (useSer == 'Y' || useSer == 'y')
{
    stringValid == true;
    return true;
}

Ok, well I will be away from home for a while today but when I get back I will read through the Serial Input Basics thread and implement it to get rid of the last strings.

Yeah, having a 'y' and an 'n' in the same if statement will probably cause an undesired effect :slight_smile: .

Mike44449:
Yeah, having a 'y' and an 'n' in the same if statement will probably cause an undesired effect :slight_smile: .

I thought it was just a typo. I thought right. :slight_smile:

const PROGMEM boolean DEBUG_MODE = true;

For basic types declared as const, the compiler will put them in PROGMEM anyway. You don't need to add that keyword. In fact, it's good enough to put it inline in the machine code, so it never actually occupies a memory location. It's just part of the code. This also makes it run faster, because it doesn't have to open up a separate storage location to find the value.