Trossen Desktop RoboTurret Top Banner

Tutorial: Introduction to Encoders

  1. lnxfergy's Avatar
    lnxfergy lnxfergy is offline Mech Warfare Organizer Alumni
    Category
    Introduction
    Views
    22,595
    Replies
    0
     

    Introduction to Encoders

    Difficulty
    Intermediate
    An Introduction to Encoders
    My bot won't drive straight!
    Such is a common problem in robotics. Unfortunately, two motors will never be identical, two surfaces will never have the same coefficient of friction, and two wheels will never be exactly the same diameter. So what is a roboticist to do? We need to incorporate a form of feedback into our motor control. Encoders are one possible solution to the problem of a bot that won't drive straight, as well as these other problems:

    • The need to turn a specific angle
    • The need to go forward/backward a specific distance
    • Complex path planning (not covered here)

    Types of feedback for speed control of small robots
    Most people are familiar with hobby servos, the most cheap and widely-available of all motors that incorporate a feedback loop. However, servos only have a small range, about 180 degrees, due to the use of a potentiometer as their form of feedback. If we want to have our robot go more than a few inches, we need motors that can turn a full rotation, but still have feedback. What we need is an encoder that measures how much the motor or wheel turn. Encoders often use either small magnets or light.

    Do I really need encoders?

    This will depend on your environment. If your robot can reference a nearby object, such as a wall, then maybe not. You'd be surprised how much can be accomplished by just following a wall with an IR sensor.

    Theory of operation - Encoders
    We will first look at the single-channel encoder, for ease of understanding, we will say it is a magnetic one. The encoder would be constructed of a small magnet on the motor shaft, and a hall-effect sensor that puts out a pulse every time the magnet passes it. Our simple encoder gives us 1 pulse for each rotation of the motor shaft. If our motor then has a 100:1 gearbox which the wheel is connected to, we will get 100 counts per rotation of the wheel. (this is a somewhat theoretical sensor, nearly all encoders today will be Quadrature encoders and have 2 channels, but we can get this type of a sensor by ignoring the second channel)

    Encoders send out pulses related to rotation of the motor shaft. Unfortunately, you often want to control the speed, which is the distance/time, or the distance, which is the summation of pulses. We need to be able to count the pulses now. We have to realize that our pulse rate may be very high. If our wheel is going even 300RPM, our very simple encoder will give us 500 counts per second. We don't want to eat up all the processing time on our delicate micro controller - so we will use an interrupt! (see my interrupt tutorial)

    Code:
    // sample arduino code for getting relative speed from an encoder
    // this code will print the # of encoder ticks each 1/2 second
    volatile int counter = 0;
    
    void setup(){
           attachInterrupt(0, count, RISING);
           serial.begin(19200);
    }
    
    void loop(){
           counter=0;
           delay(500);
           serial.print(counter);
           serial.print("\n");
    }
    
    void count(){
           counter++;
    }
    Theory of Operation - Quadrature Encoders
    While a simple single-channel encoder can give us speed, we need more data to extract direction of rotation. While you may not always need to know the direction from the encoder, it can come in handy (for instance, if you are parked on a hill, it might be nice to know you rolled backwards rather than forwards).

    A quadrature encoder has two channels of output, commonly referred to as A and B. The two channels are 90 degrees out of phase with each other, by decoding the pulses coming out of the encoder, we can determine both speed and direction of rotation from our encoder. For instance, if you have an interrupt that triggers on a positive change in A, you can choose how your count changes based on B: if B = 0, rotation is CCW, if B = 1, rotation is CW, as seen in Figure 1 (your particular encoder may be reversed as far as CCW or CW).

    You can do a lot more to increase the resolution of the encoder. The Nubotics sensors I'll be using below give out only 32 pulses on the A channel for each rotation. However, if we then add the B channel, and pay attention to our truth table below, we can see that by keeping track of what B was last time, we can get much better resolution. Specifically, if we are interrupting on the rising edge of A:

    • and B is logic high, and B was logic high last time, we've gone clock-wise, but we've actually gone through a full wave cycle of the A channel. This is actually twice as much travel as if we had reversed direction.
    • and B is logic high, but B was logic low last time, we've still gone clock-wise, but we've reversed direction, and thus gone only 1 click in distance.
    • and B is logic low, we've gone counter-clockwise. Based on the previous B value, we can decide if we've gone 1 or 2 steps.

    Using this, we've doubled our resolution. As they say on TV though, but wait, there's more. If we interrupt on both the rising and falling edges of A, and do a similar decoding, we can double our resolution again.

    Of course, since I'm using these Nubotics sensors, it's somewhat academic to decode the A/B signals, since these awesome encoders provide an alternative Clock + Direction output that is already decoded as described above. But, we should at least discuss this, since not all encoders are quite as nice.

    Figure 1 - Quadrature Encoder output and truth table. A quadrature encoder has two channels, A and B, which are some distance out of sync with each other. If we have an interrupt routine that runs on the rising edge of A, we can determine the direction of rotation by reading B. When our encoder turns in one direction, the interrupt will be triggered on the red dots, times at which the B channel is low. If our encoder turns in the opposite direction, it will trigger our interrupt on the green dots, when B is high.

    Theory of operation - Feedback loops
    The general idea of a feedback loop is fairly simple. We have some set point value (for instance, the desired speed), and we have an actual value read from some sensor (the current speed). From this, we can compute an error. We then need to create a function that will adjust our outputs in order to make our error smaller. In technical feedback control terms, the function is known as a controller, and the device (in our case a motor) that acts on the output value is known as a plant.


    Figure 2 - A typical diagram of a feedback loop. Our set point and feedback go into a summation node, the output of which is our error, and is sent to our controller, which produces an output.

    When you drive down the road, the speed limit sign is your set point. You are a controller that adjusts the car's speed, based on the actual value you read on your speedometer. If your car has cruise control -- you've already used a closed-loop feedback device to regulate the speed of a motor!

    There are of course caveats with this. We have several things that may arise that can be detrimental or downright destructive to our system. First and foremost, a poorly configured feedback loop can easily go into oscillation, if it is really bad the oscillation could build to infinity. It will be seen that this type of situation often happens if we try to adjust too quickly. On the other hand, if we adjust too slowly, we will probably never get our actual value to equal our set point value. There are also a multitude of physical issues that will plague feedback loops. Motors don't typically have linear speed control, especially on their low-end speeds. Friction on wheels, inclines, and other properties of the environment cause spurious changes in the actual value -- these spurious changes can also set off oscillation in the controller. For all of these reasons, creating the function or controller can be difficult, time-consuming, and involve quite a bit of tuning.

    Figure 3 - Diagrams of: (A) overshoot and oscillation, (B) undershoot and residual offset, (C) near-perfect convergence.

    Typically, to make sure feedback is as responsive as needed, without overshooting, designers use a PID loop. PID stands for Proportional, Integral, and Derivative. Each of these are a smaller controller inside our control function. Each of our portions as an associated K factor, the amount it adds to the output, for instance, our proportional term might have a Kp of 0.5, and our derivative term might have a Kd of 0.2. If we have computed the error, we now have to compute how much we change our output value:

    • Proportional term: this term adds a proportion of our error to the output, it is typically what a novice would use for their entire feedback function. Typically just Kp * error.
    • Integral term: this is used to adjust out any residual offset we might have. If our controller typically approaches the value we want, but typically never actually makes it all the way to the value, an integral term may be used. Many controllers are actually just P-D controllers, they omit an Integral term. Typically just Ki * sum(errors).
    • Derivative term: effectively fights back against overshooting, by adding a value relative to how fast we are approaching our set point. Typically just Kd * (error_this_cycle-error_last_cycle).

    We will examine the control of a motor's speed, however the same process can be applied to anything really: position control, temperature of an oven, etc. When using an encoder, we have to count very delicate and often high-speed pulses, and thus we really need a dedicated controller that is running in real-time. This is best achieved using a microcontroller with interrupts to count pulses, and some hardware timer to set your time intervals if doing speed control. Our example will use an Arduino, due to it's low cost, and easy availability.

    The Speed Control Problem
    Controlling the speed of a motor is probably the most common application of wheel encoders. A quick run-down of what our software will have to do:

    1. We will store a set point value in a variable
    2. An interrupt will count pulses from the encoder, creating our actual value
    3. A second time-based interrupt will occur at some frequency (say, maybe 5 or 10hz), this will trigger our function to run and update our outputs

    The first two parts of this are almost trivial. If using a quadrature encoder, the code for #2 may be more complex. #3 is where we really earn our pay though. For now, we'll stick to a non-quadrature encoder, to keep the example code simple.

    For this example, I am using an Arduino-based robot, the controller is actually an AVRRA Mini board configured as an Arduino. I'm using GM8 motors and the associated wheels, powered by an SN754410 motor driver. My encoders are the Nubotics ones designed specifically for these motors. The encoders need just a simple 5V supply, and have both an A/B style output or a clock+direction style output. They give 128 counts per rotation when using the clock+direction output.


    Figure 4 - The miniBot

    There is an important question we need to answer:
    how often should the loop run? The more often it runs, the more accurate your speed will be (in theory), however, the speed at which ticks are generated by the encoder will govern the top speed of the loop. I'm going to cheat, and use the Clock+Direction output on the encoders to get high resolution without a lot of processing in my Interrupt routine. My encoders will therefore give 128 clicks per rotation of the wheel doing a full rotation, RPM should be around 70-80, so we can expect about 160 clicks per second. For now, we'll use a 5hz update rate, it should give us enough ability to regulate speed, while still having fast enough reaction time to actually regulate the speed.

    It's tough to regulate speed when you're need your top end speed, since you don't have any ability to go faster. Thus, we'll try to go about 100 clicks per second (which turned out to be about a PWM value of 200 out of 255 or 80% speed
    on this robot). The demo code below implements just a proportional control. It manages not to oscillate too badly (although differing flooring materials may cause issues). The code below will run 3 trials. The first trial has a low kP, and it doesn't ever make it to the desired speed. The second trial is too high of a kP, it overshoots somewhat (although does manage to level out well). The third trial is just about the right kP.

    Code:
    // Encoder Demo - M. Ferguson
    // This sketch controls the speed of our motors. Only the code
    // for the left channel is shown here.
    
    // My motors library is available at svn.blunderingbotics.com
    #include <Motors.h>
    Motors drive = Motors();
    
    // ms for each frame, this gives us 5Hz update rate
    #define FRAME_LEN    200 
    unsigned long last;  // last time our loop ran, to do 5hz update
    
    // left/right actual values, used by our ISR to count ticks
    volatile int lCount = 0;
    volatile int rCount = 0;
    
    // left/right desired values
    int lSet;
    int rSet;
    
    // motor speeds
    int lSpeed;
    int rSpeed;
    
    // PID tuning parameters
    int kP;
    int trial = 0;
    
    // enable our interrupts
    void setup(){
        Serial.begin(38400);
        // Encoder clock output is tied to Digital2, call ISR function named left.
        attachInterrupt(0, left, RISING);
        lSet = 20;          // we'll try to go about 50rpm
        last = millis();   
        lSpeed = 0;
        kP = 20;            // trial 0 - kP too low, residual offset
    }
    
    // main loop
    void loop(){
        while(millis() < last + FRAME_LEN);
        // find error, and then reset counter...
        int error = lSet - lCount;
        lCount = 0;
        last = millis();
        // do our update
        int nextSpeed = (kP * error)/100 + lSpeed;
        drive.set(nextSpeed,0);
        lSpeed = nextSpeed;
        // output some data
        Serial.print(nextSpeed);
        Serial.print(",");
        Serial.println(error);
        
        // our demo code: change our kP over time
        if(millis() > 30000){
            // we are done
            drive.set(0,0);
            while(1);
        }else if(millis() > 20000 && trial == 1){
            // trial 3 - just right! 
            drive.set(0,0);
            kP = 100;
            lSpeed = 0;
            trial = 2;
        }else if(millis() > 10000 && trial == 0){
            // trial 2 - kP is too high, oscillates
            drive.set(0,0);
            kP = 1500;
            lSpeed = 0;
            trial = 1;
        }
    }
    
    // only regulating left speed right now...
    void left(){
        // we want this to be fast, so we'll avoid digitalRead()
        // digital 8 is the direction channel, but that is actually AVR PB0, so:
        if(PINB&0x01)
            lCount++;
        else
            lCount--;
    }
    Some commonly available (and awesome) encoders
    While you could go the route of printing your own encoder disks, and building a circuit that works, there are quite a few nice packages already out there. Possibly my favorite unit is the Nubotics encoders for the Solarbotics motors (they also make a version for continuous rotation modified servos). These encoders fit right onto the motor case and are about just the right resolution for many common tasks people want to do with small robots.

    For slightly larger robots, Lynxmotion sells thier 37MM gear motors with shafts on both ends. The back end can be used with a very high-end US Digital encoder. These encoders are actually so fine of a resolution that they typically give several thousand clicks per inch, even with large wheels.
    Often, this is just too much data, and your poor micro controller can't do anything else but count the rotations of the wheel. An encoder divider is a circuit board that can be used to reduce the number of counts your encoder gives off, Banebots sells a fairly inexpensive one that will also translate your A/B signals into clock+direction.

    You can also often find surplus motors with built in encoders.
    Unfortunately, many times the pin-outs are not that well known.
    Attached Files
    • feedback
    • trace
    • convergence
    • truth
    • miniBot