Shuichi Machida's Weblog

« 前のページ | メイン | 次のページ »

http://blogs.sun.com/machida/date/20080521 2008年 5月 21日 水曜日

[Sun SPOT(24)] SDC SQUARE 5月号 Sun SPOT連載第4回: 『入出力ポートにセンサーやアクチュエータを取り付けてみよう』

SDC SQUARE 5月号に、

が掲載されております。

JavaOne 2008 の Hands on Labs (LAB-7430)で Bend Sensor(曲げセンサー)を使っていて (大渕さんによる体験レポートはこちら)、手軽で面白そうだったので使ってみました!

~~~~

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

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

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

 目次

  • センサーボードの入出力インタフェース
  • Step 0: LEDを点滅させる
  • Step 1: 曲げセンサーの値を読み取る
  • Step 2: スピーカーで音を出す
  • Step 3: 曲げセンサーの値を使ってリモートのスピーカーで音を出す
  • まとめ

~~~~

見どころ(動画):

Step 0: LEDを点滅させる:

 

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

 

Step 2: スピーカーで音を出す:

 

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

 

是非ご覧下さい! m(_ _)m 


[Sun SPOT Demo(8)] 反射型フォトセンサー(Photo Reflector)を使ってみる。

ライントレーサを Sun SPOTで作ってみたくなり、その前段階として反射型フォトセンサー(RPR-220)を試してみることにしました。

# 反射型フォトセンサー

反射型フォトセンサーは発光部(赤外線LED)と受光部(フォトトランジスタ)で構成されており、発光部に電流を流してLEDを発光させ、近距離(6mm程度)の物体からの反射光を受光するとトランジスタに電流が流れます。下図のような回路(抵抗値は、今回4mA程度LEDに電流を流すことを想定して、データシートの特性から算出しています)を組むと受信した反射光の量に応じた出力(電圧値)の変化を検出できます。

 

 ライントレーサは白地に引かれた黒いラインをロボットカーでトレースするもので、ラインを逸れたことを検出するために白と黒を判別する必要があります。物体が黒い場合、反射光が少なく電流があまり流れず、R2 部分であまり電圧降下がおこりません。一方、物体が白い場合は反射光が多くなり、多くの電流が流れて R2 での電圧降下が大きくなります。つまり、ざっくり言ってしまえば、「出力電圧が閾値より大きければ黒、小さければ白」というような判別ができます。

。。。

といことで、上図の回路を作成してみました。

 

 # 少し拡大。

 

今回は単純に、白黒の縞模様のボードを用意して、フォトセンサーを横切らせてみることにしました。プログラムでは、白を検出したときに Sun SPOT上のLEDを白く光らせます。

# 簡単なプログラム↓

 package org.sunspotworld;

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

/**
 * PhotoReflectorDemoSpot:
 * 反射型フォトセンサーを使って物体の白黒を判別してLED表示する
 * Sun SPOTアプリケーション
 */
public class PhotoReflectorDemoSpot extends MIDlet {
    private PhotoReflector photoReflector;    // 反射型フォトセンサー制御用クラス
    private ITriColorLED[] leds;      // LED
    
    /** アプリケーション起動時にVMによって最初に呼び出されるメソッド */    
    protected void startApp() throws MIDletStateChangeException {
        new BootloaderListener().start();

        // 反射型フォトセンサー制御用クラスのインスタンスを生成
        // ここでは、アナログ入力ポート A0を指定
        try {
            photoReflector = new PhotoReflector(EDemoBoard.A0);
        } catch (IOException ex) {
            System.out.println("Error initializing the PhotoReflector!!" );
            ex.printStackTrace();
            notifyDestroyed();
        }
        
        // LEDを初期化
        leds = EDemoBoard.getInstance().getLEDs();
        initLeds();
        
        while (true) {
            try {
                if (photoReflector.isWhite()) {
                    // 白の場合LEDをOn
                    ledsOn(true);
                } else {
                    // 黒の場合LEDをOff
                    ledsOn(false);                    
                }

            } catch (IOException ex) {
                ex.printStackTrace();
            } catch (Exception ex) {
                ex.printStackTrace();
                notifyDestroyed();
            }
            Utils.sleep(200L);
        }
    }
    
    private void ledsOn(boolean on) {
        for (int i = 0; i < leds.length; i++) {
            leds[i].setOn(on);
        }
    }
    
    /* LEDを青色に初期化する */
    private void initLeds() {
        for (int i = 0; i < leds.length; i++) {
            leds[i].setOff();
            leds[i].setColor(LEDColor.WHITE);
        }
    }
    
    protected void pauseApp() {
    }

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

 

 package org.sunspotworld;

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

/**
 * PhotoReflector:
 * 反射型フォトセンサー(RPR-220)制御用クラス
 */
public class PhotoReflector {
    private IScalarInput input;    // アナログ入力
    private int threshold;         // 白黒判別の閾値
    
    /**
     * PhotoReflector のインスタンスを生成
     * @param pinId    センサーが使用するアナログ入力ポートのID
     */
    public PhotoReflector(int pinId) throws IOException {
        // アナログ入力ポートにアクセスするための参照を取得
        input = EDemoBoard.getInstance().getScalarInputs()[pinId];
    
        // 閾値を計算。ここでは単純に、電流がほとんど流れない状態(黒)の
        // 80%まで電圧が降下したときに白と判定する
        threshold = (int)(input.getRange() * 0.8);
    }

    /** 白の場合 true */
    public boolean isWhite() throws IOException {
        int value = input.getValue();
        System.out.println("value=" + value);
        return (value < threshold) ? true : false;
    }

    
    /** 黒の場合 true */
    public boolean isBlack() throws IOException {
        return !isWhite();
    }
}

 

プログラムを Sun SPOTにインストールして、実行してみます。。

。。。。

どうやら上手くいったようです ;-)

# 動画↓


次は、この反射型フォトセンサーを2つ使ってサーボカーをライントレーサ化してみようと思います。

http://blogs.sun.com/machida/date/20080519 2008年 5月 19日 月曜日

[Sun SPOT でリモコンカーを作ろう(4)] 車体を組み上げてちょっと動かしてみる。

間にいろいろ作りたいものが出てきて、リモコンカーの製作がすっかり遅れていました orz たしか目標は、半田付けをしないで、できるだけ安い費用で作成することでした。キット化に向けたプロトタイプ作成という意味合いもあります。

今までのエントリはこちら:


最終的に、いくつか部品を追加、変更して、以下のパーツを使用することにしました(太字が今回追加した部分):

 品名  参考価格  数量
 無限回転サーボモータ(SX-01改)  3150  2
 スイッチ付き電池ボックス(単三×3: SBH331AS)
 147  1
 ブレッドボード(165-40-3010E)  210  1
ホクセイ マイクロ・テールギア 350  1
TAMIYA 楽しい工作シリーズ ナロータイヤセット(58mm径) -> MODELING ACCESSORIES Eタイヤ 44mm 2ヶ入り(OK MODEL CO, LTD.)

400  600

 1
ジャンプワイヤ(SPP-70)
 483  1
ジャンプワイヤキット(SKS-140)  1103  1
マウンティングストラップ  525  1
420GUR25 キャスター(ウレタン)
 138  1
ピンヘッダ(オス) 6P  50  1
(必要に応じて)サーボ延長ケーブル  数百円  2

何とか10,000円以内に収まりました ;-) 予算の半分以上はサーボです。。ジャンプワイヤは数本ずつ、マウンティングストラップは1本しか使わないので、シェアすればもう少し安くなります。また、前輪についてはちょうどいいものがなかなか見つからずしばらく探していたのですが、先日ツクモロボット王国に行った際に手頃なキャスターを見つけたので、マイクロ・テールギアの代わりに使うことにしました(こういうのはハンズにも売ってますね)。あ、あとサーボとキャスターを電池ボックスにくっつけるのに、強力接着剤が要りますね。

それでは早速組み上げます。

。。。。

# 完成!意外にまともな外観になりました。

 # ケーブリング部分を拡大

 # 前面から

 # 裏はこんな感じです↓

車体も完成したので、簡単に動かしてみます。今回は単純に、左回り、右回りなどの定型動作を自律的に繰り返すプログラムを作成してみました↓

package org.sunspotworld;

import com.sun.spot.peripheral.Spot;
import com.sun.spot.sensorboard.EDemoBoard;
import com.sun.spot.sensorboard.peripheral.ITriColorLED;
import com.sun.spot.sensorboard.peripheral.LEDColor;
import com.sun.spot.util.*;
import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;

public class ServoCarSpot extends MIDlet {
    private ServoCar servoCar;
    private ITriColorLED[] leds;

    /** ここからスタート */
    protected void startApp() throws MIDletStateChangeException {
        new BootloaderListener().start();   // monitor the USB (if connected) and recognize commands from host
        Spot.getInstance().getSleepManager().disableDeepSleep();

        servoCar = new ServoCar(EDemoBoard.H0, EDemoBoard.H1);
        leds = EDemoBoard.getInstance().getLEDs();

        while (true) {
            initLeds(LEDColor.BLUE);
            blinkLeds();
            Utils.sleep(1000L);

            // 左前回り
            servoCar.leftForward();
            Utils.sleep(5000L);
            servoCar.stop();

           // 右前回り
            servoCar.rightForward();

            Utils.sleep(5000L);
            servoCar.stop();

            // 右後ろ周り
            servoCar.rightBackward();

            Utils.sleep(5000L);
            servoCar.stop();

            // 左後ろ周り
            servoCar.leftBackward();

            Utils.sleep(5000L);
            servoCar.stop();

            // 極地左回り
            servoCar.pivotLeft();

            Utils.sleep(5000L);
            servoCar.stop();

            // 極地右回り
            servoCar.pivotRight();

            Utils.sleep(5000L);
            servoCar.stop();

            initLeds(LEDColor.RED);
            blinkLeds();
            Utils.sleep(5000L);
        }
    }

    private void initLeds(LEDColor color) {
        for (int i = 0; i < leds.length; i++) {
            leds[i].setOff();
            leds[i].setColor(color);
        }
    }

    private void blinkLeds() {
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < leds.length; j++) {
                leds[j].setOn();
            }
            Utils.sleep(150L);
            for (int j = 0; j < leds.length; j++) {
                leds[j].setOff();
            }
            Utils.sleep(150L);            
        }
    }

    protected void pauseApp() {
    }

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

package org.sunspotworld;

import com.sun.spot.sensorboard.EDemoBoard;
import com.sun.spot.sensorboard.IDemoBoard;
import com.sun.spot.sensorboard.peripheral.Servo;

public class ServoCar {
    private static final int MIN_VALUE = 1000;
    private static final int MID_VALUE = 1500;    
    private static final int MAX_VALUE = 2000;
    private Servo rightServo;
    private Servo leftServo;


    public ServoCar(int rightPinId, int leftPinId) {
        IDemoBoard demo = EDemoBoard.getInstance();
        /** 右側のタイヤを制御 */
       rightServo = new Servo(demo.getOutputPins()[rightPinId]);
        /** 左側のタイヤを制御 */
       leftServo = new Servo(demo.getOutputPins()[leftPinId]);
        stop();
    }

    /** 停止 */
    public void stop() {
        rightServo.setValue(MID_VALUE);
        leftServo.setValue(MID_VALUE);
    }

    /** 左前回り */
    public void leftForward() {
        rightServo.setValue(MAX_VALUE);
    }

    /** 右前回り */
    public void rightForward() {
        leftServo.setValue(MIN_VALUE);
    }

    /** 右後ろ回り */
    public void rightBackward() {
        rightServo.setValue(MIN_VALUE);
    }

    /** 左後ろ回り */
    public void leftBackward() {
        leftServo.setValue(MAX_VALUE);
    }

    /** 極地左回り */
    public void pivotLeft() {
        rightServo.setValue(MAX_VALUE);
        leftServo.setValue(MAX_VALUE);
    }

    /** 極地右回り */
    public void pivotRight() {
        rightServo.setValue(MIN_VALUE);
        leftServo.setValue(MIN_VALUE);
    }

}

プログラムをSun SPOT本体に流し込んで、いざ実行です。

。。。

無事、動きました ;-) 

動画はこちら↓

http://blogs.sun.com/machida/date/20080516 2008年 5月 16日 金曜日

[Sun SPOT Demo(7)] Sound Effects KIT を使って効果音を出してみる。

昨日家に帰った後、無性に効果音を出してみたくなったので、家に転がっていたキットを使って試してみました。

# ELEKIT の8色バトルサウンド。プラモデルやジオラマセットに組み込めば効果抜群(何の?)のようです。。


このキットはジャンパを差し替えることで8種類の効果音を再生可能で、DC2.5~5.0Vの電源とスピーカーを接続して、スイッチを押したときに音が出ます。今回は、Sun SPOTで効果音の再生と再生時間を制御するためにいくつか改造します。改造点は以下の1~3です:

 

改造したサウンド発生回路をSun SPOTに接続します。

  • 電池ボックスの5Vを Sun SPOTのVH、GNDを Sun SPOTのGNDに
  • サウンド発生回路の +5V を Sun SPOTのH0、GNDを Sun SPOTのGNDに
  • スピーカーをサウンド発生回路のスピーカ接続用ソケットに
# 完成図

 

プログラムでは、大電流出力ポート H0 をOn, Of することで、サウンドの発生と再生時間を制御します。また、今回はサウンド発生のトリガーとして Sun SPOTの照度センサーを使用し、周りが暗くなったら効果音を発生させるようにします。

 

package org.sunspotworld;

import com.sun.spot.peripheral.Spot;
import com.sun.spot.sensorboard.EDemoBoard;
import com.sun.spot.sensorboard.peripheral.ILightSensor;
import com.sun.spot.sensorboard.peripheral.ILightSensorThresholdListener;
import com.sun.spot.util.*;
import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;

public class SoundEffectsDemoSpot extends MIDlet {
    private final int LOW_VALUE = 20;   // 暗い
    private final int HIGH_VALUE = 60; // 明るい
            
    private SoundEffects sounds;
    private ILightSensor lightSensor;
    
    private boolean lastHigh = false;

    /** ここからスタート */
    protected void startApp() throws MIDletStateChangeException {
        new BootloaderListener().start();   // monitor the USB (if connected) and recognize commands from host
        Spot.getInstance().getSleepManager().disableDeepSleep();
        
        sounds = new SoundEffects(EDemoBoard.H0);

        initLightSensor();
    }

    private void initLightSensor() {
        lightSensor = EDemoBoard.getInstance().getLightSensor();
        
        /* 照度センサーの値がある一定の閾値(low, high)を超えたときに
           呼び出されるイベントリスナーを登録 */
        lightSensor.addILightSensorThresholdListener(new ILightSensorThresholdListener() {
            public void thresholdChanged(ILightSensor light, int low, int high) {
                System.out.println("thresholdChanged( low=" + low + ", high=" + high + " )" );
            }
            
            public void thresholdExceeded(ILightSensor light, int val) {
                if (val <= LOW_VALUE) {
                    // 暗くなった
                    System.out.println("It's dark now!: " + val);
                    if (isLastHigh()) {
                        play();
                        setLastHigh(false);
                    }                        
                } else {
                    // 明るくなった
                    System.out.println("It's bright now!: " + val);     
                    setLastHigh(true);
                }
                lightSensor.enableThresholdEvents(true);
            }
        });
        
        // 照度センサーに閾値を設定し、イベント処理を有効にする
        lightSensor.setThresholds(LOW_VALUE, HIGH_VALUE);  // low, high(明るさ)   
        lightSensor.enableThresholdEvents(true);
    }

    
    private boolean isLastHigh() {
        return this.lastHigh;
    }
    
    private void setLastHigh(boolean lastHigh) {
        this.lastHigh = lastHigh;
    }
    
    /** 効果音を生成する */
    private void play() {
        sounds.play();
    }

    
    protected void pauseApp() {
        // This will never be called by the Squawk VM
    }

    protected void destroyApp(boolean arg0) throws MIDletStateChangeException {
        // Only called if startApp throws any exception other than MIDletStateChangeException
    }
}

 

package org.sunspotworld;

import com.sun.spot.sensorboard.EDemoBoard;
import com.sun.spot.sensorboard.io.IOutputPin;
import com.sun.spot.sensorboard.peripheral.ITriColorLED;
import com.sun.spot.sensorboard.peripheral.LEDColor;
import com.sun.spot.util.Utils;

public class SoundEffects {
    private static final long DEFAULT_DURATION = 5000L;
    private long duration;
    
    private IOutputPin out;
    private ITriColorLED[] leds;
    
    public SoundEffects(int pinId) {
        this(pinId, DEFAULT_DURATION);
    }
    
    public SoundEffects(int pinId, long duration) {
        out = EDemoBoard.getInstance().getOutputPins()[pinId];
        initLeds();
        
        if (duration > 0) {
            this.duration = duration;
        } else {
            this.duration = DEFAULT_DURATION;
        }
    }
    
    /** 効果音を生成する */
    public void play() {
        // 効果音スタート
        updateLeds(true);
        out.setHigh();
        
        Utils.sleep(duration);
        
        // 効果音ストップ
        updateLeds(false);        
        out.setLow();            
    }

    
    private void initLeds() {
        leds = EDemoBoard.getInstance().getLEDs();
        for (int i = 0; i < leds.length; i++) {
            leds[i].setOff();
            leds[i].setColor(LEDColor.BLUE);
        }
    }
    
    private void updateLeds(boolean isOn) {
        for (int i = 0; i < leds.length; i++) {
            leds[i].setOn(isOn);
        }
    }
}

それでは実行してみます。

。。。。

# ♪☆×○★!! 

 

動画はこちらです↓ (注:音が出ます)




http://blogs.sun.com/machida/date/20080514 2008年 5月 14日 水曜日

# [Sun SPOT Demo(1)-2] 距離センサー(Distance Sensor) Demo: 動画を撮ってみた。

もうひとつ、[Sun SPOT Demo(1)]でご紹介した距離センサーのデモの動画も撮ってみました。


[Sun SPOT Demo(4)-2] Isolate migration: 動画を撮ってみた。

[Sun SPOT Demo(4)] で実行中の音楽をSun SPOT間で移動させる、Isolate migration のデモをご紹介しましたが、動画を撮ってみました。


JASRACが怖いのでメロディーがショボいですが orz

http://blogs.sun.com/machida/date/20080512 2008年 5月 12日 月曜日

[Sun SPOTトラブルシューティング(2)] Sun SPOTが暴走して操作を受け付けなくなったときの対処法

うまく動かないアプリケーションをSun SPOTにインストールしてしまったり、起動時に致命的なエラーが発生した場合など、Sun SPOTが暴走してステータスLEDが点滅を繰り返し、以後のインストールやファームウェアのアップグレードを受け付けなくなってしまう(ようにみえる)ことがあります。

このような場合は、コマンドラインから以下のコマンド:

 # ant upgrade -Dport=<portnumber>

または

 # ant upgrade -Dspotport=<portnumber>

をお試しください。<portnumber> はSun SPOTが使用しているシリアル(COM)ポートの番号です。

おそらく、大抵の場合は復旧すると思います。

# 例

C:\> cd Sun\SunSPOT\sdk    (SDKのホームに移動)

C:\Sun\SunSPOT\sdk> ant upgrade -Dport=COM29


フォーラムの以下のポストも参考になると思います:

- SPOT recovery problem

http://blogs.sun.com/machida/date/20080507 2008年 5月 07日 水曜日

[Sun SPOTで"デューク神棚(Duke Temple)"を動かそう(4)] 動画を撮ってみた。

動画を撮ってみました。扉が開く時の音に、何となく趣が。。。 ないですね。

 

# Duke "model" temple



 

http://blogs.sun.com/machida/date/20080501 2008年 5月 01日 木曜日

[Sun SPOT API Tips(2)] Sun SPOTのバッテリー残量や電流消費量を調べたい時は?

よく

  • 「Sun SPOTのバッテリー残量を取得できますか?」
  • 「Sun SPOTの電流消費量を調べられますか?」

といった質問を受けることあります。 Solarium (a.k.a. SPOT World)という管理ツールを使うと、ホストPC上からリモートにある Sun SPOTのステータス情報を取得することができるのですが、Sun SPOTアプリケーションではSun SPOT API V3.0 IPowerController インタフェースが使えます。このAPIを使うとバッテリー残量や電流消費量などの電源関連の情報がいろいろ入手できます。

コード例:

package org.sunspotworld;

import com.sun.spot.peripheral.IPowerController;
import com.sun.spot.peripheral.Spot;
import com.sun.spot.util.*;
import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;

public class StartApplication extends MIDlet {

    protected void startApp() throws MIDletStateChangeException {
        new BootloaderListener().start();   // monitor the USB (if connected) and recognize commands from host
        Spot.getInstance().getSleepManager().disableDeepSleep();
        
        IPowerController powerController =
                Spot.getInstance().getPowerController();

        
        while (true) {
            // Vbatt の値で、大体のバッテリー残量を推定可能
            // APIドキュメントによると、2700~4700mVの範囲
            System.out.println("battery supply(mV): " +
                    powerController.getVbatt());
            
            // 現在の電流消費量を取得
            System.out.println("current discharge(mA): " +
                    powerController.getIdischarge());        
            
            // 現在のチャージ電流の量を取得
            System.out.println("current charge(mA): " +
                    powerController.getIcharge());                        

            Utils.sleep(10000L);
        }
    }

    protected void pauseApp() {
    }

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

 

# 実行結果


# 裏で無線通信しているので、その時だけ電流消費量が数十mA 上がっています。

なお、ホストPCにUSB接続している場合はバスパワーで動作して充電します。USBつないでいないのにchargeの方も値が出てる(ようにみえる)のは謎です。。

[Sun SPOT API Tips(1)] Sun SPOTのバッテリー残量少/リセット/パワーオフのタイミングで処理を実行したい時は?

Sun SPOT アプリケーションで

  • Sun SPOTのバッテリ残量が少なくなった
  • Sun SPOTをリセットした
  • Sun SPOTをパワーオフした
タイミングで処理を実行したい場合、FiqInterruptDaemon クラスが使えます。

o Sun SPOT API V3.0

FiqInterruptDaemon のインスタンスを Spot から取得して、イベントを処理するハンドラを登録します。例えばこんな感じです↓

package org.sunspotworld;

import com.sun.spot.peripheral.FiqInterruptDaemon;
import com.sun.spot.peripheral.IEventHandler;
import com.sun.spot.peripheral.Spot;
import com.sun.spot.util.*;
import com.sun.squawk.VM;

import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;

public class StartApplication extends MIDlet {
    
    protected void startApp() throws MIDletStateChangeException {
        new BootloaderListener().start();   // monitor the USB (if connected) and recognize commands from host

        // FiqInterruptDaemon は、eSPOT(メインボード)の電源コントローラからの
        // イベントを処理するハンドラを扱うためのクラス
        FiqInterruptDaemon fiqDaemon =
                Spot.getInstance().getFiqInterruptDaemon();


        // リセットボタンを押した時の処理を実行するハンドラを登録する
        fiqDaemon.setButtonHandler(new IEventHandler() {
            public void signalEvent() {
                System.out.println("Resetting..." );
                // TODO: ここに何か処理を実装                                
                VM.stopVM(0);
            }
        });

        
        // 電源をオフにした時の処理を実行するハンドラを登録する        
        fiqDaemon.setPowerOffHandler(new IEventHandler() {
            public void signalEvent() {
                System.out.println("Powering off..." );
                // TODO: ここに何か処理を実装                
                // APIドキュメントによると、パワーオフまでにこのハンドラで
                // 処理できる時間は 400ms 程度。
            }
        });

        
        // バッテリー残量が少なくなった時の処理を実行するハンドラを登録する
        fiqDaemon.setLowBatteryHandler(new IEventHandler() {
            public void signalEvent() {
                System.out.println("Battery is Low!!" );
                // TODO: ここに何か処理を実装
            }
        });

        
        while (true) {
            // TODO: ここに何か処理を実装
            Utils.sleep(2000L);
        }
//        notifyDestroyed();                      // cause the MIDlet to exit
    }

    protected void pauseApp() {
    }

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

http://blogs.sun.com/machida/date/20080428 2008年 4月 28日 月曜日

[Sun SPOT Demo(6)] OLED Display (μOLED-96-G1) を使ってみる。

 ストロベリー・リナックスで超小型のフルカラーOLED(有機EL)ディスプレイモジュールを見つけたので、Sun SPOTのeDemoボードに取り付けてみました。

# OLEDモジュール。かなり小さいです!

# 裏。microSDカードも使えます。

 以前 LCDをつないだ時にも書きましたが、Sun SPOTの eDemoボード上の汎用I/Oピン D0, D1を使うと簡単に非同期シリアル通信(UART)ができます (最大 baud rate は 38400, data 8bits, no parity, 1or2 stop bits)。

# UART で使用するI/Oピン

今回のOLEDモジュールもシリアル接続できるので、Sun SPOTで制御できそうです。ただ、OLEDモジュールは TTLレベル(0-3.3V)とのことで、そのまま Sun SPOTのRx と OLEDモジュールの Tx をつなぐとD0ピンの許容最大電圧 3.0Vを超えてしまいます。このような場合、レベルシフトバッファなどを使って電圧を調整するそうなのですが、抵抗で分圧しても良いみたいですので、今回は抵抗を使います。

# 1KΩと100Ωの抵抗で分圧。電圧計で実際に測ったところ 2.8V程度でしたが、ロジックレベル 'H' として十分認識される範囲なのでOKでしょうか。。

 

早速電子回路の作成です。 OLEDモジュールは最大で100mA以上消費するので、Sun SPOTの5Vでは危なそうなので外部電源を使います。

 # ちょっと拡大。相変わらず半田付けがてきとうです。目が悪すぎて手元が良く見えないせいということにしておきます ;-)

 作成した回路基板がSun SPOTにマウントできるように、裏にはピンヘッダを取り付けています。

 装着。

# 横から見るとこんな感じです。ArshanがUSから持ってきていたディスプレイボードとは違い、不恰好です orz

# まぁ、eProto ボードが手元にないのでしょうがないのですが。。

 。。。

ハードウェアは完成です!ここまでくれば、後は パパッと Javaでプログラムを作成してインストールするだけです。

。。。

電源を入れてみました。

# OLEDのロゴに続いて、、、 

# お約束の?メッセージが表示されました ;-) それにしても、このディスプレイ結構表示が綺麗です。

プログラムはこんな感じです。

#  メイン部分のプログラム(MIDlet)

package org.sunspotworld;

import com.sun.spot.peripheral.ISleepManager;
import com.sun.spot.peripheral.Spot;
import com.sun.spot.util.*;
import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;

public class StartApplication extends MIDlet implements MicroOLEDConstants {
    private IOled oled;  // OLED
    
    protected void startApp() throws MIDletStateChangeException {
        new BootloaderListener().start();
        
        // USBケーブルでPCにつないでいない状態で電源が切れないように、
        // 自動電源管理によるdeep sleepをオフにする
        ISleepManager sleepManager = Spot.getInstance().getSleepManager();
        sleepManager.disableDeepSleep();
        
        // uOLED-96-G1 にアクセスするためのクラスのインスタンスを生成
        oled = new MicroOLED96G1();

        
        // OLED にメッセージを表示
        try {
            oled.clear();
            oled.drawString("Hi!", 0, 0, FONT_8x12, RED);
            oled.drawString("This is", 0, 2, FONT_8x12, GREEN);
            oled.drawString("a Sun SPOT!!", 0, 3, FONT_8x12, BLUE);
        } catch (Exception ex) {
            ex.printStackTrace();
            Utils.sleep(2000L);
        }

        
        while (true) {
            // TODO: ここに処理を追加
            Utils.sleep(2000L);
        }
    }
    
    protected void pauseApp() {
    }
    
    protected void destroyApp(boolean arg0) throws MIDletStateChangeException {
    }
}

IOledは、OLEDモジュールにアクセスするためのインタフェースを定義します。

package org.sunspotworld;

public interface IOled {
    /** OLEDの画面をクリア */
    void clear() throws CommandExecutionException;
    /** column, row で指定された位置に引数で指定された文字列を表示する */
    void drawString(String text, int column, int row, int font, byte[] color) throws CommandExecutionException;
}

package org.sunspotworld;

public interface MicroOLEDConstants {
    // コマンド
    // ここでは、今回実際に使用するもののみ定義
    public static final byte ACK = (byte)0x06;
    public static final byte NAK = (byte)0x15;
    
    public static final byte COMMAND_INIT = (byte)'U';
    public static final byte COMMAND_CLS = (byte)'E';
    public static final byte COMMAND_DRAW_STRING = (byte)'s';
    
    // フォント関連
    public static final byte FONT_5x7 = (byte)0x00;
    public static final byte FONT_8x8 = (byte)0x01;
    public static final byte FONT_8x12 = (byte)0x02;
    
    // 文字列関連
    public static final byte STRING_TERMINATOR = (byte)0x00;

    // 色関連
    public static final byte[] BLACK = {(byte)0x00, (byte)0x00};
    public static final byte[] RED = {(byte)0xF8, (byte)0x00};
    public static final byte[] GREEN = {(byte)0x07, (byte)0xE0};    
    public static final byte[] BLUE = {(byte)0x00, (byte)0x1F};    
    public static final byte[] WHITE = {(byte)0xFF, (byte)0xFF};    
}

 

package org.sunspotworld;

public class CommandExecutionException extends java.lang.Exception {
    public CommandExecutionException() {
    }

    public CommandExecutionException(String message) {
        super(message);
    }
}

MicroOLED96G1 は IOled インタフェースの実装クラスで、OLEDにアクセスするための機能を実装します。なおここでは最小限の機能のみ実装しています(実際、このデバイスはかなり高機能です)。

package org.sunspotworld;

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

public class MicroOLED96G1 implements IOled, MicroOLEDConstants {
    private EDemoBoard demo;

    public MicroOLED96G1() {
        demo = EDemoBoard.getInstance();
        // baud: 38400bps, 8bits, no parity, 1 stop bit
        demo.initUART(EDemoBoard.SERIAL_SPEED_9600, false);
        initialize();
    }
   
    private void initialize() {
        send(COMMAND_INIT);
        try {
            receiveAck();
        } catch (CommandExecutionException ex) {
            ex.printStackTrace();
        }
    }

    public void clear() throws CommandExecutionException {
        send(COMMAND_CLS);
        receiveAck();
    }

   
    public void drawString(String text, int column, int row, int font, byte[] color) throws CommandExecutionException {
        byte[] data = text.getBytes();
        if (data.length > 255)
            throw new IllegalArgumentException("text length out of range: " + data.length);
       
        send(COMMAND_DRAW_STRING);
        send((byte)column);
        send((byte)row);
        send((byte)font);
        for (int i = 0; i < color.length; i++) {
            send(color[i]);
        }
        for (int i = 0; i < data.length; i++) {
            send(data[i]);
        }
        send(STRING_TERMINATOR);
       
        receiveAck();
    }

   
    private void send(final byte data) {
        demo.sendUART(data);
    }

    private byte receive(final long timeout) throws IOException {
        long startTime = System.currentTimeMillis();
        while (true) {
            try {
                byte data = demo.receiveUART();
                return data;
            } catch (IOException ex) {
                if ((timeout > 0) & (System.currentTimeMillis() - startTime) > timeout) {
                    throw ex;
                }
            }
            Utils.sleep(10L);
        }
    }

    private byte receiveAck() throws CommandExecutionException {
        int retry = 0;
        while (retry < 3) {
            Utils.sleep(20L);
            try {
                byte status = demo.receiveUART();
                if (!(status == ACK)) {
                    throw new CommandExecutionException("response -> " + Integer.toHexString(status));
                }
                return status;
            } catch (IOException ex) {
                System.out.println("Error in receiveAck: " + ex);
                retry++;
            }
        }
        throw new CommandExecutionException("No response" );
    }
}

とりあえず動かすことはできましたが、まだこのデバイスの持つ機能をほとんど使っていないので、時間があるときにもう少しいじってみようと思います。


http://blogs.sun.com/machida/date/20080425 2008年 4月 25日 金曜日

[Sun SPOT(23)] Sun SPOT SDK の'nightly builds' と SPOT Manager Tool v3.0 が公開されました。

前回のエントリで、「Sun SPOTのEmulator を試したい場合は SDK Beta プログラムに参加してみてください」と書いたのですが、その2日後ぐらいにフォーラムBlog SPOT Blog でビッグニュースが :-)

  • Sun SPOT Manager Tool v3.0 が公開されました
  • SDKの'nightly builds' がリリースされるようになりました
'nightly builds'  は2~3週間に1度リリースされるみたいですね。

Sun SPOT Manager ToolのURL:

  • http://www.sunspotworld.com/SPOTManager/


是非アクセスしてみてください。

http://blogs.sun.com/machida/date/20080422 2008年 4月 22日 火曜日

[Sun SPOT(22)] Q: 「まだ購入していないけど Emulator を使って Sun SPOTプログラムを試してみたい!」

以前、こちらのエントリ等でSun SPOT SDK のインストールや SPOT Managerツール、エミュレータの使用方法について紹介したのですが、書いた時点ではこれらのツールは Sun SPOT Java Development Kit (つまり実機)を購入頂いた方のみ使用可能ということで、途中からツールの配布URLを削除していました。

フォーラムDavidさんのブログでアナウンスされていますが、最新版の Software-only Beta の配布が始まっております。まだ購入していないけどエミュレータを使ってSun SPOTプログラムを試してみたい!という方は、是非 Beta プログラムに参加してみてください!

参加方法は David のブログを参照してください。ブログにあるアドレスにメールを送ればOKです。メールのSubject(件名)に "SDK Beta" と書くのを忘れずに、とのことです。

ツールに対する改善点やご要望などのフィードバックも、是非よろしくお願いいたします!

ツールのインストールや設定方法は  David のこのエントリでも詳しく紹介されています。


# Sun SPOTアプリケーションをエミュレータで実行中。。。


[Sun SPOT で"デューク神棚(Duke Temple)" を動かそう(3)] 距離センサー(Distance Sensor)で人を検出して扉を開閉する。

前回のエントリで扉の開閉までできましたので、次は神棚に距離センサーを取り付けて、

  • 前に人が立った時に自動的に扉が開く
  • 人が立ち去った時に扉が閉まる
ような動作を実装してみます。

# 距離センサーを取り付けたところ↓

前回作成した回路に、距離センサーを追加します。Sun SPOT の 5V、GNDに距離センサーの5V、GNDを、またアナログ入力ピンA0に距離センサーの出力を接続します。

# 接続の図。

 # ちょっと拡大。

ちょっと長いですが、プログラムはこんなかんじです↓

扉の開閉を安定させるために、3回以上連続して人を検出した/しなかった時に扉を開閉しています。

 package org.sunspotworld;

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

public class TempleDemoSpot extends MIDlet {
    private DoorController doorController;  // 扉開閉制御スレッド
    
    protected void startApp() throws MIDletStateChangeException {
        try {
            initAndRun();
        } catch (IOException ex) {
            ex.printStackTrace();
        } finally {
            if (doorController != null) {
                doorController.terminate();
            }
        }
    }
    
    private void initAndRun() throws IOException {
        new BootloaderListener().start();
        Spot.getInstance().getSleepManager().disableDeepSleep();
        
        doorController = new DoorController();
        doorController.start();
        
        while (true) {
            // TODO:
            // 後でここに処理を追加する
            Utils.sleep(2000L);
        }
    }
    
    /**
     * DoorController クラスは、アナログ入力ピンA0に接続した距離センサーから
     * 定期的(DETECTION_INTERVAL間隔)に値を取得して、以下の動作を実行します:
     *  o COUNT_THRESHOLD 回連続して10cm~80cmの間に物体を検出した場合に扉を開きます
     *  o COUNT_THRESHOLD 回連続して物体を検出しなかった場合に扉を閉じます
     */
    class DoorController extends Thread {
        private final long DETECTION_INTERVAL = 1000L;
        private final int COUNT_THRESHOLD = 3;
        private TempleManager templeManager;    // 神棚の扉の開閉を制御するクラス
        private DistanceSensor distanceSensor;  // 距離計測用クラス

        private int countInRange;      // 連続して物体を検出した回数
        private int countOutOfRange;   // 連続して物体を検出しなかった回数
        
        private volatile boolean  running = true;
        private boolean doorOpened = false;   // 扉の状態(開: true, 閉: false)
        
        DoorController() throws IOException {
            // 大電流出力ピンH0、H1を使用する
            IOutputPin[] pins = EDemoBoard.getInstance().getOutputPins();
            templeManager = new TempleManager(
                    pins[EDemoBoard.H0],pins[EDemoBoard.H1]);
            
            // アナログ入力ピンA0を使用する
            distanceSensor = new DistanceSensor(EDemoBoard.A0);
        }
        
        public void run() {
            while (running) {
                try {
                    doDetect();    // 物体の検出を実行
                    updateDoorStatus();    // 扉の状態(開/閉)を更新
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
                Utils.sleep(DETECTION_INTERVAL);
            }
        }
        
        /*
         * 物体の検出を実行する
         */
        private void doDetect() throws IOException {
            // 距離センサーから値を取得
            float distance = distanceSensor.getValue();
            if (distance >= 10.0f && distance <= 80.0f) {
                // 物体を検出した
                updateCountInRange();
            } else {
                // 物体を検出しなかった
                updateCountOutOfRange();
            }
        }
        
        /*
         * 扉の状態(開/閉)を更新する
         */
        private void updateDoorStatus() {
            if (countInRange >= COUNT_THRESHOLD) {
                // 連続COUNT_THRESHOLD回以上物体を検出した
                if (!isDoorOpened()) {
                    // 扉が閉じているので開く
                    templeManager.openSesame();
                    setDoorOpened(true);
                }
                countInRange = 0;
            } else if (countOutOfRange >= COUNT_THRESHOLD) {
                // 連続COUNT_THRESHOLD回以上物体を検出しなかった
                if (isDoorOpened()) {
                    // 扉が開いているので閉じる
                    templeManager.shutSesame();
                    setDoorOpened(false);
                }
                countOutOfRange = 0;
            }
        }
        
        private void updateCountInRange() {
            countInRange++;
            countOutOfRange = 0;
            System.out.println("updateCountInRange(countInRange=" + countInRange);
        }
        
        private void updateCountOutOfRange() {
            countOutOfRange++;
            countInRange = 0;
            System.out.println("updateCountOutOfRange(countInRange=" + countOutOfRange);
        }
        
        private boolean isDoorOpened() {
            return doorOpened;
        }
        
        private void setDoorOpened(boolean doorOpened) {
            this.doorOpened = doorOpened;
        }
        
        public void terminate() {
            running = false;
        }
        
        public void destroy() {
            templeManager.destroy();
        }
    }
    
    protected void pauseApp() {
    }
    
    protected void destroyApp(boolean arg0) throws MIDletStateChangeException {
        if (doorController != null) {
            doorController.destroy();
        }
    }
}

 package org.sunspotworld;

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

public class DistanceSensor {
    private static final float MAX_VOLTAGE = 3.0f;
    
    private IScalarInput input;
    private int range;
    
    public DistanceSensor(int pinId) throws IOException {
        input = EDemoBoard.getInstance().getScalarInputs()[pinId];
        range = input.getRange();
        System.out.println("DistanceSensor init: range=" + range);
    }
    
    public float getValue() throws IOException {
        int value = input.getValue();
        float voltage = (float)value / range * MAX_VOLTAGE;
        System.out.println("A/D value -> " + value + ", voltage -> " + voltage);

        // calculates the distance
        float distance = 23.333f / (voltage - 0.236f) - 0.420f;
        System.out.println("Distance=" + distance);
        return distance;
    }
}

 

 package org.sunspotworld;

import com.sun.spot.sensorboard.io.IOutputPin;
import com.sun.spot.util.Utils;

public class TempleManager {
    private final long DURATION = 6000L;
    
    private IOutputPin out1;
    private IOutputPin out2;
    
    public TempleManager(IOutputPin out1, IOutputPin out2) {
        this.out1 = out1;
        this.out2 = out2;
        
        stop(1L);
    }
    
    /**
     * 扉を開く
     */
    public void openSesame() {
        System.out.println("openSesame()" );
        stop(1L);
        out1.setHigh();
        Utils.sleep(DURATION);
        out1.setLow();
    }
    
    /**
     * 扉を閉じる
     */
    public void shutSesame() {
        System.out.println("shutSesame()" );        
        stop(1L);
        out2.setHigh();
        Utils.sleep(DURATION);
        out2.setLow();
    }
    
    /**
     * ストップ
     */
    public void stop(long duration) {
        out1.setLow();
        out2.setLow();
        Utils.sleep(duration);
    }
    
    public void destroy() {
        out1.setLow();
        out2.setLow();
    }
}

 

それでは実行してみます。

。。。

# デューク登場 ;-)


http://blogs.sun.com/machida/date/20080421 2008年 4月 21日 月曜日

[Sun SPOT で"デューク神棚(Duke Temple)" を動かそう(2)] Hello Crystal Duke!!

 前回のエントリでDCモータの制御回路の簡単な動作確認まで行ったので、いよいよ神棚 の作成に取り掛かります。もともと工作セットのパーツで組み立て説明書が付いているのできっと簡単でしょう。

。。。

二時間経過。。

。。。 

く、結構めんどい。。。

。。。

か、完成! ;-)

# 神棚 

前回と同じように回路を接続します。

 # ちょっと拡大

さて、扉を開閉するプログラムはどうしましょう。元々付いていた音センサーを利用してもよいのですが、そういえば以前距離センサーを使ったことがあったので、これは使えそうです。 かしわ手で反応するのではなく、人が10cm~80cmの距離に近づいた時に扉が開いているように制御する、というのは良いかもです。ただ、そろそろ昼時も近づいてきたのでこれは後日の課題として、今日のところは単純に、扉の開閉を繰り返すSun SPOTプログラムを作成して動作確認だけすることにします。

こんな感じです↓

package org.sunspotworld;

import com.sun.spot.peripheral.Spot;
import com.sun.spot.sensorboard.EDemoBoard;
import com.sun.spot.sensorboard.io.IOutputPin;
import com.sun.spot.util.*;
import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;

public class StartApplication extends MIDlet {
    private TempleManager templeManager;    // 神棚の扉の開閉を制御するクラス
    
    protected void startApp() throws MIDletStateChangeException {
        new BootloaderListener().start();
        Spot.getInstance().getSleepManager().disableDeepSleep();
        
        // 大電流出力ピンH0、H1を使用する
        IOutputPin[] pins = EDemoBoard.getInstance().getOutputPins();
        templeManager = new TempleManager(
                pins[EDemoBoard.H0],pins[EDemoBoard.H1]);

        while (true) {
            templeManager.openSesame();    // 扉を開く
            templeManager.stop(1000L);      // ストップ
            templeManager.shutSesame();    // 扉を閉じる
            templeManager.stop(1000L);      // ストップ
        }
    }
    protected void pauseApp() {
    }

    protected void destroyApp(boolean arg0) throws MIDletStateChangeException {
        if (templeManager != null) {
            templeManager.destroy();
        }
    }
}

package org.sunspotworld;

import com.sun.spot.sensorboard.io.IOutputPin;
import com.sun.spot.util.Utils;

/**
 * 神棚の扉の開閉を制御するクラス
 */
public class TempleManager {
    private final long DURATION = 6000L;
    
    private IOutputPin out1;
    private IOutputPin out2;
    
    public TempleManager(IOutputPin out1, IOutputPin out2) {
        this.out1 = out1;
        this.out2 = out2;
        
        stop(1L);
    }
    
    /**
     * 扉を開く
     */
    public void openSesame() {
        stop(1L);
        out1.setHigh();
        Utils.sleep(DURATION);
        out1.setLow();
    }
    
    /**
     * 扉を閉じる
     */
    public void shutSesame() {
        stop(1L);
        out2.setHigh();
        Utils.sleep(DURATION);
        out2.setLow();
    }    

    /**
     * ストップ
     */
    public void stop(long duration) {
        out1.setLow();
        out2.setLow();
        Utils.sleep(duration);
    }    
    
    public void destroy() {
        out1.setLow();
        out2.setLow();
    }
}

Sun SPOT にプログラムをインストールして実行してみましょう。

。。。

# 扉が開き始めました!;-) なにやら透明な物体が。。。

 # クリスタルデューク登場!

 # 拡大。



Valid HTML! Valid CSS!

This is a personal weblog, I do not speak for my employer.