[{"data":1,"prerenderedAt":443},["ShallowReactive",2],{"portfolio-\u002Fportfolio\u002Flego-line-follower-robot":3},{"id":4,"title":5,"award":6,"body":7,"cover":422,"description":178,"extension":423,"featured":424,"link":425,"meta":426,"navigation":427,"order":428,"path":429,"pinned":424,"repo":425,"role":430,"seo":431,"stack":432,"stem":439,"summary":440,"year":441,"__hash__":442},"portfolio\u002Fportfolio\u002Flego-line-follower-robot.md","LEGO 循跡避障機器人","第一名",{"type":8,"value":9,"toc":412},"minimark",[10,14,30,45,48,74,80,84,91,115,122,126,132,135,142,168,171,181,201,205,211,218,306,309,337,340,354,357,377,380],[11,12,13],"h2",{"id":13},"專案概述",[15,16,17,18,22,23,26,27,29],"p",{},"2018 年我參加了",[19,20,21],"strong",{},"全能機器人國際邀請賽","的",[19,24,25],{},"循跡競賽","，最後拿下",[19,28,6],{},"。",[15,31,32,33,36,37,40,41,44],{},"循跡競賽的規則很直觀：機器人要沿著場地上的黑色軌道從起點跑到終點，比的是",[19,34,35],{},"完\n成度與速度","。難的地方在於軌道上會擺放",[19,38,39],{},"寶特瓶障礙物","——機器人不能撞倒它，必\n須自己偵測、繞開，再",[19,42,43],{},"回到軌道上","繼續前進。也就是說，這不只是「跟著線走」，而\n是要讓機器人在「循線」與「避障」兩種模式之間可靠地切換。",[15,46,47],{},"我的機器人以 LEGO 打造，設計上有三個關鍵：",[49,50,51,58,64],"ul",{},[52,53,54,57],"li",{},[19,55,56],{},"左右兩顆顏色感測器","夾著軌道，用兩邊的反射光差值判斷偏移方向與幅度",[52,59,60,63],{},[19,61,62],{},"前方一顆超音波感測器","，負責提早發現寶特瓶障礙物",[52,65,66,69,70,73],{},[19,67,68],{},"四顆全向輪（萬向輪）","，讓機器人可以",[19,71,72],{},"不轉彎、直接橫向平移","閃避障礙，這\n是整台車在避障時又快又穩的關鍵",[75,76,77],"blockquote",{},[15,78,79],{},"比賽至今已經好幾年，當時的機器人實體與程式都已經沒有保留下來，只剩下獎狀。\n這篇是依照我記得的設計重新整理，圖與程式碼皆為重新繪製、改寫的示意。",[11,81,83],{"id":82},"機構設計感測器配置與-x-型全向輪","機構設計：感測器配置與 X 型全向輪",[15,85,86],{},[87,88],"img",{"alt":89,"src":90},"機器人俯視圖：車身四個角各一顆 45 度斜放的全向輪，車頭前方是超音波感測器，下方左右兩顆顏色感測器夾著黑線；右側說明全向輪可以前進、橫向平移與原地旋轉","\u002Fimages\u002Fportfolio\u002Flego-line-follower-layout.svg",[49,92,93,99,105],{},[52,94,95,98],{},[19,96,97],{},"顏色感測器（左、右）","：裝在車頭下方、貼近地面，間距略大於軌道寬度。正常\n行駛時黑線剛好落在兩顆感測器中間，兩邊都讀到白色；一旦車子偏移，其中一邊就\n會先讀到黑線",[52,100,101,104],{},[19,102,103],{},"超音波感測器（前方）","：朝向行進方向，持續量測前方距離，距離小於門檻就代表\n寶特瓶出現在軌道上",[52,106,107,110,111,114],{},[19,108,109],{},"四顆全向輪，X 型配置","：四顆輪子各自由一顆馬達驅動，以 45 度斜放在車身四\n角。全向輪的輪框上有一圈可以自由滾動的小滾輪，所以每顆輪子只在自己的方向上\n「出力」，側向不會產生阻力；四顆輪子的出力向量疊加起來，就能組合出",[19,112,113],{},"前後、\n左右、旋轉","三種動作",[15,116,117,118,121],{},"選擇全向輪是整台車最重要的設計決策。一般兩輪差速車遇到障礙物時，必須「轉\n彎 → 前進 → 轉回來 → 再轉彎找線」，每一次轉彎都會累積角度誤差，回到軌道時車頭\n方向常常是歪的，很容易一找到線就又衝出去。全向輪可以",[19,119,120],{},"保持車頭方向不變、直接\n側移","，繞完障礙物後車身仍然和軌道平行，回到線上馬上就能繼續循跡。",[11,123,125],{"id":124},"循跡控制兩顆顏色感測器的差值控制","循跡控制：兩顆顏色感測器的差值控制",[15,127,128],{},[87,129],{"alt":130,"src":131},"循跡判斷示意圖：線在正中間時左右感測器都讀到白色，直走；線偏到左邊時左感測器讀到黑色，往左修正；線偏到右邊時右感測器讀到黑色，往右修正","\u002Fimages\u002Fportfolio\u002Flego-line-follower-tracking.svg",[15,133,134],{},"最直覺的寫法是「左邊看到黑就左轉、右邊看到黑就右轉」，但這種**開關式（Bang-\nBang）**控制只有兩種動作，車子會在線上不停左右擺盪，速度一快就甩出軌道。",[15,136,137,138,141],{},"改善的方式是使用顏色感測器的",[19,139,140],{},"反射光強度","（0–100 的連續數值），而不是只判斷黑\n白：",[143,144,145,156,162],"ol",{},[52,146,147,150,151,155],{},[19,148,149],{},"計算誤差","：",[152,153,154],"code",{},"error = 左反射值 − 右反射值","。線在正中間時兩邊差不多，誤差接近\n0；線越偏向某一邊，那一邊越暗，誤差的絕對值就越大",[52,157,158,161],{},[19,159,160],{},"比例修正（P）","：轉向量與誤差成正比——偏一點就修一點、偏很多就修很多，車子\n的行進軌跡會平滑很多",[52,163,164,167],{},[19,165,166],{},"微分修正（D）","：再加上「誤差的變化量」，在車子快速擺回中線時提前減少修\n正，抑制過衝造成的來回擺盪",[15,169,170],{},"當時程式是用 EV3 的圖形化積木撰寫，以下用虛擬碼表達核心邏輯：",[172,173,179],"pre",{"className":174,"code":176,"language":177,"meta":178},[175],"language-text","Kp = 0.8           \u002F\u002F 比例增益，依實測調整\nKd = 4.0           \u002F\u002F 微分增益\nBASE_SPEED = 45    \u002F\u002F 直線時的基礎速度\nlastError = 0\n\nloop:\n    error = leftSensor.reflected - rightSensor.reflected\n    turn  = Kp * error + Kd * (error - lastError)\n    lastError = error\n\n    \u002F\u002F X 型全向輪：前進分量 + 旋轉分量\n    drive(forward = BASE_SPEED, strafe = 0, rotate = turn)\n","text","",[152,180,176],{"__ignoreMap":178},[15,182,183,186,187,190,191,193,194,196,197,200],{},[152,184,185],{},"Kp","、",[152,188,189],{},"Kd"," 和基礎速度沒有標準答案，只能在場地上反覆實測：先只調 ",[152,192,185],{}," 到車子剛\n好開始擺盪，再慢慢加上 ",[152,195,189],{}," 把擺盪壓下來，最後才把速度往上拉。另外每次換場地\n都會先",[19,198,199],{},"校正感測器","——讀一次白底、一次黑線的反射值，因為比賽場地的燈光和練習\n時不同，數值會跟著偏移。",[11,202,204],{"id":203},"避障狀態機-橫向平移","避障：狀態機 + 橫向平移",[15,206,207],{},[87,208],{"alt":209,"src":210},"避障路徑示意圖：超音波偵測到寶特瓶後停車，往右平移離開軌道，直走越過瓶身，再往左平移直到顏色感測器重新看到黑線，回到循跡模式","\u002Fimages\u002Fportfolio\u002Flego-line-follower-avoidance.svg",[15,212,213,214,217],{},"避障的流程用一個簡單的",[19,215,216],{},"狀態機","實作，每個狀態只做一件事，結束條件明確，不容\n易在比賽中「卡在中間」：",[219,220,221,237],"table",{},[222,223,224],"thead",{},[225,226,227,231,234],"tr",{},[228,229,230],"th",{},"狀態",[228,232,233],{},"動作",[228,235,236],{},"切換條件",[238,239,240,257,275,290],"tbody",{},[225,241,242,248,251],{},[243,244,245],"td",{},[152,246,247],{},"FOLLOW",[243,249,250],{},"循跡（上一段的差值控制）",[243,252,253,254],{},"超音波距離 \u003C 門檻 → ",[152,255,256],{},"STEP_OUT",[225,258,259,263,269],{},[243,260,261],{},[152,262,256],{},[243,264,265,266],{},"停車，往右",[19,267,268],{},"橫向平移",[243,270,271,272],{},"平移固定距離（馬達角度） → ",[152,273,274],{},"PASS",[225,276,277,281,284],{},[243,278,279],{},[152,280,274],{},[243,282,283],{},"直線前進",[243,285,286,287],{},"前進超過瓶身長度 → ",[152,288,289],{},"STEP_IN",[225,291,292,296,301],{},[243,293,294],{},[152,295,289],{},[243,297,298,299],{},"往左",[19,300,268],{},[243,302,303,304],{},"任一顏色感測器讀到黑線 → ",[152,305,247],{},[15,307,308],{},"幾個在練習時踩過、後來特別處理的細節：",[49,310,311,317,323,331],{},[52,312,313,316],{},[19,314,315],{},"距離門檻要留緩衝","：超音波量測有延遲，車速越快煞停距離越長。門檻設得太近會\n直接撞上瓶子，太遠又可能誤判場邊的物體，最後依實際車速調出一個剛好的距離",[52,318,319,322],{},[19,320,321],{},"用馬達角度量距離，而不是用時間","：平移與前進的距離都以馬達編碼器的旋轉角度\n判斷，不會因為電池電量下降、馬達變慢而走不夠遠",[52,324,325,150,328,330],{},[19,326,327],{},"回線時用「找到線」當結束條件",[152,329,289],{}," 不是平移固定距離，而是一直平移到顏\n色感測器重新看到黑線為止，即使前面幾步有誤差也能自我修正",[52,332,333,336],{},[19,334,335],{},"回線後先降速","：剛回到軌道時車身位置還不穩定，先以較低速度循跡一小段，再恢\n復正常速度",[11,338,339],{"id":339},"競賽成果",[49,341,342,351],{},[52,343,344,345,348,349],{},"在 ",[19,346,347],{},"2018 全能機器人國際邀請賽 循跡競賽"," 中拿下 ",[19,350,6],{},[52,352,353],{},"機器人能穩定完成循線，遇到寶特瓶障礙物時自動繞開、回到軌道，全程不需人工介\n入",[11,355,356],{"id":356},"收穫",[49,358,359,365,371],{},[52,360,361,364],{},[19,362,363],{},"先把問題拆成狀態，再處理每個狀態","：循跡和避障如果寫在同一個大迴圈裡，條件\n會互相干擾；拆成狀態機後，每個狀態只要關心自己的動作與結束條件，除錯時也能很\n快知道車子卡在哪一步。這和我後來設計後端流程、事件驅動系統時的思考方式是一樣\n的",[52,366,367,370],{},[19,368,369],{},"回饋控制的直覺","：從開關式控制改成比例 + 微分控制，是我第一次實際感受到\n「依誤差大小調整反應」的威力。後來在 Kubernetes 上設定 HPA、調整告警門檻\n時，背後其實是同一套回饋迴路的觀念",[52,372,373,376],{},[19,374,375],{},"硬體選型決定了軟體的難度","：選擇全向輪讓避障邏輯簡化成「平移出去、平移回\n來」，省掉大量轉彎角度的修正程式。好的架構選擇，往往能讓後面的程式寫得更簡\n單",[11,378,379],{"id":379},"使用技術",[49,381,382,388,394,400,406],{},[52,383,384,387],{},[19,385,386],{},"平台","：LEGO MINDSTORMS EV3（主控制器、四顆大型伺服馬達）",[52,389,390,393],{},[19,391,392],{},"感測器","：顏色感測器 × 2（反射光強度模式）、超音波感測器 × 1",[52,395,396,399],{},[19,397,398],{},"機構","：X 型配置四顆全向輪（萬向輪），支援全向移動",[52,401,402,405],{},[19,403,404],{},"控制","：PD 循跡控制、避障狀態機、馬達編碼器距離控制、感測器校正",[52,407,408,411],{},[19,409,410],{},"開發工具","：EV3 圖形化程式（積木式）",{"title":178,"searchDepth":413,"depth":413,"links":414},2,[415,416,417,418,419,420,421],{"id":13,"depth":413,"text":13},{"id":82,"depth":413,"text":83},{"id":124,"depth":413,"text":125},{"id":203,"depth":413,"text":204},{"id":339,"depth":413,"text":339},{"id":356,"depth":413,"text":356},{"id":379,"depth":413,"text":379},"\u002Fimages\u002Fportfolio\u002Flego-line-follower-cover.svg","md",false,null,{},true,10,"\u002Fportfolio\u002Flego-line-follower-robot","競賽作品 ‧ 機構設計與控制程式",{"title":5,"description":178},[433,434,435,436,437,216,438],"LEGO MINDSTORMS EV3","顏色感測器","超音波感測器","全向輪（萬向輪）","PD 控制","機器人競賽","portfolio\u002Flego-line-follower-robot","2018 年參加全能機器人國際邀請賽，以 LEGO 機器人拿下循跡競賽第一名。機器人左右各一顆顏色感測器夾著軌道循線，前方以超音波感測器偵測寶特瓶障礙物，搭配四顆全向輪（萬向輪）直接橫向平移閃避，繞過障礙後再自動找回軌道。",2018,"2dIKeceHoD7hisk3v18eS2AZt_49t-c02RqunQcoFF8",1792959149987]