Alternative to String..

I am trying to read a row of record from SD card and display on LCD. I am using ( the bad ) String. It works well and i am not in any tight corner for Flash space as the whole code is just about 36K on a Mega 2560. But then was wanting to find out a more elegant and optimum way to do this as what i have done seems pretty convoluted ! :

The first part of code snippet : Declare String variables and parse the data from SD card :

// Declare Globals 

String SlNo;
String Priority; 
String Freq; 
String Route;
String RouteNo;
String RFID;
String EquipID;
String EquDesc;
String GrType;
String GrQty;

...
...
...
// FUNCTION TO RETRIEVE SPECIFIC RECORD AND LOAD FEILDS INTO GLOBAL VARIABLES

void parseCharString (int recPosition ) {

  char letter;
  String readString;

  int deLim1, deLim2, deLim3, deLim4, deLim5, deLim6, deLim7, deLim8, deLim9, deLim10, deLim11;

  File dataFile = SD.open("ACTUAL.CSV", FILE_READ);         // Open the file
  dataFile.seek(recPosition);                               // Go to the required row
  if (dataFile) {
    while ( dataFile.available()) {
      letter = dataFile.read();
      if (letter == '>') {
        dataFile.close();

        deLim1 = readString.indexOf('<');
        deLim2 = readString.indexOf(',');
        SlNo = readString.substring( deLim1 + 1, deLim2);
        deLim3 = readString.indexOf(',', deLim2 + 1);
        Priority = readString.substring(deLim2 + 1, deLim3);
        deLim4 = readString.indexOf(',', deLim3 + 1);
        Freq = readString.substring(deLim3 + 1, deLim4);
        deLim5 = readString.indexOf(',', deLim4 + 1);
        Route = readString.substring(deLim4 + 1, deLim5);
        deLim6 = readString.indexOf(',', deLim5 + 1);
        RouteNo = readString.substring(deLim5 + 1, deLim6);
        deLim7 = readString.indexOf(',', deLim6 + 1);
        RFID = readString.substring(deLim6 + 1, deLim7);
        deLim8 = readString.indexOf(',', deLim7 + 1);
        EquipID = readString.substring(deLim7 + 1, deLim8);
        deLim9 = readString.indexOf(',', deLim8 + 1);
        EquDesc = readString.substring(deLim8 + 1, deLim9);
        deLim10 = readString.indexOf(',', deLim9 + 1);
        GrType = readString.substring(deLim9 + 1, deLim10);
        deLim11 = readString.indexOf('>', deLim10 + 1);
        GrQty = readString.substring(deLim10 + 1, deLim11);

        readString = "";
        break;
      }
      else {
        readString += letter;
      }
    }
  }
  else {
    lcd.clear();
    lcd.print(F("ACTUAL.CSV"));
    lcd.setCursor(0, 1);
    lcd.print(F("  SD File Error2 !  "));
    lcd.setCursor(0, 2);
    lcd.print(F(" Reset and Verify.."));
    while (1);                                                        // Hold the message...and wait for reset.
  }
}

...
...
...

Now for code to display the parsed data on to a LCD

// FUNCTION TO DISPLAY THE DETAILS AND AWAIT USER RESPONSE...

void awaitScanDisp() {

  lcd.clear();

  char buf[21];
  int len = sizeof(buf);       // Directly displaying String variable has issues..

  Route.toCharArray( buf, len);
  lcd.setCursor(0, 0);         // Part of first line
  lcd.print(buf);

  EquipID.toCharArray( buf, len);
  lcd.setCursor(12, 0);        // Remaining part of first line
  lcd.print(buf);

  EquDesc.toCharArray( buf, len);
  lcd.setCursor(0, 1);         // Second line
  lcd.print(buf);

  GrType.toCharArray( buf, len);
  lcd.setCursor(0, 2);         // Third line
  lcd.print(buf);

  lcd.setCursor(0, 3);         // Fourth line. ( Third line left blank )
  lcd.print(F( "SCAN RFID! ToGo:"));

  getNoOfRecords();
  lcd.setCursor(18, 3);
  lcd.print( numToGrease);

}

Mogaraghu:
wanting to find out a more elegant and optimum way to do this

The first obvious thing to do to improve this would be to lose the String class and do thing with char arrays.

String objects abuse RAM, not flash. They are by and for programmers that never learned C code as if it were Basic or Pascal or some other language, to let them write myString = stringOne + stringTwo because string.h functions look too hard.

C strings are char arrays containing ASCII text values with a zero char at the end serving as terminator.

You set the length and make sure you never exceed it.

With String you add char after char and it just gets bigger except no, the String object makes a copy of itself with the new char added and deletes the old one leaving a hole in your heap. Next add needs a bigger space than the hold so it makes a copy with the add in the next space big enough and deletes the old one. Next add fits in the hole and the cycle begins again.
Nowhere in all that are the CPU cycles to make the copies, delete the old and search the heap for space free. They're gone.

With C strings you can access the array just like any array. Add a char? Fine if there's room, add it to the array and make sure there's a zero at the end. If your code will never exceed the array length -1 (for the terminating zero) then you need never check but otherwise when you don't know how much data will come in you need to do bounds checking, not a big deal.

There's a whole slew of string.h functions with what look like cryptic names but there's a pattern and you will likely never need to use half of the set.

Mostly I just operate on the array and don't bother with string.h functions. I'd parse the file data a char at a time as read and only save what I have to but that's me, I've done a lot of that in the past.

Another point, it's more efficient to "walk" an index through the string, parsing as you go, rather than specifying fixed offsets each time. What you're doing is like going shopping, buying bananas, going home, going back to buy a tomato, going home, going back and buying bread, going home, etc. Just go to the store and walk down the aisle, picking what you need as you go. :slight_smile:

The C string library is optimized to support that method.

+1 aarg,

I didn't want to bring up pointers but that's the best way I know to code the "walk". Pointers scare a lot of beginners and even intermediate coders which is a shame given how fundamental they are.

wanting to find out a more elegant and optimum way to do this

Using Strings is certainly not elegant and optimum but why do you mess around converting to a char array before printing the data in your current code ?

OK folks ... I knew that String is a passionate topic !!

But what I read ( GoForSmoke ) is quite informative ...instead of telling something is just bad ...telling why is more interesting to read.

I am not sure if I would do shopping as suggested but then things were fine till someone said Pointers. Somehow I am feeling tired now and maybe will look into it tomorrow.

Would be happy to get a small example of how to parse a small record - maybe into one or two char arrays - will help the learning. ( Wouldn't mind even pointers !!)

Have you looked at Robin2's Serial Input Basics thread? It has all of those examples.

There are also plenty of places where you can read about why String is bad on a microcontroller without someone writing it out on your thread just special for you.

Mogaraghu:
I am not sure if I would do shopping as suggested but then things were fine till someone said Pointers. Somehow I am feeling tired now and maybe will look into it tomorrow.

Would be happy to get a small example of how to parse a small record - maybe into one or two char arrays - will help the learning. ( Wouldn’t mind even pointers !!)

Take your time with pointers. You can do much the same with indexes for now.
Pointers: Go down to definitions so that later the terms don’t confuse you.
Under it all it makes consistent sense, you want to see that to know it and will need to write code just testing your ideas out.

A pointer is just an address with a data type attached. Add one and the address is incremented by the length of the data type. You can subtract one from the other to know how far apart they are.

Every array name is a pointer. 2D array name is a pointer to an array of pointers to the data associated with each. The sheer number of uses for pointers makes it a bit of a study (and practice!).

Two weeks of learn, practice, learn, etc is not too long to get a solid working grip on pointers. Do not push on when you‚Äôre tired! Let your brain grow the neural connections that make the lessons yours and maybe find good ‚Äúbrain food‚ÄĚ to eat well before you study. Nose to the grindstone is for idiots.

The State Machine example in this tutorial might do for a beginning: Gammon Forum : Electronics : Microprocessors : How to process incoming serial data without blocking

If you start with this tutorial, it teaches the basics of non-blocking and why: Gammon Forum : Electronics : Microprocessors : How to do multiple things at once ... like cook bacon and eggs

I like Nick’s tutorials because they’re full-serve while using commonsense examples and keeping things as simple as possible.

The State Machine example is very simple but could be expanded. It’s showing a principle with a technique.

Delta_G:
Have you looked at Robin2's Serial Input Basics thread? It has all of those examples.

There are also plenty of places where you can read about why String is bad on a microcontroller without someone writing it out on your thread just special for you.

Oh yes thanks for the reminder... I refer to Robin's tutorials regularly. Will do so again...

@GoForSmoke

Sure... its not that Pointers are so difficult...I do have a bit of assembly coding background with 8051 years back. So the concept of pointers is pretty much clear...just that never got into the parcticeof using it regularly or thinking any problem of sorting or seeking based on pointers.

Thanks for the Nick's tutorial pages and specific links - I am frequent visitor there !!

Oh good, you have real background! You can look at high level languages in low level terms. Big Advantage!

Mostly you will have to get used to how the * and & symbols are used but once you do it may be old friends in new clothes.

You should also find C strings much easier to understand and exploit than String objects.

Do you know this site? AVR Libc Home Page
That's the heart of Arduino-C/C++ with the standard libs documented.

@Mogaraghu, you have been around here for some time now and this question (as well as some others) makes me think that you don't spend much time reading other people's Threads. There is a great deal of info to be gained (about good practices and bad) by reading as many other Forum Threads as you have time for. Even if the subject matter is not obviously relevant to your own interests programming is programming and techniques that will be useful for you will crop up all over the place.

The other advantage of reading other Threads is that you are likely to stumble across ideas that you never thought of and which could be very useful - as in "Wow, I didn't know you could do that!"

...R

Yes Robin... you have judged very well.

Its true I rarely go through other peoples thread, except those that get thrown up when I do a search for a particular issue. Most times I never get past that stage as almost always I find the answer I am looking for.

As it is, the time spent in front of a computer is too much for anyones comfort ( I am not counting social media which generally stays with the cell phone ).

In fact many a time you will see some basic questions coming up from me - that's mainly because of trying to run a full company which is not into electronics or software and doing this embedded stuff mostly as hobby or as a help to some friends with no commercial interests. What you don't keep in touch regularly starts fading from memory over time.

Even this current query which I raised has thrown up some good pointers ( pun intended !!) but it will be a while before I get the time to check out on HW ....

OK so the String object gets the boot. If everyone hates it like plague, wonder what purpose it was developed for . Something like the GOTO: statement I guess …

Anyway I followed the parsing example as in Robin’s tutorial ( thanks !!) and am giving the modified code which works as intended. I am happy…

// FUNCTION TO RETRIEVE SPECIFIC RECORD AND LOAD FIELDS INTO GLOBAL VARIABLES

void parseCharString (int recPosition )
{
  char letter;
  static int index = 0; 
  File dataFile = SD.open("ACTUAL.CSV", FILE_READ);         // Open the file
  dataFile.seek(recPosition + 2);                           // Go to the required row..ignore '<' and status marker
  if (dataFile)
  {
    while ( dataFile.available())
    {
      letter = dataFile.read();
      if (letter == '>')
      {
        dataFile.close();                                    // end of record. Close the data file...
        index = 0; 
        
        strcpy( recordFromSDCcopy, recordFromSDC);           // get ready to parse the data. Use a copy as original is modified by strtok()

        char * strtokIndx;                                   // this is used by strtok() as an index

        strtokIndx = strtok(recordFromSDCcopy, ",");         // get the first part - the string
        strcpy(SlNo, strtokIndx);                            // copy it to relevant variable..

        strtokIndx = strtok(NULL, ",");                      // this continues where the previous call left off
        strcpy(Priority, strtokIndx);

        strtokIndx = strtok(NULL, ",");
        strcpy(Freq, strtokIndx);

        strtokIndx = strtok(NULL, ",");
        strcpy(Route, strtokIndx);

        strtokIndx = strtok(NULL, ",");
        strcpy(RouteNo, strtokIndx);

        strtokIndx = strtok(NULL, ",");
        strcpy(RFID, strtokIndx);

        strtokIndx = strtok(NULL, ",");
        strcpy(EquipID, strtokIndx);

        strtokIndx = strtok(NULL, ",");
        strcpy(EquDesc, strtokIndx);

        strtokIndx = strtok(NULL, ",");
        strcpy(GrType, strtokIndx);

        strtokIndx = strtok(NULL, ",");
        strcpy(GrQty, strtokIndx);
      }
      else
      {
        recordFromSDC[index]= letter;
        index ++ ; 
      }
    }
  }
  else
  {
    lcd.clear();
    lcd.print(F("ACTUAL.CSV"));
    lcd.setCursor(0, 1);
    lcd.print(F("  SD File Error2 !  "));
    lcd.setCursor(0, 2);
    lcd.print(F(" Reset and Verify.."));
    while (1);                                                        // Hold the message...and wait for reset.
  }
}

And the modified display function :

// FUNCTION TO DISPLAY THE DETAILS DURING GREASING OPERATION

void dispGreasingScreen() {
  lcd.clear();
 
  lcd.setCursor(0, 0);                                   // Part of first line
  lcd.print(SlNo);

  lcd.setCursor(12, 0);                                  // Remaining part of first line
  lcd.print(EquipID);

  lcd.setCursor(0, 1);                                   // Second line
  lcd.print(EquDesc);

  lcd.setCursor(0, 2);                                   // Third line
  lcd.print(GrType);

  lcd.setCursor(0, 3);                                   // Part of fourth line ...
  lcd.print("Plan: ");
  lcd.print(GrQty);

  lcd.setCursor(11, 3);                                  // Balance of fourth line
  lcd.print("Act: ");
}

The only one thing that needs work is to convert the char array into float at some points to compare. Like earlier I was doing this when GrQty was a String type:

if (flowTotal >= GrQty.toFloat())
{
// do something 
}

Now that’s not possible. I need to use atof() I guess.

1 Like

Mogaraghu:
OK so the String object gets the boot. If everyone hates it like plague, wonder what purpose it was developed for . Something like the GOTO: statement I guess .....

Computers with loads of memory and programmers that can't handle (or be bothered to learn) C strings.

An unconditional jump has more relevance to small environments (like Arduino) than String objects.