Creating a Differential Drive
Goals
- Create a second Motor+Config
- Set up the drivetrain object
- Make a drivetrain go forward
Differential Drives
A Kitbot (and our testbench bot, TABI) has two motors on either side. Together, those two motors work to move the robot, and allow it to turn.
If you just spin one motor (as we've seen) then it rotates around the other. So, let's set up the second motor.
Left+Right Motors
Currently we have one motor, called motor. But well need two so let's prepare for this
java1
2
3
4
5public class ExampleSubsystem extends SubsystemBase{ //The first parameter is the motor's ID, and unique to each motor. // Ther second is the type of motor; We only use Brushless. SparkMax motor = new SparkMax(1, MotorType.kBrushless); }
Let's rename this motor to motorLeft. This will cause a error in your ExampleSubsystem and periodic functions too, so you'll need to fix it there as well.
This is also a case where VS Code can help. If you right click the variable name (motor) you can use Rename Symbol and enter the new name. This will fix all places where this specific motor is being used, and you can avoid many such errors .
Now we can just create a second motor called motorRight, and we'll have this:
java1
2
3
4
5
6public class ExampleSubsystem extends SubsystemBase{ //The first parameter is the motor's ID, and unique to each motor. // Ther second is the type of motor; We only use Brushless. SparkMax motorLeft = new SparkMax(1, MotorType.kBrushless); SparkMax motorRight = new SparkMax(3, MotorType.kBrushless); }
Just for fun, let's add this to our Periodic function.
java1
2
3
4
5
6
7
8public class ExampleSubsystem extends SubsystemBase{ // ... there's other stuff up here public void periodic(){ motorLeft.set(0.2); motorRight.set(0.2); } }
Enable your robot, and see what happens!
In most setups, I expect this will spin in place! Probably fairly quickly!
Making The Drivetrain go "Forward"
Since you gave both motors a "positive" value, you probably wanted the robot as a whole to go forward. But, looking at the robot mechanically, you'll see the issue.
A "positive" output causes the motor itself to spin "clockwise" in a consistent way. But the left and right side are "mirrored" relative to each other, so the impact on the robot's motion is flipped!
You might be tempted to just change one motor's output value to "negative" and fix this. However, you have then remember which motor gets a negative, every time you want to deal with this. It's much better to tell the motor "hey, you're mounted the wrong way" and have it take care of this automatically. Let's do that.
Configuring your other motor
We previously configured a motor, which looks like this when we strip out the commentary, which configures motorLeft, but not motorRight.
java1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21public class ExampleSubsystem extends SubsystemBase{ SparkMax motorLeft = new SparkMax(1, MotorType.kBrushless); SparkMax motorRight = new SparkMax(4, MotorType.kBrushless); public ExampleSubsystem(){ var config = new SparkMaxConfig(); config.smartCurrentLimit(10); config.inverted(false); config.idleMode(IdleMode.kBrake); motorLeft.configure( config, ResetMode.kNoResetSafeParameters, PersistMode.kNoPersistParameters ); } // ... periodic is down here somewhere }
We can actually just fix this fairly easily: Just apply the same config to the second motor!
java1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26public class ExampleSubsystem extends SubsystemBase{ SparkMax motorLeft = new SparkMax(1, MotorType.kBrushless); SparkMax motorRight = new SparkMax(4, MotorType.kBrushless); public ExampleSubsystem(){ var config = new SparkMaxConfig(); config.smartCurrentLimit(10); config.inverted(false); config.idleMode(IdleMode.kBrake); motorLeft.configure( config, ResetMode.kNoResetSafeParameters, PersistMode.kNoPersistParameters ); motorRight.configure( config, ResetMode.kNoResetSafeParameters, PersistMode.kNoPersistParameters ); } // ... periodic is down here somewhere }
But this won't fix our problem. If we set config.inverted(false) it spins how it is now.
But if we set config.inverted(true) it just spins the other way.
Instead, what we need to split the motor configs out into "common" settings and different ones.
java1
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
33public class ExampleSubsystem extends SubsystemBase{ SparkMax motorLeft = new SparkMax(1, MotorType.kBrushless); SparkMax motorRight = new SparkMax(4, MotorType.kBrushless); public ExampleSubsystem(){ //Set up our "default" config values that apply to all motors var config = new SparkMaxConfig(); config.smartCurrentLimit(10); config.idleMode(IdleMode.kBrake); var configLeft = new SparkMaxConfig() .apply(config) //Copy our "default" config settings .inverted(false); var configRight = new SparkMaxConfig() .apply(config) //Copy our "default" config settings .inverted(true); motorLeft.configure( configLeft, ResetMode.kNoResetSafeParameters, PersistMode.kNoPersistParameters ); motorRight.configure( configRight, ResetMode.kNoResetSafeParameters, PersistMode.kNoPersistParameters ); } // ... periodic is down here somewhere }
If your robot goes "backwards", just flip the inversion that each motor sees, and you'll be set.
Now your robot will drive straight, with "positive" meaning "forward" for each side.
As a thought process, think about what you'd need to send to each motor to make it
- spin in place clockwise
- reverse
- drive in a big circle
- drive in a small circle
- pivot around the left wheel
While we can write a bot while thinking about motors like this, there's an easier way.
Streamlining the Driving Process
Now, let's start changing our thinking from "two motors" to "one drivetrain". We'll do this with the help of a DifferentialDrive object.
java1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21public class ExampleSubsystem extends SubsystemBase{ SparkMax motorLeft = new SparkMax(1, MotorType.kBrushless); SparkMax motorRight = new SparkMax(4, MotorType.kBrushless); //Make a spot for our drivetrain in the class DifferentialDrive differentialDrive; public ExampleSubsystem(){ //... Motor Configs go here //Now we take our configured motors and create a new // DifferentialDrive object differentialDrive= new DifferentialDrive( motorLeft, motorRight ); } // ... constructor and periodic not shown }
Then, let's go to our periodic and remove the existing .set(...) calls, and replace with a single DifferentialDrive call that does the same thing:
java1
2
3
4
5public class ExampleSubsystem extends SubsystemBase{ public void periodic(){ differentialDrive.arcadeDrive( 0.2, 0.0 ); } }
arcadeDrive uses a "forward" value and a "turn" value, and figures out what each motor should be doing. We just set the "forward" value to positive, and no turn value.
java1
2
3
4
5
6
7
8//Goes forward differentialDrive.arcadeDrive( 0.2, 0.0 ); //goes backward differentialDrive.arcadeDrive( -0.2, 0.0 ); //pivots to the left/counterclockwise differentialDrive.arcadeDrive( 0.0, 0.2 ); //Drives in an circle differentialDrive.arcadeDrive( 0.1, 0.2 );
This doesn't seem like it'll simplify things yet, but once we hook up a controller this will become clear.
It's sometimes helpful to note that "left" is counterclockwise for math reasons, and follows mathematical conventions. A "more positive" rotation is counterclockwise, both for our robot and when graphing angles.
Kitbot + multi-motor Gearboxes
For our testbenches, this is all we need! They have one motor per side. The Kitbot, however, has two motors per side.
Since we always drive these motors as a"pair", we can just tell one motor on each side to follow the "primary" motor, and then never think about about the follower again. This mostly means more configs
java1
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
51public class ExampleSubsystem extends SubsystemBase{ SparkMax motorLeft = new SparkMax(1, MotorType.kBrushless); SparkMax motorRight = new SparkMax(4, MotorType.kBrushless); //Create the new motors with their appropriate ids. SparkMax followerLeft = new SparkMax(22, MotorType.kBrushless); SparkMax followerRight = new SparkMax(23, MotorType.kBrushless); DifferentialDrive differentialDrive ; // We'll create the object later public ExampleSubsystem(){ //Set up our "default" config values that apply to all motors var config = new SparkMaxConfig(); config.smartCurrentLimit(10); config.idleMode(IdleMode.kBrake); var configLeft = new SparkMaxConfig() .apply(config) //Copy our "default" config settings .inverted(false); var configLeftFollower = new SparkMaxConfig() .apply(config) //Copy our "default" config settings .follow(motorLeft, false); //rotate the same direction as the leader var configRight = new SparkMaxConfig() .apply(config) //Copy our "default" config settings .inverted(true); var configRightFollower = new SparkMaxConfig() .apply(config) //Copy our "default" config settings .follow(motorRight, false); // Rotate the same direction as the leader //Now we just write the config to the motor motorLeft.configure( configLeft, ResetMode.kNoResetSafeParameters, PersistMode.kNoPersistParameters ); // ... and repeat for each motor and it's config //Now, we create the differentialDrive just like before. //The followers will figure out what they do on their own. differentialDrive = new DifferentialDrive( motorLeft, motorRight ); } // ... periodic is down here somewhere }
Important! It's helpful to remember that without inversion, a motor output shaft spins "clockwise" relative to it's mounting plate.
The rev config .inverted(true) only applies when setting a motor value for leader motors (motorLeft and motorRight). We use this setting to define what "forward" means on a mechanism.
For followers, they instead care about whether they should spin in the same direction as the leader, or spin in the opposite direction. So .follow(leader,inverted) allows us to indicate this.
For our chassis gearbox, they should spin the same way as the leader: Meaning we do not invert the followers relative to the leaders ; They spin the same direction, so they get .follow(leader,false)
When configuring a follower, if you're not sure set the max output very low using smartCurrentLimit(...);. A current limit of 5-10 is generally safe and will move mechanisms, and you can see if anything is trying to go the wrong way.