目次
#contents
* 課題2 [#v2998a0d]
下の図のようなコースを作成し、指定されたコースを走行し途中でゴール付近の空き缶にボールを当てるロボットを作成する。
#ref(./2018b-mission2.png,10)
今回、私が走行させるコースは第一コースで
ロボットを長方形X内におき、Aをスタート
Bを右折
Kで一時停止して左折
Jを直進
Iを直進
Hを左折
Gで一時停止して左折
Eで一時停止して右折
Lを経て正方形Y内に入って停止
ボールはロボットが弧KJIH上にある時にQ地点の空き缶に当てる
というものだ
図で表すと・・・
#ref(ko-su.png):
*ロボットの概要 [#g730a7c1]
ロボットの全体像↓(進行方向は写真右方向)
#ref(19651.jpg):
一つ目のモーターにボールを転がす機能を付け、二つ目と三つ目にタイヤを付けた。また、後方にタイヤを付け前回の課題1と同様に三輪車型のロボットにした。NXT本体は重心ができるだけモーターについているタイヤの上付近に来るようにした。しかし本体の両脇を計2本の棒でしか支えていないので不安定なのが反省点。
矢印の方向に棒を動かしボールを転がす↓
#ref(19651_LIV2.jpg):
光センサーと超音波センサー、後輪は以下の写真のように取り付けた。
#ref(19655_LI.jpg):
進行方向とは逆向きに超音波センサーを付けている。はじめは超音波センサーで空き缶を発探知した後に360度旋回してボールを当てる「だけ」と軽く考えていたが、あまかった。誤差が生じる原因となり、空き缶を精確に探知することはできたが旋回時に回りすぎたり回らなすぎたりしてボールが空き缶に当たらないことがあった。
*プログラム [#g58dc551]
使える光センサーが一つだけなので幅2cmの黒線のふちを光センサーで探知しながらトレースしていくようにした。
「白」「白と黒の境界線」「黒」で動きを変えるようにした。このときの光センサーの値はそれぞれ60より上、60以下50より上、50以下を示す(細かい値は下図を参照)。それぞれの動きは黒の領域に曲がる、直進、白の領域に曲がる という動きをする。そして「白」または「黒」の時間が150を超えると旋回に切り替わる様にプログラムした。
それぞれの光センサーの細かい値↓
#ref(line.png):
以上より
1,ラインの右端をトレースする関数
2,ラインの左端をトレースする関数
3,円周上を走行する関数
4,空き缶を探知しボールを当てる関数
合計4つの関数を使用しプログラムを作成した。
**ラインの右端をトレースする関数 [#r12b3ac8]
はじめの何秒(t_min間)かは交差点を判断しても停止せず、その時間の経過後は交差点判断をすると停止する関数となっている。
ここでそれぞれの関数で定義したt_start,t0,t1の説明をする。t_startは関数が始まった時間を記録する。t0は白or黒を探知し始めた時間を記録する。t1は曲がる動作をし始めた時間と直進を終えた時間を記録,更新する。
従って、t1-t0で白or黒を連続で探知し続けた時間を示す。
参考↓
#ref(./21594.jpg)
この図の黄色の丸番号と下記関数のコメントの丸番号が対応している。
void follow_line_migi(long t_min)
{
SetSensorLight(S1);
long t_start=CurrentTick(); //関数が始まった時間
long t0=CurrentTick(); //白or黒を探知し始めた時間
long t_max=150; //連続して黒の限界値
long t1=CurrentTick(); //曲がる動作をし始めた時間と直進を終えた時間
long t_kosa=200; //交差点の判断までの時間
while(t1-t_start<t_min){ //t_minの間交差点の判断をしない
if(SENSOR_1>60){
if(t1-t0<t_max){
OnFwd(OUT_C,40);Off(OUT_A); //少し白の部分に入ったら左折
}else{
OnFwd(OUT_C,20);OnRev(OUT_A,50); //大きく白の部分に出たら左旋回
}
}else if(SENSOR_1>50){
OnFwd(OUT_AC,30); //白と黒の境界線なので直進
t0=CurrentTick(); //��白or黒を探知していないのでt0更新
}else{
if(t1-t0<t_max){
OnFwd(OUT_A,40);Off(OUT_C); //少し黒の部分に入ったら右折
}else{
OnFwd(OUT_A,20);OnRev(OUT_C,50); //大きく黒の部分に出たら右旋回
}
}
t1=CurrentTick(); //� ̄�左折or直進が終わったので更新
}
PlaySound(SOUND_CLICK); //音を鳴らしここから交差点の判断をするようになる
while(true){
if(SENSOR_1>60){
if(t1-t0<t_max){
OnFwd(OUT_C,40);Off(OUT_A); //少し白の部分に入ったら左折
}else{
OnFwd(OUT_C,20);OnRev(OUT_A,50); //大きく白の部分に出たら左旋回
}
}else if(SENSOR_1>50){
OnFwd(OUT_AC,40); //白と黒の境界線なので直進
t0=CurrentTick(); //白or黒を探知していないのでt0更新
}else{
if(t1-t0<t_max){
OnFwd(OUT_A,40);Off(OUT_C); //少し黒の部分に入ったら右折
}else if(t1-t0<t_kosa){
OnFwd(OUT_A,20);OnRev(OUT_C,50); //大きく黒の部分に出たら右旋回
}else{
Off(OUT_AC);break; //�す�の時間が一定値以上になったら交差点と判断しwhileのループから抜ける
}
}
t1=CurrentTick(); //�1�左折or直進が終わったので更新
}
Off(OUT_AC); //�せ澆泙�
PlaySound(SOUND_CLICK); //音を鳴らし交差点と判断したことを知らせる
}
**ラインの左端をトレースする関数 [#da00466a]
「ラインの右端をトレースする関数」と同様に、はじめの何秒(t_min間)かは交差点を判断しても停止せず、その時間の経過後は交差点判断をすると停止する関数となっている。
t_startは関数が始まった時間を記録する。t0は白or黒を探知し始めた時間を記録する。t1は曲がる動作をし始めた時間と直進を終えた時間を記録,更新する。
上記の「ラインの右端をトレースする関数」にある参考画像の丸番号と下記関数の丸番号は左右が反転しているが対応している。
void follow_line_hidari(long t_min)
{
SetSensorLight(S1);
long t_start=CurrentTick(); //関数が始まった時間
long t0=CurrentTick(); //白or黒の時間を探知し始めた時間
long t_max=150; //連続して黒の限界値
long t1=CurrentTick(); //曲がる動作をし始めた時間と直進を終えた時間
long t_kosa=200;
while(t1-t_start<t_min){ //t_minの間交差点の判断をしない
if(SENSOR_1>60){
if(t1-t0<t_max){
OnFwd(OUT_A,40);Off(OUT_C); //少し白の部分に入ったら右折
}else{
OnFwd(OUT_A,20);OnRev(OUT_C,50); //大きく白の部分に出たら右旋回
}
}else if(SENSOR_1>50){
OnFwd(OUT_AC,40); //白と黒の境界線なので直進
t0=CurrentTick();
t0=CurrentTick(); //��白or黒を探知していないのでt0更新
}else{
if(t1-t0<t_max){
OnFwd(OUT_C,40);Off(OUT_A); //少し黒の部分に入ったら左折
}else{
OnFwd(OUT_C,20);OnRev(OUT_A,50); //大きく黒の部分に出たら左旋回
}
}
t1=CurrentTick(); //右左折or直進が終わったので更新
t1=CurrentTick(); //� ̄�左折or直進が終わったので更新
}
Off(OUT_AC);
PlaySound(SOUND_CLICK); //音を鳴らしここから交差点の判断をするようになる
while(true){
if(SENSOR_1>60){
if(t1-t0<t_max){
OnFwd(OUT_A,40);Off(OUT_C); //少し白の部分に入ったら右折
}else{
OnFwd(OUT_A,20);OnRev(OUT_C,50); //大きく白の部分に出たら右旋回
}
}else if(SENSOR_1>50){
OnFwd(OUT_AC,30); //白と黒の境界線なので直進
t0=CurrentTick();
t0=CurrentTick(); //白or黒を探知していないのでt0更新
}else{
if(t1-t0<t_max){
OnFwd(OUT_C,40);Off(OUT_A); //少し黒の部分に入ったら左折
}else if(t1-t0<t_kosa){
OnFwd(OUT_C,20);OnRev(OUT_A,50); //大きく黒の部分に出たら左旋回
}else{
Off(OUT_AC);break; //黒の時間が一定値以上になったら交差点と判断しwhileのループから抜ける
Off(OUT_AC);break; //�す�の時間が一定値以上になったら交差点と判断しwhileのループから抜ける
}
}
t1=CurrentTick(); //右左折or直進が終わったので更新
t1=CurrentTick(); //�1�左折or直進が終わったので更新
}
Off(OUT_AC); //止まる
Off(OUT_AC); //�せ澆泙�
PlaySound(SOUND_CLICK); //音を鳴らし交差点と判断したことを知らせる
}
**円周上を走行する関数 [#s78b5dee]
円周上を交差点判断しても停止せずに進む関数。上記の「ラインの左端をトレースする関数」「ラインの右端をトレースする関数」とほとんど同じ関数。しかし、円周上を走行中に「空き缶を探知しボールを当てる関数」を組み込むため、わかりやすくするためにこの関数を使用した。
t_startは関数が始まった時間を記録する。t0は白or黒を探知し始めた時間を記録する。t1は曲がる動作をし始めた時間と直進を終えた時間を記録,更新する。
void follow_line_ensyu(long t_min)
{
SetSensorLight(S1);
long t_start=CurrentTick(); //関数が始まった時間
long t0=CurrentTick(); //白or黒の時間を探知し始めた時間
long t_max=150; //連続して黒の限界値
long t_kousa=200; //交差点判断
long t1=CurrentTick(); //曲がる動作をし始めた時間と直進を終えた時間
while(t1-t_start<t_min){
if(SENSOR_1>60){
if(t1-t0<t_max){
OnFwd(OUT_A,40);Off(OUT_C);
}else{
OnFwd(OUT_A,30);OnRev(OUT_C,60);
}
}else if(SENSOR_1>50){
OnFwd(OUT_AC,30);
t0=CurrentTick();
}else{
if(t1-t0<t_max){
OnFwd(OUT_C,40);Off(OUT_A);
}else if(t1-t0<t_kousa){
OnFwd(OUT_C,30);OnRev(OUT_A,50);
}else{
OnFwd(OUT_AC,40);Wait(200);PlaySound(SOUND_CLICK);
}
}
t1=CurrentTick();
}
Off(OUT_AC);
PlaySound(SOUND_CLICK);
}
**空き缶を探知しボールを当てる関数 [#sd91fe1c]
超音波センサーで空き缶を探知しその方向を向いてボールを当てる関数。
void search_direction()
{
ResetTachoCount(OUT_ABC); //モーターの角度をリセット
OnFwd(OUT_A,50);
OnRev(OUT_C,50);
SetSensorLowspeed(S4); //端子4に超音波センサー
int d1;
d1=1000; //最小距離を1000と定義する
long t0=CurrentTick();
long t1=CurrentTick();
long t2=CurrentTick();
while(t2-t0<=3570){ //3570の間回り続ける
t2=CurrentTick();
if(SensorUS(S4)<d1){ //センサーで探知し最小距離より短い場合
d1=SensorUS(S4); //最小値を再定義
PlaySound(SOUND_CLICK);
t1=CurrentTick();
}
t2=CurrentTick();
}
t0=CurrentTick();
Off(OUT_AC);
OnFwd(OUT_C,50);
OnRev(OUT_A,50);
Wait(t0-t1); //センサーで最後に最小値のd1を探知した位置を向く
Off(OUT_AC);
Wait(1000);
RotateMotorEx(OUT_AC,50,360,100,true,false); //ボールを転がす
RotateMotor(OUT_B,30,60);
Wait(5000);
RotateMotor(OUT_B,50,-60);
RotateMotorEx(OUT_AC,50,380,100,true,false);
}
***没案 [#ca601e09]
はじめに作った「空き缶を探知しボールを当てる関数」。先生に指摘され精確性を欠くことが発覚したため没になった。
void search_direction()
{
OnFwd(OUT_A,50);
OnRev(OUT_C,50);
SetSensorLowspeed(S4);
int d1;
d1=1000;
long t0=CurrentTick();
long t1=CurrentTick();
while(t1-t0<3570){
if(SensorUS(S4)<d1){
d1=SensorUS(S4);
PlaySound(SOUND_CLICK);
}
t1=CurrentTick();
}
while(true){
if(SensorUS(S4)==d1){
PlaySound(SOUND_CLICK);
Off(OUT_AC);
break;
}
}
}
なぜ精確性を欠くかというと、二つ目のwhile文にある"if(SensorUS(S4)==d1)"が最大の原因。この一文の意味は「もしセンサーの値が"全く"同じなら」という意味になる。この"全く"というものが曲者で、1mmでもずれていたら実行されないことを示している。ロボットは動いているので"全く"同じことはほぼあり得ないのでほとんど実行されないために、この関数は精確性を欠くのである。
** 完成したプログラム [#r2c92666]
task main()
{
follow_line_migi(1); //A~Bの右を進む
OnFwd(OUT_AC,50); //車線変更
Wait(200);
follow_line_hidari(1); //B~Kの左を進む
PlaySound(SOUND_CLICK);
Wait(1000); //Kにて一時停止
OnFwd(OUT_C,50);
Wait(500);
follow_line_ensyu(15000); //円周回る
search_direction(); //缶にボール当てる
follow_line_hidari(5000); //〜Gまで進む
Wait(1000); //Gにて一時停止
OnFwd(OUT_C,50); //Gを左折
Wait(500);
follow_line_migi(1); //G~Eの右を進む
follow_line_migi(15000);
Wait(1000); //Eにて一時停止
follow_line_migi(3000); //E~Lの右を進む
Wait(1000);
OnFwd(OUT_AC,50);
Wait(1000);
Off(OUT_AC);
OnFwd(OUT_A,50);
Wait(2000);
Off(OUT_AC);
}
*まとめ [#n5faf5d7]
**結果 [#f3dce85a]
・結論からいうと、発表日に発表することが出来なかった。
・発表日当日、先生からの指摘により「空き缶を探知しボールを当てる関数」の探知の仕方が精確性を欠く事が発覚。
・上記されている「空き缶を探知しボールを当てる関数」は当日の講義後に書き直したもの。
・その他の関数の動作にあまり不備はなかったが電池の消耗が激しかった。
**考察 [#rf327cfc]
・「空き缶を探知しボールを当てる関数」を作る際先生がおっしゃっていたことを正確に理解していなかったので、完璧に理解するまで先生に質問すべきだった。
・電池の消耗が激しかった。機体かプログラムに工夫を加えればもう少し電池の消耗の軽減できたと考えられる。
・NXT本体の支えが非常に不安定でちょっとした振動でバランスを崩しかけてしまうことがあったので改良が必要。また限られた部品をいかに工夫するかが今後の課題だと考えられる。
**感想 [#a3f4c67a]
今回は授業日の午前中にプログラムが完成し課題も達成していた。しかし、上記にあるように「空き缶を探知しボールを当てる関数」がうまく作動せず先生からの指摘によりその関数だけ作り直すことになり発表することが出来なかった。また、悪い意味で手本になってしまった。これらより前回以上に悔しさが募った。次回は絶対に発表したい。