4. Serial Communications – Arduino Cookbook [Book]

You need to send data in binary format, because you want to pass information with the fewest number of bytes or because the application you are connecting to only handles binary data.

This sketch sends a header followed by two integer (16-bit) values as binary data. The values are generated using the Arduino random function (see Recipe 3.11 ):

Mục lục bài viết

Discussion

Sending binary data requires careful planning, because you will
get gibberish unless the sending side and the receiving side
understand and agree exactly how the data will be sent. Unlike text
data, where the end of a message can be determined by the presence of
the terminating carriage return (or another unique character you
pick), it may not be possible to tell when a binary message starts or
ends by looking just at the data—data that can have any value can
therefore have the value of a header or terminator character.

This can be overcome by designing your messages so that the
sending and receiving sides know exactly how many bytes are expected.
The end of a message is determined by the number of bytes sent rather
than detection of a specific character. This can be implemented by
sending an initial value to say how many bytes will follow. Or you can
fix the size of the message so that it’s big enough to hold the data
you want to send. Doing either of these is not always easy, as
different platforms and languages can use different sizes for the
binary data types—both the number of bytes and their order may be
different from Arduino. For example, Arduino defines an int as two bytes, but Processing (Java)
defines an int as four bytes
(short is the Java type for a
16-bit integer). Sending an int
value as text (as seen in earlier text recipes) simplifies this
problem because each individual digit is sent as a sequential digit
(just as the number is written). The receiving side recognizes when
the value has been completely received by a carriage return or other
nondigit delimiter. Binary transfers can only know about the
composition of a message if it is defined in advance or specified in
the message.

This recipe’s Solution requires an understanding of the data
types on the sending and receiving platforms and some careful
planning. Recipe 4.7
shows example code using the Processing language to receive these
messages.

Sending single bytes is easy; use Serial.print(byteVal). To send an integer
from Arduino you need to send
the low and high bytes that make up the integer (see Recipe 2.2 for more on data
types). You do this using the lowByte and highByte functions (see Recipe 3.14):

Serial.print(lowByte(intValue), BYTE);
Serial.print(highByte(intValue), BYTE);

The preceding code sends the low byte followed by the high byte.
The code can also be written without the BYTE parameter (see Recipe 4.2), but using the
parameter is a useful reminder (when you come back later to make
changes, or for others who may read your code) that your intention is
to send bytes rather than ASCII characters.

Sending a long integer is done by breaking down the four bytes
that comprise a long in two steps.
The long is first broken into two
16-bit integers; each is then sent using the method for sending
integers described earlier:

int longValue = 1000;
int intValue;

First you send the lower 16-bit integer value:

intValue = longValue && 0xFFFF;  // get the value of the lower 16 bits
Serial.print(lowByte(intVal), BYTE);
Serial.print(highByte(intVal), BYTE);

Then you send the higher 16-bit integer value:

intValue = longValue >> 16;  // get the value of the higher 16 bits
Serial.print(lowByte(intVal), BYTE);
Serial.print(highByte(intVal), BYTE);

You may find it convenient to create functions to send the data.
Here is a function that uses the code shown earlier to print a 16-bit
integer to the serial port:

// function to send the given integer value to the serial port
void sendBinary(int value)
{
  // send the two bytes that comprise a two byte (16 bit) integer
  Serial.print(lowByte(value), BYTE);  // send the low byte
  Serial.print(highByte(value), BYTE); // send the high byte
}

The following function sends the value of a long (4-byte) integer by first sending the
two low (rightmost) bytes, followed by the high (leftmost)
bytes:

// function to send the given long integer value to the serial port
void sendBinary(long value)
{
  // first send the low 16 bit integer value
  int temp = value && 0xFFFF;  // get the value of the lower 16 bits
  sendBinary(temp);
  // then send the higher 16 bit integer value:
  temp = value >> 16;  // get the value of the higher 16 bits
  sendBinary(temp);
}

These functions to send binary int and long values have the same name: sendBinary. The
compiler distinguishes them by the type of value you use for the
parameter. If your code calls printBinary with a 2-byte value, the version
declared as void sendBinary(int
value)
will be called. If the parameter is a long value, the version declared as void sendBinary(long value) will be called.
This behavior is called function overloading. Recipe 4.2 provides another
illustration of this; the different functionality you saw in Serial.print is due to the compiler
distinguishing the different variable types used.

You can also send binary data using structures. Structures are a
mechanism for organizing data, and if you are not already familiar
with their use you may be better off sticking with the solutions
described earlier. For those who are comfortable with the concept of
structure pointers, the following is a function that will send the
bytes within a structure to the serial port as binary data:

void sendStructure( char *structurePointer, int structureLength)
{
  int i;

  for (i = 0 ; i < structureLength ; i++)
    serial.print(structurePointer[i], BYTE);
  }

sendStructure((char *)&myStruct, sizeof(myStruct));

Sending data as binary bytes is more efficient than sending data
as text, but it will only work reliably if the sending and receiving
sides agree exactly on the composition of the data. Here is a summary
of the important things to check when writing your code:

Variable size

Make sure the size of the data being sent is the same on both sides. An
integer is 2 bytes on
Arduino, 4 bytes on most other platforms. Always check your
programming language’s documentation on data type size to ensure
agreement. There is no problem with receiving a 2-byte Arduino
integer as a 4-byte integer in Processing as long as Processing
expects to get only two bytes. But be sure that the sending side
does not use values that will overflow the type used by the
receiving side.

Byte order

Make sure the bytes within an int or long are sent in the same order
expected by the receiving side.

Synchronization

Ensure that your receiving side can recognize the beginning
and end of a message. If you start listening in the middle of a
transmission stream, you will not get valid data. This can be
achieved by sending a sequence of bytes that won’t occur in the
body of a message. For example, if you are sending binary values
from analogRead, these can
only range from 0 to 1,023, so the most significant byte must be
less than 4 (the int value of
1,023 is stored as the bytes 3 and 255); therefore, there will
never be data with two consecutive bytes greater than 3. So,
sending two bytes of 4 (or any value greater than 3) cannot be
valid data and can be used to indicate the start or end of a
message.

Structure packing

If you send or receive data as structures, check your compiler
documentation to make sure the packing is
the same on both sides. Packing is the padding that a compiler uses to align data
elements of different sizes in a structure.

Flow control

Either choose a transmission speed that ensures that the receiving
side can keep up with the sending side, or use some kind of
flow control. Flow control is a handshake
that tells the sending side that the receiver is ready to get
more data.

Xổ số miền Bắc