# Swerve drive (TalonFx with CANcoder) ###### tags: `2023`, `swerve`, `frc`, `PID`, `MK4` ## 格式 ### Swerve Module 順序 ![](https://i.imgur.com/OHW5R66.png) 1, 2, 3, 4, 分別為frontLeft, frontRight, rearLeft, rearRight (儘量都按照這個順序寫程式比較不會亂掉) <br/> ### Swerve Module Motor 稱呼 ![](https://i.imgur.com/B3My9oV.png) <font color="#377D22">turning</font> 用於控制驅動方向 <font color="#ED1C24">drive</font> 用於驅動機器人 ### 常數稱呼 (公制單位 m, m/s, s) ![](https://i.imgur.com/riHTTjz.png) <font color="#377D22">軌道寬度 (track width)</font> 兩邊輪子的距離 <font color="#ED1C24">長度 (length) </font> 前後輪子中心的距離 ## 程式 ### 概念 (前言) 大部分邏輯 WPI 的函式庫裡都已經有了,需要做的是把範例程式所使用的馬達改成實際使用的。 :::info 詳細原理可以大概參考: https://compendium.readthedocs.io/en/latest/tasks/drivetrains/swerve.html 因為官方的 Swerve 程式比較晚推出,所以可以在一些早期的文檔中找到其他團隊自己寫的控制程式,有助於理解原理,但非必要 ::: <br/> ### 第一步 (建立專案) 在創建專案的時候選擇:example -> SwerveControllerCommand :::info 主要以 Command-based programming 為架構 下面會稍微介紹,想要瞭解更多可以參考 WPI 的文檔 ::: ![](https://i.imgur.com/m25kjdM.png) #### Subsystem 打開專案後會看到叫做 Subsystems 的資料夾,主要用來存放實體機器人在程式中對應的部分,例如:底盤控制程式都會在 DriveSubsystem 裡面。 而 SwerveModule 則不是一個 Subsystem, 關鍵差別在於有沒有繼承 WPI 的 SubsystemBase. SwerveModule 對應到一組 Swerve, 包含一顆turning motor, drive motor, CANcoder. #### Constant 存放機器人常數的地方,把常數都寫在一起,方便之後修改(改一次而不是在所有用到的地方都要改) 包含軌道寬度、長度、馬達 can ID、CANcoder can ID... #### RobotContainer 可以當作把程式中機器人各個部位組裝起來的地方。 會建立各個子系統的物件,像是 DriveSubsystem 以及控制器 (Joystick, Xbox controller) <br/> ### 第二步 (修改 SwerveModule) :::info 有許多使用到的常數尚未提及,可以先在 Constant 中宣告,讓程式可以編譯,數值取得的方法會在後面說到 ::: #### 2-1 馬達設定 :::info 建議確實理解每一個設定的意義,在尋找錯誤的時候會比較容易 ::: ##### 2-1-1 建立 drive motor, turning motor, cancoder ```java private final WPI_TalonFX m_driveMotor; private final WPI_TalonFX m_turningMotor; private final CANCoder m_turningEncoder; ``` <br/> ##### 2-1-2 在建構式建立馬達實例 ```java m_driveMotor = new WPI_TalonFX(driveMotorChannel); m_turningMotor = new WPI_TalonFX(turningMotorChannel); m_turningEncoder = new CANCoder(turningMotorEncoderChannel); ``` <br/> ##### 2-1-3 在建構式(與類別同名的方法)中初始化、設定馬達 ```java // 避免有其他以前的設定 m_driveMotor.configFactoryDefault(); m_turningMotor.configFactoryDefault(); m_turningEncoder.configFactoryDefault(); ``` <br/> ##### 2-1-4 感測器設定 ```java // 讓 CANcoder 以絕對位置模式啟動,而非相對位置 // 絕對位置模式即使機器人斷電重開還是能夠辨認目前的角度,方便我們知道 turning motor 的朝向 m_turningEncoder.configSensorInitializationStrategy(SensorInitializationStrategy.BootToAbsolutePosition); // 讓 encoder 的數值範圍在 -180 ~ 180 之間 m_turningEncoder.configAbsoluteSensorRange(AbsoluteSensorRange.Signed_PlusMinus180); // turningEncoderReversed 是建構式的參數,設定正反轉 m_turningEncoder.configSensorDirection(turningEncoderReversed); // cancoderOffset 是建構式的參數,設置絕對位置的偏移量 // 之後會寫在 Constants 裡面的,後面會詳細講 m_turningEncoder.configMagnetOffset(cancoderOffset); // 把絕對位置作為相對位置的初始值,之後取用相對位置就可以 m_turningEncoder.setPositionToAbsolute(); ``` <br/> ##### 2-1-5 把 CANcoder 設成 turning motor 的感測器,方便使用 MotionMagic 或 Position closed loop 等 Talon 內建的閉環 :::info 使用馬達控制器的閉環可以減少 Roborio 的計算量,並且由於馬達控制器通常可以以更高的頻率運行閉環控制,會更加精準,減少測量的延遲 ::: ```java // talon 可以設置兩個感測器,第二個參數0 表示將 0號感測器設置成 CANcoder m_turningMotor.configRemoteFeedbackFilter(m_turningEncoder, 0); // 選中設置好的0號遠端感測器 (遠端表示不是 falcon 內建的感測器) m_turningMotor.configSelectedFeedbackSensor(RemoteFeedbackDevice.RemoteSensor0, 0, 10); // 把 1號感測器設為 NONE, 避免自動選中集成編碼器 m_turningMotor.configSelectedFeedbackSensor(FeedbackDevice.None, 1, 10); ``` <br/> drive 的感測器則使用集成編碼器就可以了 ```java m_driveMotor.configSelectedFeedbackSensor(FeedbackDevice.IntegratedSensor); ``` <br/> ##### 2-1-6 設定馬達輸出正反轉、傳感器相位 三個變數都用建構式的參數傳入,方便我們把正反轉、及上面的絕對編碼器偏移都寫在 Constants 中,並給每個 Swerve Module 不同的值 ```java m_driveMotor.setInverted(driveReversed); m_driveMotor.setSensorPhase(driveEncoderReversed); m_turningMotor.setInverted(turningMotorReversed); ``` <br/> ##### 2-1-7 設定沒有輸出時的模式 ```java // 設定為 Brake, 避免 drive 馬達在轉動時也改變到角度 m_turningMotor.setNeutralMode(NeutralMode.Brake); // drive 馬達則是 Coast, Brake 都可以,個人習慣是 Coast, 方便推動機器人測試 m_driveMotor.setNeutralMode(NeutralMode.Coast); ``` <br/> #### 2-2 新增得到感測器狀態的方法 (方便修改其他地方) ##### 2-2-1 角度傳換成原始單位的方法 ```java // 把從 CANcoder 取得的角度值轉換成原始單位 // CANcoder 轉一圈會增加 4096 個原始單位 // 因此將 角度 / 360 可以知道目前是轉了幾分之幾圈 // 乘上 4096,將幾分之幾圈以原始單位表示 private double deg2raw(double deg) { return deg / 360 * 4096; } ``` :::info falcon 的集成編碼器旋轉一圈是增加 2048 (分辨率),但是將 CANcoder設作 falcon 的傳感器之後,分辨率就會變成 CANcoder 的分辨率 4096 ::: <br/> ##### 2-2-2 drive encoder position 的方法 ```java // 回傳 drive 的 encoder 位置,並將原始單位變為公尺 public double getDriveEncoderPosition() { return m_driveMotor.getSelectedSensorPosition() * ModuleConstants.kDriveCoefficient; // m } ``` <br/> ##### 2-2-3 drive encoder velocity 的方法 ```java // 回傳 drive 的速度,因為 talon 函式回傳的是 raw unit per 100ms,所以除了將長度單位換成公尺外,也需要乘10,將100毫秒變成秒 public double getDriveEncoderVelocity() { return m_driveMotor.getSelectedSensorVelocity() * ModuleConstants.kDriveCoefficient * 10; // m/s } ``` <br/> ##### 2-2-4 turning encoder angle 的方法 ```java // 回傳 turning 的角度,並將原始單位換成角度 public double getTurningEncoderAngle() { return m_turningMotor.getSelectedSensorPosition() / 4096.0 * 360.0; // deg } ``` <br/> ##### 2-2-5 建立好上面4個方法之後,就可以將其他地方改成對應的函式,下面只提供一個例子 ```java public SwerveModulePosition getPosition() { return new SwerveModulePosition(getDriveEncoderPosition(), Rotation2d.fronDegrees(getTurningEncoderAngle())); } ``` <br/> #### 2-3 修改 setDesiredState 方法 ##### 2-3-1 因為希望使用 Talon 內建的 PID, 所以我們需要在剛剛設定馬達的地方加上 PID 值,以及 MotionMagic 會需要的兩個設定 ```java // 設定 PID, 數值需要自己調整 (未調整前可能會沒辦法動) // 附上純底盤時的參考數值,如果要運作得更好仍然需要依實際情況調整 m_driveMotor.config_kF(0, 0); // 0.065 m_driveMotor.config_kP(0, 0); // 0.15 m_driveMotor.config_kI(0, 0); m_driveMotor.config_kD(0, 0); m_turningMotor.config_kF(0, 0); // 0.14 m_turningMotor.config_kP(0, 0); // 1.2 m_turningMotor.config_kI(0, 0); m_turningMotor.config_kD(0, 0); ``` <br/> ##### 2-3-2 MotionMagic 的參數 :::info MotionMagic 和 position closed loop 功能類似,都能讓電機轉到指定的 encoder 位置 但是 MotionMagic 可以遵循我們設定的最大加速度、最大速度,讓動作更流暢,避免一般 PID 的大震盪 詳細可以參考 CTRE DOC ::: ```java // 設定最大加速度 m_turningMotor.configMotionAcceleration(4096); // 設定最大速度 m_turningMotor.configMotionCruiseVelocity(5108); ``` <br/> ##### 2-3-3 setDesiredState 方法中, ```java SwerveModuleState state = SwerveModuleState.optimize(desiredState, Rotation2d.fronDegrees(getTurningEncoderAngle())); ``` 是用來優化轉向的,例如,當馬達需要 180 度時,可以優化成旋轉 0 度,並反轉 drive 的輸出 <br/> ##### 2-3-4 接著,將優化過的目標狀態設給馬達 ```java // 將 m/s 轉換成 raw unit / 100ms, 並設給 drive m_driveMotor.set(ControlMode.Velocity, state.speedMetersPerSecond / ModuleConstants.kDriveCoefficient / 10.0); // 將角度轉換成 raw unit, 並設給 turning m_turningMotor.set(ControlMode.MotionMagic, deg2raw(state.angle.getDegrees())); ``` :::success 到此大概就改完這個部份了,其實主要的部份都圍繞在單位轉換上,需要注意個個函式輸入輸出的單位。 ::: <br/> ### 第三步 (修改 DriveSubsystem) 將慣性測量單元換成我們使用的硬體,以 Pigeon2 為例 ```java // 宣告 private final WPI_Pigeon2 m_gyro = new WPI_Pigeon2(DriveConstants.kPigeon2Port); // 建構式中的初始化 public DriveSubsystem() { // 避免之前的設定殘留 m_gyro.configFactoryDefault(); // 把所有設定都包裝成一個物件 Pigeon2Configuration config = new Pigeon2Configuration(); // 安裝的姿態 config.MountPosePitch = 0; config.MountPoseRoll = 0; config.MountPoseYaw = 0; config.EnableCompass = true; config.DisableNoMotionCalibration = false; config.DisableTemperatureCompensation = false; config.enableOptimizations = false; m_gyro.configAllSettings(config); // 重設方位 m_gyro.setYaw(0); } ``` :::info 使用帶有 WPI_ 前綴的 Pigeon2 類別,會自動反轉 Yaw 讀值,自動符合其他 WPI 庫的習慣 ::: <br/> ### 第四步 (取得各項常數) :::warning 需要有一台機器人!!!!!!!! ::: #### 4-1 取得 CANcoder 偏移量 ![](https://i.imgur.com/kjw2HRJ.png) 將輪子都轉向同一個方向 **確保輸出為正的時候,輪子都向前轉** e.g., percentOutput 0.3 每顆輪子都是向前轉 確認完成後,就可以用 Phoenix Tuner 的 self-test 功能得到 目前 CANcoder 的值,加上負號即是該顆 CANcoder 的偏移量 ```java // example (in Constants.java) public static final double kCancoderOffset1 = -212.43164062; public static final double kCancoderOffset2 = -164.1796825; public static final double kCancoderOffset3 = -319.921875; public static final double kCancoderOffset4 = -68.46679688; ``` :::info 因為我們設置的偏移量會在讀值時加進感測器的數值中,因此需要加上負號 ::: :::warning 確定馬達轉向可以避免之後出現奇怪的問題 ::: <br/> #### 4-2 計算 kDriveCoefficient 該常數用來把 Drive 馬達的讀數(raw unit) 轉換成以公尺為單位 我們知道 falcon 轉一圈是 2048, 輪胎轉一圈在不打滑的情況下會前進輪胎的圓周長;若有齒輪比(通常是減小),則輪子轉一圈實際上可能是馬達轉好幾圈,因此計算方式為: <br/> $WheelDiameter * Math.PI\; /\; 2048\; /\; gearRatio$ ```java public static final double kDriveCoefficient = 0.1 * Math.PI / kEncoderCPR / 8.14; ``` :::info 齒輪比通常可以在產品介紹上找到,以 MK4 standard 為例,是8.14,代表馬達轉 8.14 圈,輪胎轉一圈 ::: <br/> #### 4-3 馬達、感測器正反轉 如果在 4-1 有確實做好,馬達的部分應該不需要調整,否則就是反轉到輸出的正向與機器人正向相同 **感測器部分需要確定馬達輸出正向會讓讀值增加** :::info 可以用 Phoenix Tuner 的 Control, self-test 功能 ::: ```java public static final boolean kFrontLeftDriveEncoderReversed = true; public static final boolean kRearLeftDriveEncoderReversed = false; public static final boolean kFrontRightDriveEncoderReversed = true; public static final boolean kRearRightDriveEncoderReversed = false; ``` <br/> #### 4-4 Track width, length 測量左右輪子中心距離、前後輪子中心距離 ```java public static final double kTrackWidth = 0.5; public static final double kWheelBase = 0.5; ``` ### 第五步 (馬達設定優化) #### 5-1 Current limit 由於底盤馬達需要承載整個機器人的重量,加上一些寬鬆的電流保護可以避免馬達受損 ```java // 啟用電流限制, 最大電流 30A, 瞬間最大電流 50A, 0.1秒以下視為瞬間 var voltage = new SupplyCurrentLimitConfiguration(true, 30, 50, 0.1); m_driveMotor.configSupplyCurrentLimit(voltage); ``` :::warning 太嚴格的電流限制可能會導致馬達不能動作 ::: <br/> #### 5-2 Voltage compensation 設定電壓補償可以讓機器人在該設定電壓之上都正常運作,避免因為電池狀態造成輸出不一致 ```java m_driveMotor.enableVoltageCompensation(true); m_driveMotor.configVoltageCompSaturation(9); m_turningMotor.enableVoltageCompensation(true); m_turningMotor.configVoltageCompSaturation(9); ``` :::info 設定為 9, 表示當 percentOutput 1 時,會輸出 9V 的電壓; 但若是電池實際電壓低於 9V, 仍會造成輸出不一致,但是實際比賽中不應該讓電池電壓那麼低 ::: <br/> #### 5-3 Neutral Deadband 設定死區,當目標輸出小於設定值,就不輸出,可以讓無用的過小輸出歸零,避免在閉環控制時不斷消耗電量 ```java m_driveMotor.configNeutralDeadband(0.05); m_turningMotor.configNeutralDeadband(0.1); ``` :::info 更多 NeutralDeadband 的資訊:https://v5.docs.ctr-electronics.com/en/stable/ch13_MC.html#peak-nominal-outputs ::: <br/> #### 5-4 Nominal output 設定 Nominal output, 可以用來取代 PID 中的 I, 尤其轉向馬達需要完全精準 ```java m_turningMotor.configNominalOutputForward(0.095); m_turningMotor.configNominalOutputReverse(-0.095); ``` :::info Nominal output 可以將高於 Neutral Deadband 設定值的輸出提高,確保該輸出能夠讓馬達運動 更多資訊:https://v5.docs.ctr-electronics.com/en/stable/ch13_MC.html#peak-nominal-outputs ::: ### 最後 調整 PID 值,第一個可以從 turning 的精確度開始,再調整 drive 的速度精確度; :::info turning 應該調到完全精準,即使是微小的誤差,也會在長距離的直線行駛中變得明顯 ::: ~~希望會有因為這篇文章而成功的人類థ౪థ~~