Skip to Content Java Solaris コミュニティ パートナー My Sun 日本 各国のサイト

Sun SPOT :無線センサーデバイスの新潮流
 

第4回:入出力ポートにセンサーやアクチュエータを取り付けてみよう


サン・マイクロシステムズ株式会社 町田修一

シリーズ第4回となる今回のテーマは、前回に続いて「Sun SPOTアプリケーション開発」です。Sun SPOTの入出力ポートにセンサーやアクチュエータをいろいろ取り付けて、Javaプログラムで制御してみましょう。曲げセンサーとスピーカーで、何か面白いアプリケーションが作れるでしょうか?



  目次



センサーボードの入出力インタフェース

Sun SPOT に付いていないセンサーを使いたい、あるいはサーボモータやリレー、スピーカーなどのアクチュエータをSun SPOTで制御したい場合、

  • 専用の拡張ボードを作成してSun SPOT本体に取り付ける
  • eDemoセンサーボードの入出力インタフェースを介して接続する

などの方法が考えられます。Sun SPOTは柔軟に拡張・カスタマイズできるように設計されており、利用者が拡張ボードを作成して取り付けられる構造になっています。また、拡張ボードを作成するために必要なハードウェア仕様はすべてオープンソースとして以下のサイト:

で公開されています。本記事の執筆時点で、図1に示すようないくつかの拡張ボードの仕様が公開されており、この仕様を元にプリント基板ベンダーに依頼してボードを作成したり(残念ながら現時点ではこれらのボードは販売されていません)、あるいは一から設計することも可能です。

図 1: オープンソースハードウェア
図1: オープンソースハードウェア

ただ、拡張ボードを作成するとなるとそれなりに大変です。また、Sun SPOTに元々付いている eDemoセンサーボード上には、外付けデバイスを接続できる入出力インタフェースがいくつか用意されています(図2)。

図 2: “eDemo”センサーボードの入出力インタフェース
図2: “eDemo”センサーボードの入出力インタフェース

そこで、今回はより簡単な方法として、eDemoセンサーボード上の入出力インタフェースを使って

  • LED
  • 曲げセンサー
  • スピーカー

をSun SPOTに取り付けて、Javaプログラムで制御する方法を紹介します。


Step 0: LEDを点滅させる

それでは最初に、外付けのLEDを点滅させてみます。一見簡単そうですが、入出力インタフェースの基本を理解するには最適だと思います。今回は図3のLED(赤色)を使用します。

図 3: LED(順電圧: 2.0V、電流: 20mA)
図3: LED(順電圧: 2.0V、電流: 20mA)

LEDを点灯させるための回路は、例えば図4のようになります。左側の回路図は見慣れない方が多いかもしれませんが、これは右側と同じものを表しています。電源と抵抗をつないで回路を作れば電流が流れて点灯します(小学校の理科の授業で習った豆電球の回路に少し似ていますね)。

図 4: LED点灯のための回路
図4:LED点灯のための回路

LEDに電源をつなぐ際の注意点として、LEDは流れる電流の量で光の明るさが変わるのですが、特定の電圧以下ではごくわずかな電流しか流れず光らないという特性があります。順電圧というのはLEDが光るのに必要な電圧で、このとき流れる電流量がLEDの特性としてマニュアルに記載されています。さらに、LEDに順電圧を超える電圧をかけると、急激に電流の値が増加して簡単に破損してしまいます。そこで、大抵の場合はLEDに電流が流れすぎないように回路に抵抗を追加します。 図5の写真は、LEDに3Vの電源をつないで点灯させたところです。

図5: LEDに電源を接続して点灯させたところ
図5:LEDに電源を接続して点灯させたところ

ところで、これだとLEDは点灯し続けるだけですし、仮にスイッチを付けたとしても手でOn、Offしなければ点滅しないのであまり面白くありません。図4のスイッチと同じ働きを Sun SPOTで実現するにはどうすればいいでしょう?

Sun SPOTを含め、マイコンには一般的に汎用入出力ポート(図2のD0~D4)というインタフェースが用意されていて、出力をプログラムから制御することで、出力ポートに接続した負荷(LED、リレー、モータ、トランジスタなど)を駆動することができます。出力値としてはHighまたはLowの2値の電圧をとります(例えば High(3V)、Low(0V)など)。

出力ポートに負荷を直接つないで駆動するには注意が必要です。出力ポートに流せる電流の最大値が決まっていて、この値は一般的に小さいからです。例えば、DCモータなどは駆動に必要な電流が大きく、出力ポートに直接つなぐことができないので、間にインタフェース回路を介して接続します。

出力ポートへの電流の流し方にはソースとシンクの2種類あります。出力ポートから流れ出る場合をソース、出力ポートに流れ込む場合をシンクと呼び、それぞれに最大許容電流が決まっています。

図6: 出力ポートによる負荷の駆動
図6:出力ポートによる負荷の駆動

図6に示すように、例えばソース電流を流す場合には、出力ポートをHighに設定します。こうするとGND(0V)との電位差が生じ、出力ポートからGNDの方向に電流が流れます。逆にシンク電流の場合は、出力ポートをLowに設定することで、Vcc(例えば5V)から出力ポートに電流が流れます。このように、出力ポートの電圧を切り替えて、スイッチのように動作させます。

前置きが長くなりましたが、イメージがつかめたところで、実際に回路とプログラムを組んでLEDを点滅させてみましょう。

図7が作成する回路です。今回は、Sun SPOTの汎用入出力ポートD0 を使うことにします。D0のHigh の時の電圧は3Vです。また、使用する抵抗の値は、10mA 程度の電流でLEDを駆動することにするとオームの法則より

R = V / I = (3.0 - 2.0) / 0.01 = 100 Ω

と計算できます。

図7: 今回作成するLEDを点滅させるための回路
図7:今回作成するLEDを点滅させるための回路

回路はブレッドボード(図8の左側)を使って、半田ごてを使わずに作成します。ブレッドボードは図中に示した赤ラインの方向が電気的につながっており、ライン間はジャンプワイヤ(図8の右側)で結線します。

図8: (左)ブレッドボード (右)ジャンプワイヤ
図8:(左)ブレッドボード (右)ジャンプワイヤ

Sun SPOTの出力ポート D0 、GNDとLEDの回路を接続して完成です(図9)。

図9: (Step 0のハードウェアを組み上げたところ
図9: Step 0のハードウェアを組み上げたところ

プログラムは、前回紹介した手順でNetBeans IDEを使って作成します。今回は、250ms間隔でLEDの点灯、消灯を繰り返すプログラムを作成します。リスト1に全ソースコードを示します。

アプリケーションの構成

  クラス名 説明
1 ExternalLEDDemoSpot 外付けのLEDを点滅させるSun SPOTアプリケーション


リスト 1: ExternalLEDDemoSpot -外付けのLEDを点滅させる Sun SPOTアプリケーション

package org.sunspotworld;

import com.sun.spot.sensorboard.*;
import com.sun.spot.sensorboard.io.IIOPin;
import com.sun.spot.sensorboard.peripheral.*;
import com.sun.spot.util.*;
import javax.microedition.midlet.*;

/**
 * ExternalLEDDemoSpot: 
 * 汎用入出力ポートに接続した外付けのLEDを点滅させる 
 * Sun SPOTアプリケーション
 */
public class ExternalLEDDemoSpot extends MIDlet {
    
    /** アプリケーション起動時にVMによって最初に呼び出されるメソッド */
    protected void startApp() throws MIDletStateChangeException {
        new BootloaderListener().start();

        // "eDemo"ボードの参照を取得
        IDemoBoard eDemo = EDemoBoard.getInstance();
        
        // 汎用入出力ポート(D0)にアクセスするための参照を取得
        // (今回は、ポートD0に外付けのLEDを接続する)
        IIOPin ledPin = eDemo.getIOPins()[EDemoBoard.D0];
        ledPin.setAsOutput(true);
        
        // eDemoボード上のLEDにアクセスするための参照を取得して、
        // 左端のLEDを赤色に設定する
        ITriColorLED[] leds = eDemo.getLEDs();
        leds[0].setColor(LEDColor.RED);

        // 外付けのLEDとeDemoボード上の左端のLEDを
        // 250ms 間隔で点滅(on-off)させる
        while (true) {
            // On
            ledPin.setHigh();     // 論理High(3V)出力
            leds[0].setOn();
            Utils.sleep(250);
            
            // Off
            ledPin.setLow();      // 論理Low(0V)出力
            leds[0].setOff();
            Utils.sleep(250);            
        }
    }

    protected void pauseApp() {
    }

    protected void destroyApp(boolean arg0) throws MIDletStateChangeException {
    }
}

アプリケーションの実行結果を図10に示します。写真だと点滅しているところまでは分かりませんが、LEDが赤く点灯しています(Sun SPOTのLEDも点滅させています)。

図10: Step 0の実行結果
図10: 0の実行結果

以下で動画もご覧頂けます。


Blinking an LED

Step Step 1: 曲げセンサーの値を読み取る

次に、Sun SPOTのアナログ入力ポート(図2のA0~A3)を使って外付けセンサーの値を読み取ってみます。アナログ入力ポートを使うと、センサーからの入力(電圧値)をA/D変換後のデジタル値として読み取ることができます。今回はアナログセンサーとして曲げセンサー(浅草ギ研)を使用します(図11)。

図11: 曲げセンサー
図11: 曲げセンサー

この曲げセンサーは、真っ直ぐな状態では約10KΩ、曲げると曲げる度合いにより抵抗値が30~40KΩまで増えるという特性を持っています。図12は、曲げセンサーを曲げたところです。

図12: 曲げセンサーを曲げたところ
図12: 曲げセンサーを曲げたところ

図13が作成する回路です。 抵抗値の変化は、アナログ入力ポートに入力される電圧値の変化として読み取ることができます。

図13: 今回作成する曲げセンサーの値を読み取るための回路
図13: 今回作成する曲げセンサーの値を読み取るための回路

LEDのときより若干複雑になりましたが、この回路は曲げセンサー(可変抵抗のようなもの)と抵抗を直列接続してその間に電圧(3V)をかけると、2つの抵抗で電圧が分圧されることを利用しています。分圧の比が抵抗値の比に等しくなる、というのは中学校で習いましたよね?今回は抵抗値として10KΩ(曲げセンサーが真っ直ぐな状態の抵抗値とほぼ同じ)を使用するため、例えば、

曲げセンサー(Ω) R 抵抗(Ω) 計算式 出力(V)
10K 10K 10 / (10 + 10) * 3.0 1.5
40K 10K 10 / (10 + 40) * 3.0 0.6


と計算してみると、曲げセンサーが真っ直ぐのときは1.5Vが出力され、曲げる度合いが大きくなると出力電圧の値が小さくなっていき0.6Vぐらいまで下がることがわかります。

それでは今回もブレッドボードとジャンプワイヤを使って回路を作成します。Sun SPOTの3V電源、GND、アナログ入力ポートA0を曲げセンサーの回路と接続して完成です(図14)。

図14: Step 1のハードウェアを組み上げたところ
図14: Step 1のハードウェアを組み上げたところ

念のため、曲げセンサーが真っ直ぐのときのアナログ入力ポートへの出力電圧を電圧計で測定してみます。予想通り、1.5Vが出力されていますね(図15)

図15: 真っ直ぐのときの出力値の測定
図15: 真っ直ぐのときの出力値の測定

プログラムは、NetBeans IDE を使って作成します。今回は、曲げセンサーの値を取得してLED表示するプログラムを作成します。リスト2および3に全ソースコードを示します。

アプリケーションの構成

  クラス名 説明
1 BendSensorDemoSpot1 曲げセンサーの値を取得してLED表示するSun SPOTアプリケーション
2 BendSensor 曲げセンサー制御用クラス


リスト2 :BendSensorDemoSpot1 曲げセンサーの値を取得してLED表示する Sun SPOTアプリケーション

package org.sunspotworld;

import com.sun.spot.sensorboard.EDemoBoard;
import com.sun.spot.sensorboard.peripheral.*;
import com.sun.spot.util.*;
import java.io.IOException;
import javax.microedition.midlet.*;

/**
 * BendSensorDemoSpot1:
 * 曲げセンサーから取得した値を元に、センサーボード上の8個のLEDを
 * 曲げの大きさに応じて段階的に点灯させる Sun SPOTアプリケーション
 */
public class BendSensorDemoSpot1 extends MIDlet {
    private static final float VOLT_DIFF = 0.05f;    // 曲げの大きさの一段階の幅
    private BendSensor bendSensor;    // 曲げセンサー制御用クラス
    private ITriColorLED[] leds;      // LED
    
    /** アプリケーション起動時にVMによって最初に呼び出されるメソッド */    
    protected void startApp() throws MIDletStateChangeException {
        new BootloaderListener().start();

        // 曲げセンサー制御用クラスのインスタンスを生成
        // ここでは、アナログ入力ポート A0、入力最大電圧 3.0V、
        // 分圧で使用する抵抗値として10KΩを指定
        bendSensor = new BendSensor(EDemoBoard.A0, 3.0f, 10000);
        
        // 曲げの大きさの表示に使用するLEDを初期化
        leds = EDemoBoard.getInstance().getLEDs();
        initLeds();
        
        // 以下、曲げセンサーから曲げの大きさを電圧値として取得して
        // LED表示を更新する処理を 200ms毎に繰り返す
        while (true) {
            try {
                float volt = bendSensor.getVolt();
                System.out.println("volt=" + volt);
                updateLeds(volt);
            } catch (IOException ex) {
                ex.printStackTrace();
            } catch (Exception ex) {
                ex.printStackTrace();
                notifyDestroyed();
            }
            Utils.sleep(200L);
        }
    }
    
    /* 引数で渡された曲げセンサーの値に応じてLED表示を更新する */
    private void updateLeds(float volt) {
        // 左端のLEDから何個目まで点灯させるか計算する
        int maxIndex = getMaxIndex(volt);

        // 左端のLEDから順に maxIndex まで点灯させ、残りを消灯する
        if (maxIndex >= 0) {
            for (int i = 0; i <= maxIndex; i++) {
                leds[i].setOn();
            }
        }
        
        for (int i = maxIndex + 1; i < leds.length; i++) {
            leds[i].setOff();
        }
    }
    
    /* 左端のLEDから何個目まで点灯させるか計算して、右端のLEDのインデックスを返す */
    private int getMaxIndex(float volt) {
        float target = bendSensor.getBend0Volt();
        for (int i = -1; i < leds.length; i++) {
            target -= VOLT_DIFF;                        
            if (volt > target) {
                return i;
            }
        }
        return leds.length-1;
    }
    
    /* LEDを青色に初期化する */
    private void initLeds() {
        for (int i = 0; i < leds.length; i++) {
            leds[i].setOff();
            leds[i].setColor(LEDColor.BLUE);
        }
    }
    
    protected void pauseApp() {
    }

    protected void destroyApp(boolean arg0) throws MIDletStateChangeException {
    }
}

リスト3: BendSensor - 曲げセンサー制御用クラス

package org.sunspotworld;

import com.sun.spot.sensorboard.EDemoBoard;
import com.sun.spot.sensorboard.io.IScalarInput;
import java.io.IOException;

/**
 * BendSensor:
 * 曲げセンサー制御用クラス
 * 今回使用する曲げセンサーは
 *  - まっすぐな状態で: 約 10KΩ
 *  - 90度曲げた状態で: 約 30K~40KΩ
 * に抵抗値が変化するので、分圧に使用する抵抗を10KΩとする
 */
public class BendSensor {
    // まっすぐな状態での曲げセンサーの抵抗値(Ω)
    public static final int VALUE_BEND_0 = 10000;
    private IScalarInput input;    // アナログ入力
    private float maxVolt;        // 曲げセンサーに加える電圧値
    private float regValue;       // 分圧用の抵抗の値
    private float bend0Volt;      // まっすぐな状態での入力ポートへの入力値
    
    /**
     * BendSensor のインスタンスを生成
     * @param pinId    曲げセンサーが使用するアナログ入力ポートのID
     * @param maxVolt  曲げセンサーに加える電圧値
     * @param regValue 分圧用の抵抗の値
     */
    public BendSensor(int pinId, float maxVolt, int regValue) {
        // アナログ入力ポートにアクセスするための参照を取得
        input = EDemoBoard.getInstance().getScalarInputs()[pinId];
        this.maxVolt = maxVolt;
        this.regValue = regValue;
        
        // 曲げセンサーがまっすぐな状態における、入力ポートへの入力値を計算
        // 今回抵抗として 10KΩ、電圧に3.0Vを使用しているので、
        // 曲げセンサーと抵抗とでちょうど半々に分圧され、計算的には 1.5V となる
        bend0Volt = (maxVolt * regValue) / (VALUE_BEND_0 + regValue);
    }
    
    /* 曲げの大きさを電圧値として返す */
    public float getVolt() throws IOException {
        return ((float)input.getValue() / input.getRange()) * maxVolt;
    }
    
    public float getBend0Volt() {
        return bend0Volt;
    }
}

アプリケーションの実行結果を図16に示します。曲げセンサーの曲がりの度合いによってLEDが点灯しています。

図16: Step 1の実行結果
図16: Step 1の実行結果

以下で動画もご覧頂けます。


Using a Bend Sensor

Step2: スピーカーで音を出す

次に、Sun SPOTの汎用入出力ポートにスピーカーをつないで音を出してみましょう(図17)。

図17: Sun SPOT + スピーカー
図17: Sun SPOT + スピーカー

市販スピーカーのミニプラグをSun SPOTに接続するために、Phone Jack (図18)を使用します。

 図18: Phone Jack
図18: Phone Jack

Sun SPOTの出力ポート D0、GNDと Phone Jack を接続して完成です(図19)。

 図19: Step 2: のハードウェアを組み上げたところ
図19: Step 2: のハードウェアを組み上げたところ

今回は、音生成用ライブラリ(ToneGenerator)を使って「ドレミファソラシド」を繰り返し演奏するプログラムを作成します。リスト4および5に全ソースコードを示します。

アプリケーションの構成

クラス名 説明
1 ToneGeneratorDemoSpot1 “ドレミファソラシド”を演奏するSun SPOTアプリケーション
2 MelodyPlayer 音の生成用クラス


リスト4: ToneGeneratorDemoSpot1 スピーカーを使って”ドレミファソラシド”を演奏する Sun SPOTアプリケーション

/*
 * Copyright (c) 2006, 2007 Sun Microsystems, Inc.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/
package org.sunspotworld.demo;

import com.sun.spot.sensorboard.EDemoBoard;
import javax.microedition.midlet.*;

/**
 * ToneGeneratorDemoSpot1:
 * 外付けスピーカーを使って"ドレミファソラシド" と繰り返し演奏する 
 * Sun SPOTアプリケーション
 * 付属のデモアプリ GPIOToneGeneratorSampleCode をベースに作成
 */
public class ToneGeneratorDemoSpot1 extends MIDlet {
    private MelodyPlayer player;    // メロディ演奏用クラス
    
    // 長調の音階
    static int major[] = { 0, 2, 4, 5, 7, 9, 11, 12 };
    
    /** アプリケーション起動時にVMによって最初に呼び出されるメソッド */        
    protected void startApp() throws MIDletStateChangeException {
        // メロディ演奏用クラスのインスタンスを生成
        // 今回はスピーカーを汎用入出力ポート D0に接続する
        player = new MelodyPlayer(EDemoBoard.D0);

        int start_note = 21;	// 開始の音 C or "ド"
        try {
            while(true){
                // "ドレミファソラシド" を繰り返す
                for (int i=0;i<major.length;i++) {
                    player.playNote(major[i] + start_note, 500, 80);
                }
            }
        } catch (Exception ex) { //A problem in reading the sensors.
            ex.printStackTrace();
            notifyDestroyed();
        }
    }
    
    protected void pauseApp() {
    }
    
    protected void destroyApp(boolean unconditional) throws MIDletStateChangeException {
    }
}

リスト5: MelodyPlayer - 音の生成用クラス

/*
 * Copyright (c) 2006, 2007 Sun Microsystems, Inc.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/
package org.sunspotworld.demo;

import com.sun.spot.sensorboard.EDemoBoard;
import com.sun.spot.sensorboard.peripheral.ToneGenerator;
import com.sun.spot.util.Utils;
import java.io.IOException;

/**
 * MelodyPlayer:
 * メロディ演奏用クラス
 * Sun SPOTの汎用入出力ポートに接続したスピーカーを使ってメロディを演奏する
 * 付属のデモアプリ GPIOToneGeneratorSampleCode をベースに作成
 */
public class MelodyPlayer {
    private ToneGenerator toneGen;    // トーンジェネレータ
    
    /**
     * @param pinId スピーカーで使用する汎用入出力ポートのID
     */
    public MelodyPlayer(int pinId) {
        // トーンジェネレータの生成
        toneGen = new ToneGenerator(EDemoBoard.getInstance().getIOPins()[pinId]);
    }

    /**
     * note で指定された音を演奏する
     * @param note	The note number (0=A110)
     * @param dur	全演奏時間
     * @param len	音を鳴らす時間
     */
    public void playNote(int note, int dur, int len) {
        int on = dur * len/100; 
        // note の値から、その音の周波数に対応する周期を取得する
        toneGen.setPeriod(note2period(note));
        toneGen.setDuration(on);
        toneGen.beep();
        Utils.sleep(dur);
    }        

    // 一番下のオクターブの音の周期
    private static final int tone_map[] = {
        4545, 4290, 4050, 3822, 3608, 3405, 3214, 3034, 2863, 2703, 2551, 2408
    };
    
    /**
     * note で指定された音の周期を計算する
     **/
    private static int note2period(int note) {
        int octave = note/12;
        return tone_map[note%12] / (1<<octave);
    }
}

以下でアプリケーションの実行結果の動画をご覧頂けます。


Playing a melody with a speaker

Step 3: 曲げセンサーの値を使ってリモートのスピーカーで音を出す

最後に、曲げセンサーとスピーカーを組み合わせて、もう少しだけ手の込んだ以下のようなアプリケーションを作ってみましょう:

  • 1つのSun SPOTで曲げセンサーの値を読み取り、曲げの度合いに応じた音を決定して音データをブロードキャスト
  • もう1つのSun SPOTで音データを受信し、音を出す

図20に示すように、ハードウェア構成自体は Step 1および 2で作成したものをそのまま使います。

 図20: Step 3のハードウェア構成
図20: Step 3のハードウェア構成

曲げセンサー側の Sun SPOTアプリケーションは、Step 1で作成したものを若干修正したもので、メッセージ送信の機能を追加しています。リスト6~8に全ソースコードを示します。

アプリケーションの構成

クラス名 説明
1 BendSensorDemoSpot2 曲げセンサーの値を取得して演奏用データをブロードキャストするSun SPOTアプリケーション
2 DatagramSender メッセージ送信用クラス
3 BendSensor 曲げセンサー制御用クラス


リスト6: BendSensoprDemoSpot2 -曲げセンサーの値を取得して演奏用データをブロードキャストする Sun SPOTアプリケーション

package org.sunspotworld;

import com.sun.spot.sensorboard.EDemoBoard;
import com.sun.spot.sensorboard.peripheral.*;
import com.sun.spot.util.*;
import java.io.IOException;
import javax.microedition.midlet.*;

/**
 * BendSensorDemoSpot2:
 * スイッチ1を押すと曲げセンサーから値を読み取り、センサーボード上の
 * 8個のLEDを曲げの大きさに応じて段階的に点灯させると同時に、演奏用データ
 * をスピーカーをつないだリモートの Sun SPOTにブロードキャストして
 * メロディを演奏する Sun SPOT アプリケーション
 */
public class BendSensorDemoSpot2 extends MIDlet {
    private static final String MESSAGE_BEND = "BEND";  // メッセージ
    private static final String DELIM = ",";    
    // ブロードキャストで使用する無線通信プロトコル
    private final String PROTOCOL = "radiogram://broadcast:240";   
    
    private static final float VOLT_DIFF = 0.05f;    // 曲げの大きさの一段階の幅
    private DatagramSender sender;    // メッセージ送信用クラス    
    private BendSensor bendSensor;    // 曲げセンサー制御用クラス
    private ITriColorLED[] leds;      // LED
    
    /* アプリケーション起動時にVMによって最初に呼び出されるメソッド */
    protected void startApp() throws MIDletStateChangeException {
        new BootloaderListener().start();         
        try {
            initAndRun();
        } catch (Exception ex) { 
            ex.printStackTrace();
        }
    }    
    
    /* メイン部分 */
    private void initAndRun() throws IOException {
        // 曲げセンサー制御用クラスのインスタンスを生成
        // ここでは、アナログ入力ポート A0、入力最大電圧 3.0V、
        // 分圧で使用する抵抗値として10KΩを指定        
        bendSensor = new BendSensor(EDemoBoard.A0, 3.0f, 10000);

        // データ送信用クラスのインスタンスを生成
        sender = new DatagramSender(PROTOCOL, 31, 3);        
        
        // 曲げの大きさの表示に使用するLEDを初期化        
        leds = EDemoBoard.getInstance().getLEDs();
        initLeds();
        
        // スイッチ1を初期化
        initSw1(); 
        
        while (true) {
            Utils.sleep(2000L);
        }                
    }
    
    /* スイッチ1を押した時に、演奏するnote(音)の値をブロードキャスト */
    private void initSw1() {
        ISwitch sw1 = EDemoBoard.getInstance().getSwitches()[EDemoBoard.SW1];

        // スイッチ1にリスナを登録する
        // リスナはスイッチが押された/離されたときに呼び出される        
        sw1.addISwitchListener(new ISwitchListener() {
            // スイッチが押された
            public void switchPressed(ISwitch sw) {
                try {
                    float volt = bendSensor.getVolt();
                    updateLeds(volt);

                    // フォーマット: BEND, <note>
                    int note = getNote(volt);
                    System.out.println("Sending the message: note=" + note);
                    
                    // note(音符)を"BEND" メッセージに乗せてブロードキャストする
                    sender.send(MESSAGE_BEND + DELIM + note);
                } catch (IOException ex) {
                    System.out.println("Error sending the message: " + ex.getMessage());
                    ex.printStackTrace();
                }
            }
            // スイッチが離された
            public void switchReleased(ISwitch sw) {
            }
        });
    }    
    
    // 長調の音階 
    static int major[] = { 0, 2, 4, 5, 7, 9, 11, 12 };   
    // 最初の音
    static int start_note = 21;	
        
    /* 曲げの大きさに応じて "ドレミファソラシド" のいずれかの音を選択 */
    private int getNote(float volt) {
        int maxIndex = getMaxIndex(volt);
        if (maxIndex >= 0) {
            return major[maxIndex] + start_note;
        } else {
            return -1;
        }
    }
    

    /* 引数で渡された曲げセンサーの値に応じてLED表示を更新する */
    private void updateLeds(float volt) {
        // 左端のLEDから何個目まで点灯させるか計算する
        int maxIndex = getMaxIndex(volt);

        // 左端のLEDから順に maxIndex まで点灯させ、残りを消灯する
        if (maxIndex >= 0) {
            for (int i = 0; i <= maxIndex; i++) {
                leds[i].setOn();
            }
        }
        
        for (int i = maxIndex + 1; i < leds.length; i++) {
            leds[i].setOff();
        }
    }

    /* 左端のLEDから何個目まで点灯させるか計算して、右端のLEDのインデックスを返す */        
    private int getMaxIndex(float volt) {
        float target = bendSensor.getBend0Volt();
        for (int i = -1; i < leds.length; i++) {
            target -= VOLT_DIFF;                        
            if (volt > target) {
                return i;
            }
        }
        return leds.length-1;
    }

    /* LEDを青色に初期化する */
    private void initLeds() {
        for (int i = 0; i < leds.length; i++) {
            leds[i].setOff();
            leds[i].setColor(LEDColor.BLUE);
        }
    }
    
    protected void pauseApp() {
    }

    protected void destroyApp(boolean arg0) throws MIDletStateChangeException {
    }
}

リスト7: DatagramSender - メッセージ送信用クラス

package org.sunspotworld;

import com.sun.spot.io.j2me.radiogram.RadiogramConnection;
import com.sun.spot.peripheral.Spot;
import java.io.IOException;
import javax.microedition.io.*;

/**
 * DatagramSender:
 * メッセージ送信用クラス
 * BendSensorDemoSpot2 がスピーカーを接続したリモートの Sun SPOTに
 * 向けて演奏データをブロードキャストする際に使用
 */
public class DatagramSender {
    private DatagramConnection conn;
    private Datagram datagram;
    
    /*
     * プロトコル(データグラム)、出力パワー及びマルチホップ
     * 通信時の最大ホップ数を指定して、DatagramSenderのインスタンスを生成
     */
    public DatagramSender(String protocol, int power, int maxHops) throws IOException {
        Spot.getInstance().getRadioPolicyManager().setOutputPower(power);
        conn = (DatagramConnection)Connector.open(protocol);
        ((RadiogramConnection)conn).setMaxBroadcastHops(maxHops);   
        datagram = conn.newDatagram(conn.getMaximumLength());
    }

    /* データグラムメッセージの送信 */
    public synchronized void send(String message) throws IOException {
        datagram.reset();
        datagram.writeUTF(message);
        conn.send(datagram);
    }    
}

リスト8: BendSensor - 曲げセンサー制御用クラス(再掲)

package org.sunspotworld;

import com.sun.spot.sensorboard.EDemoBoard;
import com.sun.spot.sensorboard.io.IScalarInput;
import java.io.IOException;

/**
 * BendSensor:
 * 曲げセンサー制御用クラス
 * 今回使用する曲げセンサーは
 *  - まっすぐな状態で: 約 10KΩ
 *  - 90度曲げた状態で: 約 30K~40KΩ
 * に抵抗値が変化するので、分圧に使用する抵抗を10KΩとする
 */
public class BendSensor {
    // まっすぐな状態での曲げセンサーの抵抗値(Ω)
    public static final int VALUE_BEND_0 = 10000;
    private IScalarInput input;    // アナログ入力
    private float maxVolt;        // 曲げセンサーに加える電圧値
    private float regValue;       // 分圧用の抵抗の値
    private float bend0Volt;      // まっすぐな状態での入力ポートへの入力値
    
    /**
     * BendSensor のインスタンスを生成
     * @param pinId    曲げセンサーが使用するアナログ入力ポートのID
     * @param maxVolt  曲げセンサーに加える電圧値
     * @param regValue 分圧用の抵抗の値
     */
    public BendSensor(int pinId, float maxVolt, int regValue) {
        // アナログ入力ポートにアクセスするための参照を取得
        input = EDemoBoard.getInstance().getScalarInputs()[pinId];
        this.maxVolt = maxVolt;
        this.regValue = regValue;
        
        // 曲げセンサーがまっすぐな状態における、入力ポートへの入力値を計算
        // 今回抵抗として 10KΩ、電圧に3.0Vを使用しているので、
        // 曲げセンサーと抵抗とでちょうど半々に分圧され、計算的には 1.5V となる
        bend0Volt = (maxVolt * regValue) / (VALUE_BEND_0 + regValue);
    }
    
    /* 曲げの大きさを電圧値として返す */
    public float getVolt() throws IOException {
        return ((float)input.getValue() / input.getRange()) * maxVolt;
    }
    
    public float getBend0Volt() {
        return bend0Volt;
    }
}

一方、スピーカー側の Sun SPOTアプリケーションはStep 2で作成したものを修正したもので、メッセージ受信の機能を追加しています。リスト9~11に全ソースコードを示します。

  クラス名 説明
1 ToneGeneratorDemoSpot2 受信した演奏用データの音をスピーカーを使って演奏するSun SPOTアプリケーション
2 MessageReceiver メッセージ受信用クラス
3 MelodyPlayer 音の生成用クラス


リスト9: ToneGeneratorDemoSpot2 - 受信した演奏用データの音をスピーカーを使って演奏する Sun SPOTアプリケーション

/*
 * Copyright (c) 2006, 2007 Sun Microsystems, Inc.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/
package org.sunspotworld.demo;

import com.sun.spot.sensorboard.EDemoBoard;
import com.sun.spot.util.BootloaderListener;
import java.io.IOException;
import javax.microedition.midlet.*;

/**
 * ToneGeneratorDemoSpot2:
 * BendSensorDemoSpot2 がブロードキャストするメッセージ(演奏データ)を
 * 受信して、外付けスピーカーを使って演奏するSun SPOTアプリケーション
 * 付属のデモアプリ GPIOToneGeneratorSampleCode をベースに作成
 */
public class ToneGeneratorDemoSpot2 extends MIDlet {
    // メッセージ受信で使用する無線通信プロトコル    
    private final String PROTOCOL = "radiogram://:240";
    
    private MelodyPlayer player;      // メロディ演奏用クラス
    private MessageReceiver receiver; // メッセージ受信用クラス
    
    /* アプリケーション起動時にVMによって最初に呼び出されるメソッド */
    protected void startApp() throws MIDletStateChangeException {
        new BootloaderListener().start();
        try {
            initAndRun();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
    
    /* メイン部分 */
    private void initAndRun() throws IOException {
        // メロディ演奏用クラスのインスタンスを生成
        // 今回はスピーカーを汎用入出力ポート D0に接続する        
        player = new MelodyPlayer(EDemoBoard.D0);
        
        // データ受信用クラスのインスタンスを生成
        receiver = new MessageReceiver(PROTOCOL);
        
        try {
            // メッセージを受信する度に、受け取った演奏データの音を演奏する
            while(true){
                try {
                    int note = receiver.getNote();
                    if (note != -1) {
                        System.out.println("Playing note...: " + note);
                        player.playNote(note, 500, 80);
                    }
                } catch (IOException ex) {
                    System.out.println("Error receiving a datagram: " + ex.getMessage());
                }
            }
        } catch (Exception ex) { //A problem in reading the sensors.
            ex.printStackTrace();
            notifyDestroyed();
        }
    }
    
    protected void pauseApp() {
    }
    
    protected void destroyApp(boolean unconditional) throws MIDletStateChangeException {
    }
}

リスト10: MessageReceiver - メッセージ受信用クラス

package org.sunspotworld.demo;

import java.io.IOException;
import javax.microedition.io.*;

/**
 * MessageReceiver:
 * メッセージ受信用クラス
 * BendSensorDemoSpot2 からブロードキャストされるメッセージを受信する
 */
public class MessageReceiver {
    private static final String MESSAGE_BEND = "BEND";  // メッセージ
    private static final String DELIM = ",";
    
    private DatagramConnection conn;
    private Datagram datagram;
    
    /*
     * プロトコル(データグラム)を指定して、
     * DatagramReceiverのインスタンスを生成
     */
    public MessageReceiver(String protocol) throws IOException {
        conn = (DatagramConnection)Connector.open(protocol);
        datagram = conn.newDatagram(conn.getMaximumLength());      
    }
    
    /* BendSensorDemoSpot2 からブロードキャストされるメッセージ 
      を受信して、演奏用データ(音: "ドレミファソラシドのいずれか")
      を取り出す */
    public synchronized int getNote() throws IOException {
        datagram.reset();
        conn.receive(datagram);
        String message = datagram.readUTF();
        String address = datagram.getAddress();
        int note = -1;
        
        // 先頭が "BEND" で始まるものだけメッセージとして受け付ける
        if (message.startsWith(MESSAGE_BEND)) {
            try {
                note = Integer.parseInt(
                        message.substring(message.indexOf(DELIM) + 1));
                System.out.println("Got a note(from " + address + ": " + note);
            } catch (Exception ex) {
                System.out.println("Error parsing the message: " + message);
                ex.printStackTrace();
            } 
        } else {
            System.out.println("Unknown message received from " +
                    address + ": " + message);
        }
        return note;
    }
}

リスト11: MelodyPlayer - 音の生成用クラス(再掲)

/*
 * Copyright (c) 2006, 2007 Sun Microsystems, Inc.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/
package org.sunspotworld.demo;

import com.sun.spot.sensorboard.EDemoBoard;
import com.sun.spot.sensorboard.peripheral.ToneGenerator;
import com.sun.spot.util.Utils;
import java.io.IOException;

/**
 * MelodyPlayer:
 * メロディ演奏用クラス
 * Sun SPOTの汎用入出力ポートに接続したスピーカーを使ってメロディを演奏する
 * 付属のデモアプリ GPIOToneGeneratorSampleCode をベースに作成
 */
public class MelodyPlayer {
    private ToneGenerator toneGen;    // トーンジェネレータ
    
    /**
     * @param pinId スピーカーで使用する汎用入出力ポートのID
     */
    public MelodyPlayer(int pinId) {
        // トーンジェネレータの生成
        toneGen = new ToneGenerator(EDemoBoard.getInstance().getIOPins()[pinId]);
    }

    /**
     * note で指定された音を演奏する
     * @param note	The note number (0=A110)
     * @param dur	全演奏時間
     * @param len	音を鳴らす時間
     */
    public void playNote(int note, int dur, int len) {
        int on = dur * len/100; 
        // note の値から、その音の周波数に対応する周期を取得する
        toneGen.setPeriod(note2period(note));
        toneGen.setDuration(on);
        toneGen.beep();
        Utils.sleep(dur);
    }        

    // 一番下のオクターブの音の周期
    private static final int tone_map[] = {
        4545, 4290, 4050, 3822, 3608, 3405, 3214, 3034, 2863, 2703, 2551, 2408
    };
    
    /**
     * note で指定された音の周期を計算する
     **/
    private static int note2period(int note) {
        int octave = note/12;
        return tone_map[note%12] / (1<<octave);
    }
}

アプリケーションの実行結果を図21に示します。

図21: Step 3の実行結果
図21: Step 3の実行結果

画像だとよく分からないと思いますので、以下の動画もご覧下さい。


Playing with a Bend Sensor and a Speaker

まとめ

第4回『入出力ポートにセンサーやアクチュエータを取り付けてみよう』はいかがでしたか?ぜひ、身近にあるセンサーやデバイスを Sun SPOTにつないで、みなさんのセンサーネットワークを創造してみてください。

  アンケート

  このコンテンツは参考になりましたか?
  非常に参考になった   少し参考になった   普通   全く参考にならない  

  自由記述:
            



 

バックナンバー
 ・ 2008年2月
 ・ 2008年3月
 ・ 2008年4月


お問い合わせ 会社情報 ニュース 採用情報 プライバシー 利用規定 商標 Copyright 1994-2009 Sun Microsystems, Inc.