Java ME 8 + Raspberry Pi + Sensores = Un Mundo IoT (Parte 2)

Por José Cruz
Publicado en Septiembre 2016

En la Parte 1 de esta serie se explicó como conectar sensores electrónicos al Raspberry Pi Modelo B usando interfaces (GPIO) de entrada/salida. (Este artículo y el resto de estas series también aplican para el Raspberry Pi Modelo B+ o el Raspberry Pi 2 Modelo B)

Este artículo se enfoca en el uso de la interfaz I2C para conectar los sensores, y muestra ejemplos de cómo desarrollar clases de Java ME 8 que pueden:

Nota: El código de ejemplo completo para este proyecto NetBeans IDE 8.0.1 puede ser descargado aquí.

Habilitar la interfaz I2C en el Raspberry Pi

I2C es un transporte serial multimaestro, este conecta los periféricos de baja velocidad a la tarjeta madre, celulares, sistemas incrustados o aparatos electrónicos. Esto también se conoce como una interface de doble hilo. Revise las especificaciones del bus 12C para más información.

I2C utiliza solo dos líneas bidireccionales, Línea de Data Serial (SDA) y Reloj Serial (SCL), las cuales a menudo están cargadas mediante resistencias en forma de “pull-up” hacia la línea de voltaje positivo, conocida como VDD.

Para habilitar I2C en el Raspberry Pi, necesitamos configurar el kernel para cargar los módulos automáticamente en el arranque, modificando el archivo /etc/modules añadiendo dos líneas nuevas, como se muestra en el Listado 1. Luego se guarda el archivo y se reinicia

$ sudo nano /etc/modules 
i2c-bcm2708

i2c-dev 

$ sudo reboo

Listado 1. Configurando el kernel para la carga de los módulos y activación de I2C.

A seguir, necesitamos instalar las herramientas de I2C y hacer cualquier usuario miembro del grupo I2C, como se muestra en el Listado 2:

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

Listado 2. Instalando las herramientas y añadiendo usuarios.

Para verificar si las herramientas I2C están correctamente instaladas, se usa el comando mostrado en el Listado 3:

$ sudo i2cdetect –y 1


Listado 3. Verificando si las herramientas están correctamente instaladas
.

La Figura1. Muestra una lista de todas las direcciones de los dispositivos conectados a nuestro Raspberry PI.


Figura 1. Lista de todos los dispositivos I2C conectados a Raspberry Pi.

Antes de conectar un sensor al bus I2C, es muy importante obtener la información sobre la dirección del sensor y todos los registros que este soporta, consultando la hoja de notas técnicas del sensor. Usando los links mencionados anteriormente, se puede encontrar la documentación de cada sensor usado en este artículo.

Circuitos que crearemos

Basado en el diagrama mostrado en la Figura 2 y los componentes mostrados en la Figura 3, crearemos los circuitos mostrados en la Figura 4


Figura 2. Diagrama de bloques de los circuitos que vamos a crear.


Figura 3. Componentes de vamos a utilizar.


Figura 4. Esquema de los circuitos que vamos a crear.

Resumen del código que crearemos

Las siguientes subsecciones presentan una vista del código que escribiremos para controlar los sensores y para leer o escribir data desde o hacia ellos respectivamente.

Usando un Enumerado para definir todos los registros que estaremos manipulando

Para cada dispositivo sensor que emplee la interface I2C, crearemos una clase de tipo enumerado que defina todos los registros que estaremos manipulando en el dispositivo. La clase enumerado tendrá tres componentes:

public enum DeviceNameXXXXX {

Listado 4. Clase de tipo Enumerado

El código para la enumeración hará lo siguiente:

  • Define el nombre de cada registro del dispositivo:
NAME(value)

Listado 5. Definiendo el nombre del registro.

  • Define una variable publica que almacena los valores del registro:
public byte cmd;

Listado 6. Definiendo una variable publica para almacenar el valor del registro.

  • Define un constructor
Private DeviceName XXXXX(int cmd) {
this.cmd = (byte) cmd;
}

Listado 7. Definiendo un constructor.

  • Define métodos read y write para leer valores y escribir valores de los registros.
public int read(I2CDevice device) {
return I2CUtils.read(device, this.cmd);
}

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

Listado 8. Definiendo métodos read y write.

Usando una clase Device para implementar todos las operaciones de control de sensores

Para cada dispositivo sensor, crearemos una clase para implementar todos las operaciones de control de sensores. Esta clase necesita extender clase I2Crpi, la cual define un dispositivo en una dirección específica y almacena la dirección I2C en una variable publica device , mostrado en el Listado 9

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

Listado 9. Definición de la clase I2CRpi.

Crearemos una clase de constructor, que inicializa y activa la comunicación para una dirección I2C específica usando el API DeviceManager y la clase I2CdeviceConfig para establecer las siguientes condiciones (ver Listado 10):

  • Numero controlador: El número del bus del dispositivo conectado (un valor entero positivo o cero) o DeviceConfig.DEFAULT
  • Dirección I2C: La dirección I2C del dispositivo esclavo en el bus.
  • Tamaño de la dirección: tamaño de la dirección en bits (ADDR_SIZE_7 or ADDR_SIZE_10) o DeviceConfig.DEFAULT
  • Frecuencia del reloj: La frecuencia del reloj del dispositivo esclavo en Hz o DeviceConfig.DEFAULT
public I2CRpi(int i2cAddress) throws IOException {
config = new I2CDeviceConfig(DeviceConfig.DEFAULT,
i2cAddress,
DeviceConfig.DEFAULT,
DeviceConfig.DEFAULT);

device = (I2CDevice) DeviceManager.open(I2CDevice.class, config);
}


Listado 10. Estableciendo las condiciones iniciales para el dispositivo sensor
.


También es importante cerrar un dispositivo para liberar los recursos como se muestra en el Listado 11

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

Listado 11. Cerrando los recursos de los dispositivos.

Esto es todo lo que necesitamos para crear un enumerado y una clase de dispositivo para cada sensor.

Conectando un dispositivo tipo Servo

El PCA9685 16-channel 12-bit PWM es un circuito de Adafruit usado para controlar hasta dieciséis motores servo sobre una interface I2C en la dirección 0x41. Para adjunta esta dirección se necesita hacer una conexión usando un punto de soldadura en el pin A0 (mostrado en rojo en la Figura 4), si necesitamos configurar otra dirección, se debe conectar otro pin por ejemplo de dirección 0x42 pin A1, 0x43 pines A0 y A1, como combinación binaria.

Para evitar posibles daños al Raspberry Pi, se debe usar una fuente de voltaje externa para el motor servo y usar un regulador de 5 voltios, LM7805, como se muestra en la figura 4.

Este dispositivo tiene los siguientes registros (direcciones son mostradas en hexadecimal):

  • 00h: Mode 1
  • 06h a 45h: LED_ON y LED_OFF registros de control
  • FEh: Prescaler para programar la frecuencia de salida

Para soportar este registro, crearemos un enumerado llamado PCA9685, como se muestra en el Listado 12.

public enum PCA9685 {
//Define registro names
MODE1(0x0),
PRESCALE(0xFE),
LED0_ON_L(0x6),
LED0_ON_H(0x7),
LED0_OFF_L(0x8),
LED0_OFF_H(0x9);

public byte cmd;//Public variable that stores registro value 
private PCA9685(int cmd) { //Constructor 
this.cmd = (byte) cmd;
}

public int read(I2CDevice device) { //Read and write methods 
return I2CUtils.read(device, this.cmd);
}

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

Listado 12. Enumerado que define todos los registros del control de los motores servo

Ahora vamos a conectar el dispositivo a los pines GND, SCL, SDA, and VCC 3.3v del Raspberry Pi, como se muestra en la figura 4, y crear una clase de Java ME 8 llamada PCA9685Device para controlar el motor servo, por ejemplo un modelo de servo estándar 2730766 de RadioShack, adjunto al canal 0. Ver el Listado 13.

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

Listado 13. Creando la clase PCA9685Device.

Ahora se define un constructor, este crea un dispositivo en la dirección definida y llama a la operación de reinicio usando el Modo 1, como se muestra en el Listado 14. 

public PCA9685Device() throws IOException {
super(PWMServoDriverAddr);
reset();
}

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

Listado 14. Estableciendo la dirección de control y reinicio.

Creamos el método setPWMFreq para definir la frecuencia PWM desde la tabla de control, por ejemplo, un RGB LED conectado a cualquier canal. El método setPWM se utiliza para controlar un motor servo conectado a el puerto num y recibir data para establecer el pulso PWM; Ver Listado 15.

public void setPWMFreq(float freq)  { 
// Set PWM frequency float prescaleval = 25000000.0F; prescaleval /= 4096.0F; prescaleval /= freq;
prescaleval -= 1.0;
Logger.getGlobal().log(Level.FINE,"Estimated pre-scale: " + prescaleval); float prescale = (float)
Math.floor(prescaleval + 0.5);
Logger.getGlobal().log(Level.FINE,"Final pre-scale: " + prescale);

int oldmode = PCA9685.MODE1.read(device); int newmode = (oldmode & 0x7F) | 0x10; 
// sleep PCA9685.MODE1.write(device, (byte) newmode);
// go to sleep PCA9685.PRESCALE.write(device, (byte) prescale);
// set the prescaler PCA9685.MODE1.write(device, (byte) oldmode);

I2CUtils.I2Cdelay(5); 
//This sets the MODE1 registro to turn on autoincrement.PCA9685.MODE1.write(device,(byte)(oldmode|0x80));}

//Set PWM pulse to control servo motor public void setPWM(byte num, short on, short off){
I2CUtils.writedevice,(byte)(PCA9685.LED0_ON_L.cmd+4*num),(byte)(on & 0xFF));
I2CUtils.writedevice,(byte) (PCA9685.LED0_ON_H.cmd+4*num), (byte) (on >> 8));
I2CUtils.writedevice,(byte)(PCA9685.LED0_OFF_L.cmd+4*num), (byte)(off & 0xFF));
I2CUtils.writedevice,(byte)(PCA9685.LED0_OFF_H.cmd+4*num), (byte) (off >> 8)); } }

Listado 15, Estableciendo frecuencia y el pulso PWN para controlar el motor servo.

Ahora expenderemos la clase MIDlet TestSensors (de la parte 1) para controlar el motor servo, como se muestra en el Listado 16.

public class TestSensors extends MIDlet {
... 
//Define control servo motors object
PCA9685Device servo;
...
public void startApp() {
...
//Initialize control servo motors
try {
servo = new PCA9685Device();
servo.setPWMFreq(60);
} catch (IOException ex) { 
Logger.getGlobal().log(Level.SEVERE,ex.getMessage());
}
...
{

public void destroyApp(boolean unconditional) {
...
servo.close();
...
}

class ReadSensors extends Thread {
private double distance=0.0;

@Override
public void run() {
while (shouldRun){
...
//Move servo motor to right and left
System.out.println("Servo moves right");
for (int i=150;i<600;i+=50){
servo.setPWM((byte) 0, (short) 0, (short) i);
I2CUtils.I2Cdelay(1000);
}
System.out.println("Servo moves left");
for (int i=600;i>150;i-=50){
servo.setPWM((byte) 0, (short) 0, (short) i);
I2CUtils.I2Cdelay(1000);
}
...
}
}
}
}

Listado 16. Extendiendo la clase MIDlet para controlar el motor servo.

Conectando el sensor de Luz y Proximidad

El sensor de proximidad/luz VCNL4000 de Adafruit o SparkFun puede ser usado para detectar la aproximación de un objeto usando luz infrarroja (IR) en un rango de 20 cm, o se puede usar el sensor de luz ambiental dentro de un rango de 0.25 to 16383 lux. Toda la data que es recolectada se puede leer mediante la interface I2C en la dirección 0x13

Este sensor tiene los siguientes registros (direcciones son mostradas en hexadecimal)

80h: Registro del comando
81h: Registro del ID del producto
82h: No usado
83h: Configuraciones de corriente del LED IR
84h: Registro del parámetro de la luz ambiental
85h y 86h: Registro del resultado de la luz ambiental
87h y 88h: Registro del resultado de la medición de proximidad
89h: Registro de frecuencia de la señal de medición de proximidad
8Ah: Registro del ajuste del modulador de proximidad
8Bh: Registro del nivel de luz ambiental IR (no lo utilizaremos)

Para soportar estos registros, creamos un enumerado llamado, VCNL4000, como se muestra en el Listado 17:

public enum VCNL4000 {
COMMAND(0x80),
PRODUCTID(0x81),
IRLED(0x83),
AMBIENTPARAMETER(0x84),
AMBIENTDATA(0x85),
AMBIENTDATA2(0x86),
PROXIMITYDATA(0x87),
PROXIMITYDATA2(0x88),
SIGNALFREQ(0x89),
PROXIMITYADJUST(0x8A),
// Commands used by COMMAND registro 
MEASUREAMBIENT(0x10),
MEASUREPROXIMITY(0x08),
AMBIENTREADY(0x40),
PROXIMITYREADY(0x20);

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

Listado 17. Enumerado que define los registros del sensor de luz y proximidad.

Ahora conectaremos el sensor de luz y proximidad a los pines, 3.3v, SCL, SDA, GND y Vin 5v del Raspberry Pi, como se muestra en la Figura 4, y creamos una clase de Java ME 8 llamada VCNL4000Device para controlarlo. Para establecer la frecuencia del LED IR, creamos un enumerado Freq, como se muestra en el Listado 18.

public class VCNL4000Device extends I2CRpi {

public enum Freq { // commands to set frequency for IR LED
F3M125(0x00), //3.125 MHZ
F1M5625(0x01), //1.5625 MHz
F781K25(0x02), //781.25 KHz
F390K625(0x03); //390.625 KHz

public byte value;
Freq(int value) { // Read frequency value
this.value = (byte) value;
}
}

Listado 18. Clase VCNL4000Device y enumerado Freq.

Ahora definiremos un constructor que crea un dispositivo en la dirección definida (ver Listado 19) y ejecuta las siguientes acciones:

  • Lea el registro del ID del producto para detectar si el sensor está presente.
  • Colocar el LED IR a una corriente de 200mA
  • Chequear la frecuencia de la medición de la proximidad
  • Lee el registro del ajuste de proximidad
private static final int VCNL4000_ADDRESS = 0x13;
public VCNL4000Device() throws IOException {
super(VCNL4000_ADDRESS);

byte rev = (byte) VCNL4000.PRODUCTID.read(device);
if ((rev & 0xF0) != 0x10) {
Logger.getGlobal().log(Level.SEVERE, "Sensor not found");
return;
}

VCNL4000.IRLED.write(device, (byte) 20);    // set to 20 * 10mA = 200mA
Logger.getGlobal().log(Level.FINE, "IR LED current = " + 
String.valueOf(VCNL4000.IRLED.read(device) * 10) + " mA");
Logger.getGlobal().log(Level.FINE, "Proximity measurement frequency = ");
byte freq = (byte) VCNL4000.SIGNALFREQ.read(device);
if (freq == Freq.F3M125.value) {
Logger.getGlobal().log(Level.FINE, "3.125 MHz");
}
if (freq == Freq.F1M5625.value) {
Logger.getGlobal().log(Level.FINE, "1.5625 MHz");
}
if (freq == Freq.F781K25.value) {
Logger.getGlobal().log(Level.FINE, "781.25 KHz");
}
if (freq == Freq.F390K625.value) {
Logger.getGlobal().log(Level.FINE, "390.625 KHz");
}

VCNL4000.PROXIMITYADJUST.write(device, (byte) 0x81);
Logger.getGlobal().log(Level.FINE, "Proximity adjustment registro = 
"+String.valueOf(VCNL4000.PROXIMITYADJUST.read(device)));

}

Listado 19.Estableciendo la dirección de control y condiciones iniciales

Luego creamos los siguientes métodos de operación (vea en el listado 20):

  • setLEDcurrent: Establecer la corriente para el LED
  • continuousConversionOn: Activar la conversión continua
  • continuousConversionOff: Desactivar la conversión continua
  • setSignalFreq: Establecer la frecuencia del IR
  • setProximityAdjust: Ajustar la proximidad
  • getProximityAdjust: Leer los valores de ajuste de proximidad
  • readProximity: Leer aproximación de los objetos al dispositivo en centímetros
  • readAmbientLight: Leer el indicador de luz ambiental
public void setLEDcurrent(byte cur) {
if ((cur > 20) || (cur < 0)) cur = 5; 
VCNL4000.IRLED.write(device, cur);
 }

public void continuousConversionOn() {
VCNL4000.AMBIENTPARAMETER.write(device, (byte) 0x89);
}

public void continuousConversionOff() {
VCNL4000.AMBIENTPARAMETER.write(device, (byte) 0x09);
 }

public void setSignalFreq(Freq freq) {
//# Setting the proximity IR test signal frequency. 
//# The proximity measurement is using a square IR 
//# signal as measurement signal. Four different values are possible: 
//# 00 = 3.125 MHz
//# 01 = 1.5625 MHz
//# 02 = 781.25 kHz (DEFAULT)
//# 03 = 390.625 kHz
VCNL4000.SIGNALFREQ.write(device, freq.value);
}

public int getSignalFreq() {
return VCNL4000.SIGNALFREQ.read(device);
}

public void setProximityAdjust() {
VCNL4000.PROXIMITYADJUST.write(device, (byte) 0x81);
}

public int getProximityAdjust() {
return VCNL4000.PROXIMITYADJUST.read(device);
}

public short readProximity() {
byte temp = (byte) VCNL4000.COMMAND.read(device);
VCNL4000.COMMAND.write(device, (byte) (temp | VCNL4000.MEASUREPROXIMITY.cmd));
while (true) {
byte result = (byte) VCNL4000.COMMAND.read(device);
//Serial.print("Ready = 0x"); Serial.println(result, HEX);
if ((result & VCNL4000.PROXIMITYREADY.cmd) > 0) {
short data = (short) (VCNL4000.PROXIMITYDATA.read(device) << 8);
data = (short) (data | VCNL4000.PROXIMITYDATA2.read(device));
return data;
}
I2CUtils.I2Cdelay(10);
}
 }

public short readAmbientLight() {
byte temp = (byte) VCNL4000.COMMAND.read(device);
VCNL4000.COMMAND.write(device, (byte) (temp | VCNL4000.MEASUREAMBIENT.cmd));
while (true) {
byte result = (byte) VCNL4000.COMMAND.read(device);
if ((result & VCNL4000.AMBIENTREADY.cmd) > 0) {
short data = (short) (VCNL4000.AMBIENTDATA.read(device) << 8);
data = (short) (data | VCNL4000.AMBIENTDATA2.read(device));
return data;
}
I2CUtils.I2Cdelay(10);
}
}

}

Listado 20. Métodos de operación

Ahora extendemos las clases de MIDled TestSensors (desde la Parte 1) para leer la luz ambiental y proximidad de objetos, como se muestra en el Listado 21:

public class TestSensors extends MIDlet {
... 
//Define Light and proximity sensor object
VCNL4000Device vcnl;
...

public void startApp() {
...
//Initialize Light and proximity sensor
try {
vcnl = new VCNL4000Device();
} catch (IOException ex) {
Logger.getGlobal().log(Level.SEVERE,ex.getMessage());
}
...
}

public void destroyApp(boolean unconditional) {
...
vcnl.close();
...
}

class ReadSensors extends Thread {
private double distance=0.0;

@Override
public void run() {
while (shouldRun){
...
//Detect objects and ambient light
if (vcnl!=null){
System.out.println("VCNL4000 Ambient light:"+ 
vcnl.readAmbientLight());
vcnl.setProximityAdjust();
I2CUtils.I2Cdelay(2000);
System.out.println("VCNL4000 Proximity (ctms):"+ 
vcnl.readProximity());
}
...
}
}
}
}

Listado 21. Extendiendo la clase MIDlet para soportar el sensor de luz y proximidad

Conectando el sensor de Humedad y Temperatura

El HTDU21D es ideal para registro de data verificando el ambiente, perfecto para una estación de clima o control de sistema de humidad. Todo lo que se necesita son dos líneas y se tendrá lecturas de humedad relativas y lecturas de temperaturas precisas, mediante una interface I2C en la dirección 0x40.

Soporta una rango de humedad amplio (0 hasta 100 porcentaje relativo de humedad) y un amplio rango de temperatura (-40 hasta 125 grados centígrados).

Este sensor tiene los siguientes registros (direcciones están expresadas en hexadecimales)

E3h: Lectura principal y mantenida de temperatura
E5h: Lectura principal y mantenida de humedad
F3h: Lectura principal y no mantenida de temperatura
F5h: Lectura principal y no mantenida de humedad
E6h: Escribir registro del usuario
E7h Leer registro del usuario
FEh: Reinicio

Para soportar estos registros, se crea un enumerado llamado HTU21D, como se muestra en el Listado 22.

public enum HTU21D {
TRIGGER_TEMP_MEASURE_HOLD(0xE3),
TRIGGER_HUMD_MEASURE_HOLD(0xE5),
TRIGGER_TEMP_MEASURE_NOHOLD(0xF3),
TRIGGER_HUMD_MEASURE_NOHOLD(0xF5),
WRITE_USER_REG(0xE6),
READ_USER_REG(0xE7),
SOFT_RESET(0xFE);

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

Listado 22. Enumerado que define los registros del sensor de humedad y temperatura.

Ahora se conecta el sensor de temperatura y humedad al Raspberry Pi's en los pines 3.3v, GND, SDA, y SCL, como se muestra en la Figura 4, y se crea una clase de Java ME 8 llamada HTU21Ddevice con este constructor usando la dirección de control, como se muestra en el Listado 23:

public class HTU21DDevice extends I2CRpi {
private static final int HTDU21D_ADDRESS = 0x40;

public HTU21DDevice() throws IOException {
super(HTDU21D_ADDRESS);
}

Listado 23. Clase HTU21DDevice con su constructor estableciendo la dirección de control.

Luego crear los siguientes métodos de operación (ver el Listado 24)

  • readHumidity: Leer humedad relativa
  • readTemperature: Leer la temperatura (centígrados)
  • setResolution: Colocar la resolución del sensor; según su hoja de datos.
public float readHumidity() {
try {
device.write(HTU21D.TRIGGER_HUMD_MEASURE_NOHOLD.cmd);
} catch (IOException ex) {
Logger.getGlobal().log(Level.WARNING,ex.getMessage());
}
I2CUtils.I2Cdelay(200); //Hang out while measurement is taken. 50mS
ByteBuffer rxBuf = ByteBuffer.allocateDirect(3);
try {
device.read(rxBuf);
} catch (IOException ex) {
Logger.getGlobal().log(Level.WARNING,ex.getMessage());
}
rxBuf.clear();
short msb, lsb, checksum;
msb = (short) I2CUtils.asInt(rxBuf.get(0));
lsb = (short) I2CUtils.asInt(rxBuf.get(1));
checksum = (short) I2CUtils.asInt(rxBuf.get(2));
short rawHumidity = (short) (((short) msb << 8) | (short) lsb);
if (check_crc(rawHumidity, checksum) != 0) {
return (999); //Error out
}
rawHumidity &= 0xFFFC; //Zero out the status bits but keep them in place
//Given the raw humidity data, calculate the actual relative humidity
float tempRH = rawHumidity / (float) 65536; //2^16 = 65536
float rh = -6 + (125 * tempRH); 
return (rh);
}

public float readTemperature() {
try {
device.write(HTU21D.TRIGGER_TEMP_MEASURE_NOHOLD.cmd);
} catch (IOException ex) {
Logger.getGlobal().log(Level.WARNING,ex.getMessage());
}
I2CUtils.I2Cdelay(200); //Hang out while measurement is taken
ByteBuffer rxBuf = ByteBuffer.allocateDirect(3);
try {
device.read(rxBuf);
} catch (IOException ex) {
Logger.getGlobal().log(Level.WARNING,ex.getMessage());
}
rxBuf.clear();
short msb, lsb, checksum;
msb = (short) I2CUtils.asInt(rxBuf.get(0));
lsb = (short) I2CUtils.asInt(rxBuf.get(1));
checksum = (short) I2CUtils.asInt(rxBuf.get(2));
short rawTemperature = (short) (((short) msb << 8) | (short) lsb);
if (check_crc(rawTemperature, (short) checksum) != 0) {
return (999); //Error out
}
rawTemperature &= 0xFFFC; //Zero out the status bits but keep them in place
//Given the raw temperature data, calculate the actual temperature
float tempTemperature = rawTemperature / (float) 65536; //2^16 = 65536
float realTemperature = -46.85F + (175.72F * tempTemperature); 

return (realTemperature);
}

public void setResolution(byte resolution) throws IOException {
byte userRegister = read_user_registro(); //Go get the current registro state
userRegister &= 0b01111110; //Turn off the resolution bits
resolution &= 0b10000001; //Turn off all other bits but resolution bits
userRegister |= resolution; //Mask in the requested resolution bits
HTU21D.WRITE_USER_REG.write(device, userRegister);
}
...
}

Listado 24. Métodos de operación.

Ahora extenderemos la clase de MIDlet TestSensors (de Parte 1) para leer humedad y temperatura relativa, como se muestra en el Listado 25.

public class TestSensors extends MIDlet {
... 
//Define humidity and temperature sensor object
HTU21DDevice htu;
...

public void startApp() {
...
//Initialize humidity and temperature sensor
try {
htu= new HTU21DDevice();
} catch (IOException ex) {
Logger.getGlobal().log(Level.SEVERE,ex.getMessage());
}
...
}

public void destroyApp(boolean unconditional) {
...
htu.close();
...
}

class ReadSensors extends Thread {
private double distance=0.0;

@Override
public void run() {
while (shouldRun){
...
//Read temperature and humidity
if (htu!=null){
System.out.println("HTU21D Temperature centigrade:"+ 
htu.readTemperature());
I2CUtils.I2Cdelay(2000);
System.out.println("HTU21D Humidity:"+ htu.readHumidity());
}
...
}
}
}
}

Listado 25. Extendiendo la clase MIDlet para soportar el sensor de temperatura y humedad relativa.

Conectando el Modulo de Brújula digital

El HMC5883L es un circuito sensor magneto resistivo es un trio de sensores y circuitos de soporte específicos para medir los campos magnéticos. Esto puede medir un amplio campo magnético con rango de fuerza (+/-8 Oe).

Con un amplio suministro de poder, este sensor convierte cualquier incidente en un campo magnético en determinada direcciones a una salida de voltaje diferencial. En la presencia del campo magnético, un cambio en el puente resistidor de los elementos causa un correspondiente cambio en voltaje a través de los puentes de salida.

Este sensor tiene los siguientes registros (direcciones están mostradas en hexadecimal):

00h: Configuración registro A
01h: Configuración registro B
02h: Modo registro
03h: Data de salida X MSB registro
04h Data de salida X LSB registro
05h: Data de salida Z MSB registro
06h: Data de salida Z LSB registro
07h: Data de salida Y MSB registro
08h: Data de salida Y LSB registro
09h: Registro de status
10h: Identificación registro A
11h: Identificación registro B
12h: Identificación registro C

Para el soporte de estos registros crearemos un enumerado llamado HMC5883L, como se muestra en el Listado 26.

public enum HMC5883L {
ConfigRegA(0x00),
ConfigRegB(0x01),
ModeReg(0x02),
DataRegBegin(0x03);

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

Listado 26. Enumerado que define los registros del compás digital

Ahora conectar el compás digital al Raspberry Pi pines 5v, GND, SCL, y SDA, como se muestra en la Figura 4, y crear una clase de Java ME 8 llamada HMC5883Ldevice con este constructor usando la dirección de control provista. Para establecer el tipo de medición, declaramos un enumerado llamado Measurement como se muestra en el Listado 27.

public class HMC5883LDevice extends I2CRpi {

public enum Measurement {
Continuous(0x00),
SingleShot(0x01),
Idle(0x03);

public byte value;
Measurement(int value) {
this.value = (byte) value;
}
}
...
private static final int HMC5883L_ADDRESS = 0x1E;

public HMC5883LDevice() throws IOException {
super(HMC5883L_ADDRESS);
m_Scale = 1; //start scale for calculations
}

Listado 27. Clase HMC5883LDevice con su constructor estableciendo la dirección de control.

Crear los siguientes métodos de operación (ver Listado 23):

  • ReadRawAxis: Leer data del sensor.
  • ReadScaledAxis: Leer data del sensor con conversión de escala, ver la hoja técnica del sensor.
  • SetScale: Establecer la escala de conversión.
  • SetMeasurementMode: Establecer modo de medida (continua, disparo-simples, o en espera) utilizando el enumerado Measurement.
  • calculateHeading: Calculando rumbo en grados.
public MagnetometerRaw readRawAxis() {
ByteBuffer buffer = ByteBuffer.allocateDirect(6);
try {
device.read(HMC5883L.DataRegBegin.cmd, 1, buffer);
} catch (IOException ex) {
Logger.getGlobal().log(Level.WARNING,ex.getMessage());
}
// Read each of the pairs of data as a signed short
buffer.rewind();
byte[] data = new byte[6];
buffer.get(data);
MagnetometerRaw raw = new MagnetometerRaw();
raw.XAxis = (data[0] << 8) | data[1];
raw.ZAxis = (data[2] << 8) | data[3];
raw.YAxis = (data[4] << 8) | data[5];
return raw;
 }

public MagnetometerScaled readScaledAxis() {
MagnetometerRaw raw = ReadRawAxis();
MagnetometerScaled scaled = new MagnetometerScaled();
scaled.XAxis = raw.XAxis * m_Scale;
scaled.ZAxis = raw.ZAxis * m_Scale;
scaled.YAxis = raw.YAxis * m_Scale;
return scaled;
}

public int setScale(float gauss) {
int regValue = 0x00;
if (gauss == 0.88) {
regValue = 0x00;
m_Scale = 0.73F;
} else if (gauss == 1.3) {
regValue = 0x01;
m_Scale = 0.92F;
} else if (gauss == 1.9) {
regValue = 0x02;
m_Scale = 1.22F;
} else if (gauss == 2.5) {
regValue = 0x03;
m_Scale = 1.52F;
} else if (gauss == 4.0) {
regValue = 0x04;
m_Scale = 2.27F;
} else if (gauss == 4.7) {
regValue = 0x05;
m_Scale = 2.56F;
} else if (gauss == 5.6) {
regValue = 0x06;
m_Scale = 3.03F;
} else if (gauss == 8.1) {
regValue = 0x07;
m_Scale = 4.35F;
} else {
return ErrorCode_1_Num;
}
// Setting is in the top 3 bits of the registro.
regValue = regValue << 5;
HMC5883L.ConfigRegB.write(device, (byte) regValue);
return 0;
}

public void SetMeasurementMode(Measurement mode) {
HMC5883L.ModeReg.write(device, mode.value);
// Write(ModeRegister, mode);
}

public double calculateHeading(){
MagnetometerRaw raw = readRawAxis();
MagnetometerScaled scaled = readScaledAxis();
double heading = Math.atan2((double) scaled.YAxis, (double) scaled.XAxis);
// see explanation in the source code
double declinationAngle = - 0.2021672989739025;       
heading += declinationAngle;
// Correct for when signs are reversed.
if (heading < 0) {
heading += 2 * Math.PI;
}
// Check for wrap due to addition of declination.
if (heading > 2 * Math.PI) {
heading -= 2 * Math.PI;
}
// Convert radians to degrees for readability.
return heading * 180 / Math.PI;
}
}

Listado 28. Métodos operativos

Ahora extender la clase MIDlet TestSensors  (de la Parte 1) para calcular el rumbo en grados, como se muestra en el Listado 29.

public class TestSensors extends MIDlet {
... 
//Define HMC5883L compass module object
HMC5883LDevice hmc; 
...

public void startApp() {
...
//Initialize compass module 
try {
hmc = new HMC5883LDevice();
hmc.setScale(1.3F);
hmc.setMeasurementMode(HMC5883LDevice.Measurement.Continuous);
} catch (IOException ex) {
Logger.getGlobal().log(Level.SEVERE,ex.getMessage());
} 
...
}

public void destroyApp(boolean unconditional) {
...
hmc.close();
...
}

class ReadSensors extends Thread {
private double distance=0.0;

@Override
public void run() {
while (shouldRun){
...
//Display heading degrees point north
if (hmc != null) {
System.out.println("Heading degrees:"+hmc.calculateHeading());
I2CUtils.I2Cdelay(2000);
//Display magnetometer coordinates
HMC5883LDevice.MagnetometerRaw values = hmc.readRawAxis();
System.out.print("X:" + values.XAxis);
System.out.print(" Y:" + values.YAxis);
System.out.println(" Z:" + values.ZAxis);
}
...
}
}
}
}

Listado 29. Extendiendo la clase MIDlet para soportar el dispositivo compas digital.

Estableciendo algunas configuraciones adicionales

Antes de ejecutar el MIDlet usando NetBeans IDE 8.0.2, es importante establecer el permiso apropiado del API. Para hacer esto, en el IDE, seleccione JavaMEDemos, y luego click derecho y seleccionar Application Descriptor , y luego seleccionar tabla API Permissions,.Incluir el permiso que se muestra en el Listado 30 y en la Figura 5:

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

Listado 30. Permiso a incluir.


Figura 5. Estableciendo permisos del API.

Conclusión

Si necesita leer o escribir data resultante de cambios en estados lógicos, puede utilizar el bus I2C. También puede ser usado para manipular registros de valores, escribir o recibir data (como son temperaturas, humedad, intensidad de luz o información desde una brújula electrónica), y configurar pulsos PWM de diferentes magnitudes.

Es importante contar con la documentación técnica para cada sensor y en particular, información relacionada con los diferentes valores a estar presentes en los registros de cada sensor para así obtener los valores deseados basados en la configuración establecida.

Para manipular los registros de cada dispositivo, se recomiendo emplear un enumerado como una de los creados (PCA9685, VCNL4000, HTU21D, y HMC5883L) para mostrar los valores numéricos de nuestra clase en Java y hacer control manual del bus usando la clase de dispositivo respectiva (tal como PCA9685Device, VCNL4000Device, HTU21DDevice, y HMC5883LDevice).

En el siguiente artículo, estaremos examinando otro tipo de sensores usando otro tipo de interfaces, como es la interface de periféricos de manera serial o SPI y la interfaz universal asíncrona de envío y recepción UART.


José Cruz (@joseacruzp) es un ingeniero de software quien ha estado trabajando con Java desde 1998. Es el líder de desarrollo de aplicaciones Java, Java ME y Java EE en Ferretería EPA C.A. en Venezuela. Desde temprana edad su hobbie ha sido la electrónica. Esto le ha permitido combinar la computación con la electrónica y desarrollar proyectos donde Java y los dispositivos embebidos como Arduino y Raspberry Pi son protagonistas.

Este artículo ha sido revisado por el equipo de productos Oracle y se encuentra en cumplimiento de las normas y prácticas para el uso de los productos Oracle.