I have an application that accepts text commands from Serial. I declared a base class (CmndBase) for each active command. Then I derive a series of classes from CmndBase for performing the actions requested, based on a command keyword.
First try I was very loose with the allowable keyword string lengths (81 characters), and the application bombed after adding more than 6 commands because each command object must store its own keyword :~
I cut the keyword string lengths (7), and was successful in implementing the 10 commands I needed. But I don't know how many more commands I could add to the list as people use the application and find new things for it to do.
I believe that the issue I ran into was the 2K limit on RAM, right?
Aside from trial and error, is there a way to determine how close I am to maxing out the runtime RAM?
Is there a better way to write my code (see below) to avoid this 2K limitation?
Here is what my base class declaration looks like:
class CmndBase {
public:
CmndBase( const char* cname ); // constructor with command name
CmndBase* getNext(){ return next; } // next object in linked list
void setNext( CmndBase* link ){ next = link; } // sets the link to next
// executes the command, given the parsed command line
virtual boolean doCommand( CmndParser* pars ); // override this always
char* getName(){ return keyword; }
protected:
char keyword[7]; // string that identifies the command
CmndBase* next; // pointer to next command in the linked list
};
And here is a typical derived class:
class unitsCmnd : public CmndBase {
public:
unitsCmnd();
virtual boolean doCommand( CmndParser* pars );
};
And here is a typical constructor for a derived class
Aside from trial and error, is there a way to determine how close I am to maxing out the runtime RAM?
No. The key, in your statement, is runtime. The compiler could tell you how much RAM is being used at a given point in time (before init() and setup() are run, for instance), but constructors use up RAM when executed, as do local variables in a function or block statement, as well as the stack that is used to transfer values to/from functions when a function is called.
Determining, at compile time, what order you will end up calling functions in, is impossible. So the stack size can not be determined at compile time. Therefore the total RAM being used can not be determined at compile time.
There is a function called FreeMemory that tries to allocate and then free successively larger blocks of memory (in SRAM), until malloc fails, which can give you a rough idea, at run time, how much memory is left.
if the text commands are defined at command time, put the classes into the flash memory (PROGMEM) and not into RAM. Then your limit are 32kB instead of 2kB.
If the commands strings can be defined, consider putting the into a flat character array instead of storing 7 characters for each class. This way, the length of the commands just take up the minimal amount of space and you just have to worry about the total size not exceeding the maximum.
If the command strings a static, consider making a fixed size string table . If you have a lot of keywords, go for a trie-data structure, but with that little memory that's probably overdoing it.
Also because of the small number of commands, the double linked list is probably wasteful. This two pointer take up 4 bytes in each class. 2 bytes are used by the virtual function pointer for the payload. You might have one more pointer overhead for the class, but I can't check at the moment. Lets assume not. If now you make a mixed command table where you have always command string - function pointer pairs, one after the other, you can reduce the necessary amount of data needed per command to command string-length + 3 bytes (2 for the function pointer, 1 for either \0 to terminate the string or the length of the string). To access the correct function from a command, just do a sequential compare of the strings and skip 2 bytes after each end of string if the string doesn't match. That's not very efficient on large scale data, but for a buffer of 1kB or less, it really doesn't matter. The maximum number of commands is then limited only by the allocated buffer size and you don't really care of people like few commands with long names or many with short. The whole thing can be encapsulated in a class with a function to execute by name and one to register a new keyword to keep the gritty details of storage away form the developer.
On the small amount of data processed on the Arduino, the classic data structures often aren't worth the hassles and bring more drawbacks than advantages compared to very stupid but light algorithms. That's the joy of embedded programming.
The keywords are all known at compile time, so I am looking closely at your suggestion to create a big string table for all of the keywords, perhaps in PROGMEM. Then each command instance would only have to store a pointer into the table for its keyword, rather than the keyword text itself. I think this can work.
But now that I have changed the code to limit keywords to use only 7 characters, storing a pointer instead of the text only saves me 5 bytes of RAM per command. So the decision to be made is whether the extra programming overhead is worth the luxury of longer keywords.
(The list is only singly linked, BTW, but your point is still valid that the list pointer(s) use up RAM as well).