如何分分鍾成為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卡請按以下步驟操作:
- 格式化SD卡。
- 下載Raspbian(一個專為樹莓派優化的基於Debian的免費操作係統)。
- 創建一個可引導的鏡像。使用像Win32 Disk Imager這樣的應用可以更方便地創建鏡像。
當你準備好SD卡之後,樹莓派就可以開機了。第一次開機時,樹莓派會加載軟件配置工具讓你進行基本的設置。以下是此時應該注意的選項:
- 勾選“擴展文件係統(Expand Filesystem)”選項,使操作係統對整個SD存儲具有權限。
- 選擇“國際化(Internationalisation)”選項中選擇與當地對應的語言與區域。
- 在主菜單選擇“高級(Advanced)”選項,通過開啟SSH將樹莓派設置為headless嵌入式設備模式(沒有顯示器)。
- 設置靜態IP地址,確保樹莓派總以相同的IP地址接入。雖然這不是必須的,但我發現在樹莓派headless模式下總是很有用。設置靜態IP需要編輯/etc/network/interfaces文件如下圖:
(圖1)
現在你已經準備好了連接樹莓派,你可以選擇使用[PuTTY](https://www.putty.org/)連接。如下圖:
(圖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,運行以下代碼:
pi@raspberrypi 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)
通用外設輸入輸出連接器(GPIO connector)上有許多不同的連接類型管腳:
– 通用外設輸入輸出管腳(GPIO)
– 集成電路總線管腳(IIC)
– 串行外設接口管腳(SPI)
– RxTx串口管腳
這意味著我們有好幾個選擇可以連接LED和開關,以上任何一個GPIO管腳都可以,隻要記住管腳數字和外設ID,因為你需要這些信息才能用代碼指向這些設備。
現在按照下圖焊接電路。注意我們將LED連接到16管腳(GPIO 23),把開關連接到11管腳(GPIO 17)。同時加上 兩個電阻以保證電壓在安全範圍之內。
(圖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)
在你把傳感器連接到樹莓派之後,就可以檢查是否能看到IIC設備了。在樹莓派上運行以下命令:
sudo i2cdetect -y 1
你應該能在表格中看到設備。圖6中顯示了兩個IIC設備:一個在地址40,另一個在地址70。
使用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)
要讀取溫度,我們需要采取以下步驟:
- 按代碼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開發。
最後更新:2017-05-19 17:02:04