Java ME 8 + Raspberry Pi + 传感器 = IoT 世界(第 1 部分)

作者:Jose Cruz

了解如何将传感器连接到 Raspberry Pi 并使用 Java 进行控制。

2014 年 9 月发布

Java ME 8 的最新版本包括一个用于控制 LED、继电器、LCD、传感器、电机和开关等各种设备的强大的 API。

本文是由三部分组成的系列文章的第一部分,该系列文章主要介绍如何使用通用输入/输出 (GPIO)、内部集成电路总线 (I2C)、串行外设接口总线 (SPI) 或通用异步接收器/发送器 (UART) 接口将电子传感器连接到 Raspberry Pi B 型。

使用 Java ME 8 控制具有不同类型接口的设备并将设备连接到 Raspberry Pi,我们可以打造一个物联网 (IoT) 世界。

本文着重介绍如何使用 GPIO,并展示如何开发具有以下功能的 Java ME 8 类的示例:

:该 NetBeans IDE 8.0 项目的完整代码示例可从这里下载。

设备 I/O API

设备 I/O API 规范针对小型嵌入式设备上运行的 Java 应用定义了一个通用外围设备 I/O API。它针对一些最常见的外围设备定义了 API,包括以下外围设备:

  • GPIO 设备
  • I2C 设备
  • SPI 设备
  • 模拟数字转换器 (ADC)
  • 数字模拟转换器 (DAC)
  • UART 设备
  • 内存映射 I/O (MMIO) 设备
  • AT 命令设备
  • 监视器计时器
  • 脉冲计数器
  • 脉冲宽度调制 (PWM) 发生器
  • 通用设备

我们要创建的电路

GPIO 设备可用作数字输入或数字输出,可以禁用或启用,还可用于驱动“中断”线路。然而,一个很重要的考虑因素是,所有 Raspberry Pi GPIO 引脚均以 3.3 V 运行。因此,查看要连接的设备的技术规范,确定它们使用的是 3.3 V 还是 5 V 就显得非常重要。在某些情况下,需要使用像这样的逻辑电平转换器。

基于图 1 所示的方框图和图 2 所示的组件,我们创建图 3 所示的电路。

cruz-gpio-f1

图 1. 我们将创建的电路的方框图

cruz-gpio-f2

图 2. 我们将使用的组件

cruz-gpio-f3

图 3. 我们将创建的电路的示意图

连接火焰检测器

来自 DFRobot 的 DFR0076 火焰检测器可用于检测火焰或约 760 纳米到 1100 纳米之间的其他波长的光。可以将其连接到 3.3 V 或 5 V,检测范围约为 20 厘米 (4.8 V) 到 100 厘米 (1 V)。检测到火焰时,信号引脚将输出高电平。

将火焰传感器连接到 Raspberry Pi 的 3.3 V、Gnd 和 GPIO 22 引脚(如图 3 所示),并创建一个 Java ME 8 类,用于火焰检测器传感器控制。

首先,创建一个使用设备访问 API 的 DFR0076Device 类,定义一个支持连接 GPIO 的接口的变量 pin,如清单 1 所示。

public class DFR0076Device {

    private GPIOPin pin = null;  //Define the pin for flame sensor control

清单 1. 用于火焰检测器传感器控制的类

接下来,使用 DeviceManager API 和 GPIOPinConfig 类创建一个初始化并激活 GPIO 22 脚的类构造函数(参见清单 2),建立以下条件:

  • 设备名称: 0
  • 引脚编号:GPIO 22(通过 pinGPIO 指定)
  • 方向:仅输入
  • 模式:上拉
  • 触发器:上升沿
  • 初始值:false
public DFR0076Device(int pinGPIO) { 
...
   pin = (GPIOPin) DeviceManager.open(new GPIOPinConfig(
           0, pinGPIO,GPIOPinConfig.DIR_INPUT_ONLY,GPIOPinConfig.MODE_INPUT_PULL_UP,
           GPIOPinConfig.TRIGGER_RISING_EDGE, false));
...
}

清单 2. 建立初始条件

现在,创建一个方法来接收定义好的监听器类,该类支持火焰检测事件,如清单 3 所示。

public void setListener(PinListener flameListener) {
...
     if (pin!=null)
          pin.setInputListener(flameListener);
...
}

清单 3. 支持火焰检测事件的方法

完成时关闭引脚并确保释放引脚监听器也很重要,如清单 4 所示。

public void close() {
...
       if (pin!=null){
            pin.setInputListener(null);
            pin.close();
       }
...
}

清单 4. 关闭引脚并释放监听器

现在创建一个主 MIDlet,它将调用我们的代码并定义一个用于处理火焰检测事件的监听器类,如清单 5 所示。

public class TestSensors extends MIDlet {
    DFR0076Device flame;
    private static final int FLAME_DETECTOR_PIN = 22;
       public void startApp() {
           //Initialize Flame Sensor
           flame = new DFR0076Device(FLAME_DETECTOR_PIN);
           flame.setListener(new FlameSensor());
       }
       public void destroyApp(boolean unconditional) {
             flame.close();
       }

    private static int waitnext = 1;
    class FlameSensor implements PinListener {
            public void valueChanged(PinEvent event) {
                if (event.getValue() && --waitnext == 0) {
                    System.out.println("WARNING Flame detected!!!");
                    waitnext = 10;
                }
             }
        }
}

清单 5. 创建一个调用我们代码的 MIDlet

连接运动检测器

现在,我们向 TestSensors MIDlet 添加运动检测器功能。为此,我们需要如图 2 所示 HC-SR501 这样的运动传感器。

PIR 传感器可感知运动。所有物体都会发射少量红外辐射,温度越高,发出的辐射越多。PIR 传感器能够检测到检测区域内 IR 水平的变化(例如,当人进入室内时),由此感知运动。

我们将使用的 PIR 传感器有三个引脚:接地、数字输出和 3–5 Vdc 输入。空闲时,如果未检测到运动,数字输出信号将保持为低。但检测到运动时,数字输出信号将脉冲高 (3.3 V)。您可以将数字输出脚直接连接到 Raspberry Pi。

为进行测试,PIR 传感器上准备了跳线(参见图 4)。

  • 将跳线设置到“L”位置将产生一次(不可重复)触发。跳线设在此位置时,将在传感事件发生后启用传感,这样就可以持续检测运动。如果检测不到运动,输出仍将锁定为低 3 秒钟。
  • 将跳线设置到“H”位置将产生可重复触发。跳线设到“H”(默认)位置时,将在发生传感事件后(即,一旦输出为高)禁用传感。

您可以进行以下调整:

  • 顺时针调整灵敏度电位计将传感距离提升到约 7 米;逆时针调整将传感距离缩短到约 3 米。
  • 顺时针调整延时电位计将感应延迟延长到 300 秒;逆时针调整将感应延迟缩短到 5 秒。
cruz-gpio-f4

图 4. 测试跳线和电位计

我们将 PIR 传感器连接到 Raspberry Pi 5 V、Gnd 和 GPIO 24 脚,如图 3 所示;并创建一个 Java ME 8 类 HCSR501Device 使用设备访问 API 对其进行控制,如清单 6 所示。

public class HCSR501Device {
    private GPIOPin pin = null;

清单 6. HCSR501Device

然后,使用 DeviceManager API 和 GPIOPinConfig 类创建一个初始化并激活 GPIO 24 脚的类构造函数(参见清单 7),建立以下条件:

  • 设备名称: 0
  • 引脚编号:GPIO 24(通过 pinGPIO 指定)
  • 方向:仅输入
  • 模式:下拉
  • 触发器:上升沿
  • 初始值:false
  • 等 3 秒钟时间待 PIR 初始化
public HCSR501Device(int pinGPIO) {
 ...
   pin = (GPIOPin) DeviceManager.open(new GPIOPinConfig(
           0, pinGPIO, GPIOPinConfig.DIR_INPUT_ONLY, GPIOPinConfig.MODE_INPUT_PULL_DOWN,
           GPIOPinConfig.TRIGGER_RISING_EDGE, false));
       I2CUtils.I2Cdelay(3000);    //wait for 3 seconds
  ...
}

清单 7. 建立初始条件

现在,创建一个方法来接收定义好的监听器类,该类支持运动检测事件,如清单 8 所示。

public void setListener(PinListener pirListener) {
...
       if (pin!=null)
          pin.setInputListener(pirListener);
...
}

清单 8. 支持运动检测事件的方法

完成时关闭引脚并确保释放引脚监听器也很重要,如清单 9 所示。

public void close() {
...
     if (pin!=null){
          pin.setInputListener(null);
          pin.close();
     }
...
}

清单 9. 关闭引脚并释放监听器

我们来扩展 MIDlet 类以支持 PIR 传感器及其监听器,如清单 10 所示。

//Define HCSR501 Device object
HCSR501Device pir;
private static final int MOTION_DETECTOR_PIN = 24;

@Override
public void startApp() {
...    
    //Initialize PIR sensor
    pir = new HCSR501Device(MOTION_DETECTOR_PIN);
    pir.setListener(new PirSensor());
...    
}

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

//Check PIR Sensor for motion detection
class PirSensor implements PinListener {

    @Override
    public void valueChanged(PinEvent event) {
        if (event.getValue()) {
            System.out.println("WARNING Motion detected!!!");
        }

    }
}

清单 10. 扩展 MIDlet 类以支持 PIR 传感器及其监听器

连接距离传感器

HC-SR04 是超声测距检测器,与蝙蝠和海豚类似,它也使用声纳确定物体的距离。它配备了一个超声发射器和接收器模块,并具有以下特性。

  • 电源:+5 Vdc
  • 静态电流:<2 mA
  • 工作电流:15 mA
  • 有效角度:<15 度
  • 测距距离:2 厘米–400 厘米/1 英寸–13 英尺
  • 分辨率:0.3 厘米
  • 测量角度:30 度
  • 触发器输入脉冲宽度:10 µs

要开始测量,HC-SR04 触发器脚必须收到高 (5 V) 脉冲至少 10 µs,这将启动传感器并使其发射 8 个周期的超声脉冲波,频率为 40 KHz,然后等待接收器接收反射超声脉冲波。

当传感器由接收器检测到超声脉冲波时,会将 echo 脚设置为高 (5 V),等待一会儿,等待时间与所测距离成正比。要计算距离,使用以下公式,其中“声速(单位:厘米/秒)”等于 34029 厘米/秒:

距离 =((echo 脚高电平持续时间,单位:ns)*(声速,单位:厘米/秒))/ 2 /1000000000 ns

如前所述,此传感器以 5 V 运行,因此来自 GPIO 23 的 3.3 V 信号可以毫无问题地激活触发脚。

读取 echo 信号电平时,如果其处于活动状态,传感器将发送 5 V,这会超过 Raspberry Pi GPIO 17 脚支持的最高电压,因此需要使用分压器将信号从 5 V 转换为 3.3 V。

在图 5 中,我们可以看到如何使用两个 330 欧姆和 470 欧姆的电阻构建此分压器,从而在 GPIO 17 脚实现 2.9 V 的电压。您可以使用这个在线分压器工具根据这两个电阻计算输出电压。

cruz-gpio-f5

图 5. 分压器电路

现在需要创建一个名为 HCSR04Device 的 Java ME 8 类来控制 HC-SR04 传感器并发送脉冲,以厘米为单位测量距离。参见清单 11。

public class HCSR04Device {

    private final int PULSE = 10000;        // #10 µs pulse = 10,000 ns
    private final int SPEEDOFSOUND = 34029; // Speed of sound = 34029 cm/s

    private GPIOPin trigger = null;
    private GPIOPin echo = null;

清单 11. 创建 HCSR04Device

现在,使用 DeviceManager API 和 GPIOPinConfig 类创建一个初始化并激活 GPIO 触发器和 echo 脚的类构造函数(参见清单 12),建立以下条件:

  • 设备名称: 0
  • 引脚编号:触发器 = GPIO 23,echo = GPIO 17(通过 _trigger_echo 指定)
  • 方向:触发器 = 仅输出,echo = 仅输入
  • 模式:触发器 = 输出推拉,echo = 输入上拉
  • 触发器:二者皆无
  • 初始值:false
  • 等 0.5 秒,待 HC-SR04 初始化
public HCSR04Device(int _trigger, int _echo) {
...
trigger = (GPIOPin) DeviceManager.open(new GPIOPinConfig(0, _trigger, 
GPIOPinConfig.DIR_OUTPUT_ONLY, GPIOPinConfig.MODE_OUTPUT_PUSH_PULL,
       GPIOPinConfig.TRIGGER_NONE, false));

echo = (GPIOPin) DeviceManager.open(new GPIOPinConfig(0, _echo, 
GPIOPinConfig.DIR_INPUT_ONLY, GPIOPinConfig.MODE_INPUT_PULL_UP, 
GPIOPinConfig.TRIGGER_NONE, false));

       I2CUtils.I2Cdelay(500);  //wait for 0.5 seconds
 ...
 }

清单 12. 建立初始条件

pulse 方法计算距离(单位为厘米),它将触发器脚从 1 设置为 0,表示 10 µs (10000 ns);然后,计算 echo 脚由 0 变为 1 所需时间(单位均为纳秒),如清单 13 所示。

public double pulse() {
  long distance = 0;
  try {
      trigger.setValue(true); //Send a pulse trigger; must be 1 and 0 with a 10 µs wait
      I2CUtils.I2CdelayNano(0, PULSE);// wait 10 µs
      trigger.setValue(false);
      long starttime = System.nanoTime(); //ns
      long stop = starttime;
      long start = starttime;
      //echo will go 0 to 1 and need to save time for that. 2 seconds difference
      while ((!echo.getValue()) && (start < starttime + 1000000000L * 2)) {
          start = System.nanoTime();
      }
      while ((echo.getValue()) && (stop < starttime + 1000000000L * 2)) {
          stop = System.nanoTime();
      }
      long delta = (stop - start);
      distance = delta * SPEEDOFSOUND; // echo from 0 to 1 depending on object distance
  } catch (IOException ex) {
      Logger.getGlobal().log(Level.WARNING,ex.getMessage());
  }
  return distance / 2.0 / (1000000000L); // cm/s
}

清单 13. 距离计算方法

最后,关闭这两个脚,释放所有资源(参见清单 14)。

public void close() {
...
    if ((trigger!=null) && (echo!=null)){
           trigger.close();
        echo.close();;
        }
...
}

清单 14. 关闭二脚

现在,我们来扩展 MIDlet 类以支持 HC-SR04 传感器并计算距离,如清单 15 所示。

public class TestSensors extends MIDlet {
    //Define HCSR04 Device object
    HCSR04Device hcsr04;
    private static final int TRIGGER_PIN = 23;
    private static final int ECHO_PIN = 17;
    
    //Define execution of read sensors thread
    private volatile boolean shouldRun = true;
    private ReadSensors sensorsTask;
    
    @Override
    public void startApp() {
    ...
        //Initialize Ultrasound sensor
        hcsr04=new HCSR04Device(TRIGGER_PIN, ECHO_PIN);
        //Start read sensors data thread
        sensorsTask=new ReadSensors();
        sensorsTask.start();
    }

    @Override
    public void destroyApp(boolean unconditional) {
        shouldRun=false;
    ...
        hcsr04.close();
    }
    
    // Thread to read distance each 5 seconds
    class ReadSensors extends Thread {
        private double distance=0.0;
        
        @Override
        public void run() {
            while (shouldRun){
                distance = hcsr04.pulse();
                if (distance>0) 
                    System.out.println("Object detected at " + distance + " cm.");
                I2CUtils.I2Cdelay(5000);
            }
        }
    }
    
}

清单 15. 扩展 MIDlet 类以支持 HC-SR04 传感器并计算距离

使用 NetBeans IDE 8.0 运行此 MIDlet 之前,建立 API 权限非常重要。为此,选择项目 JavaMEDemos,右键单击并选择 Properties 以显示 Project Properties 窗口,选择 Application Descriptor,并选择 API Permissions 选项卡。包括以下四个权限,如图 6 所示:

  • jdk.dio.DeviceMgmtPermission *:* , open
  • jdk.dio.gpio.GPIOPinPermission *:* , open, setdirection
  • jdk.dio.gpio.GPIOPortPermission *:* , open
  • java.util.logging.LoggingPermission control, null
cruz-gpio-f6

图 6. 建立 API 权限

在仿真器中使用 NetBeans IDE 8 运行 MIDlet

如果您没有 Raspberry Pi,但想要测试 MIDlet,可以使用 NetBeans IDE 8.0 中的一个设备仿真器根据选定引脚配置自行创建一个。在本例中,我们将选择以下引脚配置:

  • GPIO 17:输入
  • GPIO 22:输入
  • GPIO 23:输出
  • GPIO 24:输入

我们将配置仿真器定义这些 GPIO 引脚,在将 Java 代码上传到 Raspberry Pi 之前执行 MIDlet 测试。

首先,选择 Tools > Java ME > Custom Device Editor,打开 Custom Device Editor 窗口,如图 7 所示。

cruz-gpio-f7

图 7. Custom Device Editor 窗口

选择 MEEP,然后单击 New 新建一个名为 MEEPCustomDevice 的可自定义设备,显示在 Edit MEEP Device 窗口中,如图 8 所示。

cruz-gpio-f8

图 8. Edit MEEP Device 窗口

在 GPIO 选项卡中,创建所需的 GPIO 引脚并指定配置。完成后,单击 OK

在 NetBeans IDE 中,现在可以看到 Device Selector 窗口中出现一个新设备 MEEPCustomDevice。

选择 External Events Generator 图标并单击 Install,使用您创建的设备安装 MIDlet。参见图 9。

cruz-gpio-f9

图 9. 使用 MEEPCustomDevice 安装 MIDlet

选择 GPIO Pins 选项卡,查看在 Edit MEEP Device 窗口(图 8)中配置的所有 GPIO 引脚(图 10)。

cruz-gpio-f10

图 10. 查看 GPIO 引脚

选择 Tools > External Events Generator 打开一个新窗口,如图 11 所示。在该窗口中,可以单击 GPIO 17、GPIO 22、和 GPIO 24 引脚的按钮分别模拟距离传感器、火焰传感器和运动传感器。此外,您还可以在控制台上看到已检测事件的日志,如图 12 所示。

cruz-gpio-f11

图 11. External Events Generator 窗口

cruz-gpio-f12

图 12. 已检测事件日志

总结

每种设备都有自己的技术规范,必须详加查看,然后才能确定可以连接何种类型的接口。尤其是,确定每种设备的设计工作电压以确保每台设备与 Raspberry Pi 的生命周期非常重要。

GPIO 接口方便连接传感器,可用作数字输入或数字输出,可以禁用或启用,还可用于驱动“中断”线路。针对每台设备定义一个 Java ME 8 类非常重要,该类处理所需事务,包含合适的控制逻辑,如我们为火焰传感器、运动传感器和距离传感器设备创建的 DFR0076DeviceHCSR501DeviceHCSR04Device 类。

通过创建 MIDlet,您可以轻松地将应用部署到 Raspberry Pi,执行您可以想到的任何试验。如果您没有 Raspberry Pi 但希望模拟一个,可以利用 NetBeans IDE 8 强大的 Java 集成开发环境,您可以在其中创建虚拟设备并试验您定义的接口。

在本系列的下一篇文章中,我们将使用其他类型的接口(如 I2C、SPI 和 UART)检查其他类型的传感器(如可以检测温度、压力和光线强度的传感器)。

另请参见

关于作者

Jose Cruz (@joseacruzp) 是一名软件工程师,自 1998 年以来一直使用 Java。他是委内瑞拉 Ferreteria EPA C.A. 的 Java、Java ME 和 Java EE 开发团队负责人。从小,他的业余爱好就是电子产品。这导致他自然而然地将计算与电子相结合,开发基于 Arduino 和 Raspberry Pi 等 Java 和嵌入式设备的项目。