Arms can be a real challenge. We did this last year, and it was a huge learning exercise, but we got it working pretty smoothly eventually.
The first thing you should consider is trying to “linearize” the system, and model it to counteract the forces of gravity. Most notably, when the arm is straight out, it will respond VASTLY differently than it will if it’s straight up. This makes it nearly impossible to set good gains, because the gains change significantly based on the position of the arm. PIDs are linear controllers, and the math just doesn’t handle non-linear changes well.
What we did for this was figuring out the max force applied when directed straight outward (highest static force), and save it as a value kArmFF
. Then just add this to your PID’s output, like so.
armPower = /get PID output here/;
armPower += kArmFF*Math.cos(Math.toRadians(currentArmPos)
This is known as an “arbitrary feed forward”, and removes gravity from the equation. Note though, that your system may not agree perfectly with the math, so don’t worry if you have to fudge numbers a bit. Lowering the FF, or applying less FF when going down may be what your system needs to operate properly. We had to do this due to our system having a large FF, and the stickyness of our system meant gravity and small outputs for common PID changes often couldn’t force it back down otherwise.
If you want to see our code that has this, you can find it here:
Because of your system design, the arbitrary feed forward can be significantly more complex, and may need to account for the second arm tilted up (as well as roughly level), and possibly account for the the variable weight of the balls. It may be a bit tedious to calculate, but if you just add things piecemeal, figure out the min/max forces, and scale appropriately based on your angles, you should be able to get FeedForward motor outputs that hold the system stationary. This lets the PID’s error correction work properly, which will greatly the PID tuning process and minimize points where the PID behaves poorly.
Just having your PID and feed forward will probably be enough to let you hook it to the throttle. To do this, you just need to use some linear interpolation or math to convert -1-1 to 256-512. Pass this to a manual control setup, and you’re good.
I can warn you of one “gotchya” we’ve seen. An unplugged controller generates an input of 0 on that axis. This will send the arm directly to a middle position, which tends to be extremely unsafe. I would recommend the arm command ignore input until the joystick input roughly matches the bot’s current position, after which you can have the arm follow the throttle.
For smooth motion in auto commands, a proper motion profiled PID loop is best. However, if this is proving out of reach, this you can simply rate-limit your setpoint using the SlewRateLimiter
class. This will let you force the full setpoint change to occur over a specified number of seconds. This isn’t a great fix in general, and should be considered a stopgap solution: This demands sub-optimal performance, and it’s a headache maintaining the various slew rates and time profiles across multiple commands or sequences. It’s a good quick fix for one or two autos as week 1 competitions approach though.
If you’re still struggling to get a PID base class, I can point you to this one our team put together and make a lot of use of. It’s not perfect, but designed for quick PID setup, and has couple math tweaks to make sure it stays fairly stable when used without in motion profiles (notably in setpoint constraints and I-term wind-up prevention, and output ramping). Currently there’s no internal support for arbitrary feed forward, but you can just add it to the output and get the same results in almost all cases.