Saturday, June 28, 2014

Using 2 I2C Buses on the Beaglebone Black

The Beaglebone Black (rev. B or C) has two I2C buses that are accessible by user programs.  This post shows how you can use two I2C devices with the same device address, with each one on a separate bus.  The new version of the Sparkfun TMP102 temperature sensor can be configured to use different I2C device addresses, but this requires removing a solder jumper, so I have used two of them for this example.

In order to use both I2C buses, you need to load a device mapping overlay file, /lib/firmware/BB-I2C1-00A0.dtbo.  This is accomplished via the shell echo command in the code below.

The code below reads the temperature from two TMP102s and prints the reading from each (converted to degrees Fahrenheit) to the terminal window.

In looking into this, I found "BeagleBone Black I2C References" by Joshua Datko very helpful.

The Python code below requires the SMBus library.  The following apt-get command (run as root) will install this library.

apt-get install python-smbus

Connections


TMP102 #1  Beaglebone Black
VCC        D9 3
GND        D9 1
SDA        D9 20  (/dev/i2c-1 bus 1)
SCL        D9 19  (/dev/i2c-1 bus 1)
ADD0       (Not connected)

TMP102 #2 Beaglebone Black
VCC       D9 3
GND       D9 1
SDA       D9 18   (/dev/i2c-2 bus 2)
SCL       D9 17   (/dev/i2c-2 bus 2)
ADD0      (Not connected)

Python Code


# -*- coding: utf-8 -*-
# Need to declare encoding in line above in order to use degree sign
import os
import smbus
import time

# Path to slots on Beaglebone Black rev. C
slots = "/sys/devices/bone_capemgr.9/slots"
os.system("echo BB-I2C1 > %s" % slots)

bus1 = smbus.SMBus(1) # pins D9 19, 20, /dev/i2c-1
bus2 = smbus.SMBus(2) # pins D9 17, 18, /dev/i2c-2

tmp102_addr = 0x48

while(True):
   data = bus1.read_i2c_block_data(tmp102_addr, 0)
   msb = data[0]
   lsb = data[1]
   t1 = (((msb << 8) | lsb) >> 4) * 0.0625
   data = bus2.read_i2c_block_data(tmp102_addr, 0)
   msb = data[0]
   lsb = data[1]
   t2 =  (((msb << 8) | lsb) >> 4) * 0.0625
   print "t1: %.3f°  t2: %.3f°" % (t1 * 9/5 + 32,
      t2 * 9/5 + 32)
   time.sleep(0.5)

Sunday, June 22, 2014

Java 8 Program to Record GPS Data to a JavaDB Database on the Beaglebone Black

I posted yesterday about how to install the Java 8 JDK on the Beaglebone Black.  The Java JDK includes the JavaDB database (previously known as Derby).  This example shows how to read GPS data over a serial connection and record the data to JavaDB database table.  I have developed and tested this code on the Beaglebone Black Rev. C running Debian.  This example assumes that you are working at the Beaglebone Black's command line (using a monitor, keyboard, and mouse connected to the Beaglebone or via SSH).

While JavaDB is included with the JDK, a couple steps are needed to configure it.  I am using JavaDB in network server mode so that it can be accessed simultaneously from programs running in separate JVMs.

I am using the Copernicus II DIP GPS module from Sparkfun.

Configuring JavaDB


Add the following exports to your .bashrc to set the environment variables for JavaDB.  You should already have JAVA_HOME set after installing Java 8.

export DERBY_HOME=$JAVA_HOME/db
export PATH=$PATH:$DERBY_HOME/bin 

Also run the export statements at the command line or log out and back in to add these values to the environment.

Edit the /usr/jdk1.8.0/jre/lib/security/java.policy file to allow the server to accept connections by adding the following line before the closing brace of the grant section.

permission java.net.SocketPermission "localhost:1527", "listen";

Start the JavaDB server with the following command -


$JAVA_HOME/db/bin/startNetworkServer &

Creating the Database Table


This example uses a simple database table called gps_readings in a database named gpsdb.  Run ij to create the table.  At the ij> prompt, run the following command to create the database:

connect 'jdbc:derby://localhost:1527/gpsdb;create=true';

After the DB has been created, you can connect in future using the same connect statement or you can drop the ;create=true to cut down on typing.

Here is what I ran at the ij command prompt to create the gps_readings table:

ij> create table gps_readings (
> utc_time_date timestamp not null,
> lat varchar(15) not null,
> long varchar(15) not null,
> constraint pk_gps_readings_utc_time_date primary key(utc_time_date)
> );


Connecting the Copernicus II GPS Module


GPS Module  Beaglebone Black
VCC         P9 3
GND         P9 1
TX-B        P9 26
RX-B        P9 24

The Java Code


import gnu.io.*;
import java.io.*;
import java.util.*;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.Statement;
import java.time.LocalDate;
import java.time.ZoneId;

public class Gps {
    private static String port = "/dev/ttyS80";
    private InputStream inStream;
    private OutputStream outStream;
    // NMEA command to set Copernicus II to output $GPGLL every second.
    private static String nmeaString = "$PTNLSNM,0002,01*55\r\n"; 

    private Connection connect = null;
    private Statement statement = null;
    private static String driverClass = "org.apache.derby.jdbc.ClientDriver";
    private String jdbcURL = "";
    private static String sql = "INSERT INTO GPS_READINGS(UTC_TIME_DATE, LAT, LONG) VALUES(?,?,?)";

    // Constructor takes JDBC URL for JavaDB server as argument
    public Gps(String url) { jdbcURL = url; }

    public void recordGPS() {
        try {
            // echo & its args have to be called using an array of Strings,
            // just using a single String (as with ln command below) does 
            // not work.
            String[] configPinsCmd = { "bash", "-c", 
                "echo BB-UART1>/sys/devices/bone_capemgr.9/slots" };
            Process p = Runtime.getRuntime().exec(configPinsCmd);
            p.waitFor();
            // RXTXComm library uses /dev/ttyS80, so symbolic link needed
            String lnPortCmd = "ln -s /dev/ttyO1 /dev/ttyS80";
            p = Runtime.getRuntime().exec(lnPortCmd);
            p.waitFor();
            CommPortIdentifier portId = CommPortIdentifier.getPortIdentifier(port);
            SerialPort serialPort = (SerialPort) portId.open("GPS", 5000);
            // Change serial port speed as needed
            serialPort.setSerialPortParams(19200, SerialPort.DATABITS_8,
                SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
            serialPort.setFlowControlMode(SerialPort.FLOWCONTROL_NONE);
            inStream = serialPort.getInputStream();
            outStream = serialPort.getOutputStream();
            byte[] nmeaCmd = nmeaString.getBytes();
            String gpsData = "";
            outStream.write(nmeaCmd, 0, nmeaCmd.length);
            Class.forName(driverClass).newInstance();
            connect = DriverManager.getConnection(jdbcURL);
            PreparedStatement statement = connect.prepareStatement(sql);
            while(true) {
                if(inStream.available() > 0) {
                int b = inStream.read();
                    if(b != 13) {
                        gpsData += (char)b;
                    }
                    else {
                        gpsData = gpsData.trim();
                        System.out.println(gpsData);
                        String[] datum = gpsData.split(",");
                        gpsData = "";
                        // Check for valid $GPGLL NMEA sentence
                        if(datum.length < 8 || !("$GPGLL").equals(datum[0]) || datum[1] == null || 
                               !("A").equals(datum[6])) {
                           continue;
                        }
                        else {
                            LocalDate todayUTC = LocalDate.now(ZoneId.of("UTC"));
                            String t = datum[5].substring(0,2) + '.';
                            t += datum[5].substring(2,4) + '.';
                            t += datum[5].substring(4,6);
                            statement.setString(1, todayUTC.toString() + '-' + t);
                            statement.setString(2, datum[1] + ' ' + datum[2]);
                            statement.setString(3, datum[3] + ' ' + datum[4]);    
                            statement.executeUpdate();
                        }
                    }
                }
            }
        } 
        catch (Exception ex) {
          ex.printStackTrace();
        }
        finally {
            try {
                statement.close();
                connect.close();
            } 
            catch(Exception exc) {
                exc.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        Gps copernicus = new Gps("jdbc:derby://localhost:1527/gpsdb");
        copernicus.recordGPS();
   }
}

Compiling the Code


The Ubuntu distribution that came installed on my Beaglebone Black Rev. C included the Java RXTXComm library.  If you need to get it, you can run the following apt-get command to install it.

apt-get install librxtx-java

Use the following javac command to compile the code -

javac -cp /usr/share/java/RXTXcomm.jar:/usr/jdk1.8.0/db/lib/derby.jar Gps.java

Running the Program at the Command Line


With the JavaDB network server running, use the following command to run the program -

java -Djava.library.path=/usr/lib/jni/ -cp /usr/share/java/RXTXcomm.jar:/usr/jdk1.8.0/db/lib/derbyclient.jar:. Gps

While the program is running, you will see the GPS GLL sentences printed out to the console.  The readings are parsed and saved to the DB table.  You can use ij to connect to the database and run a query against the table to see the entries.  Using JavaDB in network server mode allows you to query the DB table while the program is running. 

Note that the command to run the program needs the java.library.path parameter set to the location of the native library (.so file) for RXTXComm.  Be sure to include the derbyclient.jar (not derby.jar) file in the classpath (-cp).



Saturday, June 21, 2014

Installing Java 8 (JDK 8) on the Beaglebone Black

I just got my new Beaglebone Black Rev. C with 4GB of onboard storage, so I wanted to try installing the Java 8 JDK.  The process is very quick and easy.

The first step is to download the installation package from the Oracle JDK 8 for ARM Downloads page. Remember to select the radio button indicating your acceptance of the license agreement and look for the product  Linux ARM v6/v7 Hard Float ABI (file name jdk-8-linux-arm-vfp-hflt.tar.gz).

If you haven't downloaded the file directly to the Beaglebone, use scp to copy the file to the Beaglebone.

Use the following commands to unzip and untar the archive:

gunzip jdk-8-linux-arm-vfp-hflt.tar.gz
tar xfv jdk-8-linux-arm-vfp-hflt.tar

I put the JDK file tree under the /usr directory (mv jdk1.8.0 /usr/).  If you put it somewhere else, adjust the paths below accordingly.

I added the following lines to my .bashrc file to adjust the path and define JAVA_HOME in the environment:

export JAVA_HOME=/usr/jdk1.8.0
export PATH=$PATH:$JAVA_HOME/bin

Confirm that all is working by running the following command to get the current Java version:

java -version

The result should look like this -

java version "1.8.0"
Java(TM) SE Runtime Environment (build 1.8.0-b132)
Java HotSpot(TM) Client VM (build 25.0-b70, mixed mode)


Friday, June 20, 2014

GPS for the Beaglebone Black: Sparkfun Copernicus II DIP Module - Improved & Tested with Beaglebone Black Rev. C

I published some sample code last October that showed a simple Python script that read NMEA GPS data from the Copernicus II via a serial port.  The code below is a slightly improved version that I have tested with the Beaglebone Black Rev. C (the new model with 4GB NAND) that comes with Debian installed.  This example uses the UART1 overlay included in the /lib/firmware directory.  This code also adds a couple formatting functions for the output.  The output is displayed in the terminal window.

This example assumes that you are working at the command line (via SSH or via an attached monitor, keyboard, and mouse).  This code is in Python and is not intended to run as Bonescript.

Connections



GPS Module  Beaglebone Black
VCC         P9 3
GND         P9 1
TX-B        P9 26
RX-B        P9 24

Code


# -*- coding: utf-8 -*-
# Need to declare encoding in line above in order to use degree sign

import serial
import time
import datetime
import re
import os
from decimal import Decimal

# Use overlay file BB-UART1-00A0.dtbo from /lib/firmware
os.system("echo BB-UART1 > /sys/devices/bone_capemgr.9/slots")

# Adjust the connection speed as needed. 
# You can use Triimble Studio on your PC to detect/set speed
# if you can't figure it out. 
# BB-UART1 maps UART1 to /dev/ttyO1 (note capital letter O, 
# not zero)
serial = serial.Serial("/dev/ttyO1", baudrate=19200)

# Send NMEA config cmd to output only $GPRMC message every second
nmeaCmd = "$PTNLSNM,0100,01*56\r\n"
serial.write(nmeaCmd)

resp = ""

# format UTC time as hh:mm:ss
def formatTime(utc):

        return "%s:%s:%s" % (utc[0:2], utc[2:4], utc[4:6])

# format date as mm/dd/yyyy
def formatDate(date):
        return "%s/%s/20%s" % (date[2:4], date[0:2], date[4:6])

# format latitude as deg°min'sec"
def formatLat(lat):
        deg = lat[0:2]
        min = lat[2:4]
        sec = 60 * Decimal(lat[4:9])
        return "%s°%s'%0.3f\"" % (deg, min, sec)

def formatLong(long):
        deg = long[0:3]
        # If 1st digit in degrees is 0, replace with space
        if deg[0] == '0':
                deg = ' ' + deg[1:]
        min = long[3:5]
        sec = 60 * Decimal(long[5:12])
        return "%s°%s'%0.3f\"" % (deg, min, sec)

while True:
        while (serial.inWaiting() > 0):
                resp += serial.read()
                if "\r\n" in resp:
                        if "$GPRMC" in resp:
                                data = resp.split(',')
                                info = "UTC: %s  Date: %s  N Lat: %s  W Long: %s" % (
                                        formatTime(data[1]), formatDate(data[9]),
                                        formatLat(data[3]), formatLong(data[5]))
                                print info
                        resp = ""

Tuesday, June 3, 2014

C Program to Read Multiple DS18B20 1-Wire Temperature Sensors on a Beaglebone Black

Here is a fairly simple C program that reads from multiple DS18B20 One-Wire temperature sensors and prints the results (device ID and temperature for each) to the terminal.  This example uses a linked list to keep track of the attached sensors rather than fixed arrays.

For help getting started with One-Wire on the Beaglebone Black (including loading the pin configuration), see this post at Hipster Circuits.  This configuration is required before continuing.

Connections


Looking at the flat side of the DS18B20's plastic head, connect the left pin to ground on the Beaglebone Black (P9-1), the right pin to 3V3 (P9-3), and the center pin to P9-22.  A 4.7k Ohm pull-up resistor is required on the connection of the first sensor's center pin to P9-22.  If using multiple sensors, each needs to be connected to the voltage and ground; parasitic power mode does not seem to be supported.  The center (data) pins need to be connected together (with the pull-up on the connection to the BB Black).

C Code


#include <stdio.h>
#include <dirent.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
     
// struct to hold ds18b20 data for linked list
struct ds18b20 {
char devPath[128];
char devID[16];
char tempData[6];
struct ds18b20 *next;
};

// Find connected 1-wire devices. 1-wire driver creates entries for each device 
// in /sys/bus/w1/devices on Beaglebone Black.  Create linked list.
int8_t findDevices(struct ds18b20 *d) {
DIR *dir;
        struct dirent *dirent;  
struct ds18b20 *newDev;
        char path[] = "/sys/bus/w1/devices";
        int8_t i = 0;
        dir = opendir(path);
        if (dir != NULL)
        {
                while ((dirent = readdir(dir))) {
                        // 1-wire devices are links beginning with 28-
                        if (dirent->d_type == DT_LNK &&
                                        strstr(dirent->d_name, "28-") != NULL) {
newDev = malloc( sizeof(struct ds18b20) );
                                strcpy(newDev->devID, dirent->d_name);
                                // Assemble path to OneWire device
                                sprintf(newDev->devPath, "%s/%s/w1_slave", path, newDev->devID);
                                i++;
newDev->next = 0;
d->next = newDev;
d = d->next;
                        }
}
(void) closedir(dir);
        }
        else
{
                perror ("Couldn't open the w1 devices directory");
                return 1;
        }
return i;
}

int8_t readTemp(struct ds18b20 *d) {
while(d->next != NULL){
d = d->next;
int fd = open(d->devPath, O_RDONLY);
if(fd == -1)
        {
        perror ("Couldn't open the w1 device.");
                return 1;
        }
char buf[256];
ssize_t numRead;
        while((numRead = read(fd, buf, 256)) > 0) {
                strncpy(d->tempData, strstr(buf, "t=") + 2, 5);
                float tempC = strtof(d->tempData, NULL);
                printf("Device: %s  - ", d->devID);
                printf("Temp: %.3f C  ", tempC / 1000);
                printf("%.3f F\n\n", (tempC / 1000) * 9 / 5 + 32);
        }
        close(fd);
}
return 0;
}

int main (void) {
struct ds18b20 *rootNode;
struct ds18b20 *devNode;
// Load pin configuration. Ignore error if already loaded
system("echo BB-W1 > /sys/devices/bone_capemgr.9/slots 2>/dev/null");
while(1) {
rootNode = malloc( sizeof(struct ds18b20) );
devNode = rootNode;
int8_t devCnt = findDevices(devNode);
printf("\nFound %d devices\n\n", devCnt);
readTemp(rootNode);
// Free linked list memory
while(rootNode) {
// Start with current value of root node
devNode = rootNode;
// Save address of next devNode to rootNode before deleting current devNode
rootNode = devNode->next;
// Free current devNode.
free(devNode);
}
// Now free rootNode
free(rootNode); 
}
return 0;
}

Compiling & Running the Code

Assuming you have the code above saved in a file called w1m.c, run the following command at the Beaglebone Black's command line to compile it and produce an executable called w1m

 gcc -Wall -o w1m w1m.c

The code should compile without warnings or errors.  You can then run ./w1m to see the output.  The program reports the number of sensors found and then lists the ID and temperature reading for each one.  If you add a sensor to the circuit, it should be detected in about a minute.  If you remove a sensor or two, they will be dropped from the list after a couple minutes, but will show a false temperature near freezing until the driver removes the entry for the disconnected sensor(s).

Press Ctrl-C to stop execution.

Monday, June 2, 2014

Installing the Java 8 Embedded JRE on the pcDuino 2

Download the Java 8 embedded JRE from http://www.oracle.com/technetwork/java/embedded/downloads/javase/.  I usually download files to my desktop computer and then use SCP to copy the files to my pcDuino, though you can, of course, download directly to the pcDuino if you have a screen, keyboard, and mouse set up.

1. Check the "Accept" box in the section OTN LICENSE AGREEMENT

2. Scroll down to the section ORACLE JAVA SE EMBEDDED VERSION 8.  For the pcDuino, download "ARMv7 Linux - VFP, HardFP ABI, Little Endian." The name on the download link is ejdk-8-fcs-b132-linux-arm-vfp-hflt-03_mar_2014.tar.gz.

3. Use gunzip and tar to extract the installation files.

4. Set the JAVA_HOME environment variable to point to the JRE in the directory tree just extracted.  In my case, this is what I used.  Change the path to match where you extracted the files on your pcDuino.


    export JAVA_HOME=/media/BBerkland2/ejdk1.8.0/linux_arm_vfp_hflt/jre/

 5.  Now run the jrecreate.sh script.  I used the following command to install all aspects of the JRE.  See this document for configuration options.  Again, update that path to jrecreate.sh to match your circumstances.  The destination directory for installation (--dest) should not already exist.  

  /media/BBerkland2/ejdk1.8.0/bin/jrecreate.sh --dest /media/BBerkland2/Java8/

The installation process may take 10 - 15 minutes, and there isn't any indication of progress.  Just let it run.  When you see the command prompt, the installation is complete.

Update your .bashrc file to update your path and permanently export the JAVA_HOME environment variable.  

You can quickly confirm that all is well by running java -version. 

Note that there is no Java 8 compiler (javac) for the pcDuino, so you will need to do development work on another machine.