In lieu of using built-in joystick programming blocks, it is possible to gain more control over the robot—and control in the way that works best for one's specific uses—by mapping joystick values to custom motor power levels. This method can be used to achieve a linear, one-to-one connection between the joystick's motion the robot's actual movement, eliminating dead zones, and making full use of the joystick's motion for maximum control.
To the novice programmer, the joystick blocks built-in to many programming languages may seem like something of a black box. They're easy to use, but many people don't stop to think about what's happening behind the scenes.
What's happening in the background is actually rather simple. When the joystick is moved (referring here to the North-South direction), 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. The joystick lever is really just 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.
Arcade drive and holonomic drive can also be visualized: these are just more sophisticated combinations of Y-axis and X-axis “sensor” values from the joystick that are then fed to each appropriate motor.
Some background first to explain why one would care about mapping joystick values. As described in the article about cortex motor ports, the VEX Forum user @jpearman1) 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 the robot's motor go any faster if that motor is connected to an external motor controller (Cortex motor ports 2-9) (internal motor controllers, however, 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.]
Putting these two pieces of information together will lead to one conclusion: using the built-in joystick blocks will not achieve the maximum performance out of one's robot. First, there will be a ~5% dead zone in the middle where moving the joystick does nothing to the motor output (combine that with a robot's inertia, and the zone gets bigger than in the graph above). Next, the top and bottom 10% of physical joystick range does nothing in terms of change in speed, since the external 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. Yes, the built-in blocks do work fine. But if a team is looking to “up the ante,” or if a builder is wondering what those top robot 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).
The following sections explain how one could create a linear output, covering the full range of joystick movement, instead of the curve shown above. This discussion is based on the VEX Forum thread "24C's Motor Control Value Remapping".
This is the most time-consuming part of this process. In order to make this joystick mapping really work for one's robot, one needs real-world data. @jpearman's graph above is for a motor with no load. A competition robot—no matter what motor or port being used—will indeed have a load placed on it, and so any robot motor's performance will not have the very smooth curve as in the graph below. Actual motor's performance will look something like the green line in the graph below.2) Furthermore, a real-world graph will be different for every single application: a chassis graph will look different than a lifting-arm graph, etc.
The green line above shows Team 24C's actual performance of their chassis: motor RPMs for each battery power level, from 0 to 127 (as described in their VEX Forum thread). How did they get this data? Their procedure is as described below. First, some notes:
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 only one side's wheels are running, the robot will just drive in circles, so mat space isn't generally a problem 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, the program must return the arm to a pre-determined height after each test, then give the motors a rest. Lifting time would require modification, as the robot would probably run out of arc at 100 PWM well before 7 seconds elapsed as in the scenario above.
The next step in the process of improving joystick functionality is to establish what one wants the joystick to do. What do I mean by that? Well, Team 24C 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 for convenience). So they drew a straight line from that point back down to around 10 RPM. Looking closely at their green line, one can see that when enough power is applied to the robot to overcome starting inertia and friction, it does move at about 10 RPM. Team 24C's “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.
Given the two lines in the graph above; how can one create a look-up table in the driver-control code to translate the drive-team's movement of the stick into battery power levels that will produce the action resembling the red line in the graph above?
Luckily, it's not that hard: all that's needed is matching 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 the “Ideal” movement, to achieve 10 RPMs, one does not need to set the battery power to the expected 13 PWM. Instead, shift over to the right and see that a battery power of 21 is what's actually needed to achieve 10 RPM in the real world. In a resulting 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, in reality only 39 power is required. When the joystick registers 59, the lookup table will set the motor to 39 PWM.
So for every single value, from 0 to 127, one would make these correlations, and then in one's 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).
Team 24C also posted their (stunning) results after making these transformations, shown by the blue line in the graph below.3)
One can also use this array in a PID algorithm. 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 shows that an increase of +1 PWM does not have the same effect 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.
Many programmers who have used or written a PID algorithm have probably related to PID battery power changes as if the effect on the motor's performance is following the red line, but now it's clear that it's actually following the green line.
PID is a nuanced adjustment. To avoiding uneven changes in motor output, one can use this 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. By employing this transformation in the PID algorithm, one can achieve linear output differences for each change indicated by the PID algorithm, thus following the blue line above.
For the 2017-18 In The Zone game, robots will be driving around part of the time while holding a ~4 lb. mobile goal. Robots will have lots of starting inertia and friction during these movements, and will have an even larger “dead zone” at low joystick values. Having a robot that is calibrated to drive assuming it's holding 4 extra pounds will be different than driving a robot that's just using the standard joystick programming block.
Creating two different mapping arrays, triggered via joystick button, would allow a team to have linear output regardless of whether their robot is holding something heavy or not.