#nomenubar #navi(NQC入門) RISには標準で光センサが1つ、タッチ・センサ2つ含まれています ((2005年度の授業では、さらに光センサを1つ追加したので合計4つのセンサが使用できます))。 このセクションではセンサからの入力条件によってロボットの動作を変えるプログラムを作ってみましょう。 目次 #contents *5.1 タッチ・センサを使う [#sa151722] RCXでは3つある端子(1〜3)にどの種類のセンサでも接続することができます。 次の例は端子1に接続されたタッチ・センサが押されるまで直進を続けるロボットのプログラムです。 task main() { SetSensor(SENSOR_1,SENSOR_TOUCH); // センサ端子1に接続されたセンサが // タッチセンサであると宣言 OnFwd(OUT_A+OUT_C); until (SENSOR_1 == 1); // タッチセンサが押されるまで待つ Off(OUT_A+OUT_C); } &color(#000000,#ccccff){センサの種類の指定}; 各センサをセンサ端子 1,2,3 のどこにつないでもかまいませんが、 それぞれの端子につながれたセンサーがどのタイプであるのかを 宣言する必要があります。そのために使うのが SetSensor です。 // SetSensor(センサ端子,センサの種類); // のようにして使います。『センサ端子』は、 SENSOR_1, SENSOR_2, SENSOR_3 のいずれかを指定し、『センサの種類』は、 タッチ・センサの場合、SENSOR_TOUCH, 光センサの場合、SENSOR_LIGHT を 指定します。例えば、 // SetSensor(SENSOR_1,SENSOR_TOUCH); SetSensor(SENSOR_2,SENSOR_LIGHT); SetSensor(SENSOR_3,SENSOR_TOUCH); // と宣言すると、センサ1と3はタッチ・センサ、 センサ2は光センサである、と指定したことになります。 ちなみにRISには含まれていませんが、LEGO社純正の回転センサや温度センサを使用する場合には、SENSOR_ROTATION や SENSOR_CELCIUS をセンサの種類として指定します。 &color(#000000,#ccccff){センサの値}; この例のように通常モード((通常モード以外にrawモードというモードがあります。このモードだと0か1ではなく、0〜1023までの値になります(つまり10bitで量子化されている))で使用する場合には、 タッチ・センサーが押されていなければ SENSOR_1 の値が 0、押されていれば 1 という値となります。 &color(#000000,#ccccff){until と条件式について}; until は ( ) の中に書かれた条件が満たされるまで次の命令を実行しません。 従って // until (SENSOR_1 == 1); // は、SENSOR_1 の値が 1 に等しくなる (つまりタッチセンサが押される) まで次の命令に進まないという意味です。 ここでは、== という記号に注意してください。 左辺と右辺が等しいかどうかを比較するときには単に = を使うのではなく、 == を使います。そのほかにも、 // < 小さい <= 小さいか等しい > 大きい >= 大きいか等しい != 等しくない // などの記号があります。また2つ以上の条件を組み合わせるときには // && かつ || または // という記号を使います。たとえば、 // until ((SENSOR_1 == 1) && (SENSOR_3 == 1)); Off(OUT_A+OUT_C); // のように使います。この例では、SENSOR_1 と SENSOR_3 の 両方が押された時のみ until の次の命令(モータを止める)に進みます。 条件式全体を括弧でくるのを忘れないように。 条件式のそのもの値は、真の時 true、 偽の時 false です。 つまり // 1 + 2 == 3 // という条件式の値は true、 // ( 1 + 2 == 3) && ( 2 + 3 == 4) // という条件式の値は false となります。 *5.2 while を使った動作をくり返し [#ub13798f] ある条件が満たされている間だけ動作をさせるためには while を使うと便利です。上のプログラムを while を使って書き直すと以下のようになります。 task main() { SetSensor(SENSOR_1,SENSOR_TOUCH); while (SENSOR_1 == 0){ // タッチセンサが押されていない間、繰り返す OnFwd(OUT_A+OUT_C); // モータAとモータC を正回転 } Off(OUT_A+OUT_C); } このように while は括弧内の条件が満たされている間、くり返し命令を実行します。 つまり // while ( 条件式 ) { 命令1; 命令2; … } // のように使います。 ところでこの例の場合、一度タッチセンサが押されて止まってしまうとそれでプログラムが終ってしまいます。そこで、センサが押される度に車が反転して(あるいは方向を変えて)進むというプログラムを作成してみましょう。 実はこのときも while を使うと簡単にできます。 task main() { SetSensor(SENSOR_1, SENSOR_TOUCH); while(true) { OnFwd(OUT_A+OUT_C) ; until (SENSOR_1 == 1) ; OnRev(OUT_A+OUT_C) ; Wait(50); // 0.5 秒間バックする OnFwd(OUT_A) ; Wait(150); // さらに A のモータだけを正回転させて // 1.5 秒間で方向転換させる // 時間は適当に調節してください OnFwd(OUT_C); // C のモータも正回転する } } この例のように条件式の中に true (真) を入れておくと常に条件が成り立っているので while の外の次の命令が実行されることはありません。つまり永遠に動作を繰り返します (これは無限ループとよばれます)。 もちろん while(true) の代わりに、while(1 + 2 == 3) と書いても同じ意味です。 一方ある回数だけ繰り返すには前のセクションで説明した repeat を使うのが便利です。 repeat ( 回数 ) { 命令1; 命令2; … } ★ 練習問題~ 机の上で動き回るロボットを作成しなさい。 ただし方向変換は机の端に来たときに行うこと。 *5.3 if を使った条件判断 [#fbaf7618] 上の例と同じ動きを if 文を使って書くこともできます。 task main() { SetSensor(SENSOR_1, SENSOR_TOUCH); OnFwd(OUT_A+OUT_C) ; while (true) { if (SENSOR_1 == 1 ) { OnRev(OUT_A+OUT_C) ; Wait(50); OnFwd(OUT_A); Wait(150); OnFwd(OUT_C) ; } } } この例のようにある条件を満たす時だけ命令を実行するには、 // if ( 条件式 ) { 命令1 ; 命令2 ; … } // とします。また、 // if ( 条件式 ) { 命令1 ; … } else { 命令2 ; … } // のように、さらに else を使えば、 条件式が成り立つ場合、成り立たない場合にそれぞれ実行する命令を一つの if ブロックの中で記述することができます。 *5.4 光センサを使う [#jf65b5ae] タッチセンサに慣れたら次は光センサに挑戦してみましょう。 ロボットの前に下向きで光センサを取り付け、 地面の明るさを判断して動作するロボットを作ってみましょう。 /* 黒い線を横切ったら止まるロボットのプログラム */ task main () { SetSensor(SENSOR_1, SENSOR_LIGHT); OnFwd(OUT_A+OUT_C); until (SENSOR_1 < 40) ; // センサの値が40未満(適当に調節)になるまで // 次の命令を実行しない Off(OUT_A+OUT_C); } &color(#000000,#ccccff){センサの値}; 光センサの場合、明るさによって 0 から 100 までの値が SENSOR_1 に代入されます (明るいほど大きな値)((rawモードでは0〜1023))。 現在の明るさは view ボタンを押すことによって測定することができます。 部屋の明るさにもよりますが、閾値 (threshold) を 40 くらいに設定しておけば、白と黒の区別はつくでしょう。 *5.5 光センサを使った応用例 (ライントレース) [#z7eab73f] 応用として、黒い線をトレースするプログラムを作ってみましょう。 以下のサンプル・プログラムでは、 光センサーが黒い線の上にあるかないかを判断して左右のモータを交互に動かしています (黒い線をトレースするというより、 黒と白の境界をトレースすると言った方がいいかも知れません)。 /* 黒い線をトレースするロボット のプログラム */ #define THRESHOLD 40 // 閾値 task main () { SetSensor(SENSOR_1, SENSOR_LIGHT); while (true) { if (SENSOR_1 < THRESHOLD) { // 黒線上にいるとき OnFwd(OUT_A); Off(OUT_C); } else { // 黒線から外れたとき Off(OUT_A); OnFwd(OUT_C); } } } ★ 練習問題~ 光センサを2つ使ってライントレースをするロボットを作りなさい。 ★ 練習問題~ インストの『Top Secret』を参考にモータをひとつだけ使ってライントレースをするロボットを作りなさい。 ★ 練習問題~ 上の例で、黒線上に光センサがある場合にロボットを直進させるよう、プログラムを変更しなさい。 ★ 練習問題~ 前輪操舵あるいは後輪操舵あるいは全輪操舵でライントレースするロボットを作成しなさい。 *5.6 タイマーについて [#timer] 上のライン・トレースのロボットはRCXのrunボタンか電源ボタンを押して止めるまで動き続けます。では、ある時間だけロボットを動かすにはどのようにすればよいでしょうか。実は「タイマー」という機能を使えば時間を容易に制御できます。 以下はタイマーを使って上のライン・トレースのプログラムを書き直したものです。 /* 黒い線をトレースするロボット のプログラム */ #define THRESHOLD 40 // 閾値 #define RUN_TIME 150 // トレースする時間(単位 1/10 秒) task main () { SetSensor(SENSOR_1, SENSOR_LIGHT); ClearTimer(0); // Timer(0) をリセット while ( Timer(0) <= RUN_TIME ) { // タイマー0がRUN_TIME(15秒)以下の時に繰り返す if (SENSOR_1 < THRESHOLD) { // 黒線上にいるとき OnFwd(OUT_A); Off(OUT_C); } else { // 黒線から外れたとき Off(OUT_A); OnFwd(OUT_C); } } Off(OUT_A+OUT_B); } このようにタイマーは時間を計るための機能で、ストップウォッチのようなものだと考えてください。また、時間のある値を読み取って条件を判断するのに使うという意味ではタッチ・センサや光センサと同じセンサの一種、つまり「時間センサ」と考えることもできます。 RCX では独立した4つのタイマーを使うことができます。 タイマーの値は 1/10 秒単位の整数値で、それぞれ Timer(0)〜Timer(3) で得ることができます。 タイマーをリセット (0に戻す) には、 ClearTimer(0); // タイマー 0 をリセット … ClearTimer(3); // タイマー 3 をリセット とします。この ClearTimer という命令はストップウォッチで言うと、リセットと同時にスタートボタンを押すようなものです。 さらに、RCX2 では精度のよい 1/100 秒単位のタイマ FastTimer(n) も使えます (ただし n=0,1,2,3)。 リセットするには同じように ClearTimer(n) を使います。 Wait の時間の単位が 1/100 秒であることを考慮すると、Timer よりもこの FastTimer を使った方が、時間の演算をするのにいちいち10をかけたり 10で割ったりしなくてもよいので、プログラムのミスが少なくなります。 このことは時間測定の精度が上がるという以上のメリットではないでしょうか。 ところで、このように RCX2 用の機能を使ったときには nqc でコンパイルする際に -Trcx2 というオプションをつける必要があります。 コンパイルの度にこのオプションをつけるのが面倒な場合は、 .bashrcあたりに NQC_OPTIONS="-Trcx2" export NQC_OPTIONS と書いておきましょう (bash の場合)。 ★ 練習問題~ ある地点から前進し、障害物に触れたらそのまま後進してもとの位置にもどるロボットを製作しなさい。 ★ 練習問題~ 前向きに光センサーを取り付けたロボットを作成し、 そのロボットがもっとも明るい方向 (例えば照らした懐中電灯の方向) を向くようにしなさい。 ★ 練習問題~ 前問で作成したロボットが、 懐中電灯の光を追いかけて進むようにプログラムを改良しなさい。 #navi(NQC入門)