Hello, our team has been working on a swerve drive robot and we started to have some weird issues with the motion of the robot. The bot is using MK4i modules and is being controlled with an Xbox controller. The issue is that when moving the robot in a circle without rotating the robot the wheels will do one revolution and then start to move in the wrong direction.
The robot is supposed to move in a circle but instead after moving around the circle once the modules switch direction which causes the motion to look like this:
The modules are supposed to move continuously in a circle but instead after completing one revolution, they switch direction. This works properly if the rotation optimizer is commented out but the wheels do not rotate efficiently and switch direction a lot. It also works properly if the field-oriented conversions are commented out and in this case, the wheels move continuously and smoothly.
Robot.h:
#pragma once
#include <frc/TimedRobot.h>
#include <frc/XboxController.h>
#include <ctre/Phoenix.h>
#include <iostream>
#include <frc/Timer.h>
#include <string>
#include <frc/Solenoid.h>
#include <frc/smartdashboard/SmartDashboard.h>
#include "frc/smartdashboard/Smartdashboard.h"
#include "networktables/NetworkTable.h"
#include "networktables/NetworkTableInstance.h"
#include "networktables/NetworkTableEntry.h"
#include "networktables/NetworkTableValue.h"
#include <chrono>
#include <thread>
#include <math.h>
#include <cameraserver/CameraServer.h>
#include "AHRS.h"
//#include "frc/span"
class Robot : public frc::TimedRobot {
public:
void RobotInit() override;
void RobotPeriodic() override;
void AutonomousInit() override;
void AutonomousPeriodic() override;
void TeleopInit() override;
void TeleopPeriodic() override;
void DisabledInit() override;
void DisabledPeriodic() override;
void TestInit() override;
void TestPeriodic() override;
//**********************Drive Loop***************
void drive(double FWD, double STR, double RCW){
yaw = ahrs->GetYaw();
if(yaw<0){
yaw += 360;
}
yaw = (yaw * (M_PI/180));
//**************FOC conversions**************************
double temp = ((FWD * cos(yaw)) + (STR * sin(yaw)));
STR = ((-FWD) * sin(yaw)) + (STR * cos(yaw));
FWD = temp;
std::cout<< "FWD "<<FWD<<" STR "<<STR<<" RCW "<< RCW <<std::endl;
//********Setting ratios for wheel distance********
L = 25.75;
W = 25.75;
R = sqrt((L * L) + (W * W));
A = STR - RCW * (L / R);
B = STR + RCW * (L / R);
C = FWD - RCW * (W / R);
D = FWD + RCW * (W / R);
//********Set wheel angles and speeds********
ws1 = sqrt((B * B) + (C * C));
wa1 = atan2(B, C) * 180 / M_PI;
wa1 *= conversionFactor;
ws2 = sqrt((B * B) + (D * D));
wa2 = atan2(B, D) * 180 / M_PI;
wa2 *= conversionFactor;
ws3 = sqrt((A * A) + (D * D));
wa3 = atan2(A, D) * 180 / M_PI;
wa3 *= conversionFactor;
ws4 = sqrt((A * A) + (C * C));
wa4 = atan2(A, C) * 180 / M_PI;
wa4 *= conversionFactor;
//*********Optimize wheel angles********
angleOptimize(oldwa1,wa1);
wa1=outputAngle;
ws1*=direction;
angleOptimize(oldwa2,wa2);
wa2=outputAngle;
ws2*=direction;
angleOptimize(oldwa3,wa3);
wa3=outputAngle;
ws3*=direction;
angleOptimize(oldwa4,wa4);
wa4=outputAngle;
ws4*=direction;
//********Set max speed********
double max = ws1;
if (ws2 > max)max = ws2;
if (ws3 > max)max = ws3;
if (ws4 > max)max = ws4;
if (max > 1) { ws1 /= max; ws2 /= max; ws3 /= max; ws4 /= max; }
//********Set angle motor position********
m_rra.Set(TalonFXControlMode::Position, wa4);
m_fra.Set(TalonFXControlMode::Position, wa1);
m_rla.Set(TalonFXControlMode::Position, wa3);
m_fla.Set(TalonFXControlMode::Position, wa2);
//********Set old angles********
oldwa1 = wa1;
oldwa2 = wa2;
oldwa3 = wa3;
oldwa4 = wa4;
//********Drive Speed!********
double PercentOut = -0.3; // must be negative
//********Set Drive Motors********
m_frd.Set(PercentOut*ws1);
m_fld.Set(PercentOut*ws2);
m_rld.Set(PercentOut*ws3);
m_rrd.Set(PercentOut*ws4);
}
*********************Angle Optimization Algorithm*************************
void angleOptimize(double current, double desired){
direction = 1;
angle1 = (desired - (fmod(current,4096)));
angle2 = ((2048 - abs(fmod(desired,4096))) + (2048 - abs(fmod(current,4096))));
if (abs(angle1) <= abs(angle2)) {
shortest = angle1;
check1 = false;
}
else {
shortest = angle2;
check1 = true;
}
angle1 = ((desired - 2048) - fmod(current,4096));
angle2 = ((2048 - (abs(fmod(desired,4096))-2048)) + (2048 - (abs(fmod(current,4096)))));
if (abs(angle2) < abs(angle1)) {
shortest2 = angle2;
check2 = true;
}
else {
shortest2 = angle1;
check2 = false;
}
if (abs(shortest) <= abs(shortest2)) {
if (check1 == true) {
if (current < 0) {
current -= shortest;
}
else {
current += shortest;
}
}
else {
current += shortest;
}
}
else {
direction = -1;
if (check2 == true) {
if (current < 0) {
current -= shortest2;
}
else {
current += shortest2;
}
}
else {
current += shortest2;
}
}
outputAngle = current;
}
private:
//***********************CONTROLLERS********************************
frc::XboxController m_driverController{0};
//**************MOTORS AND ENCODERS********************************
WPI_TalonFX m_fra{9};
WPI_TalonFX m_frd{11};
WPI_TalonFX m_rra{12};
WPI_TalonFX m_rrd{10};
WPI_TalonFX m_fla{0};
WPI_TalonFX m_fld{5};
WPI_TalonFX m_rla{15};
WPI_TalonFX m_rld{18};
WPI_CANCoder m_rrsensor{5};
WPI_CANCoder m_frsensor{2};
WPI_CANCoder m_rlsensor{1};
WPI_CANCoder m_flsensor{3};
//*******************SWERVE DRIVE VARIABLES*****************************
double conversionFactor = 4096.0/ 360.0;
double L;
double W;
double R;
double A;
double B;
double C;
double D;
double ws1;
double wa1;
double ws2;
double wa2;
double ws3;
double wa3;
double ws4;
double wa4;
double oldwa1;
double oldwa2;
double oldwa3;
double oldwa4;
//**************************NAVX-MXP (Gyro board)**********************
AHRS *ahrs;
double yaw;
//*********************Swerve Drive Optimization************************
double direction;
double angle1;
double angle2;
double shortest;
bool check1;
double shortest2;
bool check2;
double outputAngle;
};
Robot.cpp:
#include "Robot.h"
void Robot::RobotInit() {
m_rra.SetSensorPhase(true);
m_fra.SetSensorPhase(true);
m_rla.SetSensorPhase(true);
m_fla.SetSensorPhase(true);
m_rra.SetNeutralMode(NeutralMode::Brake);
m_fra.SetNeutralMode(NeutralMode::Brake);
m_rla.SetNeutralMode(NeutralMode::Brake);
m_fra.SetNeutralMode(NeutralMode::Brake);
//*******************Configure Rear Right Angle Motor********************************
m_rra.ConfigFactoryDefault();
m_rra.ConfigRemoteFeedbackFilter(5, RemoteSensorSource(13), 0, 0);
m_rra.ConfigSelectedFeedbackSensor(FeedbackDevice::RemoteSensor0, 0, 0); // PIDLoop=0, timeoutMs=0
m_rra.Config_kP(0, 1.7);
m_rra.Config_kI(0, .0016);
m_rra.Config_kD(0, 160);
m_rra.Config_kF(0, 0);
m_rra.Config_IntegralZone(0, 20);
m_rra.ConfigNominalOutputForward(0);
m_rra.ConfigNominalOutputReverse(0);
m_rra.ConfigPeakOutputForward(1);
m_rra.ConfigPeakOutputReverse(-1);
//*******************Configure Front Right Angle Motor*******************************
m_fra.ConfigFactoryDefault();
m_fra.ConfigRemoteFeedbackFilter(2, RemoteSensorSource(13), 0, 0);
m_fra.ConfigSelectedFeedbackSensor(FeedbackDevice::RemoteSensor0, 0, 0); // PIDLoop=0, timeoutMs=0
m_fra.Config_kP(0, 1.7);
m_fra.Config_kI(0, .0016);
m_fra.Config_kD(0, 160);
m_fra.Config_kF(0, 0);
m_fra.Config_IntegralZone(0, 20);
m_fra.ConfigNominalOutputForward(0);
m_fra.ConfigNominalOutputReverse(0);
m_fra.ConfigPeakOutputForward(1);
m_fra.ConfigPeakOutputReverse(-1);
//*******************Configure Rear Left Angle Motor*********************************
m_rla.ConfigFactoryDefault();
m_rla.ConfigRemoteFeedbackFilter(1, RemoteSensorSource(13), 0, 0);
m_rla.ConfigSelectedFeedbackSensor(FeedbackDevice::RemoteSensor0, 0, 0); // PIDLoop=0, timeoutMs=0
m_rla.Config_kP(0, 1.7);
m_rla.Config_kI(0, .0016);
m_rla.Config_kD(0, 160);
m_rla.Config_kF(0, 0);
m_rla.Config_IntegralZone(0, 20);
m_rla.ConfigNominalOutputForward(0);
m_rla.ConfigNominalOutputReverse(0);
m_rla.ConfigPeakOutputForward(1);
m_rla.ConfigPeakOutputReverse(-1);
//*************Configure Front Left Angle Motor********************************
m_fla.ConfigFactoryDefault();
m_fla.ConfigRemoteFeedbackFilter(3, RemoteSensorSource(13), 0, 0);
m_fla.ConfigSelectedFeedbackSensor(FeedbackDevice::RemoteSensor0, 0, 0); // PIDLoop=0, timeoutMs=0
m_fla.Config_kP(0, 1.7);
m_fla.Config_kI(0, .0016);
m_fla.Config_kD(0, 160);
m_fla.Config_kF(0, 0);
m_fla.Config_IntegralZone(0, 20);
m_fla.ConfigNominalOutputForward(0);
m_fla.ConfigNominalOutputReverse(0);
m_fla.ConfigPeakOutputForward(1);
m_fla.ConfigPeakOutputReverse(-1);
//**************Configure Cancoder Magnet Offsets**************************
m_flsensor.ConfigMagnetOffset(100);
m_frsensor.ConfigMagnetOffset(110);
m_rlsensor.ConfigMagnetOffset(250);
m_rrsensor.ConfigMagnetOffset(148);
m_flsensor.SetPositionToAbsolute();
m_frsensor.SetPositionToAbsolute();
m_rlsensor.SetPositionToAbsolute();
m_rrsensor.SetPositionToAbsolute();
//**********************Configure the gyro board****************************
ahrs = new AHRS(frc::I2C::Port::kMXP);
}
void Robot::RobotPeriodic() {}
void Robot::AutonomousInit() {}
void Robot::AutonomousPeriodic() {}
void Robot::TeleopInit() {
//timer.Reset();
//timer.Start();
}
void Robot::TeleopPeriodic() {
double forward = -m_driverController.GetRightY();
double strafe = m_driverController.GetRightX();
double rotate = m_driverController.GetLeftX();
//********Controller Deadzones********
if((forward<.2)&&(forward>-.2)){forward = 0;}
if((strafe<.2)&&(strafe>-.2)){strafe = 0;}
if((rotate<.2)&&(rotate>-.2)){rotate = 0;}
drive(forward, strafe, rotate);
}
void Robot::DisabledInit() {}
void Robot::DisabledPeriodic() {}
void Robot::TestInit() {}
void Robot::TestPeriodic() {}
#ifndef RUNNING_FRC_TESTS
int main() {
return frc::StartRobot<Robot>();
}
#endif
This is the link to our GitHub which has additional videos that CD wouldn’t let me include: GitHub - LovelandHighRobotics1977/Whiplash: Code for Whiplash, team 1977's 2023 charged up FRC robot
Any information or suggestions would be greatly appreciated!