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.

  1. You will be modifying the SerialComm class that you created in last week’s studio. Open up the code you wrote last week and copy it into 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 will start.

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 last week.

  1. You should author two new methods (in addition to main) in your SerialComm class: available() and readByte().

    The available() method should return a boolean that is true if there is at least one byte available to be read from the serial port. The getInputBufferBytesCount() method of SerialPort is helpful here.

    The readByte() method should read a single byte from the serial port and return a byte that contains the value read. The readBytes(int byteCount) method of SerialPort is helpful here, but watch out for its return type (an array of bytes, byte[], rather than a single byte).

    In the main routine, first create a new SerialComm object.

    Next, continuously check if a byte is available() and read it if there is. Have your program infinitely do this.

  2. 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 a char, 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.

  3. 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.

  1. 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 for 3 (0x33) and convert it back upon receipt, but that just seems silly. However, that’s what happens when you use Serial.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, not 0x33, over a stream, you use Serial.write(). From the documentation for Serial.write(), it’s clear that it can only send one byte at a time: it can only physically send numbers between 0x0 and 0xff. Anything more than that is truncated: 301 (0x012d) is sent as 45 (0x002d).

    Make a new Arduino (in Arduino/studio6/datatypetest) sketch that first Serial.write()s then Serial.print()s the numbers 0 through 360 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 command man 7 ascii in terminal) to understand the output if it’s confusing, and remember that an Arduino sketch needs to Serial.begin(9600) before it can use any of the serial commands.

  2. The Arduino is unable to Serial.write() numbers larger than 255. 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 number 0x4d21 as 0x4d and 0x21. 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’s main 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.

  3. Extend the Arduino C code to send an Arduino long int of 4 bytes (the return value from millis() 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

  1. Commit your code and verify in your web browser that it is all there.
  2. Check out with a TA.

Changes to repo structure:

  • studio6/
    • datatypetest/datatypetest.ino
    • SerialComm.java

Key Concepts

  • Serial communication
    • Sending bytes from Arduino to Java (Serial.write)
  • Java Input
    • Reading serial bytes
  • OPTIONAL
    • Serial.print vs Serial.write
      • conversion and communication efficiency
    • Multi-byte communication
      • byte significance i.e. most significant byte
      • sequencing
  1. 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.