Running Out of Memory? Switch on String?

Status
Not open for further replies.

PBear

Member
I'm not a programmer but I made a joke keyboard that switches out certain typed words. For example if they type "monkey" it replaces it with "andrew". Right now I am using a string to capture the last 20 characters typed, then I have a big else if to check for certain keywords using endsWith("keyword"). This approach works great for upto 40 keywords then it stops working, I assume its the memory getting filled from the giant else if (using 85% of storage). I read switches are more efficient but it only uses single chars/integers. Is there a way to switch on a string or have another idea to solve this? I'm using a teensy 2.0, thanks.
 
Here is small part of the else if so you get the idea, I call files so I can change the words without messing with uploading another sketch, I can just pop out the SD card and change the words, but the keywords stay the same.

This is working if I have less than roughly 40 keywords

Code:
//monkey
  if (lasttwenletters.endsWith("monkey")) {
    TypeOutFile("MONKEY.TXT"); //types out the file to keyboard
    lasttenletters[19]='*'; //change character
  }
  //there
  else if (lasttwenletters.endsWith("there")) {
    TypeOutFile("THERE.TXT");
    lasttenletters[19]='*'; //change character
  }
  //ask
  else if (lasttwenletters.endsWith("ask")) {
    TypeOutFile("ASK.TXT");
    lasttenletters[19]='*'; //change character
  }
I'm trying this: but having problem with switching on a string not an interger
Code:
switch (lasttwenletters.endsWith) {
    case "MONKEY":
      TypeOutFile("MONKEY.TXT");
      lasttwenletters[19]='*';
      break;
    case "THERE":
      TypeOutFile("THERE.TXT");
      lasttwenletters[19]='*';
      break;
    case "ASK":
      TypeOutFile("ASK.TXT");
      lasttwenletters[19]='*';
      break;
    }
 
Last edited:
Actually I wasn't thinking. the switch value has to be an enum, int or char and not a string, so it is a bit more complicated. If you post/PM the complete sketch I will take a look at it.
 
Why don't you just have one file with delimited entries on seperate lines as follows.

e.g.

monkey, Andrew
there, <there replacement>
ask, <ask replacement>
etc

I am guessing you could open the file and read it line by line in the time available. Read the line and parse it into two variables, I guess you see where I am going ???

Hopefully you can do this fast enough allowing you to reclaim all that memory whilst only having to manage a single file. :)
 
Arduino uses 2 types of strings, ordinary C code strings, and String objects. Both use memory in not-so-obvious ways.

C strings are allocated in RAM by default. Sometimes that's important, like when passing a string to the SD library, since it will access RAM to read your string. But many places accept a special type of string allocated only in flash, which saves RAM. When F() isn't supported, you'll get compiler errors. They may be very cryptic... just read any error that results from F() as "this function doesn't support F()". To try this, just put F() around your string. For example:

Code:
  Keyboard.print(F("this string does not consume precious RAM"));

Ordinary C strings are statically allocated, which means you'll see the memory they consume reported in the memory estimate that appears every time you compile. Teensy 2.0 has only 2560 bytes of RAM.... it goes quickly once you start manipulating text and using libraries which need memory. Placing F() around every string constant you can will help a lot.


String objects are completely different. They are dynamically allocated, so you will not see their usage in the memory estimate. In theory, they use only the memory needed for whatever text they're storing. In practice, things don't always work out so perfectly. If a String needs to increase its storage, and something else (likely another String) is in memory right after it, the memory allocator will need to allocate a new chunk of memory, leaving an unused hole where the String used to be located. This is called memory fragmentation.... in really bad cases, a lot of the memory ends up being little unusable holes too small to be reused.

The main thing you can do about fragmentation from String is using String's reserve function. For example, something like this might be used in setup() before you start storing data:

Code:
  lasttwenletters.reserve(24);

That will allocate space for up to 24 characters. Then as you add and remove characters, as long as you don't store more than 24, that String will not need to allocate new space. If you are having fragmentation problems, even using reserve() on just the important Strings can really help. Usually strings that retain data for a long time, or strings that grow in length as you process text, are the best candidates for reserve(). Strings that store data on a very temporary basis, especially if you're not growing the length of the string as you go, usually aren't helped by reserve().


Of course, it's also quite possible to simply not have enough RAM. F() and reserve() are the easy steps to try. Restructuring your program might help, if you know you're storing data needlessly. But with all optimization work, it gets increasingly more difficult and usually you get diminishing returns as you go. When that happens, upgrading to more memory is usually the most time-effective solution. A Teensy 3.0 has about 14K RAM usable, which is a lot more than the 2.5K you're working with now.
 
What you need is a data structure to have the input text and output text, so that you don't have to duplicate the code for each keyword. On teensy 2.0/2.0++ as Paul said, memory is limited and you may need to use F/PROGMEM to put the strings into the read-only address space where the code is located. Unfortunately, after being a programmer for 34 years, such concepts are natural to me, but I don't know of a simple way to get non-programmers over the hump of how to think about data structures without going through a full fledged course on programming (there is no royal road to programming).

Here are two links to document F/PROGMEM:

I would strongly recommend that you not use anything that uses dynamic allocation (i.e. the String class), unless you are real disciplined to use it carefully and release things as soon as possible.

So, something that does what you want might be (note, I verified it compiled, but I haven't run it):

Code:
#include <avr/pgmspace.h>
#include <string.h>
#include <stddef.h>

// List of text to be searched for
char search_1[] PROGMEM = "monkey";
char search_2[] PROGMEM = "there";
char search_3[] PROGMEM = "ask";

PGM_P search_table[] PROGMEM = {
  search_1,
  search_2,
  search_3,
};

// List of text to be replaced, must be in the same order as search_...
char replace_1[] PROGMEM = "ape";
char replace_2[] PROGMEM = "here";
char replace_3[] PROGMEM = "tell";

PGM_P replace_table[] PROGMEM = {
  replace_1,
  replace_2,
  replace_3,
};

// Buffer of the last 20 characters
const int num_last_chars = 20;
char last_chars[num_last_chars+1];		// include extra byte for null

void setup (void)
{
  size_t i;

  // Initialize the last_chars array
  for (i = 0; i < num_last_chars; i++)
    {
      last_chars[i] = ' ';
    }

  Serial.begin (9600);
}

void loop (void)
{
  size_t i, len;
  char buffer[num_last_chars+1];

  if (Serial.available ())
    {
      // push characters down, note use memmove and not memcpy because
      // this is an overlapping move
      memmove ((void *)&last_chars[0], (void *)&last_chars[1], num_last_chars-1);
      last_chars[num_last_chars-1] = Serial.read ();

      for (i = 0; i < sizeof (search_table) / sizeof (search_table[0]); i++)
	{
	  strcpy_P (buffer, (PGM_P)pgm_read_word (&(search_table[i])));
	  len = strlen (buffer);
	  if (memcmp ((void *)buffer, (void *)&last_chars[num_last_chars-len], len) == 0)
	    {
	      strcpy_P (buffer, (PGM_P)pgm_read_word (&(replace_table[i])));
	      Serial.println (buffer);
	      last_chars[num_last_chars-1] = '*';	// make sure we don't match again
	      break;					// no need to do rest of loop
	    }
	}
    }
}

:cool:

As a side note, if the Arduino community ever decides to go with more modern compilers, such as 4.7.2 that Paul uses for the Teensy 3.0 side of things, I did a lot of the machine independent infrastructure within GCC to make split address spaces a lot cleaner in the 4.5 time frame. I wrote it originally for the Cell microprocessor, but the AVR maintainer has modified the AVR gcc to use it for PROGMEM. So even if the cell processor is fading away, some of that code is useful for other processors.
 
Last edited:
Thanks for the suggestions. I stopped using String and hashed (djb2) the char string for the switch. Seems to be working and made the code a lot smaller. I know there are better ways, but this seems to be working for 60+ words. I look forward to learning this whole programming thing. Thanks again.
 
Nicely done! It would be great if you can/will share your code. A lot of people are struggling with strings and memory - especially then ones who are using an ardruino and not a teensy due to a memory leak ;-)
 
Status
Not open for further replies.
Back
Top