2010年6月3日 星期四

使用NXC撰寫NXT程式---第八章 再探循跡車

使用NXC撰寫NXT程式---第八章 再探循跡車


我們在第六章有寫過循跡車的程式如下:

//簡易循跡車
#define _LIGHT 45
#define _POWER 50
#define _TURN_TIME 100
task main()
{
  SetSensorLight(IN_3); //或用 S3
  while (true)
  {
    if (SENSOR_3<=_LIGHT) //左轉修正
    {
      NumOut(5,20,SENSOR_3,true);
      Off(OUT_C);
      OnFwd(OUT_B, _POWER); 
      Wait(_TURN_TIME);
    }
    else //右轉修正
    {
      NumOut(5,20,SENSOR_3,true);
      Off(OUT_B);
      OnFwd(OUT_C, _POWER); 
      Wait(_TURN_TIME);
    }
  } //end while
}

這個程式差強人意,走起路來歪七扭八怪彆扭的,我們接下來要使用PID控制的方式來讓循跡車走的更優雅。

關於PID控制,請先閱讀這篇 J. Sluka 所寫的文章,底下的作法仍根據這篇說明而來。

我們假設光感在全白的底色上所讀到的數據是50,而在黑色軌跡上的讀數是40,
上述程式的做法,是以這兩者的平均值45為判斷標準,
若機器人是沿黑線的左邊行進,小於等於45,表示偏向黑色軌跡,機器人需往左修正;
反之則以反方向修正。

不是左轉,就是右轉,以45為判斷的標準,典型的二元論。

難道沒有模糊地帶?

要讓機器人更聰明,必需讓它了解不僅只有這兩種情況。

假設我們的光感在不同的狀態下所讀到的數據如下:






至少現在我們可以有三種選擇:
1.小於45且大於等於40:向左修正
2.大於45且小於等於50:向右修正
3.等於45:直線前進

機器人現在多了一項反應了。

仔細一想,我們可以加入更多的情況讓機器人辨示,以做出更多不同的反應。




如上圖,我們現在可以有五種選擇:
1.在40到42之間:向左修正,且要修正大一點
2.在42到45之間:向左修正,但修正小一點即可
3.等於45:直線前進
4.在45到48之間:向右修正,但修正小一點即可
5.在48到50之間:向右修正,且要修正大一點

修正角度的大小,可以用馬達的輸出馬力來控制。

以此細分下去,我們可以讓光感讀到的數據,對映一個馬達輸出的馬力,
最簡單的算法,就是以線性等比例的方式來換算。

我們設定當光感讀數是45時,B、C馬達皆以50%的馬力輸出(即走直線),
而在兩個極端點(即光感讀數為50及40時),B、C兩個馬達的輸出為100%及0(或0及100%),
我們可以畫出底下的函數圖形:





這條線的方程式為:
Power=10*(Light-45)+50
10即為斜率,在PID領域有個術語,稱為比例常數。

如果上面的函數圖形代表C馬達,
那我們可以讓B馬達的斜率為負,如下圖。



現在我們可以寫程式了。



//有比例控制的循跡車
//-----------------------------------------------
//請依您的情況調整Kp、offset與Tp值
//我的情況是Kp=2 、 offset=47 、 Tp=80 時走的最快
//而Kp=1 、 offset=47 、 Tp=50 時走的最平順
//-----------------------------------------------


task main()
{

    int Kp = 10;//比例常數,調小此數值會走的比較平順
    int offset = 45;//不修正時的光感值,依實際情況設定
    int Tp = 50;//不修正時的馬力,愈大愈快,但要配合Kp

    int Light;//光感讀數
    int PowerB, PowerC;//B、C馬達的輸出馬力

    SetSensorLight(IN_3); //或用 S3

    while (true)
    {

      Light = SENSOR_3 ;//讀取光感數據
      PowerC = (Kp*(Light-offset))+Tp;//代入方程式
      PowerB = (-Kp*(Light-offset))+Tp;//另一個馬達的斜率是負的
      TextOut(5,40,NumToStr(Light),true);
      TextOut(5,20,NumToStr(PowerB));
      TextOut(75,20,NumToStr(PowerC));

      OnFwd(OUT_B,PowerB);
      OnFwd(OUT_C,PowerC);

    }

}


有可能在您調整參數的過程中,或是光感也有可能偵測到小於40或大於50的情況下,讓PowerC或PowerC大於100或小於0。雖然NXT對馬達的Power有自我保護,且NXC對小於0的Power值會自動逆轉,但程式師應該要對任何可能發生的意外負責,仔細考慮各種可能情況,否則,就會像福音戰士中的初號機一樣「暴走」,導致不可預期的失控。當然設計越複雜,就越難保證不失控。但從小事養成謹慎的態度是很好且必要的。我們的輸出入設計只限制在一個合理的範圍:在40<=Light<=50時,-100<=Power<=100。(Power為負值時逆轉)其他情況我們並沒有設計讓機器人如何反應,所以,我們要在程式碼中,好好的規範一下。底下是較有保障的程式。
註:
由於小弟偷懶,事隔1年半才又著手寫這篇文章,之前的程式不曉得誤了多少人,幸好不會讓NXT暴走或壞掉。可見寫網路文章也得戒慎恐懼啊!




//有比例控制且較穩定的循跡車
//-----------------------------------------------
//請依您的情況調整Kp、offset與Tp值
//我的情況是Kp=2 、 offset=47 、 Tp=80 時走的最快
//而Kp=1 、 offset=47 、 Tp=50 時走的最平順
//-----------------------------------------------


task main()
{
    int LightMax = 50;//光感讀入的最大值
    int LightMin = 40;//光感讀入的最小值
    int PowerMax = 100;//Power的最大值
    int PowerMin = -100;//Power的最小值
    int Kp = 10;//比例常數,調小此數值會走的比較平順
    int offset = (LightMax+LightMin)/2;//不修正時的光感值,依實際情況設定
    int Tp = 50;//不修正時的馬力,愈大愈快,但要配合Kp

    int Light;//光感讀數
    int PowerB, PowerC;//B、C馬達的輸出馬力

    SetSensorLight(IN_3); //或用 S3

    while (true)
    {
      Light = SENSOR_3 ;//讀取光感數據
      //限制輸入 Light 在比例控制的範圍
      if (Light < LightMin)
        Light=LightMin;

      else if (Light > LightMax)
        Light=LightMax;


      PowerC = (Kp*(Light-offset))+Tp;//代入方程式
      PowerB = (-Kp*(Light-offset))+Tp;//另一個馬達的斜率是負的
      //限制輸出 Power 在比例控制的範圍
      if (PowerC < PowerMin)
        PowerC=PowerMin;

      else if (PowerC > PowerMax)
        PowerC=PowerMax;


      if (PowerB < PowerMin)
        PowerB=PowerMin;

      else if (PowerB > PowerMax)
        PowerB=PowerMax;


      TextOut(5,40,NumToStr(Light),true);
      TextOut(5,20,NumToStr(PowerB));
      TextOut(75,20,NumToStr(PowerC));

      OnFwd(OUT_B,PowerB);
      OnFwd(OUT_C,PowerC);

    }

}



(未完待續...)

30 則留言:

  1. 請問一下,那要如何用LEGO MINSTROM 2.0來寫ㄋ?

    回覆刪除
    回覆
    1. 我寫了底下的程式,請參考。
      http://www.tngs.tn.edu.tw/download/NXT/follow2_p.rbt

      刪除
  2. 我想請教一夏,那若是循機車遇到 十字的路線該怎麼跑 我是想要用兩個光感感測,那該怎麼寫呢?

    回覆刪除
  3. 可使用單光感循跡,若另一顆光感看到黑線,就表示到了十字(或T字)路口了,
    這時可暫停循跡,然後看你要轉彎或前進後,再繼續循跡。

    回覆刪除
  4. 謝謝你喔 ~ 因為我們老師有出作業除了要我們走十字路口 還有 菱形 菱形我大概知道怎麼做 假如用一個光感的話 那他總會往其中一邊跑 但是 十字的話我想說 用第二個偵測看看可是我擔心用兩個光感 因為另一邊同時偵測到的話 那凌型也會 那 這樣的話 我是不是還要在加上第三個 來針對 菱形 或是 T字型的路口, 還是說我調整機器人的架構,就是光感測的擺放位置。
    不好意思喔~請教這麼多問題,因為我很想要了解怎麼樣可以讓我的機器人過彎或是過角度過的順利,因為我剛接觸NXC所以對這方面不是很熟悉。

    回覆刪除
    回覆
    1. 不好意思,不太了解你說的菱形是什麼狀況。

      刪除
  5. 另外我想要請教老師您寫的程式
    PowerC = (Kp*(Light-offset))+Tp;
    PowerB = (-Kp*(Light-offset))+Tp;
    因為我在課本上有看到馬達的輸出不是都用
    OnFwd(out_B,馬力);
    那這個方程是我不太會理解,想說可不可以請老師可以跟我在說明一下。

    回覆刪除
    回覆
    1. 我是以比例控制算出要傳給BC馬達的馬力後,
      再傳給OnFwd(out_B,PowerB)。
      方程式是根據上面的直線方程式來的,
      使用高中數學中的「點斜式」即可。

      刪除
  6. 作者已經移除這則留言。

    回覆刪除
  7. 作者已經移除這則留言。

    回覆刪除
  8. 老師您好
    我所說的菱形圖
    就是 如像

    http://www.tnu.edu.tw/ee/upimages/file/robot/new-2-18/C12%20%E8%BC%AA%E5%BC%8F%E6%A9%9F%E5%99%A8%E4%BA%BA%E5%BE%AA%E8%B7%A1%E6%AF%94%E8%B3%BD%E8%A6%8F%E5%89%87_13.02.08_.pdf
    的場地圖
    我的程式碼如下


    #define Light 45
    //S1
    #define Lighta0 42//平均值
    #define Lighta1 54//白值
    #define Lighta2 30//黑值
    #define reda 50
    //S2
    #define Lightb0 49//平均值
    #define Lightb1 62//白值
    #define Lightb2 36 //黑值
    //S3
    #define Lightc0 47//平均值
    #define Lightc1 60//白值
    #define Lightc2 34//黑值

    #define pwrs 90
    #define pwr 85
    #define pwra 85
    #define pwrb 90
    task main()
    {
    SetSensorLight(S1);
    SetSensorLight(S2);
    SetSensorLight(S3);

    while(true)
    {
    if(Sensor(S1)>Lighta0 && Sensor(S3)>Lightc0) //1白3白 -> 直走
    {
    OnFwdSync(OUT_AC,pwrs,0);
    }//if
    if(Sensor(S1)>Lighta0 && Sensor(S2)Lightc0) //1白2黑3白 -> 直走
    {
    OnFwdSync(OUT_AC,pwrs,0);
    }//if
    else if(Sensor(S1)>Lighta0 && Sensor(S3) 右前方修正
    {
    OnFwd(OUT_A,pwra);
    OnFwd(OUT_C,-90);
    ;
    }//else if
    else if(Sensor(S1)Lightc0) //1黑2白 -> 左前方修正
    {
    OnFwd(OUT_A,-90);
    OnFwd(OUT_C,pwra);

    }//else if
    else if(Sensor(S1)Lightb0 &&Sensor(S3)Lightc0 ) //1黑2黑3白 45度角 轉直
    {
    OnFwd(OUT_A,0);
    OnFwd(OUT_C,pwrb);
    }//else if
    else if(Sensor(S1)>Lighta0 && Sensor(S2) 直走
    {
    OnFwdSync(OUT_AC,pwr,0);
    }//else
    }//while
    }//main



    老師你可以幫我看一下嗎
    因為我剛開始寫這個所以不是很熟悉
    然後 對於菱形有一個問題就是說 家如我往菱形跑時 假如我不是 剛好

    else if(Sensor(S1)Lightb0 &&Sensor(S3)Lighta0 && Sensor(S3)>Lightc0) //1白3白 -> 直走
    {
    OnFwdSync(OUT_AC,pwrs,0);
    }//if
    if(Sensor(S1)>Lighta0 && Sensor(S2)Lightc0) //1白2黑3白 -> 直走
    {
    OnFwdSync(OUT_AC,pwrs,0);
    }//if
    else if(Sensor(S1)>Lighta0 && Sensor(S3) 右前方修正
    {
    OnFwd(OUT_A,pwra);
    OnFwd(OUT_C,-90);

    a =a+1;
    }//else if
    else if(Sensor(S1)Lightc0) //1黑2白 -> 左前方修正
    {
    OnFwd(OUT_A,-90);
    OnFwd(OUT_C,pwra);
    b =b+1;
    }//else if
    else if(Sensor(S1)Lightb0 &&Sensor(S3)5&&b>5)
    {

    OnFwdSync(OUT_AC,pwra,50);
    Wait(200);



    }




    else if(Sensor(S1)Lightc0 ) //1黑2黑3白 45度角 轉直
    {
    OnFwd(OUT_A,0);
    OnFwd(OUT_C,pwrb);
    }//else if
    else if(Sensor(S1)>Lighta0 && Sensor(S2) 直走
    {
    OnFwdSync(OUT_AC,pwr,0);
    }//else
    }//while
    }//main
    因為 菱形轉彎時 我寫成說 遇到 光感1 黑 < 光感2> 白 光感3 <黑
    往旁邊轉 但是我不太會設所以設定轉彎 幾秒

    老師可不可以幫我看一下給我一些意見


    因為我在學校測試時

    是可以 跑 但是 因為這樣 遇到 45度角時 我轉彎 我一中一個光感 會拐到"角" 因此誤判成為菱形 那我該如何去寫
    老師謝謝你喔 謝謝你願意回答我,因為我剛學這個不久 所以我也不太會寫 所以用最傳統的查表方式來寫 所以可能寫的不是這麼好 希望老師可以給我一些意見
    謝謝老師

    回覆刪除
  9. 老師 且我也在計算說 那我轉彎的時候 過直角 那我兩個輪子改怎麼轉 才可以轉得漂亮因為
    我不知道怎麼寫
    區分
    90度
    45度
    30度
    甚至 說 圓形跑完轉 45度角等
    的比較特別的路線
    想說可不可以請老師幫助我一下 給我一些意見
    謝謝老師這麼麻煩你 還有感謝老師
    為我說明
    PowerC = (Kp*(Light-offset))+Tp;
    PowerB = (-Kp*(Light-offset))+Tp;

    回覆刪除
  10. 老師我發現我 op文上去的時候好像 我打上去 跟網頁顯出來好像怪怪的 就是會 少掉一些字


    裡面其中一段

    老師你可以幫我看一下嗎
    因為我剛開始寫這個所以不是很熟悉
    然後 對於菱形有一個問題就是說 家如我往菱形跑時 假如我不是 剛好 ....

    後面是
    假如我不是 剛好
    else if(Sensor(S1)
    Lightb0 &&Sensor(S3)Lighta0 && Sensor(S3)>Lightc0) //1白3白 -> 直走
    {
    OnFwdSync(OUT_AC,pwrs,0);
    }//if
    if(Sensor(S1)>Lighta0 && Sensor(S2)Lightc0) //1白2黑3白 -> 直走
    {
    OnFwdSync(OUT_AC,pwrs,0);
    }//if
    else if(Sensor(S1)>Lighta0 && Sensor(S3) 右前方修正
    {
    OnFwd(OUT_A,pwra);
    OnFwd(OUT_C,-90);


    回覆刪除
  11. 哇!這軌道有點難跑吔!我得找時間試試。請問你3顆光感是怎麼擺的?

    回覆刪除
  12. 請問一下3顆光感,那要如何用LEGO MINSTROM 2.0來寫ㄋ?

    回覆刪除
  13. 3顆光感通常還是使用1顆來循跡,
    另2顆用來偵測交叉路口

    回覆刪除
  14. 請問老師
    要讓樂高機器人循著黑色線行走的程式碼在哪裡可以找的到呢?

    回覆刪除
  15. 請問老師:
    如果用您上面PID的程式,如何過虛線(斷線),要怎麼裝感應器來偵測前面是虛線呢?謝謝!

    回覆刪除
    回覆
    1. 不好意思,如有時間,虛線我也想試試看,目前沒有辦法幫你囉!

      刪除
  16. 老師不好意思,我有一段程式碼不懂意思,可以麻煩老師幫我做個註解嗎?
    lude
    #pragma hdrstop
    //---------------------------------------------------------------------------
    USEFORM("MainForm.cpp", frmMain);
    USEFORM("DisplayForm.cpp", frmDisplay);
    USEFORM("GainListForm.cpp", frmGainList);
    USEFORM("PositionForm.cpp", frmPosition);
    USEFORM("PKInputForm.cpp", frmPKInput);
    USEFORM("SelectPK.cpp", frmSelectPK);
    USEFORM("PKGame3.cpp", frmPKGame13);
    USEFORM("SetMySetForm.cpp", frmSetMySet01);
    USEFORM("SetMySetForm02.cpp", frmSetMySet02);
    USEFORM("SelSaveChanForm.cpp", SelSaveChan);
    USEFORM("TankGame.cpp", frmTankGame);
    USEFORM("SetFlowForm.cpp", frmSetFlow);
    USEFORM("FlowHelpForm.cpp", frmFlowHelp);
    USEFORM("FlowMainForm.cpp", frmFlowMain);
    USEFORM("StartSettingForm.cpp", frmStartSetting);
    USEFORM("OddballForm.cpp", frmOddball);
    USEFORM("TestModeForm.cpp", frmTestMode);
    USEFORM("..\analysis\fftreportform.cpp", FFTReport);
    USEFORM("..\analysis\analysis.cpp", Analysis_Form);
    USEFORM("ZuyinMatrixForm.cpp", frmZuyinMatrix);
    USEFORM("Control.cpp", Stimuli);
    USEFORM("Unit1.cpp", Form1);
    USEFORM("Unit2.cpp", Form2);
    USEFORM("WordMatrixForm.cpp", frmWordMatrix);
    PI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
    {
    try
    {
    Application->Initialize();
    Application->CreateForm(__classid(TfrmMain), &frmMain);
    Application->CreateForm(__classid(TfrmDisplay), &frmDisplay);
    Application->CreateForm(__classid(TfrmGainList), &frmGainList);
    Application->CreateForm(__classid(TfrmPosition), &frmPosition);
    Application->CreateForm(__classid(TfrmPKInput), &frmPKInput);
    Application->CreateForm(__classid(TfrmSelectPK), &frmSelectPK);
    Application->CreateForm(__classid(TfrmPKGame13), &frmPKGame13);
    Application->CreateForm(__classid(TfrmSetMySet01), &frmSetMySet01);
    Application->CreateForm(__classid(TfrmSetMySet02), &frmSetMySet02);
    Application->CreateForm(__classid(TSelSaveChan), &SelSaveChan);
    Application->CreateForm(__classid(TfrmTankGame), &frmTankGame);
    Application->CreateForm(__classid(TfrmSetFlow), &frmSetFlow);
    Application->CreateForm(__classid(TfrmFlowHelp), &frmFlowHelp);
    Application->CreateForm(__classid(TfrmFlowMain), &frmFlowMain);
    Application->CreateForm(__classid(TfrmStartSetting), &frmStartSetting);
    Application->CreateForm(__classid(TfrmOddball), &frmOddball);
    Application->CreateForm(__classid(TfrmTestMode), &frmTestMode);
    Application->CreateForm(__classid(TFFTReport), &FFTReport);
    Application->CreateForm(__classid(TAnalysis_Form), &Analysis_Form);
    Application->CreateForm(__classid(TfrmZuyinMatrix), &frmZuyinMatrix);
    Application->CreateForm(__classid(TStimuli), &Stimuli);
    Application->CreateForm(__classid(TForm1), &Form1);
    Application->CreateForm(__classid(TForm2), &Form2);
    Application->CreateForm(__classid(TfrmWordMatrix), &frmWordMatrix);
    Application->Run();
    }
    catch (Exception &exception)
    {
    Application->ShowException(&exception);
    }
    catch (...)
    {
    try
    {
    throw Exception("");
    }
    catch (Exception &exception)
    {
    Application->ShowException(&exception);
    }
    }
    return 0;

    回覆刪除
    回覆
    1. C++ Builder 的程式碼,
      主要是把程式裏各式各樣的Form建立起來

      刪除
  17. 老師 請問如果要用Borland C++Builder去寫紅外線循跡車 該怎麼寫呢?

    回覆刪除
    回覆
    1. Borland C++Builder可以編譯出NXT的原生執行檔嗎?
      我Google了一下,
      都是使用第三方的函式庫或VLC元件編譯出PC上的程式碼,
      透過藍牙遙控來實作的。
      但是遙控有傳輸上的時間延遲問題,
      在變化大的路徑上是否可以即時校正,或許不容易克服。

      若要實驗看看,
      就要閱讀該Library或VCL的API文件,
      再使用本文所提的方法來計算馬達的輸出。

      刪除
  18. 老師不好意思,再請問一下,如果要用bcb寫從a點走到b點的路徑程式,該怎麼寫呢?

    回覆刪除
  19. 想請教您這些編譯的環境要在哪載啊

    回覆刪除
  20. 老師學生最近遇到要用BCB(Borland C++Builder)編寫關於PID的問題
    是否有相關資訊 或是已編寫好的語言能供學生參考學習?
    或是能大概講一下該怎麼去編寫PID在BCB上

    回覆刪除
    回覆
    1. 是指要控制NXT嗎?
      其實PID是一種利用數學的控制方法,
      它是無關語言的,
      重點是校正誤差值的數學式,
      找到之後,程式的寫法和上面的大同小異,
      只是在控制硬體上,要有相對的API或函式庫。

      另外,BCB 6 只能編譯win32平台上的程式,
      所以必需把你的硬體與pc連線(以有線或無線的方式進行連線),
      借由在win32平台上執行程式,把運算結果傳給外接硬體去執行。

      或者,在硬體上執行自己的平台,用相對應的平台編譯器去編譯出原生可執行程式,才能傳到硬體上去獨立運作。

      要控制NXT或 EV3,請參考MonoBrick
      http://www.monobrick.dk/software/monobrick/

      刪除