Working with Inter-integrated Circuits by Using Java Embedded and a Raspberry Pi

Overview

Purpose

This tutorial covers how to create Java Embedded applications that read and write to an inter-integrated circuit (I2C) on a Raspberry PI by using Java ME Embedded 8 Early Access (EA).

Time to Complete

Approximately 1 hour

Introduction

Intelligent devices are becoming an ever more important and ubiquitous part of our everyday lives. Mobile phones represented the first wave of smaller personal computers. And now, as the price of electronics and processing power continues to fall, there is an intersection between sensors and other electromechanical devices and computers that live on the edge of the Internet: close to the source of the data, processing the data locally and sending just what is required to other computers to consume. This wave of machine-to-machine (M2M) technology, or more broadly, the Internet of Things (IoT), is rapidly shaping the future of computing. Oracle Java Platform, Micro Edition (Java ME) provides Java developers with a direct path to this new market space by using their existing knowledge and skills.

I2C (pronounced "eye-squared see") devices are perhaps the most widely available digital circuits. The advantage of I2C is the simplicity of its design. I2C devices communicate by using a two-line control bus, with one line for serial data (SDA) and the second line for serial clock (SCL). The bus protocol is designed so that individual devices have a specific address. A master controller sets up the communication with an individual component on the bus by sending out a start request on the SDA line, followed by the address of the device. If the device with the address is ready, it responds with an acknowledge request. Data is then sent on the SDA line, using the SCL line to control the timing of each bit of data. When the communication with a single device is complete, the master sends a stop request. This simple protocol makes it possible to have multiple I2C devices on a single two-line bus.

Scenario

In this tutorial, you create a Java ME Embedded 8 EA application that communicates with an I2C device designed to sense temperature and barometric pressure. You use a Bosch BMP085 digital pressure sensor device, which was designed for a broad set of use cases, including embedded devices that require low-voltage and ultra-low-power consumption.

Hardware and Software Requirements

The following is a list of hardware and software requirements:

Prerequisites

Before starting this tutorial, you should:

  • Have completed the Oracle By Example tutorial titled "Configuring the Raspberry Pi as an Oracle Java Embedded Platform."
  • Have completed the Oracle By Example tutorial titled "Working with GPIO By Using Java Embedded and a Raspberry Pi."
  • Purchase the hardware required for the tutorial.
  • Solder wires or a header to the BMP085 breakout board. See this tutorial for more information.

Creating a Java ME Project That Uses the Bosch BMP085 I2C Device

In this section, you use NetBeans to develop Java ME application that uses the Bosch BMP085 Digital Pressure Sensor and the I2CDevice API.

Creating a NetBeans Java ME Embedded Project

  1. Select File > New Project.

    NetBeans
  2. Select Java ME from Categories and Java ME 8 Embedded Application from Projects and click Next.

    New Project
  3. Enter BMP085Example as the project name, clear Create Default Package and IMlet Class, and click Next.

    New Embedded Application
  4. Select EmbeddedExternalDevice1 as the device type and click Finish.

    New Embedded Application

Adding Classes to the BMP085Example Project

In this section, you will add three classes to the project: BMP085IMlet, BMP085Device and BMP085Mode.

  1. Right-click Source Packages and select New > Java Package.

    NetBeans Project view
  2. Enter com.example as the package name and click Finish.

    New Java Package dialog
  3. Right-click the com.example package and select New > MIDlet.

    NetBeans Projects tab
  4. Enter BMP085IMlet as the MIDlet name and click Finish.

    New MIDlet dialog
  5. Right-click com.example and select New > Java Class.

    NetBeans Projects tab
  6. Enter BMP085Device as the class name and click Finish.

    New Java Class dialog
  7. Right-click com.example and select New > Java Class.

    NetBeans Projects tab
  8. Enter BMP085Mode as the class name and click Finish.

    New Java Class dialog box

Implementing the BMP085Device Class

The BMP085Device class reads the temperature and pressure directly from the sensor. The process of reading the temperature and pressure actually requires five steps:

  • Read 11 two-byte words (calibration data) from a set of electrically erasable programmable read-only memory (EEPROM) registers.
  • Write a command to a control register on the device to initiate temperature measurement.
  • Read the uncompensated temperature as a two-byte word and use the calibration constants to determine the true temperature.
  • Write a command to a control register on the device to initiate pressure measurement.
  • Read the uncompensated pressure as a three-byte word and use the temperature and calibration constants to determine the true pressure.

Note: The code for this method is provided in the zip file for this tutorial.

  1. Add the following class fields:

    
        // EEPROM registers - these represent calibration data
        private short AC1;
        private short AC2;
        private short AC3;
        private int AC4;
        private int AC5;
        private int AC6;
        private short B1;
        private short B2;
        private short MB;
        private short MC;
        private short MD;
        private static final int CALIB_BYTES = 22;
    
        // Uncompensated temperature
        private int UT;
    
        // Uncompensated pressure
        private int UP;
    
        // Variables common between temperature and pressure calculations
        private int B5;
    
        // EEPROM address data
        private static final int EEPROM_start = 0xAA;
        private static final int EEPROM_end = 0xBF;
    
        // Device Information
        private static final int i2cBus = 1;                        // Raspberry Pi's I2C bus
        private static final int address = 0x77;                    // Device address
        private static final int serialClock = 3400000;             // 3.4MHz Max clock
        private static final int addressSizeBits = 7;               // Device address size in bits
    
        // Conversion Timing data - pressure conversion delays
        // are defined in the BMP085Mode enum
        private static final int tempConvTime = 5;                  // Max delay time of 4.5 ms
    
        // Temperature and Pressure Control Register Data
        private static final byte controlRegister = (byte) 0xF4;    // Control register address
        private static final byte tempAddr = (byte) 0xF6;           // Temperature read address
        private static final byte pressAddr = (byte) 0xF6;          // Pressure read address
        private static final byte getTempCmd = (byte) 0x2E;         // Read temperature command
        private static final byte getPressCmd = (byte) 0x34;        // Read pressure command
    
        // Address byte length
        private static final int subAddressSize = 1;                // Size of each address (in bytes)
    
        // Device object
        private I2CDevice bmp085;
    
  2. Add a public initialize method that returns void, takes no arguments, and throws IOException.

    
        public void initialize() throws IOException {
            I2CDeviceConfig config = new I2CDeviceConfig(i2cBus, address, addressSizeBits, serialClock);
            bmp085 = (I2CDevice) PeripheralManager.open(config);
            readCalibrationData();
        }
    

    The IMlet invokes this method to initialize the BMP085 device on the I2C bus.

  3. Add a private readCalibrationData method that returns void, takes no arguments, and throws IOException.

    
        private void readCalibrationData() throws IOException {
            // Read all of the calibration data into a byte array
            ByteBuffer calibData = ByteBuffer.allocateDirect(CALIB_BYTES);
            int result = bmp085.read(EEPROM_start, subAddressSize, calibData);
            if (result < CALIB_BYTES) {
                System.out.format("Error: %n bytes read/n", result);
                return;
            }
            // Read each of the pairs of data as a signed short
            calibData.rewind();
            AC1 = calibData.getShort();
            AC2 = calibData.getShort();
            AC3 = calibData.getShort();
    
            // Unsigned short values
            byte[] data = new byte[2];
            calibData.get(data);
            AC4 = (((data[0] << 8) & 0xFF00) + (data[1] & 0xFF));
            calibData.get(data);
            AC5 = (((data[0] << 8) & 0xFF00) + (data[1] & 0xFF));
            calibData.get(data);
            AC6 = (((data[0] << 8) & 0xFF00) + (data[1] & 0xFF));
    
            // Signed sort values
            B1 = calibData.getShort();
            B2 = calibData.getShort();
            MB = calibData.getShort();
            MC = calibData.getShort();
            MD = calibData.getShort();
        }
    

    The initialize method invokes this method.

  4. Add a private getTemperature method that returns a float, takes no arguments, and throws IOException.

    
        private float getTemperature() throws IOException {
            // Write the read temperature command to the command register
            ByteBuffer command = ByteBuffer.allocateDirect(subAddressSize).put(getTempCmd);
            command.rewind();
            bmp085.write(controlRegister, subAddressSize, command);
    
            // Delay before reading the temperature
            try {
                Thread.sleep(tempConvTime);
            } catch (InterruptedException ex) {
            }
    
            ByteBuffer uncompTemp = ByteBuffer.allocateDirect(2);
            int result = bmp085.read(tempAddr, subAddressSize, uncompTemp);
            if (result < 2) {
                System.out.format("Error: %n bytes read/n", result);
                return 0;
            }
    
            // Get the uncompensated temperature as an unsigned two byte word
            uncompTemp.rewind();
            byte[] data = new byte[2];
            uncompTemp.get(data);
            UT = ((data[0] << 8) & 0xFF00) + (data[1] & 0xFF);
    
            // Calculate the actual temperature
            int X1 = ((UT - AC6) * AC5) >> 15;
            int X2 = (MC << 11) / (X1 + MD);
            B5 = X1 + X2;
            float celsius = (float) ((B5 + 8) >> 4) / 10;
    
            return celsius;
        }
    

    This method writes the read temperature command to a control register on the device to start the temperature conversion process. After the conversion delay, the uncompensated temperature can be read. Using the math provided by the BMP085 data sheet, you can use the calibration data to calculate the true temperature in degrees Celsius.

  5. Add a private getPressure method that returns a float, takes an instance of a BMP085Mode object, and throws an IOException.

    
        private float getPressure(BMP085Mode mode) throws IOException {
            // The pressure command is calculated by the enum
            // Write the read pressure command to the command register
            ByteBuffer command = ByteBuffer.allocateDirect(subAddressSize).put(mode.getCommand());
            command.rewind();
            bmp085.write(controlRegister, subAddressSize, command);
    
            // Delay before reading the pressure - use the value determined by the oversampling setting (mode)
            try {
                Thread.sleep(mode.getDelay());
            } catch (InterruptedException ex) {
            }
    
            // Read the uncompensated pressure value
            ByteBuffer uncompPress = ByteBuffer.allocateDirect(3);
            int result = bmp085.read(pressAddr, subAddressSize, uncompPress);
            if (result < 3) {
                System.out.format("Error: %n bytes read/n", result);
                return 0;
            }
    
            // Get the uncompensated pressure as a three byte word
            uncompPress.rewind();
            byte[] data = new byte[3];
            uncompPress.get(data);
            UP = ((((data[0] << 16) & 0xFF0000) + ((data[1] << 8) & 0xFF00) + (data[2] & 0xFF)) >> (8 - mode.getOSS()));
    
            // Calculate the true pressure
            int B6 = B5 - 4000;
            int X1 = (B2 * (B6 * B6) >> 12) >> 11;
            int X2 = AC2 * B6 >> 11;
            int X3 = X1 + X2;
            int B3 = ((((AC1 * 4) + X3) << mode.getOSS()) + 2) / 4;
            X1 = AC3 * B6 >> 13;
            X2 = (B1 * ((B6 * B6) >> 12)) >> 16;
            X3 = ((X1 + X2) + 2) >> 2;
            int B4 = (AC4 * (X3 + 32768)) >> 15;
            int B7 = (UP - B3) * (50000 >> mode.getOSS());
    
            int Pa;
            if (B7 < 0x80000000) {
                Pa = (B7 * 2) / B4;
            } else {
                Pa = (B7 / B4) * 2;
            }
    
            X1 = (Pa >> 8) * (Pa >> 8);
            X1 = (X1 * 3038) >> 16;
            X2 = (-7357 * Pa) >> 16;
    
            Pa += ((X1 + X2 + 3791) >> 4);
    
            return (float) (Pa) / 100;
        }
    

    This method is similar to the getTemperature method. However, four possible modes can be used to read pressure, and each mode alters the command sent to the control register and the conversion delay time. The modes are defined in the BMP085Mode enum class. The pressure value returned by this method is in hector pascal units (hPa).

    Note: The complex math in this method is due to the way that Bosch guarantees the accuracy of the device. Each device is calibrated at the factory, and the calibration data is used to determine the true temperature and pressure.

  6. Add a public getTemperaturePressure method that returns a float array, takes an instance of the BMP085Mode enum class as a parameter, and throws an IOException.

        public float[] getTemperaturePressure(BMP085Mode mode) throws IOException {
            float[] result = new float[2];
            result[0] = getTemperature();
            result[1] = getPressure(mode);
            return result;
        }
    

    This method invokes the getTemperature and getPressure methods and returns the results of those calls in a float array.

  7. Add a celsiusToFarhenheit public static method that returns a float array and takes a float parameter.

        public static float celsiusToFahrenheit(float temp) {
            return (float) ((temp * 1.8) + 32);
        }
    

    This method converts the temperature value in Celsius to Fahrenheit.

  8. Add a close public method that returns void, takes no parameters, and throws IOException.

    
        public void close() throws IOException {
            bmp085.close();
        }
    

    This method is called when the resource is no longer needed.

  9. Add a pascalToInchesMercury public static method that returns a float and takes a float parameter.

        public static float pascalToInchesMercury(float pressure) {
            return (float) (pressure * 0.0296);
        }
    

    This method converts the pressure value in hectopascal units to inches mercury .

  10. Press Ctrl + Shift + I to fix any missing import statements.

  11. Press Ctrl + S to save the file.

Implementing the BMP085Mode Enum Class

Note: The code for this method is provided in the zip file for this tutorial.

  1. Modify the type of the class from class to enum.

    
    public enum BMP085Mode {
    
    
  2. Add the following enum constant definitions to the enum class.

    
        // Relationship between Oversampling Setting and conversion delay (in ms) for each Oversampling Setting constant
        // Ultra low power:        4.5 ms minimum conversion delay
        // Standard:               7.5 ms 
        // High Resolution:       13.5 ms
        // Ultra high Resolution: 25.5 ms
        ULTRA_LOW_POWER(0, 5), STANDARD(1, 8), HIGH_RESOLUTION(2, 14), ULTRA_HIGH_RESOLUTION(3, 26);
    
    
  3. Add the following class fields.

    
        private final int oss;                                      // Oversample setting value
        private final int delay;                                    // Minimum conversion time in ms
        private static final byte getPressCmd = (byte) 0x34;        // Read pressure command
        private final byte cmd;                                     // Command byte to read pressure
    
    
  4. Add a constructor to the enum to store the constant values defined by the enum types.

    
        BMP085Mode(int oss, int delay) {
            this.oss = oss;
            this.delay = delay;
            this.cmd = (byte)(getPressCmd + ((oss << 6) & 0xC0));
        }
    
    
  5. Add getter methods for the delay, command, and oversampling setting (oss) fields.

    
        // Return the conversion delay (in ms) associated with this oversampling setting
        public int getDelay() {
            return delay;
        }
        
        // Return the command to the control register for this oversampling setting
        public byte getCommand() {
            return cmd;
        }
        
        // Return this oversampling setting
        public int getOSS() {
            return oss;
        }
    
  6. Fix any missing import statements and save the file.

Implementing the BMP085IMlet Class

Note: The code for this method is provided in the zip file for this tutorial.

  1. Add the following class fields:

    
        private BMP085Device bmp085Device;
        private Timer task;
    
  2. Implement the startApp method.

    
        @Override
        public void startApp() {
            try {
                bmp085Device = new BMP085Device();
                bmp085Device.initialize();
                // Start the task and run it every five seconds
                task = new Timer();
                task.schedule(report, 0, 5000);
    
            } catch (IOException ex) {
                System.out.println("IOException: " + ex);
            }
        }
    

    This method creates an instance of BMP085Device, and then calls the initialize method on the instance. The method creates an instance of a Timer, and schedules the task report to run every 5 seconds (5000 milliseconds).

  3. Implement the destroyApp method.

    
        @Override
        public void destroyApp(boolean unconditional) {
            // Stop the timer task
            task.cancel();
            // Close the resource
            try {
                bmp085Device.close();
            } catch (IOException ex) {
                System.out.println(ex.getMessage());
            }
        }
    

    This method is called when the IMlet is being destroyed by the Application Management System (AMS).

  4. Remove the pauseApp method from the code generated by NetBeans.

    In Java ME 8, Java ME Embedded Profile (MEEP 8) is the next version of the Information Module Profile - Next Generation (IMP-NG) profile. In the new profile, the pauseApp method has been deprecated. IMlets will only support the startApp and destroyApp methods. Java ME 8 IMlets are never paused, and therefore this method may be removed from the Midlet class.

  5. Add an instance of TimerTask as an anonymous inner class.

    
        private TimerTask report = new TimerTask() {
    
            @Override
            public void run() {
                float celsius = 0, fahrenheit = 0, hectorPascal = 0, inchesMercury = 0;
                try {
                    float[] result = bmp085Device.getTemperaturePressure(BMP085Mode.STANDARD);
                    celsius = result[0];
                    fahrenheit = BMP085Device.celsiusToFahrenheit(celsius);
                    hectorPascal = result[1];
                    inchesMercury = BMP085Device.pascalToInchesMercury(hectorPascal);
                } catch (IOException ex) {
                    System.out.println("IOException: " + ex);
                }
                System.out.format("Temperature: %.2f C, %.2f F\n", celsius, fahrenheit);
                System.out.format("Pressure: %.2f hPa, %.2f inHg\n\n", hectorPascal, inchesMercury);
            }
    
        };
    

    This method is invoked every 5 seconds. It invokes the getTemperaturePressure method to read the current temperature and barometric pressure from the sensor. It also invokes the two static helper methods to convert the temperature in Celsius to Fahrenheit and the pressure in hectopascal to inches mercury. The results are written to the console (which will appear in the PuTTY window.)

  6. Fix any missing import statements and save the file.

Creating the Application Circuit by Using the Breadboard

In this section, you lay out the application circuit by using a Bosch BMP085 I2C device and wires on a breadboard.

  1. Place the 26-pin header connector into the breadboard so that:

    • The connector straddles the center.
    • Pin 1 (marked 3V3) is in the first column at the top right.
    • The "key" (the square cutout in the center of the connector) is in the center at the top.
    Breadboard

    Note: Your breadboard may look different.

  2. Insert the BMP085 breakout board into the breadboard so that the breakout board's GND pin is in row 19. All of the breakout board's pins should be in column a.

    Breadboard

    Caution: Before wiring the circuit, check that you have version 2 of the Raspberry Pi. For more information, see "Checking the Hardware Version of Your Raspberry Pi" in the tutorial titled Working with GPIO by Using Java Embedded and a Raspberry Pi.

  3. Perform the following tasks, using the row numbers shown in the image as a reference:

    1. Insert a red wire from row 1 (pin 1) to the positive (red) rail at the top.
    2. Insert a red wire between the positive (red) rail and row 25, marked VIN on the BMP085 breakout board.
    3. Insert a blue wire between row 23, marked SCL on the BMP085 breakout board, and row 3 (pin 5) on the connector (marked SCL).
    4. Insert a white wire between row 22, marked SDA on the BMP085 breakout board, and row 2 (pin 3) on the connector (marked SDA).
    5. Insert a green wire between row 19, marked GND on the BMP085 breakout board, and the negative (blue) rail.
    6. Insert a green wire between row 3 on the bottom (pin 6) and the negative (blue) rail.
    Breadboard

    Your wiring should look similar to this picture:

    Breadboard

    Caution: Double-check that pin 1, marked 3V3 (on the top), is connected to the positive rail, and pin 6, the third one from the right on the bottom (marked GND), is connected to the negative rail. If you connect to the 5V pin by accident, you could damage your Raspberry Pi.

  4. Review the application circuit that you created.
  5. Breadboard

    Notice that pin 3 (marked GPIO2) is the SDA line, and pin 5 (marked GPIO3) is the SCL line. The breakout board has built-in pull-up resistors, so the wiring is very straightforward.

  6. Unplug the power cable of your Raspberry Pi.

  7. Connect the ribbon cable between the breadboard and the Raspberry Pi.

    If you have not performed this step before, see "Connecting the Ribbon Cable Between the Raspberry Pi and the Header" in the tutorial titled Working with GPIO by Using Java Embedded and a Raspberry Pi.

  8. Reconnect power to your Raspberry Pi.

Preparing the Raspberry Pi for I2C

Before you can use I2C devices with the Raspberry Pi, you must enable it by loading two modules and removing I2C from the blacklist. To test the I2C bus and detect devices, you install i2c-tools.

Configuring I2C on the Raspberry Pi

  1. Perform the following steps:

    1. Connect to your Raspberry Pi with PuTTY.
    2. Enter sudo nano /etc/modules.
    PuTTY Window
  2. Enter i2c-bcm2708 and i2c-dev after the snd-bcm2835 line.

    PuTTY Window
  3. Press Ctrl + O and then press Enter to save the changes.

    PuTTY Window
  4. Press Ctrl + X to close nano.

    PuTTY Window
  5. Enter sudo nano /etc/modprobe.d/raspi-blacklist.conf and press Enter.

    PuTTY Window
  6. Comment out the two blacklist lines in the file by adding a comment sign (#) at the beginning of each line.

    PuTTY Window
  7. Press Ctrl + O and then press Enter to save the changes.

    PuTTY Window
  8. Press Ctrl + X to exit nano.

    PuTTY Window

Installing i2c-tools

  1. Perform the following steps:

    1. Enter sudo apt-get install python-smbus and press Enter.
    2. At the prompt, enter y and press Enter.
    PuTTY Window
  2. Enter sudo apt-get install i2c-tools and press Enter.

    PuTTY Window
  3. Enter sudo reboot and press Enter.

    PuTTY Window

Scanning for I2C devices with i2c-tools

  1. Reconnect to the Raspberry Pi by using a PuTTY connection.

    PuTTY Window
  2. Enter sudo i2cdetect -y 1.

    PuTTY Window

    The number 77 indicates that the i2cdetect tool found an I2C device at address 0x77 on the I2C bus.

Running and Testing the BMP085Example Project on the Raspberry Pi

In this section, you test the application that you created by using NetBeans with the application circuit that you wired.

  1. In a Windows Command Prompt, change directories to C:\Java_ME_platform_SDK_8.0_EA\bin and enter the emulator.exe -Xjam -Xdevice:EmbeddedExternalDevice1 command to start the external embedded emulator.

    Command Prompt
  2. Perform the following steps:

    1. Boot the Raspberry Pi.
    2. Connect to it with PuTTY.
    3. Change directories with the cd javame8ea/bin command.
    4. Start the AMS with the sudo ./usertest.sh command.
    PuTTY Window
  3. In NetBeans, right-click the BMP085Example project and select Run.

    NetBeans

    In the PuTTY window, the temperature and barometric pressure that are read from the BMP085 device are displayed and updated every 5 seconds.

    PuTTY window
  4. Gently put your finger on the sensor (the square in the center of the BMP085 device) to increase the temperature.

    PuTTY Window
  5. To stop the application, click Stop in the emulator.

    EmbeddedExternalDevice 1 Emulator

Summary

In this tutorial, you learned to:

  • Create a Java ME Embedded Project that uses I2C
  • Create an application circuit by using a breadboard and the I2C breakout board
  • Prepare the Raspberry Pi for I2C
  • Run and test the application on the Raspberry Pi and read the temperature and pressure from the sensor

Resources

Credits

  • Lead Curriculum Developer: Tom McGinn
  • Other Contributors: Edgar Alberto Martinez Cruz

To navigate this Oracle by Example tutorial, note the following:

Topic List:
Click a topic to navigate to that section.
Expand All Topics:
Click the button to show or hide the details for the sections. By default, all topics are collapsed.
Hide All Images:
Click the button to show or hide the screenshots. By default, all images are displayed.
Print:
Click the button to print the content. The content that is currently displayed or hidden is printed.

To navigate to a particular section in this tutorial, select the topic from the list.