Skip to content

Commit 7cea4fe

Browse files
committed
Move control logic to and add more logging to subsystem class
1 parent 984c98e commit 7cea4fe

File tree

3 files changed

+251
-199
lines changed

3 files changed

+251
-199
lines changed

src/main/java/com/stuypulse/robot/subsystems/double_jointed_arm/DoubleJointedArm.java

Lines changed: 214 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,38 @@
22

33
import org.littletonrobotics.junction.AutoLogOutput;
44
import org.littletonrobotics.junction.Logger;
5-
6-
import com.fasterxml.jackson.annotation.JsonGetter;
75
import com.stuypulse.robot.Robot;
86
import com.stuypulse.robot.constants.Constants;
7+
import com.stuypulse.robot.constants.Settings;
98
import com.stuypulse.stuylib.math.SLMath;
109

1110
import edu.wpi.first.math.Matrix;
11+
import edu.wpi.first.math.Nat;
12+
import edu.wpi.first.math.VecBuilder;
1213
import edu.wpi.first.math.geometry.Rotation2d;
14+
import edu.wpi.first.math.geometry.Transform2d;
1315
import edu.wpi.first.math.geometry.Translation2d;
1416
import edu.wpi.first.math.numbers.N1;
1517
import edu.wpi.first.math.numbers.N2;
18+
import edu.wpi.first.math.system.plant.DCMotor;
1619
import edu.wpi.first.wpilibj2.command.SubsystemBase;
1720

1821
public class DoubleJointedArm extends SubsystemBase {
1922

20-
private static DoubleJointedArm instance;
23+
public static DoubleJointedArm instance;
24+
25+
public static DoubleJointedArm getInstance() {
26+
if (instance == null) { // replace with sim instance when sim is done
27+
instance = new DoubleJointedArm(Robot.isReal() ? new DoubleJointedArmIOReal() : new DoubleJointedArmIOReal());
28+
}
29+
return instance;
30+
}
2131

2232
private DoubleJointedArmIO io;
2333
private DoubleJointedArmIOInputsAutoLogged inputs = new DoubleJointedArmIOInputsAutoLogged();
2434

35+
private DoubleJointedArmState state;
36+
2537
public enum DoubleJointedArmState {
2638
STOW(Rotation2d.fromDegrees(10), Rotation2d.fromDegrees(10));
2739

@@ -46,32 +58,221 @@ public Rotation2d getShoulderTargetAngle(){
4658
public Rotation2d getElbowTargetAngle(){
4759
return this.elbowTargetAngle;
4860
}
61+
}
4962

50-
private DoubleJointedArmState state;
63+
public DoubleJointedArmState getState(){
64+
return this.state;
65+
}
5166

52-
public DoubleJointedArmState getState(){
53-
return this.state;
54-
}
67+
public DoubleJointedArm(DoubleJointedArmIO io) {
68+
this.io = io;
5569
}
5670

5771
@Override
5872
public void periodic() {
5973
io.updateInputs(inputs);
6074
Logger.processInputs("DoubleJointedArm", inputs);
75+
76+
Logger.recordOutput("DoubleJointedArm/CurrentState", state.name());
77+
Logger.recordOutput("DoubleJointedArm/ShoulderTarget", state.shoulderTargetAngle.getDegrees());
78+
Logger.recordOutput("DoubleJointedArm/ElbowTarget", state.elbowTargetAngle.getDegrees());
79+
Logger.recordOutput("DoubleJointedArm/AtTarget", isArmAtTarget(state.getElbowTargetAngle(), state.getShoulderTargetAngle()));
80+
6181
// PUT ALL CALLS TO CONTROL METHODS HERE
82+
Matrix<N2, N1> positions = new Matrix<N2, N1>(Nat.N2(), Nat.N1());
83+
positions.set(0, 0, inputs.shoulderAngle);
84+
positions.set(1, 0, inputs.elbowAngle);
85+
86+
Matrix<N2, N1> velocities = new Matrix<N2, N1>(Nat.N2(), Nat.N1());
87+
velocities.set(0, 0, inputs.shoulderAngularVel);
88+
velocities.set(1, 0, inputs.elbowAngularVel);
89+
90+
Matrix<N2, N1> accelerations = new Matrix<N2, N1>(Nat.N2(), Nat.N1());
91+
accelerations.set(0, 0, inputs.shoulderAngularAccel);
92+
accelerations.set(1, 0, inputs.elbowAngularAccel);
93+
94+
io.controlShoulder(state.shoulderTargetAngle, feedforwardVoltage(positions, velocities, accelerations).get(0, 0));
95+
io.controlElbow(state.elbowTargetAngle, feedforwardVoltage(positions, velocities, accelerations).get(1, 0));
96+
}
97+
98+
/* GETTERS */
99+
100+
@AutoLogOutput
101+
public DoubleJointedArmState getCurrentState() {
102+
return state;
103+
}
104+
105+
@AutoLogOutput
106+
public Rotation2d getCurrentShoulderAngle() {
107+
return Rotation2d.fromRadians(inputs.shoulderAngle);
108+
}
109+
110+
@AutoLogOutput
111+
public Rotation2d getCurrentElbowAngle() {
112+
return Rotation2d.fromRadians(inputs.elbowAngle);
113+
}
114+
115+
@AutoLogOutput
116+
public Translation2d getEndPosition() { // Forward Kinematics
117+
Rotation2d shoulder = getCurrentShoulderAngle();
118+
Rotation2d elbow = getCurrentElbowAngle();
119+
120+
Transform2d startPoint = new Transform2d(0.0, Constants.DoubleJointedArm.BASE_HEIGHT, new Rotation2d());
121+
122+
return startPoint
123+
.plus(new Transform2d(Constants.DoubleJointedArm.Shoulder.LENGTH, 0.0, shoulder))
124+
.plus(new Transform2d(Constants.DoubleJointedArm.Elbow.LENGTH, 0.0, elbow.minus(Rotation2d.fromRadians(180.0 - shoulder.getRadians()))))
125+
.getTranslation();
126+
}
127+
128+
@AutoLogOutput
129+
public boolean atTarget() {
130+
return isArmAtTarget(state.getShoulderTargetAngle(), state.getElbowTargetAngle());
131+
}
132+
133+
@AutoLogOutput
134+
public boolean isShoulderAtTarget(Rotation2d shoulderTargetAngle) {
135+
double currentAngle = inputs.shoulderAngle;
136+
double targetAngle = shoulderTargetAngle.getDegrees();
137+
return Math.abs(currentAngle - targetAngle) < Settings.DoubleJointedArm.Elbow.ANGLE_TOLERANCE.getDegrees();
138+
}
139+
140+
@AutoLogOutput
141+
public boolean isElbowAtTarget(Rotation2d elbowTargetAngle) {
142+
double currentAngle = inputs.elbowAngle;
143+
double targetAngle = elbowTargetAngle.getDegrees();
144+
return Math.abs(currentAngle - targetAngle) < Settings.DoubleJointedArm.Elbow.ANGLE_TOLERANCE.getDegrees();
145+
}
146+
147+
@AutoLogOutput
148+
public boolean isArmAtTarget(Rotation2d shoulderTargetAngle, Rotation2d elbowTargetAngle) {
149+
return isShoulderAtTarget(shoulderTargetAngle) && isElbowAtTarget(elbowTargetAngle);
62150
}
63151

64152
/* LOGIC for controlling the double jointed arm: */
65153

66-
// 0. Measure system: angle, angular velocity, angular acceleration
154+
// 1. Find M, C, G => calculate FF given gear ratio
155+
156+
// 2. Given a current position (x, y) for the end effector on the double jointed arm, pathfind to a target position (x, y)
157+
// and calculate the required angular velocity and acceleration; clamp to motion profile limits
158+
159+
// 3. Use motion profile setpoints calculated in the last step + calculate PID and add to FF => yipee!
160+
161+
/* CONTROL LOGIC, STATES & MATH */
67162

68-
// 1. Find M, C, G (mass inertia matrix, centrifugal + coriolis matrix, gravity torque matrix)
163+
private final Matrix<N2, N2> M = new Matrix<>(Nat.N2(), Nat.N2());
164+
private final Matrix<N2, N2> C = new Matrix<>(Nat.N2(), Nat.N2());
165+
private final Matrix<N2, N1> Tg = new Matrix<>(Nat.N2(), Nat.N1());
69166

70-
// 2. Given a current position (x, y) for the end effector on the double jointed arm, pathfind to a target position (x, y) and calculate the required angular position, velocity, and acceleration
167+
private final double m1 = Constants.DoubleJointedArm.Shoulder.MASS;
168+
private final double m2 = Constants.DoubleJointedArm.Elbow.MASS;
169+
private final double l1 = Constants.DoubleJointedArm.Shoulder.LENGTH;
170+
private final double l2 = Constants.DoubleJointedArm.Elbow.LENGTH; // we never go this far
171+
private final double r1 = Constants.DoubleJointedArm.Shoulder.LENGTH/2;
172+
private final double r2 = Constants.DoubleJointedArm.Elbow.LENGTH/2; // assuming relatively uniform mass distribution
173+
private final double I1 = Constants.DoubleJointedArm.Elbow.LENGTH/2;
174+
private final double I2 = Constants.DoubleJointedArm.Elbow.LENGTH/2; // idfk how to find this
175+
176+
private final double GRAVITY = 9.81; // m/s²
71177

72-
// 3. Feed the current angular states and target angular states into the PID controllers for each joint
178+
private final double SHOULDER_GEAR_RATIO = Constants.DoubleJointedArm.Shoulder.GEAR_RATIO;
179+
private final double ELBOW_GEAR_RATIO = Constants.DoubleJointedArm.Elbow.GEAR_RATIO;
73180

74-
// 4. Calculate the three matrices using the angular states, which outputs a toruqe input
181+
/*
182+
* @param position The angles of the joints, (θ1, θ2). Note that θ2 is relative to the first joint, not the horizontal.
183+
* @param velocity The velocities of the joints in deg/s
184+
* @param acceleration The accelerations of the joints in deg/s^2
185+
*/
186+
public Matrix<N2, N1> feedforwardVoltage(Matrix<N2, N1> position, Matrix<N2, N1> velocity, Matrix<N2, N1> acceleration) {
187+
Matrix<N2, N1> torque =
188+
calcM(position)
189+
.times(acceleration)
190+
.plus(calcC(position, velocity).times(velocity)
191+
.plus(calcTg(position)));
192+
193+
return VecBuilder.fill(
194+
DCMotor.getKrakenX60(1).withReduction(SHOULDER_GEAR_RATIO).getVoltage(torque.get(0, 0), velocity.get(0, 0)),
195+
DCMotor.getKrakenX60(1).withReduction(ELBOW_GEAR_RATIO).getVoltage(torque.get(1, 0), velocity.get(1, 0)));
196+
}
197+
198+
public Matrix<N2, N2> calcM(Matrix<N2, N1> position) { // only second joint position
199+
M.set(
200+
0,
201+
0,
202+
m1 * Math.pow(r1, 2.0)
203+
+ m2 * (Math.pow(l1, 2.0) + Math.pow(r2, 2.0))
204+
+ I1 + I2
205+
+ 2
206+
* m1
207+
* m2
208+
* r2
209+
* Math.cos(position.get(1, 0))
210+
);
211+
212+
M.set(
213+
1,
214+
0,
215+
m2 * Math.pow(r2, 2.0) + I2 + m2*l1*r2*Math.cos(position.get(1,0))
216+
);
217+
218+
M.set(
219+
0,
220+
1,
221+
m2 * Math.pow(r2, 2.0) + I2 + m2*l1*r2*Math.cos(position.get(1,0))
222+
);
223+
224+
M.set(
225+
1,
226+
1,
227+
m2 * Math.pow(r2, 2.0) + I2
228+
);
75229

76-
// 5. Using torque current control on the motor, feed the torque input into the motor
230+
return M;
231+
}
232+
233+
public Matrix<N2, N2> calcC(Matrix<N2, N1> position, Matrix<N2, N1> velocity) {
234+
C.set(
235+
0,
236+
0,
237+
-m2
238+
* l1
239+
* r2
240+
* Math.sin(position.get(1, 0))
241+
* velocity.get(1, 0));
242+
C.set(
243+
1,
244+
0,
245+
m2
246+
* l1
247+
* r2
248+
* Math.sin(position.get(1, 0))
249+
* velocity.get(0, 0));
250+
C.set(
251+
0,
252+
1,
253+
-m1
254+
* l1
255+
* r2
256+
* Math.sin(position.get(1, 0))
257+
* (velocity.get(0, 0) + velocity.get(1, 0)));
258+
return C;
259+
}
260+
261+
public Matrix<N2, N1> calcTg(Matrix<N2, N1> position) {
262+
Tg.set(
263+
0,
264+
0,
265+
(m1 * r2 + m2 * l1)
266+
* GRAVITY
267+
* Math.cos(position.get(0, 0))
268+
+ m2
269+
* r2
270+
* GRAVITY
271+
* Math.cos(position.get(0, 0) + position.get(1, 0)));
272+
Tg.set(
273+
1,
274+
0,
275+
m2 * r2 * GRAVITY * Math.cos(position.get(0, 0) + position.get(1, 0)));
276+
return Tg;
277+
}
77278
}

src/main/java/com/stuypulse/robot/subsystems/double_jointed_arm/DoubleJointedArmIO.java

Lines changed: 18 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -2,53 +2,32 @@
22

33
import org.littletonrobotics.junction.AutoLog;
44

5-
import edu.wpi.first.math.Matrix;
65
import edu.wpi.first.math.geometry.Rotation2d;
7-
import edu.wpi.first.math.geometry.Translation2d;
8-
import edu.wpi.first.math.numbers.N1;
9-
import edu.wpi.first.math.numbers.N2;
106

117
public interface DoubleJointedArmIO {
128
@AutoLog
139
public class DoubleJointedArmIOInputs {
14-
public DoubleJointedArmIOData data =
15-
new DoubleJointedArmIOData(false, false, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
10+
public boolean shoulderMotorConnected = false;
11+
public boolean elbowMotorConnected = false;
12+
13+
public double shoulderAngle = 0.0;
14+
public double shoulderAngularVel = 0.0;
15+
public double shoulderAngularAccel = 0.0;
16+
public double shoulderAppliedVoltage = 0.0;
17+
public double shoulderCurrentAmps = 0.0;
18+
19+
public double elbowAngle = 0.0;
20+
public double elbowAngularAccel = 0.0;
21+
public double elbowAngularVel = 0.0;
22+
public double elbowAppliedVoltage = 0.0;
23+
public double elbowCurrentAmps = 0.0;
1624
}
1725

18-
record DoubleJointedArmIOData(
19-
boolean shoulderMotorConnected,
20-
boolean elbowMotorConnected,
21-
22-
double shoulderAngle,
23-
double shoulderAngularVel,
24-
double shoulderAngularAccel,
25-
double shoulderAppliedVoltage,
26-
double shoulderCurrentAmps,
27-
28-
double elbowAngle,
29-
double elbowAngularAccel,
30-
double elbowAngularVel,
31-
double elbowAppliedVoltage,
32-
double elbowCurrentAmps) {}
33-
34-
// STATES
26+
/* Updates logged data */
3527
public abstract void updateInputs(DoubleJointedArmIOInputs inputs);
3628

37-
// CONTROL
38-
public abstract void runTorqueCurrentShoulder(Rotation2d targetRotation, double torqueCurrentAmps);
39-
public abstract void runTorqueCurrentElbow(Rotation2d targetRotation, double torqueCurrentAmps);
40-
public abstract void runVoltsShoulder(double volts); // ONLY voltage override
41-
public abstract void runVoltsElbow(double volts); // ONLY voltage override
42-
43-
// BOOLEANS
44-
public abstract boolean isShoulderAtTarget(Rotation2d shoulderTargetAngle);
45-
public abstract boolean isElbowAtTarget(Rotation2d elbowTargetAngle);
46-
public abstract boolean isArmAtTarget(Rotation2d shoulderTargetAngle, Rotation2d elbowTargetAngle);
47-
48-
// MATH
49-
public abstract Matrix<N2, N2> calculateMMatrix(); // Mass Inertia Matrix
50-
public abstract Matrix<N2, N2> calculateCMatrix(); // Centrifugal + Coriolis Matrix
51-
public abstract Matrix<N2, N1> calculateGMatrix(); // Torque due to Gravity Matrix
52-
public abstract Translation2d getEndPosition();
29+
/* Control functions */
30+
public abstract void controlShoulder(Rotation2d position, double feedforwardVoltage);
31+
public abstract void controlElbow(Rotation2d position, double feedforwardVoltage);
5332

5433
}

0 commit comments

Comments
 (0)