Forum Stats

  • 3,872,132 Users
  • 2,266,392 Discussions
  • 7,911,057 Comments

Discussions

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

Yolande Poirier-Oracle
Yolande Poirier-Oracle Member Posts: 97 Silver Badge
edited Nov 23, 2015 4:58AM in Java ME Embedded

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 a general-purpose input/output (GPIO) interface. Part 2 focused on using inter-integrated circuit bus (I2C) interfaces to connect sensors. Part 3 showed how to use universal asynchronous receiver transmitter circuit (UART) interfaces to connect a GPS receiver board and a text-to-speech module, as well as how to convert an I2C interface to UART.

This article, which is the last in this series, focuses on using a serial peripheral interface (SPI) bus to connect the following modules:

Note: The complete example code for this NetBeans IDE 8.0.2 project can be downloaded from the attachment below.

Enabling SPI on the Raspberry Pi

SPI is a synchronous serial communication interface specification used for short-distance communication, mainly in embedded systems.

SPI devices communicate in full-duplex mode using a master-slave architecture that has a single master device. The master device originates the frame for reading and writing using two logical signals: Master Input, Slave Output (MISO) and Master Output, Slave Input (MOSI). The master device needs a clock to synchronize with slaves. To do that, it uses the Serial Clock (SCLK) signal.

Multiple slave devices are supported by selecting individual slave select (SS) lines. The Raspberry Pi uses two logical signals, CE0 and CE1, for this purpose.

The Raspberry Pi's SPI logical signals MOSI, MISO, SCLK, CE0, and CE1 can be found at pins 19, 21, 23, 24, and 26 respectively. See the Raspberry Pi pinout.

To enable SPI on the Raspberry Pi, we need to start by removing any blacklist for the SPI module. First, run the commands shown in Listing 1 to edit the raspi-blacklist.conf file.

$ sudo nano /etc/modprobe.d/raspi-blacklist.conf

Listing 1. Editing the raspi-blacklist.conf file

In the file, look for the line blacklist spi-bcm2708 and insert a # at the beginning of that line to comment it. Then press Control-X Y and press Return to save the file.

Reboot the Raspberry PI using the command shown in Listing 2.

$ sudo reboot

Listing 2. Rebooting the Raspberry Pi

To check whether the SPI module was correctly installed, use the commands shown in Listing 3:

$ ls /dev/spi*
/dev/spidev0.0  /dev/spidev0.1 $ lsmod
... ... spi_bcm2708

Listing 3. Verifying that the SPI module was correctly installed

If the SPI module was not correctly installed, try to load the SPI module manually by running the command shown in Listing 4.

$ sudo modprobe spi-bcm2708

Listing 4. Loading the SPI module

A loopback test can be used to test the SPI send and receive capability. Connect a wire between the MOSI pin (19) and the MISO pin (21) and execute the commands shown in Listing 5.

$ wget https://raw.githubusercontent.com/raspberrypi/linux/rpi-3.10.y/Documentation/spi/spidev_test.c
$ gcc -o spidev_test spidev_test.c
$ ./spidev_test -D /dev/spidev0.0
spi mode: 0 bits per word: 8 max speed: 500000 Hz (500 KHz) FF FF FF FF FF FF 40 00 00 00 00 95 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF DE AD BE EF BA AD F0 0D

Listing 5. Loopback SPI test

If the output looks like the output shown in Listing 5, the SPI send and receive capability is working.

Circuits We Will Create

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

f1.gif

Figure 1. Block diagram of the circuits we will create

f2.png

Figure 2. Components we will use

f3.png

Figure 3. Schematic of the circuits we will create

Summary of the Code We Will Write

This section provides an overview of the code we will write to control the devices and to read data from them and write data to them.

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

public class SPIRpi {
    private SPIDeviceConfig config;
    public SPIDevice device = null; // Save device address and info
    public static final int CE0=0; // SPI address 0 CE0
    public static final int CE1=1; // SPI address 1 CE1

Listing 6. Definition of the SPIRpi class

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

  • Controller number: The number of the bus the slave device is connected to (the Raspberry Pi uses 0 and only one controller)
  • SPI address: The address of the slave device on the bus
  • SPI device configuration: Determines how the master activates the chip select signal to control a slave device; by default, all are low
  • Clock frequency that the master synchronizes with the slave; by default, it is 2 MHz
  • Mode number, which is based on the clock polarity and phase; by default, it is 0
  • Address size: The address size in bits, which is 8 by default
  • Bit communication order, which is big endian by default

public SPIRpi(int address) throws IOException {
config = new SPIDeviceConfig(0, 
 address, 
 SPIDeviceConfig.CS_ACTIVE_LOW, 
         2000000, 
         0, 
         8, 
         Device.BIG_ENDIAN);
        device = DeviceManager.open(config);
}

Listing 7. Establishing the initial conditions for devices

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

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

Listing 8. Closing the device resources

This is all we will need to do to create a device class.

Connecting the MCP3008 8-Channel 10-Bit ADC

The MCP3008 is a solution for the absence of Raspberry Pi analog inputs. It adds eight channels of 10-bit analog input to projects. It has the following features:

  • 10-bit resolution
  • Eight input channels
  • Analog inputs programmable as single-ended or pseudo-differential pairs
  • On-chip sample and hold
  • SPI serial interface (mode 0; mode 0 and 1; and mode 1)
  • Single supply operation: 2.7 V–5.5 V
  • 200 kilo samples per second (ksps) maximum sampling rate at VDD = 5 V
  • 75 ksps maximum sampling rate at VDD = 2.7 V
  • Low-power CMOS technology
  • 5 nA typical standby current, 2 µA maximum current
  • 500 µA maximum active current at 5 V

Let's now connect the device to the Raspberry Pi's GND, MOSI, MISO, SCLK, and VCC 3.3 V pins, and an additional 10K ohm potentiometer to test analog values at MCP3008 pin 1, as shown in Figure 3. Then create a Java ME 8 class called MCP3008Device with a constructor to establish the device's SPI address to control the ADC, as shown in Listing 9.

public class MCP3008Device extends SPIRpi {

    public MCP3008Device(int address) throws IOException {
        super(address);
    }

Listing 9. Creating the MCP3008Device class

The readChannel method (see Listing 10) reads an analog value from a particular channel. To do that, we need send byte 0x01 to read the channel command, we send (8 + channel number) <<4 to select the channel number, and we send byte 0x00 to start reading the MCP3008.

The Raspberry Pi (the master) receives data from the device in a three-byte array. The second and third bytes have the analog values, and with the formula ((rcvBuf.get(1) & 3) << 8) + rcvBuf.get(2) we read all the analog data.

If we need to convert analog value to volts, we use the method convertVolts. The MCP3008 Vref pin (15) is 3.3V, so the formula that we will use is (data * 3.3 ) / 1023, as shown in Listing 10.

      
public int readChannel(int channel) throws IOException {
        ByteBuffer sndBuf = ByteBuffer.wrap(new byte[]{
                            MCP3008.READ_CHANNEL.cmd, (byte) ((8 + channel) << 4), 0});
        ByteBuffer rcvBuf = ByteBuffer.wrap(new byte[3]);
        device.begin();
        device.writeAndRead(sndBuf, rcvBuf);
        device.end();

        return ((rcvBuf.get(1) & 3) << 8) + rcvBuf.get(2);
    }

    public float convertVolts(int data) {
        return (float) (data * 3.3) / (float) 1023;
    }
} 

Listing 10. Method to read a channel and convert its value to volts

Let's now create a test MIDlet class called TestMCP3008 to read analog values from the potentiometer and convert them to volts, as shown in Listing 11. Modifying the value of the potentiometer changes the analog value at channel 0.

public class TestMCP3008 extends MIDlet {
...
    @Override
    public void startApp() {
    ...
            MCP3008Device mcp3008 = new MCP3008Device(SPIRpi.CE0);
            int digitalData = mcp3008.readChannel(0);
            Logger.getGlobal().log(Level.INFO, "Channel 0 Value:" + digitalData);
            Logger.getGlobal().log(Level.INFO, "Volts at channel 0:" + 
                      String.format("%.2f", mcp3008.convertVolts(digitalData)));
            mcp3008.close();
    ...
    }

    @Override
    public void destroyApp(boolean unconditional) {
    }
} 

Listing 11. Test MIDlet for the MCP3008 ADC

Connecting the FRAM Breakout Board

This is a breakout board for the MB85RS64V, a FRAM chip in a configuration of 8,192 words x 8 bits, using the ferroelectric process and silicon gate CMOS process technologies for forming the nonvolatile memory cells. These are some of its general features:

  • Bit configuration: 8,192 words x 8 bits
  • Serial Peripheral Interface (SPI)
  • Correspondent to SPI mode 0 (0, 0) and mode 3 (1, 1)
  • Operating frequency: 20 MHz (maximum)
  • High endurance: 1012 times/byte
  • Data retention: 10 years (+ 85 °C), 95 years (+ 55 °C), over 200 years (+ 35 °C)
  • Operating power supply voltage: 3.0 V to 5.5 V

This chip has the following commands specified in op-codes, (op-codes are shown in hexadecimal):

  • 06h: WREN (set write enable latch)
  • 04h: WRDI (reset write enable latch)
  • 05h: RDSR (read status register)
  • 01h: WRSR (write status register)
  • 03h: READ (read memory code)
  • 02h: WRITE (write memory code)
  • 9Fh: RDID (read device ID)

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

public enum FRAM {

    OPCODE_WREN(0b0110),
    OPCODE_WRDI(0b0100),
    OPCODE_RDSR(0b0101),
    OPCODE_WRSR(0b0001),
    OPCODE_READ(0b0011),
    OPCODE_WRITE(0b0010),
    OPCODE_RDID(0b10011111);
   
    public byte cmd;

    private FRAM(int cmd) {
        this.cmd = (byte) cmd;
    }
}

Listing 12. Enumeration that defines all FRAM command op-codes

Let's now connect the FRAM breakout board to the Raspberry Pi's 3.3 V, GND, SCLK, MISO, MOSI, and CE1 pins, as shown in Figure 3, and create a Java ME 8 class called FRAMDevice with its constructor using the control address to initialize the FRAM, as shown in Listing 13.

public class FRAMDevice extends SPIRpi {

    private int manufacturerID; //Code of manufacturer
    private int productID; //Product ID

    public FRAMDevice(int address) throws IOException {
        super(address);
    }

Listing 13. FRAMDevice class with its constructor establishing the control address

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

  • getManufacturerID: Obtains the manufacturer ID code (0x04)
  • getProductID: Obtains the product ID code (0x0302)
  • getDeviceID: Reads the product and manufacturer ID if communication with the device is OK
  • write8: Writes a byte to a memory address
  • writeEnabled: Enables or disables write operations
  • read8: Reads a byte from the memory address

public int getManufacturerID() {
        return manufacturerID;
    }
   
    public int getProductID() {
        return productID;
    }

    public void getDeviceID() throws IOException {
        ByteBuffer sndBuf = ByteBuffer.wrap(new byte[]{FRAM.OPCODE_RDID.cmd, 0, 0, 0, 0});
        ByteBuffer rcvBuf = ByteBuffer.wrap(new byte[5]);
        device.begin();
        device.writeAndRead(sndBuf, rcvBuf);
        device.end();

        manufacturerID = (rcvBuf.get(1));
        productID = (rcvBuf.get(3) << 8) + rcvBuf.get(4);
    }

    public void write8(int addr, byte value) throws IOException {
        device.begin();
        device.write(FRAM.OPCODE_WRITE.cmd);
        device.write((addr >> 8));
        device.write((addr & 0xFF));
        device.write(value);
        device.end();
    }

    public void writeEnable(boolean enable) throws IOException {
        device.begin();
        if (enable) {
            device.write(FRAM.OPCODE_WREN.cmd);
        } else {
            device.write(FRAM.OPCODE_WRDI.cmd);
        }
        device.end();
    }

    public int read8(int addr) throws IOException {
        ByteBuffer sndBuf = ByteBuffer.wrap(new byte[]{
                                            FRAM.OPCODE_READ.cmd, 
                                            (byte) (addr >> 8), 
                                            (byte) (addr & 0xFF), 0});
        ByteBuffer rcvBuf = ByteBuffer.wrap(new byte[4]);
        device.begin();
        device.writeAndRead(sndBuf, rcvBuf);
        device.end();
        return rcvBuf.get(3);
    }
}

Listing 14. Operation methods

Let's now create a test MIDlet class TestFRAM to read the manufacturer and product IDs, and to write and read bytes at address 45 and address 47, as shown in Listing 15.

public class TestFRAM extends MIDlet {
...
    @Override
    public void startApp() {
    ...
            FRAMDevice fram = new FRAMDevice(1);
            fram.getDeviceID();
            Logger.getGlobal().log(Level.INFO, "Manufacturer ID:" + 
                                                fram.getManufacturerID());
            Logger.getGlobal().log(Level.INFO, "Product ID:" + fram.getProductID());
            fram.writeEnable(true);
            fram.write8(1, (byte) 45);
            fram.writeEnable(false);
            fram.writeEnable(true);
            fram.write8(2, (byte) 47);
            fram.writeEnable(false);
            Logger.getGlobal().log(Level.INFO, "Byte at memory address 1 = " + 
                                   fram.read8(1));
            Logger.getGlobal().log(Level.INFO, "Byte at memory address 2 = " +
                                   fram.read8(2));
            fram.close();
      ...

    }

    @Override
    public void destroyApp(boolean unconditional) {

    }
}

Listing 15. Test MIDlet for FRAM breakout board

Connecting the OLED Display

The OLED 0.96-inch SPI serial 128X64 display from SainSmart uses driver chip SSD1306. These are some of its general features:

  • Power supply: 3.3 V to 5 V
  • Power consumption: maximum 20 mA when all dots light are on
  • Communication mode: SPI
  • Works with all microcontrollers and microprocessors
  • Communication signal can work on 3.3 V and 5.0 V
  • Pixels: 128x64,  high bright self-light emitting

We can use this display to shows numbers, words, and graphics. To do that, we port the Adafruit Arduino C Library and Adafruit Gfx Library to Java.

The SSD1306 driver chip has the following registers (addresses are shown in hexadecimal):

  • 00h: Command mode
  • 40h: Data mode
  • A0h: Set segment remap
  • A7h: Inverse display
  • A8h: Set multiplex ratio
  • Aeh: Display off
  • Afh: Display on
  • 81h: Set contrast level
  • 01h: External vcc
  • 02h: Internal vcc
  • 21h: Set column address
  • 22h: Set page address
  • 2fh: Activate scroll
  • 2eh: Deactivate scroll
  • 26h: Right horizontal scroll
  • 27h: Left horizontal scroll
  • A4h: Entire display resume
  • A5h: Entire display on
  • A6h: Normal display
  • D3h: Set display offset
  • Dah: Set com pins
  • Dbh: Set vcomh deselect level
  • D5h: Set display clock div
  • D9h: Set precharge period
  • 00h: Set lower column start address
  • 10h: Set higher column start address
  • 40h: Set start line
  • 20h: Set memory mode
  • C0h: Set com output scan direction normal
  • C8h: Set com output scan direction remap
  • 8Dh: Charge_Pump_Setting
  • A3h: Set vertical scroll area
  • 29h: Vertical and right horizontal scroll
  • 2Ah: Vertical and left horizontal scroll

To support these registers, create two enumerations called OLED and SSD1306, as shown in Listing 16 and Listing 17.

public enum OLED {

    /*=========================================================================
     SSDxxxx Common Displays
     -----------------------------------------------------------------------
     Common values to all displays
     =========================================================================*/
    SSD_Command_Mode(0x00), /* C0 and DC bit are 0         */
    SSD_Data_Mode(0x40), /* C0 bit is 0 and DC bit is 1 */
    SSD_Set_Segment_Remap(0xA0),
    SSD_Inverse_Display(0xA7),
    SSD_Set_Muliplex_Ratio(0xA8),
    SSD_Display_Off(0xAE),
    SSD_Display_On(0xAF),
    SSD_Set_ContrastLevel(0x81),
    SSD_External_Vcc(0x01),
    SSD_Internal_Vcc(0x02),
    SSD_Set_Column_Address(0x21),
    SSD_Set_Page_Address(0x22),
    SSD_Activate_Scroll(0x2F),
    SSD_Deactivate_Scroll(0x2E),
    SSD_Right_Horizontal_Scroll(0x26),
    SSD_Left_Horizontal_Scroll(0x27),
    Scroll_Left(0x00),
    Scroll_Right(0x01),
    Scroll_2Frames(0x07),
    Scroll_3Frames(0x04),
    Scroll_4Frames(0x05),
    Scroll_5Frames(0x00),
    Scroll_25Frames(0x06),
    Scroll_64Frames(0x01),
    Scroll_128Frames(0x02),
    Scroll_256Frames(0x03),
    VERTICAL_MODE(01),
    PAGE_MODE(01),
    HORIZONTAL_MODE(02);

    /**
     *
     */
    public byte cmd;

    private OLED(int cmd) {
        this.cmd = (byte) cmd;
    }

}

Listing 16. Enumeration that defines all OLED registers

public enum SSD1306 {

    /*=========================================================================
     SSD1306 Displays
     -----------------------------------------------------------------------
     The driver is used in multiple displays (128x64, 128x32, etc.).
     =========================================================================*/
    Entire_Display_Resume(0xA4),
    Entire_Display_On(0xA5),
    Normal_Display(0xA6),
    Set_Display_Offset(0xD3),
    Set_Com_Pins(0xDA),
    Set_Vcomh_Deselect_Level(0xDB),
    Set_Display_Clock_Div(0xD5),
    Set_Precharge_Period(0xD9),
    Set_Lower_Column_Start_Address(0x00),
    Set_Higher_Column_Start_Address(0x10),
    Set_Start_Line(0x40),
    Set_Memory_Mode(0x20),
    Set_Com_Output_Scan_Direction_Normal(0xC0),
    Set_Com_Output_Scan_Direction_Remap(0xC8),
    Charge_Pump_Setting(0x8D),
    // Scrolling s
    SET_VERTICAL_SCROLL_AREA(0xA3),
    VERTICAL_AND_RIGHT_HORIZONTAL_SCROLL(0x29),
    VERTICAL_AND_LEFT_HORIZONTAL_SCROLL(0x2A);

    /**
     *
     */
    public byte cmd;

    private SSD1306(int cmd) {
        this.cmd = (byte) cmd;
    }

}

Listing 17. Enumeration that defines all the SSD1306 registers

Let's now connect the OLED display to the Raspberry Pi's 3.3 V, GND, MOSI, SCLK, DC (GPIO 24), RESET (GPIO 25), and CS (CE0) pins, as shown in Figure 3, and create a Java ME 8 class called SSD1306Device with its constructor using the control address and defining the buffer used to store all OLED display data, as shown in Listing 18.

public class SSD1306Device extends SPIRpi {

    private GPIOPin reset = null;
    private GPIOPin dc = null;
    //Buffer for display all oled contents
    public ByteBuffer poledbuff = ByteBuffer.wrap(new byte[128 * 64 / 8]);

    public SSD1306Device(int address) throws IOException {
        super(address);
        begin();

    } 

Listing 18. SSD1306Device class

The constructor calls the begin() method to initialize the OLED (see Listing 19) and executes the following actions:

  • Defines the GPIO 25 configuration for the reset pin, output only and open, not trigger
  • Defines the GPIO 24 configuration for the data/command pin, output only and open, not trigger
  • Resets the OLED display using a combination high, low, and high at the reset pin
  • Establishes some initial conditions for the OLED display
  • Stops all scroll activity
  • Clears the display buffer
  • Activates the display

private void begin() throws IOException {
        // define device for trigger pin
        reset = (GPIOPin) DeviceManager.open(new GPIOPinConfig(
                0, 25, GPIOPinConfig.DIR_OUTPUT_ONLY,
                GPIOPinConfig.MODE_OUTPUT_OPEN_DRAIN,
                GPIOPinConfig.TRIGGER_NONE, false));
        // define device for echo pin
        dc = (GPIOPin) DeviceManager.open(new GPIOPinConfig(
                0, 24, GPIOPinConfig.DIR_OUTPUT_ONLY, 
                GPIOPinConfig.MODE_OUTPUT_OPEN_DRAIN,
                GPIOPinConfig.TRIGGER_NONE, false));
        // Setup reset pin direction (used by both SPI and I2C)  
        reset.setValue(true);
        // VDD (3.3V) goes high at start, let's just chill for a ms
        I2CUtils.I2Cdelay(1);
        // bring reset low
        reset.setValue(false);
        // wait 10ms
        I2CUtils.I2Cdelay(10);
        // bring out of reset
        reset.setValue(true);
        sendCommand(OLED.SSD_Display_Off.cmd);
        sendCommand(OLED.SSD_Set_Muliplex_Ratio.cmd, 0x3F);
        sendCommand(SSD1306.Charge_Pump_Setting.cmd, 0x14);
        sendCommand(SSD1306.Set_Memory_Mode.cmd, 0x00); 
        sendCommand(SSD1306.Set_Display_Clock_Div.cmd, 0x80); 
        sendCommand(SSD1306.Set_Display_Offset.cmd, 0x00); 
        sendCommand(SSD1306.Set_Start_Line.cmd | 0x0);
        // use this two commands to flip display
        sendCommand(OLED.SSD_Set_Segment_Remap.cmd | 0x1);
        sendCommand(SSD1306.Set_Com_Output_Scan_Direction_Remap.cmd);
        sendCommand(SSD1306.Set_Com_Pins.cmd, 0x12);
        sendCommand(SSD1306.Set_Precharge_Period.cmd, 0xF1);
        sendCommand(SSD1306.Set_Vcomh_Deselect_Level.cmd, 0x40);
        sendCommand(SSD1306.Entire_Display_Resume.cmd);
        sendCommand(SSD1306.Normal_Display.cmd);
        // Reset to default value in case of 
        // no reset pin available on OLED, 
        sendCommand(OLED.SSD_Set_Column_Address.cmd, 0, 127);
        sendCommand(OLED.SSD_Set_Page_Address.cmd, 0, 7);
        sendCommand(OLED.SSD_Set_ContrastLevel.cmd, 0xCF);
        stopscroll();
        // Empty uninitialized buffer
        clearDisplay();
        // turn on oled panel
        sendCommand(OLED.SSD_Display_On.cmd);
        // wait 100ms
        //usleep(100000);
        I2CUtils.I2Cdelay(100);
    }

Listing 19. Establishing the initial conditions

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

  • sendCommand: Sends a command with one or two attributes
  • sendData: Sends data
  • stopscroll: Stops all scroll activity
  • display: Sends all the contents of the data buffer to the OLED display
  • clearDisplay: Clears all the contents in the data buffer
  • close: Frees all resources

    private void sendCommand(int cmd) throws IOException {
        // Set D/C line to low to switch to command mode
        dc.setValue(false);
        device.write(cmd);
    }

    private void sendCommand(int cmd, int data) throws IOException {
        // Set D/C line to low to switch to command mode
        dc.setValue(false);
        ByteBuffer sndBuf = ByteBuffer.wrap(new byte[]{(byte) cmd, (byte) data});
        device.write(sndBuf);
    }

    private void sendCommand(int cmd, int data, int data2) throws IOException {
        // Set D/C line to low to switch to command mode
        dc.setValue(false);
        ByteBuffer sndBuf = ByteBuffer.wrap(new byte[]{(byte) cmd, 
                            (byte) data, (byte) data2});
        device.write(sndBuf);

    }

    private void sendData(int cmd) throws IOException {
        // Set D/C line to high to switch to data mode
        dc.setValue(true);
        device.write(cmd);
    }

    private void stopscroll() throws IOException {
        sendCommand(OLED.SSD_Deactivate_Scroll.cmd);
    }

    public void display() throws IOException {
        sendCommand(SSD1306.Set_Lower_Column_Start_Address.cmd); // low col = 0
        sendCommand(SSD1306.Set_Higher_Column_Start_Address.cmd); // hi col = 0
        sendCommand(SSD1306.Set_Start_Line.cmd); // line #0
        // Set D/C line to high to switch to data mode
        dc.setValue(true);
        // Send all data to OLED
        device.write(poledbuff);
    }

    public void clearDisplay() throws IOException {
        poledbuff = ByteBuffer.wrap(new byte[128 * 64 / 8]);
        display();
    }

    public void close() {
        try {
            reset.close();
            dc.close();
            device.close();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }
}

Listing 20. Operation methods

Let's now create a test MIDlet class called TestSSD1306 that uses the Gfx library and its basic method drawPixel to display a single character, a text string, circles, lines, rectangles, and triangles, as shown in Listing 21.

public class TestSSD1306 extends MIDlet {

    private LoggingHandler loggerHandler = LoggingHandler.getInstance();

    private GfxLib display = null;

    private void testdrawcircle() throws IOException {
        for (int i = 0; i < display.height(); i += 2) {
            display.drawCircle(display.width() / 2, 
            display.height() / 2, i, GfxLib.WHITE);
            display.display();
        }
    }

    public void testfillrect() throws IOException {
        int color = 1;
        for (int i = 0; i < display.height() / 2; i += 3) {
            // alternate colors
            display.fillRect(i, i, display.width() - i * 2, 
            display.height() - i * 2, color % 2);
            display.display();
            color++;
        }
    }

    public void testdrawtriangle() throws IOException {
        for (int i = 0; i < Math.min(display.width(), display.height()) / 2; i += 5) {
            display.drawTriangle(display.width() / 2, display.height() / 2 - i,
                                 display.width() / 2 - i, display.height() / 2 + i,
                                 display.width() / 2 + i, display.height() / 2 + i,
                                 GfxLib.WHITE);
            display.display();
        }
    }

    public void testfilltriangle() throws IOException {
        int color = GfxLib.WHITE;
        for (int i = Math.min(display.width(), display.height()) / 2; i > 0; i -= 5) {
            display.fillTriangle(display.width() / 2, display.height() / 2 - i,
                    display.width() / 2 - i, display.height() / 2 + i,
                    display.width() / 2 + i, display.height() / 2 + i, GfxLib.WHITE);
            if (color == GfxLib.WHITE) {
                color = GfxLib.BLACK;
            } else {
                color = GfxLib.WHITE;
            }
            display.display();
        }
    }

    public void testdrawroundrect() throws IOException {
        for (int i = 0; i < display.height() / 2 - 2; i += 2) {
            display.drawRoundRect(i, i, display.width() - 2 * i, display.height() - 2 * i,
                                  display.height() / 4, GfxLib.WHITE);
            display.display();
        }
    }

    public void testfillroundrect() throws IOException {
        int color = GfxLib.WHITE;
        for (int i = 0; i < display.height() / 2 - 2; i += 2) {
            display.fillRoundRect(i, i, display.width() - 2 * i, display.height() - 2 * i,
                                  display.height() / 4, color);
            if (color == GfxLib.WHITE) {
                color = GfxLib.BLACK;
            } else {
                color = GfxLib.WHITE;
            }
            display.display();
        }
    }

    public void testdrawrect() throws IOException {
        for (int i = 0; i < display.height() / 2; i += 2) {
            display.drawRect(i, i, display.width() - 2 * i, display.height() - 2 * i,
                             GfxLib.WHITE);
            display.display();
        }
    }

    public void testdrawline() throws IOException {
        for (int i = 0; i < display.width(); i += 4) {
            display.drawLine(0, 0, i, display.height() - 1, GfxLib.WHITE);
            display.display();
        }
        for (int i = 0; i < display.height(); i += 4) {
            display.drawLine(0, 0, display.width() - 1, i, GfxLib.WHITE);
            display.display();
        }
        I2CUtils.I2Cdelay(3000);

        display.clearDisplay();
        for (int i = 0; i < display.width(); i += 4) {
            display.drawLine(0, display.height() - 1, i, 0, GfxLib.WHITE);
            display.display();
        }
        for (int i = display.height() - 1; i >= 0; i -= 4) {
            display.drawLine(0, display.height() - 1, display.width() - 1, i,
                             GfxLib.WHITE);
            display.display();
        }
        I2CUtils.I2Cdelay(3000);

        display.clearDisplay();
        for (int i = display.width() - 1; i >= 0; i -= 4) {
            display.drawLine(display.width() - 1, display.height() - 1, i, 0,
                             GfxLib.WHITE);
            display.display();
        }
        for (int i = display.height() - 1; i >= 0; i -= 4) {
            display.drawLine(display.width() - 1, display.height() - 1, 0, i, 
                             GfxLib.WHITE);
            display.display();
        }
        I2CUtils.I2Cdelay(3000);

        display.clearDisplay();
        for (int i = 0; i < display.height(); i += 4) {
            display.drawLine(display.width() - 1, 0, 0, i, GfxLib.WHITE);
            display.display();
        }
        for (int i = 0; i < display.width(); i += 4) {
            display.drawLine(display.width() - 1, 0, i, display.height() - 1, 
                             GfxLib.WHITE);
            display.display();
        }
        I2CUtils.I2Cdelay(3000);
    }

    private void waitAndClear() throws IOException {
        I2CUtils.I2Cdelay(3000);
        display.clearDisplay();
    }

    @Override
    public void startApp() {
    ...
            display = new GfxLib(0);
            Logger.getGlobal().log(Level.INFO, "OLED Init");

            display.drawChar(5, 5, (byte) 65, 1, 0, 1);
            display.display();
            display.drawChar(5, 20, (byte) 66, 1, 0, 2);
            display.display();
            waitAndClear();

            display.setTextSize(1);
            display.setTextColor(GfxLib.WHITE);
            display.setCursor(0, 0);
            display.print("Test Oled!\n");

            display.setTextSize(1);
            display.setTextColor(GfxLib.BLACK, GfxLib.WHITE);
            display.print("Java ME is Great!\n");
            display.display();
            waitAndClear();

            testdrawcircle();
            waitAndClear();

            testdrawtriangle();
            waitAndClear();

            testdrawline();
            waitAndClear();

            testdrawrect();
            waitAndClear();

            testfillrect();
            waitAndClear();

            testfilltriangle();
            waitAndClear();

            testdrawroundrect();
            waitAndClear();

            testfillroundrect();
            waitAndClear();

        ...

    }

    @Override
    public void destroyApp(boolean unconditional) {
        display.close();
    }
}

Listing 21. Test MIDlet class for the OLED display

Performing Some Additional Configuration

Before running our test MIDlets 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 following permission, as shown in Figure 4:

jdk.dio.spibus.SPIPermission *:* , open

image.png

Figure 4. Establishing API permissions

Conclusion

This is the last part of this series, which gave examples of how to use different types of devices with the Raspberry Pi, from GPIO devices to I2C, UART, and SPI devices. Each article in the series presented many possibilities for connecting sensors and devices to the Raspberry Pi and then using Java ME 8 to control them.

To build on this article, you can connect different analog devices to the MCP3008 channels (remember, it has eight channels). For example, you could connect an analog temperature sensor, such as the TMP35, and light-dependent resistors (LDRs), and then read their values with the Raspberry Pi SPI bus.

If you experiment with connecting other devices, remember to define an enumeration that defines all the commands for each device and a Java ME 8 class that implements all the device's logical rules (gleaned from the device's technical documentation), such as the different values that can be entered into the device's registers to obtain sensor values or operate the device.

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

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!

SPI.zip 60.5K