1. 程式人生 > >如何分分鐘成為Java嵌入式開發人員

如何分分鐘成為Java嵌入式開發人員

用Java開發下一代嵌入式產品

在我10年的Java佈道師生涯裡,沒有哪次Java新版本釋出能讓我如此興奮。Java 8的釋出不僅在語言本身加入了些不錯的新特性,還在嵌入式開發上加入了很棒的功能,進行了優化,還有簡潔的開發文件。如果你是一名Java程式設計師,並且準備好和我一同加入機器間技術的潮流,或者說開發下一代改變世界的裝置,那麼就讓我們開始學習物聯網(IoT)把。

在你開始嵌入式開發之前,你需要知道你具體想要開發出什麼,以及你打算在哪執行你的程式。這十分重要,因為得根據目的選擇不同版本的Embedded Java。

 

如果你想要開發跟桌面應用相似的應用,或者你想要開發出優美的UI,那麼你需要選擇從Java SE衍生出來的Oracle Java SE Embedded版本。它支援同Java SE一樣的平臺和功能。此外,它還提供了其它特性,相容更多平臺,小巧的Java執行環境(JREs),支援headless模式配置,以及記憶體優化。

如果你想要更方便地連線如開關、感測器、馬達之類的外設,Oracle Java ME Embedded將是你最好的選擇。它具有裝置訪問API,為嵌入式平臺最常見的外設提供了介面:通用輸入/輸出(GPIO)、積體電路匯流排(IIC)、序列外設介面匯流排(SPI)、模數轉換器(ADC)、數模轉換器(DAC)、通用非同步收發傳輸器(UART)、記憶體對映輸入輸出(MMIO)、AT命令裝置、看門狗定時器、脈衝計數器、脈衝寬度調製器(PWM)和通用裝置。

至於裝置,Embedded Java覆蓋了大部分的平臺,從傳統的Java SE桌面平臺與伺服器平臺到基於STM微處理器的STM32F4DISCOVERY板、樹莓派和windows平臺。在這篇文章中,我將使用樹莓派,不僅僅是因為它是十分強大,且只有卡片大小的計算機,還因為它價格便宜。最新版只要35美元。

準備樹莓派

樹莓派需要一張存有Linux映象的SD卡才能開機。因為樹莓派沒有硬碟,SD卡就被用來儲存執行所需的Linux映象。該SD卡也被當作儲存裝置用於載入其它的應用程式。

配置SD卡請按以下步驟操作:

  1. 格式化SD卡。
  2. 下載Raspbian(一個專為樹莓派優化的基於Debian的免費作業系統)。
  3. 建立一個可引導的映象。使用像Win32 Disk Imager這樣的應用可以更方便地建立映象。

當你準備好SD卡之後,樹莓派就可以開機了。第一次開機時,樹莓派會載入軟體配置工具讓你進行基本的設定。以下是此時應該注意的選項:

  1. 勾選“擴充套件檔案系統(Expand Filesystem)”選項,使作業系統對整個SD儲存具有許可權。
  2. 選擇“國際化(Internationalisation)”選項中選擇與當地對應的語言與區域。
  3. 在主選單選擇“高階(Advanced)”選項,通過開啟SSH將樹莓派設定為headless嵌入式裝置模式(沒有顯示器)。
  4. 設定靜態IP地址,確保樹莓派總以相同的IP地址接入。雖然這不是必須的,但我發現在樹莓派headless模式下總是很有用。設定靜態IP需要編輯/etc/network/interfaces檔案如下圖:

圖1
(圖1)

現在你已經準備好了連線樹莓派,你可以選擇使用[PuTTY](http://www.putty.org/)連線。如下圖:

圖2
(圖2)

在樹莓派上安裝Embedded Java

現在是時候決定你打算在你的裝置上執行什麼樣的應用了。我個人喜歡搞外設,所以在這篇文章中我將使用Oracle Java ME Embedded,這樣我才能使用裝置訪問API。但是你也可以用Oracle Java SE Embedded來開發樹莓派應用。

在樹莓派上安裝Oracle Java ME Embedded二進位制檔案十分簡單,只需要通過SSH連線用FTP協議把樹莓派版本的zip壓縮檔案從桌面傳輸到樹莓派,然後再解壓到一個新目錄就好了。

整合開發環境

使用Java ME SDK和NetBeans IDE是建立嵌入式應用不錯的選擇。這兩者結合就能在裝置上執行之前先在虛擬機器中進行測試,並且能夠自動地將程式碼傳輸到樹莓派執行,甚至能在執行時除錯。你所需要做的只是確保Java ME SDK是IDE的Java平臺的一部分。你需要在 工具->Java平臺 點選“新增平臺”的選項,然後選擇SDK的路徑。

為了能夠遠端管理樹莓派上的嵌入式應用,你需要執行應用管理系統(AMS)。通過SSH,執行以下程式碼:

[email protected] sudo
javame8ea/bin/usertest.sh

第一個嵌入式應用

Oracle Java ME Embedded應用與Java ME應用看起來完全一樣,就如下例:

public class Midlet extends MIDlet {

@Override
public void startApp() {
System.out.println(“Started…”);
}

@Override
public void destroyApp(boolean unconditional) {
System.out.println(“Destroyed…”);
}
}
(程式碼1)

你的應用必須繼承MIDlet類,並且重寫兩個生命週期方法:startApp和destroyApp。這兩個方法分別在應用啟動時和快結束前被呼叫。以上程式碼能在控制檯輸出資訊。

開啟LED燈

現在讓我們做些更有趣的事,比如通過開關來實現開啟和關閉LED燈。首先讓我們看下樹莓派的通用外設輸入輸出(GPIO)管腳。

圖3

(圖3)

通用外設輸入輸出聯結器(GPIO connector)上有許多不同的連線型別管腳:

– 通用外設輸入輸出管腳(GPIO)

– 積體電路匯流排管腳(IIC)

– 序列外設介面管腳(SPI)

– RxTx串列埠管腳

這意味著我們有好幾個選擇可以連線LED和開關,以上任何一個GPIO管腳都可以,只要記住管腳數字和外設ID,因為你需要這些資訊才能用程式碼指向這些裝置。

現在按照下圖焊接電路。注意我們將LED連線到16管腳(GPIO 23),把開關連線到11管腳(GPIO 17)。同時加上 兩個電阻以保證電壓在安全範圍之內。

圖4

(圖4)

現在讓我們看下程式。裝置訪問API中的PeripheralManager類能夠讓你用外設ID連線到任何型別的外設,這能夠極大地簡化程式碼。比如要連線LED,只需要用靜態方法open,提供管腳ID 23如下程式碼:

private static final int LED1_ID = 23;
...
GPIOPin led1 = (GPIOPin) PeripheralManager.open(LED_ID);

(程式碼2)

要改變LED的值(即開關函式)只要用setValue方法傳入相應引數:

//開啟LED
led1.setValue(true);

這實在不能再簡單了。

我們能用PeripheralManager中同樣的open方法來連線開關,但我們將用稍微不同的方法來設定一些配置資訊。首先,建立GPIOPinConfig物件(程式碼3),其中包含了如下資訊:
private static final int Button_Pin = 17;
...
GPIOPinConfig config1 = new GPIOPinConfig("BUTTON 1",
Button_Pin,
GPIOPinConfig.DIR_INPUT_ONLY,
PeripheralConfig.DEFAULT,
GPIOPinConfig.TRIGGER_RISING_EDGE,
false);

(程式碼3)

外設名稱

– 管腳號
– 傳輸方向:輸入、輸出還是雙向
– 模式:上拉、下拉還是開漏
– 觸發器:無觸發、下降沿觸發、上升沿觸發還是雙邊沿觸發,高電平觸發、低電平觸發還是雙電平觸發
– 初始值

接著我們用該配置物件呼叫open方法,如下:

GPIOPin button1 = (GPIOPin) PeripheralManager.open(config1);

(程式碼4)

我們也可以給管腳新增監聽器,這樣管腳值一旦發生改變,我們就能夠知道。在這個例子中,我們想要知道什麼時候開關的值發生了改變,這樣我們就能相應的改變LED的值:

button1.setInputListener(this);
然後實現valueChanged方法,當監聽器事件發生時就呼叫該方法。

@Override
public void valueChanged(PinEvent event) {
GPIOPin pin = (GPIOPin) event.getPeripheral();
try {
if (pin == button1) {
// Toggle the value of the led
led1.setValue(!led1.getValue());
}
}catch (IOException ex) {
System.out.println("IOException: " + ex);
}
}

(程式碼5)

在結束時關閉管腳是十分重要的,同時還要保證關掉了LED。

public void stop() throws IOException {
if (led1 != null) {
led1.setValue(false);
led1.close();
}
if (button1 != null) {
button1.close();
}
}

(程式碼6)

整個類的程式碼可以在連結找到。

現在,我們剩下的只有MIDlet來啟用我們的程式碼了。程式碼7中的startApp方法會生成一個物件來控制我們的兩個通用輸入輸出裝置(LED和開關),並且監聽我們的輸入。stopApp方法則保證所有東西都被正確地關閉。

public class Midlet extends MIDlet{

private MyFirstGPIO gpioTest;

@Override
public void startApp() {
gpioTest = new MyFirstGPIO();
try {
gpioTest.start();
} catch (PeripheralTypeNotSupportedException |
PeripheralNotFoundException|
PeripheralConfigInvalidException |
PeripheralExistsException ex) {
System.out.println(“GPIO error:”+ex.getMessage());
} catch (IOException ex) {
System.out.println(“IOException: ” + ex);
}
}

@Override
public void destroyApp(boolean unconditional) {
try {
gpioTest.stop();
} catch (IOException ex) {
System.out.println(“IOException: ” + ex);
}
}
}
}
(程式碼7)

感知環境

做到LED和開關已經十分不錯,但感知周圍環境才是真正有意思的。在下面的例子中,我將演示如何著手使用IIC協議的感測器。

IIC裝置可能是最常見的裝置,它們最大的有點是設計簡單。IIC只有兩條雙向的開漏線:序列資料線(SDA)和序列時鐘線(SCL)。

總線上的裝置都會有一個特殊的地址。主控制器通過在序列資料線上發出開始請求和裝置地址建立通訊連線。如果對應地址的裝置空閒,則返回請求。然後資料就在序列資料線上傳輸,用序列時鐘線來控制每一位元的時間。

一旦通訊結束,控制器就發出停止請求。這樣的協議使得在兩條總線上得以增加多個裝置。

啟動樹莓派的積體電路匯流排

如果你檢視樹莓派的管腳圖(圖3),你會發現兩個IIC管腳:管腳3是資料匯流排,管腳5是時鐘匯流排。IIC預設未開啟,所以我們需要採取以下步驟才能讓我們的應用使用匯流排。

首先,用終端連線樹莓派,然後在/etc/modules檔案增加一下兩行:

i2c-bcm2708
i2c-dev

i2c-tools包十分有用,它能夠檢測裝置,保證一切正常運轉。可以通過以下命令安裝:

sudo apt-get install python-smbus
sudo apt-get install i2c-tools

最後,樹莓派中有個黑名單檔案/etc/modprobe.d/raspi-blacklist.conf,預設情況下SPI和IIC都在該名單中。這意味著除非我們移除它們或者把他們設為註釋,IIC和SPI在樹莓派上是不能用的。編輯該檔案去除以下兩行:

blacklist spi-bcm2708
blacklist i2c-bcm2708

重啟樹莓派,確保應用所有的修改。

新增感測器

Bosch Sensortec的BMP180感測器是測量大氣壓和氣溫的經濟解決方案。由於氣壓隨著海拔高度改變,你也可以把它當作海拔高度測量儀。BMP180使用IIC協議,工作電壓為3V到5V,十分適合連線到樹莓派。

按照以下的圖5把BMP180焊接到樹莓派上。通常情況下,使用IIC裝置時需要需要在序列資料線和序列時鐘線加上一個上拉電阻。幸運的是,樹莓派支援上拉電阻,所以你只需要把它們連線在一起。 圖5
(圖5)

在你把感測器連線到樹莓派之後,就可以檢查是否能看到IIC裝置了。在樹莓派上執行以下命令:

sudo i2cdetect -y 1

你應該能在表格中看到裝置。圖6中顯示了兩個IIC裝置:一個在地址40,另一個在地址70。

圖6

使用IIC裝置來獲取溫度
在你程式設計連線IIC裝置之前有一些必須知道的事項:

  • 裝置地址是多少?IIC使用7位作為裝置地址,樹莓派使用IIC匯流排1。
  • 暫存器的地址是多少?在我們的例子中,我們將讀取溫度值,而相應暫存器地址是0xF6。(針對BMP180)
  •  是否需要設定控制暫存器來啟動感測器?某些裝置預設處於睡眠狀態,除非我們啟動它,否則它是不會監測任何資料的。此處裝置的控制暫存器地址是0xF4。(針對BMP180)
  •  裝置的時鐘頻率是多少?BMP180頻率為3.4Mhz。

程式碼8將BMP180的這些引數設定為靜態變數供之後的程式碼使用://Raspberry Pi's I2C bus
private static final int i2cBus = 1;
// 裝置地址
private static final int address = 0x77;
// 3.4MHz最大時鐘頻率
private static final int serialClock = 3400000;
// 地址寬度
private static final int addressSizeBits = 7;
...

// 溫度感測器控制暫存器地址
private static final byte controlRegister = (byte) 0xF4;
// 讀取溫度地址
private static final byte tempAddr = (byte) 0xF6;
// 讀取溫度命令地址
private static final byte getTempCmd = (byte) 0x2E;


// 裝置物件
private I2CDevice bmp180;
(程式碼8)
程式設計連線裝置依然是使用PeripheralManager類的靜態方法open。該處我們將針對IIC裝置建立一個I2CDeviceConfig物件(程式碼9)。該物件能讓我們設定裝置的匯流排,地址,地址位數(位元單位)和時鐘速度。

...
//設定配置資訊
I2CDeviceConfig config = new I2CDeviceConfig(i2cBus,
address, addressSizeBits, serialClock);
//連線IIC裝置
bmp180 = (I2CDevice) PeripheralManager.open(config);
...

(程式碼9)

要讀取溫度,我們需要採取以下步驟:

  1. 按程式碼10a和程式碼10b從裝置讀取校準資料。該步只針對BMP180感測器,使用其它溫度感測器時不一定需要這一步。

// EEPROM暫存器——校準資料
private short AC1;
private short AC2;
private short AC3;
private int AC4;
private int AC5;
private int AC6;
private short B1;
private short B2;
private short MB;
private short MC;
private short MD;
private static final int CALIB_BYTES = 22;


// 讀取所有的校準資料為一個byte型陣列
ByteBuffer calibData=
ByteBuffer.allocateDirect(CALIB_BYTES);
int result = bmp180.read(EEPROM_start,
subAddressSize, calibData);
if (result < CALIB_BYTES) {
System.out.format(“Error: %n bytes read/n”,
result);
return;
}
// 將每組資料讀取為帶符號short型
calibData.rewind();
AC1 = calibData.getShort();
AC2 = calibData.getShort();
AC3 = calibData.getShort();
(程式碼10a)

//unsigned short型
byte[] data = new byte[2];
calibData.get(data);
AC4 = (((data[0] << 8) & 0xFF00) + (data[1] & 0xFF));
calibData.get(data);
AC5 = (((data[0] << 8) & 0xFF00) + (data[1] & 0xFF));
calibData.get(data);
AC6 = (((data[0] << 8) & 0xFF00) + (data[1] & 0xFF));

// signed short型
B1 = calibData.getShort();
B2 = calibData.getShort();
MB = calibData.getShort();
MC = calibData.getShort();
MD = calibData.getShort();

(程式碼10)

2.寫入到裝置上的一個控制暫存器,初始化溫度感測器(程式碼11)。

// 將讀取溫度命令寫入到暫存器
ByteBuffer command = ByteBuffer.allocateDirect
(subAddressSize).put(getTempCmd);
command.rewind();
bmp180.write(controlRegister, subAddressSize, command);

(程式碼11)

3.讀取未補償溫度為兩個位元組的變數,用校準常量得出真實的溫度。程式碼如下(依然針對BMP180)

ByteBuffer uncompTemp = ByteBuffer.allocateDirect(2);
int result = bmp180.read(tempAddr, subAddressSize,
uncompTemp);
if (result < 2) {
System.out.format("Error: %n bytes read/n", result);
return 0;
}

// 讀取溫度為無符號byte型
uncompTemp.rewind();
byte[] data = new byte[2];
uncompTemp.get(data);
UT = ((data[0] << 8) & 0xFF00) + (data[1] & 0xFF); // 計算實際溫度 // 程式碼只適用BMP180 int X1 = ((UT – AC6) * AC5) >> 15;
int X2 = (MC << 11) / (X1 + MD); B5 = X1 + X2; // 攝氏度為單位的溫度 float celsius = (float) ((B5 + 8) >> 4) / 10;

(程式碼12)

最後,攝氏度為單位的溫度資料就被儲存在了celsius變數中。你可以在連結找到整個程式。

作為練習,你也可以把該程式擴充套件到讀取壓力、海拔或者兩者。

總結:

我們通過演示如何使用GPIO和IIC裝置的真實案例學習瞭如何建立Java嵌入式應用。現在是時候輪到你自己在樹莓派上連線更多裝置了,希望你喜歡樹莓派嵌入式Java開發。

 

http://ifeve.com/new-to-java-embedded/