Using encoders for traction control help

Hi all,
We’ve been using encoders to measure the wheel velocity, and then encoders on some omni wheels to measure actual velocity. However, I’ve been unsuccessful in getting this to turn into good traction control.

My code currently finds the velocity according to the omnis and the encoders on the transmission, and also finds the current motor value and the desired motor value (according to the joystick). Then, using some magical function I have yet to get working, it computes what the new motor value should be.

The problem, of course, is said magical function. I’ve been trying a bunch of different ideas, and none of them seem to work well (or really improve it at all). However, I am able to tell when the robot is slipping by seeing if the difference between the velocity on the real wheels and the velocity on the omni wheels is above some threshold. Has anyone had any success programming traction-control drive code using encoders?

Thanks,
Kevin

I haven’t actually started on this yet, but the basic idea is that if a wheel is spinning, slow that wheel down and speed up the other wheels. If all wheels are spinning, I’m not sure what you do. I think you would just slow down all of them.

Also, your wheel configuration is important. If you have a 2 motor drive and all wheels are powered, you have to slow down the side that is slipping and speed up the side that is not. For all-wheel drive, slow down the one and speed up the others.

This is just my thinking, I could be wrong (I haven’t tried it yet!). Go ahead and search on traction control systems and see what you can find. Good luck!

Thanks for the tips. Here’s examples of what I’ve been trying (none of which works):


float correctForSlip(float transVel, float omniVel, float realWheel, float desiredWheel)
{
	// make sure that there's enough of a difference
	const float slipThreshold = 15.0;
	if (fabs(transVel - omniVel) < slipThreshold)
		return desiredWheel;

// note for forums: code after here varies depending upon attempt

	const float adjustSlipK = -0.005; // tuned experimentally
	
	float delta = fabs(transVel - omniVel) * adjustSlipK;
	
	if (transVel < omniVel) // need to up the number
		return desiredWheel + delta;
	else
		return desiredWheel - delta;
}

That didn’t work, so I tried instead:


	if (transVel != 0) {
		float decreaseMtrMultiplier = omniVel / transVel;
		return decreaseMtrMultiplier * desiredWheel;
	} else {
		const float seedSlipK = 0.001;
		return seedSlipK * omniVel;
	}

And then this fails too:


	// stupid method: increase wheel speed
	const float speedupK = 0.05;
	if (realWheel > 0)
		return realWheel + speedupK;
	else if (realWheel < 0)
		return realWheel - speedupK;
	else { // realWheel == 0
		if (omniVel > 0)
			return speedupK;
		else if (omniVel < 0)
			return speedupK;
		else // should never happen
			return desiredWheel;
	}

??? Perhaps I’m missing something here, but the “back off when a wheel is going too fast, increase when going too slow” seems to be difficult to implement correctly.

-Kevin

Since your setup may be different than others, it may be difficult to hone in an algorithm that will work for your specific platform. There are many good articles on the internet on how traction control can work. They are, however, application specific - meaning it depends on the number of drive wheels and their respective setup.

This pseudo-code should get you started…


commanded_speed = Get_Our_Desired_Speed()
robot_speed = Get_Actual_Robot_Velocity()
wheel_speed = Get_Wheel_Velocity()

speed_error = robot_speed - wheel_speed

// Detect if we are slipping
If absolute value of speed_error is greater than tolerance then we are slipping
{
  // If we are, get back our traction ASAP!
  Set commanded_speed to robot_speed
}
Else 
{
  // If we aren't slipping, let's keep it that way!
  Limit commanded_speed based on maximum acceleration to prevent slipping
}

Send commanded_speed to the motors
Close the loop between commanded_speed and wheel_speed with a PID controller (optional)

Hopefully this will be useful!

Also, keep in mind that if you turn differentially (tank-style), your wheels ARE going to slip a little bit. Take that into account or your idler wheel is going to freak out your traction control system every time you turn.

One way we’ve discussed (with no encoder wheel, just the drive wheels) is to detect the rate of acceleration of the wheel in comparison to the angle of the joystick. If the wheel acceleration vs. joystick angle “acceleration” is at one curve and suddenly spikes, the wheels are probably slipping, and so you dial the speed back and reengage (the ABS method).

For your application, I would suggest having an encoder wheel for every independently controlled axis (as in 2 for a differential drive, one on each side), that way you could do turning more easily. You might be able to fool a pre-existing PID control function into working between the drive wheels and the encoder wheel. Probably the best way to do this would be to take the PID function included with WPILib and modify it to do all of the adjustment on one entity, instead of scaling both (usually it scales up the slower one and scales down the faster one, but you just want to scale down the faster one).

I suggest the mighty Wikipedia and Google for details on car traction control, as it’s all done without encoder wheels, and will probably not be that different from what you’re trying to do.

That’s not true daltore:

Many vehicles, and most all of the better traction control systems (especially on higher end sports cars) and ABS systems use wheel speed sensors. In fact, next time you go to get your tires changed ask them if they charge extra for wheels that have speed sensors (I know Sams Club and Discount Tire both do).

Acceleration integration, measuring voltages and currents to the motor controllers, and measuring rate of change of the joystick position are all very valid ways of doing traction control, but the absolute best way of doing it is measuring the wheel position / velocity exactly.

Hi, thanks for the suggestions. Here’s what I’m currently using, and it seems to work better than my other algorithms, but still very poorly (worse than no correction at all :().

Here is the algorithm:


// transVel: the velocity according to the wheels (transmission)
// omniVel: the velocity of the robot (off the omni wheels)
// realWheel: the PWM value currently being sent to the wheel (not currently used)
// desiredWheel: the PWM value the joystick says the wheel should be
// maxSpeed: the maximum velocity of the robot
float correctForSlip(float transVel, float omniVel, float realWheel, float desiredWheel, float maxSpeed)
{
	// make sure that there's enough of a difference
	const float slipThreshold = 30.0;
	if (fabs(transVel - omniVel) < slipThreshold)
		return desiredWheel;

       // We are slipping.
	
	// calculate what speed we are saying we should go at
	// This should be whatever the omni wheels say
	float correctSpeed = omniVel / maxSpeed;

        // at this point, we can return correctSpeed
        // however, this makes it really jerky, so we this following instead:
	// Average them, to still give the drivers some control
	return (desiredWheel + correctSpeed) / 2.0;
}

In short, it sees if it’s slipping, and if so, it converts the velocity of the robot into a PWM value (float correctSpeed = omniVel / maxSpeed; ) and then averages that with the desired PWM value according to the joystick.

So, the step I’m stuck on is the “Limit commanded_speed based on maximum acceleration to prevent slipping” step from Abwehr’s algorithm. Does anyone know of any better ways of limiting the commanded speed?

Thanks,
-Kevin

Maybe this will help, maybe not. I’m not a programmer.

If commanded speed is higher than what will support the maximum slippage - that is, if the joystick is pushed too hard - the wheel slips. First thing we need to do is figure out how much slip we have (which you seem to have done?) and now you ask how to reduce the commanded speed to one that maintains a slip level lower than your threshold (say, 20% slip).

This becomes a case of a PID loop. First we need to determine the Proportional error (“P”) and correct for it: Let’s say wheel speed is 100 and trans speed is 130, so we have 30% slip (130-100=30) and we have to slow the motors down. Cut back the current PWM value by a certain amount (let’s say 1 PWM value out of 254 for every 10% of slip (the “correction factor”, which would be 3 here)). Then, we loop around to the top, check the commanded value, check the wheel speed, check the trans speed and repeat the P error calculation - if still too fast, reduce by 1 and repeat all again.

OK, that will work for excessive acceleration. Deceleration is your problem, but it works the same way - or you can ignore it (reasonable for this game).

But, this is far from optimized: Maybe a step of (1PWM * (slip/10)) is not fast enough to control slippage. Maybe it is too fast. Or perhaps you need to limit or vary the step size according to the slip percentage…This is called Derivative control, or “D”, because it measures the slope of the P error curve (*which is a calculus derivative of the curve equation, but that’s not important here…). *For example, if the error is 100% slip (wheels turning twice as fast as robot is moving), we might cut PWM by 20 counts at a time, 40% slip, maybe 6 counts, and so on.

To implement D, we can take the last correction factor (which was a 3 PWM step drop in the example), subtract it from the current (just-calculated) correction factor, then multiply it by some scale factor (start with 1, could go to 0 or up to maybe 5) to come up with a step size that is not a simple multiplication, but has some ‘memory’. You might even use a multiplication with the P error also to give it even more nonlinearity, you have to play with the scale factors.

Got this so far?

The last case - Integral - is actually not necessary in a highly dynamic system. And your robot likely qualifies. What the “I” factor does is correct for small but persistent errors, by allowing them to build up over time so that they can be noticed and acted upon. You see, P and to a lesser extent D need to be somewhat insensitive to small errors, otherwise they’d go crazy all the time and oscillate. But a small error can, over time, cause a problem. You get the I factor by adding the last P error to the current P error, and keep adding them up forever. Once it gets big enough it has some impact, then the system corrects the error so that the I factor decreases (less than 0% slip subtracts) and the cycle repeats. But again, you don’t really need this.

OK, so you go around and around, seeing where you are and where you need to be each time, and nudge in the right direction. If your nudges are too slow, bigger scale factors are needed, or a faster loop. Too fast (or oscillating), smaller factors or a slower loop. Factors are easier to change than loop speed. Eventually you fall below your target slip amount and your correction factors all go to unity (1) and commanded input = commanded output.

Be sure to put an over-ride for the driver, as turns can be better with high slippage. Maybe the trigger button turns it off.

I can explain this a dozen different ways, so ask about what isn’t clear.

But first, find and read the whitepaper “PID without a PhD” as this will make you understand that PID algorithms are well-understood and easily implemented - it’s just that tuning them (meaning getting the various multiplication factors correct) is a bear. Approach the system in the same way that the paper advises - in your code I see stuff I don’t understand, and I don’t see you doing the right stuff to the error values - and then see what happens.

I really hope this helps. Write back.

I’ve figured something out about traction control which actually is really obvious, but I didn’t realize until now: You DON’T want to only have your traction control kick in when it’s slipping. Otherwise, you would have your robot at rest (no slipping). The driver goes all the way forward, the drive code says ok because there’s no slip, and then you’re spinning out hugely, and then have to compensate hugely, etc.

So here’s the algorithm that I’ve come up with, and it seems to work OK:

  1. Get the desired PWM value and convert it to a desired velocity (multiply it by the maximum velocity of the robot)
  2. If the desired velocity is outside of some maximum range of the current velocity (according to the omni wheels), then clamp it to within that range
  3. Convert desired velocity back into desired PWM value by dividing by the maximum velocity of the robot

The problem is this: the maximum range that works best for pushing seems to be different for driving… It seems that it would help to dynamically calculate the best maximum velocity change, but I’m not sure how.

I will organize this post as follows: I will begin with a dissection of your approach to traction control, a general explanation of a few things, and then an explanation of our team’s approach to traction control. I apologize in advance for the length of this post, but I want to be thorough.

First, your code: as currently posted, I see a few reasons why it’s not performing as expected. For clarification, the way I interpret your intentions is that you will take the velocity from the encoders, compare it to the expected velocity from the omniwheel encoder, and adjust the speed once slippage occurs. So, here goes: …

// make sure that there’s enough of a difference
const float slipThreshold = 30.0;
if (fabs(transVel - omniVel) < slipThreshold)
return desiredWheel;

This will always return “desiredWheel” if you are inputting values directly from GetY() or the direct float which drives the speed controller; if you are using PWMs, then I’m not sure if this threshold is sufficient; it seems fairly high, since it would be about 25% (assuming -128 to 128) or the possible scale for PWMs. Perhpas you can shift the slipThreshold so that it can, in fact, be violated.

// calculate what speed we are saying we should go at
// This should be whatever the omni wheels say
float correctSpeed = omniVel / maxSpeed;

I haven’t looked at the CIM motor charts (what’s the exact term for them? they display motor torque, output shaft speed, etc), but are CIMs fully linear? If so, this will work; if not, then this will have slight differences and cause a jumpy control system. This gives you the current motor output based on the current velocity and the maximum speed of the robot, assuming CIMs are linear; to apply Occam’s Razor, wouldn’t it be simpler just to record the last time the wheels were not slipping as the correct motor output for the current velocity?

    // at this point, we can return correctSpeed
    // however, this makes it really jerky, so we this following instead:

// Average them, to still give the drivers some control
return (desiredWheel + correctSpeed) / 2.0;

This will take the inputted desired wheel speed, take the current wheel speed, average them, and return that motor output. The way I see it, all this does is reduce the sensitivity of the joysticks by about 1/2 – if the current wheel speed is 0, and the joystick input is 1.0 (full forward), you’re still going to get slippage, at least temporarily.

A few last general issues I see with your code.
First, the omniwheel is in the middle of the robot, right? If it’s not in the exact same position as the wheel you are adjusting, you’re going to have issues when you turn because the velocities will be slightly different; this will result in a very jumpy, buggy control system.
Second, you’re using the slip after the slip occurs, but then you have already lost about 1/6 of your possible acceleration, and the system will either lose acceleration or allow slipping x times per second, depending on how fast your corrections are. This is the only way you can do it with velocity; my suggestion is that you use acceleration instead of velocity. With a little experimentation (even just by tweaking a constant in the code) and taking the derivative of the velocity, you can cap the acceleration greater than the max you will get while slipping, but less than the acceleration which would allow slippage. This should be somewhere around 0.50 to 0.55 m/s^2, using provided coefficients of friction (which have been found to be inaccurate, thus the necessary tweaking).

Lastly, my team’s approach to traction control: we have one encoder on each drivetrain; one for the left, one for the right. We take the distance from the encoder and derive it to get v, then derive it again to get a. If a is greater than a certain threshold, we adjust the outputted motor speeds proportionally (the constant has been found experimentally to allow good, smooth acceleration without slipping). If anyone would like to see our algorithm, or get further explanations of how we do it, you can PM me; I’d gladly share the code, I just don’t want to lengthen this post more than I already have, and the code doesn’t necessarily make sense taken out of context (without the classes I have defined, and the variables I have declared).

If you would like any more assistance, I’d be glad to help you in any way. Communication is not limited to CD, if AIM or Facebook would be easier to do.

Disclaimer: This is my second year doing this, and my second year programming. I am not familiar with PID loops; everything here is what I’ve picked up this season.

So, if I understand your algorithm correctly, it works like this (for each side):

  1. Get the acceleration according to each encoder on the transmission
  2. If the acceleration is too high, compute the ratio:

maximum_acceleration / real_acceleration

where maximum_acceleration is determined experimentally. Then, multiply the desired motor value by said ratio.

Is that right? So do you just ignore the velocity according to the encoders on the omni wheels (undriven wheels)? It sounds simple, but it also sounds like it could work (occam’s razor ftw).

Yes, except I actually am just using a constant instead of the ratio; I am going to try that ratio idea, though; I rather like it, and it may work a bit more optimally, as well.

I do ignore velocity, and we don’t have any omni encoders, and I haven’t installed our accelerometer or gyro yet. The one thing I could add would be a comparison to test for real time slipping – this will only occur if we get in a shoving match, especially against a wall, but in those cases, I trust our drivers to compensate.

Occam’s Razor always wins.

I tried implementing the algorithm, but it leads to very jerky behavior. It seems to see that it’s accelerating too fast, then back off, then speed up again, etc. Did you also have this problem?

Thank you for your help.