Looking at the example code provided for the DriveSubsystem in the Trajectory Tutorial and the Java Ramsete example, I see that both create a DifferentialDrive object used for teleoperated arcade drive, but when receiving left/right drive voltages from the Ramsete controller they directly set motor voltages without using the DifferentialDrive, i.e.:
/**
* Controls the left and right sides of the drive directly with voltages.
*
* @param leftVolts the commanded left output
* @param rightVolts the commanded right output
*/
public void tankDriveVolts(double leftVolts, double rightVolts) {
m_leftMotors.setVoltage(leftVolts);
m_rightMotors.setVoltage(-rightVolts);
}
Is this a recommended way to use DifferentialDrive? It seems like a possible culprit for the MotorSafety timeout, since each iteration through the robot periodic code is directly setting motor voltages and bypassing the DifferentialDrive object, therefore not updating its internal timers.
DifferentialDrive does not implement any motor safety features; those are handled by the speed controllers themselves. Since setVoltage() ultimately delegates to set() (after doing some adjustment to the value), this can’t cause a motor safety issue.
Try to look through your code for anything that could cause the main loop to block.
DifferentialDrive does implement motor safety (in RobotDriveBase). I think this is a bug and the OP is correct in saying the code should be calling the DifferentialDrive function.
The code should definitely not call the DifferentialDrive function, because it does not correct for the battery voltage. Rather, the code should disable motor safety.
Ah, good point on the battery voltage correction. OP, call setSafetyEnabled(false) on the DifferentialDrive object at the start of the command to disable motor safety. I would recommend calling setSafetyEnabled(true) at the end of the path following command. We need to fix the example.
Thanks for the confirmation – thought I was losing my ability to read a class hierarchy!
Is it a better (safer) workaround to disable motor safety on DifferentialDrive or to manually compute the conversion from voltage to [-1,1] and call DifferentialDrive.tankDrive with the computed values?
With the mismatch in motor inversion between DifferentialDrive and kinematics (which I’ve seen lamented in other threads on this forum) we are tempted to scrap DifferentialDrive completely and roll our own, but I’m sure these safety/timeout features are there for a reason.
I would strongly that recommend advanced users ignore the DifferentialDrive class entirely, and use their own class wrapping DifferentialDriveKinematics, instead. Here’s an example project showing how this might be done:
You probably don’t want to call tankDrive; it squares the inputs by default (although this can be disabled with a third parameter) and yeah, inversions get tricky. Some of the CAN motor controllers can set the voltage internally when setVoltage is called, which is better than trying to do this division yourself.
You can certainly roll your own DifferentialDrive, it’s not that complicated, even if you want to do input squaring for fine control when using joysticks. The motor safety features are primarily there to help protect you from potential code loops etc that block updating the motors. E-stop from the DS is the primary safety protection so this is just an additional layer.
If I’m reading this right and properly understand how the PIDController works (still learning), if the robot is currently moving 1 m/s in a straight line and we call setSpeeds with an identical input of 1 m/s, won’t the PID controller determine the error is zero and therefore set the speed controller groups to zero? It seems like this is missing some feedforward (from characterization, for instance) to maintain the desired speed, with the PID just there to correct it?
Saw the same motor safety message. We want to use the DifferentialDrive for teleop, but in autonomous the Ramsete-based moves directly write to the left and right motor controllers, and the DifferentialDrive then complains, in fact briefly stops the motors.
Disabling motor safety seemed to harsh, since after all we do want to use the diff drive in telelop, and motor safety can be a good thing.
We solved that by calling differential_drive.feed() whenever we directly set the left and right motor speeds, so its motor safety timer is reset for each period.
@Oblarg I like the idea of using DifferentDriveKinematics for teleop driving. Our robot never drives straight, especially as the season causes wear-and-tear on the drivetrain. But as implemented in the example, wouldn’t the robot be unable to drive straight at its true max speed?
The driver provides input on the controller, which is then a percentage of the programmed max speed. If the bot is turning, the outside wheels will exceed the chassis max speed. Given this behavior, the programmed max speed has to be lowered to leave headroom to avoid exceeding the full speed of a single side of the drivetrain.
When the driver requests to drive straight at “full speed”, they will not be able to achieve the true max speed of both sides of the drivetrain.
I’m just posting back to say thanks and let you know we got it working. We have successfully removed DifferentialDrive and replaced it with DifferentialDriveKinematics with feedforward on the roboRIO and PID on the Talon SRX’s!
Great news! Remember that RamseteCommand has an alternate ctor precisely for drive subsystems that handle the PID control themselves, so it should not be any additional effort to get your path-following working with your new drive system.
Same here; our lead programmer chose to remove DifferentialDrive from our drive subsystem and will implement the parts he wants himself. Thanks to everyone for the quick feedback today. The programming students were thrilled to see their robot following the path so cleanly!
Thanks @Oblarg. We actually did path following first so it seemed like it would be a slam dunk to make it work with teleop. The feedforward in the example tripped us up along the way though.
Yeah, I’ve opened an issue on our tracker and will fix that when I get the time. Feedback like this is crucial.
Also of note: PID during teleop is great, but it can result in murdering your batteries and putting extra wear on your components if you’re not careful. I highly recommend passing your joystick controls through a slew rate limiter if you’re going to use closed-loop control during teleop; a rate limit of something on the order of 1/3 second from 0 to max will make it handle much more “smoothly” and reduce the wear on the robot.
Well, OK. Does that mean the example is more of just that, an example and not something you’d actually want to use? It was still helpful for tuning.
I did adjust the closedLoopRamp on the Talons to help dampen abrupt changes. When the joystick input is directly controlling voltage, there is no additional damping beyond openLoopRamp. Is the concern that PID may ramp the voltage up higher and faster than a manual driver to get to velocity? I just want to understand why there would be more wear on components.