User interfaces can take up a lot of memory. That is why, while developing the Arduino User Interface I focused on developing a way to store things in Program Memory instead. It turns out that storing -- and then using -- program memory objects can be slightly confusing. So I thought I would write a quick post about it.
Most people are familiar with the simple use of program memory for strings. Instead of writing
Serial.println("This is taking up a lot of mem");
You can write
Serial.println(F("This is taking up no mem"));
The F() stores the string in Flash (or Program Memory) instead of creating a character array in RAM and then passing it to println. This is very good, considering that the Arduino UnoArduino Uno has only 2k of SRAM and more than 32k of Program Memory!
Storing in Program Memory
But what if you want to do something more? The threading module requires an array of pointers to functions. At first glance, this also looks fairly easy.
typedef int (* funptr)(int index) // declare the funptr type
int return_0(){
return 0;
}
int return_1(){
return 1;
}
PROGMEM const funptr fptrs[] = {&return_0, &return_1, NULL} // fun_x are declared functions
The problem comes about when you have to pass the pointers variable to other functions -- there is no way to declare that it is coming from program memory!
Before I explain, perhaps it is a good idea if we remember what a pointer is: it is a number which represents a certain location in memory. So:
int x = 8; // x is storing the integer 8
int *p_x = &x; // p_x is storing the location of x
So right now our fptrs are just an array of data that is stored in Program Memory, each of them pointing to functions.
Lets say we have a function like the one below:
int call_func(int index, const TH_funptr *ptrs){
return ptrs[index](); // THIS DOESN'T WORK
}
The reason the above code doesn't work is because fptrs are in program memory! To access it we have to read out of program memory. Fortunately, there is a function to read 2 bytes of data (a word) from program memory: pgm_read_word
To solve this problem, I developed the get_pointer routine.
void *get_pointer(PGM_P s, uint8_t index, uint8_t size){
return (void *)pgm_read_word(s + index * size);
}
So now our function would look like
// not very useful, but at least it works
int call_func(int index, const TH_funptr *ptrs){
return get_pointer((PGM_P)fptrs, index, 2)(); // This works. Note that you have to cast fptrs as type PGM_P
}
More Complex Storage in Program Memory
The Arduino page has an excellent tutorial on how to store strings in program memory, and that is what is fundamentally used in the usertools library. Basically, you must construct each pointer, and then store an array of pointers (tons of fun).
This also applies to more complex data types. I would have THOUGHT that you could store basic types in an array, but it turns out you can't. For instance, variables are composed of a pointer to the variable and an 8 bit unsigned integer representing it's size. To store a variable, you have to store a pointer to each element in program memory, and then store the array of those pointers into program memory as well (using way more program memory than I would like, but no RAM).
That is the reason to expose variables you have to do:
UI_V(v1, myvar1); // Variables have to be declared specially. Declare variable names first with UI_V
UI_V(v2, myvar2); // Variables have to be declared specially. Declare variable names first with UI_V
expose_variables(UI_VA(v1), UI_VA(v2)); // Then wrap the variable names in UI_VA. Alot of things have to be done to take up zero RAM!
Now you know what goes into taking up zero RAM.