Arm Gravity Feedforward w NEO motors

I’m trying to implement PID + feedforward control for an arm w gravity feedforward. I thought that there were a few ways to do this:

  1. Use the ArmFeedforward class, and during periodic: update the voltage of the motor based on the angle (accounting for gravity feedforward) and the PID.

  2. Use the motor’s internal PID and Feedforward, and each time I change the setpoint, I tell the motor its new setpoint.

I feel like ideally, I would use 2. But, I’m not sure how to implement the gravity feedforward for the motor. I know that SparkPIDController has a method to set the feedforward to a double value and not a lambda for the feedforward depending on the angle. So how does the motor update the feedforward voltage based on the angle if I set it to a constant value?

Lastly, does anyone recommend 1 over 2, or a third way of motion control?

Last year, I was updating the ArmFeedforward with the angle setpoint to calculate a new feedforward each time it changed and then using that as the arbitrary feedforward in the setReference of the SparkMaxPIDController.

1 Like

I agree with @ngreen. I just want to add context from the two options you presented. I think the best answer is both. As @ngreen pointed out, you can use the motor controller’s PID with ArmFeedforward. Use the feedforward calculated on the roboRIO, and pass it to the arbitrary feedforward parameter for setReference.

The only comment I have is that you said, “internal PID and Feedforward”. SPARK Max doesn’t have built-in arm gravity feed forward (aside… TalonFX does have an arm gravity feedback feature), but you can still use the internal PID with feedforward from the rio.

2 Likes

The TalonFX has a built-in feedback compensator for gravity on an arm. The live measurement of the angle is used, not the setpoint. The current CTRE docs describe it as a feedforward to avoid confusing users who are unfamiliar with feedback linearization.

You may want to consider doing the same with the ArmFeedforward class, especially if you’re not using a motion profile.

1 Like

Yes, that is technically correct. Post edited.

1 Like

No need to edit the post - they do call it a feedforward in their docs atm - just wanted to call attention to the implementation difference because it’s relevant to use of ArmFeedforward (nothing stops you from doing a feedback linearization instead by passing it the measurement instead of the setpoint).

If you’re not using a well-tuned motion planner, whether you do this can matter a whole lot more than whether the feedback loop is on the RIO or on the motor controller in terms of overall system performance. Feedforward linearization can fail miserably if the setpoint is far from the current state.

1 Like

Are there any updates to SmartMotion? Understanding and Tuning Smart Motion for an arm - #45 by nuclearnerd

1 Like

None that I know of, but I don’t work for REV. Their documentation around the feature (including the tuning instructions) remains as it was at the time of that post.

1 Like

The spark max onboard feedforward (kFF) is equivalent to a kV, but in different units.

1 Like

Wait so I have to set the reference during each periodic call in order to update the feedforward?

It depends what behavior you’re going for.

  • @ngreen’s code is using a trapezoidal motion profile so you have setpoints at every period that you can use for feedforward. In this case you would set the arbitrary feedforward on each period, based on the desired state.
  • If you’re just using position control, depending where your end state is and the behavior you want, you could use the end state as a feedforward value and set it only once. But as @Oblarg mentioned, “Feedforward linearization can fail miserably if the setpoint is far from the current state.”
  • If you’re just using position control, and you need the arm to hold itself throughout the motion, you could calculate the arbitrary feedforward value based on the arm’s current position (this is technically feedback, as @Oblarg pointed out). This is what CTRE’s phoenix 5 documentation describes.
1 Like

The arbitrary feed forward value is just a percent output or voltage, whichever you specify. It’ll hold that value until you change it. If you want to more accurately compensate for gravity on a pivoting arm (useful if you’re just using position control with large setpoint differences), the reference must be set periodically.

If you don’t want to directly call setReference() in periodic(), consider ways of having a command continually run to handle it instead. A simple way to do that is to register a default command that just calls setReference(), recalculating the feedforward but reusing the current setpoint.

How did you obtain the constants for the ArmFeedforward?

This has an explanation of what the constants are: Introduction to DC Motor Feedforward — FIRST Robotics Competition documentation

You can measure them by hand, or try to use the SysID tool to automatically calculate them.

3 Likes

ReCalc can also estimate your control gains.

3 Likes

Any ideas on how to implement ngreen’s method? It takes TrapezoidProfile.State as an argument and I do not know how to get that.

Read the docs for TrapezoidProfile.

Do we need a feedforward for the arm? I noticed that the NEO motors maintain their position in brake mode even with the maximum load that will be exerted…so what is the point of the kg if the motors will always counteract the weight of the arm?

Brake mode only kicks into effect in “neutral mode”. When you actively control the arm you should add a gravity-compensation feed-forward so the closed-loop control will be linear.

2 Likes

Got it, thanks!

1 Like