Hi there,
following my previous topic (Max number of Strings in a char array - #3 by Elso - Project Guidance - Arduino Forum) regarding the maximum number of strings I can place in an array, I am trying to use the PROGMEM to store the string arrays I have in the Flash memory; the main problem is that I don't k ow if this is the right procedure to follow in my case.
What I am trying to achieve is:
to have two arrays containing strings, about 300 strings each, each containing between 3 to 8 letters. Each time the sketch run, two strings are randomly selected from the two arrays (one pulled from each one of them); then they are concatenated and visualised on an OLED display, using the Adafruit library (GitHub - ladyada/Adafruit_CharacterOLED: Adafruit 'Liquid Crystal' compatible libary for Character OLEDs).
As those are too many strings and I already had some memory issues with my script (I am using an Arduino Mega 2560) I would like to move those strings to the flash memory.
I went to the PROGMEM tutorial page (PROGMEM - Arduino Reference) but it seems to me that I need to copy the string table / array to the RAM buffer.
My questions are:
do I need to copy the entire array or simply the string randomly selected?
do I need to clear that buffer once I don't need the string anymore to make space for another one?
Many thanks for your help, I am attaching a shorter version of my code
// include the library code:
#include <Adafruit_CharacterOLED.h>
// initialize the library with the numbers of the interface pins
Adafruit_CharacterOLED lcd(6, 7, 8, 9, 10, 11, 12);
long randNames; // random word taken from the group male names
long randLastnames; // random last name
char finalWord[40]; // enough room for all strings together
int myLenght; // lenght of the final word
char* maleNames[]={"FRANK", "JOHN", "MARK", "LORIS", "MICHAEL", "....,
};
char* maleLastnames[]={"STEIN", "OLLIS", "GRAND", "MERLIN", "KRANZ", "...
};
char* mySpace = " ";
void setup(){
Serial.begin(9600);
}
void loop(){
randNames = random (320); //picks a random name
randLastnames = random (320); //picks a random last name
finalWord[0] = 0; // start with a null string:
strcat(finalWord, maleLastnames[randLastnames]); // add first string
strcat(finalWord, mySpace); // add second string, the empty space string
strcat (finalWord, maleNames[randNames]); //add third string
myLenght = strlen(finalWord);
if (myLenght < 16) {// if the final word doesn't exceed 16 spaces
lcd.setCursor(0, 0); //positions the cursor on the first row
lcd.print(finalWord); //prints the word picked from the group
delay(10000); // wait a bit for ppl to read
lcd.clear(); // clears the display
delay(5000); // dark/empty display
}
}
You forgot a link to that OLED display.
Is this the one with an SD card ? If so, you could also store the data in a file (or files) on the SD card.
The Adafruit library doesn't use the standard 'stream' class which is used by many Arduino functions.
That stream class can also handle strings in Flash.
The Adafruit library requires the data to be in RAM.
Declaring string arrays in Flash is not so easy.
You could concatenate the strings with a seperation character.
You could make them all 8 characters long and use it as one long string.
The normal way to do this, is to declare each name with each an own string variable in Flash. After that declare an array of pointers (in Flash) to those string names (in Flash).
As far as I know, no easy way exists for this.
Thanks for the help,
the OLED display doesn't have an SD card unfortunately, it is a Winstar OLED 16x2 like this model:
pins are grouped differently on my model, but that is a detail.
Do I need to make each string 8 characters long?
I am going through the tutorial you linked, thanks for the resource!
I will let you know how it goes!
To make each name 8 characters long, it it possible to declare it as one long string.
The example is exactly the same as this (but written in a different way):
prog_char listOne[] PROGMEM = "AA BBBBBBBBCCC ";
It would be one long string, only to avoid declaring both the strings and an array of strings seperately.
Okay...
I still seem to have a problem understanding the strings being 8 characters long, sorry, just being thick probably XD
At the moment, looking at the tutorial I have created two lists and a buffer into which I copy one string at time that is being concatenated into a char, I have rewritten the previous code like this:
// include the library code:
#include <Adafruit_CharacterOLED.h>
#include <avr/pgmspace.h>
// initialize the library with the numbers of the interface pins
Adafruit_CharacterOLED lcd(6, 7, 8, 9, 10, 11, 12);
prog_char string_0[] PROGMEM = "MARK"; // "String 0" etc are strings to store - change to suit.
prog_char string_1[] PROGMEM = "FRANCIS";
prog_char string_2[] PROGMEM = "JOHN";
prog_char string_3[] PROGMEM = "ANDREW";
prog_char string_4[] PROGMEM = "ROBERT";
prog_char string_5[] PROGMEM = "SEAN";
prog_char string_6[] PROGMEM = "HAYES"; // "String 0" etc are strings to store - change to suit.
prog_char string_7[] PROGMEM = "LUDWIG";
prog_char string_8[] PROGMEM = "HARRISON";
prog_char string_9[] PROGMEM = "GRAND";
prog_char string_10[] PROGMEM = "SALTER";
prog_char string_11[] PROGMEM = "BOND";
// Then set up a table to refer to your strings.
PROGMEM const char *string_table1[] = // change "string_table" name to suit
{
string_0,
string_1,
string_2,
string_3,
string_4,
string_5 };
PROGMEM const char *string_table2[] = // change "string_table" name to suit
{
string_6,
string_7,
string_8,
string_9,
string_10,
string_11};
char buffer[30]; // make sure this is large enough for the largest string it must hold
long randNames; // random word taken from the group names
long randLastnames; // random last name
char finalWord[40]; // enough room for all strings together
int myLenght; // lenght of the final word
//int myCentre; // coordinates to place the final word in the centre
char* mySpace = " ";
void setup(){
Serial.begin(9600);
}
void loop(){
randNames = random (320); //picks a synonym
randLastnames = random (320); //picks an adjective
finalWord[0] = 0; // start with a null string:
strcpy_P(buffer, (char*)pgm_read_word(&(string_table1[randNames])));
strcat (finalWord, buffer); //add third string
strcat(finalWord, mySpace); // add second string, the empty space string
strcpy_P(buffer, (char*)pgm_read_word(&(string_table2[randLastnames])));
strcat(finalWord, buffer); // add first string
myLenght = strlen(finalWord);
Serial.println(finalWord);
}
This should give me the final word as a result of Names + Lastnames, one at time, I guess.
Instead I cannot see anything printed on the serial monitor, for the moment I am not trying to write to the OLED.
Your code is running with a few changes.
I hope you don't mind that I place the code without checking myself what changes are actually needed.
You don't need to include the pgmspace.h and I use integers for random. The random needs to be 0...5, so random(6) will do that.
prog_char string_0[] PROGMEM = "MARK"; // "String 0" etc are strings to store - change to suit.
prog_char string_1[] PROGMEM = "FRANCIS";
prog_char string_2[] PROGMEM = "JOHN";
prog_char string_3[] PROGMEM = "ANDREW";
prog_char string_4[] PROGMEM = "ROBERT";
prog_char string_5[] PROGMEM = "SEAN";
prog_char string_6[] PROGMEM = "HAYES"; // "String 0" etc are strings to store - change to suit.
prog_char string_7[] PROGMEM = "LUDWIG";
prog_char string_8[] PROGMEM = "HARRISON";
prog_char string_9[] PROGMEM = "GRAND";
prog_char string_10[] PROGMEM = "SALTER";
prog_char string_11[] PROGMEM = "BOND";
// Then set up a table to refer to your strings.
PROGMEM const char *string_table1[] = // change "string_table" name to suit
{
string_0,
string_1,
string_2,
string_3,
string_4,
string_5 };
PROGMEM const char *string_table2[] = // change "string_table" name to suit
{
string_6,
string_7,
string_8,
string_9,
string_10,
string_11};
char buffer[30]; // make sure this is large enough for the largest string it must hold
int randNames; // random word taken from the group names
int randLastnames; // random last name
char finalWord[40]; // enough room for all strings together
int myLenght; // lenght of the final word
//int myCentre; // coordinates to place the final word in the centre
void setup(){
Serial.begin(9600);
}
void loop(){
randNames = random (6); //picks a synonym
randLastnames = random (6); //picks an adjective
finalWord[0] = '\0'; // start with a null string:
strcpy_P(buffer, (char*)pgm_read_word(&(string_table1[randNames])));
strcat (finalWord, buffer); //add third string
strcat(finalWord, " "); // add second string, the empty space string
strcpy_P(buffer, (char*)pgm_read_word(&(string_table2[randLastnames])));
strcat(finalWord, buffer); // add first string
myLenght = strlen(finalWord);
Serial.println(finalWord);
delay(1000);
}
#include <avr/io.h>
#include <avr/pgmspace.h>
const char PROGMEM namesTable[] = { // all this text stored in flash
"MARK\0FRANCIS\0JOHN\0ANDREW\0ROBERT\0SEAN\0HAYES\0LUDWIG\0HARRISON\0GRAND\0SALTER\0BOND\0"
};
PGM_P nT; // will point into namesTable
#define FIRSTNAMES 6
#define LASTNAMES 6
PGM_P firstName[ FIRSTNAMES ];
PGM_P lastName[ LASTNAMES ];
void printProgstring( PGM_P FM )
{
byte fb;
do
{
fb = pgm_read_byte( FM++ );
if ( fb ) Serial.print( fb );
}
while ( fb );
}
void setup(void)
{
Serial.begin( 9600 );
nT = namesTable;
firstName[ 0 ] = nT;
byte i; // only counting to 6 so 1 byte is enough
for ( i = 1; i < FIRSTNAMES; i++ )
{
while ( pgm_read_byte( nT++ ));
firstName[ i ] = nT++;
}
for ( i = 0; i < LASTNAMES; i++ )
{
while ( pgm_read_byte( nT++ )); // find the \0 at the end of name[ i-1 ]
lastName[ i ] = nT++;
}
for ( i = 0; i < FIRSTNAMES; i++ )
{
printProgstring( firstName[ i ] );
Serial.println( );
}
Serial.println( );
for ( i = 0; i < LASTNAMES; i++ )
{
printProgstring( lastName[ i ] );
Serial.println( );
}
}
void loop(void)
{
}
Elso,
I wrote before about "You could concatenate the strings with a seperation character".
The example by GoForSmoke uses a null '\0' as seperation character.
GoForSmoke,
I would make two concatenated strings. One for first names and one for last names.
It would make the code easier to read.
And perhaps the data could be written like this:
const char PROGMEM namesTable[] = { // all this text stored in flash
"MARK\0" \
"FRANCIS\0" \
"JOHN\0" \
"ANDREW\0" \
............ and so on
};
I delimited with \0 so that C string functions could easily use the parts. But my 1-byte-buffer print routine could look for anything. Consider though, using \0 means it can print commas.
Yeah, could go with 2 at storage but since it is the pointers that are used, I went with 1. It's only an example anyway. XD
Thank you very much indeed guys for sharing your knowledge and opinion.
I can see I am simply scratching the surface of the Progmem and strings, I will solder together an new mini OLED tonight and will see if everything works correctly.
So far I have been only compiling to check that everything works, strange enough Arduino will always give me the same randomly calculated names and last names, is that normal? I mean it puts together names and last names once, then if I unplug it and plug it back either some minutes later or the day after, it gives me back names and last names with the same order it did before, as if it can only ramdomly calculate names and last names once.
The random function starts with a seed and will produce the same numbers every time.
To start with a different seed, use : http://arduino.cc/en/Reference/RandomSeed
You could read noise from an analog input, or the light from an ldr, or the time from a RTC, and use that for the seed.
Hi,
I came to this thread because of the same problem with OP, but my project is for ESP8266.
To make Krodal's code above to work on ESP8266, small change should be made that is
change
pgm_read_word
to
pgm_read_dword
Notice the change from using 'word' to 'dword'. I got this solution from ESP8266 forum here.
Otherwise the ESP will always reset.
Below is full revised Krodal's code that works on ESP8266 (tested on NodeMcu v3)
prog_char string_0[] PROGMEM = "MARK"; // "String 0" etc are strings to store - change to suit.
prog_char string_1[] PROGMEM = "FRANCIS";
prog_char string_2[] PROGMEM = "JOHN";
prog_char string_3[] PROGMEM = "ANDREW";
prog_char string_4[] PROGMEM = "ROBERT";
prog_char string_5[] PROGMEM = "SEAN";
prog_char string_6[] PROGMEM = "HAYES"; // "String 0" etc are strings to store - change to suit.
prog_char string_7[] PROGMEM = "LUDWIG";
prog_char string_8[] PROGMEM = "HARRISON";
prog_char string_9[] PROGMEM = "GRAND";
prog_char string_10[] PROGMEM = "SALTER";
prog_char string_11[] PROGMEM = "BOND";
// Then set up a table to refer to your strings.
PROGMEM const char *string_table1[] = // change "string_table" name to suit
{
string_0,
string_1,
string_2,
string_3,
string_4,
string_5 };
PROGMEM const char *string_table2[] = // change "string_table" name to suit
{
string_6,
string_7,
string_8,
string_9,
string_10,
string_11};
char buffer[30]; // make sure this is large enough for the largest string it must hold
int randNames; // random word taken from the group names
int randLastnames; // random last name
char finalWord[40]; // enough room for all strings together
int myLenght; // lenght of the final word
//int myCentre; // coordinates to place the final word in the centre
void setup(){
Serial.begin(9600);
}
void loop(){
randNames = random (6); //picks a synonym
randLastnames = random (6); //picks an adjective
finalWord[0] = '\0'; // start with a null string:
strcpy_P(buffer, (char*)pgm_read_dword(&(string_table1[randNames])));
strcat (finalWord, buffer); //add third string
strcat(finalWord, " "); // add second string, the empty space string
strcpy_P(buffer, (char*)pgm_read_dword(&(string_table2[randLastnames])));
strcat(finalWord, buffer); // add first string
myLenght = strlen(finalWord);
Serial.println(finalWord);
delay(1000);
}