Beyond Beauty: JavaFX, I2C, Parallax, Touch, Raspberry Pi, Gyroscopes and Much More. (Part I)

I2C and the Rpi

When I think about realism on user interfaces, the first thing that comes to my mind is 3D.   Unfortunately, 3D and embedded are two worlds that don't go along very well.  When we think about embedded, we think about constrained devices, but it doesn't have to mean ugly interfaces.  Now we have Java SE Embedded (which includes JavaFX), it provides us with a great tool to create astonishing UI for embedded applications.

This was my inspiration for my next demo, what can I build, that could be cool, applied to embedded, and of course, how can I play with new hardware that I haven't played with it yet!  :-)  That was my new and exciting challenge.

The inspiration

The hardware:

  • Raspberry Pi was definitely the device, I could easily have Java and JavaFX on it.
  • I want to build an interface that reacts to the device orientation.  For the purpose of tracking positioning I used a combination of accelerometer + gyroscope, and the MPU-9150 Breakout from sparkfun electronics, was just perfect for this.  The MPU-9150 is a System in Package (SiP) that combines two chips: the MPU-6050, which contains a 3-axis gyroscope, 3-axis accelerometer, and an onboard Digital Motion Processor (DMP) capable of processing complex 9-axis MotionFusion algorithms; and the AK8975, a 3-axis digital compass. The supply voltage range was from 2.4V to 3.46V, just perfect for our RPi, and it uses I2C as a communication protocol also supported by RPi. 


The software

  • I'm using Oracle Java SE Embedded and JavaFX apis for the user interface. 
  • I'm using I2C for the communication protocol. Fortunately,  I found Pi4J apis that just do the job for me.  It has a great set of APIs to hide all I2C low level details, by exposing a really clean set of APIs, very easy to use.
  • The parallax effect.  Some time ago I saw a very cool applications using the Parallax effect, it provides a lot of realism to the scene without adding the extra processing cost of the 3D rendering.  Parallax effect is a scrolling technique in computer graphics, wherein background images move by the camera slower than foreground images, creating an illusion of depth in a 2D interface and adding realism to the immersion.  We see it everyday, just pay attention on how things move when you drive around.  



Getting I2C working on the Raspberry Pi

If you pay attention to the Raspberry Pi pins you will see that it has two pins for I2C, one for data's bus (SDA) and one for the clock (SCL).


Great!, I was ready to start connecting things...  But to my surprise, I2C is not enabled by default on the RPi, and there are few steps you need to follow.

Enabling I2C in the RPi

  • First go to:  /etc/modules and add the following lines:
    • i2c-bcm2708
    • i2c-dev
  • Install i2c-tools.  This is not required, but it's very handy for detecting devices and making sure everything works properly.
    • sudo apt-get install python-smbus
    • sudo apt-get install i2c-tools
  • There is a file called raspi-blacklist.conf, and by default SPI and I2C are part of this black list!
    • Edit /etc/modprobe.d/raspi-blacklist.conf  and comment out the lines
      • blacklist spi-bcm2708
      • blacklist i2c-bcm2708


Connecting the Gyroscope/Accelerometer to the Pi.

Connecting the board and the RPi is pretty straight forward.  I2C requires pullup resistors,  but the RPi provide them, so we just need to connect the MPU-9150 directly to the Pi, following the next diagram:

The MPU-9150 has a AD0 pin,  this allows you to set the last bit of the board's address, to either 0 (ground) or  1 (Vcc).  This allows us to connect two boards to the same bus.  In my case I just grounded this pin, then my board address is 0x68


Verifying the communication:

Once we connect the board to the RPi, we can continue to check if we can actually see it as a I2C device. 

  • Try running on your pi:  sudo i2cdetect -y 1  or sudo i2cdetect -y 0(0 for the 256 Pi model B).
  • You should be able to see your device on the table.  The following snapshot shows two I2C devices, one at address 40 and the second on address 70.


  • You should also see a couple of new entries under /dev:
    • spidev0.0
    • spidev0.1
    • I2c-0
    • I2c-1
  • If don't see any of these entries, try running:
    • sudo modprobe i2c-dev

The Hardware setup

This is the big picture of the demo setup.  I just mounted everything on a piece of wood, then, changes on the board position and direction will be reflected on the UI rendered on the screen.



The software


WiringPi Native library.

WiringPi is an Arduino wiring-like library written in C and released under the GNU_LGPLv3 license, which is usable from C and C++ and many other languages with suitable wrappers.  This library provides access to GPIOs pin, I2C and SPI interface, and UART lines.

WiringPi includes a command-line utility gpio which can be used to program and setup the GPIO pins. You can use this to read and write the pins and even use it to control them from shell scripts.



Pi4J

Provides a bridge between the native libraries (WiringPi) and Java for full access to the Raspberry Pi.

Features:
  • Wrapper classes for direct access to WiringPi Library from Java
  • Export & unexport GPIO pins
  • Configure GPIO pin direction
  • Configure GPIO pin edge detection
  • Control/write GPIO pin states
  • Pulse GPIO pin state
  • Read GPIO pin states
  • Listen for GPIO pin state changes (interrupt-based; not polling)
  • Automatically set GPIO states on program termination (GPIO shutdown)
  • Triggers for automation based on pin state changes
  • Send & receive data via RS232 serial communication
  • I2C Communication
  • SPI Communication
  • Extensible GPIO Provider interface to add GPIO capacity via expansion boards
  • Access system information and network information from the Raspberry Pi
  • Wrapper classes for direct access to WiringPi Library from Java


Installing and using Pi4J

  • First, get the APIs. From the Pi command line type:
    • wget http://pi4j.googlecode.com/files/pi4j-0.0.5.deb
  • Install the packages
    •   sudo dpkg -i pi4j-0.0.5.deb
  • Check the source files at:                
    • /opt/pi4j/lib                
    • /opt/pi4j/examples
  • Make sure you include the Pi4J lib folder in the classpath:        
    • javac -classpath .:classes:/opt/pi4j/lib/'*' ...
    • sudo java -classpath .:classes:/opt/pi4j/lib/'*' ...
  • Pi4J comes with a set of demos, you can build them and start from there. 
    • Build all demos:            
      • cd /opt/pi4j/examples            
      • ./build
  • At any time you can uninstall Pi4J
    • sudo dpkg -r pi4j


The Code:


First, we need to get the I2CBusI2CFactory class has a method called getInstance that allows us to do that.  Once we get the bus, we get the device itself (our board), just make sure you provide the correct address (did you ground or not the AD0 pin on your board?),  in our case we grounded AD0 and our address is x68)
.


public class Sensors {
   
     I2CBus bus;
     I2CDevice device;

  
    public Sensors() {
        System.out.println("Starting sensors reading:");

        // get I2C bus instance
        try {
           //get i2c bus
           bus = I2CFactory.getInstance(I2CBus.BUS_1);
           System.out.println("Connected to bus OK!");
           
           //get device itself
           device = bus.getDevice(0x68);
           System.out.println("Connected to device OK!");
   ...
   }
...
}


After getting the device, I thought I was going to get the readings right away, but it wasn't the case.  For the MPU-9150 few registries need to be set before the reading start:  (Find full description at the MPU_9150 Register Map and Description Revision)

  • Register 107 – Power Management 1 PWR_MGMT_1
This register allows the user to configure the power mode and clock source. It also provides a bit for resetting the entire device, and a bit for disabling the temperature sensor.



Parameters
:
DEVICE_RESET When set to 1, this bit resets all internal registers to their default values.
SLEEP When set to 1, this bit puts the MPU-9150 into sleep mode.
CYCLE When this bit is set to 1 and SLEEP is disabled, the MPU-9150 will cycle between sleep mode and waking up to take a single sample of data from active sensors at a rate determined by LP_WAKE_CTRL (register 108).
TEMP_DIS When set to 1,  this bit disables the temperature sensor.
CLKSEL 3-bit unsigned value. Specifies the clock source of the device.


  • Register 108 – Power Management 2  PWR_MGMT_2



    This register allows the user to configure the frequency of wake-ups in Accelerometer Only Low Power Mode.  This register also allows the user to put individual axes of the accelerometer and gyroscope into standby mode. 

    Parameters
    LP_WAKE_CTRL 2-bit unsigned value.  Specifies the frequency of wake-ups during Accelerometer Only Low Power Mode.
    STBY_XA When set to 1, this bit puts the X axis accelerometer into standby mode.
    STBY_YA When set to 1, this bit puts the Y axis accelerometer into standby mode.
    STBY_ZA When set to 1, this bit puts the Z axis accelerometer into standby mode.
    STBY_XG When set to 1, this bit puts the X axis gyroscope into standby mode.
    STBY_YG When set to 1, this bit puts the Y axis gyroscope into standby mode.
    STBY_ZG When set to 1, this bit puts the Z axis gyroscope into standby mode.

  • Register 27: Gyroscope Configuration  GYRO_CONFIG

    This register is used to trigger gyroscope self-test and configure the gyroscopes’ full scale range.



    The self-test for each gyroscope axis can be activated by controlling the XG_ST, YG_ST, and ZG_ST bits of this register.
    FS_SEL is a 2-bit unsigned value and it allows us to select the full scale range of gyroscopes.
  • Register 28: Accelerometer Configuration ACCEL_CONFIG

    This register is used to trigger accelerometer self test and configure the accelerometer full scale range.



    The self-test for each accelerometer axis can be activated by controlling the XA_ST, YA_ST, and ZA_ST bits of this register.

    Parameters:
    XA_ST When set to 1, the X - Axis accelerometer performs self - test
    YA_ST When set to 1, the Y - Axis accelerometer performs self - test
    ZA_ST When set to 1, the Z - Axis accelerometer performs self - test
    ACCEL _FS_SE 2-bit unsigned value . Selects the full scale range of accelerometers.
    ACCEL_HPF 3-bit unsigned value. Selects the Digital High Pass Filter configuration.

Now setting these registries looks like this:


public class Sensors {
   
    I2CBus bus;
    I2CDevice device;
  
    public Sensors() {
        System.out.println("Starting sensors reading:");

        // get I2C bus instance
        try {
           //get i2c bus
           bus = I2CFactory.getInstance(I2CBus.BUS_1);
           System.out.println("Connected to bus OK!");

           
           //get device itself
          
device = bus.getDevice(0x68);
           System.out.println("Connected to device OK!");
  

           //start sensing, using config registries 6B  and 6C   
          
device.write(0x6B, (byte) 0b00000000);
           device.write(0x6C, (byte) 0b00000000);

           System.out.println("Configuring Device OK!");
           
           //config gyro
          
device.write(0x1B, (byte) 0b11100000);
           //config accel   
          
device.write(0x1C, (byte) 0b00011001);
           System.out.println("Configuring sensors OK!");
        
           startReading();

        } catch (IOException e) {
           System.out.println(e.getMessage());
        }
   }

...
}




Now how to read the gyroscope and accelerometers values?  The values for the accelerometer measurements are stored in registers 59 to 64.  Each measurement is a 16-bit value, stored in two consecutive registers, as shown in the following table.



Similarly, the gyroscope measurements are stored in registers 67-72.


Now the code will look like this:




public class Sensors {
   
    I2CBus bus;
    I2CDevice device;

    byte[] accelData, gyroData;
  
    public Sensors() {
        System.out.println("Starting sensors reading:");

        // get I2C bus instance
        try {
           //get i2c bus
           bus = I2CFactory.getInstance(I2CBus.BUS_1);
           System.out.println("Connected to bus OK!");
           

           //get device itself
          
device = bus.getDevice(0x68);
           System.out.println("Connected to device OK!");
  

           //start sensing, using config registries 6B  and 6C   
          
device.write(0x6B, (byte) 0b00000000);
           device.write(0x6C, (byte) 0b00000000);

           System.out.println("Configuring Device OK!");
           
           //config gyro
          
device.write(0x1B, (byte) 0b11100000);
           //config accel   
          
device.write(0x1C, (byte) 0b00011001);
           System.out.println("Configuring sensors OK!");
        
           startReading();

        } catch (IOException e) {
           System.out.println(e.getMessage());
        }
   }

 
    //Create a separate thread for reading the sensors
   
public void startReading() {

        Task task = new Task<Void>() {
            @Override
            public void call() {
                try {

                    readingSensors();
                } catch (IOException e) {
                }
                return null;
            }
        };

        new Thread(task).start();
    }



    private void readingSensors() throws IOException {
        while (true) {
           accelData = new byte[6];
           gyroData = new byte[6];


           //You can read one registry at a time,
           //or you can read multiple consecutive ones,
           //in our case we are reading 6 consecutive registries
           //from 0x3B, meaning we are reading all the
           //accelerometer measurements

           int r = device.read(0x3B, accelData, 0, 6);
           if (r != 6) {
             System.out.println("Error reading accel data, < 6 bytes");
           }

           //Convert the values to integers, using the
           //helper method asInt

           int accelX = asInt(accelData[0]) * 256 + asInt(accelData[1]);
           int accelY = asInt(accelData[2]) * 256 + asInt(accelData[3]);
           int accelZ = asInt(accelData[4]) * 256 + asInt(accelData[5]);

        
           System.out.println("accelX: " + accelX + ", accelY: " + accelY + ", accelZ: " + accelZ);

         //Reading gyroscope measurements.
         r = device.read(0x43, gyroData, 0, 6);
         if (r != 6) {
           System.out.println("Error reading gyro data, < 6 bytes");
         }
        

         //Convert the values to integers, using the
         //helper method asInt

         gyroX = (asInt(gyroData[0]) * 256 + asInt(gyroData[1]));
         gyroY = (asInt(gyroData[2]) * 256 + asInt(gyroData[3]));
         gyroZ = (asInt(gyroData[4]) * 256 + asInt(gyroData[5]);

         System.out.println("gyroX: " + gyroX + ", gyroY: " + gyroY + ", gyroZ: " + gyroZ);   
        

         //Use the values as you want
         ...
       }
    }


  //Helper method
  private static int asInt(byte b) {
        int i = b;
        if (i < 0) {
            i = i + 256;
        }
        return i;
    }

}



After you get the values from the accelerometer and gyroscope, you can use them as you wish.  In my next blog entry we see how this values are going to be used to update the UI to provide realism to our interface.

Hope you enjoyed!
  See video of the demo here

Comments:

Post a Comment:
  • HTML Syntax: NOT allowed
About

Angela Caicedo

Search

Categories
Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today