// Problems with strings on the Arduino:
// 1) Mysterious crashes after the program has run for many days. Possibly due to memory fragmentation
// 2) Precious ram used to do very simple things like print "hello world".
// 3) If output to the debug screen with Serial.println and then later port a program to a different display, need to change lots of code
// To solve these problems:
// 1) Remove all references to the String class.
// 2) Move all fixed strings (eg debug messages, error messages) from ram to flash.
// 3) Third, move all serial output to just one line in one function, so this can be changed for different displays.
// and 4), learn do code using old fashioned C string manipulation, which is actually quite fun, and can do most anything the String class can do
// First step - compile an empty program to an arduino, and take a look at the message at the bottom of the screen 'Global variables use xx bytes'.
// then aim is to keep this number low, and for any temporary string space to only use the stack, so it is recovered fully when a function ends
// Experiments inspired by https://hackingmajenkoblog.wordpress.com/2016/02/04/the-evils-of-arduino-strings/
// By way of example, this is the syntax for printing Hello World which ends up being two lines
//
// const static char s1[] PROGMEM = "Hello World"; // store the text in flash
// printStringln(getString(s1)); // move to temporary string and then print
//
// and then the prototype code for most of the functions, move a string from inputString to outputString
//
// const static char s1[] PROGMEM = "Parse Test"; // move fixed text into temporary array
// strcpy_P(inputString1, s1); // copy to one of the temp strings, must use strcpy_P not strcpy
// char* outputString = parseString(inputString1); // move to outputString, note the char* before outputString. parseString function is strcpy()
// printStringln(outputString); // print out
//
// So now can avoid using the String class completely - uses far less ram, no mysterious bugs due to memory fragmentation
// also minimise use of Serial. class, only use it in one place to output one character. Makes it easy to port code to different displays.
// looking at https://www.arduino.cc/en/Reference/PROGMEM then const char messageOne[] PROGMEM = "Here is a big hello string"
// must be at the start, however, adding the 'static' means it can be in a function, and still not use any ram
// putting it in a function is more convenient as can see what the message is rather than having all the messages in one place.
// see the progmem link above, not using this, but could be useful with setting up string arrays for lots of messages, then store in progmem
// removed declaring constant strings at the start of the program, easier to have the actual message local in a function so can read what it is
// start with a completely blank program - uses 9 bytes (or 2048 on a Uno). Add Serial.begin and it uses 182 bytes
// add Serial.println(""); and it uses 200 bytes. Change to Serial.println("0123456789"); and this uses 210 bytes, and an extra byte for every debug message like this
// change to Serial.println(F("0123456789")); and it is back to 200 bytes, however can't use F when creating strings that are not printed straight away
// and also can't use F for other custom print routines, eg printing to an LCD screen
// add in inputString arrays and outputstring array, and is now 422 bytes, aim to stay with this number while adding multiple further test routines
// even with just these three universal strings, the ram used is 20% of a uno. Howwever, on a uno there is 32k of flash so plenty of room for storing strings.
// https://stackoverflow.com/questions/43917979/return-the-address-of-a-global-string-variable so can return outputString
// https://en.wikibooks.org/wiki/C_Programming/Strings for more strings
// Microsoft Basic by Ken Knecht 1983, back when basic was much simpler. Copied string examples from this as can do a lot with just a few functions.
// added multiple examples in both basic syntax and c syntax.
// more things to think about, reading in a text file from an SD card, processing each text line one at a time. Or decoding a page of HTML
char inputString1[80]; // general purpose input string, 80 bytes
char inputString2[80]; // general purpose input string, 80 bytes
char outputString[80]; // general output string, 80 bytes
void setup() {
Serial.begin(9600); // start serial port
messageTestExample(); // print Hello World
parseTest(); // create a string in flash, move to inputstring, move to outputstring, print on the screen
printNumber(19); // print a number on the screen
stringCompareTest(); // create two strings, compare if equal. Also shows how debug messages are in flash, saving precious ram
integerToStringTest(); // integer to a string
stringToIntegerTest(); // string to an ingeter
longToStringTest(); // long to a string 1234
longToBinaryTest(); // long to a binary string 110101
longToHexTest(); // long to a hex string 123ABC
// old Basic functions
leftStringTest(); // get the left characters from a string
midStringTest(); // get the middle characters from a string
instrTest(); // find where a string is in another string
lenTest(); // length of a string
ascTest(); // character to a number
chrTest(); // number to a character
hexTest(); // number to a hex string
basicStrTest(); // number to a string
basicValTest(); // string to a number
basicStringAddTest(); // add two strings together
finishMessage(); // for debugging, string errors tend to corrupt code so it never gets to here, so make sure this prints
}
void loop()
{
}
//******************* Test examples *********************
void messageTestExample()
{
const static char s1[] PROGMEM = "Hello World"; // store the text in flash
printStringln(getString(s1));
}
void parseTest()
{
const static char s1[] PROGMEM = "Parse Test"; // move fixed text into temporary array
strcpy_P(inputString1, s1); // copy to one of the temp strings, must use strcpy_P not strcpy
char* outputString = parseString(inputString1); // move to outputString, note the char* before outputString
printStringln(outputString); // print out
}
void stringCompareTest() // compare two strings
{
uint8_t a; // a byte
const static char s1[] PROGMEM = "Long string of text"; // move fixed text into temporary array
strcpy_P(inputString1, s1); // copy to one of the temp strings, must use strcpy_P not strcpy
const static char s2[] PROGMEM = "Long string of text"; // move fixed text into temporary array
strcpy_P(inputString2, s2); // copy to the other input string
a = stringCompare(inputString1,inputString2); // 0 is a match
if (a == 0) {
const static char s3[] PROGMEM = "String compare - Strings match"; // store the text in flash
printStringln(getString(s3));
}else{
const static char s4[] PROGMEM = "String compare - Strings do not match"; // store the text in flash
printStringln(getString(s4));
}
}
void leftStringTest()
{
const static char s1[] PROGMEM = "Test left string routine"; // move fixed text into temporary array
strcpy_P(inputString1, s1); // copy to inputString1
char* outputString = basicLeftString(inputString1, 14); // left most characters, 1 returns one character, 0 not allowed
printStringln(outputString);
}
void midStringTest()
{
const static char s1[] PROGMEM = "Test mid string routine"; // move fixed text into temporary array
strcpy_P(inputString1, s1); // copy to inputString1
char* outputString = basicMidString(inputString1, 6, 10); // mid characters, first is 1, start at 6, and return 10 characters
printStringln(outputString);
}
void stringToIntegerTest()
{
int i;
const static char s1[] PROGMEM = "500";
strcpy_P(inputString1, s1);
i = stringToInteger(inputString1); // value is in i
i = i + 100; // do something to this number
printNumber(i);
}
void longToStringTest()
{
char* outputString = longToString(-12345678); // long to string
printStringln(outputString);
}
void longToBinaryTest()
{
char* outputString = longToBinaryString(65535);
printStringln(outputString);
}
void longToHexTest()
{
char* outputString = longToHexString(65535);
printStringln(outputString);
}
void integerToStringTest()
{
char* outputString = integerToString(1234); // convert integer to a string
printStringln(outputString);
}
void instrTest() // uses strstr, see also strchr to find a character in a string
{
int index;
const static char s1[] PROGMEM = "Find a needle in a haystack";
strcpy_P(inputString1, s1);
const static char s2[] PROGMEM = "needle";
strcpy_P(inputString2, s2);
index = basicInstr(inputString1,inputString2); // find where needle is
printNumber(index);
}
void lenTest()
{
int i;
const static char s1[] PROGMEM = "Hello World";
strcpy_P(inputString1, s1);
i = basicLen(inputString1);
printNumber(i);
}
void ascTest()
{
uint8_t n;
const static char s1[] PROGMEM = "A Hello World"; // should print 65
strcpy_P(inputString1, s1);
n = basicAsc(inputString1);
printNumber(n);
}
void chrTest()
{
char* outputString = basicChr(66); // ascii B
printStringln(outputString);
}
void hexTest()
{
char* outputString = basicHex(254);
printStringln(outputString);
}
void basicStrTest() // number to string
{
char* outputString = basicStr(120); // same as long to string
printStringln(outputString);
}
void basicStringAddTest()
{
const static char s1[] PROGMEM = "String "; // move fixed text into temporary array
strcpy_P(inputString1, s1); // copy to one of the temp strings, must use strcpy_P not strcpy
const static char s2[] PROGMEM = "Add"; // move fixed text into temporary array
strcpy_P(inputString2, s2); // copy to the other input string
char* outputString = basicStringAdd(inputString1,inputString2);
printStringln(outputString);
}
void finishMessage()
{
const static char s1[] PROGMEM = "Finished"; // print a finish message, bugs tend to corrupt the final message so end with this
printString(getString(s1));
}
//******************** String Routines ******************
char* getString(const char* str) // String replacement - move string from flash to local buffer
{
strcpy_P(outputString, (char*)str);
return outputString;
}
void printString(const char *str) // all output directed through this one routine, so can change Serial.print to whatever display is being used
{
const char *p;
p = str;
while (*p) {
Serial.print(*p); // explanation in majenko's webpage
p++;
}
}
void printStringln(const char *str) // print line with crlf
{
printString(str);
crlf(); // carriage return, line feed
}
void crlf() // carriage return and linefeed
{
const static char crlf[] PROGMEM = "\r\n"; // carriage return, then line feed, maybe not the most efficient way to do this but works
printString(getString(crlf)); // print out, using one central function for output so easier to change destination with different displays
}
void printNumber(long n)
{
char* outputString = integerToString(n); // convert to number
printStringln(outputString);
}
int stringCompare(const char *str1, const char *str2)
{
uint8_t a;
a = strcmp(str1, str2); // 0 is a match
return a;
}
char* basicLeftString(const char *str, int i)
{
strncpy(outputString, (char*)str, i); // copy to outputString
outputString[i] = '\0'; // put in new null terminator
return outputString;
}
char* basicMidString(const char *str, int stringStart, int stringLength)
{
strncpy(outputString, (char*)str + stringStart - 1, stringLength); // copy to outputString using Basic nomenclature where 1 is the first character
outputString[stringLength] = '\0'; // put in new null terminator
return outputString;
}
char* integerToString(int n) // returns outputString
{
itoa(n, outputString, 10); // itoa is for integers, 10 is for base 10 (could use 2 for binary, 16 for hex)
return outputString;
}
char* longToString(long n)
{
ltoa(n, outputString, 10); // base 10
return outputString;
}
char* longToBinaryString(long n)
{
ltoa(n, outputString, 2); // base 2 is binary
return outputString;
}
char* longToHexString(long n)
{
ltoa(n, outputString, 16); // base 16 is binary, returns lower case A-F
return outputString;
}
int stringToInteger(const char *str1)
{
int i;
i = atoi(inputString1);
return i;
}
char* parseString(const char *str1)
{
strcpy(outputString, str1); // copy string
return outputString;
}
int basicInstr(const char *haystack, const char *needle) // same as Basic instr
{
char *e;
int index;
e = strstr(haystack, needle); // get pointer to the string. See also strchr which looks for just a single character
index = (int) (e - inputString1); // find where the substring is
index = index + 1; // add one so same as Basic
return index;
}
int basicLen(const char *str1)
{
int i;
i = strlen(str1);
return i;
}
uint8_t basicAsc(const char *str1) // returns ascii value of the left most character in a string.
{
uint8_t c;
c = str1[0]; // get the value
return c;
}
char* basicChr(uint8_t c) // convert c to a string of length 1 character
{
outputString[0] = c;
outputString[1] = '\0'; // null terminator
return outputString;
}
char* basicHex(uint32_t n) // can be any sort of number, long, byte, uint8,16,32 etc
{
ltoa(n, outputString, 16); // base 16 is binary, returns lower case A-F
return outputString;
}
char* basicStr(long n)
{
ltoa(n, outputString, 10); // base 10
return outputString;
}
long basicVal(const char *str1)
{
long i;
i = atol(inputString1); // returns a long but can cast into other types
return i;
}
void basicValTest()
{
long i;
const static char s1[] PROGMEM = "4567";
strcpy_P(inputString1, s1);
i = basicVal(inputString1); // value is in i
printNumber(i);
}
char* basicStringAdd(const char *str1, const char *str2) // adds string 2 to the end of string 1
{
strcat((char*) str1,(char*) str2); // add strings together, answer in str1
strcpy(outputString, str1); // copy string to outputstring
return outputString;
}