Java Embedded (9)紅外線測距模組與類比數位轉換
Java Embedded (8)Raspberry Pi GPIO 的基礎應用與實作(下) << 前情 9-1 認識紅外線測距模組瞭解使用Raspberry Pi的GPIO控制LED和讀取按鈕,這一章開始說明GPIO一些比較不一樣的應用,首先是讀取距離感應器資訊。在生活中的很多裝置都需要讀取距離的資訊,最常見的就是行動電話,接電話時把電話靠近耳朵,螢幕就會自動關閉,電話離開的時候會自動開啟螢幕,所以行動電話裡面一定有一個可以偵測距離的感應器。還有自動掃地機這類裝置,它可以偵測裝置與障礙物的距離,根據這些距離的資訊,它就不會撞到牆璧或是家具。 測試距離的零件有很多種選擇,從最精確的雷射測距器,或是一般用途的超音波測距器,它們的差異是可以偵測的距離和精確度。這一章使用紅外線測距模組做為應用程式的接近感應器,透過類比數位轉換晶片,也可以偵測物體的距離。你需要準備這些需要的零件:
這些零件會用在幾個不同階段的練習,剛開始的時候不會用到全部的零件,最後的練習會連接好所有的零件,搭配寫好的應用程式,執行一些比較複雜的工作。先認識紅外線測距模組零件,這是它的外觀: 這種紅外線測距模組依照偵測距離有幾種不同的規格,這裡使用的「GP2Y0A41SK0F」可以偵測4到30公分。你可以依照實際的需求選擇不同的規格,例如「GP2Y0A21YK0F」可以偵測10到80公分。紅外線測距模組使用Japanese Solderless Terminal(JST)連接針腳,購買的時候要注意是否附這種規格的連接線。它有三個針腳: 紅外線測距模組偵測前方的物體,使用訊號針腳輸出的電壓值表示距離。輸出3.1V的時候是4公分,0.3V的時候是30公分。Raspberry Pi可以搭配類比數位轉換晶片,例如MCP3008,讀取紅外線測距模組的輸出電壓,就可以精確的算出偵測的物體距離。 不過這裡先把紅外線測距模組當作「接近感應器」來使用,Raspberry Pi的GPIO只能輸出與輸入數為的訊號,也就是只有高與低電壓兩種。設定為輸入用圖的GPIO針腳,接收到0V到1.19V範圍的電壓為低電壓訊號,接收到1.34V到3.3V範圍的電壓為高電壓訊號。 在GP2Y0A41SK0F的規格書,有一個像這樣的距離與電壓變化圖表,偵測距離為10公分的時候,輸出電壓大約為1.25V: 根據這樣的特性,使用GPIO高電壓訊號的1.34V電壓值,使用「12.3F * Math.pow(1.34, -1)」的公式,可以算出偵測距離為9.179公分的時候,GPIO可以接收到高電壓的訊號: 應用程式可以使用輸入用途的GPIO針腳,偵測紅外線測距模組的輸出針腳,接收到高電壓的時候,表示有物體接近到9.179公分。 9-2 連接紅外線測距模組Sharp GP2Y0A41SK0F紅外線測距模組採用JST)連接針腳,提供紅、黑、黃三個顏色的線路,你可以把它們插在麵包板上,再由Raspberry Pi連接需要的線路,包含5V、接地(GND)與設定為輸入用途的GPIO針腳。 為了讓測試的效果比較明顯,另外外連接一個LED,物體接近時會點亮LED。依照下面的線路圖連接所有的零件: 9-3 撰寫物體接近感應的應用程式連接好線路以後,最好再檢查一次,如果沒有錯誤的話,就可以啟動Raspiberry Pi,接下來準備撰寫應用程式。在NetBeans建立一個名稱為「DMSDemo01」的Java應用程式,記得為這個專案加入GPIO類別庫。參考列的程式碼完成這個應用程式: package dmsdemo01; import com.pi4j.io.gpio.GpioController; import com.pi4j.io.gpio.GpioFactory; import com.pi4j.io.gpio.GpioPinDigitalInput; import com.pi4j.io.gpio.GpioPinDigitalOutput; import com.pi4j.io.gpio.PinPullResistance; import com.pi4j.io.gpio.RaspiPin; public class DMSDemo01 { public static void main(String[] args) { // 建立GPIO控制物件 final GpioController gpio = GpioFactory.getInstance(); // 建立控制GPIO_01輸入的物件,紅外線測距模組的輸出針腳 final GpioPinDigitalInput pin01 = gpio.provisionDigitalInputPin(RaspiPin.GPIO_01, PinPullResistance.PULL_DOWN); // 建立控制GPIO_04,LED final GpioPinDigitalOutput pin04 = gpio.provisionDigitalOutputPin(RaspiPin.GPIO_04); int counter = 0; while (true) { // 讀取紅外線測距模組的輸出針腳的狀態 boolean isHigh = pin01.isHigh(); // 如果有物體靠近 if (isHigh) { counter++; System.out.println("Approaching... " + counter); if (counter > 2) { break; } } // 設定LED針腳的狀態與紅外線測距模組的輸出針腳一樣 pin04.setState(isHigh); delay(500); } gpio.shutdown(); System.exit(0); } private static void delay(int ms) { try { Thread.sleep(ms); } catch (InterruptedException e) { System.out.println(e.toString()); } } } 儲存與執行這個應用程式,使用一個物體或是你的手,放在紅外線測距模組前面並前後移動。接近感應器三次以後程式會自動結束。執行的結果會像這樣: Approaching... 1 Approaching... 2 Approaching... 3 如果應用程式希望在物體接近的時候,除了點亮LED外,也可以蜂鳴器發出警示聲音,就要先認識蜂鳴器這種零件。蜂鳴器有很多種規格,分為交流電與直流電,直流電的蜂鳴器也有各種不同的電壓規格。因為這裡採用GPIO輸出的針腳讓蜂鳴器發出警示聲音,GPIO輸出是直流電3.3V的電壓,所以你在選購蜂鳴器的時候,要特別留意它的電壓規格,一般販售的直流電3V到6V規格都可以使用。 蜂鳴器零件有兩個針腳,長針腳或標示為「+」的連接3.3V,短針腳連接GND,通電以後蜂鳴器就會發出聲音。這是一般蜂鳴器零件的外觀: 使用前面已經連接好的線路,參考下面的線路圖,把LED、電阻和蜂鳴器連接好。連接GPIO04針腳控制LED,蜂鳴器直接插在麵包板上,短針腳連接GND以後,長針腳連接到GPIO04針腳,不過要特別留意,連接蜂鳴器長針腳的時候,不可以經過電阻,否則電流就不夠讓蜂鳴器發出聲音: 完成零件與線路連接以後,執行之前已經寫好的程式碼,就可以執行測試的工作。在執行這個應用程式之前,先提醒一件關於蜂鳴器的事情,雖然建議你購買的蜂鳴器只有小小的一顆,不過它的聲音還蠻大的,所以你購買的蜂鳴器在頂端可能會貼一張圓形的貼紙,建議你不要把它撕下來,這樣聲音就會小一些。執行這個應用程式,測試看看物體接近紅外線測距模組的的時候,LED應該就會點亮,蜂鳴器也會發出警示聲音。 9-4 將類比訊號轉換為數位資訊Raspberry Pi提供的GPIO輸入應用,只能夠讀取數位的資訊。以GPIO輸入針腳來說,它只能夠偵測到高電壓與低電壓兩種訊號,也就是數位的1(高電壓)與0(低電壓)資訊。 這裡使用的紅外線測距模組,就是一種輸出類比訊號的零件,如果需要偵側物體的距離,之前說明的方式就沒有辦法完成。接下來使用一個常見的類比數位轉換零件「MCP3008」,把類比的資訊轉換為Raspbbery Pi可以處理的數位資訊,讓應用程式可以延伸到很普遍的類比零件與設備,例如可變電阻、搖桿、溫度感應器與紅外線測距模組。這是MCP3008晶片的外觀: MCP3008的規格是8通道10位元(bit)的數位類比轉換器,它可以輸入並轉換八個類比訊號,轉換後的數位資訊是0到1023。下面的圖型是它的針腳位置與名稱: 依照下面的線路圖,連接所有的零件: 為了讓應用程式可以更靈活的使用MCP3008,在應用程式為這個晶片建立一個包裝功能與提供服務的類別: package dmsdemo02; import com.pi4j.io.gpio.GpioPinDigitalInput; import com.pi4j.io.gpio.GpioPinDigitalOutput; public class MCP3008 { private static final boolean DEBUG = true; /** * MCP3008的八個輸入埠 */ public enum MCP3008Channels { /** * MCP3008的八個輸入埠 */ CH_00(0), CH_01(1), CH_02(2), CH_03(3), CH_04(4), CH_05(5), CH_06(6), CH_07(7); private int channel; private MCP3008Channels(int channel) { this.channel = channel; } public int getChannel() { return channel; } } // Serial data out private GpioPinDigitalInput serialDataOutput = null; // Serial data in、Serial clock、Chip select private GpioPinDigitalOutput serialDataInput = null; private GpioPinDigitalOutput serialClock = null; private GpioPinDigitalOutput chipSelect = null; public MCP3008(GpioPinDigitalOutput serialClock, GpioPinDigitalInput serialDataOutput, GpioPinDigitalOutput serialDataInput, GpioPinDigitalOutput chipSelect) { this.serialClock = serialClock; this.serialDataOutput = serialDataOutput; this.serialDataInput = serialDataInput; this.chipSelect = chipSelect; } /** * 讀取指定輸入埠的資料 * * @param channel 輸入埠 * @return 讀取的資料 */ public int read(int channel) { chipSelect.high(); serialClock.low(); chipSelect.low(); int adccommand = channel; // 0x18 => 00011000 adccommand |= 0x18; adccommand <<= 3; // 傳送讀取的輸入埠給MCP3008 for (int i = 0; i < 5; i++) { // 0x80 => 0&10000000 if ((adccommand & 0x80) != 0x0) { serialDataInput.high(); } else { serialDataInput.low(); } adccommand <<= 1; tickPin(serialClock); } int adcOut = 0; // 讀取指定輸入埠的資料 for (int i = 0; i < 12; i++) { tickPin(serialClock); adcOut <<= 1; if (serialDataOutput.isHigh()) { adcOut |= 0x1; } } chipSelect.high(); // 移除第一個位元 adcOut >>= 1; return adcOut; } /** * 讀取指定輸入埠的資料 * * @param channel 輸入埠 * @return 讀取的資料(電壓) */ public float readVoltage(int channel) { float result = -1; int value = read(channel); result = value * 3.3F / 1023; return result; } private void tickPin(GpioPinDigitalOutput pin) { pin.high(); pin.low(); } } 完成MCP3008類別以後,其它的工作就簡單多了。參考下列的程式碼完成這個應用程式: package dmsdemo02; import com.pi4j.io.gpio.GpioController; import com.pi4j.io.gpio.GpioFactory; import com.pi4j.io.gpio.GpioPinDigitalInput; import com.pi4j.io.gpio.GpioPinDigitalOutput; import com.pi4j.io.gpio.RaspiPin; public class DMSDemo02 { public static void main(String[] args) { System.out.println("DMSDemo02 start..."); // 建立GPIO控制物件 GpioController gpio = GpioFactory.getInstance(); // 建立GPIO_04輸入針腳物件 final GpioPinDigitalInput serialDataOutput = gpio.provisionDigitalInputPin(RaspiPin.GPIO_04); // 建立控制GPIO_05、GPIO_01、GPIO_06輸出物件 final GpioPinDigitalOutput serialDataInput = gpio.provisionDigitalOutputPin(RaspiPin.GPIO_05); final GpioPinDigitalOutput serialClock = gpio.provisionDigitalOutputPin(RaspiPin.GPIO_01); final GpioPinDigitalOutput chipSelect = gpio.provisionDigitalOutputPin(RaspiPin.GPIO_06); // 建立MCP3008物件 final MCP3008 mcp3008 = new MCP3008( serialClock, serialDataOutput, serialDataInput, chipSelect); int counter = 0; while (true) { // 讀取連接到MCP3008 0號通道的紅外線測距模組 float adcValue = mcp3008.readVoltage( MCP3008.MCP3008Channels.CH_00.getChannel()); // 轉換為距離(公分) double distance = 12.3F * Math.pow(adcValue, -1); if (distance < 5) { counter++; System.out.println("Approaching... " + counter); if (counter > 2) { break; } } System.out.println("Distance: " + String.format("%2.2f", distance)); delay(1000); } gpio.shutdown(); System.exit(0); } /** * 暫停指定的時間(毫秒、1000分之一秒) * * @param ms */ public static void delay(int ms) { try { Thread.sleep(ms); } catch (InterruptedException e) { System.out.println("================= " + e); } } } 示範影片: 執行這個應用程式以後,可以在畫面上顯示目前偵測的距離。不過這裡使用的紅外線測距模組,有效範圍是4到30公分,超過這個範圍的偵測結果都要忽略。 課程相關的檔案都可以GitHub瀏覽與下載。 |
cocolan1007
08/31
不好意思請問一下~
按照老師您說的步驟把9-4 將類比訊號轉換為數位資訊的實作做完後,在樹莓派上跑出來的距離會變成Distance: infinity
請問會是因為我用的是pi3嗎?
還是程式碼哪部分需要更改?