実験5 触覚ロボット


ここで行うこと。

実験5-1 触覚を作りテストする

実験3-7で作りましたが、これをもう一度組み立ててください。
触覚スイッチテストのプログラムで正しく動くことを確認してください。

実験5-2 シングルタスクの触覚ロボット

実験3-8で行いましたが、次の実験を行う前に正しく動作するようにして下さい。

実験5-3 マルチタスクを用いる

シングルタスクの放浪プログラムは短く理解しやすいものでした。FixedMovementJBotが殆どの仕事を担っています。残念ながら、もしこれ以上のタスクを付け加えるとすると大変なことになります。これを行うにはマルチタスクのサポートが必要になります。実験2で示したBasicSensorを拡張してWhiskerSensorを作ることから始めましょう。それからセンサーを用いたマルチタスクの放浪プログラムを作ります。新しいBasicSensorクラスを単純に定義することによって異なったタイプのセンサーを使うことができます。次のソースはWhiskerSensorクラスです。

Basic whisker sensor class
package JBot;

import stamp.util.os.* ;
import stamp.core.* ;

/**
 * Basic whisker sensor class
 * 
 * 触覚センサーを用いて障害物検知をする
 * 障害物の45, 90 135度゜の方向を返す。
 */

public class WhiskerSensor extends BaseSensor {
  /**
   * 障害物が検出されたかどうかを示す
   * 通常、イベントを用いるではなくポーリングを用いる
   *
   * 戻り:物体検出
   */
  public boolean obstacleDetected () {
    return CPU.readPin ( CPU.pin6 ) | CPU.readPin ( CPU.pin4 ) ;
  }

  /**
   * 障害物位置を示す
   * For simple detection systems the detection of an object
   * on the right and left will return front.
   *
   * 戻り:障害物の相対方向 (left, right, etc.)
   */
  public int obstacleDirection () {
    switch (   ( CPU.readPin ( CPU.pin6 ) ? 2 : 0 )
             + ( CPU.readPin ( CPU.pin4 ) ? 1 : 0 )) {
    case 0:   // both low, 後進して右回り
      return front ;

    case 1:   // P4 low, 後進して左回り
      return right ;

    case 2:   // P6 low, 後進して右回り
      return left ;

    default:
    case 3:  // 直進
      return none ;
    }
  }

  /**
   * 指定された方向の障害物までの距離を得る
   * noneという値は障害物がないことを意味する
   *
   * 入力:調べる方向
   *
   * 戻り:指定された方向にある障害物までの距離
   */
  public int obstacleDistance ( int direction ) {
    return 0 ; // 方向に寄らずいつもゼロの距離を返す
  }

  /**
   * 最小報告距離を設定する
   * この距離より遠くにある物体に対しては報告しない
   * 最小値はゼロである
   *
   * 入力:距離までの最小距離をcmで示す
   */
  public void setMinimumEventDistance () {
    /* デフォールトの場合では最小距離は無視する
     * 例えば、接触センサーの場合は障害物にぶつかってから
     * 方向が分かるからである
     */
  }


  /**
   * 通知イベントを設定する
   *
   * 入力:Event event:変化が起こったときに報告するイベントオブジェクト   */
  public void setEvent ( Event event ) {
    this.event = event ;
  }

  // このクラスあるいはサブクラスで用いるためにProtectedにする

  /**
   * 障害物のステータスが変った時にイベントを発生する
   * サブクラスのメソッドにより呼ばれる
   */
  protected void notifyEvent () {
    if ( event != null )
      event.notify () ;
  }
}


障害物検出はこのクラスに分離され、動作は次に示す一般的なマルチタスクの放浪プログラムによって扱います。

AvoidObstacleTaskクラスは次のようになります。

単純な障害物回避タスク
package JBot;

import stamp.util.os.* ;

/**
 * 単純な障害物回避タスク
 * 
 * センサーオブジェクトを用いて障害物から距離をおくことを試みる
 * J-Botは決まった距離ずつ移動する
 */

public class AvoidObstacleTask extends Task {
  JBotInterface jbot ;
  BaseSensor sensor ;

  public AvoidObstacleTask ( BaseSensor sensor, JBotInterface jbot ) {
    this.sensor = sensor ;
    this.jbot = jbot ;
  }

  protected void execute () {
    final int turnAround = 1 ;

    switch ( state ) {
    case initialState:
      if ( sensor.obstacleDetected ()) {
        int direction = sensor.obstacleDirection () ;

        if ( sensor.obstacleDistance ( direction ) < 5 ) {
          // 非常に接近、5cm後退し回る

          jbot.move ( -5 ) ;
          jbot.wait ( turnAround ) ;
        } else {
          // 物体から離れて旋回する場所がある

          if ( direction < 75 ) {
            // 何か左にある
            jbot.pivot ( -2 ) ;
          } else {
            // 何か前か右にある
            jbot.pivot ( 2 ) ;
          }
          jbot.wait ( turnAround ) ;
        }
      } else {
        // 何もない。1cm先に進む

        jbot.move ( 1 ) ;
        jbot.wait ( initialState ) ;
      }
      break;

    case turnAround:
      // J-Botは後進し、180゜旋回する

      jbot.pivot ( 4 ) ;
      jbot.wait ( initialState ) ;
      break;

    default:                    // 異常なステータスを捕捉する
      stop () ;
      break;
    }
  }
}




AvoidObstacleTaskWhiskerTest1プログラムはwhisker2の方法と同じに動作します。

AvoidObstacleTaskクラスをテストする
import stamp.core.*;
import stamp.util.os.* ;
import JBot.* ;

/**
 * AvoidObstacleTaskクラスをテストする
 * 
 * J-Botを障害物を避けるように動かす
 */

public class AvoidObstacleTaskWhiskerTest1 {
  public static void main () {
    new AvoidObstacleTask
          ( new WhiskerSensor ()
          , new RampingJBot ( new MultitaskingJBot ())) ;

    Task.TaskManager () ;
    System.out.println ( "All done" ) ;
  }
}


AvoidObstacleTaskWhiskerTest1は全ての仕事はAvoidObstacleTaskオブジェクトとWhiskerSensorオブジェクトで行われているのでシンプルです。動作や障害物回避に多くの変更も無く新しいタスクを付け加えることが可能です。RampingJBotに対するコントロールイベントはシングルタスクのFixedMovementJBotに対するMultitaskingJBotであることに注意してください。

課題5-3

 上記のAvoidObstacleTaskWhiskerTest1は正しく動きません。複数のソースを訂正する必要があります。これらを直して正しく動作するようにしなさい。

実験5-4 触覚記憶 - 触覚とEEPROM

障害物情報をJavelinの中のEEPROMに入れて実行させて見ましょう。EEPROMはJavaのプログラムを入れるメモリーと同じですが、全てのメモリーをプログラムが使用することはほとんどありません。プログラムは実行のためEEPROMからRAMにコピーされRAMはプログラムが走った時にオブジェクトを作るためEEPROMに未使用のスペースが必要です。

障害物検出はAvoidObstacleTaskで扱われますが、これを拡張して次のAvoidObstacleRecordingTaskにします。

EEPROM
Electrically Erasable Programmable Read-Only Memory
 電気的に消去(書き換え)できるROM。電源を切ってもデータは消えない。データの消去には5Vより高い電圧が必要だが、最近のEEPROMは内部で電源電圧の5Vを昇圧しているため、基板に実装したままデータを消去して書き換えるのが容易になりました。そのためシステムの動作中にデータを書き換えることができます。ただEEPROMには、およそ数十万~百万回までしか、消去/書き換えができないという欠点があります。さらにEEPROMはそのデータを1bitだけ書き換えるときにも、すべてのbitをいったん消去して書き換えなければならないため、RAMのようにランダムな読み書きは困難です。この欠点を改良したのがフラッシュメモリです。




EEPROM記録を用いての障害物回避
package JBot;

import stamp.util.os.* ;
import stamp.core.* ;

/**
 * EEPROM記録を用いての障害物回避
 * 
 * センサーオブジェクトを用いて障害物から距離をおくことを試みる
 * J-Botは決まった距離ずつ移動する
 * 障害物情報はEEPROMに保存される
 */

public class AvoidObstacleRecordingTask extends AvoidObstacleTask {
  int memoryIndex ;
  int memorySize ;

  public AvoidObstacleRecordingTask ( BaseSensor sensor, JBotInterface jbot ) {
    super ( sensor, jbot ) ;

    memoryIndex = 1 ;
    memorySize = ( EEPROM.size () - 1 ) / 2 ;

    // 最初の幾つかの障害物のみ記録する、255以下でなければいけない
    if ( memorySize > 10 ) {
      memorySize = 10 ;
    }

    // 保存される記録の数を保存する
    EEPROM.write ( 0, (byte) memorySize ) ;
  }


  protected void execute () {
    final int turnAround = 1 ;

    switch ( state ) {
    case initialState:
      if ( sensor.obstacleDetected ()) {
        int direction = sensor.obstacleDirection () ;

        // EEPROMが一杯になったら出る
        if ( memorySize == 0 ) {
          stop () ;
          break ;
        }

        // EEPROMに障害物の詳細を記録する
        EEPROM.write ( memoryIndex, (byte)direction ) ;
        EEPROM.write ( memoryIndex + 1, (byte)sensor.obstacleDistance ( direction )) ;
        memoryIndex += 2 ;
        -- memorySize ;

        if ( sensor.obstacleDistance ( direction ) < 5 ) {
          // 非常に接近、5cm後退し回る

          jbot.move ( -5 ) ;
          jbot.wait ( turnAround ) ;
        } else {
          // 物体から離れて旋回する場所がある

          if ( direction < 75 ) {
            // 何か左にある
            jbot.pivot ( -2 ) ;
          } else {
            // 何か前か右にある
            jbot.pivot ( 2 ) ;
          }
          jbot.wait ( turnAround ) ;
        }
      } else {
        // 何もない。1cm先に進む

        jbot.move ( 1 ) ;
        jbot.wait ( initialState ) ;
      }
      break;

    case turnAround:
      // J-Botは後進し、180゜旋回する

      jbot.pivot ( 4 ) ;
      jbot.wait ( initialState ) ;
      break;

    default:                    // 異常なステータスを捕捉する
      stop () ;
      break;
    }
  }
}


このクラスに3つのものが付け加わりました。1つ目は幾つかの新しいオブジェクト変数、2番目はEEPROMクラスメソッドを用いてこれらの変数を初期化するコンストラクタ。memorySize変数は記録されるエントリー数を入れるのに用いられます。byteの値の範囲は0から255なのでEEPROMのデータの最大数は255になります。2バイトの16bit整数に入れるのは可能ですが、練習のためにこのままでやります。メモリーのインデックスはエントリー数が0番目に入りますから、1から始まります。障害物情報はこのバイトの後から格納されます。

結局、データは実行メソッドのinitialStateで格納されます。これは障害物がセンサーを用いて検出された場所です。この点でセンサーはWhiskerSensorです。J-Botは幾つかの物体のエントリーを設定した後で停止します。J-Botが止まる前に長く走らないように10という値をコンストラクタに入れて始めます。

一度J-Botが停止したら、拾い上げてPCに繋げます。EEPROMの中に格納されているデータは電源が切れていても維持されるので、もし必要ならばJ-Botの電源を切ります。次の段階はEEPROMの内容を読んでその情報をメッセージウィンドウに表示するプログラムをダウンロードすることです。EEPROMに保存されているデータはメモリーの逆の端にロードされるので新しいプログラムによって上書きはされません。次のプログラムはEEPROMからの情報を表示するDumpObstacleです。

EEPROMに格納したデータをダンプする
import stamp.core.*;

/**
 * AvoidObstacleRecordingTaskによってEEPROMに格納したデータをダンプする
 * 
 * EEPROMからデータを読み、それをメッセージウィンドウに表示する
 */

public class DumpObstacle {
  public static void main() {
    int memoryIndex = 1 ;

    for ( int memorySize = (int) EEPROM.read ( 0 )
        ; memorySize > 0
        ; -- memorySize ) {

      System.out.print   ( memoryIndex ) ;
      System.out.print   ( "  Direction: " ) ;
      System.out.print   ((int) EEPROM.read ( memoryIndex )) ;
      System.out.print   ( "  Distance: " ) ;
      System.out.println ((int) EEPROM.read ( memoryIndex + 1 )) ;
      memoryIndex += 2 ;
    }
  }
}


オフセット1で1エントリーあたり2バイトのデータであることを仮定します。オフセットと2つの値が一行に表示されます。それぞれの値の前にDirectionとDistanceがついていますが、数値が何を示すか覚えておく必要はありません。情報は早くスクロールしますが、メッセージウィンドウは情報を保持しています。

課題5-4

  1. 触覚はどのようにしてぶつかったことをCPUに知らせていますか?

  2. もしI/Oピンが出力に設定されていた場合、抵抗はどんな影響をもたらしますか?

  3. 触覚が押されたときに監視しているI/Oピンにはどのくらいの電圧が生じるか? 入力抵抗に生じるバイナリー値はどのくらいか? もし入力ピンを監視するためにI/OピンP8が使われた場合、触覚が押されたときにin8の値はどのようになり、触覚が押されないときにはどのような値になるか?

  4. LED回路を設定するためにはI/Oピンをどの方向にするか?

  5. CPU.pin6はどちらに接続されているか? CPU.pin4はどうか?

  6. whisker1.javaからCPU.delay呼び出しを取り除いた場合、何が起こるか?

  7. switch文の代わりにif文を用いたwhisker2.javaを作りなさい。

  8. AvoidObstacleRecordingTaskはDirectionとDistanceをEEPROMに格納しましたが、これに障害物に対しての応答行動を加えなさい。また、これを読み出すDumpObstacleも同様に修正しなさい。

  9. 両方の触覚が押されている間J-Botがゆっくりと後ろに動くようにwhisker2.javaを修正しなさい。そうでなければそこに留まります。左の触覚が押されたときには反時計回りに、右の触覚が押されたときには時計回りに旋回するようにさらにプログラムを修正しなさい。それが終わったら、触覚が押されたときに音が鳴るようにしなさい。

  10. whisker2.javaを改造して円を描くようにしなさい。そして、内側の触覚を触った時には回転半径を小さくし、内側の直径が5cmになるようにしなさい。同様に外側の触覚を触った時には回転直径が5cm大きくなるようにしなさい。

    J-Botの円の直径をコントロールする時に、電源がリセットされた後でもこの直径を記憶しているようにプログラムしなさい。データをEEPROMに保存するwriteコマンドが使え、EEPROMのデータは不揮発性と呼ばれます。JavelinのRAMは各々のリセットで消され(揮発性)ますが、EEPROMはJavelinが電源が入った次の時に使えるデータを保存できます。(不揮発性)

実験5終わり