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

Version 5

    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. Part 2 focused on using inter-integrated circuit bus (I2C) interfaces to connect sensors.

     

    This article focuses on using universal asynchronous receiver/transmitter (UART) interfaces to connect new modules that do the following:

     

     

    In addition, because we won't have all UART interfaces you need, this article describes how to convert an I2C interface to UART using the SparkFun I2C/SPI-to-UART breakout board (SC16IS750). We'll also use a little mono audio amplifier, the SparkFun module TPA2005D1.

     

    Note: The complete example code for this NetBeans IDE 8.0.2 project can be downloaded from the attached zip file at the bottom of this article.

     

    Enabling UART on the Raspberry Pi

     

    UART is a serial port that consists of two signals—a transmit signal (TxD) and a receive signal (RxD)—made available on pin 8 (BCM or GPIO 14) and pin 10 (BCM or GPIO 15), respectively, on the GPIO header. You can view the Raspberry pinout here.

     

    Remember that all GPIO pins operate at 0 and 3.3 V logic levels, not the common RS-232 serial port level of +/- 12 V. If you wish to connect a UART device, you need a board or adapter to convert the higher signal levels to the lower GPIO signal levels. For example, you can use a MAX3232CPE transceiver and some capacitors. See this tutorial.

     

    By default, the Raspberry Pi uses its built-in UART interface as the serial console. To have control of it, we need to run a few commands to release it.

     

    First, back up the /boot/cmdline.txt file by running the following command:

     

    $ sudo cp /boot/cmdline.txt /boot/cmdline_backup.txt

     

    Then, enter the following command from the command line to edit the file.

     

    $ sudo nano /boot/cmdline.txt

     

    Change the following line in the file, from this:

     

    dwc_otg.lpm_enable=0 console=ttyAMA0,115200 kgdboc=ttyAMA0,115200 
    console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline rootwait
    

     

    to this:

     

    dwc_otg.lpm_enable=0 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 
    elevator=deadline rootwait
    

     

    Next, enter the following command from the command line to edit the /etc/inittab file:

     

    $ sudo nano /etc/inittab

     

    Then change the following line in the file from this:

     

    #Spawn a getty on Raspberry Pi serial line
    T0:23:respawn:/sbin/getty -L ttyAMA0 115200 vt100
    

     

    to this (to comment out the second line):

     

    #Spawn a getty on Raspberry Pi serial line
    #T0:23:respawn:/sbin/getty -L ttyAMA0 115200 vt100
    

     

    As the last step, reboot the Raspberry Pi.

     

     

    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.

     

    image001.gif

     

    Figure 1. Block diagram of the circuits we will create

     

    image002.gif

     

    Figure 2. Components we will use

     

    image003.gif

     

    Figure 3. Schematic of the circuits we will create

     

     

    Connecting the GPS Receiver Engine Board

     

    The EM-406a is a compact, high-performance, low power consumption GPS engine board. It uses the SiRF Star III LP Single GPS chipset, which can track up to 20 satellites at a time, and has the following features:

     

    • Very high sensitivity (tracking sensitivity: -159 dBm)
    • Extremely fast TTFF (Time To First Fix) at low signal levels
    • Support for the NMEA 0183 data protocol
    • Built-in SuperCap to maintain system data for rapid satellite acquisition
    • Built-in patch antenna
    • Foliage lock for weak signal tracking
    • All-in-view 20-channel parallel processing
    • Snap lock 100ms reacquisition time

     

    Let's now connect the device to the Raspberry Pi's GND, GPS Tx to UART RxD, GPS Rx to UART TxD, and VCC 5v pins, as shown in Figure 3, and create a Java ME 8 class called GPSEM406ADevice to control the GPS device. See Listing 1.

     

    public class GPSEM406ADevice {
    
        private UARTConfig config=null; //UART config parameters object
        private UART uart = null;       //UART object
        
        private static String nmea = ""; //NMEA protocol data read from UART event listener
        
        private String time = null; //Time from GPS
        private final String longitude[] ={null, null};     //Longitude read from GPS
        private final String latitude[] = {null, null};     //Latitude read from GPS
        private final String altitude[] = {null, null};     //Altitude read from GPS
    

     

    Listing 1. Creating the GPSEM406ADevice class

     

    Next, create a class constructor that initializes and activates UART using the DeviceManager API and the UARTConfig class (see Listing 2) to establish the following conditions for serial communication and activate the input data listener to receive status from the GPS device.

     

    • Controller Number: DeviceConfig.DEFAULT
    • Channel Number: DeviceConfig.DEFAULT
    • Baud Rate: 4800
    • Data Bits: DATABITS_8
    • Parity: PARITY_NONE
    • Stop Bits: STOPBITS_1
    • Flow control: FLOWCONTROL_NONE

     

    public GPSEM406ADevice() throws IOException {
            //Config UART
            config = new UARTConfig(DeviceConfig.DEFAULT, DeviceConfig.DEFAULT, 4800,
                    DATABITS_8, PARITY_NONE, STOPBITS_1, FLOWCONTROL_NONE);
            //Create UART object
            uart = (UART) DeviceManager.open(UART.class, config);
            // Initialize GPS to talk NMEA protocol
            GPS_Switch_Mode_To_NMEA();
            //Create read data listener to UART 
            uart.setEventListener(UARTEvent.INPUT_DATA_AVAILABLE, new MyUARTListener());
    }
    

     

    Listing 2. Establishing the initial conditions for the GPS device and UART

     

    The GPS_Switch_Mode_To_NMEA method (see Listing 3) initializes the GPS device to talk the NMEA protocol by using several byte commands (see explanation here) that establishes this type of text protocol.

     

     

    private static byte[] hexStringToByteArray(String s) {
            int len = s.length();
            byte[] data = new byte[len / 2];
            for (int i = 0; i < len; i += 2) {
                data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
                        + Character.digit(s.charAt(i + 1), 16));
            }
            return data;
     }
        
    private void GPS_Switch_Mode_To_NMEA() {
            byte[] data = hexStringToByteArray("A0A20018810201010001010105010101000100010001000100012580013AB0B3");
            ByteBuffer buffer = ByteBuffer.allocateDirect(data.length);
            buffer.put(data);
            buffer.clear();
    ...
                uart.write(buffer);
                I2CUtils.I2Cdelay(10000);
                Logger.getGlobal().log(Level.FINE, "GPS Config Ok...");
    ...
    }
    

     

    Listing 3. Method to initialize the GPS device to talk the NMEA protocol

     

    Then create the following format and get methods (see Listing 4):

     

    • formatTime: Format read time
    • formatLat: Format read latitude
    • formatLong: Format read longitude
    • formatAlt: Format read altitude
    • getTime: Read time
    • getLatitude: Read latitude
    • getLongitude: Read longitude
    • getAltitude: Read altitude

     

    private String formatTime(String time) {
       return (time != null) ? time.substring(0, 2) + ":" + time.substring(2, 4) + ":" + 
    time.substring(4, 6) : "";
    }
    
    private String formatLat(String[] pos) {
       return ((pos[0] != null) && (pos[1] != null)) ? pos[0].substring(0, 2) + 
    "* " + pos[0].substring(2, 4) + "' " + pos[0].substring(5, 9) + "\" " + pos[1] : "";
    }
    
    private String formatLong(String[] pos) {
       return ((pos[0] != null) && (pos[1] != null)) ? pos[0].substring(0, 3) + 
    "* " + pos[0].substring(3, 5) + "' " + pos[0].substring(6, 10) + "\" " + pos[1] : "";
    }
    
    private String formatAlt(String[] alt) {
       return ((alt[0] != null) && (alt[1] != null)) ? alt[0] + " " + alt[1] : "";
    }
    
    public String getTime() {
       return formatTime(time);
    }
    
    public String getLongitude() {
       return formatLong(longitude);
    }
    
    public String getLatitude() {
       return formatLat(latitude);
    }
    
    public String getAltitude() {
       return formatAlt(altitude);
    }
    

     

    Listing 4. Format and get methods

     

    To read all UART data, we need to create a listener class called MyUARTListener that implements UARTEventListener with a synchronized method eventDispatched (see Listing 5).

     

    class MyUARTListener implements UARTEventListener {
            @Override
            public synchronized void eventDispatched(UARTEvent event) {
                if (event.getID() == INPUT_DATA_AVAILABLE) {
                    ByteBuffer buffer = ByteBuffer.allocateDirect(10);
                    try {
                        int nrocar = uart.read(buffer);
                        char c;
    
                        for (int i = 0; i < nrocar; i++) {
                            c = (char) buffer.get(i);
                            nmea = nmea.concat(String.valueOf(c));
                        }
    
                        if (nmea.contains("\r\n")) {
                            //System.out.println(nmea);
                            if (nmea.contains("$GPGGA")) {
                                processNMEA(nmea);
                            }
                            nmea = "";
                        }
                    } catch (IOException ex) {
                        Logger.getGlobal().log(Level.WARNING, ex.getMessage());
                    }
                }
            }
    }
    

     

    Listing 5. Listener class MyUARTListener

     

    When data is available, we parse it with the processNMEA method (see Listing 6). We use this method to read all the GPS data into a special String that begins with the $GPGGA tag.

     

    private void processNMEA(String message) {
        try {
            StringTokenizer tokens = new StringTokenizer(message, ",");
            // pull off the first token and check if it is the message we want
            //$GPGGA,130612.255,,,,,0,00,,,M,0.0,M,,00 (no info)
            //$GPGGA,161229.487,3723.2475,N,12158.3416,W,1,07,1.0,9.0,M,,,,0000*18 (with info)
    
            tokens.nextToken(); // $GPGGA position
            // Next token is the time
            time = tokens.nextToken();
            latitude[0] = tokens.nextToken();
            latitude[1] = tokens.nextToken();
            if ((latitude[0].equals("0")) || (latitude[1].equals("00"))) {
                Logger.getGlobal().log(Level.FINE, "Time: " + formatTime(time) + " - No GPS satellite link...");
            } else {
                longitude[0] = tokens.nextToken();
                longitude[1] = tokens.nextToken();
                // Skip the next three tokens
                tokens.nextToken(); // Position indicator
                tokens.nextToken(); // Satellites used
                tokens.nextToken(); // Horizontal Dilution of precision
                altitude[0] = tokens.nextToken();
                altitude[1] = tokens.nextToken();
                Logger.getGlobal().log(Level.FINE, "Time: " + getTime() + "\n"
                    + " Latitude: " + getLatitude() + " Longitude: " + getLongitude() + "\n"
                    + " Altitude: " + getAltitude() + "\n");
            }
        } catch (Exception ex) {
        }
    }
    

     

    Listing 6. Parser method processNMEA

     

    Finally, free all resources by closing the UART object (see Listing 7).

     

    public void close() {
    ...
       uart.setEventListener(UARTEvent.INPUT_DATA_AVAILABLE, null);
       uart.close();
    ...
    }
    

     

    Listing 7. Closing the UART object and freeing the listener

     

    Let's now create the Test MIDlet class called TestGPSEM406A to receive the time, latitude, longitude, and altitude from satellites, as shown in Listing 8.

     

    public class TestGPSEM406A extends MIDlet {
    
        private LoggingHandler loggerHandler = LoggingHandler.getInstance();
        private GPSEM406ADevice gps;
    
        @Override
        public void startApp() {
            loggerHandler.start();
            Logger.getGlobal().setLevel(Level.ALL);
            try {
                gps = new GPSEM406ADevice();
    
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
        @Override
        public void destroyApp(boolean unconditional) {
            gps.close();
        }
    }
    
    

     

    Listing 8. Test MIDlet for the EM-406a GPS device

     

    Converting I2C Interface to UART by Connecting the I2C/SPI-to-UART Breakout Board

     

    This is a breakout board for the SC16IS750, which is a handy chip used to convert I2C or SPI serial signals to a single-channel, high-performance UART interface.  The input pins of the SC16IS750 are 5.5 V–tolerant, so this board should work with both 3.3 V and 5 V controllers. The following are some of its general features:

     

    • Single full-duplex UART interface
    • Selectable I2C-bus or SPI interface
    • 64-byte first-in, first-out (FIFO) transmitter and receiver
    • Full compatibility with industrial standard 16C450 and equivalent
    • Baud rates up to 5 Mb/sec in 16x clock mode
    • Auto hardware flow control using RTS/CTS
    • Auto software flow control with programmable Xon/Xoff characters
    • Single or double Xon/Xoff characters
    • Receive and transmit FIFO levels
    • Programmable special character detection
    • Line break generation and detection
    • Software reset

     

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

     

    When LCR bit 7 = 0:

     

    • 00h: XHR (Receive/transmit holding register)
    • 02h: FCR (FIFO control register)
    • 03h: LCR (line control register)
    • 04h: MCR (Modem control register)
    • 05h: LSR (line status register)
    • 06h: MSR (modem status register)
    • 08h: XLVL (transmit FIFO level register)
    • 09h: RXLVL (receive FIFO level register)

     

    When LCR bit 7 = 1:

     

    • 00h: DLL
    • 01h: DLH (division register)

     

    To support these registers, create an enumeration called SC16IS750, as shown in Listing 9.

     

    public enum SC16IS750 {
        XHR(0x00),
        FCR(0x02),
        LCR(0x03),
        MCR(0x04),
        LSR(0x05),
        MSR(0x06),
        TXLVL(0x08),
        RXLVL(0x09),
        DLL(0x00),
        DLH(0x01);
    
        public int cmd;
    
        private SC16IS750(int cmd) {
            //SC16IS740 expects a R/W bit first, 
            //followed by the 4 bit register address of the byte.
            //So shift the bits left by three bits:
            this.cmd = cmd << 3;
        }
        ...
    }
    

     

    Listing 9. Enumeration that defines all SC16IS750 registers

     

    Let's now connect the SC16IS750 breakout board to the Raspberry Pi's 3.3 V, SCL, SDA, and GND pins, as shown in Figure 3, and create a Java ME 8 class called SC16IS750Device with its constructor using the control address to initialize the converter registers, as shown in Listing 10.

     

    public class SC16IS750Device extends I2CRpi {
    
        private static final int SC16IS750_write = 0x48;
    
        public SC16IS750Device() throws IOException {
            super(SC16IS750_write);
            configUARTregs();
        }
    

     

    Listing 10. SC16IS750Device class with its constructor establishing the control address

     

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

     

    • configUARTregs: Configure all registers to establish I2C-to-UART conversion
    • write: Write a String to buffer data
    • bytesToRead: Bytes at buffer to be read
    • read: Read all bytes at buffer

     

    private void configUARTregs() {
    ...
         //Line Control Register: Enable Writing DLH & DLL
         //& set no Parity, 1 stop bit, and 8 bit word length
         SC16IS750.LCR.write(device, (byte) 0b10000011);
    
         //Division registers DLL & DLH
         // Write '96' to get 9600 baud rate
         // Assumes you have the version with the ~14MHz crystal
         // (16x9600 = 153600 = 14.7456Mhz/96)
         SC16IS750.DLL.write(device, (byte) 96);
         SC16IS750.DLH.write(device, (byte) 00);
         
         //Line Control Register: Disable Writing DLH & DLL
         //Same setup 
         SC16IS750.LCR.write(device, (byte) 0b00000011);
    
         //Modem Control Register
         //Normal Operating Mode
         SC16IS750.MCR.write(device, (byte) 0b00000000);
    
         //FIFO Control Register: Enable the FIFO and no other features
         SC16IS750.FCR.write(device, (byte) 0b00000111);
    ...
    }
    
    public void write(String cad) {
         ByteBuffer buffer = ByteBuffer.allocateDirect(cad.length());
         buffer.put(cad.getBytes());
         buffer.clear();
         try {
             device.write(SC16IS750.XHR.cmd, 1, buffer);
         } catch (IOException ex) {
              Logger.getGlobal().log(Level.WARNING,ex.getMessage()); 
         }
    }
    
    public int bytesToRead(){
        return SC16IS750.RXLVL.read(device);
    }
        
    public int read(){
        return SC16IS750.XHR.read(device);
    }
    

     

    Listing 11. Operation methods

     

    Connecting the Text-to-Speech Module to the I2C/SPI-to-UART Breakout Board

     

    The Emic 2 text-to-speech module is a multilanguage voice synthesizer that converts a stream of digital text into natural sounding speech. Its simple command-based interface makes it easy to integrate into any embedded system.

     

    Here are some of its general features.

     

    • High-quality speech synthesis for English and Spanish languages
    • Nine predefined voice styles comprising male, female, and child
    • Dynamic control of speech and voice characteristics, including pitch, speaking rate, and word emphasis
    • Industry-standard DECtalk text-to-speech synthesizer engine (5.0.E1)
    • Communication: Asynchronous 9600 b/sec serial (8N1)
    • On-board audio power amplifier and 1/8-inch (3.5 mm) audio jack

     

    Let's now connect the Emic 2 module's GND, SOUT, and SIN pins to the SC16IS750 breakout board's GND, RX, and TX pins, respectively, and connect its 5 V pin to the Raspberry Pi's 5 V pin, as shown in Figure 3. Then, create a Java ME 8 class called  EMIC2Device with its constructor that creates an SC16IS750 object using class SC16IS750Device, as shown in Listing 12.

     

    public class EMICI2CDevice {
    
        private SC16IS750Device sc;
    
        // Define all messages for Emic
        private final String[] emic2Msgs = {
            "Emic 2 Ok.", //0
            "Hello my friends.", //1
            "I am testing I2C to UART converter.", //2
            "See you later.", //3
            "Bye.", //4
            "Hola como estan.", //5
            "Ahora hablo Espanol.", //6
            "Adios amigos."//7    
        };
    
        public EMICI2CDevice() throws IOException {
            //I2C Emic interface using SC16IS750
            sc = new SC16IS750Device();
        }
    

     

    Listing 12. EMIC2Device class with its constructor creating an SC16IS750 object

     

    Create the following operation methods (see Listing 13):

     

    • msg: Write a predefined message
    • getMsg: Get a predefined message
    • write: Write a String
    • writeCommand: Send a command
    • waitResponse: Wait for Emic 2 response characters
    • close: Close UART communication

     

    public void msg(int msgnum) {
        write(emic2Msgs[msgnum]);
        Logger.getGlobal().log(Level.FINE, emic2Msgs[msgnum]);
    }
    
    public String getMsg(int num) {
            return emic2Msgs[num];
    }
    
    public void write(String cad) {
        cad = "S " + cad.concat("\r\n");
        sc.write(cad);
        // Wait for response from Emic-2. It responds to all commands with :
        waitResponse(2);
    }
    
    public void writeCommand(String cad) {
        cad = cad.concat("\r\n");
        sc.write(cad);
        // Wait for response from Emic-2. 
        waitResponse(5);
    }
    
    private void waitResponse(int nrobytes) {
        int work = 0;
        //wait two chars :\n
        while (work != nrobytes) {
            work = sc.bytesToRead();
        }
        for (; 0 < work; work--) {
            sc.read();
        }
    }
    
    public void close() {
        sc.close();
    }
    

     

    Listing 13. Operation methods

     

    We can connect an headphone to the onboard 1/8-inch (3.5 mm) audio jack, but if we want to use a little speaker, the SparkFun mono audio amplifier module TPA2005D1 is recommended, as shown in Figure 3, with an additional 10K volume control potentiometer. We must connect SP- and SP+ from the Emic 2 to IN- and IN+ on the amplifier module's connector.

     

    Let's now create the test MIDlet class called TestEMIC2 to hear the Emic 2 speak predefined messages in English and Spanish, as shown in Listing 14.

     

    public class TestEMIC2 extends MIDlet {
    
        @Override
        public void startApp() {
    ...
                EMICI2CDevice sc = new EMICI2CDevice();
    
                sc.writeCommand("W200");
                sc.writeCommand("L0"); //Set English
                sc.writeCommand("N0");
                sc.msg(0);
                sc.msg(1);
                sc.msg(2);
                sc.msg(3);
                sc.msg(4);
                sc.write("End of English Test.");
                
                sc.writeCommand("W200");
                sc.writeCommand("L1"); //Set Spanish
                sc.writeCommand("N0");
                sc.msg(5);
                sc.msg(6);
                sc.msg(7);
                sc.write("Fin de la prueba en Espanol.");
    
                sc.close();
        ...
        }
    ...
    }
    

     

    Listing 14. Test MIDlet for Emic 2

     

    Performing Some Additional Configuration

     

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

     

    jdk.dio.uart.UARTPermission *:* , open
    

     

    Figure4.jpeg

     

    Figure 4. Establishing API permissions

     

    Conclusion

     

    The Raspberry Pi has a UART port that can be used for serial communication with some devices such as the EM-406a GPS device, but remember that it is very important to check voltage levels, because this port works at 3.3 V, not at the classic serial level of +/- 12 V.

     

    By default, the UART port is used by the serial console and it must be released before you can manipulate it.

     

    If you need to connect additional serial devices, you can use SC16IS750 modules that convert the I2C or SPI protocols to UART. You can use up to fifteen modules with distinct I2C addresses, and you can manipulate up to fifteen serial devices.

     

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

     

    See Also

     

     

    About the Author

     

    Jose Cruz (@joseacruzp) 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!