In this post we are going to see how to choose integers or floating point numbers through the serial port, within this series of posts aimed at deepening the use of the serial port.
Just like in the previous post, where we saw how to receive characters and text strings, if we search the internet we will see many codes to perform this action, with greater or lesser degree of success. This is because there is more than one way to carry out the process, each with its advantages and disadvantages.
In this post we will present the main methods for receiving a number through the serial port, and when it is more convenient to use one or the other.
The DEBUG functions are only for visualizing the results, when you use this code you can remove the parts related to its definition and use.
Receive a single digit
Just like when we saw how to receive a single character, receiving a single digit is very simple and efficient. In fact, it is practically identical to receiving a character, but then we remove the value of ‘0’ to convert the char to an integer.
#define DEBUG(a) Serial.println(a);
void setup()
{
Serial.begin(9600);
}
void loop()
{
if (Serial.available())
{
char data = Serial.read();
if (data >= '0' && data <= '9')
{
data -= '0';
DEBUG((int)data);
}
}
}
The efficiency of the method is maximum, as it contains hardly any processing. The main disadvantage, logically, is that we can only receive one digit. Although it is generally insufficient, it can be useful for receiving a simple transmission, such as a menu option or the number of flashes of an LED.
Receive with Serial class
If we want to receive numbers with more than one digit, which is normal, we have several options available. The first one we are going to see is to use the functions of the Serial class, Serial.parseInt() and Serial.parseFloat().
Thus, the following example receives an integer number.
#define DEBUG(a) Serial.println(a);
void setup()
{
Serial.begin(9600);
Serial.setTimeout(50);
}
void loop()
{
if (Serial.available())
{
int data = Serial.parseInt();
DEBUG((int)data);
}
}
While the reception of a float would be as follows,
#define DEBUG(a) Serial.println(a);
void setup()
{
Serial.begin(9600);
Serial.setTimeout(50);
}
void loop()
{
if (Serial.available())
{
float data = Serial.parseFloat();
DEBUG(data);
}
}
This form of reception is widely used because it is convenient and easy to integrate into our code. However, it has several disadvantages that make its use not recommended.
In general, it is a rather slow method. In addition, it is blocking, that is, the program remains stopped waiting for the number until the timeout occurs.
On the other hand, if it receives a single line break, it will return a zero, which is a problem in a good number of applications.
Receive with String class
A much more suitable way is to use the functions of the String class. We use the Serial.readStringUntil() function to receive a line in a String, and then the String.toInt() and String.toFloat() functions to convert to a number. The example for an integer number is as follows.
#define DEBUG(a) Serial.println(a);
void setup()
{
Serial.begin(9600);
}
void loop()
{
if (Serial.available() > 0)
{
String str = Serial.readStringUntil('\n');
int data = str.toInt();
DEBUG(data);
}
}
While the example for a floating point number would be as follows.
#define DEBUG(a) Serial.println(a);
void setup()
{
Serial.begin(9600);
}
void loop()
{
if (Serial.available() > 0)
{
String str = Serial.readStringUntil('\n');
float data = str.toFloat();
DEBUG(data);
}
}
The function is practically as simple as the previous example, but it is usually more efficient. In addition, we have more control because we can choose any character as a separator (useful, for example, to divide a comma-separated text).
In general, this should be your first choice for receiving numbers through the serial port.
Receive in a char array
Just like when receiving text strings, if we cannot or do not want to use the String class, we can use a char array and the atoi() and atof() functions to achieve similar functionalities, as we saw when converting text to numbers.
The reading of an integer would be as follows
#define DEBUG(a) Serial.println(a);
void setup()
{
Serial.begin(9600);
Serial.setTimeout(50);
}
void loop()
{
if (Serial.available())
{
char buffer[7];
Serial.readBytesUntil('\n', buffer, 7);
int data = atoi(buffer);
DEBUG(data);
}
}
While the reading of a float would be done like this.
#define DEBUG(a) Serial.println(a);
void setup()
{
Serial.begin(9600);
Serial.setTimeout(50);
}
void loop()
{
if (Serial.available())
{
char buffer[7];
Serial.readBytesUntil('\n', buffer, 7);
int data = atof(buffer);
DEBUG(data);
}
}
However, we have the drawback of having to define the size of the buffer ourselves, something that is not necessary with the String class.
The speed is similar to that achieved with the String class, since the String class uses the atoi and atof functions internally. In fact, it is slightly superior because it requires less processing.
On the other hand, the control capacity of the process is similar to what we would obtain with the String class.
Therefore, in general we will prefer to use the String class, and we will only use these methods when we do not want or cannot use the String class.
Receive with naive method
Finally, we have the “naive” method which we remember is the elegant way of saying to do the process “by hand”. At least it is interesting to understand how the previous methods work internally.
Here we have a code to perform the reception and conversion to an integer number.
#define DEBUG(a) Serial.println(a);
void setup()
{
Serial.begin(9600);
}
int data = 0;
bool isNegative = false;
void loop()
{
while (Serial.available())
{
char incomingChar = Serial.read();
if (incomingChar >= '0' && incomingChar <= '9')
data = (data * 10) + (incomingChar - '0');
else if (incomingChar == '-')
isNegative = true;
else if(incomingChar == '\n')
{
data = isNegative? -data : data;
DEBUG(data);
data = 0;
isNegative = false;
}
}
}
The code required to receive a floating point number is slightly longer.
#define DEBUG(a) Serial.println(a);
void setup()
{
Serial.begin(9600);
}
float data = 0;
int dataReal = 0;
int dataDecimal = 0;
int dataPow = 1;
bool isDecimalStage = false;
bool isNegative = false;
void loop()
{
while (Serial.available())
{
char incomingChar = Serial.read();
if (incomingChar == '-')
isNegative = -1;
else if (incomingChar == '.' || incomingChar == ',')
isDecimalStage = true;
else if (incomingChar >= '0' && incomingChar <= '9')
{
if (isDecimalStage == false)
dataReal = (dataReal * 10) + (incomingChar - '0');
else
{
dataDecimal = (dataDecimal * 10) + (incomingChar - '0');
dataPow *= 10;
}
}
else if (incomingChar == '\n')
{
data = (float)dataReal + (float)dataDecimal / dataPow;
data = isNegative ? -data : data;
WATCH_STOP;
DEBUG(data);
dataReal = 0;
dataDecimal = 0;
dataPow = 1;
isDecimalStage = false;
sign = 1;
}
}
}
The naive method is not much faster than the atoi or atof functions and, by extension, than the toInt() or toFloat() methods of the String class(), since the implementation shown is similar to the one used internally by these functions.
The main advantage is that we have complete control over the process. For example, in the case of our conversion process to a floating point number, we have allowed it to accept ’.’ and ’,’ as decimal separators, without a substantial loss of efficiency.
But in general, unless we have special requirements that justify manual conversion, we will have similar efficiencies using the functions of the String class.
Download the code
All the code in this post is available for download on Github.