*目次 [#x3aa4876]
#contents

*課題について [#t1601fb1]
課題2で用いたコースを利用し、C地点、D地点にそれぞれ3つずつ置かれた紙コップを下の図の星の地点に運搬する。またコップを積み重ねると得点が上がる。~
#ref(2016a/Member/maccha/Mission3/2016a-mission3.jpg,50%,コース)
得点等の詳細なルールは[[2016年前期/課題3:http://yakushi.shinshu-u.ac.jp/robotics/?2016a%2FMission3]]を参照していただきたい。

*作戦内容 [#h1f2ef59]
使用するロボットは2体で、1体は紙コップを積む役(以下ロボA)、もう1体は紙コップを運ぶ役(以下ロボB)を担う。~
ロボAはA地点からスタートし、Pの交差点、SP間のある地点(上図のコースの青い三角形)へとライントレースを行う。そして青い三角形の地点でロボBが運んできたコップを積み、積み終わったコップを星の地点に運ぶ。~
ロボBはB地点からスタートし、Rの交差点、Pの交差点、Sの交差点へとライントレースし、まずC地点へ向かいコップを1つ回収し、Sの交差点を通り青い三角形の地点へ運ぶ。その後D地点へと向かいコップを回収し青い三角形の地点へと運ぶ。これを繰り返し全てのコップを青い三角形まで運ぶ。

*ロボットについて [#x07350df]
+ロボAについて~
#ref(2016a/Member/maccha/Mission3/15420.jpg,70%,全体像1)
#ref(2016a/Member/maccha/Mission3/15421.jpg,70%,全体像2)
コップを持ち上げる役を担うので、コップを持ち上げる機構を考えた。~
#ref(2016a/Member/maccha/Mission3/S__18096133.jpg,70%,持ち上げるところ)
#ref(2016a/Member/maccha/Mission3/15422.jpg,70%,持ち上げるところ2)
上図(持ち上げるところ)の赤い矢印の向きにアームが動きながら、それと同時に上図(持ち上げるところ2)の機構でアーム部分が丸ごと上昇するようになっている。なお下降の際はアームが開くようになっている。アームは上昇、下降するため輪ゴムを介して可動するようにした。~
~
+ロボBについて
#ref(2016a/Member/maccha/Mission3/S__18096140.jpg,70%,全体像3)
#ref(2016a/Member/maccha/Mission3/S__18096141.jpg,70%,全体像4)
ライントレースをするように機体の中央部分に光センサを取り付けた。またコップを運ぶために前方部にパーツを付けた。

*プログラム [#zbb79b0d]
**基本の定義 [#z547a634]
以下はロボA、ロボBともに使った基本の定義である。
 #define turn_right_SENKAI OnFwd(OUT_A); OnRev(OUT_C);   //旋回
 #define turn_right OnFwd(OUT_A); Off(OUT_C);            //片方のみ動かす
 #define turn_left_SENKAI OnFwd(OUT_C); OnRev(OUT_A);    //旋回
 #define turn_left OnFwd(OUT_C); Off(OUT_A);             //片方のみ動かす
 #define go_forward(t) OnFwd(OUT_AC); Wait(t); Off(OUT_AC);
 #define Black 43     //これより小さかったら黒
 #define White 47     //これより大きかったら白
 #define STEP 1       //whileの一回の判断の時間
次に2体のロボット間での通信で用いたメッセージを示す。
 #define closs_P 1    //ロボAがcloss Pを曲がる許可
 #define GO_cupC 2    //Cにコップを取りに行く
 #define GO_cupD 3    //Dにコップを取りに行く
 #define COME_cupC 4  //Cからのコップを持って来い
 #define COME_cupD 5  //Dからのコップを持って来い

**ロボAのプログラム [#d7ccbaf0]
おおまかな流れは、~
A地点からスタートし交差点Pまでライントレース、その後ロボBから送られたメッセージcloss_Pを受け取るまで交差点Pで停止、closs_Pを受け取った後は青い三角形の地点まで進みコップを積み重ねる~
である。
+ライントレース等、移動に関するサブルーチン~
ライントレースの仕組みは課題2と同じなので、[[2016a/Member/maccha/Mission2:http://yakushi.shinshu-u.ac.jp/robotics/?2016a%2FMember%2Fmaccha%2FMission2]]
を参照していただきたい。以下にプログラムを示す。
 sub trace_line_for_closs()
 {
    ClearTimer(0);
    while (FastTimer(0) <  36) {
       if (SENSOR_2 <= Black) {            //黒
          turn_right_SENKAI; 
       } else if (SENSOR_2 < Black +2) {   //黒に近いグレー
          turn_right;
       } else if (SENSOR_2 > White -2) {   //白に近いグレー
          turn_left;
          ClearTimer(0);
       } else if (SENSOR_2 >= White) {     //白
          turn_left_SENKAI;
          ClearTimer(0);
       } else {                            //グレー
       OnFwd(OUT_AC);
       ClearTimer(0);
       }
       Wait(STEP);
    }
    Off(OUT_AC);
    PlaySound(SOUND_FAST_UP);
    Wait(100);
 }
これは交差点で停止するプログラム。
 sub trace_line()
 {
    while (true) {
       if (SENSOR_2 <= Black) {            //黒
          turn_right_SENKAI;
       } else if (SENSOR_2 < Black +2) {   //黒に近いグレー
          turn_right;
       } else if (SENSOR_2 > White -2) {   //白に近いグレー
          turn_left;
       } else if (SENSOR_2 >= White) {     //白
          turn_left_SENKAI;
       } else {                            //グレー
       OnFwd(OUT_AC);
       }
       Wait(STEP);
    }
 }
これは永遠にライントレースをするプログラム。
 sub crossP()
 {
    ClearTimer(0);
    while (FastTimer(0) <  120 ) {
       if (Message() == closs_P) {
          PlaySound(SOUND_UP);
          turn_left;
       } else {
          Off(OUT_AC);
          ClearTimer(0);
       }
       Wait(STEP);
    }
 }
これは交差点Pでメッセージcloss_Pを受け取ったら交差点Pを左折するプログラム。
+コップを積むサブルーチン~
コップが届いたことを知るために下図のようにタッチセンサを取り付けた。
#ref(2016a/Member/maccha/Mission3/S__18096138.jpg,70%,タッチセンサ)
まず、アームを昇降させる時間について定義した。
 #define age_sage 350     //上げたり下げたりする時間
コップを積むサブルーチンは、Cからのコップを受け取った時とDからのコップを受け取った時の2つのサブルーチンを組んだ。
 sub tumutumu_C()
 {
    int cupC = 0;
    OnFwd(OUT_B);          //放す&アーム下げる
    Wait(100);
    Off(OUT_B);
    while (cupC < 1) {
       if (SENSOR_1 == 1) {      //コップ触ってたら
          SendMessage(GO_cupD);      //Dに行ってこい
          PlaySound(SOUND_CLICK);
          Wait(200);             //少し待機
          OnRev(OUT_B);          //掴む&アーム上げる
          Wait(age_sage);
          Off(OUT_B);
          turn_right;            //右向く
          Wait(200);
          Off(OUT_AC);
          OnFwd(OUT_B);          //放す&アーム下げる
          Wait(age_sage);
          Off(OUT_B);
          while (SENSOR_2 > Black) {
             turn_left;
          }
          Off(OUT_AC); 
          SendMessage(COME_cupD);      //Dのコップを持って来い
          PlaySound(SOUND_CLICK);
          cupC++;
       } else {                  //コップが触ってなかったら
       Off(OUT_AC);
       }
       Wait(STEP);
    }
    OnRev(OUT_B);
    Wait(100);
 }
これはCのコップを積むプログラムである。まず、変数 cupC の役割はコップを積む作業が終わったら次の動作(サブルーチン)に移るようにすることである。また途中でコップを持った状態で turn_right があるが、これは下図のようにコップを積むためである。これは全て積み終わったあとにコップをゴール地点へ運ぶことを簡単にするためである。
#ref(2016a/Member/maccha/Mission3/Tumu.jpg,70%,コップを積む)
 sub tumutumu_D()
 {
    int cupD = 0;
    OnFwd(OUT_B);          //放す&アーム下げる
    Wait(100);
    Off(OUT_B);
    while (cupD < 1) {
       if (SENSOR_1 == 1) {      //コップ触ってたら
          SendMessage(GO_cupC);      //Cに行ってこい
          PlaySound(SOUND_CLICK);
          Wait(200);             //少し待機
          OnRev(OUT_B);          //掴む&アーム上げる
          Wait(age_sage);
          Off(OUT_B);
          turn_right;            //右向く
          Wait(200);
          Off(OUT_AC);
          OnFwd(OUT_B);          //放す&アーム下げる
          Wait(age_sage);
          Off(OUT_B);
          while (SENSOR_2 > Black) {
             turn_left;
          }
          Off(OUT_AC); 
          SendMessage(COME_cupC);      //Cのコップを持って来い
          PlaySound(SOUND_CLICK);
          cupD++;
       } else {                  //コップ触ってなかったら
       Off(OUT_AC);
       }
       Wait(STEP);
    }
    OnRev(OUT_B);
    Wait(100);
 }
これはDのコップを積むプログラムだが、ロボBとのメッセージ以外、変数 cupD の役割を含めCのコップを積むプログラムと同じである。
+全容~
 task main()
 {
    SetSensor(SENSOR_1, SENSOR_TOUCH);
    SetSensor(SENSOR_2, SENSOR_LIGHT);
    ClearMessage();
    int n = 0;                       //whileの終了に用いる
    go_forward(100);
    trace_line_for_closs();
    while (n < 1) {
       if (Message() == closs_P) {
          crossP();
          n++;
       } else {
          Off(OUT_AC);
       }
       Wait(STEP);
    }
    while (n < 2) {
       if (SENSOR_1 == 0) {     //ロボBが青い三角形の地点に着いているタイミングなので
          trace_line();
       } else {
          Off(OUT_AC);
          n++;
       }
       Wait(STEP);
    }
    repeat (3) {
       tumutumu_C();
       tumutumu_D();
    }
    turn_right;
    Wait(200);
    OnFwd(OUT_AC);
    Wait(400);
    Off(OUT_AC);
 }
**ロボBのプログラム [#b2405aa7]
おおまかな流れは、~
ライントレースで青い三角形とC、D地点を往復する~
だけである。特にコップを取る工夫はしていない。コップはエリア内であれば自由に置けたので下図の星のように置きロボットの動きにコップを合わせるようにした。
#ref(2016a/Member/maccha/Mission3/area_C.jpg,70%,C地点のコップ)
#ref(2016a/Member/maccha/Mission3/area_D.jpg,70%,D地点のコップ)
+ライントレースについて~
ライントレースの仕組みはロボAと同じく[[2016a/Member/maccha/Mission2:http://yakushi.shinshu-u.ac.jp/robotics/?2016a%2FMember%2Fmaccha%2FMission2]]を参照していただきたい。しかし今回は黒線の右側をトレースする(課題2と同じ)だけでなく、左側をトレースするよう、サブルーチンと定義を作った。
 sub trace_line_for_cross_R()
 {
    ClearTimer(0);
    while (FastTimer(0) <  36) {
       if (SENSOR_2 <= Black) {            //黒
          turn_right_SENKAI;
       } else if (SENSOR_2 < Black +2) {   //黒に近いグレー
          turn_right;
       } else if (SENSOR_2 > White -2) {   //白に近いグレー
          turn_left;
          ClearTimer(0);
       } else if (SENSOR_2 >= White) {     //白
          turn_left_SENKAI;
          ClearTimer(0);
       } else {                            //グレー
       OnFwd(OUT_AC);
       ClearTimer(0);
       }
       Wait(STEP);
    }
    Off(OUT_AC);
    PlaySound(SOUND_FAST_UP);
    Wait(100);
 }
これは右側をトレースし、交差点で止まるプログラム。
 sub trace_line_for_cross_L()
 {
    ClearTimer(0);
    while (FastTimer(0) <  36) {
       if (SENSOR_2 <= Black) {            //黒
          turn_left_SENKAI;
       } else if (SENSOR_2 < Black +2) {   //黒に近いグレー
          turn_left;
       } else if (SENSOR_2 > White -2) {   //白に近いグレー
          turn_right;
          ClearTimer(0);
       } else if (SENSOR_2 >= White) {     //白
          turn_right_SENKAI;
          ClearTimer(0);
       } else {                            //グレー
       OnFwd(OUT_AC);
       ClearTimer(0);
       }
       Wait(STEP);
    }
    Off(OUT_AC);
    PlaySound(SOUND_FAST_UP);
    Wait(100);
 }
これは左側をトレースし、交差点で止まるプログラム。
以下このように名前の最後が _R となっているものは右側をトレースするときに用いるもの、 _L となっているものは左側をトレースするときに用いるものである。
 sub trace_line_to_start_R()
 {
    ClearTimer(0);
    while (FastTimer(0) < 50) {
       if (SENSOR_2 <= Black) {            //黒
          turn_right_SENKAI;
       } else if (SENSOR_2 < Black +2) {   //黒に近いグレー
          turn_right;
       } else if (SENSOR_2 > White -2) {   //白に近いグレー
          turn_left;
       } else if (SENSOR_2 >= White) {     //白
          turn_left_SENKAI;
       } else {                            //グレー
       OnFwd(OUT_AC);
       }
       Wait(STEP);
    }
 }
これと、
 sub trace_line_to_start_L()
 {
    ClearTimer(0);
    while (FastTimer(0) < 50) {
       if (SENSOR_2 <= Black) {            //黒
          turn_left_SENKAI;
       } else if (SENSOR_2 < Black +2) {   //黒に近いグレー
          turn_left;
       } else if (SENSOR_2 > White -2) {   //白に近いグレー
          turn_right;
       } else if (SENSOR_2 >= White) {     //白
          turn_right_SENKAI;
       } else {                            //グレー
       OnFwd(OUT_AC);
       }
       Wait(STEP);
    }
 }
は交差点Sから青い三角形までライントレースをするプログラムである。
 #define cross_straight_R turn_left; Wait(50);
 #define cross_straight_L turn_right; Wait(50);
 #define cross_right turn_right; Wait(130);
 #define cross_left turn_left; Wait(130);
これらは上から交差点を直進(右側と左側の2パターン)、右折、左折するプログラムである。
 #define turn_time 400     //RからR or LからL (180°回転)
 #define turn_time_change 290      //RからL or LからR (90°ぐらい回転)
これらはプログラムの右に書いてあるように、トレースする部分を変える、または変えないように回転する時間である。
+C地点とD地点への往復について~
ここではサブルーチンではなく、タスクを作成した。これは他のサブルーチンを組み入れたいからである。以下にそのタスクを示すが、変数 cupC cupD はrepeatの外でとることで、C(D)のコップの何個目を持ちに行くかを判断することに用いることができ、1・2・3個目でそれぞれ違う動きをするようにした。また変数 n m はwhile文の制御に用いた。コップを取る順番を下図に示した。なおCの1個目はこのタスクとは別に取りに行くので、このタスクはDの1個目から取りに行く。
#ref(2016a/Member/maccha/Mission3/area_C_number.jpg,70%,C地点のコップ2)
#ref(2016a/Member/maccha/Mission3/area_D_number.jpg,70%,D地点のコップ2)
 task go_to_CD()
 {
    int cupC = 0;
    int cupD = 0;
    repeat(3){
       int n = 0;
       int m = 0;
       while (n < 1) {
          if(Message() == GO_cupD) {         //C貰ったからDに行ってこい
             PlaySound(SOUND_UP);
             OnRev(OUT_AC);
             Wait(250);
             Off(OUT_AC);
             turn_right;
             Wait(turn_time);           //LからLへ
             trace_line_for_cross_L();
             cross_left;
             trace_line_for_cross_L();
             if(cupD == 0) {
                turn_right;
                Wait(turn_time_change);
                trace_line_for_cross_R();
             }
             if(cupD == 1) {
                go_forward(50);
                trace_line_for_cross_R();
                turn_right;
                Wait(turn_time);          //180°回転
                trace_line_for_cross_R();
                cross_right;
                trace_line_for_cross_R();
             }
             if(cupD == 2) {
                while (SENSOR_2 > Black) {
                   OnFwd(OUT_AC);
                }
                turn_right;
                Wait(turn_time);
                while (SENSOR_2 > Black) {
                   OnFwd(OUT_AC);
                }
                cross_right;
                trace_line_for_cross_R();
             }
             cupD++;
             while (m < 1) {
                if(Message() == COME_cupD) {      //C置いたからD持って来い
                   PlaySound(SOUND_UP);
                   n++;
                   m++;
                   trace_line_to_start_R();
                } else {
                   Off(OUT_AC);
                }
                Wait(STEP);
             }
          } else {
          Off(OUT_AC);
          }
          Wait(STEP);
       }
       while (n < 2) {                       //ここからC
          if(Message() == GO_cupC) {         //D貰ったからCに行ってこい
             PlaySound(SOUND_UP);
             OnRev(OUT_AC);
             Wait(250);
             Off(OUT_AC);
             turn_left;
             Wait(turn_time);           //RからRへ
             trace_line_for_cross_R();
             cross_right;
             trace_line_for_cross_R();
             if(cupC == 0) {
                go_forward(50);
                trace_line_for_cross_L();
                turn_right;
                Wait(turn_time);          //180°回転
                trace_line_for_cross_L();
                cross_left;
                trace_line_for_cross_L();
             }
             if(cupC == 1) {
                while (SENSOR_2 > White) {
                   turn_left;
                }
                while (SENSOR_2 > Black) {
                   OnFwd(OUT_AC);
                }
                turn_right;
                Wait(turn_time);
                while (SENSOR_2 > Black) {
                   OnFwd(OUT_AC);
                }
                cross_left;
                trace_line_for_cross_L();
             }
             cupC++;
             while (m < 2) {
                if(Message() == COME_cupC) {      //D置いたからC持って来い
                   PlaySound(SOUND_UP);
                   n++;
                   m++;
                   trace_line_to_start_L();
                } else {
                   Off(OUT_AC);
                }
                Wait(STEP);
             }
          } else {
          Off(OUT_AC);
          }
          Wait(STEP);
       }
    }
 }
+全容
 task main()
 {
    SetSensor(SENSOR_2, SENSOR_LIGHT);
    ClearMessage();
    int cupC = 0;
    int cupD = 0;
    int n = 0;
    go_forward(50);
    repeat (2) {                       //スタートからPの交差点まで
       trace_line_for_cross_R();
       cross_straight_R;
    }
    trace_line_for_cross_R();          //Sの交差点まで
    cross_right;
    trace_line_for_cross_R();          //Cの1個目の回収
    turn_right;
    Wait(turn_time_change);            //90°旋回(RからLへ)
    while(SENSOR_2 > White) {
       OnFwd(OUT_AC);
    }
    Off(OUT_AC);
    trace_line_for_cross_L();
    cross_left;
    trace_line_to_start_L();
    SendMessage(closs_P);                   //用意できたから青い三角形に来て
    PlaySound(SOUND_CLICK);
    start go_to_CD;
 }
**本番で使ったプログラム [#hf840467]
本番前日の午後9時時点でロボットと上記のプログラムは出来上がっていたが、通信等の調整が間に合わないため、ロボBのみでCのコップを1つ運ぶプログラムを組むという苦渋の決断を下した。それを以下に示す。
 #define turn_right_SENKAI OnFwd(OUT_A); OnRev(OUT_C);   //旋回
 #define turn_right OnFwd(OUT_A); Off(OUT_C);            //片方のみ動かす
 #define turn_left_SENKAI OnFwd(OUT_C); OnRev(OUT_A);    //旋回
 #define turn_left OnFwd(OUT_C); Off(OUT_A);             //片方のみ動かす
 #define go_forward(t) OnFwd(OUT_AC); Wait(t); Off(OUT_AC);
 #define cross_straight_R turn_left; Wait(50);
 #define cross_straight_L turn_right; Wait(50);
 #define cross_right turn_right; Wait(160);
 #define cross_left turn_left; Wait(160);
 #define Black 43     //これより小さかったら黒
 #define White 52     //これより大きかったら白
 #define STEP 1       //一回の判断の時間
 #define turn_time 400     //RからR or LからL (180°回転)
 #define turn_time_change 300      //RからL or LからR (90°ぐらい回転)
 #define closs_P 1    //closs Pを曲がる許可
 #define GO_cupC 2    //Cを取りに行け
 #define GO_cupD 3    //Dを取りに行け
 #define COME_cupC 4  //Cを持って来い
 #define COME_cupD 5  //Dを持って来い

 sub trace_line_for_cross_R()
 {
    ClearTimer(0);
    while (FastTimer(0) <  32) {
       if (SENSOR_2 <= Black) {            //黒
          turn_right_SENKAI;
       } else if (SENSOR_2 < Black +2) {   //黒に近いグレー
          turn_right;
       } else if (SENSOR_2 > White -2) {   //白に近いグレー
          turn_left;
          ClearTimer(0);
       } else if (SENSOR_2 >= White) {     //白
          turn_left_SENKAI;
          ClearTimer(0);
       } else {                            //グレー
       OnFwd(OUT_AC);
       ClearTimer(0);
       }
       Wait(STEP);
    }
    Off(OUT_AC);
    PlaySound(SOUND_FAST_UP);
    Wait(100);
 }

 sub trace_line_for_cross_L()
 {
    ClearTimer(0);
    while (FastTimer(0) <  32) {
       if (SENSOR_2 <= Black) {            //黒
          turn_left_SENKAI;
       } else if (SENSOR_2 < Black +2) {   //黒に近いグレー
          turn_left;
       } else if (SENSOR_2 > White -2) {   //白に近いグレー
          turn_right;
          ClearTimer(0);
       } else if (SENSOR_2 >= White) {     //白
          turn_right_SENKAI;
          ClearTimer(0);
       } else {                            //グレー
       OnFwd(OUT_AC);
       ClearTimer(0);
       }
       Wait(STEP);
    }
    Off(OUT_AC);
    PlaySound(SOUND_FAST_UP);
    Wait(100);
 }

 sub trace_line_to_start_R()
 {
    ClearTimer(0);
    while (FastTimer(0) < 50) {           //要調整
       if (SENSOR_2 <= Black) {            //黒
          turn_right_SENKAI;
       } else if (SENSOR_2 < Black +2) {   //黒に近いグレー
          turn_right;
       } else if (SENSOR_2 > White -2) {   //白に近いグレー
          turn_left;
       } else if (SENSOR_2 >= White) {     //白
          turn_left_SENKAI;
       } else {                            //グレー
       OnFwd(OUT_AC);
       }
       Wait(STEP);
    }
 }

 sub trace_line_to_start_L()
 {
    ClearTimer(0);
    while (FastTimer(0) < 50) {           //要調整
       if (SENSOR_2 <= Black) {            //黒
          turn_left_SENKAI;
       } else if (SENSOR_2 < Black +2) {   //黒に近いグレー
          turn_left;
       } else if (SENSOR_2 > White -2) {   //白に近いグレー
          turn_right;
       } else if (SENSOR_2 >= White) {     //白
          turn_right_SENKAI;
       } else {                            //グレー
       OnFwd(OUT_AC);
       }
       Wait(STEP);
    }
 }

 task main()
 {
    SetSensor(SENSOR_2, SENSOR_LIGHT);
    ClearMessage();
    int cupC = 0;
    int cupD = 0;
    int n = 0;
    go_forward(50);
    repeat (2) {
       trace_line_for_cross_R();
       cross_straight_R;
    }
    trace_line_for_cross_R();
    cross_right;
    trace_line_for_cross_R();
    turn_right;
    Wait(turn_time_change);            //90°旋回(RからLへ)
    while(SENSOR_2 > White) {
       OnFwd(OUT_AC);
    }
    Off(OUT_AC);
    trace_line_for_cross_L();
    cross_left;
    trace_line_to_start_L();               //要確認
    turn_left;
    Wait(100);
    while(SENSOR_2 > White) {
       OnFwd(OUT_AC);
    }
    Off(OUT_AC);
 }

*反省 [#b52787f9]
今回の課題では、ロボットの作成とプログラムの作成は出来ていたが、それを試すだけの時間が足りなかった点、また最初にグループで意見がまとまった作戦に固持しすぎた(頭が固かった)ことが問題だったと思う。その結果本番では別のプログラムを用いたわけだが、これも失敗に終わってしまった。~
他の班の作品を見ていると、なかなか興味をそそられるものが多く、自分の班のロボットを改良するヒントが多く見つけられた。やはり問題はロボットの方であったと感じた。~
他の班の作品を見ていると、なかなか興味をそそられるものが多く、自分の班のロボットを改良するヒントが多く見つけられた。やはり問題は頭が固かったことであったと感じた。~
確かに今回の課題は失敗に終わってしまったが、得るものは多かったと思う。これからの学習に役立つと思う。

トップ   編集 差分 履歴 添付 複製 名前変更 リロード   新規 一覧 検索 最終更新   ヘルプ   最終更新のRSS