# 萬向輪底盤程式工程筆記 ## 前言 在升上高三後,我持續利用課餘時間參與並協助FRC機器人競賽的程式組開發工作。而今年的自動程式,我選擇讓學弟妹撰寫,並提供必要的指導與建議,以培養他們的技術能力。 高三成員團體照: ![HIGH_SCHOOL_TEAM_PHOTO](https://hackmd.io/_uploads/SJE8mLS0ye.jpg) ## 1. 萬象輪底盤技術解析 ### 1.1 萬向輪(Swerve)概念介紹 Swerve 是一種先進的底盤運行模式,其核心特點是每個輪子都能獨立控制其速度與方向。若採用四輪驅動的 Swerve 設計,其組成為四個獨立的驅動單元(模組),每個模組均配備: ![REV-21-3005-Swerve-Hero-FINAL__82364](https://hackmd.io/_uploads/rJ4_lDr0yl.png) - **Drive Motor**: 控制輪子的轉速(前進後退) - **Turning Motor**: 控制輪子的旋轉角度(方向) - **編碼器系統**: - **Drive Encoder**: 監測輪子的轉速 - **Turning Encoder**: 監測輪子的角度 - **CANcoder**: 提供絕對角度測量 透過這種設計,Swerve 底盤可以實現向任意方向移動,同時能夠獨立控制底盤的旋轉角度,提供極高的機動性與靈活度。 ## 2. 技術基礎:深入理解 Swerve 模組架構 ### 2.1 核心組件解析 #### 2.1.1 驅動系統 - **驅動馬達 (Drive Motor)**:NEO 無刷馬達,負責控制輪子的轉速 - **轉向馬達 (Turning Motor)**:NEO 無刷馬達,負責控制輪子的方向 - **CANcoder**:專用絕對角度編碼器,提供精確的角度測量 ![Swerve底盤示意圖](https://hackmd.io/_uploads/HkagE8HCyg.jpg) #### 2.1.2 控制系統 - **SparkMax 控制器**:用於控制 NEO 無刷馬達 - **PID 控制器**:確保精確角度控制與平滑轉向 ### 2.2 程式架構設計 #### 2.2.1 物件導向結構 ```java public class SwerveMod { // 馬達宣告 private final CANSparkMax driving; // 控制輪子前進後退 private final CANSparkMax turning; // 控制輪子轉向 // 編碼器宣告 private final RelativeEncoder drivingEncoder; // 測量驅動馬達轉速和距離 private final RelativeEncoder turningEncoder; // 測量轉向馬達轉速 private final CANCoder cancoder; // 提供轉向絕對角度 // PID控制器宣告,用於精確控制轉向角度 private final PIDController turningPidController = new PIDController(0.0113, 0.0115, 0); // 物理參數 private final double wheelDiameter = 0.1016; // 輪子直徑(公尺) private final double gearRatio = 6.12; // 齒輪比 // 建構子與方法(詳見後續內容) } ``` #### 2.2.2 建構子實作 ```java public SwerveMod(int drivingID, int turningID, int cancoderID, Rotation2d offset) { // 初始化馬達 driving = new CANSparkMax(drivingID, MotorType.kBrushless); turning = new CANSparkMax(turningID, MotorType.kBrushless); // 初始化編碼器 drivingEncoder = driving.getEncoder(); turningEncoder = turning.getEncoder(); cancoder = new CANCoder(cancoderID); // 配置硬體參數 configCANcoder(offset); configSparkMax(); // 設定PID為連續輸入,處理角度週期性(-180° 到 180°) turningPidController.enableContinuousInput(-180, 180); } ``` ### 2.3 核心配置方法 #### 2.3.1 CANcoder 配置 ```java private void configCANcoder(Rotation2d offset) { CANcoderConfiguration config = new CANcoderConfiguration(); config.sensorDirection = true; // 設定感測器方向為逆時針為正向 config.magnetOffsetDeg = offset.getDegrees(); // 設定磁性偏移角度 config.sensorTimeBase = SensorTimeBase.PerSecond; cancoder.configAllSettings(config); } ``` #### 2.3.2 SparkMax 配置 ```java private void configSparkMax() { // 重置馬達控制器配置 driving.restoreFactoryDefaults(); turning.restoreFactoryDefaults(); // 設定驅動馬達編碼器轉換因子(從旋轉數到移動距離) drivingEncoder.setPositionConversionFactor( wheelDiameter * Math.PI / gearRatio ); drivingEncoder.setVelocityConversionFactor( wheelDiameter * Math.PI / (60.0 * gearRatio) ); // 設定轉向馬達編碼器轉換因子(從旋轉數到角度) turningEncoder.setPositionConversionFactor(360.0 / 12.8); // 設定馬達控制器參數 driving.setIdleMode(IdleMode.kBrake); turning.setIdleMode(IdleMode.kBrake); driving.setSmartCurrentLimit(40); turning.setSmartCurrentLimit(20); // 將設定燒入控制器永久儲存 driving.burnFlash(); turning.burnFlash(); } ``` ## 3. 系統整合:底盤控制架構 ### 3.1 SwerveChassis 類設計 ```java public class SwerveChassis extends SubsystemBase { // 四個 Swerve 模組的定義 private final SwerveMod frontLeft, frontRight, backLeft, backRight; // 陀螺儀,用於場地中心控制 private final AHRS gyro; // 模組間距參數(單位:米) private final double L = 0.487; // 對角線距離 private final double trackWidth = 0.37; // 寬度 private final double wheelBase = 0.315; // 長度 // 速度限制 private final double maxSpeed = 4.935; // 最大平移速度 (m/s) private final double maxOmega = 10.13; // 最大旋轉速度 (rad/s) public SwerveChassis() { // 初始化陀螺儀 gyro = new AHRS(SPI.Port.kMXP); resetGyro(); // 初始化四個 Swerve 模組 frontLeft = new SwerveMod(1, 2, 9, Rotation2d.fromDegrees(91.23)); frontRight = new SwerveMod(3, 4, 10, Rotation2d.fromDegrees(10.10)); backLeft = new SwerveMod(5, 6, 11, Rotation2d.fromDegrees(15.73)); backRight = new SwerveMod(7, 8, 12, Rotation2d.fromDegrees(69.08)); } // 重設陀螺儀方法 public void resetGyro() { gyro.reset(); } // 主要驅動方法 public void drive(Translation2d targetVelocity, double targetOmega, boolean fieldRelative) { // 限制最大速度 targetVelocity = limitVelocity(targetVelocity); targetOmega = Math.min(Math.max(targetOmega, -maxOmega), maxOmega); // 場地相對坐標轉換 if (fieldRelative) { targetVelocity = targetVelocity.rotateBy( Rotation2d.fromDegrees(-gyro.getAngle()) ); } // 計算每個模組的速度和角度 SwerveModuleState[] states = new SwerveModuleState[4]; // 計算各點相對於中心的位置 Translation2d fl = new Translation2d(wheelBase/2, trackWidth/2); Translation2d fr = new Translation2d(wheelBase/2, -trackWidth/2); Translation2d bl = new Translation2d(-wheelBase/2, trackWidth/2); Translation2d br = new Translation2d(-wheelBase/2, -trackWidth/2); // 計算各點的速度向量 Translation2d flVel = targetVelocity.plus(new Translation2d(0, targetOmega).times(fl.getNorm())); Translation2d frVel = targetVelocity.plus(new Translation2d(0, targetOmega).times(fr.getNorm())); Translation2d blVel = targetVelocity.plus(new Translation2d(0, targetOmega).times(bl.getNorm())); Translation2d brVel = targetVelocity.plus(new Translation2d(0, targetOmega).times(br.getNorm())); // 設置每個模組的目標狀態 frontLeft.setDesiredState(new SwerveModuleState(flVel.getNorm(), flVel.getAngle())); frontRight.setDesiredState(new SwerveModuleState(frVel.getNorm(), frVel.getAngle())); backLeft.setDesiredState(new SwerveModuleState(blVel.getNorm(), blVel.getAngle())); backRight.setDesiredState(new SwerveModuleState(brVel.getNorm(), brVel.getAngle())); } // 速度限制方法 private Translation2d limitVelocity(Translation2d velocity) { double magnitude = velocity.getNorm(); if (magnitude > maxSpeed) { return velocity.times(maxSpeed / magnitude); } return velocity; } } ``` ### 3.2 速度參數計算方法詳解 #### 3.2.1 最大平移速度計算 最大平移速度(maxSpeed)是根據馬達規格、齒輪比和輪徑計算得出: 1. **將馬達轉速從 RPM 轉為每秒轉數**: ``` 馬達每秒轉速 = freespeed / 60 = 5676 / 60 = 94.6 轉/秒 ``` 2. **考慮減速比,計算輪子每秒轉速**: ``` 輪子每秒轉速 = 馬達每秒轉速 / kDriveMotorGearRatio = 94.6 / 6.12 ≈ 15.46 轉/秒 ``` 3. **計算輪子每秒行駛距離(maxspeed)**: - 輪子周長: ``` kWheelDiameterMeters * Math.PI = 0.1016 * 3.14159 ≈ 0.3192 米/轉 ``` - 最大速度: ``` 輪子每秒轉速 * 周長 = 15.46 * 0.3192 ≈ 4.935 米/秒 ``` #### 3.2.2 最大旋轉速度計算 最大旋轉速度(maxOmega)是根據最大平移速度和轉動半徑計算得出: 1. **計算轉動半徑 L**: ``` L = √(wheelBase² + trackWidth²) = √(0.315² + 0.37²) ≈ √(0.0992 + 0.1369) ≈ √0.2361 ≈ 0.487 米 ``` 2. **計算最大旋轉速度**: ``` maxOmega = maxSpeed / L = 4.935 / 0.487 ≈ 10.13 弧度/秒 ``` ### 3.3 RobotContainer 整合 ```java public class RobotContainer { // 底盤系統初始化 private final SwerveChassis chassis = new SwerveChassis(); // 控制器宣告 private final CommandXboxController controller = new CommandXboxController(0); private final CommandJoystick joystick = new CommandJoystick(1); // 速度參數 private final double maxSpeed = 4.935; // 米/秒 private final double maxOmega = 10.13; // 弧度/秒 public RobotContainer() { // 底盤預設命令:使用RunCommand持續執行chassis.drive chassis.setDefaultCommand( new RunCommand( () -> chassis.drive( // 從左搖桿獲取平移指令(X、Y軸) new Translation2d( -controller.getLeftY() * maxSpeed, -controller.getLeftX() * maxSpeed ), // 從右搖桿獲取旋轉指令(X軸) -controller.getRightX() * maxOmega, true // 使用場地視角控制 ), chassis ) ); // 配置按鈕綁定 configureBindings(); } private void configureBindings() { // 重設陀螺儀按鈕 controller.start().onTrue(new InstantCommand(() -> chassis.resetGyro())); // 其他按鈕綁定... } } ``` ## 4. 技術挑戰與解決方案 ### 4.1 校正偏移量挑戰 #### 4.1.1 問題分析 在使用 CANcoder 進行絕對角度測量時,我們面臨了各模組間偏移量校正需要反覆調整的問題。這主要由以下因素導致: - 機械安裝誤差 - 外部環境干擾 - 初始校準偏差 - 使用過程中的累積誤差 #### 4.1.2 解決方案 1. **精確測量初始位置**: - 使用精密量具測量每個模組的初始角度 - 建立標準化的校準流程 2. **軟體補償**: - 在 `configCANcoder` 方法中應用偏移量補償 - 根據測試數據動態調整偏移量 3. **機械優化**: - 加強 CANcoder 安裝結構,減少晃動 - 定期檢查和維護機械連接處 ### 4.2 PID 參數調整 #### 4.2.1 問題分析 精確控制 Swerve 模組的角度需要適當的 PID 參數,但調整過程中我們遇到了以下挑戰: - 過度補償導致系統振盪 - 不足的補償導致響應緩慢 - 負載變化導致的參數失效 #### 4.2.2 解決方案:系統性 PID 調參方法 我們採用了以下科學步驟進行 PID 參數調整: 1. **初始設置**: - 清零所有參數 (Kp=0, Ki=0, Kd=0) - 從最簡單的 P 控制開始 2. **調節 Kp(比例項)**: - 小步漸進增加 Kp 值 - 目標:系統能大致接近設定值,但允許存在穩態誤差 - 觀察點:速度 3. **調節 Ki(積分項)**: - 在 Kp 基礎上,緩慢增加 Ki - 目標:減少或消除穩態誤差 - 觀察點:系統穩定性和積分飽和問題 4. **調節 Kd(微分項)**: - 謹慎增加 Kd 值 - 目標:減少超調和振盪 - 觀察點:噪聲敏感度和響應平滑度 5. **整體優化**: - 在實際運行環境中測試參數 - 根據不同運行狀態微調參數 最終確定的 PID 參數是經過大量測試後得出的最佳值:Kp=0.0113, Ki=0.0115, Kd=0。 ## 5. 進階子系統:電梯控制機構 電梯系統用於將機器人的抓取機構提升到不同高度以完成得分任務。 ![電梯與機械臂](https://hackmd.io/_uploads/rk-rKUHCyl.jpg) ### 5.1 電梯子系統設計 ```java public class Elevator extends SubsystemBase { // 馬達與控制器宣告 private final CANSparkMax elevatorMotor; private final SparkMaxPIDController pidController; private final RelativeEncoder encoder; private final DigitalInput limitSwitch; // 極限開關防止電梯過度下降 // 高度轉換參數 private final double gear_diameter = 1.45 * 0.0254; // 齒輪直徑,單位轉為公尺 private final double gear_circle = gear_diameter * Math.PI; // 齒輪周長 public Elevator() { // 初始化馬達和控制器 elevatorMotor = new CANSparkMax(13, MotorType.kBrushless); pidController = elevatorMotor.getPIDController(); encoder = elevatorMotor.getEncoder(); limitSwitch = new DigitalInput(0); // 配置馬達控制器 configSparkMax(); } // 配置SparkMax控制器 private void configSparkMax() { elevatorMotor.restoreFactoryDefaults(); encoder.setPosition(0); // 設定初始位置 elevatorMotor.setIdleMode(IdleMode.kBrake); // 設定煞車模式 elevatorMotor.setSmartCurrentLimit(40); // 限制電流 // 設定PID參數 pidController.setP(0.15); pidController.setI(0.0); pidController.setD(0.1); elevatorMotor.burnFlash(); // 將設定寫入控制器 } // 設定馬達功率方法 public void setPower(double p) { // 安全機制:碰到底部極限開關且要往下移動時停止 if (limitSwitch.get() && p < 0) { elevatorMotor.set(0); encoder.setPosition(0); // 設定碰到極限開關位置為初始位置 } else { elevatorMotor.set(p); } } // 停止電梯 public void stop() { elevatorMotor.set(0); } // 獲取當前高度 public double getPosition() { return encoder.getPosition() / 4 * gear_circle; // 將馬達旋轉數轉換為高度 } // 移動到指定高度方法 public void moveToPosition(double position) { // 根據齒比轉換高度為馬達轉數 double setpoint = position / gear_circle * 4; // 4為齒比 pidController.setReference(setpoint, CANSparkMax.ControlType.kPosition); } @Override public void periodic() { // 定期檢查極限開關 if (limitSwitch.get()) { encoder.setPosition(0); // 重設位置 } } } ``` ### 5.2 電梯命令設計 ```java public class ElevatorMoveToPositionCmd extends CommandBase { private final Elevator elevator; private final double targetPosition; public ElevatorMoveToPositionCmd(Elevator elevator, double targetPosition) { this.elevator = elevator; this.targetPosition = targetPosition; addRequirements(elevator); } @Override public void initialize() { // 命令初始化時無需操作 } @Override public void execute() { elevator.moveToPosition(targetPosition); } @Override public boolean isFinished() { return Math.abs(elevator.getPosition() - targetPosition) < 0.05; // 容差5cm } @Override public void end(boolean interrupted) { elevator.stop(); // 命令結束時停止電梯 } } ``` ### 5.3 電梯按鈕綁定 ```java // 在RobotContainer的configureBindings方法中 private void configureBindings() { // 電梯控制按鈕 joystick.x().onTrue(new ElevatorMoveToPositionCmd(elevator, 0.15)); // X鍵將電梯移動到0.15米高度 joystick.y().onTrue(new ElevatorMoveToPositionCmd(elevator, 0.475)); // Y鍵將電梯移動到0.475米高度 joystick.b().onTrue(new ElevatorMoveToPositionCmd(elevator, 0.35)); // B鍵將電梯移動到0.35米高度 } ``` ## 6. 高級機械控制:機械臂系統 機械臂用於控制機器人的抓取角度,可以提前抓取道具,抬高後放進得分區。 ![LINE_ALBUM_build season Record_250410_6](https://hackmd.io/_uploads/rkcmGDSCJl.jpg) ### 6.1 機械臂子系統設計 ```java public class Arm extends SubsystemBase { // 馬達與控制器宣告 private final CANSparkMax armMotor; private final SparkMaxPIDController pidController; private final RelativeEncoder encoder; // 角度計算與前饋控制 private final double angle_per_rotation = 45.0 / 180.0; // 馬達轉到45°所需的rotation private final ArmFeedforward feedforward = new ArmFeedforward(0, 0.05, 0); // 避免手臂因重力下滑 public Arm() { // 初始化馬達和控制器 armMotor = new CANSparkMax(14, MotorType.kBrushless); pidController = armMotor.getPIDController(); encoder = armMotor.getEncoder(); // 配置馬達控制器 configSparkMax(); } // 配置SparkMax控制器 private void configSparkMax() { armMotor.restoreFactoryDefaults(); encoder.setPosition(0); armMotor.setIdleMode(IdleMode.kBrake); armMotor.setSmartCurrentLimit(20); // 設定PID參數 pidController.setP(0.1); pidController.setI(0.0001); pidController.setD(0.01); armMotor.burnFlash(); } // 設定馬達功率 public void setPower(double p) { armMotor.set(p); } // 停止機械臂 public void stop() { armMotor.set(0); } // 獲取當前角度 public double getAngle() { return encoder.getPosition() * angle_per_rotation; } // 移動到指定角度方法 public void moveToAngle(double targetAngle) { double targetRotation = targetAngle / angle_per_rotation; double ff = feedforward.calculate(Math.toRadians(targetAngle), 0); pidController.setReference(targetRotation, CANSparkMax.ControlType.kPosition, 0, ff); } @Override public void periodic() { // 可以添加診斷資訊輸出 } } ``` ### 6.2 機械臂命令設計 ```java public class ArmMoveToAngleCmd extends CommandBase { private final Arm arm; private final double targetAngle; public ArmMoveToAngleCmd(Arm arm, double targetAngle) { this.arm = arm; this.targetAngle = targetAngle; addRequirements(arm); } @Override public void initialize() { // 命令初始化時無需操作 } @Override public void execute() { arm.moveToAngle(targetAngle); } @Override public boolean isFinished() { return Math.abs(arm.getAngle() - targetAngle) < 3.0; // 容差3度 } @Override public void end(boolean interrupted) { // 命令結束時無需特別停止,因為已達到目標角度 } } ``` ### 6.3 機械臂按鈕綁定 ```java // 在RobotContainer的configureBindings方法中 private void configureBindings() { // 機械臂控制按鈕 joystick.leftBumper().onTrue(new ArmMoveToAngleCmd(arm, 0)); // 左肩鈕使機械臂移動到0度 joystick.rightBumper().onTrue(new ArmMoveToAngleCmd(arm, 45)); // 右肩鈕使機械臂移動到45度 } ``` ## 7. 抓取與Roller機構 ### 7.1 Roller系統設計 Roller系統負責吸入或排出道具,並將它們傳遞給抓取機構。 ![Roller示意圖](https://hackmd.io/_uploads/HkOD_IrCkg.jpg) ```java public class Roller extends SubsystemBase { // 馬達宣告 private final CANSparkMax rollerMotor; public Roller() { rollerMotor = new CANSparkMax(15, MotorType.kBrushless); configSparkMax(); } // 配置SparkMax控制器 private void configSparkMax() { rollerMotor.restoreFactoryDefaults(); rollerMotor.setIdleMode(IdleMode.kCoast); // 設定滑行模式 rollerMotor.setSmartCurrentLimit(30); rollerMotor.burnFlash(); } // 控制滾輪速度方法 public void runRoller(double speed) { rollerMotor.set(speed); // 正值(1.0)表示吸入,負值(-1.0)表示排出 } // 停止方法 public void stop() { rollerMotor.set(0); } } ``` ### 7.2 Roller命令設計 ```java // 吸入命令 public class CoralInCmd extends CommandBase { private final Roller roller; public CoralInCmd(Roller roller) { this.roller = roller; addRequirements(roller); } @Override public void execute() { roller.runRoller(1.0); // 以100%速度吸入 } @Override public void end(boolean interrupted) { roller.stop(); // 停止馬達 } } // 排出命令 public class CoralOutCmd extends CommandBase { private final Roller roller; public CoralOutCmd(Roller roller) { this.roller = roller; addRequirements(roller); } @Override public void execute() { roller.runRoller(-1.0); // 以100%速度排出 } @Override public void end(boolean interrupted) { roller.stop(); // 停止馬達 } } ``` ## 8. 抓取機構(Grab)設計 抓取機構用於夾住道具,配合電梯和機械臂完成得分動作。 ![抓取機構示意圖](https://hackmd.io/_uploads/SJ0J_IrAyx.jpg) ### 8.1 Grab子系統設計 ```java public class Grab extends SubsystemBase { // 馬達宣告 private final CANSparkMax grabMotor; public Grab() { grabMotor = new CANSparkMax(16, MotorType.kBrushless); configSparkMax(); } // 配置SparkMax控制器 private void configSparkMax() { grabMotor.restoreFactoryDefaults(); grabMotor.setIdleMode(IdleMode.kBrake); // 設定煞車模式 grabMotor.setSmartCurrentLimit(20); grabMotor.burnFlash(); } // 抓取方法 public void intake() { grabMotor.set(0.5); // 以50%速度抓取 } // 釋放方法 public void outtake() { grabMotor.set(-0.5); // 以50%速度釋放 } // 停止方法 public void stop() { grabMotor.set(0); } } ``` ### 8.2 Grab命令設計 ```java // 抓取命令 public class GrabIntakeCmd extends CommandBase { private final Grab grab; public GrabIntakeCmd(Grab grab) { this.grab = grab; addRequirements(grab); } @Override public void execute() { grab.intake(); // 執行抓取 } @Override public void end(boolean interrupted) { grab.stop(); // 停止馬達 } } // 釋放命令 public class GrabOuttakeCmd extends CommandBase { private final Grab grab; public GrabOuttakeCmd(Grab grab) { this.grab = grab; addRequirements(grab); } @Override public void execute() { grab.outtake(); // 執行釋放 } @Override public void end(boolean interrupted) { grab.stop(); // 停止馬達 } } ``` ## 9. Yati系統設計(Roller抬升機構) Yati系統用於控制Roller的抬升和放下,以便於道具的收取和傳遞。 ### 9.1 Yati子系統設計 ```java public class Yati extends SubsystemBase { // 馬達宣告 private final CANSparkMax yatiMotor; private final RelativeEncoder encoder; public Yati() { yatiMotor = new CANSparkMax(17, MotorType.kBrushless); encoder = yatiMotor.getEncoder(); configSparkMax(); } // 配置SparkMax控制器 private void configSparkMax() { yatiMotor.restoreFactoryDefaults(); encoder.setPosition(0); yatiMotor.setIdleMode(IdleMode.kBrake); yatiMotor.setSmartCurrentLimit(20); yatiMotor.burnFlash(); } // 控制Yati移動方法 public void runYati(double speed) { yatiMotor.set(speed); } // 獲取當前位置 public double intakePos() { return encoder.getPosition(); } // 停止方法 public void stop() { yatiMotor.set(0); } } ``` ### 9.2 Yati命令設計 ```java // 抬升命令 public class YatiUpCmd extends CommandBase { private final Yati yati; public YatiUpCmd(Yati yati) { this.yati = yati; addRequirements(yati); } @Override public void execute() { yati.runYati(0.3); // 以30%速度抬升 } @Override public void end(boolean interrupted) { yati.stop(); // 停止馬達 } } // 放下命令 public class YatiDownCmd extends CommandBase { private final Yati yati; public YatiDownCmd(Yati yati) { this.yati = yati; addRequirements(yati); } @Override public void execute() { yati.runYati(-0.3); // 以30%速度放下 } @Override public void end(boolean interrupted) { yati.stop(); // 停止馬達 } } ``` ## 10. 控制器整合與操作介面設計 ### 10.1 RobotContainer中的系統初始化 ```java public class RobotContainer { // 底盤系統初始化 private final SwerveChassis chassis = new SwerveChassis(); // 電梯系統初始化 private final Elevator elevator = new Elevator(); // 機械臂系統初始化 private final Arm arm = new Arm(); // Roller系統初始化 private final Roller roller = new Roller(); // 抓取系統初始化 private final Grab grab = new Grab(); // Yati系統初始化 private final Yati yati = new Yati(); // 控制器宣告 private final CommandXboxController controller = new CommandXboxController(0); private final CommandJoystick joystick = new CommandJoystick(1); // 速度參數 private final double maxSpeed = 4.935; // 米/秒 private final double maxOmega = 10.13; // 弧度/秒 public RobotContainer() { // 底盤預設命令設置 configureDefaultCommands(); // 配置按鈕綁定 configureBindings(); } private void configureDefaultCommands() { // 底盤預設命令 chassis.setDefaultCommand( new RunCommand( () -> chassis.drive( // 從左搖桿獲取平移指令(X、Y軸) new Translation2d( -controller.getLeftY() * maxSpeed, -controller.getLeftX() * maxSpeed ), // 從右搖桿獲取旋轉指令(X軸) -controller.getRightX() * maxOmega, true // 使用場地視角控制 ), chassis ) ); } // 其他配置方法... } ``` ### 10.2 按鈕綁定與命令觸發 ```java private void configureBindings() { // 底盤控制 controller.start().onTrue(new InstantCommand(() -> chassis.resetGyro())); // 電梯控制按鈕 joystick.x().onTrue(new ElevatorMoveToPositionCmd(elevator, 0.15)); // X鍵將電梯移動到0.15米高度 joystick.y().onTrue(new ElevatorMoveToPositionCmd(elevator, 0.475)); // Y鍵將電梯移動到0.475米高度 joystick.b().onTrue(new ElevatorMoveToPositionCmd(elevator, 0.35)); // B鍵將電梯移動到0.35米高度 // 機械臂控制 joystick.leftBumper().onTrue(new ArmMoveToAngleCmd(arm, 0)); // 左肩鈕使機械臂移動到0度 joystick.rightBumper().onTrue(new ArmMoveToAngleCmd(arm, 45)); // 右肩鈕使機械臂移動到45度 // Roller控制按鈕 controller.x().whileTrue(new CoralInCmd(roller)); // X鍵按住時持續吸入 controller.y().whileTrue(new CoralOutCmd(roller)); // Y鍵按住時持續排出 // 抓取機構控制 joystick.leftTrigger().whileTrue(new GrabIntakeCmd(grab)); // 左扳機按住時持續抓取 joystick.rightTrigger().whileTrue(new GrabOuttakeCmd(grab)); // 右扳機按住時持續釋放 // Yati控制 controller.a().whileTrue(new YatiUpCmd(yati)); // A鍵按住時Roller抬升 controller.b().whileTrue(new YatiDownCmd(yati)); // B鍵按住時Roller放下 } ``` ## 11. 綜合測試與優化 ### 11.1 底盤調試流程 在完成底盤程式撰寫後,我們進行了一系列的測試和調整: 1. **模組零點校準**: - 使用特製的校準器(如鋁管)將所有輪子對齊到相同方向 - 記錄每個模組的初始絕對角度 - 根據測量結果計算並設定CANcoder的數值 2. **PID參數調整**: - 在實際運行環境中測試不同的PID參數組合 - 收集數據分析轉向精度和穩定性 - 最終確定Kp=0.0113, Ki=0.0115, Kd=0作為最佳參數 3. **場地測試**: - 驗證陀螺儀在連續運行中的漂移情況 - 根據測試結果調整代碼以提高精度 ### 11.2 整合系統測試 在各子系統獨立測試完成後,我們進行了完整機器人的整合測試: 1. **協同動作測試**: - 測試電梯和機械臂的協同運動 - 驗證抓取機構與Roller的配合效果 - 在不同速度下測試底盤與上層機構的互動 2. **操作穩定性測試**: - 模擬比賽的操作測試 - 監控各馬達的溫度和電流消耗 - 分析可能的故障點和系統瓶頸 3. **極限條件測試**: - 測試最大負載下的系統反應(如速度最大化) - 驗證極限位置和角度的安全措施 ## 12. 學習心得與反思 ### 12.1 技術能力的提升 在這次專案中,我深入學習了機器人底盤系統的設計與實作,從單個Swerve模組的控制到整體底盤的運動協調。透過實際編程和調試,我掌握了PID控制器的參數調整技巧,以及如何處理機械系統中的誤差和干擾。 萬向輪底盤的設計與控制不僅要求紮實的編程基礎,還需要對機械原理有充分理解。在程式撰寫過程中,我學會了如何將數學模型轉化為實際的控制邏輯,這種轉化能力對於未來的工程項目至關重要。 ### 12.2 問題解決經驗 面對校正偏移量和PID參數調整的挑戰,我學會了精確分析問題,並進行實驗和優化。這種反覆測試和微調的過程,培養了我的耐心和精細能力,也讓我理解到理論與實際應用之間的差距。 特別是在解決馬達振盪問題時,我通過逐步調整PID參數並分析系統響應,最終找到了穩定系統的最佳參數組合。這種問題解決的方法論不僅適用於機器人控制,也適用於其他工程領域。 ### 12.3 整合系統的複雜性認知 整合多個子系統(底盤、電梯、機械臂、抓取機構)的過程中,我意識到系統設計的複雜性和各組件間的相互影響。這促使我學習如何設計模組化的程式架構,使各子系統既能獨立運作,又能協同工作。 在設計RobotContainer時,我體會到良好的系統架構對於後期開發和維護的重要性。合理的結構設計、清晰的變數定義以及完善的命令系統,都是確保複雜機器人程式順利運行的關鍵。 ### 12.4 工程實踐的寶貴經驗 從程式碼撰寫到硬體調整,再到系統整合測試,每一步都是寶貴的工程實踐經驗。這些經驗不僅提升了我的技術能力,也讓我更深入理解機器人系統的工作原理和設計思路,為未來參與更複雜的專案奠定了堅實基礎。 同時,這次專案也讓我認識到團隊協作的重要性。與機械組、電子組的緊密合作,確保了軟體設計能夠充分考慮硬體限制,最終打造出一個高度整合的機器人系統。