This post is part of my journey from easyC to RobotC. I will post things here as I learn them, that might be helpful to others who are also new to this language.

This week, I learned how to program a joystick to drive a chassis. Unlike easyC, the standard text-based RobotC does not have joystick blocks that one can simply drag and drop and poof! you have arcade drive. Or tank drive. Or holonomic drive. In RobotC you must program these items yourself. In this post, I go through the following:

Comment Your Code

There’s really no excuse for not putting comments in your code, whether you’re using easyC or RobotC, or any other language on this earth. Mom … Do I hafta? Why? Why? Why?

  • It helps debugging. When you write some code, and the robot doesn’t do what you’re expecting, having comments placed throughout your program will help you evaluate the code you’ve written — does the code below do what the comment says it should do?
  • It helps your team. When your team has multiple programmers jumping in and out of the code, comments are vital so that other programmers can understand what you have done and why you’ve done it.
    • Without comments, the next programmer will waste lots of time (a) figuring out what you did, or (b) assuming your code does X when it really does Y, and adding more code, assuming the stuff that’s there does X.
    • And god forbid there’s a programming emergency at a competition, and none of the team’s programmers are there (or you, if you’re the only programmer); now people who are not experts at programming are going to have to start poking around in your code, changing stuff, and praying.
  • It helps yourself later. Really, you are doing YOURSELF a huge favor by putting comments in your code. Imagine this: you figure out how to do something really cool and complicated in your code during the summer, and your team goes along happily most of the year using that nifty code. Now in January you’re getting ready for States and you see that your code needs to be modified. Do you think you’re going to remember what that nifty, complex section of code does, exactly? Or why you had to do ABC in a sort-of convoluted way? HAH! No, you’re not. So then YOU’RE going to waste a lot of time trying to reconstruct your own thought process to figure out WHAT THE HECK YOU DID. Waste. Of. Time.

You don’t have to comment every line of code, but you do have to comment every block of code; if the following 6 lines of code together accomplish ABC, there needs to be a comment at the top of those 6 lines explaining what’s happening in the following section, and anything special or unusual about why it’s written the way it is.

RobotC Motor Commands

In the motors & sensors setup window, you can give each motor a name (recommended) in lieu of having to remember that the right-front motor is plugged into, say, port 7. In the setup window, you simply put some descriptive name, like “rightFront” into the setup window, and from that point on, you can just reference it by that name.

In easyC, if you want to turn on a motor, you drag over a motor block, tell the popup window which one you’re using, and give it a power level. In RobotC, you must type this stuff in yourself. To set a motor to a fixed power level, one would type something like:

motor[rightFront] = 100;

But how about connecting it to the joystick? In RobotC, controller buttons and joysticks start with the reference vexRT[ ] — VEX Remote Transmitter. Channels and buttons (the only 2 options on the joystick) are referenced in the format:

  • vexRT[Ch2], for getting the 127 to -127 value from the position of joystick channel 2; possible values Ch1 – Ch4.
  • vexRT[Btn6U] to get the 0/1 value from button group 6, top (Up) button; possible values Btn5–Btn8, and either U (Up), D (Down), L (Left), or R (Right). Note that button groups 5 & 6 only have U and D.

To set a motor equal to the value of a joystick, one would type something like:

motor[rightFront] = vexRT[Ch2];

Partner Joystick

So how do I control the robot using the partner joystick? In easyC in the joystick blocks, there’s just a dropdown asking you if you want it on joystick 1 (main) or joystick 2 (partner). In RobotC, the format for the partner joysticks exactly the same as the main joystick, but with “Xmtr2” appended to the end of the Channel (Ch) or Button (Btn) code (“Xmtr2” = “transmitter 2”). So the following 2 lines of code do the same thing, except the second line is for the partner joystick:

motor[rightFront] = vexRT[Ch2];  // main joystick
motor[rightFront] = vexRT[Ch2Xmtr2]; // partner joystick

Don’t forget the semicolons and braces!

Since RobotC requires you to type the information yourself (instead of the motor block “typing” it for you), spelling and punctuation matter. A lot. If the syntax is wrong, your program will not compile or (worse) it will compile but will not do what it’s supposed to do when downloaded to the robot. The easiest pieces to forget (IMO) are the semi-colon at the end of the line, and matching pairs of brackets or curly-braces, especially if you’re doing things quickly, or you’re on a roll.

Joystick Channels & Values

Joystick channelsIn RobotC, it’s easiest to think of each joystick channel like a sensor. By “channel” I mean the north/south and east/west movement of each joystick lever, identified by the numbers 1 and 2 (next to the right-hand stick) and 3 and 4 (next to the left-hand stick). Movement of a joystick along one of these axes returns a value between 127 (all the way “north” or “east”) to -127 (all the way “south” or “west”). Hmm, 127 to -127, what convenient numbers!

Tank Drive

Tank drive is the easiest motion to program. Like tank drive? You’re sitting pretty. In “tank drive”, moving the joysticks is akin to driving a tank, with the left joystick controlling the wheels on the left-hand side of the robot (via the up/down Channel 3), and the right joystick controlling the right-hand side of the chassis (via the up/down Channel 2). To program tank drive, your code would look something like this:

while(true) {

   // right side = Channel 2 joystick value
   motor[rightFront] = vexRT[Ch2];
   motor[rightRear] = vexRT[Ch2];

   // left side = Channel 3 joystick value 
   motor[leftFront] = vexRT[Ch3];
   motor[leftRear] = vexRT[Ch3];
}

Or to institute some habits that will be important as you get more sophisticated functionality, first read the joystick values into variables, and then set the motors to the value of the variable at the end of the loop. You can even set two motors simultaneously:

while(true) {

   // define variables
   int rightPower;
   int leftPower;

   // set variables equal to joystick values
   rightPower = vexRT[Ch2];
   leftPower = vexRT[Ch3];

   // set right side motors = rightPower variable
   motor[rightFront] =
      motor[rightRear] =
      rightPower;

   // set left side motors = leftPower variable
   motor[leftFront] =
      motor[leftRear] =
      leftPower;
}

Ta da! You now have tank drive.

Arcade Drive

Here’s where things get a little more complex. Now we don’t have the simple right joystick = right wheels / left joystick = left wheels setup. We have to take into account Channels 1 and 2, or Channels 3 and 4, depending on which joystick your driver likes to use. Assuming the person likes to drive with their left thumb, we’ll use Channel 3/4 for this example. In arcade drive, one combines the up/down (y-axis, Channel 3) value with the left/right (x-axis, Channel 4) value as follows.

while(true) {

   // set left side motors
   motor[leftFront]  =
      motor[leftRear] =
      (vexRT[Ch3] + vexRT[Ch4]);
   
   // set right side motors
   motor[rightFront] =
      motor[rightRear] =
      (vexRT[Ch3] - vexRT[Ch4]);
}

Why is one side added and one subtracted? Explaining the code above in words:

  • When you’re turning to the right, you want the left motors to go faster and the right to go slower.
    Example: joystick y-axis is 100, x-axis is 30.

    • Adding them together gives a left-motor speed of 130 (which RobotC conveniently interprets as 127)
    • For the right-hand motors, the power will be 100 – 30 = 70, so the right-side will go slower.
  • When we turn to the left, we want the right-hand motors to go faster, and the left to go slower.
    Example: y-axis is 100 and x-axis is –60.

    • The left-hand motors will get a power of 100 + (-60) = 40 ==> left side slower
    • The right-hand motors will get a power of 100 – (-60) = 160 (which RobotC interprets as 127 power) ==> right side faster

Floor/Ceiling Values

In some cases (like the examples above), the code will produce computed power levels > 127. Luckily, if you tell RobotC to set a motor to a power > 127, it will “truncate” it, and have the motor run at 127 power. However, (according to the awesome jpearman on the VEX Forum) it’s not terrific programming practice to do things this way. It’s better to limit the motor power to a given maximum or minimum in the code before setting the motor levels.

#define MAX_POWER 127
#define MIN_POWER (-127)
while (true) {

   // calculate arcade drive motor power
   leftPower =(vexRT[Ch3] + vexRT[Ch4]); 
   rightPower =(vexRT[Ch3] - vexRT[Ch4]);

   // check calculated amount against min/max
   if (rightPower > MAX_POWER) rightPower = MAX_POWER;
   else if (rightPower < MIN_POWER) rightPower = MIN_POWER;
   
   if (leftPower > MAX_POWER) leftPower = MAX_POWER;
   else if (leftPower < MIN_POWER) leftPower = MIN_POWER;

   // only now do I set the motors,
   // after all power level calculations are complete
   motor[leftFront] =
     motor[leftRear] = 
     leftPower; 

   motor[rightFront] = 
     motor[rightRear] = 
     rightPower;
}

One could shorten the 2 lines of left-side code and 2 lines of right-side code by combining them into one statement each by simply checking whether the absolute value of the given power level exceeds the maximum, and then keeping track of whether it’s positive or negative with sgn(rightPower). In this case, you’d have the benefit of carrying along one fewer constants, since we no longer need minPower:

#define MAX_POWER 127
while (true) {
   // code from above, calculating left/right power

   // check against maximum
   if( abs(rightPower) > MAX_POWER )
     rightPower = sgn(rightPower) * MAX_POWER;
   if( abs(leftPower) > MAX_POWER )
     leftPower = sgn(leftPower) * MAX_POWER; 

   // code from above, setting motors = calculated power
   
}

Holonomic Drive

holonomic chassisHolonomic drive (sometimes called an “X-wing” chassis) involves the four wheels of the chassis (using omni-wheels) being tilted at 45″ angles. In the code below, it is assumed that the left-hand joystick (Channels 3 & 4) are used for basic robot movement and strafing; the right-hand joystick is used to turn the robot, by moving the joystick side-to-side on Channel 1. Power levels for each motor are now calculated as follows (explaining the details of how holonomic drive works is beyond the scope of this post; I’m just feeding you the formulas on this one):

leftFrontPower = vexRT[Ch3] + vexRT[Ch4] + vexRT[Ch1]; 
leftRearPower = vexRT[Ch3] - vexRT[Ch4] + vexRT[Ch1];
rightFrontPower = vexRT[Ch3]- vexRT[Ch4] - vexRT[Ch1];
rightRearPower = vexRT[Ch3] + vexRT[Ch4] - vexRT[Ch1];

You can see from the formulas that if the right-hand (turning, Channel 1) joystick is left untouched, it will return a value of 0 and not have any impact on the motor powers assigned to each wheel. If the joystick is pushed to the right, Channel 1 > 0, and so both left wheels get an increase in power, and both right wheels get a decrease.

From this point in the code, you’d want to do the same checks as above to make sure your ultimate motor power levels are within the range [-127, 127], and then set the motors equal to your final computed power levels.

Deadband

Most people want a little forgiveness in how their motors react to the joystick. For example, if I push the joystick all the way up (on Channel 3, say), I want the robot to drive straight forward. But the likelihood is low that your driver will push the joystick exactly “north” and not “mostly north but a little off to the east” (or west). So you want to ignore joystick information that’s less than, say ±5. That way if the driver pushes the stick all the way up (“north”), but doesn’t get it exactly right, the robot will ignore the slight off-angle (Channel 4) and put all motors forward. Similarly, when the joysticks are not in use, sometimes the joystick lever doesn’t rest exactly at the center (a value of 0), but is “stuck” a little in one direction or another. Here also, ignoring these small errant values is very helpful.

To incorporate this functionality, somewhere before setting the motors to your calculated power, you would add lines like this:

if (leftPower > -5 && leftPower < 5 ) leftPower = 0;
if (rightPower > -5 && rightPower < 5 ) rightPower = 0;

Or, to be more sophisticated, and make your code easier to modify and understand:

#define MAX_POWER 127
#define DEADBAND 5
while (true) {

   // calculate motor power here,
   // using one of the methods above
   
   // truncate the power levels to min/max, as above
   
   // ignore values within deadband range
   if (abs(leftPower) < DEADBAND ) leftPower = 0;
   if (abs(rightPower) < DEADBAND ) rightPower = 0;

   // only now, at the bottom of the while loop,
   //set the motors to their calculated levels, as above

}

EasyC Users Can Do It Too!

EasyC users can program their own joysticks, and are not restricted to using the built-in joystick blocks. (If you’re new to this concept, see my post Set Motor Power Just Once for background material; see Mapping Joystick Values as to why you’d want to do the extra work to make your own joystick code to have better control over your chassis.)

To make your own arcade drive, you’ll have two GetJoystickAnalog blocks, and put the North-South position from, say, Channel 3 into one variable, and the East-West position of Channel 4 into another. You combine the two pieces of information together the same way as described above:

define two integer variables: motorRight_power, motorLeft_power
define two integer variables: Channel3_value, Channel4_value

while (1) {
   Channel3_value = GetJoystickAnalog(Channel 3)
   Channel4_value = GetJoystickAnalog(Channel 4)

   // check Channel3_value and Channel4_value against
   // deadband; if either is within the range [-5 to 5]
   // set it to 0

   // use the combination of joystick values 
   // to figure out motor power
   motorRight_power = Channel3_value + Channel4_value
   motorLeft_power = Channel3_value - Channel4_value

   // check left & right powers to make sure they're
   // between -127 and 127

   // include 4 motor blocks here, at the end
   motor block: motorRightFront, power = motorRight_power
   motor block: motorRightFront, power = motorRight_power
   motor block: motorLeftFront, power = motorLeft_power
   motor block: motorLeftBack, power = motorLeft_power
}

Using this example and the instructions above for RobotC, you can create your own tank drive, holonomic, and so on.

♦       ♦       ♦

That’s it for now; I’ll post more as I learn it!

Share this post: