Bizarre Driving Issue

So we’re using four wheels, four motors.

We set them up to run through some deadzone code, then perform some calculations, and assign the power to the PWMs in pairs.

The idea is to have the simpliest of holonomic drive code possible.

Here’s the jist of it:



	if(!p2_sw_trig) //if not turning
	{	//deadzone
		if(p2_x<122) 	//more than 5 below 127
		{
			deadx = ((p2_x - 122) * 127)/122 + 127;	//scaling so we don't just jump from 127 to 121 but can still reach 0
		} else if(p2_x>132)	//more than 5 above 127
		{
			deadx = ((p2_x - 132) * 127)/122 + 127;	//scaling
		} else
		{
			deadx = 127;	//don't move if within 5 of 127
		}

		printf("deadx=%d\r",(int)deadx);
		//same stuff for y axis
		if(p2_y<122)
		{
			deady = ((p2_y - 122) * 127)/122 + 127;
		} else if(p2_y>132)
		{
			deady = ((p2_y - 132) * 127)/122 + 127;
		} else
		{
			deady = 127;
		}

		printf("deady=%d\r",(int)deady);

		pair1 = deady+127-deadx;	//holonomictastic
		pair2 = deady+deadx-127;	//holonomictastic

		printf("pair1=%d\r",(int)pair1);
		printf("pair2=%d\r\r",(int)pair2);
	
		if(pair1>254)	//so moving the controller diagonally does not go outside valid values
			pair1 = 254;
		else if(pair1<0)
			pair1 = 0;
	
		if(pair2>254)
			pair2 = 254;
		else if(pair2<0)
			pair2 = 0;

		pwm04 = (pair1 - 127)/2 + 127;	//halving
		pwm01 = (127 - pair1)/2 + 127;	//halving and reversing
		pwm03 = (pair2 - 127)/2 + 127;	//halving
		pwm02 = (127 - pair2)/2 + 127;	//halving and reversing
	} else	//spinning in place
	{
		pwm01 = pwm02 = 107;	
		pwm03 = pwm04 = 107;
	}

First problem is that it doesn’t drive properly when we move the controller backwards or right. Basically, it considers the y axis neutral for all values below 127 and the x axis neutral for all values above 127. For example, if we moved the controller forward and to the right then it would be the same as just forward. And pulling straight back will not move anything (and the printfs() indicated that the value was 127 being sent to both motors). It went away when we removed the deadzone code, the motors move properly in all directions.

The other problem we’re having is that when we try and go foward the left two wheels (pwm01,pwm02) get up to about 20 or 30 before the right wheels begin responding. When we go backwards the right two wheels (pwm3,pwm4) get up to 20 or 30 before the left two wheels respond. When pressing left the back two wheels (pwm2, pwm4) get up to speed first. When pressing right the front two wheels act similarly.

We know that we could fix this by using four geartooth sensors, but the code behind that would be monstrous and confusing, and require four sensors!

Any help would be appreciated.

p2_x and p2_y are unsigned integers. When your subtract from them to end up with a negative value, the computer will start to have problems. Store them in signed integers before you try to subtract 122.

basically change this:

if(p2_x<122) 	//more than 5 below 127
{
 deadx = ((p2_x - 122) * 127)/122 + 127;
  }

to

if(p2_x<122) 	//more than 5 below 127
{
    int drive_x=p2_x;
    deadx = ((drive_x - 122) * 127)/122 + 127;
}

Also, my team has found that the outputs from the victors is way asymetrical, and that they have their own, fairly large dead zone that doesn’t happen to be centered about 127.

Can that be fixed through recalibration? Or have you found another method? But this is probably it! Thanks!

And the part where you said that they’re usigned integers wouldn’t make sense mainly because the negative works for the p2_x but not for p2_y and that doesn’t explain why p2_x doesn’t work when positive. We’ll try both typecasting and storing it in a smaller variable, but I don’t think that’s the solution for why the deadzone code isn’t working.

p2_x and p2_y are unsigned integers. When your subtract from them to end up with a negative value, the computer will start to have problems. Store them in signed integers before you try to subtract 122.

AFAIK, this shouldn’t be a problem.
You are not actually changing the values of p2_y and p2_x, are you? If you are just using the for calculations, it shouldn’t matter what type they are.

Sorry if I’m wrong, I can’t test it right now. Otherwise I would just cast p2_y as an int instead of declaring a separate variable.

Recalibration didn’t help. We made a graph of PWM output vs wheel speed and then made a lookup table of the PWM values required to achieve the desired wheel speeds. I’ll post more details about it when I have time tomorrow if you want.

When we were initially messing around with holonomic driving, my team did run into a few problems with subtracting from unsigned variables, so it’s definitely worth a try.
Have you tried following the values of your variables with the MPLab debugger? That can be an easier way to test different situations without using the robot.

Also, here’s a general thing that will cut down code, therefore space used, will “upload” faster and run faster :smiley:

Turn this:

if(p2_x<122) 	//more than 5 below 127
		{
			deadx = ((p2_x - 122) * 127)/122 + 127;	//scaling so we don't just jump from 127 to 121 but can still reach 0
		} else if(p2_x>132)	//more than 5 above 127
		{
			deadx = ((p2_x - 132) * 127)/122 + 127;	//scaling
		} else
		{
			deadx = 127;	//don't move if within 5 of 127
		}

into


if(p2_x>=122 && p2_x<=132) 	//within deadzone
	deadx = 127; //dont move
else //outside deadzone
	deadx = ((p2_x - 132) * 127)/122 + 127;	//scaling

tdlrali: It would be nice if things worked that way, but the point of the code is to find the difference between the input and the end of the deadzone then scaling it to cover 127 values. Finding that difference requires a separate case for above and below, with below subtracting 122 rather than 132. With the code you suggest, holding the controller at full reverse would result in deadx equaling -10.

Kelly: Considering our build team, I’m sort of doubtful we’d ever find enough time with the robot to make an accurate lookup table, but all suggestions are definitely appreciated. Also, no, we have not tried the debugger. I, at least, was not even aware of its existence, though kitscuzz is generally much better informed than I am.

you are overflowing, i can guarantee it. The type of the variable is an unsigned char, and none of the numbers are larger than that range within the calculation. Therefore, your values overflow when you multiply by that huge number.

Typecast “p2_x” and whatnot into signed ints, and this should rectify that issue.

(note that you could also use encoders which are much more accurate and friendly)

It still seems strange that an overflow would create such specific fail points and be different for p2_x and p2_y, but it’s not like typecasting is very difficult. We’ll give it a shot, whether it’s an issue for your reason, Kelly’s, or something none of us has thought of.

well it will create fail points because that is where your numbers will overflow. believe me, i have seen it all too many times.

I don’t doubt you, but doesn’t overflow involve the numbers becoming too large? Given the code, the largest numbers should occur at full forward and full reverse, but the failure occurs for all inputs below 122 and none above with p2_y and for all inputs above 132 and none below with p2_x. I mean, the largest number that occurs at any point is 15494 (from p2_y=254) and that works just fine. Of course, I sort of get the feeling I’m displaying some horrible ignorance here. Too bad it’s sort of difficult to get into the school and test these things out at 12:44 AM.

p2_x and deadx are (unsigned char), a single byte, and can only hold the values 0 to 255.

unsigned char p2_x;
unsigned char deadx;

We all use them to hold PWM values where 127 is neutral, 255 full forward, 0 full reverse.

For an example set p2_x to be 50, a value less than the 122 that you are having problems with. The code looks like:
deadx = ((p2_x - 122) * 127)/122 + 127;

Now subtract the 122: p2_x = p2_x - 122; /* or p2_x -= 122; or (p2_x-122) */

What’s the value of p2_x, it’s not -72 like we wanted it’s instead now 184. The math overflowed the range of values, the result became 256-72 instead. Think of it as
wrapping around, instead of going from 2, 1, 0, -1, it goes 2, 1, 0, 255, 254, 253 …

That’s the first problem, now imagine we do the multiplication, it’s going to wrap around many times and come to rest somewhere unexpected, (n*127 mod 256). Then division will only return 0, 1, 2 and that’s all folks.

So how do you fix it? Take care of the typecasting to a (signed int) that is 2 bytes wide
and can be both positive and negative, once you have finished the scaling calculation, cast the result back to an (unsigned char)
deadx = (unsigned char) (( ((int)p2_x - 122) * 127 )/122 + 127 );

C always does math using the widest type (most number of bytes) of the two values.

Aside: The last cast to (unsigned char) is actually unnecessary as we are assigning it
to an unsigned char variable and C will do the typecast for us. However by including the typecast we are making clear our intent and it’ll help remind us what is going on.

Short answer: the problem with negative values occurs because all numbers must have a positive result with (unsigned char) or it overflows. The problem with some +ve numbers happens because the result of the *127 won’t fit in a single byte. Casting to (int) will fix those problems.

However, the other comments about how to handle a deadzone are correct too., there may still be other problems.

Note: The code that reverses the motors (127-pwm) could be simplifed if you just reverse the motor connection to the victors, of one of the motor pairs. It’ll also help getting matched speeds as the speeds 127+n and 127-n are not necessarily the same.

Don’t forget that the victors also have a deadband around 127 too.
Happy coding :cool:

I’m pretty sure your current problems are caused by overflowing unsigned variables, however, if you’re like us, the next problem you’ll have is with your robot listing to one side or another as it drives. We fixed it by collecting data about the pwms and making a lookup table that relates desired wheel speeds and actual pwms.

We collected this data by hooking the robot to the computer and cycling through all the possible pwms. The units on wheel speeds are not actually RPMs, but encoder counts per 115 program loops, because there was no need to convert units.

Note the fact that the maximum speeds in each direction are not the same and also the fact that the dead zone is centered about 132. This was causing our robot to drift, because wheels that were supposed to be going in the same speeds with opposite directions weren’t. Also, notice that the slope gets really steep in the middle and flatten out at the end. We wanted our robot to accelerate linearly.

To fix this, we made a list of what the motor speeds would be if they were increasing linearly from 0 - the maximum and put it in a new column lining up with the old columns. We made our lookup table by looking at our list of “desired speeds” and finding a pwm setting that would create that speed. Unfortunately, that work is on the computer at school, but I’ll post it soon.

I’m actually considering putting together a whitepaper on the topic of holonomic driving, assuming ours works out.

graphForCD.xls (24 KB)


graphForCD.xls (24 KB)

AndrewN:
We’ll definitely try this, but I repeat that it’s strange because in the example you’ve provided we actually get the correct values. when ps_x is less than 122, the code behaves as we originally expected. Where it doesn’t behave properly is when p2_x exceeds 132, a case where there shouldn’t even be a plausible overflow.

When it does exceed 132, the deadzone code returns 127. I guess that does seem to fit into the idea that it would only return 0, 1, or 2 after the division. But I guess I’m just looking for a reason why they would both work in completely opposite positions.

But then again, all things considered, you’re all probably smarter than me anyway and this definitely fits the problem, because checking back in the notes, we got 128 occasionally.

Thanks.

Check and make sure your drives are going the right way, i dont think its your program, i hate to admit it but we could only go forward and backwards for about 6 hours because we didnt have the PWM;s in the right spots, and then we couldnt go right because the trim on our controller was wrong. So before you think its the program ( its usually that ) but u should check your robot and the controls aswell. Just my two cents.

Really the math almost succeeds by accident … try out the following code:
test.c (639 Bytes)

#include <stdio.h>

int
main(void)
{
        unsigned char p2_x;
        unsigned char deadx;

    p2_x = 0;
    while (1) {

                if(p2_x<122)    //more than 5 below 127
                {//scaling so we don't just jump from 127 to 121 but can still reach 0
                        //deadx = (p2_x - 122);
                        deadx = ((p2_x - 122) * 127);
                        //deadx = ((p2_x - 122) * 127)/122;
                        //deadx = ((p2_x - 122) * 127)/122 + 127;
                } else if(p2_x>132)     //more than 5 above 127
                {
                        deadx = ((p2_x - 132) * 127)/122 + 127; //scaling
                } else
                {
                        deadx = 127;    //don't move if within 5 of 127
                }

                        printf("p2_x: %d -> deadx: %d
", p2_x, deadx);

                p2_x++;
            if (p2_x == 0) return 0;  // I'm taking advantage of the wrap around
    }
}

Uncomment each of the four //deadx lines in turn and run it on any old C compiler and watch the output for the values below 122. I’ll bet they are not what you expect.
I left the rest alone just so you could see how it almost works… I’m very suprised and delighted at how close it comes to being correct :yikes:

But you still have a further overflow problem with the lines like:

pair1 = deady+127-deadx;

Don’t take my word for it, you’ll learn more (and have more fun) by experimenting with the code using small programs like the one above.

test.c (639 Bytes)


test.c (639 Bytes)

Another easy solution is to use the kit gyro and a simple PID feedback loop to adjust your heading. This year’s bot (and last years) have pretty noticeable drive train power asymmetry and this generally takes care of it quite well.

Actually, I’m hoping that we can avoid the use of a gyro (and the overhead from running four PID loops), but could someone please explain what exactly a PID loop is? We’re all essentially self-taught here, with no mentor, so we’re lost on some of the finer points.

Perhaps as a simple example, just one motor, one joystick, and one encoder? We know how to set up and run all the separate parts, and we know essentially what a PID loop should do (compare input to output), but how exactly it does it do the calculation?

A PID loop controls by looking at error.

The proportional portion says:
“Where should I be” - “Where I am”. Then it multiples that by a gain factor.

So - in pseudocode

Gyro_Proportional_Amount = (Current_Gyro_Heading - Gyro_Heading_We_Want) * Gain

Modify the drive values going to the motors with the Gyro_Proportional_Amount.

The I and D are basically the same, but one works on the accumulated error (the I or integrative) and one works on the error rate of change (D or derivative). There is a bit of sample code in the PID whitepaper you can search for here - it does a great job of explaining. MOST times, you can get away with just the “P” portion of the code I showed above.

PID loops aren’t difficult, and the overhead is minimal. Heck, I posted some of Team 95’s previous gyro control loops here on CD. (See http://www.chiefdelphi.com/forums/showthread.php?p=454174#post454174, for example).