We continue with this series of posts aimed at mastering the serial port of processors like Arduino. In the previous post, we saw how to send and receive a byte sequence. We left off by saying we would delve deeper into generating and processing bytes. To do this, in this post we will see how to send and receive an array via serial port directly as bytes.
When sending or receiving an array via serial port, we are working with a sequence of data of the same type (int, float). In the next post, we will generalize the process by seeing how to send or receive any grouping of data, in the form of an object or structure.
Despite being a specific case of the next post, sending and receiving the array via serial port is interesting both because it is a common need, and because it allows us to illustrate and present the byte conversion we will use in the case of sending structures.
Sending the array via serial port directly as bytes is much more efficient than sending them as text with a separator, as we saw in the post send an array separated by commas. Less data is sent, it’s more efficient because it avoids having to perform conversions, and the code is simpler.
Remember what was mentioned in the previous post regarding the differences in the number of bytes that different processors use to store different variable types.
Sending and Receiving Individual Elements
The first method involves sending the individual elements. In the example, we will send an array of ints. For brevity, the array in the example is defined but empty. In a real case, logically, this array would contain the data we want to send.
The code is quite simple. The sender iterates through the array and uses a function to send the bytes that make up each piece of data.
const size_t dataLength = 10;
int data[dataLength];
void setup()
{
Serial.begin(9600);
for(int n = 0; n < dataLength; n++)
{
sendBytes(data[n]);
}
}
void loop()
{
}
void sendBytes(uint16_t value)
{
Serial.write(highByte(value));
Serial.write(lowByte(value));
}
On the other hand, the receiver receives the bytes, uses another function to reconstruct the data from the bytes, and stores them in the array.
const size_t dataLength = 10;
int data[dataLength];
int dataIndex = 0;
void setup()
{
Serial.begin(9600);
}
void loop()
{
if(Serial.available() >= sizeof(data[0])
{
byte byte1 = Serial.read();
byte byte2 = Serial.read();
data[dataIndex] = byteToInt(byte1, byte2);
dataIndex++;
if(dataIndex >= dataLength)
{
dataIndex = 0;
}
}
}
uint16_t byteToInt(byte byte1, byte byte2)
{
return (uint16_t)byte1 << 8 | (uint16_t)byte2;
}
The biggest advantage of this method is that it allows us great flexibility in the process since we control it manually. For example, we can use the values to perform actions without having to wait for the entire array.
However, in general, this is not something we will miss. Normally we want to work with fixed frames, meaning we don’t want to perform actions until we have received the entire array.
Sending and Receiving with Direct Cast
Now let’s see how sending and receiving would be done by working directly with bytes. To send the array, we simply cast it to byte* and send the number of bytes in the array.
const size_t dataLength = 10;
int data[dataLength];
void setup()
{
Serial.begin(9600);
Serial.write((byte*)data, dataLength * sizeof(data[0]));
}
void loop()
{
}
Reception is just as simple; we simply pass the int array as a byte* to the read() function, and upon reception, the array will be overwritten with the received values.
const size_t dataLength = 10;
int data[dataLength];
void setup()
{
Serial.begin(9600);
}
void loop()
{
if(Serial.available() >= dataLength * sizeof(data[0]))
{
Serial.readBytes((byte*)data, dataLength * sizeof(data[0]));
}
}
The advantage of this method is evident. Faster, more convenient, simpler (and more “pro”). Disadvantages, as we have mentioned, we have to wait to receive all the bytes. Notice that the condition has changed and reception does not occur until all bytes of the frame have been received.
Therefore, we are constrained by the size of the serial port buffer, which in the case of Arduino Uno and Nano is 64 bytes. If a larger frame is needed, we can either change the value in the HardwareSerial.h library or, more recommended, divide the sending into several packets.
On the other hand, once again, be very aware of the possible differences in byte size of the different variable types.
In the next post, we will see a generalization of this case for sending arbitrary groupings of data in the form of structures or objects.
Download the Code
All the code from this post is available for download on Github.

