In doing my research on motors and motor controllers, and following various threads in the VEX Forum, I came across one that totally blew my mind—mapping joystick movements to custom motor power levels to achieve a linear, one-to-one connection between the joystick’s motion your robot’s actual movement, eliminating dead zones, and making full use of the joystick’s motion for maximum control.
- Joystick programming blocks
- Some background: Motor controller behavior
- Joystick block + motor controller = inefficient
- First, figure out what your joystick is doing
- Next, establish what your joystick should do
- OK, what do I do with this?
- Awesome results
- But wait! There’s more! (PID, specifically)
- easyC users too!
Joystick Programming Blocks
If you’re a novice programmer, the built-in joystick blocks may seem like something of a black box. You drag them into your program, and then you can operate your robot with the joystick; voila! Many people (most?) don’t stop to think about what’s happening behind the scenes.
As it turns out, what’s happening in the background is rather simple. When the joystick is moved (let’s just talk about the North-South direction here), it returns a value from 127 (pushed all the way up) to -127 (pushed all the way down); when the joystick is at rest, it returns a value of 0. Think of the joystick lever like any other sensor that’s returning a value to the program; it just happens to be a sensor that you control with your fingers.
You can now imagine arcade drive and holonomic drive too: these are just more sophisticated combinations of Y-axis and X-axis “sensor” values from your joystick that are fed to each appropriate motor.
Some Background: Motor Controller Behavior
Some background first to explain why you’d care about mapping joystick values. As I describe in my post about cortex motor ports, the awesome jpearman ran various tests on internal (blue line in graph below) and external (red line) motor controllers, and found that, with no load, external motor controllers “max out” at PWM of about 90 power; that is, setting battery power north of 90 or so doesn’t make your motor go any faster with external motor controllers (internal motor controllers do have an effect all the way up to 127 power). One can also see in the graph below that even with no load, there is a “dead zone” from about -5 to +5 PWM where the motor does not move at all. [Note, throughout this article “battery power” and “PWM” are used interchangeably.]
Joystick Block + Motor Controller = Inefficient
Putting these two pieces of information together will lead to one conclusion: using the built-in joystick blocks will not get you the maximum performance out of your robot. First, you’ll have a ~5% dead zone in the middle where moving the joystick does nothing to the motor (combine that with inertia, and the zone gets bigger than in the graph above). Next, the top and bottom 10% of physical joystick range gets you nothing in terms of change in speed, since the motor controller maxes out somewhere in the 90-100 PWM neighborhood (jpearman’s tests above were with no load, remember).
Think about how much physical movement there is in a VEX joystick (in inches or centimeters, depending on where you’re reading this); now cut 25% off of that number. Using the built-in joystick blocks reduces the sensitivity and precision of what can be achieved by the driver, for sure. Yes, yes, the built-in blocks do work fine. But if you’re looking to up the ante, or if you’re wondering what those top teams are doing that gets them a little edge over everyone else, this is one of those things (another one is using slew rate in their programming).
First, Figure Out What Your Joystick Is Doing
Here’s the most time-consuming part of this process. In order to make this joystick mapping really work for your robot, you need data. jpearman’s graph up top is for a motor with no load. Your robot, no matter what motor you’re dealing with, will indeed have a load placed on it, and so your motor’s performance will not have a beautiful smooth curve to it. Your actual motor’s performance will look something like the green line in the graph below. Furthermore, your graph will be different for every single application you are doing: your chassis graph will look different than your lifting-arm graph, etc.
The green line above shows LegoMindstormsmaniac’s actual performance of their chassis: motor RPMs for each battery power level, from 0 to 127 (as described in this VEX Forum thread). How did they get this data? Their procedure is as described below. First, some notes:
- These tests require an Integrated Motor Encoder or a Shaft Encoder, as you need to measure exactly how many revolutions your motor/wheel is making at each battery power level.
- If you’re using EasyC, you’ll need to be tethered to the robot; if you’re using RobotC, use the programming hardware kit/cable so you can get sensor data wirelessly.
- LegoMindstormsmaniac did this procedure twice, once for the left side and once for the right side of his chassis, which (thankfully) produced similar results.
- It’s important to keep an eye on your battery voltage throughout these tests, since a dying battery will make your resulting data useless. (LegoMindstormsmaniac made sure it “did not drop very much,” but doesn’t specify the exact threshold.) If you have an LCD screen, have the battery voltage print to the screen at the bottom of each loop of the program; otherwise have it print to the screen along with your sensor data.
- Run the test on foam field tiles; a different surface type will not give you results that transfer directly to a competition scenario.
- i = 1
- For left side of chassis, set motors to power i
- Wait 2 seconds for motors to get up to full speed
- Set encoder value to 0
- Run for 5 seconds
- Record encoder value & calculate RPM using appropriate encoder click counts for your motor/sensor combination; print to screen
- Set left-side motors to 0
- Wait 3 seconds for motors to fully decelerate
- i = i + 1
- Return to Step 2 above.
The beauty of this process is that it can all be completed in a short number of chunks (stopping as needed for battery changes). On a chassis, since you’re just running one side of it, the robot will just drive in circles, so you won’t run out of mat space, and the test can automatically pause and move to the next power level without the student doing anything to the robot.
For a lifting arm, you’d need your program to return the arm to a pre-determined height after each test, then give the motors a rest. You’d also need to modify your lifting time, as you’d probably run out of robot at 100 PWM well before 7 seconds elapsed as in the scenario above.
Next, Establish What Your Joystick Should Do
The next step in the process of improving your joystick functionality is to establish what you want your joystick to do. What do I mean by that? Well, LegoMindstormsmaniac explains that on his (her?) team, they wanted the chassis to be completely insensitive to input at very low joystick values (±10), but then they wanted a linear increase in speed from when it started moving until the top of the joystick movement. They know that the highest RPM that can be achieved on their robot is signified by the value of the green line at the upper-right of the graph (shown here again so you don’t have to scroll). So they drew a straight line from that point back down to around 15 RPM. Looking closely at their green line, one can see that when enough power is applied to the robot to overcome starting inertia, it does move at about 15 RPM. This “Ideal” joystick/robot performance is signified by the red line in the graph. The straight line means that the team wants the robot’s output to move in a one-to-one relationship with the movement of the joystick.
OK, What Do I Do with This?
OK, well, now I have the two lines in the graph above; how do I create a look-up table in my driver-control code to translate the drive-team’s movements of the stick into battery power levels that will produce the action resembling the red line in the graph above?
We’re in luck; it’s not that hard. We’re going to match up the RPM values of the red line with the corresponding PWM values of the green line. Here’s a detail section of the graph:
Starting on the red line near the origin, in my “Ideal” movement, to achieve 10 RPMs, I don’t need to set my battery power to the expected 13 PWM, I need to shift over to the right and see that a battery power of 21 is what’s actually needed to achieve 10 RPM. In my lookup table, a joystick value of 13 will translate to a battery power of 21. Further up the graph, the reverse relationship holds; to achieve 70 RPM, instead of 59 battery power, I really only need 39 power, so when the joystick registers 59, I’ll set the motor to 39 PWM.
So for every single value, from 0 to 127, one would make these correlations, and then in the program put them in a big array. In driver-control mode, treat the joystick value like a sensor input and read it to a variable, and then use its absolute value as the index for your array lookup. Set your motor power to the value you looked up in the array (minding your negatives, for reverse motor direction; alternately make your array symmetric, from -127 to 127).
The team in this example also posted their results after making these transformations, with really stunning results, shown by the blue line in the graph below. Hats off to Team 24C—that’s completely awesome!
But Wait! There’s More! (PID, Specifically)
There’s always more, isn’t there? Now that you’ve got your array, you can use this array in your PID algorithm too. In a PID algorithm, one compares a robot part’s functioning to a desired value or behavior, and makes frequent small adjustments to battery power—up or down—to keep the robot’s functionality constant, whether it be the spinning of a flywheel, the vertical position of a lifting arm, or the speeds of the left and right sides of the chassis during autonomous.
Looking closely at the green line in the graph above, however, shows that an increase of +1 PWM does not have the same affect on robot output across the board. At lower PWM levels (in this specific example; your mileage may vary), a +1 change in PWM has a significant effect on the wheel’s measured RPM; at higher battery power—and particularly at the highest PWM values—that effect is far smaller.
I’ll go out on a limb here: I’m guessing most readers out there (because I was one of them, until I wrote this) who have used or written a PID algorithm in the past have related to PID battery power changes as if the effect is following the red line, but now we all know it’s actually following the green line. Huh.
Since PID is supposed to be a nuanced adjustment, without wild swings in robot performance, why not use our array of values in making those PID adjustments? Instead of just adding or subtracting 1 from the current battery/PWM level, add or subtract 1 from the array’s index instead, and then set the motor power equal to the array’s value for the given index.
easyC Users Too!
The descriptions and references above are based off of a RobotC user’s methodology. Good news for easyC users! A very nice person has laid out exactly how you would do this in easyC: “VEX easyC Custom Drivetrain Code.” It’s a great description, complete with easyC screenshots (not just some raw C code that you have to determine how to implement).
Since I just came across all of this information near the tail end of the Starstruck season, we have not tried joystick mapping yet on a Renegade Robotics robot, but I can’t wait for our programmers to put it into action for In the Zone, especially on our chassis, given that we are expecting to be driving around a good part of the time with a ~4 lb. mobile goal, stacking cones on it as we go, which will definitely cause some inertial issues. Having a robot that is calibrated to drive assuming it’s holding 4 extra pounds will be quite different than driving a robot that’s just using the standard pre-made joystick block.
I hope you’ve found this description useful in helping your team to put these ideas into action.