-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathCTREMotionProfileFollower.java
300 lines (277 loc) · 9.89 KB
/
CTREMotionProfileFollower.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
/**
* Example logic for firing and managing motion profiles.
* This example sends MPs, waits for them to finish
* Although this code uses a CANTalon, nowhere in this module do we changeMode() or call set() to change the output.
* This is done in Robot.java to demonstrate how to change control modes on the fly.
*
* The only routines we call on Talon are....
*
* changeMotionControlFramePeriod
*
* getMotionProfileStatus
* clearMotionProfileHasUnderrun to get status and potentially clear the error flag.
*
* pushMotionProfileTrajectory
* clearMotionProfileTrajectories
* processMotionProfileBuffer, to push/clear, and process the trajectory points.
*
* getControlMode, to check if we are in Motion Profile Control mode.
*
* Example of advanced features not demonstrated here...
* [1] Calling pushMotionProfileTrajectory() continuously while the Talon executes the motion profile, thereby keeping it going indefinitely.
* [2] Instead of setting the sensor position to zero at the start of each MP, the program could offset the MP's position based on current position.
*/
package org.usfirst.frc.team8.robot;
import com.ctre.CANTalon;
import edu.wpi.first.wpilibj.Notifier;
import com.ctre.CANTalon.TalonControlMode;
public class CTREMotionProfileFollower {
/**
* The status of the motion profile executer and buffer inside the Talon.
* Instead of creating a new one every time we call getMotionProfileStatus,
* keep one copy.
*/
private CANTalon.MotionProfileStatus _status = new CANTalon.MotionProfileStatus();
/**
* reference to the talon we plan on manipulating. We will not changeMode()
* or call set(), just get motion profile status and make decisions based on
* motion profile.
*/
private CANTalon _talon;
/**
* State machine to make sure we let enough of the motion profile stream to
* talon before we fire it.
*/
private int _state = 0;
/**
* Any time you have a state machine that waits for external events, its a
* good idea to add a timeout. Set to -1 to disable. Set to nonzero to count
* down to '0' which will print an error message. Counting loops is not a
* very accurate method of tracking timeout, but this is just conservative
* timeout. Getting time-stamps would certainly work too, this is just
* simple (no need to worry about timer overflows).
*/
private int _loopTimeout = -1;
/**
* If start() gets called, this flag is set and in the control() we will
* service it.
*/
private boolean _bStart = false;
/**
* Since the CANTalon.set() routine is mode specific, deduce what we want
* the set value to be and let the calling module apply it whenever we
* decide to switch to MP mode.
*/
private CANTalon.SetValueMotionProfile _setValue = CANTalon.SetValueMotionProfile.Disable;
/**
* How many trajectory points do we wait for before firing the motion
* profile.
*/
private static final int kMinPointsInTalon = 5;
/**
* Just a state timeout to make sure we don't get stuck anywhere. Each loop
* is about 20ms.
*/
private static final int kNumLoopsTimeout = 10;
/**
* Lets create a periodic task to funnel our trajectory points into our talon.
* It doesn't need to be very accurate, just needs to keep pace with the motion
* profiler executer. Now if you're trajectory points are slow, there is no need
* to do this, just call _talon.processMotionProfileBuffer() in your teleop loop.
* Generally speaking you want to call it at least twice as fast as the duration
* of your trajectory points. So if they are firing every 20ms, you should call
* every 10ms.
*/
class PeriodicRunnable implements java.lang.Runnable {
public void run() { _talon.processMotionProfileBuffer(); }
}
Notifier _notifer = new Notifier(new PeriodicRunnable());
/**
* C'tor
*
* @param talon
* reference to Talon object to fetch motion profile status from.
*/
public CTREMotionProfileFollower(CANTalon talon) {
_talon = talon;
/*
* since our MP is 10ms per point, set the control frame rate and the
* notifer to half that
*/
_talon.changeMotionControlFramePeriod(5);
_notifer.startPeriodic(0.005);
}
/**
* Called to clear Motion profile buffer and reset state info during
* disabled and when Talon is not in MP control mode.
*/
public void reset() {
/*
* Let's clear the buffer just in case user decided to disable in the
* middle of an MP, and now we have the second half of a profile just
* sitting in memory.
*/
_talon.clearMotionProfileTrajectories();
/* When we do re-enter motionProfile control mode, stay disabled. */
_setValue = CANTalon.SetValueMotionProfile.Disable;
/* When we do start running our state machine start at the beginning. */
_state = 0;
_loopTimeout = -1;
/*
* If application wanted to start an MP before, ignore and wait for next
* button press
*/
_bStart = false;
}
/**
* Called every loop.
*/
public void control() {
/* Get the motion profile status every loop */
_talon.getMotionProfileStatus(_status);
/*
* track time, this is rudimentary but that's okay, we just want to make
* sure things never get stuck.
*/
if (_loopTimeout < 0) {
/* do nothing, timeout is disabled */
} else {
/* our timeout is nonzero */
if (_loopTimeout == 0) {
/*
* something is wrong. Talon is not present, unplugged, breaker
* tripped
*/
System.out.println("Loop timeout is 0!");
} else {
--_loopTimeout;
}
}
/* first check if we are in MP mode */
if (_talon.getControlMode() != TalonControlMode.MotionProfile) {
/*
* we are not in MP mode. We are probably driving the robot around
* using gamepads or some other mode.
*/
_state = 0;
_loopTimeout = -1;
} else {
/*
* we are in MP control mode. That means: starting Mps, checking Mp
* progress, and possibly interrupting MPs if thats what you want to
* do.
*/
switch (_state) {
case 0: /* wait for application to tell us to start an MP */
if (_bStart) {
_bStart = false;
_setValue = CANTalon.SetValueMotionProfile.Disable;
startFilling();
/*
* MP is being sent to CAN bus, wait a small amount of time
*/
_state = 1;
_loopTimeout = kNumLoopsTimeout;
}
break;
case 1: /*
* wait for MP to stream to Talon, really just the first few
* points
*/
/* do we have a minimum numberof points in Talon */
if (_status.btmBufferCnt > kMinPointsInTalon) {
/* start (once) the motion profile */
_setValue = CANTalon.SetValueMotionProfile.Enable;
/* MP will start once the control frame gets scheduled */
_state = 2;
_loopTimeout = kNumLoopsTimeout;
}
break;
case 2: /* check the status of the MP */
/*
* if talon is reporting things are good, keep adding to our
* timeout. Really this is so that you can unplug your talon in
* the middle of an MP and react to it.
*/
if (_status.isUnderrun == false) {
_loopTimeout = kNumLoopsTimeout;
}
/*
* If we are executing an MP and the MP finished, start loading
* another. We will go into hold state so robot servo's
* position.
*/
if (_status.activePointValid && _status.activePoint.isLastPoint) {
/*
* because we set the last point's isLast to true, we will
* get here when the MP is done
*/
_setValue = CANTalon.SetValueMotionProfile.Hold;
_state = 0;
_loopTimeout = -1;
}
break;
}
}
/* printfs and/or logging */
System.out.println("Status: " + _status.toString());
}
/** Start filling the MPs to all of the involved Talons. */
private void startFilling() {
/* since this example only has one talon, just update that one */
startFilling(SampleMotionProfile.Points, SampleMotionProfile.kNumPoints);
}
private void startFilling(double[][] profile, int totalCnt) {
/* create an empty point */
CANTalon.TrajectoryPoint point = new CANTalon.TrajectoryPoint();
/* did we get an underrun condition since last time we checked ? */
if (_status.hasUnderrun) {
/* better log it so we know about it */
System.out.println("There's Underrun!");
/*
* clear the error. This flag does not auto clear, this way
* we never miss logging it.
*/
_talon.clearMotionProfileHasUnderrun();
}
/*
* just in case we are interrupting another MP and there is still buffer
* points in memory, clear it.
*/
_talon.clearMotionProfileTrajectories();
/* This is fast since it's just into our TOP buffer */
for (int i = 0; i < totalCnt; ++i) {
/* for each point, fill our structure and pass it to API */
point.position = profile[i][0];
point.velocity = profile[i][1];
point.timeDurMs = (int) profile[i][2];
point.profileSlotSelect = 0; /* which set of gains would you like to use? */
point.velocityOnly = false; /* set true to not do any position
* servo, just velocity feedforward
*/
point.zeroPos = false;
if (i == 0)
point.zeroPos = true; /* set this to true on the first point */
point.isLastPoint = false;
if ((i + 1) == totalCnt)
point.isLastPoint = true; /* set this to true on the last point */
_talon.pushMotionProfileTrajectory(point);
}
}
/**
* Called by application to signal Talon to start the buffered MP (when it's
* able to).
*/
void startMotionProfile() {
_bStart = true;
}
/**
*
* @return the output value to pass to Talon's set() routine. 0 for disable
* motion-profile output, 1 for enable motion-profile, 2 for hold
* current motion profile trajectory point.
*/
CANTalon.SetValueMotionProfile getSetValue() {
return _setValue;
}
}