cadenas-de-texto-puerto-serie-arduino

Send and receive texts via serial port in Arduino

  • 6 min

It’s been over three years since we introduced the basic tutorial on the serial port as one of the main ways to communicate with a processor like Arduino. Since then, it has been a common component in the blog’s example codes.

By now we are ready (and it was about time) to delve deeper into the topic. For that, we are dedicating a series of advanced tutorials where we will see how to receive text strings, numbers, arrays, comma-separated files, and which will end with sending byte series and the presentation of the ComCenter library for robust serial port communication in Arduino.

In this first post, we are going to see how to handle characters and text strings. If you search the internet, you will see a great variety of codes for receiving text. Some are good, others passable, and others… frankly quite horrible.

The reason is that there is more than one way to handle text on the serial port, each with its advantages and disadvantages. Here we are going to see the main ways to receive characters and text, emphasizing when it is convenient to use one or the other.

The DEBUG functions are solely for visualizing the results; when you use this code you can remove the parts related to their definition and use.

Read a Character

The first case we are going to see is reading a single character via the serial port. The code is very simple, as we only need to read one byte and convert it to char. Despite being simple, it can be useful, for example, to accept commands we send to a robot or a vehicle.


#define DEBUG(a) Serial.println(a);

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

void loop()
{
   if (Serial.available())
   {
      char data = Serial.read();
      if ((data >= 'A'  && data <= 'Z') || (data >= 'a' && data <= 'z'))
      {
         DEBUG((char)data);
      }
   }
}
Copied!

With practically no processing, the speed is the highest of all the examples we are going to see (on the order of 4-8us). The disadvantage is, logically, that we are only receiving a single character.

Furthermore, being a manual process, it is easy to add validation conditions. In the example, we accept characters from A to Z in uppercase or lowercase, but it would be easy, for example, to only accept values from A to D because they are the four commands our setup expects.

Read a Text String with the String Class

If we want to read a complete text string, we can use the String class, which as we know is a wrapper around a char array included in the Arduino IDE, and which provides functions to work comfortably with text strings.

Combined with the function Serial.readStringUntil(char), we can read a text string received via the serial port in a comfortable and simple way. For example, it is common to use ‘\n’ as a separator to receive a complete line.


#define DEBUG(a) Serial.println(a);

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

void loop()
{
   if (Serial.available())
   {
      String data = Serial.readStringUntil('\n');
      DEBUG(data);
   }
}
Copied!

The method is considerably efficient, although, of course, much slower than receiving a single char. The speed depends on the processor and clock speed, but as a rough guide, it is on the order of 1ms per character received.

Read Text String with Char Array

Another alternative is to use a char array instead of the String class to receive a text string. The operation is similar, but we define a buffer before receiving the string, instead of a String instance.

This time we use the function Serial.readBytesUntil(char, char*, int), which receives a text string via the serial port and stores it in the buffer. The function returns the number of characters received and, therefore, occupied in the buffer.


#define DEBUG(a, b) for (int index = 0; index < b; index++) Serial.print(a[index]); Serial.println();

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

void loop()
{
   if (Serial.available())
   {
      char data[20];
      size_t count = Serial.readBytesUntil('\n', data, 20);
      DEBUG(data, count)
   }
}
Copied!

The performance is very similar to using the String class functions. It is slightly superior, as the amount of processing is lower, at the cost of giving up the dynamic size functions of the String class.

As disadvantages, it is less comfortable to use and, above all, we have to define the buffer size before receiving the string. If we fall short, we will receive the string incorrectly, and if we exceed it, we will be wasting memory unnecessarily.

Therefore, in general, we will prefer to use the String class functions, unless we specifically do not want or cannot use the String class in our project.

Read String with Naive Method

The last example we are going to see is the “naive method,” which as we know is an elegant way of saying to do the process “by hand.” We are going to see it in two different variants, using the String class or a char array.

The first way is to use the String class to perform concatenation. We simply receive a char, and if it is different from the separator, we use the String.concat() function to perform the concatenation.


#define DEBUG(a) Serial.println(a);

String data = "";

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

void loop()
{
  while (Serial.available())
  {
    char character = Serial.read();
    if (character != '\n')
    {
      data.concat(character);
    }
    else
    {
      Serial.println(data);
      data = "";
    }
  }
}
Copied!

The advantage of the method is giving us total control over the process. The efficiency of the method is similar to those shown previously, as the implementation is similar to that used internally by the functions.

The biggest disadvantage is that it represents a greater hassle. Notice, for example, that we had to define the String variable as global, otherwise any wait in the main loop would cause it to reset.

If again, we do not want or cannot use the String class, we can similarly use a char array to receive the text string. We simply define a buffer and increment the index when performing concatenation.


#define DEBUG(a) Serial.println(a);

char data[20];
size_t dataIndex = 0;

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

void loop()
{
  while (Serial.available())
  {
    char character = Serial.read();
    if (character != '\n')
    {
      data[dataIndex] = character;
      dataIndex ++;
    }
    else
    {
      Serial.println(data);
      dataIndex = 0;
    }
  }
}
Copied!

Logically, we have all the disadvantages of the naive method in terms of inconvenience of use, plus all the problems associated with using a fixed-size char array that we have seen before.

Therefore, normally we would avoid using this method compared to those presented earlier, unless we specifically have reasons in our project.

Download the Code

All the code from this post is available for download on Github. github-full