天天看點

如何分分鐘成為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檔案如下圖:

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

(圖1)

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

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

(圖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應用看起來完全一樣,就如下例:

<code>public class midlet extends midlet {</code>

@override

public void startapp() {

system.out.println(“started…”);

}

public void destroyapp(boolean unconditional) {

system.out.println(“destroyed…”);

(代碼1)

你的應用必須繼承midlet類,并且重寫兩個生命周期方法:startapp和destroyapp。這兩個方法分别在應用啟動時和快結束前被調用。以上代碼能在控制台輸出資訊。

打開led燈

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

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

(圖3)

通用外設輸入輸出連接配接器(gpio connector)上有許多不同的連接配接類型管腳:

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

– 內建電路總線管腳(iic)

– 串行外設接口管腳(spi)

– rxtx序列槽管腳

這意味着我們有好幾個選擇可以連接配接led和開關,以上任何一個gpio管腳都可以,隻要記住管腳數字和外設id,因為你需要這些資訊才能用代碼指向這些裝置。

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

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

(圖4)

現在讓我們看下程式。裝置通路api中的peripheralmanager類能夠讓你用外設id連接配接到任何類型的外設,這能夠極大地簡化代碼。比如要連接配接led,隻需要用靜态方法open,提供管腳id 23如下代碼:

<code>private static final int led1_id = 23; ... gpiopin led1 = (gpiopin) peripheralmanager.open(led_id);</code>

(代碼2)

要改變led的值(即開關函數)隻要用setvalue方法傳入相應參數:

<code>//打開led led1.setvalue(true);</code>

這實在不能再簡單了。

我們能用peripheralmanager中同樣的open方法來連接配接開關,但我們将用稍微不同的方法來設定一些配置資訊。首先,建立gpiopinconfig對象(代碼3),其中包含了如下資訊:

<code>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);</code>

(代碼3)

外設名稱

– 管腳号

– 傳輸方向:輸入、輸出還是雙向

– 模式:上拉、下拉還是開漏

– 觸發器:無觸發、下降沿觸發、上升沿觸發還是雙邊沿觸發,高電平觸發、低電平觸發還是雙電平觸發

– 初始值

接着我們用該配置對象調用open方法,如下:

<code>gpiopin button1 = (gpiopin) peripheralmanager.open(config1);</code>

(代碼4)

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

<code>button1.setinputlistener(this);</code>

然後實作valuechanged方法,當監聽器事件發生時就調用該方法。

<code>@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); } }</code>

(代碼5)

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

<code>public void stop() throws ioexception { if (led1 != null) { led1.setvalue(false); led1.close(); } if (button1 != null) { button1.close(); } }</code>

(代碼6)

現在,我們剩下的隻有midlet來啟用我們的代碼了。代碼7中的startapp方法會生成一個對象來控制我們的兩個通用輸入輸出裝置(led和開關),并且監聽我們的輸入。stopapp方法則保證所有東西都被正确地關閉。

<code>public class midlet extends midlet{</code>

private myfirstgpio gpiotest;

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);

gpiotest.stop();

(代碼7)

感覺環境

做到led和開關已經十分不錯,但感覺周圍環境才是真正有意思的。在下面的例子中,我将示範如何着手使用iic協定的傳感器。

iic裝置可能是最常見的裝置,它們最大的有點是設計簡單。iic隻有兩條雙向的開漏線:串行資料線(sda)和串行時鐘線(scl)。

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

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

啟動樹莓派的內建電路總線

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

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

<code>i2c-bcm2708 i2c-dev</code>

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

<code>sudo apt-get install python-smbus sudo apt-get install i2c-tools</code>

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

<code>blacklist spi-bcm2708 blacklist i2c-bcm2708</code>

重新開機樹莓派,確定應用所有的修改。

添加傳感器

bosch sensortec的bmp180傳感器是測量大氣壓和氣溫的經濟解決方案。由于氣壓随着海拔高度改變,你也可以把它當作海拔高度測量儀。bmp180使用iic協定,工作電壓為3v到5v,十分适合連接配接到樹莓派。

按照以下的圖5把bmp180焊接到樹莓派上。通常情況下,使用iic裝置時需要需要在串行資料線和串行時鐘線加上一個上拉電阻。幸運的是,樹莓派支援上拉電阻,是以你隻需要把它們連接配接在一起。 

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

(圖5)

在你把傳感器連接配接到樹莓派之後,就可以檢查是否能看到iic裝置了。在樹莓派上運作以下指令:

<code>sudo i2cdetect -y 1</code>

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

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

使用iic裝置來擷取溫度

在你程式設計連接配接iic裝置之前有一些必須知道的事項:

裝置位址是多少?iic使用7位作為裝置位址,樹莓派使用iic總線1。

寄存器的位址是多少?在我們的例子中,我們将讀取溫度值,而相應寄存器位址是0xf6。(針對bmp180)

 是否需要設定控制寄存器來啟動傳感器?某些裝置預設處于睡眠狀态,除非我們啟動它,否則它是不會監測任何資料的。此處裝置的控制寄存器位址是0xf4。(針對bmp180)

 裝置的時鐘頻率是多少?bmp180頻率為3.4mhz。

代碼8将bmp180的這些參數設定為靜态變量供之後的代碼使用:<code>//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; ...</code>

// 溫度傳感器控制寄存器位址

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)。該對象能讓我們設定裝置的總線,位址,位址位數(比特機關)和時脈速度。

<code>... //設定配置資訊 i2cdeviceconfig config = new i2cdeviceconfig(i2cbus, address, addresssizebits, serialclock); //連接配接iic裝置 bmp180 = (i2cdevice) peripheralmanager.open(config); ...</code>

(代碼9)

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

按代碼10a和代碼10b從裝置讀取校準資料。該步隻針對bmp180傳感器,使用其它溫度傳感器時不一定需要這一步。

<code>// 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;</code>

// 讀取所有的校準資料為一個byte型數組

bytebuffer calibdata=

bytebuffer.allocatedirect(calib_bytes);

int result = bmp180.read(eeprom_start,

subaddresssize, calibdata);

if (result &lt; 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)

<code>//unsigned short型 byte[] data = new byte[2]; calibdata.get(data); ac4 = (((data[0] &lt;&lt; 8) &amp; 0xff00) + (data[1] &amp; 0xff)); calibdata.get(data); ac5 = (((data[0] &lt;&lt; 8) &amp; 0xff00) + (data[1] &amp; 0xff)); calibdata.get(data); ac6 = (((data[0] &lt;&lt; 8) &amp; 0xff00) + (data[1] &amp; 0xff));</code>

// signed short型

b1 = calibdata.getshort();

b2 = calibdata.getshort();

mb = calibdata.getshort();

mc = calibdata.getshort();

md = calibdata.getshort();

(代碼10)

2.寫入到裝置上的一個控制寄存器,初始化溫度傳感器(代碼11)。

<code>// 将讀取溫度指令寫入到寄存器 bytebuffer command = bytebuffer.allocatedirect (subaddresssize).put(gettempcmd); command.rewind(); bmp180.write(controlregister, subaddresssize, command);</code>

(代碼11)

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

<code>bytebuffer uncomptemp = bytebuffer.allocatedirect(2); int result = bmp180.read(tempaddr, subaddresssize, uncomptemp); if (result &lt; 2) { system.out.format("error: %n bytes read/n", result); return 0; }</code>

// 讀取溫度為無符号byte型

uncomptemp.rewind();

byte[] data = new byte[2];

uncomptemp.get(data);

ut = ((data[0] &lt;&lt; 8) &amp; 0xff00) + (data[1] &amp; 0xff); // 計算實際溫度 // 代碼隻适用bmp180 int x1 = ((ut – ac6) * ac5) &gt;&gt; 15;

int x2 = (mc &lt;&lt; 11) / (x1 + md); b5 = x1 + x2; // 攝氏度為機關的溫度 float celsius = (float) ((b5 + 8) &gt;&gt; 4) / 10;

(代碼12)

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

總結:

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