In this post, we will see how to send and receive integer or floating-point numbers via serial port, as part of 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 you search the internet you will find many codes to perform this action, with varying degrees of success. This is because there is more than one way to perform the process, each with its advantages and disadvantages.
In this post, we will present the main methods for receiving a number via 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 their 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’s practically identical to receiving a character, but we later subtract 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 almost no processing. The biggest disadvantage, logically, is that we can only receive one digit. Although it is generally insufficient, it can be useful for receiving simple transmissions, such as a menu option or the number of LED blinks.
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 will see is using the functions of the Serial class, Serial.parseInt() and Serial.parseFloat().
Thus, the following example receives an integer.
#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 comfortable and easy to integrate into our code. However, it has quite a few disadvantages that make its use inadvisable.
In general, it is a rather slow method. Furthermore, it is blocking, meaning the program stops waiting for the number until the timeout expires.
On the other hand, if it receives a single newline character, it returns 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 into a String, and then the String.toInt() and String.toFloat() functions to convert to a number.
The example for an integer 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. Furthermore, we have greater control because we can choose any character as a separator (useful, for example, to split comma-separated text).
In general, this should be your first choice for receiving numbers via serial port.
Receive into 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 functionality, 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 inconvenience of having to define the buffer size 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.
For its part, the control over 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 code to perform the reception and conversion to an integer.
#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 needed 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 that used internally by these functions.
The biggest advantage is that we have total control over the process. For example, in 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 from this post is available for download on Github.

