Chapter 4. Serial Communications
4.0. Introduction
Serial communications provide an easy and flexible way for your Arduino board to interact with your computer and other devices. This chapter explains how to send and receive information using this capability.
Chapter 1 described how to connect the Arduino serial port to your computer to upload sketches. The upload process sends data from your computer to Arduino and Arduino sends status messages back to the computer to confirm the transfer is working. The recipes here show how you can use this communication link to send and receive any information between Arduino and your computer or another serial device.
Note
Serial communications are also a handy tool for debugging. You can send debug messages from Arduino to the computer and display them on your computer screen or an external LCD display.
The Arduino IDE (described in Recipe 1.3) provides a Serial Monitor (shown in Figure 4-1) to display serial data sent from Arduino.
You can also send data from the Serial Monitor to Arduino by entering text in the text box to the left of the Send button. Baud rate (the speed at which data is transmitted, measured in bits per second) is selected using the drop-down box on the bottom right. You can use the drop down labeled “No line ending” to automatically send a carriage return or a combination of a carriage return and a line at the end of each message sent when clicking the Send button, by changing “No line ending” to your desired option.
Your Arduino sketch can use the serial port to indirectly access (usually via a proxy program written in a language like Processing) all the resources (memory, screen, keyboard, mouse, network connectivity, etc.) that your computer has. Your computer can also use the serial link to interact with sensors or other devices connected to Arduino.
Implementing serial communications involves hardware and software. The hardware provides the electrical signaling between Arduino and the device it is talking to. The software uses the hardware to send bytes or bits that the connected hardware understands. The Arduino serial libraries insulate you from most of the hardware complexity, but it is helpful for you to understand the basics, especially if you need to troubleshoot any difficulties with serial communications in your projects.
Serial Hardware
Serial hardware sends and receives data as electrical pulses that represent sequential bits. The zeros and ones that carry the information that makes up a byte can be represented in various ways. The scheme used by Arduino is 0 volts to represent a bit value of 0, and 5 volts (or 3.3 volts) to represent a bit value of 1.
Note
Using 0 volts (for 0) and 5 volts (for 1) is very common. This is referred to as the TTL level because that was how signals were represented in one of the first implementations of digital logic, called Transistor-Transistor Logic (TTL).
Boards including the Uno, Duemilanove, Diecimila, Nano, and Mega have a chip to convert the hardware serial port on the Arduino chip to Universal Serial Bus (USB) for connection to the hardware serial port. Other boards, such as the Mini, Pro, Pro Mini, Boarduino, Sanguino, and Modern Device Bare Bones Board, do not have USB support and require an adapter for connecting to your computer that converts TTL to USB. See http://www.arduino.cc/en/Main/Hardware for more details on these boards.
Some popular USB adapters include:
Mini USB Adapter (http://arduino.cc/en/Main/MiniUSB)
USB Serial Light Adapter (http://arduino.cc/en/Main/USBSerial)
FTDI USB TTL Adapter (http://www.ftdichip.com/Products/FT232R.htm)
Modern Device USB BUB board (http://shop.moderndevice.com/products/usb-bub)
Seeedstudio UartSBee (http://www.seeedstudio.com/depot/uartsbee-v31-p-688.html)
Some serial devices use the RS-232 standard for serial connection. These usually have a nine-pin connector, and an adapter is required to use them with the Arduino. RS-232 is an old and venerated communications protocol that uses voltage levels not compatible with Arduino digital pins.
You can buy Arduino boards that are built for RS-232 signal levels, such as the Freeduino Serial v2.0 (http://www.nkcelectronics.com/freeduino-serial-v20-board-kit-arduino-diecimila-compatib20.html).
RS-232 adapters that connect RS-232 signals to Arduino 5V (or 3.3V) pins include the following:
RS-232 to TTL 3V–5.5V adapter (http://www.nkcelectronics.com/rs232-to-ttl-converter-board-33v232335.html)
P4 RS232 to TTL Serial Adapter Kits (http://shop.moderndevice.com/products/p4)
RS232 Shifter SMD (http://www.sparkfun.com/commerce/product_info.php?products_id=449)
A standard Arduino has a single hardware serial port, but serial communication is also possible using software libraries to emulate additional ports (communication channels) to provide connectivity to more than one device. Software serial requires a lot of help from the Arduino controller to send and receive data, so it’s not as fast or efficient as hardware serial.
The Arduino Mega has four hardware serial ports that can communicate with up to four different serial devices. Only one of these has a USB adapter built in (you could wire a USB-TTL adapter to any of the other serial ports). Table 4-1 shows the port names and pins used for all of the Mega serial ports.
Software Serial
You will usually use the built-in Arduino Serial library to communicate with the hardware serial ports. Serial libraries simplify the use of the serial ports by insulating you from hardware complexities.
Sometimes you need more serial ports than the number of hardware serial ports available. If this is the case, you can use an additional library that uses software to emulate serial hardware. Recipes 4.13 and 4.14 show how to use a software serial library to communicate with multiple devices.
Serial Message Protocol
The hardware or software serial libraries handle sending and receiving information. This information often consists of groups of variables that need to be sent together. For the information to be interpreted correctly, the receiving side needs to recognize where each message begins and ends. Meaningful serial communication, or any kind of machine-to-machine communication, can only be achieved if the sending and receiving sides fully agree how information is organized in the message. The formal organization of information in a message and the range of appropriate responses to requests is called a communications protocol.
Messages can contain one or more special characters that identify the start of the message—this is called the header. One or more characters can also be used to identify the end of a message—this is called the footer. The recipes in this chapter show examples of messages in which the values that make up the body of a message can be sent in either text or binary format.
Sending and receiving messages in text format involves sending commands and numeric values as human-readable letters and words. Numbers are sent as the string of digits that represent the value. For example, if the value is 1234, the characters 1, 2, 3, and 4 are sent as individual characters.
Binary messages comprise the bytes that the computer uses to represent values. Binary data is usually more efficient (requiring fewer bytes to be sent), but the data is not as human-readable as text, which makes it more difficult to debug. For example, Arduino represents 1234 as the bytes 4 and 210 (4 * 256 + 210 = 1234). If the device you are connecting to sends or receives only binary data, that is what you will have to use, but if you have the choice, text messages are easier to implement and debug.
There are many ways to approach software problems, and some of the recipes in this chapter show two or three different ways to achieve a similar result. The differences (e.g., sending text instead of raw binary data) may offer a different balance between simplicity and efficiency. Where choices are offered, pick the solution that you find easiest to understand and adapt—this will probably be the first solution covered. Alternatives may be a little more efficient, or they may be more appropriate for a specific protocol that you want to connect to, but the “right way” is the one you find easiest to get working in your project.
New in Arduino 1.0
Arduino 1.0 introduced a number of Serial enhancements and changes :
Serial.flush
now waits for all outgoing data to be sent rather than discarding received data. You can use the following statement to discard all data in the receive buffer:while(Serial.read() >= 0) ; // flush the receive buffer
Serial.write
andSerial.print
do not block. Earlier code would wait until all characters were sent before returning. From 1.0, characters sent usingSerial.write
are transmitted in the background (from an interrupt handler) allowing your sketch code to immediately resume processing. This is usually a good thing (it can make the sketch more responsive) but sometimes you want to wait until all characters are sent. You can achieve this by callingSerial.flush()
immediately followingSerial.write()
.Serial print functions return the number of characters printed. This is useful when text output needs to be aligned or for applications that send data that includes the total number of characters sent.
There is a built-in parsing capability for streams such as Serial to easily extract numbers and find text. See the Discussion section of Recipe 4.5 for more on using this capability with Serial.
The SoftwareSerial library bundled with Arduino has had significant enhancements; see Recipes 4.13 and 4.14.
A
Serial.peek
function has been added to let you ‘peek’ at the next character in the receive buffer. UnlikeSerial.read
, the character is not removed from the buffer withSerial.peek
.
See Also
An Arduino RS-232 tutorial is available at http://www.arduino.cc/en/Tutorial/ArduinoSoftwareRS232. Lots of information and links are available at the Serial Port Central website, http://www.lvr.com/serport.htm.
In addition, a number of books on Processing are also available:
Getting Started with Processing: A Quick, Hands-on Introduction by Casey Reas and Ben Fry (Make).
Processing: A Programming Handbook for Visual Designers and Artists by Casey Reas and Ben Fry (MIT Press).
Visualizing Data by Ben Fry (O’Reilly; search for it on oreilly.com).
Processing: Creative Coding and Computational Art by Ira Greenberg (Apress).
Making Things Talk by Tom Igoe (Make). This book covers Processing and Arduino and provides many examples of communication code.
4.1. Sending Debug Information from Arduino to Your Computer
Problem
You want to send text and data to be displayed on your PC or Mac using the Arduino IDE or the serial terminal program of your choice.
Solution
This sketch prints sequential numbers on the Serial Monitor:
/* * SerialOutput sketch * Print numbers to the serial port */ void setup() { Serial.begin(9600); // send and receive at 9600 baud } int number = 0; void loop() { Serial.print("The number is "); Serial.println(number); // print the number delay(500); // delay half second between numbers number++; // to the next number }
Connect Arduino to your computer just as you did in Chapter 1 and upload this sketch. Click the Serial Monitor icon in the IDE and you should see the output displayed as follows:
The number is 0 The number is 1 The number is 2
Discussion
To display text and numbers from your sketch on a PC or Mac via
a serial link, put the Serial.begin(9600)
statement in setup()
, and
then use Serial.print()
statements to print the text
and values you want to see.
The Arduino Serial Monitor function can display serial data sent from Arduino. To start the Serial Monitor, click the Serial Monitor toolbar icon as shown in Figure 4-2. A new window will open for displaying output from Arduino.
Your sketch must call the Serial.begin()
function before it can use
serial input or output. The function takes a single parameter: the
desired communication speed. You must use the same speed for the
sending side and the receiving side, or you will see gobbledygook (or
nothing at all) on the screen. This example and most of the others in
this book use a speed of 9,600 baud (baud is a
measure of the number of bits transmitted per second).
The 9,600 baud rate is approximately 1,000 characters per second. You
can send at lower or higher rates (the range is 300 to 115,200), but
make sure both sides use the same speed. The Serial Monitor sets the
speed using the baud rate drop down (at the bottom right of the Serial
Monitor window in Figure 4-2). If your output
looks something like this:
`3??f<ÌxÌ▯▯▯ü`³??f<
you should check that the selected baud rate on the serial
monitor on your computer matches the rate set by Serial.begin()
in your sketch.
Note
If your send and receive serial speeds are set correctly but you are still getting unreadable text, check that you have the correct board selected in the IDE Tools→Board menu. There are chip speed variants of some boards, if you have selected the wrong one, change it to the correct one and upload to the board again.
You can display text using the Serial.print()
function. Strings (text within double quotes) will be printed as is
(but without the quotes). For example, the following code:
Serial.print("The number is ");
prints this:
The number is
The values (numbers) that you print depend on the type of
variable; see Recipe 4.2
for more about this. For example, printing an integer will print its
numeric value, so if the variable number
is 1
, the following code:
Serial.println(number);
will print this:
1
In the example sketch, the number printed will be 0
when the loop starts and will increase by
one each time through the loop. The ln
at the end of println
causes the
next print statement to start on a new line.
That should get you started printing text and the decimal value of integers. See Recipe 4.2 for more detail on print formatting options.
You may want to consider a third-party terminal program that has more features than Serial Monitor. Displaying data in text or binary format (or both), displaying control characters, and logging to a file are just a few of the additional capabilities available from the many third-party terminal programs. Here are some that have been recommended by Arduino users:
- CoolTerm
An easy-to-use freeware terminal program for Windows, Mac, and Linux
- CuteCom
- Bray Terminal
- GNU screen
An open source virtual screen management program that supports serial communications; included with Linux and Mac OS X
- moserial
- PuTTY
An open source SSH program for Windows and Linux that supports serial communications
- RealTerm
- ZTerm
In addition, an article in the Arduino wiki explains how to configure Linux to communicate with Arduino using TTY (see http://www.arduino.cc/playground/Interfacing/LinuxTTY).
You can use a liquid crystal display as a serial output device,
although it will be very limited in functionality. Check the
documentation to see how your display handles carriage returns, as
some displays may not automatically advance to a new line after
println
statements.
See Also
The Arduino LiquidCrystal library for text LCDs uses underlying print functionality similar to the Serial library, so you can use many of the suggestions covered in this chapter with that library (see Chapter 11).
4.2. Sending Formatted Text and Numeric Data from Arduino
Problem
You want to send serial data from Arduino displayed as text, decimal values, hexadecimal, or binary.
Solution
You can print data to the serial port in many different formats; here is a sketch that demonstrates all the format options:
/* * SerialFormatting * Print values in various formats to the serial port */ char chrValue = 65; // these are the starting values to print byte byteValue = 65; int intValue = 65; float floatValue = 65.0; void setup() { Serial.begin(9600); } void loop() { Serial.println("chrValue: "); Serial.println(chrValue); Serial.write(chrValue); Serial.println(); Serial.println(chrValue,DEC); Serial.println("byteValue: "); Serial.println(byteValue); Serial.write(byteValue); Serial.println(); Serial.println(byteValue,DEC); Serial.println("intValue: "); Serial.println(intValue); Serial.println(intValue,DEC); Serial.println(intValue,HEX); Serial.println(intValue,OCT); Serial.println(intValue,BIN); Serial.println("floatValue: "); Serial.println(floatValue); delay(1000); // delay a second between numbers chrValue++; // to the next value byteValue++; intValue++; floatValue +=1; }
The output (condensed here onto a few lines) is as follows:
chrValue: A A 65 byteValue: 65 A 65 intValue: 65 65 41 101 1000001 floatValue: 65.00 chrValue: B B 66 byteValue: 66 B 66 intValue: 66 66 42 102 1000010 floatValue: 66.00
Discussion
Printing a text string is simple: Serial.print("hello
world");
sends the text string “hello world” to a device at
the other end of the serial port. If you want your output to print a
new line after the output, use Serial.println()
instead of Serial.print()
.
Printing numeric values can be more complicated. The way that
byte and integer values are printed depends on the type of variable
and an optional formatting parameter. The Arduino language is very
easygoing about how you can refer to the value of different data types
(see Recipe 2.2 for more
on data types). But this flexibility can be confusing, because even
when the numeric values are similar, the compiler considers them to be
separate types with different behaviors. For example, printing a
char
, byte
, and int
of the same value will not necessarily
produce the same output.
Here are some specific examples; all of them create variables that have similar values:
char asciiValue = 'A'; // ASCII A has a value of 65 char chrValue = 65; // an 8 bit signed character, this also is ASCII 'A' byte byteValue = 65; // an 8 bit unsigned character, this also is ASCII 'A' int intValue = 65; // a 16 bit signed integer set to a value of 65 float floatValue = 65.0; // float with a value of 65
Table 4-2 shows what you will see when you print variables using Arduino routines.
Data type | print ( | print ( | write ( | print ( | print ( | print ( |
| | | | | | |
| | | | | | |
| | | | | | |
| Format of | |||||
| | Formatting not supported for floating-point values | ||||
| |
|
Note
The expression Serial.print(val,BYTE);
is no longer
supported in Arduino
1.0.
If your code expects byte variables to behave the same as char
variables (that is, for them to print as ASCII), you will need to
change this to Serial.write(val);
.
The sketch in this recipe uses a separate line of source code for each print statement. This can make complex print statements bulky. For example, to print the following line:
At 5 seconds: speed = 17, distance = 120
you’d typically have to code it like this:
Serial.print("At "); Serial.print(t); Serial.print(" seconds: speed= "); Serial.print(s); Serial.print(", distance= "); Serial.println(d);
That’s a lot of code lines for a single line of output. You could combine them like this:
Serial.print("At "); Serial.print(t); Serial.print(" seconds, speed= "); Serial.print(s); Serial.print(", distance= ");Serial.println(d);
Or you could use the insertion-style capability of the compiler used by Arduino to format your print statements. You can take advantage of some advanced C++ capabilities (streaming insertion syntax and templates) that you can use if you declare a streaming template in your sketch. This is most easily achieved by including the Streaming library developed by Mikal Hart. You can read more about this library and download the code from Mikal’s website.
If you use the Streaming library, the following gives the same output as the lines shown earlier:
Serial << "At " << t << " seconds, speed= " << s << ", distance = " << d << endl;
See Also
Chapter 2 provides more information on data types used by Arduino. The Arduino web reference at http://arduino.cc/en/Reference/HomePage covers the serial commands, and the Arduino web reference at http://www.arduino.cc/playground/Main/StreamingOutput covers streaming (insertion-style) output.
4.3. Receiving Serial Data in Arduino
Problem
You want to receive data on Arduino from a computer or another serial device; for example, to have Arduino react to commands or data sent from your computer.
Solution
It’s easy to receive 8-bit values (chars and bytes), because
the Serial
functions use
8-bit values. This sketch receives a digit (single characters 0
through 9) and blinks the LED on pin 13 at a rate proportional to the
received digit value:
/* * SerialReceive sketch * Blink the LED at a rate proportional to the received digit value */ const int ledPin = 13; // pin the LED is connected to int blinkRate=0; // blink rate stored in this variable void setup() { Serial.begin(9600); // Initialize serial port to send and receive at 9600 baud pinMode(ledPin, OUTPUT); // set this pin as output } void loop() { if ( Serial.available()) // Check to see if at least one character is available { char ch = Serial.read(); if( isDigit(ch) ) // is this an ascii digit between 0 and 9? { blinkRate = (ch - '0'); // ASCII value converted to numeric value blinkRate = blinkRate * 100; // actual rate is 100ms times received digit } } blink(); } // blink the LED with the on and off times determined by blinkRate void blink() { digitalWrite(ledPin,HIGH); delay(blinkRate); // delay depends on blinkrate value digitalWrite(ledPin,LOW); delay(blinkRate); }
Upload the sketch and send messages using the Serial Monitor. Open the Serial Monitor by clicking the Monitor icon (see Recipe 4.1) and type a digit in the text box at the top of the Serial Monitor window. Clicking the Send button will send the character typed into the text box; if you type a digit, you should see the blink rate change.
Discussion
Converting the received ASCII characters to numeric values may not be obvious if
you are not familiar with the way ASCII represents characters. The
following converts the character ch
to its numeric value:
blinkRate = (ch - '0'); // ASCII value converted to numeric value
The ASCII characters ‘0’ through ‘9’ have a value of 48 through
57 (see Appendix G).
Converting ‘1’ to the numeric value one is done by subtracting ‘0’
because ‘1’ has an ASCII value of 49, so 48 (ASCII ‘0’) must be
subtracted to convert this to the number one. For example, if ch
is representing the character 1, its
ASCII value is 49. The expression 49-
'0'
is the same as 49-48
.
This equals 1, which is the numeric value of the character 1.
In other words, the expression (ch -
'0')
is the same as (ch -
48)
; this converts the ASCII value of the variable ch
to a numeric value.
Receiving numbers with more than one digit involves accumulating
characters until a character that is not a valid digit is detected.
The following code uses the same setup()
and blink()
functions as those shown earlier,
but it gets digits until the newline character is received. It uses
the accumulated value to set the blink rate.
Note
The newline character (ASCII value 10) can be appended automatically each time you click Send. The Serial Monitor has a drop-down box at the bottom of the Serial Monitor screen (see Figure 4-1); change the option from “No line ending” to “Newline.”
Change the code as follows:
int value; void loop() { if( Serial.available()) { char ch = Serial.read(); if( isDigit(ch) )// is this an ascii digit between 0 and 9? { value = (value * 10) + (ch - '0'); // yes, accumulate the value } else if (ch == 10) // is the character the newline character? { blinkRate = value; // set blinkrate to the accumulated value Serial.println(blinkRate); value = 0; // reset val to 0 ready for the next sequence of digits } } blink(); }
Enter a value such as 123
into the Monitor text box and click Send, and the blink delay will be
set to 123 milliseconds. Each digit is converted from its ASCII value
to its numeric value. Because the numbers are decimal numbers (base
10), each successive number is multiplied by 10. For example, the
value of the number 234 is 2 * 100 + 3 * 10 + 4. The code to
accomplish that is:
if( isDigit(ch) ) // is this an ascii digit between 0 and 9? { value = (value * 10) + (ch - '0'); // yes, accumulate the value }
If you want to handle negative numbers, your code needs to recognize a leading
minus ('-'
) sign. In this example,
each numeric value must be separated by a character that is not a
digit or minus sign:
int value = 0; int sign = 1; void loop() { if( Serial.available()) { char ch = Serial.read(); if( isDigit(ch) ) // is this an ascii digit between 0 and 9? value = (value * 10) + (ch - '0'); // yes, accumulate the value else if( ch == '-') sign = -1; else // this assumes any char not a digit or minus sign terminates the value { value = value * sign ; // set value to the accumulated value Serial.println(value); value = 0; // reset value to 0 ready for the next sequence of digits sign = 1; } } }
Another approach to converting text strings representing numbers
is to use the C language conversion function called atoi
(for int
variables) or atol
(for long
variables). These obscurely named
functions convert a string into integers or long integers. To use
them you have to receive and store the entire string in a character
array before you can call the conversion function.
This code fragment terminates the incoming digits on any character that is not a digit (or if the buffer is full):
const int MaxChars = 5; // an int string contains up to 5 digits and // is terminated by a 0 to indicate end of string char strValue[MaxChars+1]; // must be big enough for digits and terminating null int index = 0; // the index into the array storing the received digits void loop() { if( Serial.available()) { char ch = Serial.read(); if( index < MaxChars && isDigit(ch) ){ strValue[index++] = ch; // add the ASCII character to the string; } else { // here when buffer full or on the first non digit strValue[index] = 0; // terminate the string with a 0 blinkRate = atoi(strValue); // use atoi to convert the string to an int index = 0; } } blink(); }
strValue
is a numeric string
built up from characters received from the serial port.
Note
See Recipe 2.6 for information about character strings.
atoi
(short for ASCII to
integer) is a function that converts a character string to an integer
(atol
converts to a long
integer).
Arduino 1.0 added the serialEvent
function that you can use to handle incoming serial characters.
If you have code within a serialEvent
function in your sketch, this
will be called once each time through the loop
function. The following sketch performs
the same function as the first sketch in this Recipe but uses serialEvent
to handle the incoming characters:
/* * SerialReceive sketch * Blink the LED at a rate proportional to the received digit value */ const int ledPin = 13; // pin the LED is connected to int blinkRate=0; // blink rate stored in this variable void setup() { Serial.begin(9600); // Initialize serial port to send and receive at 9600 baud pinMode(ledPin, OUTPUT); // set this pin as output } void loop() { blink(); } void serialEvent() { while(Serial.available()) { char ch = Serial.read(); Serial.write(ch); if( isDigit(ch) ) // is this an ascii digit between 0 and 9? { blinkRate = (ch - '0'); // ASCII value converted to numeric value blinkRate = blinkRate * 100; // actual rate is 100mS times received digit } } } // blink the LED with the on and off times determined by blinkRate void blink() { digitalWrite(ledPin,HIGH); delay(blinkRate); // delay depends on blinkrate value digitalWrite(ledPin,LOW); delay(blinkRate); }
Arduino 1.0 also introduced the parseInt
and parseFloat
methods that simplify extracting
numeric values from Serial (it also works with Ethernet and other
objects derived from the Stream
class; see the introduction
to Chapter 15 for more about
stream-parsing with the networking objects).
Serial.parseInt()
and
Serial.parseFloat()
read Serial characters and return their numeric
representation. Nonnumeric characters before the number are ignored
and the number ends with the first character that is not a numeric
digit (or ‘.
’ if using parseFloat
.)
See the discussion of Recipe 4.5 for an example
showing parseInt
used to find and
extract numbers from Serial data.
See Also
A web search for “atoi” or “atol” provides many references to these functions. Also see the Wikipedia reference at http://en.wikipedia.org/wiki/Atoi.
4.4. Sending Multiple Text Fields from Arduino in a Single Message
Problem
You want to send a message that contains more than one piece of information (field). For example, your message may contain values from two or more sensors. You want to use these values in a program such as Processing, running on your PC or Mac.
Solution
The easiest way to do this is to send a text string with all the fields separated by a delimiting (separating) character, such as a comma:
// CommaDelimitedOutput sketch void setup() { Serial.begin(9600); } void loop() { int value1 = 10; // some hardcoded values to send int value2 = 100; int value3 = 1000; Serial.print('H'); // unique header to identify start of message Serial.print(","); Serial.print(value1,DEC); Serial.print(","); Serial.print(value2,DEC); Serial.print(","); Serial.print(value3,DEC); Serial.print(","); // note that a comma is sent after the last field Serial.println(); // send a cr/lf delay(100); }
Here is the Processing sketch that reads this data from the serial port:
// Processing Sketch to read comma delimited serial // expects format: H,1,2,3, import processing.serial.*; Serial myPort; // Create object from Serial class char HEADER = 'H'; // character to identify the start of a message short LF = 10; // ASCII linefeed // WARNING! // If necessary change the definition below to the correct port short portIndex = 1; // select the com port, 0 is the first port void setup() { size(200, 200); println(Serial.list()); println(" Connecting to -> " + Serial.list()[portIndex]); myPort = new Serial(this,Serial.list()[portIndex], 9600); } void draw() { } void serialEvent(Serial p) { String message = myPort.readStringUntil(LF); // read serial data if(message != null) { print(message); String [] data = message.split(","); // Split the comma-separated message if(data[0].charAt(0) == HEADER && data.length > 3) // check validity { for( int i = 1; i < data.length-1; i++) // skip the header & end if line { println("Value " + i + " = " + data[i]); // Print the field values } println(); } } }
Discussion
The Arduino code in this recipe’s Solution will send the
following text string to the serial port (\r
indicates a carriage return and \n
indicates a line feed):
H,10,100,1000,\r\n
You must choose a separating character that will never occur within actual data; if your data consists only of numeric values, a comma is a good choice for a delimiter. You may also want to ensure that the receiving side can determine the start of a message to make sure it has all the data for all the fields. You do this by sending a header character to indicate the start of the message. The header character must also be unique; it should not appear within any of the data fields and it must also be different from the separator character. The example here uses an uppercase H to indicate the start of the message. The message consists of the header, three comma-separated numeric values as ASCII strings, and a carriage return and line feed.
The carriage return and line-feed characters are sent whenever
Arduino prints using the println()
function,
and this is used to help the receiving side know that the full message
string has been received. A comma is sent after the last numerical
value to aid the receiving side in detecting the end of the
value.
The Processing code reads the message as a string and uses the
Java split()
method to
create an array from the comma-separated fields.
Note
In most cases, the first serial port will be the one you want when using a Mac and the last serial port will be the one you want when using Windows. The Processing sketch includes code that shows the ports available and the one currently selected—check that this is the port connected to Arduino.
Using Processing to display sensor values can save hours of debugging time by helping you to visualize the data. The following Processing sketch adds real-time visual display of up to 12 values sent from Arduino. This version displays 8-bit values in a range from –127 to +127 and was created to demonstrate the nunchuck sketch in Recipe 13.2:
/* * ShowSensorData. * * Displays bar graph of CSV sensor data ranging from -127 to 127 * expects format as: "Data,s1,s2,...s12\n" (any number of to 12 sensors is supported) * labels can be sent as follows: "Labels,label1, label2,...label12\n"); */ import processing.serial.*; Serial myPort; // Create object from Serial class String message = null; PFont fontA; // font to display servo pin number int fontSize = 12; int maxNumberOfLabels = 12; int rectMargin = 40; int windowWidth = 600; int windowHeight = rectMargin + (maxNumberOfLabels + 1) * (fontSize *2); int rectWidth = windowWidth - rectMargin*2; int rectHeight = windowHeight - rectMargin; int rectCenter = rectMargin + rectWidth / 2; int origin = rectCenter; int minValue = -127; int maxValue = 127; float scale = float(rectWidth) / (maxValue - minValue); String [] sensorLabels = {"s1", "s2", "s3", "s4", "s5", "s6", "s7", "s8", "s9", "s10", "s11", "s12"}; // this will be changed to the number of labels actually received int labelCount = maxNumberOfLabels; void setup() { size(windowWidth, windowHeight); short portIndex = 1; // select the com port, 0 is the first port String portName = Serial.list()[portIndex]; println(Serial.list()); println(" Connecting to -> " + portName) ; myPort = new Serial(this, portName, 57600); fontA = createFont("Arial.normal", fontSize); textFont(fontA); labelCount = sensorLabels.length; } void drawGrid() { fill(0); text(minValue, xPos(minValue), rectMargin-fontSize); line(xPos(minValue), rectMargin, xPos(minValue), rectHeight + fontSize); text((minValue+maxValue)/2, rectCenter, rectMargin-fontSize); line(rectCenter, rectMargin, rectCenter, rectHeight + fontSize); text(maxValue, xPos(maxValue), rectMargin-fontSize); line( xPos(maxValue), rectMargin, xPos(maxValue), rectHeight + fontSize); for (int i=0; i < labelCount; i++) { text(sensorLabels[i], fontSize, yPos(i)); text(sensorLabels[i], xPos(maxValue) + fontSize, yPos(i)); } } int yPos(int index) { return rectMargin + fontSize + (index * fontSize*2); } int xPos(int value) { return origin + int(scale * value); } void drawBar(int yIndex, int value) { rect(origin, yPos(yIndex)-fontSize, value * scale, fontSize); //draw the value } void draw() { while (myPort.available () > 0) { try { message = myPort.readStringUntil(10); if (message != null) { print(message); String [] data = message.split(","); // Split the CSV message if ( data[0].equals("Labels") ) { // check for label header labelCount = min(data.length-1, maxNumberOfLabels) ; arrayCopy(data, 1, sensorLabels, 0, labelCount ); } else if ( data[0].equals("Data"))// check for data header { background(255); drawGrid(); fill(204); println(data.length); for ( int i=1; i <= labelCount && i < data.length-1; i++) { drawBar(i-1, Integer.parseInt(data[i])); } } } } catch (Exception e) { e.printStackTrace(); // Display whatever error we received } } }
Figure 4-3 shows how
nunchuck accelerometer values (aX,Ay,aZ
) and joystick (jX,Jy
) values are displayed. Bars will
appear when the nunchuck buttons (bC
and bZ
) are pressed.
The range of values and the origin of the graph can be easily changed if desired. For example, to display bars originating at the lefthand axis with values from 0 to 1024, use the following:
int origin = rectMargin; // rectMargin is the left edge of the graphing area int minValue = 0; int maxValue = 1024;
If you don’t have a nunchuck, you can generate values with the following simple sketch that displays analog input values. If you don’t have any sensors to connect, running your fingers along the bottom of the analog pins will produce levels that can be viewed in the Processing sketch. The values range from 0 to 1023, so change the origin and min and max values in the Processing sketch, as described in the previous paragraph:
void setup() { Serial.begin(57600); delay(1000); Serial.println("Labels,A0,A1,A2,A3,A4,A5"); } void loop() { Serial.print("Data,"); for(int i=0; i < 6; i++) { Serial.print( analogRead(i) ); Serial.print(","); } Serial.print('\n'); // newline character delay(100); }
See Also
The Processing website provides more information on installing and using this programming environment. See http://processing.org/.
4.5. Receiving Multiple Text Fields in a Single Message in Arduino
Problem
You want to receive a message that contains more than one field. For example, your message may contain an identifier to indicate a particular device (such as a motor or other actuator) and what value (such as speed) to set it to.
Solution
Arduino does not have the split()
function used in the Processing code
in Recipe 4.4, but
similar functionality can be implemented as shown in this recipe. The
following code receives a message with three numeric fields separated
by commas. It uses the technique described in Recipe 4.4 for receiving
digits, and it adds code to identify comma-separated fields and store the values
into an array:
/* * SerialReceiveMultipleFields sketch * This code expects a message in the format: 12,345,678 * This code requires a newline character to indicate the end of the data * Set the serial monitor to send newline characters */ const int NUMBER_OF_FIELDS = 3; // how many comma separated fields we expect int fieldIndex = 0; // the current field being received int values[NUMBER_OF_FIELDS]; // array holding values for all the fields void setup() { Serial.begin(9600); // Initialize serial port to send and receive at 9600 baud } void loop() { if( Serial.available()) { char ch = Serial.read(); if(ch >= '0' && ch <= '9') // is this an ascii digit between 0 and 9? { // yes, accumulate the value if the fieldIndex is within range // additional fields are not stored if(fieldIndex < NUMBER_OF_FIELDS) { values[fieldIndex] = (values[fieldIndex] * 10) + (ch - '0'); } } else if (ch == ',') // comma is our separator, so move on to the next field { fieldIndex++; // increment field index } else { // any character not a digit or comma ends the acquisition of fields // in this example it's the newline character sent by the Serial Monitor // print each of the stored fields for(int i=0; i < min(NUMBER_OF_FIELDS, fieldIndex+1); i++) { Serial.println(values[i]); values[i] = 0; // set the values to zero, ready for the next message } fieldIndex = 0; // ready to start over } } }
Discussion
This sketch accumulates values (as explained in Recipe 4.3), but here each value is added to an array (which must be large enough to hold all the fields) when a comma is received. A character other than a digit or comma (such as the newline character; see Recipe 4.3) triggers the printing of all the values that have been stored in the array. You can either type a nondigit, noncomma character before pressing Send, or set the “No line ending” menu at the bottom right of the Serial Monitor to some other option.
Arduino 1.0 introduced the parseInt
method that makes it easy to
extract information from serial and web streams. Here is an example of
how to use this capability (Chapter 15
has more examples of stream parsing).
The following sketch uses parseInt
to
provide similar functionality to the previous sketch:
// Receive multiple numeric fields using Arduino 1.0 Stream parsing const int NUMBER_OF_FIELDS = 3; // how many comma-separated fields we expect int fieldIndex = 0; // the current field being received int values[NUMBER_OF_FIELDS]; // array holding values for all the fields void setup() { Serial.begin(9600); // Initialize serial port to send and receive at 9600 baud } void loop() { if( Serial.available()) { for(fieldIndex = 0; fieldIndex < 3; fieldIndex ++) { values[fieldIndex] = Serial.parseInt(); // get a numeric value } Serial.print( fieldIndex); Serial.println(" fields received:"); for(int i=0; i < fieldIndex; i++) { Serial.println(values[i]); } fieldIndex = 0; // ready to start over } }
The stream-parsing functions will time out waiting for a
character; the default is one second. If no digits have been received
and parseInt
times out then it will
return 0. You can change the timeout by calling Stream.setTimeout(timeoutPeriod)
. The
timeout parameter is a long integer indicating the number of
milliseconds, so the timeout range is from 1 millisecond to
2,147,483,647 milliseconds.
Stream.setTimeout(2147483647);
will change
the timeout interval to just under 25 days.
Here is a summary of the methods supported by Arduino 1.0 Stream parsing (not all are used in the preceding example):
boolean find(char *target);
Reads from the stream until the given target is found. It returns
true
if the target string is found. A return offalse
means the data has not been found anywhere in the stream and that there is no more data available. Note that Stream parsing takes a single pass through the stream; there is no way to go back to try to find or get something else (see thefindUntil
method).boolean findUntil(char *target, char *terminate);
Similar to the
find
method, but the search will stop if the terminate string is found. Returnstrue
only if the target is found. This is useful to stop a search on a keyword or terminator. For example:finder.findUntil("target", "\n");
will try to seek to the string
"value"
, but will stop at a newline character so that your sketch can do something else if the target is not found.long parseInt();
Returns the first valid (long) integer value. Leading characters that are not digits or a minus sign are skipped. The integer is terminated by the first nondigit character following the number. If no digits are found, the function returns
0
.long parseInt(char skipChar);
Same as
parseInt
, but the givenskipChar
within the numeric value is ignored. This can be helpful when parsing a single numeric value that uses a comma between blocks of digits in large numbers, but bear in mind that text values formatted with commas cannot be parsed as a comma-separated string (for example, 32,767 would be parsed as 32767).float parseFloat();
size_t readBytes(char *buffer, size_t length);
Puts the incoming characters into the given buffer until timeout or length characters have been read. Returns the number of characters placed in the buffer.
size_t readBytesUntil(char terminator,char *buf,size_t length);
Puts the incoming characters into the given buffer until the
terminator
character is detected. Strings longer than the givenlength
are truncated to fit. The function returns the number of characters placed in the buffer.
See Also
Chapter 15 provides more examples of Stream parsing used to find and extract data from a stream.
4.6. Sending Binary Data from Arduino
Problem
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.
Solution
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):
/* * SendBinary sketch * Sends a header followed by two random integer values as binary data. */ int intValue; // an integer value (16 bits) void setup() { Serial.begin(9600); } void loop() { Serial.print('H'); // send a header character // send a random integer intValue = random(599); // generate a random number between 0 and 599 // send the two bytes that comprise an integer Serial.write(lowByte(intValue)); // send the low byte Serial.write(highByte(intValue)); // send the high byte // send another random integer intValue = random(599); // generate a random number between 0 and 599 // send the two bytes that comprise an integer Serial.write(lowByte(intValue)); // send the low byte Serial.write(highByte(intValue)); // send the high byte delay(1000); }
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.write(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.write(lowByte(intValue), BYTE); Serial.write(highByte(intValue), BYTE);
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.write(lowByte(intVal)); Serial.writet(highByte(intVal));
Then you send the higher 16-bit integer value:
intValue = longValue >> 16; // get the value of the higher 16 bits Serial.write(lowByte(intVal)); Serial.writet(highByte(intVal));
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.write(lowByte(value)); // send the low byte Serial.write(highByte(value)); // 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.write(structurePointer[i]); } 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
orlong
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 (theint
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.
See Also
Chapter 2 provides more information on the variable types used in Arduino sketches.
See Recipe 3.15 for
more on handling high and low bytes. Also, check the Arduino
references for lowByte
at http://www.arduino.cc/en/Reference/LowByte
and highByte
at http://www.arduino.cc/en/Reference/HighByte.
The Arduino compiler packs structures on byte boundaries; see the documentation for the compiler you use on your computer to set it for the same packing. If you are not clear on how to do this, you may want to avoid using structures to send data.
For more on flow control, see http://en.wikipedia.org/wiki/Flow_control.
4.7. Receiving Binary Data from Arduino on a Computer
Problem
You want to respond to binary data sent from Arduino in a programming language such as Processing. For example, you want to respond to Arduino messages sent in Recipe 4.6.
Solution
This recipe’s Solution depends on the programming environment you use on your PC or Mac. If you don’t already have a favorite programming tool and want one that is easy to learn and works well with Arduino, Processing is an excellent choice.
Here are the two lines of Processing code to read a byte, taken
from the Processing SimpleRead
example
(see this chapter’s introduction):
if ( myPort.available() > 0) { // If data is available, val = myPort.read(); // read it and store it in val
As you can see, this is very similar to the Arduino code you saw in earlier recipes.
The following is a Processing sketch that sets the size of a rectangle proportional to the integer values received from the Arduino sketch in Recipe 4.6:
/* * ReceiveBinaryData_P * * portIndex must be set to the port connected to the Arduino */ import processing.serial.*; Serial myPort; // Create object from Serial class short portIndex = 1; // select the com port, 0 is the first port char HEADER = 'H'; int value1, value2; // Data received from the serial port void setup() { size(600, 600); // Open whatever serial port is connected to Arduino. String portName = Serial.list()[portIndex]; println(Serial.list()); println(" Connecting to -> " + Serial.list()[portIndex]); myPort = new Serial(this, portName, 9600); } void draw() { // read the header and two binary *(16 bit) integers: if ( myPort.available() >= 5) // If at least 5 bytes are available, { if( myPort.read() == HEADER) // is this the header { value1 = myPort.read(); // read the least significant byte value1 = myPort.read() * 256 + value1; // add the most significant byte value2 = myPort.read(); // read the least significant byte value2 = myPort.read() * 256 + value2; // add the most significant byte println("Message received: " + value1 + "," + value2); } } background(255); // Set background to white fill(0); // set fill to black // draw rectangle with coordinates based on the integers received from Arduino rect(0, 0, value1,value2); }
Discussion
The Processing language influenced Arduino, and the two are
intentionally similar. The setup
function in Processing is used to handle one-time initialization,
just like in Arduino. Processing
has a display window, and setup
sets its size to 600 × 600 pixels with the call to size(600,600)
.
The line String portName =
Serial.list()[portIndex];
selects the serial port—in
Processing, all available serial ports are contained in the Serial.list
object and this example uses the value of a variable called
portIndex
. println(Serial.list())
prints all the
available ports, and the line myPort = new
Serial(this, portName, 9600);
opens the port selected as
portName
. Ensure that you set
portIndex
to the serial port that
is connected to your Arduino (Arduino is usually the first port on a
Mac; on Windows, it’s usually the last port if Arduino is the most
recent serial device installed).
The draw
function in Processing works like loop
in Arduino; it is called repeatedly.
The code in draw
checks if data is
available on the serial port; if so, bytes are read and converted to
the integer value represented by the bytes. A rectangle is drawn based
on the integer values received.
See Also
You can read more about Processing on the Processing website.
4.8. Sending Binary Values from Processing to Arduino
Problem
You want to send binary bytes, integers, or long values from Processing to Arduino. For example, you want to send a message consisting of a message identifier “tag” and two 16-bit values.
Solution
// Processing Sketch /* SendingBinaryToArduino * Language: Processing */ import processing.serial.*; Serial myPort; // Create object from Serial class public static final char HEADER = 'H'; public static final char MOUSE_TAG = 'M'; void setup() { size(512, 512); String portName = Serial.list()[1]; myPort = new Serial(this, portName, 9600); } void draw(){ } void serialEvent(Serial p) { // handle incoming serial data String inString = myPort.readStringUntil('\n'); if(inString != null) { print( inString ); // echo text string from Arduino } } void mousePressed() { sendMessage(MOUSE_TAG, mouseX, mouseY); } void sendMessage(char tag, int x, int y){ // send the given index and value to the serial port myPort.write(HEADER); myPort.write(tag); myPort.write((char)(x / 256)); // msb myPort.write(x & 0xff); //lsb myPort.write((char)(y / 256)); // msb myPort.write(y & 0xff); //lsb }
When the mouse is clicked in the Processing window, sendMessage
will be called with the 8-bit tag indicating that this
is a mouse message and the two 16-bit mouse x
and y
coordinates. The sendMessage
function sends the 16-bit x
and
y
values as two bytes, with the
most significant byte first.
Here is the Arduino code to receive these messages and echo the results back to Processing:
// BinaryDataFromProcessing // These defines must mirror the sending program: const char HEADER = 'H'; const char MOUSE_TAG = 'M'; const int TOTAL_BYTES = 6 ; // the total bytes in a message void setup() { Serial.begin(9600); } void loop(){ if ( Serial.available() >= TOTAL_BYTES) { if( Serial.read() == HEADER) { char tag = Serial.read(); if(tag == MOUSE_TAG) { int x = Serial.read() * 256; x = x + Serial.read(); int y = Serial.read() * 256; y = y + Serial.read(); Serial.print("Received mouse msg, x = "); Serial.print(x); Serial.print(", y = "); Serial.println(y); } else { Serial.print("got message with unknown tag "); Serial.write(tag); } } } }
Discussion
The Processing code sends a header byte to indicate that a valid
message follows. This is needed so Arduino can synchronize if it
starts up in the middle of a message or if the serial connection can
lose data, such as with a wireless link. The tag provides an
additional check for message validity and it enables any other message
types you may want to send to be handled individually. In this
example, the function is called with three parameters: a tag and the
16-bit x
and y
mouse coordinates.
The Arduino code checks that at least MESSAGE_BYTES
have been received, ensuring
that the message is not processed until all the required data is
available. After the header and tag are checked, the 16-bit values are
read as two bytes, with the first multiplied by 256 to restore the
most significant byte to its original value.
Warning
The sending side and receiving side must use the same message
size for binary messages to be handled correctly. If you want to
increase or decrease the number of bytes to send, change TOTAL_BYTES
in the Arduino code to
match.
4.9. Sending the Value of Multiple Arduino Pins
Problem
You want to send groups of binary bytes, integers, or long values from Arduino. For example, you may want to send the values of the digital and analog pins to Processing.
Solution
This recipe sends a header followed by an integer containing the bit values of digital pins 2 to 13. This is followed by six integers containing the values of analog pins 0 through 5. Chapter 5 has many recipes that set values on the analog and digital pins that you can use to test this sketch:
/* * SendBinaryFields * Sends digital and analog pin values as binary data */ const char HEADER = 'H'; // a single character header to indicate // the start of a message void setup() { Serial.begin(9600); for(int i=2; i <= 13; i++) { pinMode(i, INPUT); // set pins 2 through 13 to inputs digitalWrite(i, HIGH); // turn on pull-ups } } void loop() { Serial.write(HEADER); // send the header // put the bit values of the pins into an integer int values = 0; int bit = 0; for(int i=2; i <= 13; i++) { bitWrite(values, bit, digitalRead(i)); // set the bit to 0 or 1 depending // on value of the given pin bit = bit + 1; // increment to the next bit } sendBinary(values); // send the integer for(int i=0; i < 6; i++) { values = analogRead(i); sendBinary(values); // send the integer } delay(1000); //send every second } // function to send the given integer value to the serial port void sendBinary( int value) { // send the two bytes that comprise an integer Serial.write(lowByte(value)); // send the low byte Serial.write(highByte(value)); // send the high byte }
Discussion
The code sends a header (the character H
), followed by an integer holding the
digital pin values using the bitRead
function to
set a single bit in the integer to correspond to the value of the pin
(see Chapter 3). It then sends
six integers containing the values read from the six analog ports (see
Chapter 5 for more
information). All the integer values are sent using sendBinary
,
introduced in Recipe 4.6. The
message is 15 bytes long—1 byte for the header, 2 bytes for the
digital pin values, and 12 bytes for the six analog integers. The code
for the digital and analog inputs is explained in Chapter 5.
Assuming analog pins have values of 0 on pin 0, 100 on pin 1, and 200 on pin 2 through 500 on pin 5, and digital pins 2 through 7 are high and 8 through 13 are low, this is the decimal value of each byte that gets sent:
72 // the character 'H' - this is the header // two bytes in low high order containing bits representing pins 2-13 63 // binary 00111111 : this indicates that pins 2-7 are high 0 // this indicates that 8-13 are low // two bytes for each pin representing the analog value 0 // pin 0 has an integer value of 0 so this is sent as two bytes 0 100 // pin 1 has a value of 100, sent as a byte of 100 and a byte of 0 0 ... // pin 5 has a value of 500 244 // the remainder when dividing 500 by 256 1 // the number of times 500 can be divided by 256
This Processing code reads the message and prints the values to the Processing console:
// Processing Sketch /* * ReceiveMultipleFieldsBinary_P * * portIndex must be set to the port connected to the Arduino */ import processing.serial.*; Serial myPort; // Create object from Serial class short portIndex = 1; // select the com port, 0 is the first port char HEADER = 'H'; void setup() { size(200, 200); // Open whatever serial port is connected to Arduino. String portName = Serial.list()[portIndex]; println(Serial.list()); println(" Connecting to -> " + Serial.list()[portIndex]); myPort = new Serial(this, portName, 9600); } void draw() { int val; if ( myPort.available() >= 15) // wait for the entire message to arrive { if( myPort.read() == HEADER) // is this the header { println("Message received:"); // header found // get the integer containing the bit values val = readArduinoInt(); // print the value of each bit for(int pin=2, bit=1; pin <= 13; pin++){ print("digital pin " + pin + " = " ); int isSet = (val & bit); if( isSet == 0) { println("0"); } else{ println("1"); } bit = bit * 2; //shift the bit to the next higher binary place } println(); // print the six analog values for(int i=0; i < 6; i ++){ val = readArduinoInt(); println("analog port " + i + "= " + val); } println("----"); } } } // return integer value from bytes received from serial port (in low,high order) int readArduinoInt() { int val; // Data received from the serial port val = myPort.read(); // read the least significant byte val = myPort.read() * 256 + val; // add the most significant byte return val; }
The Processing code waits for 15 characters to arrive. If the
first character is the header, it then calls the function named
readArduinoInt
to read two bytes and transform them back into an
integer by doing the complementary mathematical operation that was
performed by Arduino to get the individual bits representing the
digital pins. The six integers are then representing the analog
values.
See Also
To send Arduino values back to the computer or drive the pins from the computer (without making decisions on the board), consider using Firmata (http://www.firmata.org). The Firmata library and example sketches (File→Examples→Firmata) are included in the Arduino software distribution, and a library is available to use in Processing. You load the Firmata code onto Arduino, control whether pins are inputs or outputs from the computer, and then set or read those pins.
4.10. How to Move the Mouse Cursor on a PC or Mac
Problem
You want Arduino to interact with an application on your computer by moving the mouse cursor. Perhaps you want to move the mouse position in response to Arduino information. For example, suppose you have connected a Wii nunchuck (see Recipe 13.2) to your Arduino and you want your hand movements to control the position of the mouse cursor in a program running on a PC.
Solution
You can send serial commands that specify the mouse cursor position to a program running on the target computer. Here is a sketch that moves the mouse cursor based on the position of two potentiometers:
// SerialMouse sketch const int buttonPin = 2; //LOW on digital pin enables mouse const int potXPin = 4; // analog pins for pots const int potYPin = 5; void setup() { Serial.begin(9600); pinMode(buttonPin, INPUT); digitalWrite(buttonPin, HIGH); // turn on pull-ups } void loop() { int x = (512 - analogRead(potXPin)) / 4; // range is -127 to +127 int y = (512 - analogRead(potYPin)) / 4; Serial.print("Data,"); Serial.print(x,DEC); Serial.print(","); Serial.print(y,DEC); Serial.print(","); if(digitalRead(buttonPin) == LOW) Serial.print(1); // send 1 when button pressed else Serial.print(0); Serial.println(","); delay(50); // send position 20 times a second }
Figure 4-4 illustrates the wiring for two potentiometers (see Chapter 5 for more details). The switch is included so you can enable and disable Arduino mouse control by closing and opening the contacts.
The Processing code is based on the code shown in Recipe 4.4, with code added to control a mouse:
// Processing Sketch /* * ArduinoMouse.pde (Processing sketch) */ /* WARNING: This sketch takes over your mouse Press escape to close running sketch */ import java.awt.AWTException; import java.awt.Robot; import java.awt.Dimension; import processing.serial.*; Serial myPort; // Create object from Serial class arduMouse myMouse; // create arduino controlled mouse public static final short LF = 10; // ASCII linefeed public static final short portIndex = 1; // select the com port, // 0 is the first port int posX, posY, btn; // data from msg fields will be stored here void setup() { size(200, 200); println(Serial.list()); println(" Connecting to -> " + Serial.list()[portIndex]); myPort = new Serial(this,Serial.list()[portIndex], 9600); myMouse = new arduMouse(); btn = 0; // turn mouse off until requested by Arduino message } void draw() { if ( btn != 0) myMouse.move(posX, posY); // move mouse to received x and y position } void serialEvent(Serial p) { String message = myPort.readStringUntil(LF); // read serial data if(message != null) { //print(message); String [] data = message.split(","); // Split the comma-separated message if ( data[0].equals("Data"))// check for data header { if( data.length > 3 ) { try { posX = Integer.parseInt(data[1]); posY = Integer.parseInt(data[2]); btn = Integer.parseInt(data[3]); } catch (Throwable t) { println("."); // parse error print(message); } } } } } class arduMouse { Robot myRobot; // create object from Robot class; static final short rate = 4; // multiplier to adjust movement rate int centerX, centerY; arduMouse() { try { myRobot = new Robot(); } catch (AWTException e) { e.printStackTrace(); } Dimension screen = java.awt.Toolkit.getDefaultToolkit().getScreenSize(); centerY = (int)screen.getHeight() / 2 ; centerX = (int)screen.getWidth() / 2; } // method to move mouse from center of screen by given offset void move(int offsetX, int offsetY) { myRobot.mouseMove(centerX + (rate* offsetX), centerY - (rate * offsetY)); } }
The Processing code splits the message containing the x
and y
coordinates and sends them to the mouseMove
method of
the Java Robot
class. In this
example, the Robot class has a wrapper named arduMouse
that provides a move
method that scales to your screen
size.
Discussion
This technique for controlling applications running on your computer is easy to implement and should work with any operating system that can run the Processing application. If you need to invert the direction of movement on the X or Y axis you can do this by changing the sign of the axis in the Processing sketch as follows:
posX = -Integer.parseInt(data[1]); // minus sign inverts axis
Note
Some platforms require special privileges or extensions to access low-level input control. If you can’t get control of the mouse, check the documentation for your operating system.
Warning
A runaway Robot
object has
the ability to remove your control over the mouse and keyboard if
used in an endless loop. In this recipe a value is sent to
Processing to enable and disable control based on the level of
digital pin 2.
Note
Boards using the ATmeg32U4 controller chip can directly emulate a USB mouse. The Arduino Leonardo board and the PJRC Teensy come with examples showing how to emulate a USB mouse.
- Leonardo board:
http://blog.makezine.com/archive/2011/09/arduino-leonardo-opens-doors-to-product-development.html
- Teensy USB mouse example:
See Also
Go to http://java.sun.com/j2se/1.3/docs/api/java/awt/Robot.html
for more information on the Java Robot
class.
An article on using the Robot
class is available at http://www.developer.com/java/other/article.php/10936_2212401_1.
If you prefer to use a Windows programming language, the
low-level Windows API function to insert keyboard and mouse events
into the input stream is called Send
Input
. You can visit http://msdn.microsoft.com/en-us/library/ms646310(VS.85).aspx
for more information.
Recipe 4.11 that follows shows how to apply this technique to control the Google Earth application.
4.11. Controlling Google Earth Using Arduino
Problem
You want to control movement in an application such as Google Earth using sensors attached to Arduino. For example, you want sensors to detect hand movements to act as the control stick for the flight simulator in Google Earth. The sensors could use a joystick (see Recipe 6.17) or a Wii nunchuck (see Recipe 13.2).
Solution
Google Earth lets you “fly” anywhere on Earth to view satellite imagery, maps, terrain, and 3-D buildings (see Figure 4-5). It contains a flight simulator that can be controlled by a mouse, and this recipe uses techniques described in Recipe 4.10 combined with a sensor connected to Arduino to provide the joystick input.
The Arduino code sends the horizontal and vertical positions determined by reading an input device such as a joystick. There are many input options, for example you can use the circuit from Recipe 4.10 (this works well if you can find an old analog joystick that uses potentiometers that you can re-purpose).
Discussion
Google Earth is a free download; you can get it from the Google website, http://earth.google.com/download-earth.html. Download and run the version for your operating system to install it on your computer. Start Google Earth, and from the Tools menu, select Enter Flight Simulator. Select an aircraft (the SR22 is easier to fly than the F16) and an airport. The Joystick support should be left unchecked—you will be using the Arduino-controlled mouse to fly the aircraft. Click the Start Flight button (if the aircraft is already flying when you start, you can press the space bar to pause the simulator so that you can get the Processing sketch running).
Upload the Arduino sketch from Recipe 4.10 and run the Processing sketch from that recipe on your computer. Make Google Earth the Active window by clicking in the Google Earth window. Activate Arduino mouse control by connecting digital pin 2 to Gnd.
You are now ready to fly. Press Page Up on your keyboard a few times to increase the throttle (and then press the space bar on your keyboard if you had paused the simulator). When the SR22 reaches an air speed that is a little over 100 knots, you can “pull back” on the stick and fly. Information explaining the simulator controls can be found in the Google Help menu.
When you are finished flying you can relinquish Arduino mouse control back to your computer mouse by disconnecting pin 2 from Gnd.
Here is another variation that sends messages to the Processing sketch. This one combines the Wii nunchuck code from Recipe 13.2 with a library discussed in Recipe 16.5. The connections are as shown in Recipe 13.2:
/* * WiichuckSerial * * Uses Nunchuck Library discussed in Recipe 16.5 * sends comma-separated values for data * Label string separated by commas can be used by receiving program * to identify fields */ #include <Wire.h> #include "Nunchuck.h" // values to add to the sensor to get zero reading when centered int offsetX, offsetY, offsetZ; #include <Wire.h> #include "Nunchuck.h" void setup() { Serial.begin(57600); nunchuckSetPowerpins(); nunchuckInit(); // send the initialization handshake nunchuckRead(); // ignore the first time delay(50); } void loop() { nunchuckRead(); delay(6); boolean btnC = nunchuckGetValue(wii_btnC); boolean btnZ = nunchuckGetValue(wii_btnZ); if(btnC) { offsetX = 127 - nunchuckGetValue(wii_accelX) ; offsetY = 127 - nunchuckGetValue(wii_accelY) ; } Serial.print("Data,"); printAccel(nunchuckGetValue(wii_accelX),offsetX) ; printAccel(nunchuckGetValue(wii_accelY),offsetY) ; printButton(nunchuckGetValue(wii_btnZ)); Serial.println(); } void printAccel(int value, int offset) { Serial.print(adjReading(value, 127-50, 127+50, offset)); Serial.print(","); } void printJoy(int value) { Serial.print(adjReading(value,0, 255, 0)); Serial.print(","); } void printButton(int value) { if( value != 0) value = 127; Serial.print(value,DEC); Serial.print(","); } int adjReading( int value, int min, int max, int offset) { value = constrain(value + offset, min, max); value = map(value, min, max, -127, 127); return value; }
Note
These sketches use a Serial speed of 57600 to minimize latency. If you want to view the Arduino output on the Serial Monitor, you will need to change its baud rate accordingly. You will need to change the Serial Monitor’s baud rate back to 9600 to view the output of most other sketches in this book. If you don’t have a Wii nunchuck, you can use the Arduino sketch from Recipe 4.10, but you will need to change that sketch’s baud rate to 57600 and upload it to the Arduino.
You can send nunchuck joystick values instead of the
accelerometer values by replacing the two lines that begin printAccel
with the following lines:
printJoy(nunchuckGetValue(wii_joyX)); |
printJoy(nunchuckGetValue(wii_joyY)); |
You can use the Processing sketch from Recipe 4.10, but this enhanced version displays the control position in the Processing window and activates the flight simulator using the nunchuck ‘z’ button:
/** * GoogleEarth_FS.pde * * Drives Google Flight Sim using CSV sensor data */ import java.awt.AWTException; import java.awt.Robot; import java.awt.event.InputEvent; import java.awt.Dimension; import processing.serial.*; Serial myPort; // Create object from Serial class arduMouse myMouse; String message = null; int maxDataFields = 7; // 3 axis accel, 2 buttons, 2 joystick axis boolean isStarted = false; int accelX, accelY, btnZ; // data from msg fields will be stored here void setup() { size(260, 260); PFont fontA = createFont("Arial.normal", 12); textFont(fontA); short portIndex = 1; // select the com port, 0 is the first port String portName = Serial.list()[portIndex]; println(Serial.list()); println(" Connecting to -> " + portName) ; myPort = new Serial(this, portName, 57600); myMouse = new arduMouse(); fill(0); text("Start Google FS in the center of your screen", 5, 40); text("Center the mouse pointer in Google earth", 10, 60); text("Press and release Nunchuck Z button to play", 10, 80); text("Press Z button again to pause mouse", 20, 100); } void draw() { processMessages(); if (isStarted == false) { if ( btnZ != 0) { println("Release button to start"); do{ processMessages();} while(btnZ != 0); myMouse.mousePress(InputEvent.BUTTON1_MASK); // start the SIM isStarted = true; } } else { if ( btnZ != 0) { isStarted = false; background(204); text("Release Z button to play", 20, 100); print("Stopped, "); } else{ myMouse.move(accelX, accelY); // move mouse to received x and y position fill(0); stroke(255, 0, 0); background(#8CE7FC); ellipse(127+accelX, 127+accelY, 4, 4); } } } void processMessages() { while (myPort.available () > 0) { message = myPort.readStringUntil(10); if (message != null) { //print(message); String [] data = message.split(","); // Split the CSV message if ( data[0].equals("Data"))// check for data header { try { accelX = Integer.parseInt(data[1]); accelY = Integer.parseInt(data[2]); btnZ = Integer.parseInt(data[3]); } catch (Throwable t) { println("."); // parse error } } } } } class arduMouse { Robot myRobot; // create object from Robot class; static final short rate = 4; // pixels to move int centerX, centerY; arduMouse() { try { myRobot = new Robot(); } catch (AWTException e) { e.printStackTrace(); } Dimension screen = java.awt.Toolkit.getDefaultToolkit().getScreenSize(); centerY = (int)screen.getHeight() / 2 ; centerX = (int)screen.getWidth() / 2; } // method to move mouse from center of screen by given offset void move(int offsetX, int offsetY) { myRobot.mouseMove(centerX + (rate* offsetX), centerY - (rate * offsetY)); } // method to simulate pressing mouse button void mousePress( int button) { myRobot.mousePress(button) ; } }
See Also
The Google Earth website contains the downloadable code and instructions needed to get this going on your computer: http://earth.google.com/.
4.12. Logging Arduino Data to a File on Your Computer
Problem
You want to create a file containing information received over the serial port from Arduino. For example, you want to save the values of the digital and analog pins at regular intervals to a logfile.
Solution
We covered sending information from Arduino to your computer in previous recipes. This solution uses the same Arduino code explained in Recipe 4.9. The Processing sketch that handles file logging is based on the Processing sketch also described in that recipe.
This Processing sketch creates a file (using the current date and time as the filename) in the same directory as the Processing sketch. Messages received from Arduino are added to the file. Pressing any key saves the file and exits the program:
/* * ReceiveMultipleFieldsBinaryToFile_P * * portIndex must be set to the port connected to the Arduino * based on ReceiveMultipleFieldsBinary, this version saves data to file * Press any key to stop logging and save file */ import processing.serial.*; PrintWriter output; DateFormat fnameFormat= new SimpleDateFormat("yyMMdd_HHmm"); DateFormat timeFormat = new SimpleDateFormat("hh:mm:ss"); String fileName; Serial myPort; // Create object from Serial class short portIndex = 0; // select the com port, 0 is the first port char HEADER = 'H'; void setup() { size(200, 200); // Open whatever serial port is connected to Arduino. String portName = Serial.list()[portIndex]; println(Serial.list()); println(" Connecting to -> " + Serial.list()[portIndex]); myPort = new Serial(this, portName, 9600); Date now = new Date(); fileName = fnameFormat.format(now); output = createWriter(fileName + ".txt"); // save the file in the sketch folder } void draw() { int val; String time; if ( myPort.available() >= 15) // wait for the entire message to arrive { if( myPort.read() == HEADER) // is this the header { String timeString = timeFormat.format(new Date()); println("Message received at " + timeString); output.println(timeString); // header found // get the integer containing the bit values val = readArduinoInt(); // print the value of each bit for(int pin=2, bit=1; pin <= 13; pin++){ print("digital pin " + pin + " = " ); output.print("digital pin " + pin + " = " ); int isSet = (val & bit); if( isSet == 0){ println("0"); output.println("0"); } else { println("1"); output.println("0"); } bit = bit * 2; // shift the bit } // print the six analog values for(int i=0; i < 6; i ++){ val = readArduinoInt(); println("analog port " + i + "= " + val); output.println("analog port " + i + "= " + val); } println("----"); output.println("----"); } } } void keyPressed() { output.flush(); // Writes the remaining data to the file output.close(); // Finishes the file exit(); // Stops the program } // return the integer value from bytes received on the serial port // (in low,high order) int readArduinoInt() { int val; // Data received from the serial port val = myPort.read(); // read the least significant byte val = myPort.read() * 256 + val; // add the most significant byte return val; }
Don’t forget that you need to set portIndex
to the serial port connected to
Arduino.
Discussion
The base name for the logfile is formed using the DateFormat
function
in Processing:
DateFormat fnameFormat= new SimpleDateFormat("yyMMdd_HHmm");
The full filename is created with code that adds a directory and file extension:
output = createWriter(fileName + ".txt");
The file will be created in the same directory as the Processing
sketch (the sketch needs to be saved at least once to ensure that the
directory exists). To find this directory, choose Sketch→Show Sketch Folder in Processing. createWriter
is the Processing function that opens the file; this
creates an object (a unit of runtime functionality) called output
that handles the actual file output.
The text written to the file is the same as what is printed to the
console in Recipe 4.9,
but you can format the file contents as required by using the standard
string-handling capabilities of Processing. For example, the following
variation on the draw
routine
produces a comma-separated file that can be read by a spreadsheet or
database. The rest of the Processing sketch can be the same, although
you may want to change the extension from .txt to .csv:
void draw() { int val; String time; if ( myPort.available() >= 15) // wait for the entire message to arrive { if( myPort.read() == HEADER) // is this the header { String timeString = timeFormat.format(new Date()); output.print(timeString); val = readArduinoInt(); // read but don't output the digital values // output the six analog values delimited by a comma for(int i=0; i < 6; i ++){ val = readArduinoInt(); output.print("," + val); } output.println(); } } }
See Also
For more on createWriter
,
see http://processing.org/reference/createWriter_.html.
4.13. Sending Data to Two Serial Devices at the Same Time
Problem
You want to send data to a serial device such as a serial LCD, but you are already using the built-in serial port to communicate with your computer.
Solution
On a Mega this is not a problem, as it has four hardware serial ports; just create two serial objects and use one for the LCD and one for the computer:
void setup() { // initialize two serial ports on a Mega Serial.begin(9600); // primary serial port Serial1.begin(9600); // Mega can also use Serial1 through Serial3 }
On a standard Arduino board (such as the Uno or Duemilanove) that only has one hardware serial port, you will need to create an emulated or “soft” serial port.
You can use the distributed SoftwareSerial library for sending data to multiple devices.
Note
Arduino releases from 1.0 use an improved SoftwareSerial library based on Mikal Hart’s NewSoftSerial Library. If you are using an Arduino release prior to 1.0, you can download NewSoftSerial from http://arduiniana.org/libraries/newsoftserial.
Select two available digital pins, one each for transmit and receive, and connect your serial device to them. It is convenient to use the hardware serial port for communication with the computer because this has a USB adapter on the board. Connect the device’s transmit line to the receive pin and the receive line to the transmit pin. In Figure 4-6, we have selected pin 2 as the receive pin and pin 3 as the transmit pin.
In your sketch, create a SoftwareSerial
object and tell it which pins
you chose as your emulated serial port. In this example, we’re
creating an object named serial_lcd
, which we instruct to use pins 2 and 3:
/* * SoftwareSerialOutput sketch * Output data to a software serial port */ #include <SoftwareSerial.h> const int rxpin = 2; // pin used to receive (not used in this version) const int txpin = 3; // pin used to send to LCD SoftwareSerial serial_lcd(rxpin, txpin); // new serial port on pins 2 and 3 void setup() { Serial.begin(9600); // 9600 baud for the built-in serial port serial_lcd.begin(9600); //initialize the software serial port also for 9600 } int number = 0; void loop() { serial_lcd.print("The number is "); // send text to the LCD serial_lcd.println(number); // print the number on the LCD Serial.print("The number is "); Serial.println(number); // print the number on the PC console delay(500); // delay half second between numbers number++; // to the next number }
Note
If you are using Arduino versions prior to 1.0, download the NewSoftSerial library and replace references to SoftwareSerial with NewSoftSerial:
// NewSoftSerial version #include <NewSoftSerial.h> const int rxpin = 2; // pin used to receive from LCD const int txpin = 3; // pin used to send to LCD NewSoftSerial serial_lcd(rxpin, txpin); // new serial port on pins 2 + 3
This sketch assumes that a serial LCD has been connected to pin 3 as shown in Figure 4-6, and that a serial console is connected to the built-in port. The loop will repeatedly display the same message on each:
The number is 0 The number is 1 ...
Discussion
Every Arduino microcontroller contains at least one built-in serial port. This special piece of hardware is responsible for generating the series of precisely timed pulses its partner device sees as data and for interpreting the similar stream that it receives in return. Although the Mega has four such ports, most Arduino flavors have only one. For projects that require connections to two or more serial devices, you’ll need a software library that emulates the additional ports. A “software serial” library effectively turns an arbitrary pair of digital I/O pins into a new serial port.
To build your software serial port, you select a pair of pins that will act as the port’s transmit and receive lines in much the same way that pins 1 and 0 are controlled by Arduino’s built-in port. In Figure 4-6, pins 3 and 2 are shown, but any available digital pins can be used. It’s wise to avoid using 0 and 1, because these are already being driven by the built-in port.
The syntax for writing to the soft port is identical to that for
the hardware port. In the example sketch, data is sent to both the
“real” and emulated ports using print()
and println()
:
serial_lcd.print("The number is "); // send text to the LCD serial_lcd.println(number); // send the number on the LCD Serial.print("The number is "); // send text to the hardware port Serial.println(number); // to output on Arduino Serial Monitor
If you are using a unidirectional serial
device—that is, one that only sends or receives—you can conserve resources by
specifying a nonexistent pin number in the SoftwareSerial
constructor for the line you
don’t need. For example, a serial LCD is fundamentally an output-only
device. If you don’t expect (or want) to receive data from it, you can
tell SoftwareSerial using this syntax:
#include <SoftwareSerial.h> ... const int no_such_pin = 255; const int txpin = 3; SoftwareSerial serial_lcd(no_such_pin, txpin); // TX-only on pin 3
In this case, we would only physically connect a single pin (3) to the serial LCD’s “input” or “RX” line.
See Also
SoftwareSerial for Arduino 1.0 and later releases is based on NewSoftSerial. You can read more about NewSoftSerial on Mikal Hart’s website
4.14. Receiving Serial Data from Two Devices at the Same Time
Problem
You want to receive data from a serial device such as a serial GPS, but you are already using the built-in serial port to communicate with your computer.
Solution
This problem is similar to the one in Recipe 4.13, and indeed the solution is much the same. If your Arduino’s serial port is connected to the console and you want to attach a second serial device, you must create an emulated port using a software serial library. In this case, we will be receiving data from the emulated port instead of writing to it, but the basic solution is very similar.
Note
See the previous recipe regarding the NewSoftSerial library if you are using an Arduino release prior to 1.0.
Select two pins to use as your transmit and receive lines.
Connect your GPS as shown in Figure 4-7. Rx (receive) is not used in this example, so you can ignore the Rx connection to pin 3 if your GPS does not have a receive pin.
As you did in Recipe 4.13, create a SoftwareSerial
object in your sketch and tell it which pins to control. In the
following example, we define a soft serial port called serial_gps
, using pins 2 and 3 for receive and transmit,
respectively:
/* * SoftwareSerialInput sketch * Read data from a software serial port */ #include <SoftwareSerial.h> const int rxpin = 2; // pin used to receive from GPS const int txpin = 3; // pin used to send to GPS SoftwareSerial serial_gps(rxpin, txpin); // new serial port on pins 2 and 3 void setup() { Serial.begin(9600); // 9600 baud for the built-in serial port serial_gps.begin(4800); // initialize the port, most GPS devices // use 4800 baud } void loop() { if (serial_gps.available() > 0) // any character arrived yet? { char c = serial_gps.read(); // if so, read it from the GPS Serial.write(c); // and echo it to the serial console } }
If you are using Arduino versions prior to 1.0, download the NewSoftSerial library and replace references to SoftwareSerial with NewSoftSerial:
// NewSoftSerial version #include <NewSoftSerial.h> const int rxpin = 2; // pin used to receive from GPS const int txpin = 3; // pin used to send to GPS NewSoftSerial serial_gps(rxpin, txpin); // new serial port on pins 2 and 3
This short sketch simply forwards all incoming data from the GPS to the Arduino Serial Monitor. If the GPS is functioning and your wiring is correct, you should see GPS data displayed on the Serial Monitor.
Discussion
You initialize an emulated SoftwareSerial port by providing pin numbers for transmit and receive. The following code will set up the port to receive on pin 2 and send on pin 3:
const int rxpin = 2; // pin used to receive from GPS const int txpin = 3; // pin used to send to GPS SoftwareSerial serial_gps(rxpin, txpin); // new serial port on pins 2 and 3
The txpin
is not used in this
example and can be set to 255 to free up pin 3, as explained in the
previous recipe.
The syntax for reading an emulated port is very similar to that
for reading from a built-in port. First check to make sure a character
has arrived from the GPS with available()
, and then read it with
read()
.
It’s important to remember that software serial ports consume time and resources. An emulated serial port must do everything that a hardware port does, using the same processor your sketch is trying to do “real work” with. Whenever a new character arrives, the processor must interrupt whatever it is doing to handle it. This can be time-consuming. At 4,800 baud, for example, it takes the Arduino about two milliseconds to process a single character. While two milliseconds may not sound like much, consider that if your peer device—say, the GPS unit shown earlier—transmits 200 to 250 characters per second, your sketch is spending 40 to 50 percent of its time trying to keep up with the serial input. This leaves very little time to actually process all that data. The lesson is that if you have two serial devices, when possible connect the one with the higher bandwidth consumption to the built-in (hardware) port. If you must connect a high-bandwidth device to a software serial port, make sure the rest of your sketch’s loop is very efficient.
Receiving data from multiple SoftwareSerial ports
With the SoftwareSerial library included with Arduino 1.0, it is possible to create multiple “soft” serial ports in the same sketch. This is a useful way to control, say, several XBee radios or serial displays in the same project. The caveat is that at any given time, only one of these ports can actively receive data. Reliable communication on a software port requires the processor’s undivided attention. That’s why SoftwareSerial can only actively communicate with one port at a given time.
It is possible to receive on two different SoftwareSerial ports in the same sketch. You just have to take some care that you aren’t trying to receive from both simultaneously. There are many successful designs which, say, monitor a serial GPS device for a while, then later accept input from an XBee. The key is to alternate slowly between them, switching to a second device only when a transmission from the first is complete.
For example, in the sketch that follows, imagine a remote XBee module sending commands. The sketch listens to the command stream through the “xbee” port until it receives the signal to begin gathering data from a GPS module attached to a second SoftwareSerial port. The sketch then monitors the GPS for 10 seconds—long enough to establish a “fix”—before returning to the XBee.
In a system with multiple “soft” ports, only one is actively
receiving data. By default, the “active” port is the one for which
begin()
has been called most
recently. However, you can change which port is active by calling
its listen()
method. listen()
instructs the SoftwareSerial
system to stop receiving data on one port and begin listening for
data on another.
The following code fragment illustrates how you might design a sketch to read first from one port and then another:
/* * MultiRX sketch * Receive data from two software serial ports */ #include <SoftwareSerial.h> const int rxpin1 = 2; const int txpin1 = 3; const int rxpin2 = 4; const int txpin2 = 5; SoftwareSerial gps(rxpin1, txpin1); // gps device connected to pins 2 and 3 SoftwareSerial xbee(rxpin2, txpin2); // xbee device connected to pins 4 and 5 void setup() { xbee.begin(9600); gps.begin(4800); xbee.listen(); // Set “xbee” to be the active device } void loop() { if (xbee.available() > 0) // xbee is active. Any characters available? { if (xbee.read() == 'y') // if xbee received a 'y' character? { gps.listen(); // now start listening to the gps device unsigned long start = millis(); // begin listening to the GPS while (start + 100000 > millis()) // listen for 10 seconds { if (gps.available() > 0) // now gps device is active { char c = gps.read(); // *** process gps data here } } xbee.listen(); // After 10 seconds, go back to listening to the xbee } } }
This sketch is designed to treat the XBee radio as the active
port until it receives a y
character, at which point the
GPS becomes the active listening device. After processing GPS data
for 10 seconds, the sketch
resumes listening on the XBee port. Data that arrives on an inactive
port is simply discarded.
Note that the “active port” restriction only applies to multiple soft ports. If your design really must receive data from more than one serial device simultaneously, consider attaching one of these to the built-in hardware port. Alternatively, it is perfectly possible to add additional hardware ports to your projects using external chips, devices called UARTs.
4.15. Setting Up Processing on Your Computer to Send and Receive Serial Data
Solution
You can get the Processing application from the Downloads section of the Processing website, http://processing.org. Files are available for each major operating system. Download the appropriate one for your operating system and unzip the file to somewhere that you normally store applications. On a Windows computer, this might be a location like C:\Program Files\Processing\. On a Mac, it might be something like /Applications/Processing.app.
If you installed Processing on the same computer that is running the Arduino IDE, the only other thing you need to do is identify the serial port in Processing. The following Processing sketch prints the serial ports available:
/** * GettingStarted * * A sketch to list the available serial ports * and display characters received */ import processing.serial.*; Serial myPort; // Create object from Serial class int portIndex = 0; // set this to the port connected to Arduino int val; // Data received from the serial port void setup() { size(200, 200); println(Serial.list()); // print the list of all the ports println(" Connecting to -> " + Serial.list()[portIndex]); myPort = new Serial(this, Serial.list()[portIndex], 9600); } void draw() { if ( myPort.available() > 0) // If data is available, { val = myPort.read(); // read it and store it in val print(val); } }
If you are running Processing on a computer that is not running the Arduino development environment, you may need to install the Arduino USB drivers (Chapter 1 describes how to do this).
Set the variable portIndex
to
match the port used by Arduino. You can see the port numbers printed
in the Processing text window (the area below the source code, not the separate Display window; see
http://processing.org/reference/environment).
Recipe 1.4 describes
how to find out which serial port your Arduino board is using.
Get Arduino Cookbook, 2nd Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.