2
\$\begingroup\$

I have a Raspberry Pi and PWM motor controller driving a DAGU chassis with tracks. The driven wheels each have their own 8-pole encoder on them, and I count a rising or falling edge as a 'tick'. So there are 22.5 degrees/tick. The motors are geared 48:1.

The tracks and wheels give uneven load to the motor every full rotation. In other words, over the course of a full rotation the motor (or your hand) will find the tracks easier to turn at some points, and harder at others. This is due to the wheels/treads being a bit out of round.

This results in my PWM loop oscillating as well. I'm not sure how to "tune" this out though. First thought is to calculate the error over a longer period of time, but then the motors take forever to get up to speed.

Is there a general strategy for this, or should I just use something like:

if motor is stopped
  use fast PWM rate
otherwise if motor has been moving for a while
  use a slower rate

Apologies if this is the incorrect SE site. I wasn't sure if here or SO would be correct.

Main Loop:

    #define MOTOR_TICK_DELAY    1000000000LL //1000ms
    motorA.target = 1.5;
    motorB.target = 1.5;

    debug("Standby off.\n");
    bcm2835_gpio_set(STBY);

    struct timespec time;
    uint64_t cTime = 0, lTime = 0, refreshTime = 0, lTimeA = 0, lTimeB = 0;
    int timeCount = 0;

    clock_gettime(CLOCK_MONOTONIC_RAW, &time);
    lTime = time.tv_sec * MOTOR_TICK_DELAY + time.tv_nsec;

    while (quit == 0)
    {
        clock_gettime(CLOCK_MONOTONIC_RAW, &time);
        cTime = time.tv_sec * MOTOR_TICK_DELAY + time.tv_nsec;
        refreshTime = cTime - lTime;

        motorUpdate(&motorA, &cTime, &lTimeA);
        motorUpdate(&motorB, &cTime, &lTimeB);

        if (refreshTime > REFRESH_SPEED)
        {//Refresh the ncurses output}
    }

Motor PWM calculation method:

void motorUpdate(struct Motor* motor, uint64_t* cTime, uint64_t* lTime)
{
    if (encoderTick(motor->encoderPin)) ++motor->encoderCount;

    uint64_t dt = (uint64_t)(*cTime) - (uint64_t)(*lTime);

    if (dt > MOTOR_TICK_DELAY)
    {
        //debug("ctime: %llu\n", *cTime);
        //debug("ltime: %llu\n", *lTime);
        //debug("diff: %llu\n", dt);

        //actual speed = tick count / (dt in ???seconds)
        motor->actual = (float)motor->encoderCount / (dt / 100000000LL);
        motor->encoderCount = 0; //TODO need rolling average
        *lTime = *cTime; //reset motor last tick time

        //calculate raw PWM
        motor->lastError = motor->error;
        motor->error = (motor->target - motor->actual);
        motor->dError = motor->error - motor->lastError;
        motor->iError += motor->error;
        motor->pwm = (motor->error * 75 + motor->iError * 120 + motor->dError * 5);
        //motor->pwm = (motor->error * 50 + motor->iError * 100 + motor->dError * 10); //pretty smooth, takes a while
        //motor->pwm = (motor->error * 60 + motor->iError * 120 + motor->dError * 10); //a bit jerky, but doesn't overshoot

        //clamp PWM
        if (motor->pwm > PWM_RANGE) motor->pwm = PWM_RANGE;
        else if (motor->pwm < 0) motor->pwm = 0;

        //set PWM
        bcm2835_pwm_set_data(motor->pwmChannel, motor->pwm);
    }
}

Full code is also available here: https://github.com/nearwood/rpi-drd/blob/master/controller/src/controller.c

\$\endgroup\$
10
  • 1
    \$\begingroup\$ What speed (in terms of stiff-easy-stiff per second) are you aiming for? Do you have position/speed feedback, or are you doing open loop control? If you do, what's your sensor bandwidth? \$\endgroup\$
    – Jack B
    Aug 13, 2016 at 21:56
  • 1
    \$\begingroup\$ How is the loop closed? \$\endgroup\$
    – pjc50
    Aug 13, 2016 at 21:56
  • \$\begingroup\$ I've updated the question with encoder information. It's closed loop. There's no real speed I'm aiming for. The motors will drive the tracks at a max of about 50rpm in the air. I'd like to be able to utilize, say, anywhere from 10-40 rpm (the desired speed will eventually be remote controlled). \$\endgroup\$
    – Nick
    Aug 13, 2016 at 22:09
  • \$\begingroup\$ (1) Are the encoders single channel or dual? (2) Are the encoders on the wheels or on the motors? Motors would be 48 times better. \$\endgroup\$
    – Transistor
    Aug 13, 2016 at 22:14
  • \$\begingroup\$ can you post the code for your PID algorithm? \$\endgroup\$
    – Techydude
    Aug 13, 2016 at 22:21

2 Answers 2

1
\$\begingroup\$

First of all, add a feedforward loop with LUT. For first experiment a LUT can be calculated as \$ U_{static}= k_{PWM}\cdot k_u\cdot v_{SP}\$ Where \$k_u\$ is the motor constant \$[\dfrac{V}{rpm}]\$, \$v\$ is the speed \$[rpm]\$, \$U_{static}\$ are %PWM and \$k_{PWM}\$ is a constant of your H-bridge \$[\dfrac{\% PWM}{V}]\$
You can tune whatever controller with Ziegler-Nichols method. At very low speed your encoder is not capable to deliver good information, so you can implement a switch that disables the controller. Begin to set up with small steps, first use P-controller only, then turn to PI, PID could be just a headache. IMO the P-controler is what will suit your demand.

enter image description here

\$\endgroup\$
1
\$\begingroup\$

enter image description here

Figure 1. This is the Wheel Encoder Kit from DAGU, a simple add-on to any wheeled robot that can help measure the speed or distance the chassis travels. Each wheel encoder kit consists of two neodymium 8-pole magnets with rubber hubs and two hall-effect sensors terminated with 150mm cables and 3-pin female servo headers. These wheel encoders require a supply voltage of 3-24V with a supply current of 4mA. Source: Sparkfun.

The encoder has 8 poles. The encoder comes with two sensors so by putting these an odd multiple of 360/16 = 22.5° apart we can double the number of pulses and double the encoder resolution from 8 to 16 pulses per revolution.

schematic

simulate this circuit – Schematic created using CircuitLab

Figure 2. Using two sensors we can double the resolution.

schematic

simulate this circuit

Figure 3. Getting 32 pulses per revolution is possible using two sensors and both rising and falling edge detection.

By offsetting the sensors an odd multiple of 360/32 = 11.25° apart you could sense 32 encoder edges per revolution. This will take two micro inputs per encoder and a little debouncing and edge detection code but should not be too difficult.

This may not solve all your problems but it should make them at least four times easier.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.