3.6 Antilock Brake System and Traction Control

Introduction

During the test runs you have seen the rear wheels skidding or some wheels locking. It's because we accelerate or brake too hard, so the friction of the tire on the road is smaller than the force we apply. When the speed of the tire surface relative to the track (with no slip we would have perfect rolling, so the tire contact point would have zero relative speed to the track) becomes too big we loose the static friction. Because of that we can transport even less force... so we need a system that tries to avoid that. We will implement a simple wheel antilock brake system (ABS) and a simple traction control (TCL).

Anti Wheel Locking

I show you a simple implementation which considers the average slip of all wheels. You could also take a weighted sum according to the situation or just the wheel with the minimum slip. Feel free to improve it.

/* Antilocking filter for brakes */
float Driver::filterABS(float brake)
{
    if (car->_speed_x < ABS_MINSPEED) return brake;

If we are too slow don't consider ABS (division by zero).

    int i;
    float slip = 0.0;
    for (i = 0; i < 4; i++) {
        slip += car->_wheelSpinVel(i) * car->_wheelRadius(i) / car->_speed_x;
    }
    slip = slip/4.0;

Compute the average slip of the four wheels.

    if (slip < ABS_SLIP) brake = brake*slip;
    return brake;
}

If the slip variable is below a certain threshold reduce the brake value by multiplying it with the slip. Put this code into driver.cpp. You need also to insert the call into the drive method and to define the new constants. Change

    car->ctrl.brakeCmd = getBrake();

to

    car->ctrl.brakeCmd = filterABS(getBrake());

and add the constants

    const float Driver::ABS_SLIP = 0.9;        /* [-] range [0.95..0.3] */
    const float Driver::ABS_MINSPEED = 3.0;    /* [m/s] */

driver.h needs also an update:

    float filterABS(float brake);

    static const float ABS_SLIP;
    static const float ABS_MINSPEED;

Traction Control

The approach I show you for TCL is almost the same like for ABS. But there is a difference, which will lead to more "administrative" stuff. Because we can have rear, front or all wheel driven cars, we have depending on the type to check the appropriate wheels. Just for fun we will implement that with a pointer to a method (the right way would be the delegation pattern with an abstract class and three subclasses). Let's implement top down, change the call in the drive method from

        car->ctrl.accelCmd = getAccel();

to

        car->ctrl.accelCmd = filterTCL(getAccel());

Now we implement filterTCL in driver.cpp. Have a look on the funny call.

/* TCL filter for accelerator pedal */
float Driver::filterTCL(float accel)
{
    if (car->_speed_x < TCL_MINSPEED) return accel;
    float slip = car->_speed_x/(this->*GET_DRIVEN_WHEEL_SPEED)();

Like with the ABS we have a minimal speed to consider TCL. We compute the slip with a function that we plug in the GET_DRIVEN_WHEEL_SPEED variable during initialization.

    if (slip < TCL_SLIP) {
        accel = 0.0;
    }
    return accel;
}

Then we check the slip variable, and if it's too small we set the accelerator pedal to 0. Next I show you the initialization of the "plugin". This approach has the advantage that we don't need to check the car type during runtime. The following method checks the car type (from the XML file) and plugs in the fitting method.

/* Traction Control (TCL) setup */
void Driver::initTCLfilter()
{
    char *traintype = GfParmGetStr(car->_carHandle,
        SECT_DRIVETRAIN, PRM_TYPE, VAL_TRANS_RWD);
    if (strcmp(traintype, VAL_TRANS_RWD) == 0) {
        GET_DRIVEN_WHEEL_SPEED = &Driver::filterTCL_RWD;
    } else if (strcmp(traintype, VAL_TRANS_FWD) == 0) {
        GET_DRIVEN_WHEEL_SPEED = &Driver::filterTCL_FWD;
    } else if (strcmp(traintype, VAL_TRANS_4WD) == 0) {
        GET_DRIVEN_WHEEL_SPEED = &Driver::filterTCL_4WD;
    }
}

Here follow the car type specific methods.

/* TCL filter plugin for rear wheel driven cars */
float Driver::filterTCL_RWD()
{
    return (car->_wheelSpinVel(REAR_RGT) + car->_wheelSpinVel(REAR_LFT)) *
            car->_wheelRadius(REAR_LFT) / 2.0;
}


/* TCL filter plugin for front wheel driven cars */
float Driver::filterTCL_FWD()
{
    return (car->_wheelSpinVel(FRNT_RGT) + car->_wheelSpinVel(FRNT_LFT)) *
            car->_wheelRadius(FRNT_LFT) / 2.0;
}


/* TCL filter plugin for all wheel driven cars */
float Driver::filterTCL_4WD()
{
    return (car->_wheelSpinVel(FRNT_RGT) + car->_wheelSpinVel(FRNT_LFT)) *
            car->_wheelRadius(FRNT_LFT) / 4.0 +
           (car->_wheelSpinVel(REAR_RGT) + car->_wheelSpinVel(REAR_LFT)) *
            car->_wheelRadius(REAR_LFT) / 4.0;
}

Put the initialization call into newRace(...).

    initTCLfilter();

Define the constants in driver.cpp

    const float Driver::TCL_SLIP = 0.9;        /* [-] range [0.95..0.3] */
    const float Driver::TCL_MINSPEED = 3.0;    /* [m/s] */

and add the following to driver.h.

    float filterTCL(float accel);
    float filterTCL_RWD();
    float filterTCL_FWD();
    float filterTCL_4WD();

    void initTCLfilter();

    float (Driver::*GET_DRIVEN_WHEEL_SPEED)();
    static const float TCL_SLIP;
    static const float TCL_MINSPEED;

Final Remarks

If you compare the lap times of the "gear" section with the current ones, then you can see that we have won a lot. On e-track-4 we won ~15s, on e-track-2 ~3s and on mixed-2 ~7s. That's very nice. The next thing we need to improve is the way we take on the track. Ah, the measurements:

  • mixed-2: 1:35:44, 831 damage.
  • e-track-2: 1:42:96, 0 damage.
  • e-track-4: 1:59:69, 4 damage.

Downloads

In case you got lost, you can download my robot for TORCS 1.2.0 or later.

Feedback

Let me know if you read this chapter and your thoughts about it. Please send me also spelling, grammar, math and code corrections. Thank you for the feedback.

Summary

  • You know how ABS works.
  • You know how TCL works.
  • You have implemented it.
  • Your brain is full of ideas and you are keen to implement it.