Why does DifferentialDrive::ArcadeDrive do this?

The naiive implementation for an arcade drive looks something like this:

leftMotorOutput = xSpeed + zRotation;
rightMotorOutput = xSpeed - zRotation;

WPILib’s DifferentialDrive does a bit more. This is the code for the first quadrant, where speed and rotation are both positive, but you get the idea:

double maxInput = max(abs(speed), abs(rotation));
leftMotorOutput = maxInput;
rightMotorOutput = xSpeed - zRotation;

(See https://github.com/wpilibsuite/allwpilib/blob/master/wpilibc/src/main/native/cpp/drive/DifferentialDrive.cpp or https://github.com/wpilibsuite/allwpilib/blob/master/wpilibj/src/main/java/edu/wpi/first/wpilibj/drive/DifferentialDrive.java)

In effect, this slows down the motor on the outside of a turn, compared to the naiive implementation. This effect is most pronounced when the speed and rotation are equal: In both cases, one motor will be stationary, while the other wheel will be going twice as fast in the naiive implementation as in WPILib.

What is the reason they implemented it this way? How does it effect the ease of controlling the robot?

1 Like

If I’m reading your code right, the naive code will end up with a max over 1. The WPI code appears to be normalizing it so that the output in the fastest wheel doesn’t go above the maximum, but if you increase the turn rate it will decrease the other side instead.

3 Likes

Either way, the output will be capped at 1 by the motor controller’s code.

That is not necessarily true, and be careful assuming it is. A year or two ago we found that if you sent an out of range value the motor controllers would roll over. Always do you own error control and range checking.

In Addition, if you don’t normalize your turning ability will greatly decrease at high speed.

2 Likes

For wpilib C++ and Java at least, capping has always been a safe assumption.

I implemented arcade drive that way in 2016 because I couldn’t figure out the derivation on my own, so I took it from Ether’s whitepaper. There could be bugs in it and I wouldn’t know because his papers are notoriously harder to read than they should be.

I opened a PR a while ago to do it the more sane way that’s like 4 lines of code (the naive impl with normalization afterward and no if statements).

The WPIlib implementation gives more control for low-speed maneuvering involving x and z inputs which are both fractional (neither 0 nor 1). As an examle: assuming that the wheel drives would be later clipped/clamped to the range [-1…1], the naive implementation produces the same output for (0.5, 0.5) as for (1.0, 1.0): left=1, right=0.

Added: Here’s a picture of what the mappings look like:

The idea is to map the joystick positions (light color box) to the motor speeds (darker diamond). The red and black lines show output throttles of 1.0, 0.75, 0.5, 0.25, 0.0, -0.25, -0.5, -0.75, and -1.0. The naive implementation essentially just chops the diamond shown at lower left; each of the diagonal lines leading away from the diamond will produce the same output throttles as the other points on the line. The WPI lib implementation from Ether expands this diamond to a square as suggested by the transitional and WPIlib images.

Note: the numbers above are values after the sign-preserving squaring WPIlib also does for you by default. Though wouldn’t it be easier to do sign-preserving squaring with

xSpeed * Math.abs(xSpeed)

than with

Math.copySign(xSpeed * xSpeed, xSpeed)

?

On further reviewing the code and its outputs, there does appear to be a bug. if xSpeed==0, maxInput should be equal to zRotation. Alternately, xSpeed==0 could be a special case which sets leftRotation = zRotation and rightRotation = -zRotation.

What is Ether’s whitepaper? All the Google results are about Ethereum…

Paper: Arcade Drive

(found by searching “arcade drive paper” in search bar here on CD)