Serial sending floats values using char instead of String

Hi,

I've a working solution to send and receive floats trough serial with this piece code below.

#include <SPI.h>
#include <Wire.h>

//Envoi/réception de donnée via port série
#define SOP '<'
#define EOP '>'
#define SEP ','

String field[10];
uint8_t idx = 0;
bool done = false;

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


void loop() {

  if (Serial.available() > 0)  {
    done = parsePacket(Serial.read());
  }

  if (done) {
      Serial.println(' ');
      Serial.println(field[0]);
      Serial.println(field[1]);
      Serial.println(field[2]);
      Serial.println(field[3]);
      Serial.println(field[4]);
  }
}

bool parsePacket(char c)
{
  bool ready = false;
  Serial.write(c);
  switch (c)
  {
    case SEP:
      idx++;
      field[idx] = ' ';
      break;
    case SOP:
      idx = 0;
      field[idx] = ' ';
      break;
    case EOP:
      ready = true;
      break;
    default:
      field[idx] += c ;
      ; // skip
  }
  
  return ready;
}

The data is formatted like this

<2.74,262.05,678.85,10,234>

And each piece of data is correctly parsed in an array of String.

2.74
262.05
678.85
10
234
...

As the sended datas can have various types, I would like to keep a working solution to receive floats, ints or string, and parse each field later on the receiver.

Is there a way to use char array instead of class String but keeping it simple and readable. After many tries I'm not without being able to have a working alternative to String. Also I've leading spaces etc...

How can this being improved?

Thanks for your help!

if you don't mind awaiting the EOP end marker to start parsing, then you should receive the message in a buffer and then parse. You could use strtok() to find the SEP separators and keep pointers into the buffer for further analysis. of course if you reuse the buffer to receive the next command line, then the pointers will be pointing at garbage (so duplicate the command in that case before parsing)

how will you know about the type of data?

seems you speak french, you can have a look at my tutorial

would lead to something like this

const byte tailleMessageMax = 50;
char message[tailleMessageMax + 1]; // +1 car on doit avoir un caractère de fin de chaîne en C, le '\0'

const char   SOP = '<';
const char   EOP = '>';
const char*  SEP = ",";

boolean ecouter()
{
  static byte indexMessage = 0; // static pour se souvenir de cette variable entre 2 appels consécutifs. initialisée qu'une seule fois.
  boolean messageEnCours = true;

  while (Serial.available() && messageEnCours) {
    int c = Serial.read();
    if (c != -1) {
      switch (c) {
        case EOP:
          message[indexMessage] = '\0'; // on termine la c-string
          indexMessage = 0; // on se remet au début pour la prochaine fois
          messageEnCours = false;
          break;
        default:
          if (indexMessage <= tailleMessageMax - 1) message[indexMessage++] = (char) c; // on stocke le caractère et on passe à la case suivante
          break;
      }
    }
  }
  return messageEnCours;
}

bool parseMessage()
{
  const byte maxDataPoints = 50;
  char * dataPtr[maxDataPoints];
  byte dataCount = 0;

  if (message[0] != SOP) return false;
  // sinon on suppose que c'est OK
  char * ptr = strtok(&(message[1]), SEP);
  while (ptr && (dataCount < maxDataPoints)) {
    dataPtr[dataCount++] = ptr;
    ptr = strtok(nullptr, SEP);
  }

  for (byte i = 0; i < dataCount; i++) {
    Serial.print(i);
    Serial.write('\t');
    Serial.println(dataPtr[i]);
  }
  return true;
}

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

void loop() {
  if (! ecouter()) {
    if (!parseMessage()) Serial.println(F("Message erroné"));
  }
}

(with barely any test overflowing the number of data - they will just be ignored)

Great! Exactly what I need, thanks. You've saved my day.
Your proposal is really easy to read and understand. It had successfully parsed my data but only the first read, here was the result:


0	0.93
1	254.47
2	-152.69
3	12
4	234
Message erroné
Message erroné
Message erroné
...

However if I use multiple delimiters it work perfectly. I've changed your code like this:

const char*  DELIMIT=" ,\<\>\t\r\n";


bool parseMessage()
{
  const byte maxDataPoints = 50;
  char * dataPtr[maxDataPoints];
  byte dataCount = 0;

  //if (message[0] != SOP) return false;
  // sinon on suppose que c'est OK
  char * ptr = strtok(&(message[1]), DELIMIT);
  while (ptr && (dataCount < maxDataPoints)) {
    dataPtr[dataCount++] = ptr;
    ptr = strtok(nullptr, DELIMIT);
  }

  for (byte i = 0; i < dataCount; i++) {
    Serial.print(i);
    Serial.write('\t');
    Serial.println(dataPtr[i]);
  }
  return true;
}

Maybe you'll have a smarter way but this is a working solution.
I'm reading your tutorial, I've already learned a lot.

What did you send?

Something like this:

<2.74,262.05,678.85,10,234>
<2.78,264.36,679.54,10,234>
<2.82,268.10,677.55,10,234>

Are you sending CR/LF from the serial monitor? Try setting the line ending to none.

The code does not handle them, you could just ignore them as they come in.

      switch (c) {
        case '\r': // ignoré
        case '\n': // ignoré
          break;
        case EOP:
          message[indexMessage] = '\0'; // on termine la c-string
          indexMessage = 0; // on se remet au début pour la prochaine fois
          messageEnCours = false;
          break;
        default:
          if (indexMessage <= tailleMessageMax - 1) message[indexMessage++] = (char) c; // on stocke le caractère et on passe à la case suivante
          break;
      }

You could also search for the < to start parsing instead of checking the first character.
this is where I do the test on the first character:
if (message[0] != SOP) return false;
and the I start parsing after the '<', at index 1
char * ptr = strtok(&(message[1]), SEP);

you could use strchr() to check for the presence and position of the '<' and start parsing only from thereon.

while (Serial.read() != '<');
for (byte f=0; f<4; f++)
  field[f] = Serial.readStringUntil(',');
field[4] = Serial.readStringUntil('>');

Nice, very straightforward, but actually the output of Serial.readStringUntil is String not char.

I know the result is a String. Why is that a problem? field array is also String...

No problem at all. I was asking to find a way to work without the class String, because I've read argument to replace it by cstrings. But the readability using Serial.readStringUntil is awesome.

Read please this article, it can help you.

1 Like

Ah, sorry, I missed that. Perhaps because you said string not String that first time.

There are a lot of forum members who warn about the dangers of Strings. There are others who have written articles and test sketches that seem to demonstrate that they are generally safe, especially if certain things are avoided. Myself, I have decided to keep an open mind until my experiences convince me one way or the other.

1 Like

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.