Java ME 8 + Raspberry Pi + Sensors = IoT World (Part 2)

Version 4

    by Jose Cruz

     

    Learn how to connect sensors to the Raspberry Pi and control them with Java.

     

    Part 1 of this series explained how to connect electronic sensors to the Raspberry Pi Model B using general-purpose input/output (GPIO) interfaces. (That article and the rest of this series also apply to the Raspberry Pi Model B+ or the Raspberry Pi 2 Model B.)

     

    This article focuses on using inter-integrated circuit bus (I2C) interfaces to connect sensors, and it shows examples of how to develop Java ME 8 classes that can do the following:

     

     

    Note: The complete example code for this NetBeans IDE 8.0.1 project can be downloaded from the attached file below.

     

    Enabling I2C on the Raspberry Pi

     

    I2C is a multimaster serial bus that connects low-speed peripherals to a motherboard, mobile phone, embedded system or other electronic devices. It is also known as a two-wire interface. See the I2C bus specification for more information.

     

    I2C uses only two bidirectional lines, Serial Data Line (SDA) and Serial Clock (SCL), which are often pulled up with resistors.

     

    To enable I2C on the Raspberry Pi, we need to configure the kernel to load the modules automatically at startup by modifying the /etc/modules file to add two new lines, as shown in Listing 1. Then save the file and reboot.

     

    $ sudo nano /etc/modules i2c-bcm2708
    i2c-dev $ sudo reboot

     

    Listing 1. Configuring the kernel to load the modules

     

    Next, we need to install the I2C tools and make any users members of the I2C user group, as shown in Listing 2:

     

    $ sudo apt-get install i2c-tools
    $ sudo adduser pi i2c

     

    Listing 2. Installing the tools and adding users

     

    To check whether the I2C tools were correctly installed, use the command shown in Listing 3:

     

    $ sudo i2cdetect -y 1

     

    Listing 3. Verifying that the tools were correctly installed

     

    Figure 1 shows a list of all I2C addresses for devices connected to our Raspberry Pi.

     

    figure1.gif

     

    Figure 1. List of all I2C devices connected to Raspberry Pi

     

    Before connecting a sensor to the I2C bus, it is very important to obtain information about the sensor's address and all the registers it supports, by consulting the sensor's application notes or documentation. Using the links provided above, you can find the documentation for each sensor being used in this article.

     

    Circuits We Will Create

     

    Based on the block diagram shown in Figure 2 and the components shown Figure 3, we create the circuits shown in Figure 4.

    figure2.gif

     

    Figure 2. Block diagram of the circuits we will create

     

    figure3.gif

    Figure 3. Components we will use

     

    figure4.jpg

     

    Figure 4. Schematic of the circuits we will create

     

     

    Summary of the Code We Will Write

     

    The following two subsections provide an overview of the code we will write to control the sensors and to read data from them and write data to them.

     

    Using an Enumeration to Define All the Registers to Be Manipulated

     

    For each sensor device that will use the I2C interface, we will create an enumeration that defines all the registers to be manipulated by the device. The enumeration will have three components:

     

    public enum DeviceName {
    

     

    Listing 4. Enumeration components

     

    The code for the enumeration will do the following:

     

    • Define the name of each of the device's registers:

      NAME(value)
      

       

      Listing 5. Defining the name of a register

       

    • Define a public variable that stores a register value:

      public byte cmd;
      

       

      Listing 6. Defining a public variable to store a register value

       

    • Define a constructor:

      Private DeviceName (int cmd) {
               this.cmd = (byte) cmd;
      }
      

       

      Listing 7. Defining a constructor

       

    • Define read and write methods to read values from and write values to the registers:

      public int read(I2CDevice device) {
           return I2CUtils.read(device, this.cmd);
      }
      
      public void write(I2CDevice device, byte value) {
           I2CUtils.write(device, this.cmd, value);
      }
      

       

      Listing 8. Defining read and write methods

       

     

    Using a Device Class to Implement All Sensor Control Operations

     

    For each sensor device, we will create a class to implement all sensor control operations. This class needs to extend the I2CRpi class, which defines a device at a specified address and stores the device's I2C address in a public device variable, as shown in Listing 9.

     

    public class I2CRpi {
       private I2CDeviceConfig config;
       public I2CDevice device = null; // Save device address 
    

     

    Listing 9. Definition of the  I2CRpi class

     

    We will also create a class constructor that initializes and activates communication for a specific I2C address using the DeviceManager API and the I2CDeviceConfig class to establish the following conditions (see Listing 10):

     

    • Controller number: The number of the bus the slave device is connected to (a positive integer or zero) or DeviceConfig.DEFAULT
    • I2C address: The address of the slave device on the bus
    • Address size: The address size in bits (ADDR_SIZE_7 or ADDR_SIZE_10) or DeviceConfig.DEFAULT
    • Clock frequency: The clock frequency of the slave device in Hz or DeviceConfig.DEFAULT

     

    public I2CRpi(int i2cAddress) throws IOException {
        config = new I2CDeviceConfig(DeviceConfig.DEFAULT,
                    i2cAddress,
                    DeviceConfig.DEFAULT,
                    DeviceConfig.DEFAULT);
    
        device = (I2CDevice) DeviceManager.open(I2CDevice.class, config);
    }
    

     

    Listing 10. Establishing the initial conditions for a sensor device

     

    It's also important to close a device to free device resources, as shown in Listing 11.

     

    public void close() {
    ...
       device.close();
    ...
    }
    

     

    Listing 11. Closing the device resources

     

    This is all we will need to do to create an enumeration and a device class for each sensor.

     

    Connecting the Servo Driver

     

    The PCA9685 16-channel 12-bit PWM servo driver from Adafruit can be used to control sixteen servo motors over an I2C interface at address 0x41. To set this address, for each binary '1' in the address, you need to make a connection using a drop of solder on a servo driver pin to bridge the corresponding address jumper. For example, for address 0x41, add a drop of solder (shown in red in Figure 4) on the A0 pin of the servo driver. If you need to configure another address, you must connect another pin; for example, for address 0x42, use the A1 pin, and for address 0x43 use the A0 and A1 pins, like a binary combination.

     

    To avoid possibly damaging the Raspberry Pi, you should use an external power supply for the servo motor and use a 5 volt regulator, LM7805, as shown in Figure 4.

     

    This device has the following registers (addresses are shown in hexadecimal):

     

    • 00h: Mode 1
    • 06h to 45h: LED_ON and LED_OFF control registers (but we'll use only addresses 06h to 09h)
    • FEh: Prescaler to program the output frequency

     

    To support these registers, create an enumeration called PCA9685, as shown in Listing 12.

     

    public enum PCA9685 {
        //Define register names
        MODE1(0x0),
        PRESCALE(0xFE),
        LED0_ON_L(0x6),
        LED0_ON_H(0x7),
        LED0_OFF_L(0x8),
        LED0_OFF_H(0x9);
        
        public byte cmd;     // Public variable that stores register value
        private PCA9685(int cmd) {  // Constructor 
            this.cmd = (byte) cmd;
        }
        
        public int read(I2CDevice device) { // Read and write methods
            return I2CUtils.read(device, this.cmd);
        }
    
        public void write(I2CDevice device, byte value) {
            I2CUtils.write(device, this.cmd, value);
        }
    
    }
    

     

    Listing 12. Enumeration that defines all the registers for the control servo motors

     

    Let's now connect the device to the Raspberry Pi's GND, SCL, SDA, and VCC 3.3v pins, as shown in Figure 4, and create a Java ME 8 class called PCA9685Device to control the servo motor, for example a RadioShack Standard Servo model 2730766, attached to channel 0. See Listing 13.

     

    public class PCA9685Device extends I2CRpi { // Extends I2CRpi
        private static final int PWMServoDriverAddr = 0x41; // Defines I2C address 
    

     

    Listing 13. Creating the PCA9685Device class

     

    Now define a constructor that creates a device at the defined address and call the reset operation using the Mode 1 register, as shown in Listing 14.

     

    public PCA9685Device() throws IOException {
            super(PWMServoDriverAddr);
            reset();
    }
    
    public void reset() { // Initialize PWM
            PCA9685.MODE1.write(device, (byte) 0x0);
    }
    

     

    Listing 14. Establishing the control address and reset

     

    Create the setPWMFreq method to set the PWM frequency from the control board, for example, an RGB LED connected to any channel. The setPWM method is to control a servo motor connected to the num port and receive data to establish the PWM pulse; see Listing 15.

     

       public void setPWMFreq(float freq) { // Set PWM frequency
               float prescaleval = 25000000.0F;
               prescaleval /= 4096.0F;
               prescaleval /= freq;
               prescaleval -= 1.0;
               Logger.getGlobal().log(Level.FINE,"Estimated pre-scale: " + prescaleval);
               float prescale = (float) Math.floor(prescaleval + 0.5);
               Logger.getGlobal().log(Level.FINE,"Final pre-scale: " + prescale);
    
               int oldmode = PCA9685.MODE1.read(device);
               int newmode = (oldmode & 0x7F) | 0x10; // sleep
               PCA9685.MODE1.write(device, (byte) newmode); // go to sleep
               PCA9685.PRESCALE.write(device, (byte) prescale); // set the prescaler
               PCA9685.MODE1.write(device, (byte) oldmode);
    
               I2CUtils.I2Cdelay(5);
        //This sets the MODE1 register to turn on autoincrement.
               PCA9685.MODE1.write(device, (byte) (oldmode | 0x80));      
       }
    
       //Set PWM pulse to control servo motor
       public void setPWM(byte num, short on, short off){ 
             I2CUtils.writedevice,(byte)(PCA9685.LED0_ON_L.cmd+4*num),(byte)(on & 0xFF));
             I2CUtils.writedevice,(byte)(PCA9685.LED0_ON_H.cmd+4*num), (byte) (on >> 8));
             I2CUtils.writedevice,(byte)(PCA9685.LED0_OFF_L.cmd+4*num),(byte)(off & 0xFF));
             I2CUtils.writedevice,(byte)(PCA9685.LED0_OFF_H.cmd+4*num), (byte) (off >> 8));
       }
    }
    

     

    Listing 15. Establishing PWM Frequency and PWM pulse to control servo motor

     

    Now, let's extend the MIDlet class TestSensors (from Part 1) to control the servo motor, as shown in Listing 16.

     

    public class TestSensors extends MIDlet {
       ... 
       //Define control servo motors object
       PCA9685Device servo;
       ...
       public void startApp() {
       ...
          //Initialize control servo motors
          try {
             servo = new PCA9685Device();
             servo.setPWMFreq(60);
          } catch (IOException ex) { 
             Logger.getGlobal().log(Level.SEVERE,ex.getMessage());
          }
          ...
       {
    
       public void destroyApp(boolean unconditional) {
       ...
          servo.close();
       ...
       }
    
       class ReadSensors extends Thread {
          private double distance=0.0;
            
          @Override
          public void run() {
             while (shouldRun){
             ...
                //Move servo motor to right and left
                System.out.println("Servo moves right");
                for (int i=150;i<600;i+=50){
                   servo.setPWM((byte) 0, (short) 0, (short) i);
                   I2CUtils.I2Cdelay(1000);
                }    
                System.out.println("Servo moves left");
                for (int i=600;i>150;i-=50){
                   servo.setPWM((byte) 0, (short) 0, (short) i);
                   I2CUtils.I2Cdelay(1000);
                }
             ...
             }
          }
       }
    }
    

     

    Listing 16. Extending the MIDlet class to control servo motors

     

    Connecting the Light and Proximity Sensor

     

    The VCNL4000 proximity/light sensor from Adafruit or SparkFun can be used to detect its proximity to an object using infrared (IR) within a range of about 20 cm, or you can use a built-in ambient light sensor within a range of 0.25 to 16383 lux. All the data that is collected is provided through an I2C interface at address 0x13.

     

    This sensor has the following registers (addresses are shown in hexadecimal):

     

    • 80h: Command register
    • 81h: Product ID revision register
    • 82h: Not used
    • 83h: IR LED's current setting for proximity mode
    • 84h: Ambient light parameter register
    • 85h and 86h: Ambient light result registers
    • 87h and 88h: Proximity measurement result registers
    • 89h: Proximity measurement signal frequency register
    • 8Ah: Proximity modulator timing adjustment register
    • 8Bh: Ambient IR light-level register (we aren't going to use this)

     

    To support these registers, create an enumeration called VCNL4000, as shown in Listing 17.

     

    public enum VCNL4000 {
        COMMAND(0x80),
        PRODUCTID(0x81),
        IRLED(0x83),
        AMBIENTPARAMETER(0x84),
        AMBIENTDATA(0x85),
        AMBIENTDATA2(0x86),
        PROXIMITYDATA(0x87),
        PROXIMITYDATA2(0x88),
        SIGNALFREQ(0x89),
        PROXIMITYADJUST(0x8A),
        // Commands used by COMMAND register  
        MEASUREAMBIENT(0x10),
        MEASUREPROXIMITY(0x08),
        AMBIENTREADY(0x40),
        PROXIMITYREADY(0x20);
    
        public byte cmd;
        private VCNL4000(int cmd) {
            this.cmd = (byte) cmd;
        }
    ...// Read and write methods
    }
    

     

    Listing 17. Enumeration that defines all light and proximity sensor registers

     

    Let's now connect the light and proximity sensor to the Raspberry Pi's 3.3v, SCL, SDA, GND and Vin 5v pins, as shown in Figure 4, and create a Java ME 8 class called VCNL4000Device to control it. To establish the IR LED frequency, declare an enumeration called Freq, as shown in Listing 18.

     

    public class VCNL4000Device extends I2CRpi {
    
        public enum Freq { // commands to set frequency for IR LED
            F3M125(0x00), //3.125 MHZ
            F1M5625(0x01), //1.5625 MHz
            F781K25(0x02), //781.25 KHz
            F390K625(0x03); //390.625 KHz
    
            public byte value;
            Freq(int value) { // Read frequency value
                this.value = (byte) value;
            }
        }
    

     

    Listing 18. VCNL4000Device class and Freq enumeration

     

     

    Now define a constructor that creates a device at the defined address (see Listing 19) and executes the following actions:

     

    • Read the Product ID register to detect whether the sensor was found.
    • Set the IR LED working current to 200 mA.
    • Check the proximity measurement frequency.
    • Read the proximity adjustment register.

     

    private static final int VCNL4000_ADDRESS = 0x13;
    public VCNL4000Device() throws IOException {
            super(VCNL4000_ADDRESS);
    
            byte rev = (byte) VCNL4000.PRODUCTID.read(device);
            if ((rev & 0xF0) != 0x10) {
                Logger.getGlobal().log(Level.SEVERE, "Sensor not found");
                return;
            }
    
            VCNL4000.IRLED.write(device, (byte) 20);        // set to 20 * 10mA = 200mA
            Logger.getGlobal().log(Level.FINE, "IR LED current = " + 
    String.valueOf(VCNL4000.IRLED.read(device) * 10) + " mA");
            Logger.getGlobal().log(Level.FINE, "Proximity measurement frequency = ");
            byte freq = (byte) VCNL4000.SIGNALFREQ.read(device);
            if (freq == Freq.F3M125.value) {
                Logger.getGlobal().log(Level.FINE, "3.125 MHz");
            }
            if (freq == Freq.F1M5625.value) {
                Logger.getGlobal().log(Level.FINE, "1.5625 MHz");
            }
            if (freq == Freq.F781K25.value) {
                Logger.getGlobal().log(Level.FINE, "781.25 KHz");
            }
            if (freq == Freq.F390K625.value) {
                Logger.getGlobal().log(Level.FINE, "390.625 KHz");
            }
    
            VCNL4000.PROXIMITYADJUST.write(device, (byte) 0x81);
            Logger.getGlobal().log(Level.FINE, "Proximity adjustment register = 
    "+String.valueOf(VCNL4000.PROXIMITYADJUST.read(device)));
           
        }
    

     

    Listing 19. Establishing the control address and initial conditions

     

    Then create the following operation methods (see Listing 20):

     

    • setLEDcurrent: Set working current for LED
    • continuousConversionOn: Activate continuous conversion
    • continuousConversionOff: Deactivate continuous conversion
    • setSignalFreq: Set IR frequency
    • setProximityAdjust: Adjust proximity
    • getProximityAdjust: Read adjust proximity value
    • readProximity: Read proximity from object to device in centimeters
    • readAmbientLight: Read ambient light indicator

     

        public void setLEDcurrent(byte cur) {
            if ((cur > 20) || (cur < 0)) cur = 5; 
            VCNL4000.IRLED.write(device, cur);
        }
    
        public void continuousConversionOn() {
    
            VCNL4000.AMBIENTPARAMETER.write(device, (byte) 0x89);
        }
    
        
        public void continuousConversionOff() {
            VCNL4000.AMBIENTPARAMETER.write(device, (byte) 0x09);
        }
    
        
        public void setSignalFreq(Freq freq) {
            //# Setting the proximity IR test signal frequency. 
            //# The proximity measurement is using a square IR 
            //# signal as measurement signal. Four different values are possible: 
            //# 00 = 3.125 MHz
            //# 01 = 1.5625 MHz
            //# 02 = 781.25 kHz (DEFAULT)
            //# 03 = 390.625 kHz
            VCNL4000.SIGNALFREQ.write(device, freq.value);
        }
    
        public int getSignalFreq() {
            return VCNL4000.SIGNALFREQ.read(device);
        }
    
        public void setProximityAdjust() {
            VCNL4000.PROXIMITYADJUST.write(device, (byte) 0x81);
        }
    
        public int getProximityAdjust() {
            return VCNL4000.PROXIMITYADJUST.read(device);
        }
    
        public short readProximity() {
            byte temp = (byte) VCNL4000.COMMAND.read(device);
            VCNL4000.COMMAND.write(device, (byte) (temp | VCNL4000.MEASUREPROXIMITY.cmd));
            while (true) {
                byte result = (byte) VCNL4000.COMMAND.read(device);
                //Serial.print("Ready = 0x"); Serial.println(result, HEX);
                if ((result & VCNL4000.PROXIMITYREADY.cmd) > 0) {
                    short data = (short) (VCNL4000.PROXIMITYDATA.read(device) << 8);
                    data = (short) (data | VCNL4000.PROXIMITYDATA2.read(device));
                    return data;
                }
                I2CUtils.I2Cdelay(10);
            }
        }
    
        
        public short readAmbientLight() {
            byte temp = (byte) VCNL4000.COMMAND.read(device);
            VCNL4000.COMMAND.write(device, (byte) (temp | VCNL4000.MEASUREAMBIENT.cmd));
            while (true) {
                byte result = (byte) VCNL4000.COMMAND.read(device);
                if ((result & VCNL4000.AMBIENTREADY.cmd) > 0) {
                    short data = (short) (VCNL4000.AMBIENTDATA.read(device) << 8);
                    data = (short) (data | VCNL4000.AMBIENTDATA2.read(device));
                    return data;
                }
                I2CUtils.I2Cdelay(10);
            }
        }
    
    }
    

     

    Listing 20. Operation methods

     

    Let's now extend the MIDlet class TestSensors (from Part 1) to read ambient light and object proximity, as shown in Listing 21.

     

    public class TestSensors extends MIDlet {
       ... 
       //Define light and proximity sensor object
       VCNL4000Device vcnl;
       ...
    
       public void startApp() {
       ...
          //Initialize light and proximity sensor
          try {
             vcnl = new VCNL4000Device();
          } catch (IOException ex) {
             Logger.getGlobal().log(Level.SEVERE,ex.getMessage());
          }
       ...
       }
    
       public void destroyApp(boolean unconditional) {
       ...
          vcnl.close();
       ...
       }
    
       class ReadSensors extends Thread {
          private double distance=0.0;
            
             @Override
             public void run() {
                while (shouldRun){
                ...
                   //Detect objects and ambient light
                   if (vcnl!=null){
                      System.out.println("VCNL4000 Ambient light:"+ 
    vcnl.readAmbientLight());
                      vcnl.setProximityAdjust();
                      I2CUtils.I2Cdelay(2000);
                      System.out.println("VCNL4000 Proximity (ctms):"+ 
    vcnl.readProximity());
                   }
             ...
             }
          }
       }
    }
    

     

    Listing 21. Extending the MIDlet class to support ambient light and object proximity sensor

     

    Connecting the Humidity and Temperature Sensor

     

    The HTU21D is ideal for environmental sensing and data logging and perfect for a weather station or humidor control system. All you need is two lines and you'll have relative humidity readings and very accurate temperature readings over an I2C interface from address 0x40.

     

    Humidity and temperature response time as well as the recovery time (from condensing humidity) are in the range of a few seconds. This supports a wide humidity range (0 to 100 percent relative humidity) and a wide temperature range (-40 to 125 degrees centigrade).

     

    This sensor has the following registers (addresses are shown in hexadecimal):

     

    • E3h: Hold master trigger temperature measurement
    • E5h: Hold master trigger humidity measurement
    • F3h: No hold master trigger temperature measurement
    • F5h: No hold master trigger humidity measurement
    • E6h: Write user register
    • E7h: Read user register
    • FEh: Soft reset

     

    To support these registers, create an enumeration called HTU21D, as shown in Listing 22.

     

    public enum HTU21D {
        TRIGGER_TEMP_MEASURE_HOLD(0xE3),
        TRIGGER_HUMD_MEASURE_HOLD(0xE5),
        TRIGGER_TEMP_MEASURE_NOHOLD(0xF3),
        TRIGGER_HUMD_MEASURE_NOHOLD(0xF5),
        WRITE_USER_REG(0xE6),
        READ_USER_REG(0xE7),
        SOFT_RESET(0xFE);
    
        public byte cmd;
        private HTU21D(int cmd) {
            this.cmd = (byte) cmd;
        }
        ...// Read and write methods 
    }
    

     

    Listing 22. Enumeration that defines all humidity and temperature sensor registers

     

    Let's now connect the humidity and temperature sensor to the Raspberry Pi's 3.3v, GND, SDA, and SCL pins, as shown in Figure 4, and create a Java ME 8 class called  HTU21DDevice with its constructor using the control address, as shown in Listing 23.

     

    public class HTU21DDevice extends I2CRpi {
       private static final int HTDU21D_ADDRESS = 0x40;
    
       public HTU21DDevice() throws IOException {
          super(HTDU21D_ADDRESS);
       }
    

     

    Listing 23. HTU21DDevice class with its constructor establishing the control address

     

    Then create the following operation methods (see Listing 24):

     

    • readHumidity: Read relative humidity.
    • readTemperature: Read temperature (centigrade).
    • setResolution: Set the sensor resolution; see the sensor's data sheet.

     

       public float readHumidity() {
          try {
             device.write(HTU21D.TRIGGER_HUMD_MEASURE_NOHOLD.cmd);
          } catch (IOException ex) {
             Logger.getGlobal().log(Level.WARNING,ex.getMessage());
          }
          I2CUtils.I2Cdelay(200); //Hang out while measurement is taken. 50mS
          ByteBuffer rxBuf = ByteBuffer.allocateDirect(3);
          try {
             device.read(rxBuf);
          } catch (IOException ex) {
             Logger.getGlobal().log(Level.WARNING,ex.getMessage());
            }
          rxBuf.clear();
          short msb, lsb, checksum;
          msb = (short) I2CUtils.asInt(rxBuf.get(0));
          lsb = (short) I2CUtils.asInt(rxBuf.get(1));
          checksum = (short) I2CUtils.asInt(rxBuf.get(2));
          short rawHumidity = (short) (((short) msb << 8) | (short) lsb);
          if (check_crc(rawHumidity, checksum) != 0) {
             return (999); //Error out
          }
          rawHumidity &= 0xFFFC; //Zero out the status bits but keep them in place
          //Given the raw humidity data, calculate the actual relative humidity
          float tempRH = rawHumidity / (float) 65536; //2^16 = 65536
          float rh = -6 + (125 * tempRH); 
          return (rh);
       }
        
       public float readTemperature() {
          try {
             device.write(HTU21D.TRIGGER_TEMP_MEASURE_NOHOLD.cmd);
          } catch (IOException ex) {
             Logger.getGlobal().log(Level.WARNING,ex.getMessage());
          }
          I2CUtils.I2Cdelay(200); //Hang out while measurement is taken
          ByteBuffer rxBuf = ByteBuffer.allocateDirect(3);
          try {
             device.read(rxBuf);
          } catch (IOException ex) {
             Logger.getGlobal().log(Level.WARNING,ex.getMessage());
          }
          rxBuf.clear();
          short msb, lsb, checksum;
          msb = (short) I2CUtils.asInt(rxBuf.get(0));
          lsb = (short) I2CUtils.asInt(rxBuf.get(1));
          checksum = (short) I2CUtils.asInt(rxBuf.get(2));
          short rawTemperature = (short) (((short) msb << 8) | (short) lsb);
          if (check_crc(rawTemperature, (short) checksum) != 0) {
             return (999); //Error out
          }
          rawTemperature &= 0xFFFC; //Zero out the status bits but keep them in place
          //Given the raw temperature data, calculate the actual temperature
          float tempTemperature = rawTemperature / (float) 65536; //2^16 = 65536
          float realTemperature = -46.85F + (175.72F * tempTemperature); 
    
          return (realTemperature);
       }
    
       public void setResolution(byte resolution) throws IOException {
          byte userRegister = read_user_register(); //Go get the current register state
          userRegister &= 0b01111110; //Turn off the resolution bits
          resolution &= 0b10000001; //Turn off all other bits but resolution bits
          userRegister |= resolution; //Mask in the requested resolution bits
          HTU21D.WRITE_USER_REG.write(device, userRegister);
       }
       ...
    }
    

     

    Listing 24. Operation methods

     

    Let's now extend the MIDlet class TestSensors (from Part 1) to read temperature and relative humidity, as shown in Listing 25.

     

    public class TestSensors extends MIDlet {
       ... 
       //Define humidity and temperature sensor object
       HTU21DDevice htu;
       ...
    
       public void startApp() {
       ...
          //Initialize humidity and temperature sensor
          try {
             htu= new HTU21DDevice();
          } catch (IOException ex) {
             Logger.getGlobal().log(Level.SEVERE,ex.getMessage());
       }
       ...
    }
    
       public void destroyApp(boolean unconditional) {
       ...
          htu.close();
       ...
       }
    
       class ReadSensors extends Thread {
          private double distance=0.0;
         
          @Override
             public void run() {
                while (shouldRun){
                ...
                   //Read temperature and humidity
                   if (htu!=null){
                      System.out.println("HTU21D Temperature centigrade:"+ 
    htu.readTemperature());
                      I2CUtils.I2Cdelay(2000);
                      System.out.println("HTU21D Humidity:"+ htu.readHumidity());
                   }
                ...
                }
             }
       }
    }
    

     

    Listing 25. Extending the MIDlet class to support temperature and relative humidity sensor

     

    Connecting the Digital Compass Module

     

    The HMC5883L magneto resistive sensor circuit is a trio of sensors and application-specific support circuits for measuring magnetic fields. It can measure a wide magnetic field strength range (+/-8 oersted).

     

    With a power supply applied, the sensor converts any incident magnetic field in the sensitive axis directions to a differential voltage output. In the presence of a magnetic field, a change in the bridge resistive elements causes a corresponding change in voltage across the bridge outputs.

     

    These resistive elements are aligned together to have a common sensitive axis (indicated by arrows in the pinout diagram) that will provide positive voltage change with magnetic fields increasing in the sensitive direction.

     

    Because the output is proportional only to the magnetic field component along its axis, additional sensor bridges are placed at orthogonal directions to permit accurate measurement of magnetic fields in any orientation.

     

    This sensor has the following registers (addresses are shown in hexadecimal):

     

    • 00h: Configuration register A
    • 01h: Configuration register B
    • 02h: Mode register 03h: Data output X MSB register
    • 04h: Data output X LSB register
    • 05h: Data output Z MSB register
    • 06h: Data output Z LSB register
    • 07h: Data output Y MSB register
    • 08h: Data output Y LSB register
    • 09h: Status register (we won't use this register)
    • 10h: Identification register A (we won't use this register)
    • 11h: Identification register B (we won't use this register)
    • 12h: Identification register C (we won't use this register)

     

    Note: We'll read data output from registers 04h through 08h using the device.read(HMC5883L.DataRegBegin.cmd, 1, buffer); code in Listing 28.

     

    To support these registers, create an enumeration called HMC5883L, as shown in Listing 26.

     

    public enum HMC5883L {
        ConfigRegA(0x00),
        ConfigRegB(0x01),
        ModeReg(0x02),
        DataRegBegin(0x03);
    
        Public byte cmd;
        private HMC5883L(int cmd) {
            this.cmd = (byte) cmd;
        }
    ...// Read and write methods 
    }
    

     

    Listing 26. Enumeration that defines all digital compass registers

     

    Let's now connect the digital compass module to the Raspberry Pi's 5v, GND, SCL, and SDA pins, as shown in Figure 4, and create a Java ME 8 class called  HMC5883LDevice with its constructor using the provided control address. To establish the measurement type, declare an enumeration called Measurement, as shown in Listing 27.

     

    public class HMC5883LDevice extends I2CRpi {
    
        public enum Measurement {
            Continuous(0x00),
            SingleShot(0x01),
            Idle(0x03);
    
            public byte value;
            Measurement(int value) {
                this.value = (byte) value;
            }
        }
        ...
        private static final int HMC5883L_ADDRESS = 0x1E;
    
        public HMC5883LDevice() throws IOException {
            super(HMC5883L_ADDRESS);
            m_Scale = 1; //start scale for calculations
        }
    

     

    Listing 27. HMC5883LDevice class with its constructor establishing the control address

     

    Create the following operation methods (see Listing 28):

     

    • ReadRawAxis: Read sensor data.
    • ReadScaledAxis: Read sensor data with scale conversion; see sensor data sheet.
    • SetScale: Set conversion scale; see sensor data sheet.
    • SetMeasurementMode: Set measurement mode (continuous, single-shot, or idle) using the Measurement enumeration.
    • calculateHeading: Calculate heading in degrees.

     

        public MagnetometerRaw readRawAxis() {
            ByteBuffer buffer = ByteBuffer.allocateDirect(6);
            try {
                device.read(HMC5883L.DataRegBegin.cmd, 1, buffer);
            } catch (IOException ex) {
                Logger.getGlobal().log(Level.WARNING,ex.getMessage());
            }
            // Read each of the pairs of data as a signed short
            buffer.rewind();
            byte[] data = new byte[6];
            buffer.get(data);
            MagnetometerRaw raw = new MagnetometerRaw();
            raw.XAxis = (data[0] << 8) | data[1];
            raw.ZAxis = (data[2] << 8) | data[3];
            raw.YAxis = (data[4] << 8) | data[5];
            return raw;
        }
    
        public MagnetometerScaled readScaledAxis() {
            MagnetometerRaw raw = ReadRawAxis();
            MagnetometerScaled scaled = new MagnetometerScaled();
            scaled.XAxis = raw.XAxis * m_Scale;
            scaled.ZAxis = raw.ZAxis * m_Scale;
            scaled.YAxis = raw.YAxis * m_Scale;
            return scaled;
        }
    
        public int setScale(float gauss) {
            int regValue = 0x00;
            if (gauss == 0.88) {
                regValue = 0x00;
                m_Scale = 0.73F;
            } else if (gauss == 1.3) {
                regValue = 0x01;
                m_Scale = 0.92F;
            } else if (gauss == 1.9) {
                regValue = 0x02;
                m_Scale = 1.22F;
            } else if (gauss == 2.5) {
                regValue = 0x03;
                m_Scale = 1.52F;
            } else if (gauss == 4.0) {
                regValue = 0x04;
                m_Scale = 2.27F;
            } else if (gauss == 4.7) {
                regValue = 0x05;
                m_Scale = 2.56F;
            } else if (gauss == 5.6) {
                regValue = 0x06;
                m_Scale = 3.03F;
            } else if (gauss == 8.1) {
                regValue = 0x07;
                m_Scale = 4.35F;
            } else {
                return ErrorCode_1_Num;
            }
            // Setting is in the top 3 bits of the register.
            regValue = regValue << 5;
            HMC5883L.ConfigRegB.write(device, (byte) regValue);
            return 0;
        }
    
        public void SetMeasurementMode(Measurement mode) {
            HMC5883L.ModeReg.write(device, mode.value);
            // Write(ModeRegister, mode);
        }
        
        public double calculateHeading(){
             MagnetometerRaw raw = readRawAxis();
             MagnetometerScaled scaled = readScaledAxis();
             double heading = Math.atan2((double) scaled.YAxis, (double) scaled.XAxis);
             // see explanation in the source code
             double declinationAngle = - 0.2021672989739025;           
             heading += declinationAngle;
             // Correct for when signs are reversed.
             if (heading < 0) {
                 heading += 2 * Math.PI;
             }
             // Check for wrap due to addition of declination.
             if (heading > 2 * Math.PI) {
                 heading -= 2 * Math.PI;
             }
             // Convert radians to degrees for readability.
             return heading * 180 / Math.PI;
        }
    }
    

     

    Listing 28. Operation methods

     

    Let's now extend the MIDlet class TestSensors (from Part 1) to calculate the heading in degrees, as shown in Listing 29.

     

    public class TestSensors extends MIDlet {
       ... 
       //Define HMC5883L compass module object
       HMC5883LDevice hmc; 
       ...
    
       public void startApp() {
       ...
          //Initialize compass module 
          try {
             hmc = new HMC5883LDevice();
             hmc.setScale(1.3F);
             hmc.setMeasurementMode(HMC5883LDevice.Measurement.Continuous);
          } catch (IOException ex) {
             Logger.getGlobal().log(Level.SEVERE,ex.getMessage());
          } 
       ...
    }
    
       public void destroyApp(boolean unconditional) {
       ...
          hmc.close();
       ...
       }
    
       class ReadSensors extends Thread {
             private double distance=0.0;
            
             @Override
             public void run() {
                while (shouldRun){
                ...
                   //Display heading degrees point north
                   if (hmc != null) {
                      System.out.println("Heading degrees:"+hmc.calculateHeading());
                      I2CUtils.I2Cdelay(2000);
                      //Display magnetometer coordinates
                      HMC5883LDevice.MagnetometerRaw values = hmc.readRawAxis();
                      System.out.print("X:" + values.XAxis);
                      System.out.print(" Y:" + values.YAxis);
                      System.out.println(" Z:" + values.ZAxis);
                   }     
                ...
                }
             }
       }
    }
    

     

    Listing 29. Extending the MIDlet class to support the digital compass device

     

    Performing Some Additional Configuration

     

    Before running our TestSensors MIDlet using NetBeans IDE 8.0.2, it is important to establish the appropriate API permissions. To do this, in the IDE, select project JavaMEDemos, and then right-click and select Properties to show the Project Properties window.  Select Application Descriptor, and then select the API Permissions tab. Include the permission shown in Listing 30, as shown in Figure 5:

     

    jdk.dio.i2cbus.I2CPermission *:* , open
    

     

    Listing 30. Permission to include

     

    figure5.gif

     

    Figure 5. Establishing API permissions

     

    Conclusion

     

    If you need to read or write data beyond changes of logic states, it is important to use an I2C bus to manipulate register values, write or receive data (such as temperature, humidity, and light intensity or information from an electronic compass), and set PWM pulses of different magnitudes.

     

    It is important to have the technical documentation for each sensor device and, in particular, information related to the different values that can be entered into each device's registers to obtain related sensor values.

     

    To manipulate the registers of each device, you should use enumerations like the ones we created (PCA9685, VCNL4000, HTU21D, and HMC5883L) to abstract the numerical values of our classes in Java and to make handling control of the bus simpler using the respective device classes (such as PCA9685Device, VCNL4000Device, HTU21DDevice, and HMC5883LDevice).

     

    In the next article in this series, we will examine other types of sensors using other types of interfaces, such as serial peripheral interface bus (SPI) and universal asynchronous receiver/transmitter (UART).

     

    See Also

     

    Part 1 of this series

    About the Author

     

    Jose Cruz is a software engineer who has been working with Java since 1998. He is a lead developer of Java, Java ME, and Java EE at Ferreteria EPA C.A. in Venezuela. From an early age, his hobby has been electronics. This has led him to combine computing with electronics and develop projects where Java and embedded devices such as Arduino and Raspberry Pi are used.

     

    Join the Conversation

     

    Join the Java community conversation on Facebook, Twitter, and the Oracle Java Blog!