実験3 J-Botを思った場所に行かせる


ここで行うこと。

実験3-1 動作指令を変換する


前の実験では前後方向のみでしたが、この実験では全ての方向に移動できるようにします。ある量の回転するにはどのくらいのパルスを送るかを決めることによってさらに緻密な操縦ができるようになります。例えば、J-Botが正方形、三角形などを描くことができるようになります。

このレベルでのプログラムされた操縦性はとても良くなります。しかし、プログラムが長くなり、複雑な問題が生じてきます。Javaではプログラムメモリーの中に長い方向のリストを記録したりアクセスすることができます。これを単純にプログラムで行うと、方向を変えた時に不意に止まることがあります。このため、方向変換する時には減速し、方向を変えてから加速するコマンドが必要になります。この「ramping」処理(rampは傾斜の意味)は不意の停止を解決しサーボモータの寿命を長くします。
例えば車輪の円周は次のようになります

円周 = π × 車輪の直径
   = 3.141592 × 6.67cm ≒ 21cm

これで、一回転すると約21cm進むことが
分かります。
課題3-1-1

自分の使っているJ-Botの車輪の直径を計り、一回転すると実際に移動する距離を求めなさい。

ここでは説明のため上記の値を用います。
J-Botをいかに遠くまで移動させるかを決める一つの方法は、いかに速く移動できるかを決めることです。例えば、サーボモータが37.5rpm(revolution per minute)すなわち0.625回転/s で回っていたとすると、スピードは次のようになります。

21cm/回転×0.625回転/s = 13.125cm/s

50cm J-Botが移動するためにかかる時間は

ttravel=50cm ÷ 13.125cm/s ≒ 約3.81秒

車輪の回転速度を決定するのは難しいので、その代わりに移動距離を測定します。現実に起こる走行表面によるスリップも計算に入るのでこちらの方がいいかも知れません。次のプログラムでこれを行います。

実験2で作ったグラフより***のところに適切な数字を入れます。つまり、左右のサーボモータの前進のスピードが等しいと同様に後進のスピードも同じにしないと、旋回などが正常に動作しません。

車輪サーボモータの距離テストプログラム
import stamp.core.*;
import JBot.* ;

/**
 * 車輪サーボモータの距離テストプログラム
 * 
 * 決められた時間J-Botを走らせる。
 * 各々の時間は以前の時間より長い。
 * 測定のためテスト走行の前に遅延を挿入している。
 * 距離測定は各々の動作の間で行う。
 */

public class BasicWheelServoDistanceTest1 {
  public static void main () {
    BasicWheelServo leftWheel =
      new BasicWheelServo (
              CPU.pin13   // pin
            ,  ***          // forward
            ,  ***         // center
            ,  ***         // backward
            , 2000        // low
            ) ;
    BasicWheelServo rightWheel =
      new BasicWheelServo (
              CPU.pin12   // pin
            ,  ***         // forward
            ,  ***         // center
            ,  ***         // backward
            , 2000        // low
            ) ;

    for ( int i = 1 ; i <= 10 ; ++i ) {
      CPU.delay(30000);     // 測定のための初期遅延
      CPU.delay(30000);     // 測定のための初期遅延

      leftWheel.move ( 100 ) ;
      rightWheel.move ( 100 ) ;

      for ( int j = 0 ; j < i ; ++ j ) {
        CPU.delay(5000);   // 決められた時間サーボを動かす
      }

      leftWheel.stop () ;   // 車輪を止める
      rightWheel.stop () ;
    }
  }
}


BasicWheelServoDistanceTest1はJ-Botを10回前進させます。測定は次のように行います。
  1. J-Botは電池で動くようにし、サーボモータへの電源は切っておき、BasicWheelServoDistanceTest1をJ-Botにロードする。
  2. J-Botの位置にマーカーを置く。
  3. サーボモータの電源を入れ、リセットボタンを押す。
  4. スタートする前に6秒間待機してから動き出します。
  5. 少し行くと止まりますから、そこにすかさずマーカーを置きます。
  6. 繰り返し数が終わるまで4と5を繰り返します。
  7. 始めのマーカーとマーカーの間の距離を測ります。エクセルで次のような表を作り、1cmあたりのカウントを求めます。
回数
時間(ms)
距離(cm)1cmあたりのカウント
(100μs/cm)
15007.9633
2100015.9629
3150022.9655
4200030.7651
5250038.7646
6300046.5645
7350054.5642
8400062.5640
9450070.5638
10500079.0633

注:プログラム中の時間の単位は100μ秒ですので、10倍する必要があります。

課題3-1-2

これをエクセルにてグラフ化しなさい。以下のような感じになります。

J-Botが完全に直進しなくてもそのまま続けてください、大体真っ直ぐなら可です。サーボモータの設定でこれは修正できますが、大きな変更をする前にテストを終えてください。J-Botの動作はグラフで示したように比較的一貫性がある必要がありますが、実際にはばらつきがあります。これは開始と停止を含んだ様々な要因が考えられます。短い距離の動作は長い動作に比べてより影響を受けやすいでしょう。サーボモータのわずかなばらつき、サーボモータの取り付け方、車輪と床との摩擦、など、J-Botは動きのばらつきに寄与している全ての上で走っています。

プログラムは結果が安定するように、複数回データを取る必要があります。その結果は、固定距離を移動するwheel servoクラスを開発する次のステップのために平均化します。

上の表から平均値を求めると1cm動くのに641の時間がかかることが分かります。次のプログラムは決められた距離(5cmあるいは2cm)だけ進みます。
5cmにするか2cmにするかは、整数型の値の上限によって決めてください。

測定ビデオ

車輪サーボモータの距離テストプログラム2
import stamp.core.*;
import JBot.* ;

/**
 * 車輪サーボモータの距離テストプログラム
 * 
 * 決められた距離だけ進む。
 * 距離測定は各々の動きの間で行える。
 * 5cmあるいは2cm毎に動く。
 */

public class BasicWheelServoDistanceTest2 {
  public static void main () {
    BasicWheelServo leftWheel =
      new BasicWheelServo (
              CPU.pin13   // pin
            ,  ***         // forward
            ,  ***         // center
            ,  ***         // backward
            , 2000        // low
            ) ;
    BasicWheelServo rightWheel =
      new BasicWheelServo (
              CPU.pin12   // pin
            ,  ***         // forward
            ,  ***         // center
            ,  ***         // backward
            , 2000        // low
            ) ;

    for ( int i = 1 ; i <= 10 ; ++i ) {
      CPU.delay(30000);     // 測定のための初期遅延
      CPU.delay(30000);     // 測定のための初期遅延
      leftWheel.move ( 100 ) ;
      rightWheel.move ( 100 ) ;

      for ( int j = 0 ; j < i ; ++ j ) {
        CPU.delay(***);   // 決められた時間サーボを動かす
      }

      leftWheel.stop () ;   // 車輪を止める
      rightWheel.stop () ;
    }
  }
}


課題3-1-3

  1. BasicWheelServoDistanceTest1は前進の補正であるが、同様にして後進の補正を行いなさい。

  2. 上のプログラムは5cmあるいは2cmを単位にしていますが、同じ距離を1cm毎に動かすように変えて、どのようになるか調べなさい。後進も同様に調べなさい。

  3. 上のプログラムは100%のスピードで動いています。異なったスピード、25%,50%,75%でテストしなさい。後進も同様に調べなさい。

実験3-2 操縦 - 回転する

J-Botは回転あるいは旋回することによって方向を変えます。回転は両輪を同じ方向に回転させますが、異なったスピードでサーボモータを回すことによって行います。回転は遅く動いている車輪の方向に行き、回転の直径は両輪のスピード差に基づきます。

旋回は両輪が逆方向に動く時に起こります。J-Botは両輪が同じスピードの時その場で旋回します。もし両輪のスピードが異なる時には回転と旋回の混合した動きになります。ここの目的としては、その場での旋回とします。

その場で旋回

旋回テストプログラム
import stamp.core.*;
import JBot.* ;

/**
 * 旋回テストプログラム
 */

public class BasicWheelServoPivotTest1 {
  public static void main () {
    BasicWheelServo leftWheel =
      new BasicWheelServo (
              CPU.pin13   // pin
            ,  ***         // forward
            ,  ***         // center
            ,  ***         // backward
            , 2000        // low
            ) ;
    BasicWheelServo rightWheel =
      new BasicWheelServo (
              CPU.pin12   // pin
            ,  ***         // forward
            ,  ***         // center
            ,  ***         // backward
            , 2000        // low
            ) ;

    for ( int i = 1 ; i <= 4 ; ++i ) {
      CPU.delay(20000);     // 測定のための初期遅延

      leftWheel.move ( 100 ) ;
      rightWheel.move ( -100 ) ;

//      for ( int j = 0 ; j < i ; ++ j ) {
          CPU.delay(1000);   // 決められた時間サーボを動かす
//      }

      leftWheel.stop () ;   // 車輪を止める
      rightWheel.stop () ;
    }
  }
}


このプログラムはBasicWheelServoDistanceTestと非常に似ています。大きな3つの違いは、繰り返し数と、車輪の方向、旋回に対する遅延時間です。繰り返し数は旋回しすぎを抑えています。

左車輪の回転方向は前進で、右車輪の回転方向はそれと逆方向になっています。こうすることによってJ-Botを右に旋回させます。遅延時間(delay)は長い距離ではないので大きな数にはなっていません。長くするとぐるぐると回ります。

 for ( int j = 0 ;の部分はコメントになっていますので、このまま実行すると45°ずつの旋回になります。まずこれでチェックしてからコメントを外して実験します。

BasicWheelServoPivotTest1は回転の角度を増やして4ステップで行っています。角度は45゜90゜のような値になります。1000のdelayでは、4つの旋回ステップ(4000)が約180゜の回転になります。もしBasicWheelServoPivotTest2のように倍の値にすると90゜の回転になります。

BasicWheelServoPivotTest2はこの情報を用いています。違いは回転delayが2000に増えているだけです。これは近似値ですがこれに近くなります。

旋回テストプログラム
import stamp.core.*;
import JBot.* ;

/**
 * 旋回テストプログラム
 */

public class BasicWheelServoPivotTest2 {
  public static void main () {
    BasicWheelServo leftWheel =
      new BasicWheelServo (
              CPU.pin13   // pin
            ,  ***         // forward
            ,  ***         // center
            ,  ***         // backward
            , 2000        // low
            ) ;
    BasicWheelServo rightWheel =
      new BasicWheelServo (
              CPU.pin12   // pin
            ,  ***         // forward
            ,  ***         // center
            ,  ***         // backward
            , 2000        // low
            ) ;

    for ( int i = 1 ; i <= 4 ; ++i ) {
      CPU.delay(20000);     // 測定のための初期遅延

      leftWheel.move ( 100 ) ;
      rightWheel.move ( -100 ) ;

      for ( int j = 0 ; j < i ; ++ j ) {
        CPU.delay(2000);   // 決められた時間サーボを動かす
      }

      leftWheel.stop () ;   // stop wheels
      rightWheel.stop () ;
    }
  }
}


距離動作と旋回操作を統合したクラスを作ることができます。それは単純にforward,pivotRight,pivotLeftのように用いることができますが、次に作る回転を含む有用な動きを全てテストしてからにします。

課題3-2-1

  1. 車輪のスピードを変えてみなさい。どのようになるか調べなさい。

  2. 左回りの旋回をするようにしなさい。
回転させる

その場での旋回は手ごろですが、方向転換の方法としては必ずしも好ましいものではありません。特に、旋回は両輪を異なった方向に回転することが必要です。短い時間停止しなければJ-Botは横滑りします。つまり急には変れないことを意味します。これに対して、回転はスピードの異なる同方向の動きを維持します。

左回転は右の車輪より左の車輪のスピードを遅くすることで行います。次の BasicWheelServoTurnTest1はもうお馴染みの形をしています。

回転テストプログラム
import stamp.core.*;
import JBot.* ;

/**
 * 回転テストプログラム
 * 
 * 決められた時間J-Botを回転させる
 */

public class BasicWheelServoTurnTest1 {
  public static void main () {
    BasicWheelServo leftWheel =
      new BasicWheelServo (
              CPU.pin13   // pin
            ,  ***         // forward
            ,  ***         // center
            ,  ***         // backward
            , 2000        // low
            ) ;
    BasicWheelServo rightWheel =
      new BasicWheelServo (
              CPU.pin12   // pin
            ,  ***         // forward
            ,  ***         // center
            ,  ***         // backward
            , 2000        // low
            ) ;

    for ( int i = 1 ; i <= 4 ; ++i ) {
      CPU.delay(20000);     // 測定のための初期遅延

      leftWheel.move ( 20 ) ;
      rightWheel.move ( 100 ) ;

      for ( int j = 0 ; j < i ; ++ j ) {
        CPU.delay(10000);   // 決められた時間サーボを動かす
      }

      leftWheel.stop () ;   // 車輪を止める
      rightWheel.stop () ;
    }
  }
}


走っている時間(2番めのCPU.delay呼び出し)は円弧を描くため長くしています。leftWheel.forwardメソッド呼び出しは20%にスピードダウンしています。この結果、内側の直径が2cm程度の比較的急な回転をします。

このようにならなかった場合は、数字を適当に変えてください。

最初の旋回プログラムのように、BasicWheelServoTurnTest1プログラムはJ-Botを動かしますが、動作量は思ったようにはいきません。四角に動かしたいとしたら動く位置を知りたくなります。

課題3-2-2

  1. 回転のステップが45゜になるように時間を決定しなさい。

  2. 逆方向に回転するようにしなさい。(前方に動く)

  3. 後ろ方向に回転するようにしなさい。(後ろの左右45°)

実験3-3 操縦 - 基本的なJ-Botクラス

この点でのJ-Botのコントロールは系統立てられています。個々の車輪のサーボモータのコントロールはBasicWheelServoクラスでカプセル化されています。このクラスを拡張して回転とか旋回のように動きを作るのが可能ですから、もしJ-Botの動きをする他のクラスを定義するのならこれを使ったほうが簡単です。

新しいクラスの定義に入る前にどんなメソッドの組み合わせが使えるかを決める必要があります。決められた距離の前進と後進、左右の旋回、左右の回転、停止が必要になります。次のクラスがこれをします。

カプセル化 【encapsulation】
 オブジェクト指向プログラミングが持つ特徴の一つ。データとそれを操作する手続きを一体化して「オブジェクト」として定義し、オブジェクト内の細かい仕様や構造を外部から隠蔽すること。外部からは公開された手続きを利用することでしかデータを操作できないようにすることで、個々のオブジェクトの独立性が高まる。カプセル化を進めることによりオブジェクト内部の仕様変更が外部に影響しなくなり、ソフトウェアの保守性や開発効率が高まり、プログラムの部分的な再利用が容易になる。


※注意:ソースをよく読めば分かることですが、下のJBotInterfaceはすぐにコンパイルできません。この実験3-3の後ろのほうにあるEventクラスなどが正しく設定されていないとコンパイルできません。また、このEventTaskクラスが前もって入っている場合がありますが、この実験で使うのと異なるため、あると不具合が生じます。上書き保存してください。また、保存場所を間違えた場合、間違えたファイルは必ず消してください。不可解なエラーに悩まされる結果になります。

J-Botの車輪コントロールインターフェース
package JBot;

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

/**
 * J-Botの車輪コントロールインターフェース
 * 
 * J-Botの車輪コントロールクラスが備えていなければならない
 * メソッドを定義する
 */

public abstract class JBotInterface {
  protected Event startEvent ;
  protected Event nextEvent = Event.nullEvent ;
  protected Event oneTimeEvent = Event.nullEvent ;

  static final public int continuousForward  = 32760 ;
  static final public int continuousBackward = -32760 ;
  static final public int continuousLeft     = continuousForward ;
  static final public int continuousRight    = continuousBackward ;

  /**
   * 動作を開始した時に通知されるようにイベントを設定する
   * 
   * 入力:Event event: イベント。nullに設定できる。
   */
  public JBotInterface ( Event event ) {
    startEvent = Event.checkEvent(event);
  }

  /**
   * 動作を終了するために現在のタスクを待機(wait)に設定する。
   *
   * 入力:int state: 次のタスク状態
   */
  public void wait ( int state ) {
    Task waitingTask = Task.getCurrentTask () ;
    waitingTask.nextState ( state ) ;

    if ( waitingTask.taskStatus()==Task.taskRunning) {
      waitingTask.suspend () ;
      oneTimeEvent = waitingTask ;
    }
  }

  /**
   * 動作を開始した時に通知されるようにイベントを設定する
   * 多分、null
   *
   * 入力:Event event: Event
   *
   * 戻り:直前のイベント
   */
  public Event setStartEvent ( Event event ) {
    Event resultEvent = startEvent ;
    startEvent = Event.checkEvent(event) ;
    return resultEvent ;
  }

  /**
   * 動作を開始した時に通知されるようにイベントを設定する
   * 多分、null
   *
   * 入力:Event event: Event
   *
   * 戻り:直前のイベント
   */
  public Event setNextEvent ( Event event ) {
    Event resultEvent = nextEvent ;
    nextEvent = Event.checkEvent(event) ;

    // Prime the pump if necessary
    if ( movementDone ()) {
      causeNextEvent () ;
    }

    return resultEvent ;
  }

  /**
   * Cause one time and next event.
   * 通常マッチング・マルチタスク・オブジェクトによって呼ばれる
   */
  protected void causeNextEvent () {
    // 次の動作が開始できるというイベントを通知する
    nextEvent.notify ( this ) ;

    // 次の動作が開始できるというイベントを通知する
    oneTimeEvent.notify ( this ) ;
    oneTimeEvent = Event.nullEvent ;
  }

  /**
   * このメソッドは動作メソッドの始めに呼ばれるべきである。
   */
  protected void startMovement () {
    startEvent.notify (this) ;
  }

  /**
   * 動作が終わったかどうかチェックする。
   * trueを返すまで呼ばれるべきです。
   *
   * 戻り:もし動作の待機が終わったらtrueを返す
   */
  public abstract boolean movementDone () ;

  /**
   * 動作を止める。
   */
  public void stop () {
    move ( 0 ) ;
  }

  /**
   * 前進/後退(マイナスの値)するためにスピードを設定する。
   *
   * 入力:int cm:何センチ移動するか
   */
  public abstract void move ( int cm ) ;

  /**
   * 左旋回(プラス)右旋回(マイナス)するためにスピードを設定する。
   *
   * 入力:int steps:回転するステップの数
   */
  public abstract void pivot ( int steps ) ;

  /**
   * 左回転(プラス)右回転(マイナス)
   *
   * 入力:int staps:回転するためのステップ数
   */
  public abstract void turn ( int steps ) ;
}


JBotInterfaceクラスで最初に注目することはアブストラクトクラスです。これはオブジェクトを作ることはできませんが、アブストラクトクラスでないクラスのスーパークラスとして使うことができることを意味します。2番目に気がつくことはEventクラスの使用です。これはstamp.util.osパッケージの一部で、ソースの最初にある

import stamp.util.os.*;

がこれを使うことを宣言しています。Eventクラスは比較的シンプルです。Eventオブジェクトと通信するのに用いられる「notify」(通知)と呼ばれるメソッドを持っています。この場合、3つのイベントがあります。: startEvent, nextEvent, oneTimeEvent 最初のは動作が始まった時に通知します。他の2つは動きが終了したときに通知します。これは、次の実験で行うマルチタスク環境を作るためのイベントドリブン(駆動)システムにします。これらのイベントはこの後の実験で詳しく行います。

Event.nullEventは、通知された時に何もしないEventクラスの唯一のインスタンスです。これは無意味に思えますが、nullの値をチェックせずに用いるnextEventのように参照できるようになります。他のイベントがこの変数によって通常参照されるから、nullよりもnullEventを用いるほうがより効果的です。notifyメソッドに応答することはいつも同じですから、1つのnullEventオブジェクトが必要になります。それは何もしません。

JBotInterfaceクラスの定義の残りを説明する前に、そのクラスの階層構造と後の実験で用いられるイベントを眺めてみましょう。始めにJBotInterfaceの階層を見てみましょう。

  JBotInterface
   BasicJBot
    RampingJBot
     WheelEncoderJBot


次に定義しますが、BasicJBotクラスはアブストラクトのJBotInterfaceメソッドを実装して、実際のオブジェクトを作れるようにします。この方法は、J-Botに使われるメソッドを集める簡単な方法です。

BasicJBotは前後の動作、旋回、回転をサポートします。これはサーボモータを急に動かしたり止めたりするので、追加のセンサーなどのモジュールを載せているような電池駆動での使用にはむきません。

RampingJBotBasicJBotの上に作られます。急激なスピードの変化をなくし、スピードを滑らかに増減します。これは動作の開始停止時起こる電源のサージを最小化し電池駆動できるようにします。

WheelEncoderJBotRampingJBotへ閉じたフィードバックループを付け加えます。各々の車輪の内側で反射光の変化を見つけるためのIR検出器(赤外線検出器)を利用して車輪エンコーダとして用います。これは同じ時間内で各々の車輪の変化の数を確かめることによってJ-Botを直進させることができます。

この実験ではBasicJBotRampingJBotについて行います。

J-Botをコントロールするための多くの仕事はmovementDoneメソッドで行われます。オブジェクトがサーボモータの行程を保つように繰り返し呼ばれなければなりません。サーボモータは実際に連続的に動きますが、もしJ-Botが一方向に連続的に走るのではなければ、止めるかスピードを変える必要がでてきます。

movementDoneメソッドは色々の方法でポーリングされることができます。一番簡単な方法は、JBotオブジェクトを作るプログラム部分を持つことですが、むしろこれは長くなります。その代わりに2つの別の手があります。1つはシングルタスクを仮定することで、コントロールが呼んだプログラムに戻る前に各々の動作が完結している固定的な動作モードの運用です。2番目はJ-Botが動いている間に他の事を呼んだプログラムができるようになるマルチタスク環境でのタスクを用いることです。これはマルチタスクで必要になるセンサーのサポートも含めることができます。マルチタスクについては次の実験で行いますが、センサーについてはさらにその後になります。

コントロールオブジェクトはJBotInterfaceクラス階層の中でコンストラクタにパラメータ(引数)として要求されます。このコントロールオブジェクトはそのイベントクラスに基づいており、オブジェクトが作られた時にstartEventに割り当てられます。これはオブジェクトを変更することができますが、一般的にはJBotオブジェクトが作られた時に一度だけ設定されます。そのコントロールオブジェクトの階層構造は下のようになります。

  Event
   FixedMovementJBot
    Task
     MultitaskingJBot


一般的に、固定した動作のコントロールオブジェクトのBasicJBotの作成は次のようになります。

JbotInterface jbot = new BasicJBot ( new FixedMovementJBot ());
jbotJbotInterfaceの型であることに注意してください。これはJbotInterfaceBasicJBotのスーパークラスだからこのようにできます。BasicJBotはスーパークラスとしてJbotInterfaceを持っているRampingJBotのようなクラスに置き換えることができます。movementDoneメソッドがjbotによって参照されるオブジェクトを作るプログラムによって明示的に呼ばれるならば、コンストラクタへのパラメータはEvent.nullEventにすることができます。

FixedMovementJBotはこの実験で用います。MultitaskingJBotクラスは次の実験で定義します。

JbotInterfaceアブストラクトクラスに対する動作メソッドは移動、旋回、回転を持っています。各々1つの符号付整数のパラメータを持っています。正数値は前進、旋回と回転では左を意味します。負数値はその逆になります。ゼロの値はJ-Botを停止させます。これはstopメソッドと同じです。

連続動作を開始させることも可能です。J-Botは新しい動作が指令されるまで指定された動作を行います。このモードは方向転換が必要な時かどうかをセンサーで決める時に用いられます。

移動、旋回、回転のような動きのメソッドはサブクラスで定義されなければならなく、3つのイベント、startEvent, nextEvent, oneTimeEventを考慮に入れる必要があります。startMovementメソッドはstartEventを通知(notify)します。全ての動作設定が完了した後で呼ばれなければいけません。普通、イベントは繰り返しmovementDoneを呼びます。startEventはコンストラクタの中で設定されますが、setStartEventを用いて変更することができます。このメソッドは直前のイベント参照を返します。nullの引数はイベントにEvent.nullEventを設定します。

一度動作が終了したら、causeNextEventメソッドを呼ばなければいけません。このメソッドはnextEventoneTimeEventを通知します。前者はsetNextEventで設定されます。このイベントはいつも用いられることを意図しています。oneTimeEventwaitメソッドを用いて設定され、イベントが通知された後にリセットされます。そのwaitメソッドはシステムがマルチタスク環境を用いていることを仮定しています。waitメソッドはタスクから呼ばれなければなりません。イベント待ちについては次の実験のマルチタスクでさらに詳細に述べます。

BasicJBotクラスはJBotInterfaceを拡張して次のように定義されます。BasicJBotクラスはそのスーパークラスの全てのアブストラクトクラスを実装しているので、BasicJBotオブジェクトが作られます。これには2つのコンストラクタがあります。1つはデフォールトのサーボモータのオブジェクトを用いていて、もう1つはその代わりにユーザ定義したオブジェクトを用います。両方ともstartEventとして用いられるイベントを必要とします。

イベントドリブン 【event driven】
 イベント駆動ともいう。

 ユーザや他のプログラムが実行した操作(イベント)に対応して処理を行なう、プログラムの実行形式。

 ユーザが操作を行っていないときはプログラムは何もせず待機しているため、ユーザはそのプログラムを待たせた状態で他の操作を行なうことができる。

 イベントドリブンで動作するプログラムは必要以上にユーザを拘束しないため、マルチタスクOSとの親和性が高く、グラフィカルユーザインターフェースを持ったプログラムではイベントドリブン方式が広く採用されている。

 これに対し、コマンドライン入力で起動するプログラムはマルチタスク性をあまり意識する必要がないことが多く、プログラムが逐次ユーザに操作を要求するタイプのプログラムが主流である。
サージ【surge】
 日本では、100Vの交流電力が各家庭やオフィスに供給されるが、落雷などにより瞬間的に高電圧の電流が流れることがある。ごく短時間とはいえ、場合によって電圧は数万ボルトに達する場合もある。一般に定格以上の電圧がかかる電源異常は過電圧と呼ばれるが、その持続時間によって、スパイク(ナノ秒~マイクロナノ秒)とサージ(ミリ秒単位)に分類される。
 ここでは、交流ではなく直流の異常過電流のことをあらわしている。
ポーリング【Polling】
非同期に発生するイベントをチェックするための手法の1つ。プログラムや通信システムがこうした方式を利用する。

単純にいうと、何らかの変化が発生したかどうかをリモートシステムに確認しにいく、という方法である。たとえば、3つのスイッチを監視しているプログラムがあり、どれかのスイッチがオンになったらそれに対応する処理をするという例を考えよう。このとき、プログラムが無限ループを構成し、このループの中でスイッチがオンになっているかどうかをチェックするという構造が考えられる。これがポーリングである。逆に、スイッチがインテリジェントな構造になっており、オンになったときにプログラムに通知を送ることができるなら、ポーリングではなく単に通知が届くのを待つ、というプログラムにすることもできる。周辺機器からの入力を受け取るためにI/Oポートをチェックする場合などにも用いられる。

ポーリングは一見原始的な手法のようにも思えるが、変化を通知する機構がない場合にはよく使われる。新しいメールがメールサーバに届いているかどうか毎日チェックする、といった処理もポーリングだと考えられる。


基本的なJ-Bot車輪コントロールクラス
package JBot;

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

/**
 * 基本的なJ-Bot車輪コントロールクラス
 * 
 * 車輪サーボモータの自由走行に対するPWMサポートを扱う
 * move(), pivot() or turn()を用いて動作を開始させる。
 * stop() or move(0)でJ-Botを停止させる。
 */

public class BasicJBot extends JBotInterface {
  public BasicWheelServo leftWheel ;
  public BasicWheelServo rightWheel ;
  public Timer timer ;
  public int timeout ;
  public int msecPerCm ;
  public int msecPerPivot ;
  public int msecPerTurn ;

  protected static final int movementNone = 0 ;
  protected static final int movementMove = 1 ;
  protected static final int movementPivot = 2 ;
  protected static final int movementTurn = 3 ;

  protected int leftMovementSpeed ;
  protected int rightMovementSpeed ;
  protected int msecPerStep ;

  /**
   * 一般的な動きに対する車輪サーボモータを設定する。
   * 前進はcmを単位にする。
   * 旋回と回転は普通45゜ステップを単位にする。
   * デフォールトのサーボモータの設定を用いる。
   */
  public BasicJBot ( Event event ) {
               this ( event

                       // 前進
                    , ***  // ( 641 * 100us ) / 1000us = 64 msec
                       // 旋回
                    , ***  // ( 1800 * 100us ) / 1000us = 180 msec
                       // 回転
                    , ***  // ( 6500 * 100us ) / 1000us = 650 msec
                    , new BasicWheelServo (
                          CPU.pin13   // pin
                        ,  ***         // forward
                        ,  ***         // center
                        ,  ***         // backward
                        , 2000        // low
                        )
                    , new BasicWheelServo (
                          CPU.pin12   // pin
                        ,  ***         // forward
                        ,  ***         // center
                        ,  ***         // backward
                        , 2000        // low
                        )
                    ) ;
  }

  /**
   * 一般的な動きに対する車輪サーボモータを設定する。
   * 前進はcmを単位にする。
   * 旋回と回転は普通45゜ステップを単位にする。
   *
   * 入力:int msecPerCm: 直進の時の1cmあたりのミリ秒
   * 入力:int msecPerPivot: 旋回単位あたりのミリ秒
   * 入力:int msecPerTurn: 回転単位あたりのミリ秒
   * 入力:BasicWheelServo leftWheel:左車輪に対するBasicWheelServo
   * 入力:BasicWheelServo rightWheel:右車輪に対するBasicWheelServo
   */
  public BasicJBot
    ( Event event
    , int msecPerCm
    , int msecPerPivot
    , int msecPerTurn
    , BasicWheelServo leftWheel
    , BasicWheelServo rightWheel
    ) {
    super(event);
    this.msecPerCm  = msecPerCm ;
    this.msecPerPivot = msecPerPivot ;
    this.msecPerTurn  = msecPerTurn ;
    this.leftWheel    = leftWheel ;
    this.rightWheel   = rightWheel ;
    timer = new Timer () ;
  }

  /**
   * 動作が完了したかどうかチェックする。
   * これは戻り値がtrueを返すまで呼ぶ。
   *
   * 戻り:もし動作が待ち状態を完了した時にtrueを返す。
   */
  public boolean movementDone () {
    return timer.timeout ( timeout ) ;
  }

  /**
   * 前進する車輪スピードを設定する。
   *
   * 入力:int steps: 何センチ進むか
   */
  public void move ( int steps ) {
    movement ( movementMove, steps ) ;
  }

  /**
   * 左旋回(正数)あるいは右旋回(負数)に対する車輪スピードを設定する。
   *
   * 入力:int steps: 回転のステップ数
   */
  public void pivot ( int steps ) {
    movement ( movementPivot, steps ) ;
  }

  /**
   * 左回転(正数)あるいは右回転(負数)に対する車輪スピードを設定する。
   *
   * 入力:int steps: 回転のステップ数
   */
  public void turn ( int steps ) {
    movement ( movementTurn, steps ) ;
  }

  /**
   * 動作に対する車輪スピードを得る。
   * msecPerStep, leftMovementSpeed, rightMovementSpeedを設定する
   *
   * 入力:int movement: 動作のタイプ
   * 入力:int steps: 回転のステップ数
   */
  protected void getMovementSpeed ( int movement, int steps ) {
    switch ( movement ) {
    default:
    case movementMove:
      msecPerStep = msecPerCm ;
// 注意:前進、後退、旋回、回転のスピードは固定になっています。
      if ( steps > 0 ) {
        leftMovementSpeed  =  100 ;
        rightMovementSpeed =  100 ;
      } else {
        leftMovementSpeed  = -100 ;
        rightMovementSpeed = -100 ;
      }
      break;

    case movementPivot:
      msecPerStep = msecPerPivot ;

      if ( steps > 0 ) {
        leftMovementSpeed  = -100 ;
        rightMovementSpeed =  100 ;
      } else {
        leftMovementSpeed  =  100 ;
        rightMovementSpeed = -100 ;
      }
      break;

    case movementTurn:
      msecPerStep = msecPerTurn ;

      if ( steps > 0 ) {
        leftMovementSpeed  =   30 ;
        rightMovementSpeed =  100 ;
      } else {
        leftMovementSpeed  =  100 ;
        rightMovementSpeed =   10 ;
      }
      break;
    }

    // Compute timeout when appropriate
    if (    ( steps != continuousForward )
         || ( steps != continuousBackward )
         || ( steps != 0 )) {
      timeout = msecPerStep * (( steps > 0 ) ? steps : - steps ) ;
    }
  }

  /**
   * 動作に対する車輪スピードを設定する
   * 正数に対しては動作1を用いる
   * 負数に対しては動作2を用いる
   *
   * 入力:int movement: 動作タイプ
   * 入力:int steps: 動くステップ数
   */
  protected void movement ( int movement, int steps ) {
    // 引数を設定する
    getMovementSpeed ( movement, steps ) ;

    switch ( steps ) {
    case 0:
      setSpeed(0,0);
      break;

    case continuousForward:
    case continuousBackward:
      setSpeed(leftMovementSpeed,rightMovementSpeed);
      break;

    default:
      timer.mark () ;
      setSpeed(leftMovementSpeed,rightMovementSpeed);
      startMovement();
      break;
    }
  }

  /**
   * 両輪に対するスピードを設定する。
   * 設定はパーセントである。
   * 正数は前進回転
   * 負数は後進回転
   *
   * 入力:int left: 左車輪に対するスピード
   * 入力:int right: 右車輪に対するスピード
   */
  protected void setSpeed ( int left, int right ) {
    // Set real speed
    leftWheel.move(left);
    rightWheel.move(right);
  }
}


プログラムの始めに変数の宣言があります。これらはpublicになっていますが、protectedの方がいいかもしません。

これらの変数は両輪のオブジェクト参照とTimerオブジェクトを含んでいます。以前のプログラムではタイミングはCPU.delayで行っていました。この場合では、タイミングはTimerオブジェクトを用いてなされます。timeout変数は動作の持続時間を保持し、Timer.timeoutメソッドと一緒に用いられます。

3つの変数:msecPerCm, msecPerPivot, msecPerTurnはこの実験の前のほうで行った実験に基づいています。例えばBasicWheelServoDistanceTest2.javaプログラムは1cm移動する時間遅延を確かめるのに用いました。この値は641(個々に異なる)ですが、これはCPU.delayが用いている100μsの単位です。Timerは1msタイムアウトメソッドなので、10で割る必要があります。ここでは64.1ですが、整数型なので64になります。

旋回と回転の値は45゜ステップに設計されています。J-Botは細かい傾斜でプログラムできますが、その精度と信頼性はフィードバックの欠落のため制限されています。従って、45゜ステップは妥当な値です。

2つのコンストラクタの定義があります。最初のは、2番めのを用いて前もって定義された定数を用いてBasicWheelServoオブジェクトを割り当てます。これは1つのJ-Botに対しては十分でしょう。2番めの定義はもしそのクラスが定数が僅かに異なっているような複数のJ-Botに用いる場合に使われます。この場合、オブジェクトはBasicJBotクラスを修正する事無しに作ることができます。

次はpublicなメソッドの定義になります。その次はprotectedなメソッドの定義です。

movementDoneメソッドは動作を停止する時刻かどうかを決めるためにTimerオブジェクトをチェックします。もし停止の時だったらtrueの値を返します。

CPU.delayの代わりに何故これらのメソッドを使うのでしょうか? それには2つの理由があります。最初に、動作のタイミングを集中管理できること。2番めにこのタイプのポーリングアーキテクチャーは次の実験で説明するマルチタスクシステムで相性がよく合っています。マルチタスクシステムはタスクをポーリングします。車輪をコントロールするタスクはこの時点でmovementDoneメソッドを用いることができます。

性能に関しては、TimerCPU.delayを用いることはプログラムは何もしていないから同等です。事実、車輪の動作は両輪に対してPWMオブジェクトのバックグラウンド運転によってコントロールされています。

次のプログラムはBasicJBotクラスを使えるようにしました。このプログラムはBasicJBotで定義されているいくつかのメソッドのみを利用しているだけですが、非常に短くなっています。

BasicJBotクラスをテストする
import stamp.core.*;
import JBot.* ;

/**
 * BasicJBotクラスをテストする
 * 
 * BasicJBotクラスメソッドを用いてJ-Botを走らせる。
 */

public class BasicJBotTest1 {
  public static void main () {
    BasicJBot jbot = new BasicJBot (null) ;

    jbot.move ( 10 ) ;
    while ( ! jbot.movementDone ()) ;

    jbot.pivot ( -2 ) ;
    while ( ! jbot.movementDone ()) ;

    jbot.turn ( 2 ) ;
    while ( ! jbot.movementDone ()) ;

    jbot.stop () ;
    while ( ! jbot.movementDone ()) ;
  }
}


次のクラスをlib\stamp\util\os
の中に入れてコンパイルしてください。

Event クラス
/*
 * 協調的マルチタスク状態遷移マシンオペレーティングシステム
 * タスクはEventのサブクラスでタスクはEventによって再開される
 */

package stamp.util.os;

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

/**
 * イベントが生じたことを示すためのインターフェース
 */

 public class Event {
  static final public Event nullEvent = new Event () ;

  /**
   * null eventを作る。Event.nullEventを用いること。
   */
  protected Event () {
  }

  /**
   * イベント参照がnullかどうかチェックする。
   *
   * event:  チェックするevent
   *
   * returns:  もしnullだったらnullEventを返し、それ以外はeventを返す。
   */
  static public Event checkEvent ( Event event ) {
    return ( event == null ) ? nullEvent : event ;
  }

  /**
   * イベントを生じさせる。
   */
  public void notify() {
    notify(null);
  }

  /**
   * イベントを生じさせる。
   */
  public void notify( Object object ) {
  }

  /**
   * イベントの名前を得る。
   *
   * return: イベント名("Event")
   */
  public String name () {
    return "Event" ;
  }
}




次のクラスをlib\stamp\util\os
の中に入れてコンパイルしてください。

Taskクラス
/*
 * 協調的マルチタスク状態遷移マシンオペレーティングシステム
 * これはラウンドロビンタスクスケジューリングを用いた非常に
 * 基本的なOSです。
 * タスクリストは効率性の理由から循環的にリンクしています。
 *
 * 注意: もしTaskStatusクラスが用いないで、数バイトを付け加えれば
 *     コンストラクタの参照でtaskStatusListとnextTaskStatus
 *    を削除可能になる。
 */

package stamp.util.os;

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

/**
 * フリーサイクルのときはいつでもタスクが走ります。
 * 注意: 外部タスクを終了させる良い方法はtaskToAbort.nextState(abort)
 * を用いることです。代替手段はtaskToAbort.stop()ですが、これはメモリ
 * の掃除をしません。
 */

 public abstract class Task extends Event {
  /**
   * taskState() result
   */
  final public static int taskRunning       = 0 ;

  /**
   * taskState() result
   */
  final public static int taskSuspended      = 1 ;

  /**
   * taskState() result
   */
  final public static int taskSemaphoreWait = 2 ;

  /**
   * Initial value of state in execute()
   */
  final public static int initialState = 0 ;
  final public static int checkTimer   = -1 ; // sleep()のサポート用
  final public static int interrupt    = -2 ; // InterruptTaskを見よ
  final public static int terminate    = -3 ;
  final public static int stopped      = -4 ;

  /**
   * sleep()要求によるタスクを扱うためのTimerオブジェクト
   */
  public    Timer timer    = new Timer () ;

  /**
   * 次のアクティブタスクは循環リストの中
   */
  protected static Task taskStatusList  = null ;

  /**
   * taskStatusListに対するリンクされたリスト
   */
  protected Task nextTaskStatus  = null ;

  /**
   * taskState()によって戻される値
   */
  protected int taskStatus ;

  /**
   * 循環リスト上の次のアクティブリスト
   */
  protected Task nextTask  = null ;

  /**
   * execute()の中で用いられる状態変数
   */
  protected int state      = initialState ;

  /**
   * タイムアウトが起こった後の次の状態
   */
  protected int sleepState = checkTimer ;

  /**
   * Timout値
   */
  protected int hi, lo ;

  /**
   * タスクのセットアップ(設定)と開始
   */
  public Task() {
    addTask ( this ) ;

    // タスクをステータスリストに加える
    nextTaskStatus = taskStatusList ;
    taskStatusList = this ;
  }

  /**
   * タスク名を得る。
   *
   * 戻り: String の"Task"
   */
  public String name () {
    return "Task" ;
  }

  /**
   * タスクのステータスを得る
   *
   * 戻り: タスクのステータス
   *        (taskRunning, taskSuspended, taskSemaphoreWait)
   */
  public int taskStatus() {
    return taskStatus;
  }

  /**
   * 現在のタスクの状態を得る
   *
   * 戻り: int タスクの状態
   */
  public int getState () {
    return state ;
  }

  /**
   * もし、タスクが実行中ならばタスクマネージャーのタスクリスト
   * からタスクを削除する。
   * 任意のタスクから呼ぶことが出来る。
   *
   * 戻り: boolean タスクがリストから削除されたときにtrue
   */
  public boolean suspend () {
    if ( taskStatus == taskRunning ) {
      // 注意: nextTaskがnullでなかったらマネージャーはnull
      // であってはいけない
      taskStatus = taskSuspended ;
      return removeTask ( this ) ;
    }
    return false ;
  }

  /**
   * 停止するためにタスクを削除しstopの状態に設定する
   * 戻り: boolean  停止に成功したらtrue
   */
  public boolean stop() {
    if ( suspend ()) {
      state = stopped ;
      return true ;
    }

    return false ;
  }

  /**
   * もしタスクが実行していなかったならば、タスクマネージャーの
   * リストにタスクを戻して加える。
   * 任意のタスクから呼ぶことができるが、そのタスクはマネージャーを
   * 持っていなければならない
   *
   */
  public boolean resume () {
    if ( taskStatus == taskSuspended ) {
      // 注意: マネージャーはnextTaskがnullでなかったならばnullで
      //       であってはいけない
      addTask ( this ) ;
      return true;
    }
    return false ;
  }

  /**
   * resume()と同じ
   */
  public boolean start() {
    return resume () ;
  }

  /**
   * resume()と同じ
  */
  public void notify( Object object ) {
    resume () ;
  }

  /**
   * 次のexecute()呼び出しに対して状態を設定する
   *
   */
  public void nextState(int state) {
    this.state = state ;
  }

  /**
   * 次のexecute()呼び出しに対して状態を設定する
   * タスクをSuspendする。他のタスクによってresumeされる
   */
  public void sleep(int state) {
    this.state = state ;
    suspend();
  }

  /**
   * poll timerを仕掛けて設定単位時間を経過後に次の状態を続ける
   *
   * 入力: int nextState タイムアウトが起こったときに入る状態
   * 入力: int hi タイムアウト値。詳細はTimer.timeout(hi,lo)を見よ
   * 入力: int lo  タイムアウト値
   */
  public void sleep ( int nextState, int hi, int lo ) {
    this.hi = hi ;
    this.lo = lo ;
    timer.mark () ;
    sleepState = nextState ;
    state = checkTimer ;
  }

  /**
   * poll timerを仕掛けて設定単位時間を経過後に次の状態を続ける
   * 入力: int nextState  タイムアウトが起こったときに入る状態
   * 入力: int msec ミリ秒単位でのタイムアウト値
   */
  public void sleep ( int nextState, int timeMS ) {
    sleep ( nextState, timeMS/569, (timeMS%569)*115) ;
  }

  /**
   * poll timerを仕掛けて設定単位時間を経過後に次の状態を続ける
   * 入力: int nextState  タイムアウトが起こったときに入る状態
   * 入力: int timeS 秒単位でのタイムアウト値
   */
  public void sleepSec ( int nextState, int timeS ) {
    sleep ( nextState, (timeS*18)/10, 0);
  }


  /**
   * 1期間タスクを実行する。全てのサブクラスはこのメソッドで
   * 実装されなければいけない
   * GUIがアイドルのときはいつでもアイドルタスクが実行される。
   * execute()の実装はインターフェースが応答可能にするために
   * 100ms以上かかってはいけない。
   */
  protected abstract void execute() ;


  // タスクマネージャーサポート

  /**
   * クラス変数: タスクリストの現在のタスク
   */
  static protected Task taskList = null;

  /**
   * 現在のアクティブタスク
   */
  static protected Task currentTask = null ;

  /**
   * 現在実行しているタスクオブジェクトを得る
   *
   * 戻り:Task 現在実行しているタスク
   */
  static public Task getCurrentTask () {
    return currentTask ;
  }

  /**
   * タスクリストにタスクを加える
   *
   * 入力:Task aNewTask: タスクリストに加えるタスク
   */
  public static void addTask(Task aNewTask) {
    if ( taskList == null ) {
      aNewTask.nextTask = aNewTask ;
    }
    else
    {
      aNewTask.nextTask = taskList.nextTask ;
      taskList.nextTask = aNewTask ;
    }

    aNewTask.taskStatus = taskRunning ;
    taskList = aNewTask ;
  }

  /**
   * タスクリストからタスクを削除する
   *
   * 入力:aTaskToRemove:タスクリストから削除するタスク
   *
   * 戻り:boolean リストからタスクが削除できたらtrue
   */
  public static boolean removeTask(Task aTaskToRemove) {
    // 空のリストを始めに処理する

    if ( taskList != null ) {
      // 現在のタスクからリストを走査し見つかったらそれを削除する

      Task scanTask = taskList ;

      do {
        if ( scanTask.nextTask == aTaskToRemove ) {
          // 削除するタスクが見つかった

          if ( scanTask == aTaskToRemove ) {
            // リストの中の一つだけのタスクを削除する
            taskList = null ;
          }
          else
          {
            // 現在のタスクが削除されたかどうかチェックする

            if ( taskList == aTaskToRemove ) {
              taskList = aTaskToRemove.nextTask ;
            }

            scanTask.nextTask = aTaskToRemove.nextTask ;
          }

          aTaskToRemove.nextTask  = null ;
          return true ;
        }

        // リストの中の次のタスクをチェックする
        scanTask = scanTask.nextTask ;
      }
      while ( scanTask != taskList ); //全てのリストが走査されたら脱出
    }

    return false ;
  }

  /**
   * ラウンドロビン法でタスクリストの中の各々のタスクを
   * 永久に実行する。実行するタスクが無くなった時に戻る
   */
  public static void TaskManager() {
    while ( taskList != null ) {
      // execute()がremoveTaskを通して変えることができるように
      // taskListを変える
      currentTask = taskList ;
      taskList = taskList.nextTask ;

      if (currentTask.state == checkTimer) {
        // タスクがタイムアウトを待機している
        if (currentTask.timer.timeout(currentTask.hi,currentTask.lo)) {
          // タイムアウトが起こったら、次の状態を設定する
          currentTask.state = currentTask.sleepState ;

          // もし、次の状態が不適切に設定されていたらば抜け出す
          if ( currentTask.state == checkTimer ) {
            currentTask.state = terminate ;
          }
        }
      } else {
        // 次の状態を呼び出す
        currentTask.execute () ;
      }
    }
  }
}


メインメソッドでは3つの動作が行われます。最初はJ-Botは10cm前進し、90゜右に旋回し、左に90゜回転します。各々の動作呼び出しの後でmovementDoneが呼ばれるのに注意してください。これはstartEventが無いため必要です。BasicJBotコンストラクタのnullの引数はstartEventEvent.nullEventを設定している。

課題3-3

  1. 正しく動作するか確かめるためにBasicJBotの全ての動作メソッドを試しなさい。(前後、左右の旋回、前後左右の回転)

  2. J-Botを10×15cmの四角で動かしなさい。次に50×75cmの四角でも動かしてみなさい。

  3. J-Botを円で一周動かしなさい。ヒント:turnを8回行う。

実験3-4 操縦 - 固定動作クラス

BasicJBot扱いやすいですが、テストプログラムは各々のアクションで複数のメソッドが必要になります。これらはmovementDoneメソッドを取り除けば半分になります。完全に新しい実装を持ってくる代わりにBasicJBotクラスのコンストラクタに異なったオブジェクトを単純に送ります。次のFixedMovementJBotクラスの定義はmovementDoneの呼び出しの代わりをします。

固定動作に対する車輪コントロールクラス
package JBot;

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

/**
 * 固定動作に対する車輪コントロールクラス
 * 
 * 動作コントロールのためにBasicJBotクラスと組み合わせる
 */

public class FixedMovementJBot extends Event {
  protected boolean idle = true ;

  /**
   * 動作が開始された時に呼ばれる
   */
  public void notify (Object jbot) {
    if ( idle && ( jbot != null )) {
      idle = false ;
      while ( ! ((JBotInterface)jbot).movementDone ()) {
      }
      idle = true ;
    }
  }
}


FixedMovementJBotクラスはイベントクラスを元にしています。これはnotifyメソッドを再定義する必要があります。 メソッドに送られるオブジェクトはJ-Botの動作をコントロールするJBotInterfaceでしょう。それはnullでは無いのでチェックが行われます。もし、参照がnullだったらそのメソッドは何もしません。そうでなければ、movementDoneがtrueを返すまで待ちます。

その結果、JBotInterfaceのmove,pivot,turnメソッドの呼び出しは、その動作が完了するまで戻りません。次のプログラムは新しいクラス定義の練習です。

FixedMovementJBotクラスをテストする
import stamp.core.*;
import JBot.* ;

/**
 * FixedMovementJBotクラスをテストする
 * 
 * BasicJBotクラスメソッドを用いてJ-Botを走らせる
 */

public class BasicJBotTest2 {
  public static void main () {
    BasicJBot jbot = new BasicJBot ( new FixedMovementJBot ()) ;

    jbot.move ( 10 ) ;  // forward
    jbot.pivot ( -2 ) ; // pivot right
    jbot.turn ( 2 ) ;   // turn left
    jbot.stop () ;
  }
}


BasicJBotTest2のプログラムは以前のテストプログラムに比べてかなり短くなっています。また、読みやすくもなっています。jbot.moveのようにjbotの動作の呼び出しはFixedMovementJBotオブジェクトの使用のため動作が完了するまで戻りません。

実験3-5 操縦 - Ramping Servo クラス

Rampingは急にサーボモータを逆方向に回す代わりにだんだんとスピードを変えていく方法です。このテクニックは電池とサーボモータの両方の寿命を長くします。Rampingを使わないとパワー0%から100%まで一気に行きます。これを車で行うと、滑った跡が残ります。J-Botは滑った跡は残しませんが予測できないおかしな動きになります。

Rampingでプログラムする

RampingJBotクラスはは内部的な利用のためいくつかのprotectedメソッドを付け加えますが、動作メソッドの付加的な変更はありません。その代わり、両輪の現在のスピードと望まれるスピードの間にある動作の増加をどのようにするかを決定します。これは前進の後すぐに後進ができます。この場合、J-Botは前進方向で停止するようにスピードを落とし、後進方向にフルスピードで走るまで加速します。

下に示すように、RampingはRamping期間にスピードダウンやスピードアップをするから同じ距離を移動するのに僅かに長くかかります。


Rampingの実装でのフルスピードの動作はJ-BotはやはりRamping期間の間動いているから、Ramping無しのフルスピードより僅かに短くなります。Rampingは任意の数のステップを用いることができますが、非常に多くすると長い時間がかかるかJavelinの性能制限よりステップを短くしなければならなくなります。

次のクラス定義はRampingをサポートします。

Rampingを用いて固定動作をする車輪コントロールクラス
package JBot;

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

/**
 * Rampingを用いて固定動作をする車輪コントロールクラス
 * 
 * 動作コントロールに対するBasicJBotクラスを用いる
 */

public class RampingJBot extends BasicJBot {
  static final private int rampTimeout = 35 ; // milliseconds
  static final private int maxRampCount = 3 ;

  protected boolean decelerate = false ;
  protected int rampCount = 0 ;
  protected int rampLeftStep ;
  protected int rampRightStep ;

  protected int leftSpeed = 0 ;
  protected int rightSpeed = 0 ;

  protected int currentMovement = movementNone ;
  protected int currentSteps ;
  protected int nextMovement    = movementNone ;
  protected int nextSteps ;

  /**
   * 一般的な動作のために車輪サーボを設定する
   * 前進はcmを単位にする。
   * 旋回と回転は普通45゜ステップを単位にする。
   * デフォールトのサーボモータの設定を用いる。
   *
   * 入力:Event event:動作が開始した時通知するイベント。nullにできる
   */
  public RampingJBot ( Event event ) {
    super ( event ) ;
  }

  /**
   * 一般的な動きに対する車輪サーボモータを設定する。
   * 前進はcmを単位にする。
   * 旋回と回転は普通45゜ステップを単位にする。
   * デフォールトのサーボモータの設定を用いる。
   *
   * 入力:Event event:動作が開始した時通知するイベント。nullにできる
   * 入力:int msecPerCm: 直進の時の1cmあたりのミリ秒
   * 入力:int msecPerPivot: 旋回単位あたりのミリ秒
   * 入力:int msecPerTurn: 回転単位あたりのミリ秒
   * 入力:BasicWheelServo leftWheel:左車輪に対するBasicWheelServo
   * 入力:BasicWheelServo rightWheel:右車輪に対するBasicWheelServo
   */
  public RampingJBot
    ( Event event
    , int msecPerCm
    , int msecPerPivot
    , int msecPerTurn
    , BasicWheelServo leftWheel
    , BasicWheelServo rightWheel
    ) {
    super ( event, msecPerCm, msecPerPivot, msecPerTurn, leftWheel, rightWheel ) ;
  }

  /**
   * 次の動作が開始できるかチェックする。
   *
   * 戻り:もし次の動作メソッドが呼び出し可能のときtrue
   */
  public boolean ready() {
    return nextMovement == movementNone ;
  }

  /**
   * 動作が完了したかどうかチェックする。
   * これは戻り値がtrueを返すまで呼ぶ。
   *
   * 戻り:もし動作が待ち状態を完了した時にtrueを返す。
   */
  public boolean movementDone () {
    if ( currentMovement == movementNone ) {
      if ( nextMovement == movementNone ) {
        // nothing left to do
        return true;
      } else {
        // 停止から新しい動作を開始する
        currentMovement = nextMovement ;
        currentSteps    = nextSteps ;
        nextMovement    = movementNone ;

        // Rampingを開始する
        startRamping () ;
      }
    } else {
      // currentMovement is active
      if ( rampCount > 0 ) {
        if ( timer.timeout ( rampTimeout )) {
          // Ramp timeout occurred. Adjust speed.
          -- rampCount ;

          if ( rampCount == 0 ) {
            if ( decelerate ) {
              // should be stopped, setup to accelerate
              decelerate = false ;
              startRamping () ;
            } else {
              // should be up to speed
              startFullSpeedMovement(true) ;
            }
          } else {
            // setup next ramp movement
            updateRampSpeed() ;
          }
        }
      } else {
        // Rampingはしない。動作ステータスをチェックする
        if ( super.movementDone ()) {
          getNextMovement ();
        }
      }
    }
    return false ;
  }

  /**
   * 次の動作の設定を得る
   */
  protected void getNextMovement () {
    // Get nextMovement
    if ( nextMovement == movementNone ) {
      // check if anything more to do
      if ( currentMovement == movementNone ) {
        return ;
      }

      if (    ( currentMovement == movementMove )
           && ( currentSteps == 0 )) {
        // Already stopped. Do nothing more
        currentMovement = movementNone ;
        return ;
      }

      // setup to stop if no movement acquired
      nextMovement = movementMove ;
      nextSteps    = 0 ;

      // Get next movement
      causeNextEvent () ;
    }

    // update steps, currentMovement must be updated later
    currentSteps = nextSteps ;

    // check if next movement or if need to decelerate
    if ( nextMovement == currentMovement ) {
      startFullSpeedMovement(false) ;
    } else {
      // decelerate first
      currentMovement = nextMovement ;
      rampDown();
    }
  }

  /**
   * currentMovementに対してRampingを開始する
   */
  protected void startRamping () {
    if ( currentSteps == 0 ) {
      super.move(0);  // stop servos
    } else {
      getMovementSpeed ( currentMovement, currentSteps ) ;

      rampTo ( leftMovementSpeed, rightMovementSpeed ) ;
    }
  }

  /**
   * 減速に対するRampingを開始する
   */
  protected void rampDown () {
    decelerate = true ;
    rampTo ( 0, 0 ) ;
  }

  /**
   * 動作を開始する。Rampingはすでに完了
   *
   * 入力:boolean ramping:Ramping時間が引かれるべき時にtrue
   */
  protected void startFullSpeedMovement ( boolean ramping ) {
    int timeoutAdjust = 0 ;

    switch ( currentMovement ) {
    case movementMove:
      super.move ( currentSteps ) ;
      timeoutAdjust = msecPerStep - 2 ;
      break;

    case movementPivot:
      super.pivot ( currentSteps ) ;
      timeoutAdjust = msecPerStep - 2 ;
      break;

    case movementTurn:
      super.turn ( currentSteps ) ;
      timeoutAdjust =   ( currentSteps == 1 )
                      ? (( 3 * msecPerStep ) / 8 )
                      : ( msecPerStep / 2 ) ;
      break;
    }

    // adjust timeout for ramping
    if ( ramping ) {
      timeout -= timeoutAdjust ;
    }

    // Reset next movement
    nextMovement = movementNone ;
  }

  /**
   * 目的のスピードにRampingするための設定
   *
   * 入力:int left:最終的な左のスピード
   * 入力:int right:最終的な右のスピード
   */
  protected void rampTo ( int left, int right ) {
    rampCount      = maxRampCount - 1 ; // ramp down count less current step
    rampLeftStep   = ( left  - leftSpeed )  / maxRampCount ;
    rampRightStep  = ( right - rightSpeed ) / maxRampCount ;
    updateRampSpeed() ;
  }

  /**
   * rampパラメータを用いてスピードをアップデートする
   */
  protected void updateRampSpeed () {
    // set timeout mark for next ramp speed update
    timer.mark();

    // 新しいスピードに設定する
    setSpeed(leftSpeed+rampLeftStep,rightSpeed+rampRightStep) ;
  }

  /**
   * 前進するため車輪のスピードを設定する
   *
   * 入力:int cm: 移動する直線距離
   */
  public void move ( int cm ) {
    setNextMovement ( movementMove, cm ) ;
  }

  /**
   * 左旋回するように車輪スピードを設定する
   *
   * 入力:int steps: 回転するステップ数
   */
  public void pivot ( int steps ) {
    setNextMovement ( movementPivot, steps ) ;
  }

  /**
   * 左回転するように車輪スピードを設定する
   *
   * 入力:int steps: 回転するステップ数
   */
  public void turn ( int steps ) {
    setNextMovement ( movementTurn, steps ) ;
  }

  /**
   * 次の動作が起こるための設定
   * 動作はmovementDone()を呼ぶことを通して開始される
   *
   * 入力:int movement: 行う次の動作
   * 入力:int steps: 回転するためのステップ
   */
  protected void setNextMovement ( int movement, int steps ) {
    nextMovement = movement ;
    nextSteps    = steps ;

    // 現在停止中ならば動作を開始する
    if ( currentMovement == movementNone ) {
      startMovement();
    }
  }

  /**
   * 両輪のスピードを設定する
   * 設定はパーセントである
   * 正の値は前進
   * 負の値は後進
   *
   * 入力:int left: 左車輪のスピード
   * 入力:int right: 右車輪のスピード
   */
  protected void setSpeed ( int left, int right ) {
    // Set real speed
    super.setSpeed(left,right);
    leftSpeed  = left ;
    rightSpeed = right ;
  }

  /**
   * 両輪のスピードを調整する
   * 設定はパーセントである
   * 正の値は前進
   * 負の値は後進
   *
   * 入力:int left: 左車輪のスピード補正
   * 入力:int right: 右車輪のスピード補正
   */
  protected void adjustSpeed ( int left, int right ) {
    super.setSpeed
      ( leftSpeed  + (( leftSpeed  * -left  ) / 100 )
      , rightSpeed + (( rightSpeed * -right ) / 100 ));
  }
}


Rampingクラスがどのように動くか

RampingJBotクラスはBasicJBotによって備えられたサービスを使えるようにしていますが、movementDonesetSpeedメソッドの動作方法を補正します。オブジェクトはBasicJBotで起こるような大きなジャンプの代わりに小さな増加でスピードを変えることができます。例えば、move(1)メソッドが次にmove(-1)を続けるBasicJBotオブジェクトを呼ぶことはJ-Botが1cm前進し、回転方向を逆方向にし1cm後退します。RampingJBotを使わなければ、J-Botの車輪は急に発進しそして逆に動作を開始します。後者の場合相対スピード変化は200%(100%- (-100%))です。これはJ-Botが電池駆動のとき、重大なパワーサージが問題を引き起こします。

RampingJBotはJ-Botの現在のスピードを見ています(setSpeedメソッドで保存しているのを思い出すこと)。目的のスピードと比較し、スピード変化が短いステップで起こるようにスピードを設定します。これは距離の僅かな変化を起こしますが、おかしな動作とパワーサージを取り除きます。そのクラスはどのくらい遠いところまで走るかを計算して距離の変化を計算に入れようともしています。

RampingJBotオブジェクトにはいくつかの一時的な変数が定義されています。rempCountはスピードの変化間を取るステップの数をコントロールします。rampLeftSteprampRightStepは両輪のパーセントのスピード変化です。2つののサーボモータは独立にコントロールされなければならないので2つの変数が必要です。最後に、finalは目的のスピードが保存されます。整数値が用いられ、丸め誤差が起こるのでステップする時に差異が生じるのでこれは必要になります。

変数currentMovementnextMovementcurrentStepsnextStepsと一緒に動作を維持します。次の動作を保存するのはおかしく思われるだろうが、動作の間の適切な推移ができるようにするのと、2つの同じ動きが1つに組み合わさるようにするのが必要です。例えば、前に5、それから10は前に15移動するのと同じでなければならない。

setSpeedメソッドはBasicJBotのsetSpeedを呼び、現在のスピードを保存します。adjustSpeedメソッドはそのスピードを変えることができます。このメソッドはBasicJBotオブジェクトでは使えません。それは後で出てくる車輪エンコーダフィードバックに基づくスピード調整を必要とするWheelEncoderJBotサブクラスがサポートしているからです。

仕事の塊はmovementDoneメソッドでなされます。このメソッドはcurrentMovementnextMovement変数を計算に入れています。もし両方がmovementNoneに設定されていたら、メソッドはtrueを持って終わります。もしcurrentMovementmovementNoneに設定されていたら、現在の動作は終了していて、nextMovementに推移する時点であることを示します。この推移は直前の動作がnextMovementと直前の動作の最後でJBotが僅かな時間停止したrampingとが異なった時に起こります。

動作の推移をチェックする時間が無いとしたら、rampCountによって管理されるrampingスタータスをチェックします。この変数は目的のスピードに上げていく時と停止するためにスピードダウンする時の両方に用いられます。RampingはrampCountがゼロになった時に完了します。decelerate変数はrampingはupかdownかを示しています。もしそれがdownの時はJBotは停止して、次の動作のためupにします。これはstartRampingメソッドを呼ぶことによってなされます。もしJBotがRamping upの時は、フルスピードで動作を開始する時です。これはstartFullSpeedMovementを呼ぶことによってなされます。

何の推移も無い時はrampingのスピードはupdateRampSpeedを呼ぶことよって更新されます。これはrampingサポートを行うオブジェクト変数を用います。全てのelseに一致しなかった場合、動作はスーパークラスBasicJBotmovementDoneメソッドによって操作されます。動作が完了した場合はgetNextMovementを呼ぶ時です。

次の図はgetNextMovementが1つの動作で呼ばれる時を示しています。

getNextMovementメソッドはちょっと複雑です。これは次の動作がすでに与えられているかを確かめます。もしそうでなかったら、causeNextEventが呼ばれます。このサポートはJBotInterfaceクラスに戻って行います。これは典型的にMultitaskingJBotサポートと一緒に用いられます。RampingJBotクラスをコントロールするタスクはcauseNextEvent呼び出しで通知されます。タスクは動作を開始し、nextMovement変数を設定して戻ります。もし、次の動作が直前のものと同じ時は、startFullSpeedMovementが呼ばれます。ramping時間は動作時間から減算しないことを示しているfalseをパラメータとして渡します。

rampingのメソッドとしてはstartRamping,rampDown, rampToがあります。これらはramping処理のupやdownを行うためにramping変数を操作します。

startFullSpeedMovementメソッドは、ソースファイルで出てくる順番で見ているので次にあります。これは実際にBasicJBotの動作メソッドが呼ばれるところにあります。BasicJBotは全ての動作はフルスピードで走ると仮定しています。startFullSpeedMovementメソッドはrampingの間か下に示すように2つの同じ動作が続けて起こるときに走ります。上のタイムチャートで、startFullSpeedMovementメソッドはgetNextMovementの点で二度目が呼ばれます。
スイッチ文は次に行う動作のタイプを選んでいます。各々の場合、フルスピードの動作を設定するためにBasicJBotのスーパークラスメソッドを呼んでいます。変数timeoutAdjustは下に示すように設定します。
case movementMove:
      super.move ( currentSteps ) ;
      timeoutAdjust = msecPerStep - 2 ;
      break;
timeoutAdjustの値はramping upやdownを計算するために必要な変化です。もし、rampingが動作の一部の場合、この値はオブジェクトの動作のコントロールに用いられているtimeout値から減算されます。もし、同じタイプの動作が続けて2つ実行された場合、この補正は最初の動作のtimeoutを用います。

move, pivot, turnメソッドはJBotInterfaceスーパークラスの中で定義されています。動作の取り扱い方法が異なるのでこのクラスで再定義されています。RampingJBotクラスでは、これらのメソッドはnextMovementnextSteps変数の中に要求される動作を単純に保存します。前に注意したように、これらの変数はJBotが1つの動作から他に推移する時に用いられます。

次のプログラムはRampingJBotクラスの練習です。

RampingJBotクラスをテストする
import stamp.core.*;
import JBot.* ;

/**
 * RampingJBotクラスをテストする
 * 
 * BasicJBotクラスメソッドを用いてJ-Botを走らせる
 */

public class BasicJBotTest3 {
  public static void main () {
    RampingJBot jbot = new RampingJBot ( new FixedMovementJBot ()) ;

    jbot.move ( -5 ) ;        // 後進
    jbot.stop () ;

    jbot.move ( 10 ) ;
    jbot.pivot ( -2 ) ;
    jbot.turn ( 2 ) ;
    jbot.stop () ;
  }
}


このプログラムは最初に短い後進をしますが、後は以前のテストプログラムと同じ順番で動作します。このプログラムはRampingJBotに変更し最初に後進の動作を付けただけです。

実験3-6 J-Botを操作する

上ののテストプログラムはJ-Botを他のパターンで動かすように変更するのは簡単にできますが、ソースコードが長く冗長になります。もし定義する事が動きそのものだけならば、パターンを作るのは簡単です。ここでは、単純なプログラムで動作をいかに符号化し復号化するかをみましょう。

下の例では、動作はバイト配列に格納されています。Javaでは次のサンプルプログラムで示すように、定数のバイト配列を定義するのは簡単です。

配列に格納されているコマンドを用いてのJ-Botを動かす
import stamp.core.*;
import JBot.* ;

/**
 * 配列に格納されているコマンドを用いてのJ-Botを動かす
 * 
 * RampingJBotクラスメソッドとマンド表を用いてJ-Botを走らせる
 */

public class BasicJBotTest4 {
  static final byte move = -1 ;
  static final byte pivot = -2 ;
  static final byte turn = -3 ;

  RampingJBot jbot = new RampingJBot (new FixedMovementJBot ()) ;

  static byte movements [] = { move, 10, pivot, -2, move, 20 } ;

  public void performMovements ( byte movements [] ) {
    for ( int i = 0 ; i < movements.length ; i += 2 ) {
      switch ( movements [ i ] ) {
      case move:
        jbot.move ( movements [ i + 1 ] ) ;
        break ;

      case pivot:
        jbot.pivot ( movements [ i + 1 ] ) ;
        break ;

      case turn:
        jbot.turn ( movements [ i + 1 ] ) ;
        break ;
      }
    }

    jbot.stop () ;
  }


  // ----  メインプログラム ----

  public static void main () {
    BasicJBotTest4 myJBot = new BasicJBotTest4 () ;

    myJBot.performMovements ( movements ) ;
  }
}


課題3-6

  1. J-Botが10×15cmの四角を描くようにmovements配列を変更しなさい。

  2. 現在のメソッドは100%のスピードで動作するようになっています。このスピードを変更するsetMovementSpeed()をつくり、これを用いて100%,50%,30%と動作スピードを変えて動作するように配列を変更し、動かしなさい。

  3. BasicJBotTest4クラスを2つに分離し、MovementJBotBasicJBotTest5にしなさい。このようにするとMovementJBotは再利用できるようになります。

  4. 楕円とか、星型とか今までと異なったパターンで動くようにmovementsの内容を変更しなさい。

実験3-7 触覚を作りテストする



 次の部品を用意します。(各2つ)
  1. 触覚のワイヤー 
  2. ネジ       
  3. スペーサー
  4. 3ピンヘッダー  
  5. 220Ωの抵抗 
  6. 10kΩの抵抗 

下図の様にボードの前のネジを外し、スペーサーを入れ、 ワッシャー、触覚ワイヤーを乗せ、ネジでとめます。この二つの触覚はそれぞれボードのグラウンドにつながることになります。

回路図は下のようになります。
ポートの位置が異なっていますが、大体このように接続します。
スピーカは接続する必要はありません。

製作ビデオ

触覚スイッチテスト
import stamp.core.*;

/**
 * 触覚スイッチテスト
 * 
 * 触覚スイッチステータスを表示する
 */

public class whisker1 {
  public static void main() {
    CPU.setInput ( CPU.pin4 ) ;    // ピンを入力に指定する
    CPU.setInput ( CPU.pin6 ) ;
    while ( true ) {
      System.out.print   ( "P6=" ) ;
      System.out.print   ( CPU.readPin ( CPU.pin6 ) ? 1 : 0 ) ;
      System.out.print   ( "  P4=" ) ;
      System.out.println ( CPU.readPin ( CPU.pin4 ) ? 1 : 0 ) ;
      CPU.delay ( 5000 ) ;
    }
  }
}


whisker1クラスは触覚が正しく動作するかをテストするプログラムで、触覚に繋がっているJavelinのI/Oピンの状態をチェックし表示します。全てのI/OピンはJavaのプログラムが開始される時はいつでもデフォールトとして入力になっています。これは触覚に繋がっているI/Oピンは自動的に入力として機能することを意味します。電圧が5V(触覚が押されてない)の時は1、電圧が0V(触覚が押された時)の時は0を表示します。

課題3-7-1

触覚が正しく機能しているか、残りの3つの状態について調べなさい。(右の触角を押したとき、左の触角を押したとき、両方押したときに予想と同じかどうか調べる)

もし、予想と異なっていたら、回路、あるいはプログラムをチェックすること。


次は、この触覚が押された状態をメッセージウィンドウで確認するのではなく、LEDを点灯させて確認するようにします。このためには、LEDを点灯させるI/Oピンを出力用に設定する必要があります。次の2行を先頭に入れてください。

CPU.setOutput ( CPU.pin9 ) ;
CPU.setOutput ( CPU.pin10 ) ;


状態の表示の代わりに次の文に変えてください。

CPU.writePin ( CPU.pin9, CPU.readPin ( CPU.pin4 )) ;
CPU.writePin ( CPU.pin10, CPU.readPin ( CPU.pin6 )) ;


課題3-7-2

触覚を押して正しく機能するかチェックしなさい。もし、機能しない場合は回路とプログラムをチェックすること。
注: この実験でLEDを壊す人が最近増えています。正しい回路の作り方を実験1に戻って復習しましょう。特に、電源を入れながら配線をしてはいけない。

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

この実験では触覚を用いてJ-Botをガイドするようにプログラムします。触覚に何かが触れたら方向転換するようなプログラムを作ります。

放浪するプログラムwhisker2クラスを作りましょう。このプログラムは障害物にぶつかるまで直進します。この場合、J-Botは触覚の片方あるいは両方に障害物に当たったことを知ります。これを知ったらすぐに実験3-6で作ったバックしたり回転したりするルーチンを用いて回避します。そしてまた、障害物に当たるまで直進します。この触覚の状態をまとめると以下のようになります。

次のソースは触覚の状態からJ-Botの動作に適切なルーチンをいかに選ぶかを示す例です。例えば、状態1はJ-Botは直進、状態2は後進して右回転、状態3は後進して左回転、状態4は後進してUターンさせます。

状態
1highhigh障害物なし
2lowhigh左側に障害物
3highlow右側に障害物
4lowlow前方に壁のようなもの
触覚スイッチテスト
import stamp.core.*;
import JBot.*;

/**
 * 触覚スイッチテスト
 */

public class whisker2 {
  public static void main() {
    JBotInterface jbot = new RampingJBot ( new FixedMovementJBot ()) ;
    CPU.setInput ( CPU.pin4 ) ;    // ピンを入力に指定する
    CPU.setInput ( CPU.pin6 ) ;

    while ( true ) {
      switch (   ( CPU.readPin ( CPU.pin6 ) ? 2 : 0 )
               + ( CPU.readPin ( CPU.pin4 ) ? 1 : 0 )) {
      case 0:   // both low, 後進して右旋回
        jbot.stop () ;
        jbot.move ( -3 );
        jbot.pivot ( -4 ) ;
        break ;

      case 1:   // P6 low, 後進して左旋回
        jbot.stop () ;
        jbot.move ( -2 );
        jbot.pivot ( 2 ) ;
        break ;

      case 2:   // P4 low, 後進して右旋回
        jbot.stop () ;
        jbot.move ( -2 );
        jbot.pivot ( -2 ) ;
        break ;

      case 3:   // ぶつかっていない、直進
        jbot.move ( 2 );
        break ;
      }
    }
  }
}


動作チェックビデオ

課題3-8

近くに物体を置いて実際に思ったとおりに動くかチェックしなさい、うまくいかなかった場合、対処できるようにプログラムなどを考えなさい。

実験3終わり