Comma Seporated Values

Hello, I've been up all night working on a program that will except a serial input of CSV's, decompose them into their separate values, and then place those values into a char array for easy reference later.

The problem arises when I try to place the values into the char array(line 85). With it commented out, the code nicely splits the CSV into separate strings and then sends them back to me over serial. Uncomment it though, and it'll split the first two values and nothing else ever until the board is reset...

I've just attached the program for simplicity and fear of leaving something important out. If you'd like specific pieces though, I'd be happy to reply with them.

Thank you for your help,
~Abner Doubledeal~ :sleeping: :sleeping: :sleeping:

SerialEventDetailsCSV.ino (4.02 KB)

The code is short enough to post here:

/*
  Serial Event example

 When new serial data arrives, this sketch adds it to a String.
 When a newline is received, the loop prints the string and
 clears it.

 A good test for this is to try it with a GPS receiver
 that sends out NMEA 0183 sentences.

 Created 9 May 2011
 by Tom Igoe

 This example code is in the public domain.

 http://www.arduino.cc/en/Tutorial/SerialEvent

 */

String inputString = "";         // a string to hold incoming data
boolean stringComplete = false;  // whether the string is complete

int s = 0;                       // Size of inputStringComponents
char* inputStringComponents[] = {""};

void setup() {
  // initialize serial:
  Serial.begin(9600);
  // reserve 200 bytes for the inputString:
  inputString.reserve(200);
}

void loop() {
  // print the string when a newline arrives:
  if (stringComplete) {
    serialFeedback(inputString);
    
    serialComponents(inputString);
    
    // clear the string:
    inputString = "";
    stringComplete = false;
  }
}

/*
  SerialEvent occurs whenever a new data comes in the
 hardware serial RX.  This routine is run between each
 time loop() runs, so using delay inside loop can delay
 response.  Multiple bytes of data may be available.
 */
void serialEvent() {
  while (Serial.available()) {
    // get the new byte:
    char inChar = (char)Serial.read();
    // add it to the inputString:
    inputString += inChar;
    // if the incoming character is a newline, set a flag
    // so the main loop can do something about it:
    if (inChar == '\n') {
      stringComplete = true;
    }
  }
}

void serialComponents(String rawString) {
  int startIndex = 0;      //location to start looking for a "," from
  int stopIndex = 0;       //the location of the ","
  String component = "";   //a substring of "rawString" from "startIndex" to "stopIndex"
  int address = 0;         //the address in "inputStringComp." to stor "buf"
  char buf[30];            //a buff to convert "component" to a char
  int compL = 0;           //the length of "component"
  
  component.reserve(50);
  
  for (int i = 1; i < rawString.length() - 1; i++) {
    
    stopIndex = rawString.indexOf(",", startIndex);
    if ( stopIndex == -1) {
      stopIndex = rawString.length() - 1;
    }
    component = rawString.substring(startIndex, stopIndex);
    compL = component.length();
    component.toCharArray(buf, 30);
//    inputStringComponents[address] = buf;//
    
    Serial.print("                rawlength: ");
    Serial.println(rawString.length() -1);
    
    Serial.print("               startIndex: ");
    Serial.println(startIndex);
    
    Serial.print("                stopIndex: ");
    Serial.println(stopIndex);
    
    Serial.print("                component: ");
    Serial.println(component);
    
    Serial.print("                    compL: ");
    Serial.println(compL);

    Serial.print("                      buf: ");
    Serial.println(buf);
    
//    Serial.print("                  address: ");//
//    Serial.println(address);//
    
//    Serial.print("inputStringComp.[address]: ");//
//    Serial.println(inputStringComponents[address]);//
    
    Serial.print("                        i: ");
    Serial.println(i);
    Serial.println();
    
    address = ++address;
    i = stopIndex - 1;
    startIndex = stopIndex + 1;
  }
}

void serialFeedback(String feedback) {
  int lngth = 0;
  
  lngth = feedback.length() - 1;
  
  Serial.print("String: ");
  Serial.println(feedback);
  
  //Serial.print("   Length: ");
  //Serial.println(lngth);
  
  Serial.print("Charector:");
  for (int x = 1; x <= lngth; x++) {
    Serial.print(" ");
    Serial.print(x);
  }
  Serial.println("");
  
  Serial.print(" Location:");
  for (int x = 0; x < lngth; x++) {
    if ( x == 9) {
      Serial.print(" ");
    }
    Serial.print(" ");
    Serial.print(x);
  }
  Serial.println("");
  
  Serial.print("    Value:");
  for (int x = 0; x < lngth; x++) {
    Serial.print(" ");
    if (x >= 9) {
      Serial.print(" ");
    }
    Serial.print(inputString[x]);
  }
  Serial.println("");
  Serial.println("");
  
}

AbnerDoubledeal:
Hello, I've been up all night working on a program that will except a serial input of CSV's, decompose them into their separate values, and then place those values into a char array for easy reference later.

I read in the comments:

A good test for this is to try it with a GPS receiver
that sends out NMEA 0183 sentences.

Is this going to become a parser for NMEA0183 sentences from a GPS receiver?

Why don't you use nullterminated strings (C-strings), but those "String-objects" that are wasting amounts of RAM, are slow and provide much less functions than you can use with nullterminated strings?

What about a rewrite to RAM saving, effective and faster string handling with nullterminated strings?

Do you need some code for that?
Or do you want to stay with the ineffetive String-object class in your programming?

jurs:
I read in the comments:

A good test for this is to try it with a GPS receiver
that sends out NMEA 0183 sentences.

Is this going to become a parser for NMEA0183 sentences from a GPS receiver?

Why don't you use nullterminated strings (C-strings), but those "String-objects" that are wasting amounts of RAM, are slow and provide much less functions than you can use with nullterminated strings?

What about a rewrite to RAM saving, effective and faster string handling with nullterminated strings?

Do you need some code for that?
Or do you want to stay with the ineffetive String-object class in your programming?

I'm looking to control a large dot matrix display over serial with a GUI created in Processing. I was thinking CSV would be an efficient way to sync the two instead of sending one variable at a time.

//    inputStringComponents[address] = buf;//

What does that pointer point to? How much space is pointed to? Nothing and none are the correct answers, just in case you came up with something else.

AbnerDoubledeal:
Hello, I've been up all night working on a program that will except a serial input of CSV's, decompose them into their separate values, and then place those values into a char array for easy reference later.

First of all, it's "accept". CSV should work for your application. However, if you are concerned about efficiency, a binary transfer is much more efficient. Avoid using the String class for Arduino applications, it has trouble working in the small memory footprint that is available.

jurs:
Why don't you use nullterminated strings (C-strings), but those "String-objects" that are wasting amounts of RAM, are slow and provide much less functions than you can use with nullterminated strings?

I think I'm understand you here... The DMD library requires the txt being displayed to be a char.

So something like this:

#include "SPI.h"      
#include "DMD.h" 
#include "TimerOne.h"
#include "Arial_black_16.h"<arial_black_16.h> 
// you can remove the fonts if unused
#include "SystemFont5x7.h"
#define DISPLAYS_ACROSS 1   
#define DISPLAYS_DOWN 1       
/* change these values if you have more than one DMD connected */
DMD dmd(DISPLAYS_ACROSS,DISPLAYS_DOWN);

void ScanDMD()
{ 
  dmd.scanDisplayBySPI();
}

String value = "Hello";
int lngth = value.length() - 1;

void setup()
{
   Timer1.initialize( 5000 );           
/*period in microseconds to call ScanDMD. Anything longer than 5000 (5ms) and you can see flicker.*/

   Timer1.attachInterrupt( ScanDMD );  
/*attach the Timer1 interrupt to ScanDMD which goes to dmd.scanDisplayBySPI()*/

   dmd.clearScreen( true );            
/* true is normal (all pixels off), false is negative (all pixels on) */
}

void loop()
{
  dmd.drawString(0, 0, value, lngth, GRAPHICS_NORMAL);
  delay(5000);
}

wont work

AbnerDoubledeal:
I'm looking to control a large dot matrix display over serial with a GUI created in Processing. I was thinking CSV would be an efficient way to sync the two instead of sending one variable at a time.

Maybe, maybe not. But the transfer protocol you send from your Processing has nothing to do with how you do the string handling on the Arduino.

So I suppose, you want to stay with inefficient "String" objects.
In general, this is no big disadvantage, as Serial is a slow protocol, and it doesn't matter in most cases, if you lose some microseconds here and some microseconds there.

And you also can mix ineffective "String" objects with some real effective string functions from the AVR LIBC library to make it a bit more efficient.

But before I can send some code example, can you please describe the protocol a bit more detailed:

Are the comma seperated strings always containing at least one character like:
1,2,3,4711,4712,4713

Or would it be possible that there are sent empty strings in your protocol like;
1,2,,,,4713

In the second case the empty strings between the commas may need extra handling in the code, so what type of data do you have?

jurs:
Maybe, maybe not. But the transfer protocol you send from your Processing has nothing to do with how you do the string handling on the Arduino.

So I suppose, you want to stay with inefficient "String" objects.
In general, this is no big disadvantage, as Serial is a slow protocol, and it doesn't matter in most cases, if you lose some microseconds here and some microseconds there.

And you also can mix ineffective "String" objects with some real effective string functions from the AVR LIBC library to make it a bit more efficient.

But before I can send some code example, can you please describe the protocol a bit more detailed:

Are the comma seperated strings always containing at least one character like:
1,2,3,4711,4712,4713

Or would it be possible that there are sent empty strings in your protocol like;
1,2,,,,4713

In the second case the empty strings between the commas may need extra handling in the code, so what type of data do you have?

I'm willing to go with other options besides strings. They're just what I know so that's where I started

displayname
valueDisplayed
xPos
yPos
So: disp1,1000 @ 7pm,0,0
More or less

There wouldn't be any empty strings sent

AbnerDoubledeal:
There wouldn't be any empty strings sent

OK, here is a line receiver demo sketch that receives comma seperated strings:

/* The receiveCharArray() function takes one received character as a input parameter
   and returns two possible return values:
   return value is NULL ==> no valid line received yet
   return value is different from NULL ==> it is a pointer to a char array that contains the received string
*/   
#define SEPERATING_CHAR 13  // ASCII-13 is '\n' newline character and seperates strings
char* receiveCharArray(char c)
{
  static char lineBuffer[81];  // define maximum buffer length here (max string length +1)
  static byte counter=0;
  if (counter==0) memset(lineBuffer,0,sizeof(lineBuffer)); // clear buffer before using it
  if (c==SEPERATING_CHAR)
  {
    counter=0;
    return lineBuffer;
  }
  else if (c>=32 && counter<sizeof(lineBuffer)-1) 
  { // if is it a printable ASCII-character and fits into the lineBuffer
    lineBuffer[counter]=c; // insert character into lineBuffer
    counter++;
  }
  return NULL;
}


void setup() {
  Serial.begin(9600);
}

void loop() {
  char *str, *substr;
  if (Serial.available())
  {
    str=receiveCharArray(Serial.read());
    if (str!=NULL) // we have received a complete line
    {
      Serial.print("Input: ");
      int count=0;
      substr=strtok(str,",");
      while (substr!=NULL)
      {
        count++;
        Serial.print(substr);
        Serial.print("    ");
        substr=strtok(NULL,",");
      }
      Serial.print("   ==> ");
      Serial.print(count);
      Serial.println(" substrings received.");
    }
  }
}

As I'm not good in programming with the ineffective and slow "String" objects, the demo is using effective and fast nullterminated strings only.

The "strtok()" function used for seperating the substrings from the received line is a standard-C function. And as all standard-C string functions, 'strtok()' is working on nullterminated strings. If you have problems understanding, you can google the function name 'strtok' for further explanations in C-tutorials.

Please feel free to ask if you have any questions.

I'm looking to control a large dot matrix display over serial with a GUI created in Processing. I was thinking CSV would be an efficient way to sync the two instead of sending one variable at a time.

You probably need to settle on the individual data packet design, then parse that packet for the required info. Below is some servo control code that receives command packets that contain the servo position, the servo identifier for the servo to be controlled, and a comma end of packet marker. You might consider something similar.

//zoomkat 11-22-12 simple delimited ',' string parse 
//from serial port input (via serial monitor)
//and print result out serial port
//multi servos added 
// Powering a servo from the arduino usually *DOES NOT WORK*.

String readString;
#include <Servo.h> 
Servo myservoa, myservob, myservoc, myservod;  // create servo object to control a servo 

void setup() {
  Serial.begin(9600);

  //myservoa.writeMicroseconds(1500); //set initial servo position if desired

  myservoa.attach(6);  //the pin for the servoa control
  myservob.attach(7);  //the pin for the servob control
  myservoc.attach(8);  //the pin for the servoc control
  myservod.attach(9);  //the pin for the servod control 
  Serial.println("multi-servo-delimit-test-dual-input-11-22-12"); // so I can keep track of what is loaded
}

void loop() {

  //expect single strings like 700a, or 1500c, or 2000d,
  //or like 30c, or 90a, or 180d,
  //or combined like 30c,180b,70a,120d,

  if (Serial.available())  {
    char c = Serial.read();  //gets one byte from serial buffer
    if (c == ',') {
      if (readString.length() >1) {
        Serial.println(readString); //prints string to serial port out

        int n = readString.toInt();  //convert readString into a number

        // auto select appropriate value, copied from someone elses code.
        if(n >= 500)
        {
          Serial.print("writing Microseconds: ");
          Serial.println(n);
          if(readString.indexOf('a') >0) myservoa.writeMicroseconds(n);
          if(readString.indexOf('b') >0) myservob.writeMicroseconds(n);
          if(readString.indexOf('c') >0) myservoc.writeMicroseconds(n);
          if(readString.indexOf('d') >0) myservod.writeMicroseconds(n);
        }
        else
        {   
          Serial.print("writing Angle: ");
          Serial.println(n);
          if(readString.indexOf('a') >0) myservoa.write(n);
          if(readString.indexOf('b') >0) myservob.write(n);
          if(readString.indexOf('c') >0) myservoc.write(n);
          if(readString.indexOf('d') >0) myservod.write(n);
        }
         readString=""; //clears variable for new input
      }
    }  
    else {     
      readString += c; //makes the string readString
    }
  }
}

I suspect the solution that @jurs has suggested is much the same as the examples in serial input basics.

...R

jurs:
OK, here is a line receiver demo sketch that receives comma seperated strings:

/* The receiveCharArray() function takes one received character as a input parameter

and returns two possible return values:
  return value is NULL ==> no valid line received yet
  return value is different from NULL ==> it is a pointer to a char array that contains the received string
/  
#define SEPERATING_CHAR 13  // ASCII-13 is '\n' newline character and seperates strings
char
receiveCharArray(char c)
{
 static char lineBuffer[81];  // define maximum buffer length here (max string length +1)
 static byte counter=0;
 if (counter==0) memset(lineBuffer,0,sizeof(lineBuffer)); // clear buffer before using it
 if (c==SEPERATING_CHAR)
 {
   counter=0;
   return lineBuffer;
 }
 else if (c>=32 && counter<sizeof(lineBuffer)-1)
 { // if is it a printable ASCII-character and fits into the lineBuffer
   lineBuffer[counter]=c; // insert character into lineBuffer
   counter++;
 }
 return NULL;
}

void setup() {
 Serial.begin(9600);
}

void loop() {
 char *str, *substr;
 if (Serial.available())
 {
   str=receiveCharArray(Serial.read());
   if (str!=NULL) // we have received a complete line
   {
     Serial.print("Input: ");
     int count=0;
     substr=strtok(str,",");
     while (substr!=NULL)
     {
       count++;
       Serial.print(substr);
       Serial.print("    ");
       substr=strtok(NULL,",");
     }
     Serial.print("   ==> ");
     Serial.print(count);
     Serial.println(" substrings received.");
   }
 }
}




As I'm not good in programming with the ineffective and slow "String" objects, the demo is using effective and fast nullterminated strings only.

The "strtok()" function used for seperating the substrings from the received line is a standard-C function. And as all standard-C string functions, 'strtok()' is working on nullterminated strings. If you have problems understanding, you can google the function name 'strtok' for further explanations in C-tutorials.

Please feel free to ask if you have any questions.

I'm probably missing something simple, but when I run your code and send it a CSV, such as "a,b,c,d", over the serial monitor I get nothing back. Line ending is set to newline. Thoughts?

AbnerDoubledeal:
I'm probably missing something simple, but when I run your code and send it a CSV, such as "a,b,c,d", over the serial monitor I get nothing back. Line ending is set to newline. Thoughts?

Sorry, my mistake. In this line the comment about the line ending character is wrong:

#define SEPERATING_CHAR 13  // ASCII-13 is '\n' newline character and seperates strings

Of course ASCII-13 is the 'Carriage Return' ('\r') code.
So with the code as I posted it you'd have to set the line ending option to 'Carriage Return' or 'Both'.

Please fix that line and change to:

#define SEPERATING_CHAR 10  // ASCII-10 is '\n' newline character and seperates strings

or

#define SEPERATING_CHAR '\n'  // ASCII-10 is '\n' newline character and seperates strings

The code is then working with the setting 'Newline' or 'Both'.