*ライントレーサーをつくろう! [#le2c373e] **目次 [#v84fa079] #contents **ロボット紹介 [#oead820a] ***黒い線を辿る構造 [#x9d24dc6] 今回のロボットは光センサ1つ、カラーセンサ1つを使用し黒い線の上を動くものです。&br; 作られるプログラムによって、二つのセンサで黒い線を挟む方法と一つのセンサで動かす方法が考えられます。&br; 挟む方法では次の図のようになります。&br; #ref(2011b/A4/課題2左/sensa_1.gif,100%,センサの図1) 一つのセンサで黒い線をたどるには次の図のようになります。 #ref(2011b/A4/課題2左/sensa_2.gif,100%,センサの図2) 今回二人ともこちらのやり方に近いですが微妙に違ったりします。&br; 詳しくはプログラムを ***ロボットの構造 [#mcf74e21] 基本的な構造は三輪車で中心に光センサ、少し右前にずれてカラーセンサがついています。今回は二人とも線を挟まないプログラムを作ったためこの様なセンサの配置となりました。&br; #ref(2011b/A4/課題2左/sensa_r.jpg,100%,センサの写真) 今回のロボットの特徴は本当にどうでもいいことなのですが見た目です。機能性よりはるかに見た目を大事にしました。&br; 今回説明書を見ないで作ったのでキャスターも説明書のものと違います。部品数は少なくなりましたが少し車体が前に傾いてしまいます。 #ref(2011b/A4/課題2左/kyasuta_r.jpg,100%,キャスターの写真) 最初は説明書のロボットより軽量で強度のあるものを作りましたが線を探しながら進む姿に創作意欲が・・・&br; テーマは「生き物」です。&br; #ref(2011b/A4/課題2左/r_r.jpg,100%,ロボットの外形) 今回のロボットのテーマをクリアするために目を付けました。NETに入っていた発光ダイオードも活用し目が光る仕組みにしました。&br; 特にこのような姿である必要はまったくありませんがせっかくなのでかっこいいものをと思いこのような姿で走らすことにしました。 ***このロボットの問題点と改善点 [#j5adfb40] -重心が少し後ろに偏っていたため車輪が空転することがありました。この解決策としてNXT本体を少し前に出すことにしました。これにより空転することがなくなりました。 -今回のコースの中にありませんが進行方向右側への鋭角カーブは光センサがカーブにさしかかる前にカラーセンサが反応してしまいます。この解決策はプログラムにより鋭角カーブであることを判断するほかに、物理的解決策としてはカラーセンサの位置を変えることだと思います。しかし今回のコース内に鋭角カーブが存在しないため今回とくに対策を講じていません。 -目が光る分電池の減りが多少早くなるのではと思ったのですが、光らせるために使う電力は微々たるものなのであまり気にすることはないようです。今回は見た目をよくするために多少犠牲にしても仕方ないと思っています。 -実際に走らせて大きな問題となったのはコースの紙が何回も折ることにより所々盛り上がりセンサの値が大きくずれてしまうことです。解決策としてロボットで紙を抑えることにしました。 #ref(2011b/A4/課題2左/ba_r.jpg,100%,紙を抑えるバーの写真) これにより多少ではありますがセンサの誤認知を防げるようになりました。 **コース解説 [#yd121929] 今回A4チームで作ったコースは次のコースです。 #ref(2011b/A4/課題2左/ko_su.jpg,100%,コースの写真) 直線的なコースです。&br; 形的には簡単なコースといえるのではないでしょうか。しかし、線が近いところがありカラーセンサが横に広いこのロボットには難しいところもあります。&br; **プログラム紹介 [#v7d4321f] 今回の課題はロボットよりプログラムの勝負だと思います。中村と志村でお互い違う黒線の追い方をしています。 ***中村 (右回り、近道)[#h0689ae2] #define PP 50 #define BORDER 40 #define right90 500 #define left90 500 int ROT=300; long time; int isblack=1; int first_right=1; int rottime; int taskcnt=0; sub r_force(int rot){ OnFwd(OUT_C,50); OnRev(OUT_A,50); Wait(rot); Off(OUT_AC); rottime=rot; } sub l_force(int rot){ OnFwd(OUT_A,50); OnRev(OUT_C,50); Wait(rot); Off(OUT_AC); rottime=rot; } sub left(int rot){ time=CurrentTick(); OnFwd(OUT_A,50); OnRev(OUT_C,50); while(!isblack&&CurrentTick()-time<rot){ if(Sensor(IN_1)<BORDER){ first_right=0; isblack=1; rottime=CurrentTick()-time; } } Off(OUT_AC); } sub right(int rot){ time=CurrentTick(); OnFwd(OUT_C,50); OnRev(OUT_A,50); while(!isblack&&CurrentTick()-time<rot){ if(Sensor(IN_1)<BORDER){ first_right=1; isblack=1; rottime=CurrentTick()-time; } } Off(OUT_AC); } sub short_path(){ OnFwd(OUT_C,75); OnFwd(OUT_A,30); Wait(1500); Off(OUT_AC); taskcnt++; } sub Rot(){ int i; int bkcnt; int bkflg=0; bkcnt=0; switch(taskcnt){ case 1: PlayTone(1000,30); Wait(30); for(i=0;i<50;i++){ PlayTone(1000-i*20,5); Wait(5); } right(ROT*5); r_force(300); while(true){ OnFwd(OUT_AC,30); if(Sensor(IN_1)>BORDER){ isblack=0; left(ROT*3); PlayTone(1000,10); } if(!bkflg&&Sensor(IN_2)<BORDER){ bkflg=1; bkcnt++; } if(bkflg&&Sensor(IN_2)>BORDER){ bkflg=0; } if(bkcnt==2){ OnFwd(OUT_AC,50); Wait(500); Off(OUT_AC); r_force(right90); first_right=1; ROT=200; break; } } break; case 2: PlayTone(2000,30); for(i=0;i>50;i++){ PlayTone(2000-i*20,5); Wait(5); } Wait(30); r_force(200); right(ROT*10); r_force(700); while(true){ OnFwd(OUT_AC,30); if(Sensor(IN_1)>BORDER){ isblack=0; left(ROT*3); PlayTone(1000,10); } if(!bkflg&&Sensor(IN_2)<BORDER){ bkflg=1; bkcnt++; } if(bkflg&&Sensor(IN_2)>BORDER){ bkflg=0; } if(bkcnt==2){ OnFwd(OUT_AC,50); Wait(500); Off(OUT_AC); r_force(right90); first_right=0; break; } } break; } } task main(){ int i,j; time=CurrentTick(); SetSensorLight(IN_1); SetSensorColorRed(IN_2); while(true){ OnFwd(OUT_AC,50); if(Sensor(IN_1)>BORDER){ Wait(25); isblack=0; for(j=1;j<=2;j++){ if(isblack==0){ if(first_right){ right(ROT+PP*j); left((ROT+PP*j)*2); } else{ left(ROT+PP*j); right((ROT+PP*j)*2); } if(j==2){ taskcnt++; Rot(); } PlayTone(50,30); } } } if(taskcnt==2&&Sensor(IN_2)<BORDER){ short_path(); } } } ***志村(左回り、直角) [#qefd2dd7] 今回私は、通常光センサ1つを使い黒い線を辿るプログラムにしました。&br; 黒の値と白の値の間を通るプログラムです。; #ref(2011b/A4/課題2左/sensa_s.gif,100%,志村のセンサの図) こうすることでより直線的に動くことができます。&br; カラーセンサは交差点とロータリーの認識に使いました。&br; 変数を使用し交差点、ロータリーを何個過ぎたかを数えロボットの対応を変えています。&br; 今回、黒 白の値に測定値を使うことでその場でプログラムの微調整をしなくて済むようにしました。&br; 今回のプログラムのフローチャートみたいなものを書きました。プログラムの流れがわかると思います。&br; #ref(2011b/A4/課題2左/furo-tya-t_s.gif,40%,フローチャートの図) 今回の変数は&br; ro_ko (交差点とロータリーをあわせて何回目の交差かをロボットがカウントします。7回目の交差点はカーブ終わり直後でロボットがの交差点への入射角が傾いているため個別の指定をしています。この関数のおかげでコース特性を克服できます。)&br; ro_tari (ロータリー内にいる間はこの関数の値が増えます。このことで何回目の出口を出るかを指定できます。)&br; ro_deru (この変数は二回目のロータリー出口の直後の行く手をふさぐカーブの克服に使いました。ロータリーに出会うとカウントを1増やします。)&br; です。&br; 音を鳴らすことによりロボットが今どのプログラムを実行しているかがわかります。目の光の強さを変えることでもロボットの状態を判断できます。見た目だけじゃないよ! #define run_sokudo1 40 //速度1の設定 #define run_sokudo2 75 //速度2の設定 #define run_sokudo3 75 //速度3の設定 #define run_sokudo4 40 //速度4の設定 #define run_sokudo5 100 //速度5の設定 int light_b; //光センサの黒の値 int light_w; //光センサの白の値 int color_w; //カラーセンサの白の値 int color_b; //カラーセンサの黒の値 #define VOL 4 //音量4 #define oto_do4 262 //ドの音 #define oto_mi4 294 //ミの音 #define oto_so4 392 //ソの音 #define time_run 1 //一回に走る時間 int ro_ko; //交差点とロータリーの回数の変数を定義 int ro_tari; //ロータリーに入ったことを示す変数 int ro_deru; //何回目のロータリーか示す変数 int serach_l; //何回目の右ずれか示す変数 int serach_r; //何回目の左ずれか示す変数 sub serach_right() //ずれた時の修正サブルーチン(右) { OnFwd(OUT_C,run_sokudo4); //モーターCをrun_sokudo4で動かす。 Wait(time_run); //time_run/1000秒動かす Off(OUT_C); //Cを止める } sub serach_left() //ずれた時の修正サブルーチン(左) { OnFwd(OUT_A,run_sokudo4); //モーターAをrun_sokudo4で動かす。 Wait(time_run); //time_run/1000秒動かす Off(OUT_A); //Aを止める } task main() { light_w=0; //変数の初期化 light_b=0; //変数の初期化 color_b=0; //変数の初期化 color_w=0; //変数の初期化 ro_tari=0; //変数の初期化 ro_ko=0; //変数の初期化 ro_deru=0; //変数の初期化 serach_l=0; //変数の初期化 serach_r=0; //変数の初期化 SetSensorLight(IN_1); // ポート1に光センサ SetSensorColorRed(IN_2); // ポート2にカラーセンサ PlayToneEx(oto_mi4, 4000, VOL, FALSE);//ミの音を4000/1000秒鳴らす Wait(6000); //6000/1000秒待つ light_w=Sensor(IN_1)-4; //光センサの白の値に測定値-4を代入 color_w=Sensor(IN_2)-10; //カラーセンサの白の値に測定値-10を代入 PlayToneEx(oto_mi4, 4000, VOL, FALSE);//ミの音を4000/1000秒鳴らす Wait(6000); //6000/1000秒待つ light_b=Sensor(IN_1)+3; //光センサの黒の値に測定値+3を代入 color_b=Sensor(IN_2)+7; //カラーセンサの黒の値に測定値+7を代入 PlayToneEx(oto_mi4, 4000, VOL, FALSE);//ミの音を4000/1000秒鳴らす Wait(6000); //6000/1000秒待つ PlayToneEx(oto_do4, 1000, VOL, FALSE);//ドの音を1000/1000秒鳴らす(スタート合図開始) OnFwd(OUT_B,20); //目を光らせる(暗い) Wait(1100); //1100/1000秒待つ PlayToneEx(oto_do4, 1000, VOL, FALSE);//ドの音を1000/1000秒鳴らす OnFwd(OUT_B,60); //目を光らせる(明るい) Wait(1100); //1100/1000秒待つ PlayToneEx(oto_do4, 1000, VOL, FALSE);//ドの音を1000/1000秒鳴らす OnFwd(OUT_B,20); //目を光らせる(暗い) Wait(1100); //1100/1000秒待つ PlayToneEx(oto_so4, 2000, VOL, FALSE);//スタート ソの音を2000/1000秒鳴らす while(true) //無限ループ { if(Sensor(IN_2)<=color_b) //ロータリーor交差点(カラーセンサが黒を認識) { ro_ko=ro_ko+1; //ロータリーと交差点をカウント PlayToneEx(oto_mi4, 100, VOL, FALSE); //ミの音を100/1000秒鳴らす if( ro_tari!=1 && ro_tari!=2) //ロータリー内でない時にカラーセンサが反応 { ro_tari=0; //ロータリー内であることを示す変数を0に戻す。 if(ro_ko==7) //コース特性克服のためのif文(7回目の交差点) { OnFwd(OUT_A,run_sokudo3); //モーターAをrun_sokudo3で動かす。 Wait(200); //200/1000秒動かす Off(OUT_A); //Aを止める } else //通常の交差点での動き { OnFwd(OUT_A,run_sokudo1); //モーターAをrun_sokudo1で動かす。 Wait(85); //85/1000秒動かす Off(OUT_A); //Aを止める OnFwd(OUT_AC,run_sokudo2); //モーターACをrun_sokudo2で動かす。 Wait(180); //180/1000秒動かす Off(OUT_AC); //ACを止める } if(Sensor(IN_1)<light_b+20) //交差点 { PlayToneEx(oto_do4, 500, VOL, FALSE);//ドの音を500/1000秒鳴らす } else //ロータリー { ro_tari=0; //ロータリー内であることを示す変数を0に戻す。 ro_tari=ro_tari+1; //ロータリー内であることを示す変数に1をたす。 ro_deru=ro_deru+1; //何回目のロータリーか示す変数に1をたす PlayToneEx(oto_so4, 500, VOL, FALSE);//ソの音を500/1000秒鳴らす OnRev(OUT_A,run_sokudo5); //モーターAをrun_sokudo5で動かす。 OnFwd(OUT_C,run_sokudo5); //モーターCをrun_sokudo5で動かす。 Wait(340); //340/1000秒動かす Off(OUT_AC); //ACを止める } } else //ロータリー内で1回目と2回目のカラーセンサの反応 { if(ro_tari==1) //ロータリー内の1回目のカラーセンサの反応(無視) { OnFwd(OUT_AC,run_sokudo5); //モーターACをrun_sokudo5で動かす。 Wait(100); //100/1000秒動かす Off(OUT_AC); //ACを止める ro_tari=ro_tari+1; //ロータリー内であることを示す変数に1をたす。 } else //ロータリー内の2回目のカラーセンサの反応(ロータリーから出る) { ro_tari=0; //ロータリー内であることを示す変数を0に戻す。 if(ro_deru==1) //コース特性克服のためのif文(1回目のロータリーを出るとき。) { OnFwd(OUT_C,run_sokudo5); //Cをrun_sokudo5で動かす。 Wait(400); //400/1000秒動かす Off(OUT_C); //Cを止める } else //2回目のロータリーを出るとき { OnFwd(OUT_C,run_sokudo5); //Cをrun_sokudo5で動かす。 Wait(1300); //1300/1000秒動かす Off(OUT_C); //Cを止める } } } } else //カラーセンサが黒と反応していない時 { if (Sensor(IN_1)>light_b && Sensor(IN_1)<light_w)//黒白 { serach_l=0; //白のずれのカウントを0に戻す。 OnFwd(OUT_B,80); //目を光らせる(明るい) OnFwd(OUT_AC,run_sokudo5); //モーターACをrun_sokudo5で動かす。 Wait(20); //20/1000秒動かす Off(OUT_AC); //ACを止める } else //線からずれている { if(Sensor(IN_1)<light_b) //黒 { OnFwd(OUT_B,20); //目を光らせる(暗い) serach_right(); //サブルーチン } else //白 { serach_l=serach_l+1; //何回目の白かカウントする if(serach_l>800) //大きくずれた時Aの出力をあげる。 { OnFwd(OUT_B,80); //目を光らせる(明るい) OnFwd(OUT_A,run_sokudo5); //Aのモーターをrun_sokudo5で動かす。 Wait(time_run); //time_run秒走らせる Off(OUT_A); //Aを止める } else //少しのずれ { OnFwd(OUT_B,20); //目を光らせる(暗い) serach_left(); //サブルーチン } } } } } } このプログラムの問題点 -速さを追及するがゆえに正確さがおろそかになりがちなプログラムです。run_sokudo5を小さい値にすることで正確さは補えますが、100であることにこだわりたいと思います。1周34秒は魅力的 -黒 白の値を測定値にしたことで測る場所により多少の誤差が出てしまいます。スイッチを押した後に測定のためにロボットを動かす必要も出てきました。 -コース特性を克服するためのif文のせいでこのコース以外ではうまく動きませんがif文をなくすことで直進して十字路に進入でき進行方向を妨げるカーブが存在しないコースであればクリアできると思います。 #ref(2011b/A4/課題2左/hyousiki.gif,100%,ダメな物の図) - **感想・考察 [#yadc1790] ***中村 [#t8a635b5] ***志村 [#g4cad49d] 今回の課題で私が苦労したところはロータリーと交差点交差点をどう判別するか、ロータリーに進入した後にどうやって1つ目の出口を無視して2つ目の出口で抜け出すかでした。今回私は変数を使い交差点とロータリーの数を数えることでこの問題を解消しました。一回成功しても次に失敗することもありプログラム上での修正に苦労しました。今回の課題で思ったことはNXTらしさがなかなか出すことができなかったと思います。1つ挙げるならスピードはNXTだからできたことなのかなぁとも思います。 **コメント [#nd3e1c28] 気になったこと等ありましたらお気軽にコメントして下さい。 #comment