# FRC 底盤教學 (C++ / WPILib) 在 FRC 中我們主要是用 WPILib 來編寫程式,可以從 FRC 官網下載到: 🔗 https://docs.wpilib.org/en/stable/docs/zero-to-robot/step-2/wpilib-setup.html 我們隊主要是用 C++ 來編程的,以下的程式碼範例也都會用 C++ 來編寫。 開始寫機台程式之前建議熟悉一下 C++ 的指標,Command Base 編碼會用到。 會用到的程式庫有: **REV 的 CANSparkMax**(動力輸出) **CTRE Phoenix 的 TalonSRX**(轉向控制) 如果使用其他馬達控制器,就使用對應的程式庫。 ## 底盤種類 ### 1.小白輪(Tank Drive) **最常見,也最簡單編程的底盤** ![image](https://hackmd.io/_uploads/r1i9VHKUT.png) ### 2.麥輪(Mecanum Drive) **移動比起坦克式還要靈活,可以左右飄移,但容易打滑,抓地力比較小** ![image](https://hackmd.io/_uploads/HkdeSSFUp.png) ### 3.全向輪(Swerve Drive) ![image](https://hackmd.io/_uploads/rkPMM2YUp.png) (只有一個輪子的圖片,其實有四個) **最靈活的底盤,但在編程上是最難的。** ## 1. 小白輪 (Tank Drive) **假設:** 1. 左輪力為 l_power 2. 右輪力為 r_power 3. 搖桿輸入為 x(轉向)與 y(前進) 4. k 為轉向靈敏度常數 **狀況分析** 1. 向前進時 (y = 1, x = 0) **左右輪都向前** 2. 向右轉時 (y = 0, x = 1) **左輪向前、右輪向後** 所以: **l_power = y + x * k r_power = y - x * k** **Bounding** 因為馬達輸出範圍是 **[-1, 1]**,需要限制最大值。 **範例 (cpp)** ```cpp= void Robot::TeleopPeriodic(){ // 搖桿輸入 double x = joystick.GetX(); double y = joystick.GetY(); // 計算馬達輸出的力 double k = 1.0; double l_power = y + x * k; double r_power = y - x * k; // Bounding l_power = std::clamp(l_power, -1.0, 1.0); r_power = std::clamp(r_power, -1.0, 1.0); // 輸出馬達 left_motor.Set(l_power); right_motor.Set(r_power); } ``` ## 2. 麥輪 (Mecanum Drive) 麥輪有 4 個輪子,需考慮三種輸入: **x:左右移動 y:前後移動 rx:旋轉** **狀況分析:** 1. 前進 (y = 1, x = 0, rx = 0) **所有輪子都往前。** 2. 右飄移 (y = 0, x = 1, rx = 0) **左前與右後往前;右前與左後往後。** 3. 順時鐘旋轉 (y = 0, x = 0, rx = 1) **左邊輪往前、右邊輪往後。** 綜合公式 **front_left = y + x + rx back_left = y - x + rx front_right = y - x - rx back_right = y + x - rx** **Bounding** 四個輪子的力會互相影響,不能單獨限制。 正確做法是用: **d = max(|x| + |y| + |rx|, 1)** 所有輸出都除以 d。 **範例 (cpp)** ```cpp= void Robot::TeleopPeriodic(){ // 搖桿輸入 double x = joystick.GetX(); double y = joystick.GetY(); double rx = joystick.GetRawAxis(4); // 右搖桿的 X 軸 // 最大值做分母 double d = std::max({fabs(x) + fabs(y) + fabs(rx), 1.0}); // 計算輸出 double front_left = (y + x + rx) / d; double back_left = (y - x + rx) / d; double front_right = (y - x - rx) / d; double back_right = (y + x - rx) / d; // 輸出馬達 front_left_motor.Set(front_left); back_left_motor.Set(back_left); front_right_motor.Set(front_right); back_right_motor.Set(back_right); } ``` ## 3. 全向輪 (Swerve Drive) 全向輪的輪力計算較複雜。 首先需要知道: 1. 機器人長度 L 2. 機器人寬度 W 3. 搖桿輸入:speed(前後)、drift(左右)、turn(旋轉) 4. 對角線長度 **r = sqrt(L² + W²)** 力的組合公式 **A = drift - turn * (L / r) B = drift + turn * (L / r) C = speed - turn * (W / r) D = speed + turn * (W / r)** 各輪速度 **front_right = sqrt(B² + C²) front_left = sqrt(B² + D²) back_left = sqrt(A² + D²) back_right = sqrt(A² + C²)** Normalize **d = max(front_left, back_left, front_right, back_right, 1)** 然後每個輪速都除以 d。 各輪角度 **front_right_angle = atan2(B, C) front_left_angle = atan2(B, D) back_left_angle = atan2(A, D) back_right_angle = atan2(A, C)** 角度單位轉成度數: **angle = atan2(y, x) * 180 / π** **範例 (cpp)** ```cpp= void Robot::TeleopPeriodic(){ // 搖桿輸入 double speed = joystick.GetY(); double drift = joystick.GetX(); double turn = joystick.GetRawAxis(4); // 機器人尺寸 double L = 0.5; double W = 0.5; double r = sqrt(L*L + W*W); // 計算力分量 double A = drift - turn * (L / r); double B = drift + turn * (L / r); double C = speed - turn * (W / r); double D = speed + turn * (W / r); // 輪速 double front_right = sqrt(B*B + C*C); double front_left = sqrt(B*B + D*D); double back_left = sqrt(A*A + D*D); double back_right = sqrt(A*A + C*C); // Normalize double d = std::max({front_left, back_left, front_right, back_right, 1.0}); front_left /= d; back_left /= d; front_right /= d; back_right /= d; // 計算角度 (degree) double front_right_angle = atan2(B, C) * 180 / M_PI; double front_left_angle = atan2(B, D) * 180 / M_PI; double back_left_angle = atan2(A, D) * 180 / M_PI; double back_right_angle = atan2(A, C) * 180 / M_PI; // 輸出馬達 front_left_motor.Set(front_left); back_left_motor.Set(back_left); front_right_motor.Set(front_right); back_right_motor.Set(back_right); // 設定轉向馬達角度 front_left_angle_motor.Set(ControlMode::Position, front_left_angle); back_left_angle_motor.Set(ControlMode::Position, back_left_angle); front_right_angle_motor.Set(ControlMode::Position, front_right_angle); back_right_angle_motor.Set(ControlMode::Position, back_right_angle); } ```