Studio 6: Streaming Data from Arduino to Java
Introduction
Click here to access the repository for this studio.
Objectives
Up to this point everything we have sent from the Arduino to the desktop PC has been displayed on the PC screen using the Arduino IDE’s Serial Monitor. No more! The topic of today’s studio is receiving data in a Java program.
By the end of this studio you should know:
- how to receive data from a serial port in a Java program, and
- (optionally) how to send and receive data types that are larger than a single byte.
Authoring Java tools
Simple printing
The first task for Arduino to Java communication is to have a Java program receive data sent by the Arduino. We will start simple.
You may want to refer to the JavaDoc for the jSSC
library, particularly the SerialPort
class methods.
- You will be modifying the
SerialComm
class that you created in the previous studio. Open theSerialComm
class and copy the relevant parts into the corresponding file in the repository for this studio.
Make sure to give it a static void main()
method if it doesn’t already have one. This is where your Java program for this week will be placed.
This program will receive data from a serial port (your Arduino) and print it out. In order to do that, you will use additional features of the SerialPort
class that you were introduced to in the last studio.
-
You should author two new methods (in addition to
main
) in yourSerialComm
class:available()
andreadByte()
.The
available()
method should return aboolean
that istrue
if there is at least one byte available to be read from the serial port. ThegetInputBufferBytesCount()
method ofSerialPort
is helpful here.The
readByte()
method should read a single byte from the serial port and return abyte
that contains the value read. ThereadBytes(int byteCount)
method ofSerialPort
is helpful here, but watch out for its return type (an array of bytes,byte[]
, rather than a singlebyte
).In the
main
routine, first create anew
SerialComm
object.Next, continuously check if a byte is
available()
and read it if there is. Have your program infinitely do this. -
Now, take the data you read and find a way to print it to the Java console. Note the return type of
readByte()
. Though in our case it represents achar
, it is not. This is a design choice. You will have to cast it to print it properly.Using any of your previous Arduino sketches that send data to the Serial Monitor (e.g.,
heartbeat.ino
might be a good choice), exercise your Java program and ensure that you get the same text output on the Java console that you previously were getting when using the Serial Monitor built into the Arduino IDE. -
Experiment by starting the Java program first, then the Arduino. You can do this by downloading the Arduino program, running it and testing via the Serial Monitor, closing the Serial Monitor, and pulling the plug on the USB cable to the Arduino. Start up the Java program, then plug in the Arduino, which will cause it to start again (the sketch is retained in memory that is not lost when the power is removed).
After you’ve tried that, change the startup order. What happens when? Why?
A debugging version
Recall that in the previous studio there was a debug option that could be used to examine outgoing bytes from Java to the Arduino. We wish to replicate this behavior for bytes going from the Arduino to Java.
Reimplement the readByte()
method you made earlier so that it conditionally (based upon the value of the debug
instance variable) prints incoming bytes to the console in hex. When debug
is true
, you will need to convert to hex before you print: for every byte that comes from the Arduino, the debugging output should be 6 characters: the string “[0x”, 2 characters that represent the data byte in hexadecimal, and the character ‘]’ (e.g., if the byte in the data stream is 5f
, the string sent to the console should be “[0x5f]
” (or “[0x5F]
” if you prefer). You can use String.format("%02x", byte value)
to format a byte
as a two-character String
in hexadecimal.
It should continue to return the value of the byte that was input from the serial port.
You should now be able to view the data in hex as it goes through the readByte()
method and be able to view the “true” ASCII value in the Java console.
Show off your working code to an instructor or TA (using the debug mode to show the byte values).
Optional additional stuff
If you have gotten this far and it’s near the end of studio time, consider today a success and jump ahead to Finishing up. If you have time, there are some additional things you can play with that will also be useful in the upcoming assignments.
-
All of the data we’ve been transmitting so far has been ASCII characters. While it is very possible to communicate entirely using ASCII characters, it is very inefficient. In other words, you could convert your
3
to the ASCII character for3
(0x33
) and convert it back upon receipt, but that just seems silly. However, that’s what happens when you useSerial.print()
, or, in general, any “print” method: the program converts whatever you want to print from it’s internal representation into a printable representation, like an ASCII character, then sends that to an output stream.If you actually want to send a
3
, not0x33
, over a stream, you useSerial.write()
. From the documentation forSerial.write()
, it’s clear that it can only send one byte at a time: it can only physically send numbers between0x0
and0xff
. Anything more than that is truncated:301
(0x012d
) is sent as45
(0x002d
).Make a new Arduino sketch (named
datatypetest.ino
inCSE132-studio6/datatypetest
) that firstSerial.write()
s thenSerial.print()
s the numbers0
through360
in sequence. What are the results when you use your Java program to view the output? What about Serial Monitor? Use a list of ASCII characters (available on the internet or via the commandman 7 ascii
in terminal) to understand the output if it’s confusing, and remember that an Arduino sketch needs toSerial.begin(9600)
before it can use any of the serial commands. -
The Arduino is unable to
Serial.write()
numbers larger than255
. This is a serious limitation for those of you who enjoy counting.Let’s investigate how to send larger (more than one byte) data types. An integer (an
int
) in Arduino C is a two-byte type. By bitshifting appropriately, you can send each byte of this number sequentially. For example, you can send the number0x4d21
as0x4d
and0x21
. Alter this Arduino sketch to send the two bytes in this way, first the most significant byte (0x4d
in the example), then the least significant byte.Alter
SerialComm
’smain
routine to combine the two received bytes and store the result in a Java integer. You will have to bitshift. Remember, Java’s integers are 4 bytes, so there is plenty of space for a two byte number to fit1.Output the received integers on the console, and compare that with what you see from
readByte
’s debug output. -
Extend the Arduino C code to send an Arduino
long int
of 4 bytes (the return value frommillis()
would be a good choice). On the Java side, pull the same trick you did for the two-byte values above, just extend it to 4 bytes. How well does that work?
Finish up
- Commit your code and verify in your web browser that it is all there.
- Check out with a TA.
Changes to repo structure:
communication/
SerialComm.java
datatypetest/
datatypetest.ino
Key Concepts
- Serial communication
- Sending bytes from Arduino to Java (
Serial.write
)
- Sending bytes from Arduino to Java (
- Java Input
- Reading serial bytes
- OPTIONAL
Serial.print()
vsSerial.write()
- conversion and communication efficiency
- Multi-byte communication
- byte significance i.e. most significant byte
- sequencing
-
This size difference actually has a large impact when dealing with negative numbers, which, as in all systems, are stored in two’s complement. Since a number’s negativity is entirely dependent on its first bit, naively sending the Arduino
int
bytes to Java will not correctly send a negative number.The solution is sign extension. Bitshifting right in Java (and on signed values in C) automatically copies the highest bit in all the extended places (
0xff00 >> 16 == 0xffff
,0x0d00 >> 8 == 0x00d0
). This preserves the value of the number, though that exact proof is left as an excercise for the reader. ↩