2
2
3
3
import org .littletonrobotics .junction .AutoLogOutput ;
4
4
import org .littletonrobotics .junction .Logger ;
5
-
6
- import com .fasterxml .jackson .annotation .JsonGetter ;
7
5
import com .stuypulse .robot .Robot ;
8
6
import com .stuypulse .robot .constants .Constants ;
7
+ import com .stuypulse .robot .constants .Settings ;
9
8
import com .stuypulse .stuylib .math .SLMath ;
10
9
11
10
import edu .wpi .first .math .Matrix ;
11
+ import edu .wpi .first .math .Nat ;
12
+ import edu .wpi .first .math .VecBuilder ;
12
13
import edu .wpi .first .math .geometry .Rotation2d ;
14
+ import edu .wpi .first .math .geometry .Transform2d ;
13
15
import edu .wpi .first .math .geometry .Translation2d ;
14
16
import edu .wpi .first .math .numbers .N1 ;
15
17
import edu .wpi .first .math .numbers .N2 ;
18
+ import edu .wpi .first .math .system .plant .DCMotor ;
16
19
import edu .wpi .first .wpilibj2 .command .SubsystemBase ;
17
20
18
21
public class DoubleJointedArm extends SubsystemBase {
19
22
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
+ }
21
31
22
32
private DoubleJointedArmIO io ;
23
33
private DoubleJointedArmIOInputsAutoLogged inputs = new DoubleJointedArmIOInputsAutoLogged ();
24
34
35
+ private DoubleJointedArmState state ;
36
+
25
37
public enum DoubleJointedArmState {
26
38
STOW (Rotation2d .fromDegrees (10 ), Rotation2d .fromDegrees (10 ));
27
39
@@ -46,32 +58,221 @@ public Rotation2d getShoulderTargetAngle(){
46
58
public Rotation2d getElbowTargetAngle (){
47
59
return this .elbowTargetAngle ;
48
60
}
61
+ }
49
62
50
- private DoubleJointedArmState state ;
63
+ public DoubleJointedArmState getState (){
64
+ return this .state ;
65
+ }
51
66
52
- public DoubleJointedArmState getState (){
53
- return this .state ;
54
- }
67
+ public DoubleJointedArm (DoubleJointedArmIO io ) {
68
+ this .io = io ;
55
69
}
56
70
57
71
@ Override
58
72
public void periodic () {
59
73
io .updateInputs (inputs );
60
74
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
+
61
81
// 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 );
62
150
}
63
151
64
152
/* LOGIC for controlling the double jointed arm: */
65
153
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 */
67
162
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 ());
69
166
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²
71
177
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 ;
73
180
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
+ );
75
229
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
+ }
77
278
}
0 commit comments