Skip to content

50 How to calibrate an encoder motor

Björn Giesler edited this page Apr 17, 2024 · 9 revisions

Calibrating an encoder (DC) motor

Use the DCMotorTest Arduino sketch (in Arduino/Utilities/) for this. The sketch supports two motors at the same time. Initially we will only use one.

Step 1: Define motor driver type and pins

Change the pins at the beginning of the sketch to reflect the type of motor driver you are using, and what pins the motor driver and the encoder are connected to. At the time of writing, the code supports two different ways of talking to the motor driver, and you need to choose the correct one:

  1. DCMotor(PIN_DIR_A, PIN_DIR_B, PIN_PWM) or optionally DCMotor(PIN_DIR_A, PIN_DIR_B, PIN_PWM, PIN_ENABLE) uses two direction pins and one PWM pin (and optionally an enable pin). This kind of driver works by choosing the rotation direction using PIN_DIR_A and PIN_DIR_B (low/high: one direction, high/low: other direction, low/low: idle, high/high: brake) and the rotation speed using PIN_PWM. PIN_ENABLE is optional and will be pulled high (to enable the motor) on start and pulled low (to disable the motor) on stop.
  2. DCMotor(PIN_PWM_A, PIN_PWM_B) (like the DRV8871 motor driver) uses two PWM pins. This kind of driver drives the motor in direction one by setting PIN_PWM_B to low and setting the speed using PWM on PIN_PWM_A, and vice versa. Idle is done setting both to low and brake by setting both to a PWM value.

Step 2: Define encoder pins

The encoders just use two pins each, PIN_ENC_A and PIN_ENC_B. Set them according to your encoder connections. If you do not have any encoder, set them to PIN_DISABLED; you will still be able to run the motor under PWM control, but of course speed or position control calibration will not be useful.

Step 3: Test motor driver and encoder function, and correct motor direction

MAKE SURE YOUR MOTOR GEAR CAN RUN FREELY FOR THIS TEST! PUT THE DROID ON A BASE THAT LIFTS ITS WHEELS OFF THE GROUND! WE DO NOT WANT THE DROID TO MOVE AT THIS POINT.

Compile and upload the sketch, and connect via serial connection. You can type "help" (followed by a newline) to see what options the program gives you. First we will test motor driver and encoder function. To do this, enter the following, each followed by a newline:

  1. set useMotor 1 -- this selects only the first motor
  2. set mode 0 -- this selects PWM control
  3. set goal 0
  4. start -- this will start the test. The motor should not turn now! If it does, switch off power immediately or type stop, and double check your pin connections! If everything is OK, you should see lines rushing past stating that your motor goal is 0, and the encoder is not counting any ticks.
  5. If you can turn your motor manually, you should do this now. The goal should not change but the encoder values should. If they are not changing, double-check your encoder pin connections.
  6. Now you can enter positive or negative numbers to set the motor PWM rate, and let the motor turn. You can enter the numbers directly, without a command to prefix them. Please use numbers close to 0 first and work your way up. The valid range for the PWM rate you enter is between -255 (full speed backwards) and 255 (full speed forwards). Low non-zero values should give increasingly loud whine while the motor is trying to break the gearbox resistance, until it finally starts to run at low speed. Please note that the frequency of the whine depends on the Arduino's PWM frequency, which is usually in the mid hundreds of Hz (490Hz / 980Hz on an Arduino Uno, 700Hz on the MKR Wifi 1010). If you don't hear a whine, that is likely an indicator that something is wrong. Not necessarily - the PWM frequency may be set too high to hear, and motor drivers can take PWM frequencies far beyond human hearing, but it's definitely out of the ordinary so check your connections.
  7. If everything is OK for low numbers, check if anything is grinding or catching; if it is, turn the power off immediately and fix the mechanical problem. If everything is running freely, work your way up to -255 and 255 respectively. Check if the encoder values are going up with positive numbers and going down with negative numbers. If they are not, switch the encoder pins or the motor direction pins around (but not both!).
  8. Write down the approximate speed your encoder gives you at a goal value of 100. We will need to use that information for the speed controller calibration below.
  9. Finish the test by typing 0 followed by a newline, and then stop followed by a newline.

Congratulations, if everything worked so far your motor drivers and encoders are configured correctly! If you have two motors (R2, D-O, and other differential driven droid types), you can test the second motor by set useMotor 2 and repeating steps 2-7 of the above. Make sure the second motor runs in the same direction as the first. If it does not, switch your motor driver pins and your encoder pins for one of the motors. You can test both motors side to side by selecting both of them with set useMotor 3.

Step 4: Calibrate speed control

Now we will begin tuning the PID speed control for the motors. This will only make sense if you have encoder motors; if you do not, you can stop now.

PID control is governed by 3 values: The Proportional, the Integral, and the Derivative part (hence the name). Each has its own constant, that contributes to the system behavior. A lot has been written to explain PID control in depth. We do not want to repeat this here. But in a nutshell, what happens is the following:

  1. The proportional part, governed by constant speedKp, looks at the speed goal the motor should drive at, determines the present error (i.e. the difference between present speed and goal speed) and multiplies the error with speedKp. What this does is generate more control output the farther away the present speed is from the goal speed. This is the first and most important constant we want to tune. What we want to do is to get the motor to come as close as it can to the goal without overshooting and starting to swing. (Trust me, you will recognize swinging immediately.) But since our motor has some friction, if the error becomes small enough the proportional part becomes so small is not able to get it any closer to the goal. Therefore under pure proportional control we will probably not hit the goal reliably.
  2. The integral part, governed by constant speedKi, looks at the integral (i.e. the sum) of errors over time. Since that sum will increase while the system is still away from the goal, we can use it to compensate for the remainder of the error, that is too small for the proportional part to still make a difference. However we want to be careful. For the error buildup to decrease again the system will have to overshoot the goal some (because then the error switches sign from positive to negative or vice versa, so the absolute error will decrease). If we set the integral constant too high it will overshoot a lot, building up error in the opposite direction, then undershooting, etc., making the motor swing.
  3. The derivative part, governed by constant speedKd, looks at the derivative, i.e. the rate of change of our system, and this can be used to compensate / dampen swinging. For control applications where your the system is naturally swinging all the time, like inverted-pendulum stabilization, it is important, but is rarely used for motor speed control outside of problematic cases, so we can probably ignore it. We will therefore skip it for this writeup but feel free to experiment!

A word about units - we can run the system in units of encoder ticks per second, or millimeters per second. The PID controller code we use internally always uses ticks per second, but can convert to/from millimeters per second for convenience. For this to work, you need to set the constants WHEEL_CIRCUMFERENCE and WHEEL_TICKS_PER_TURN in the code to reflect your droid's physical properties. We will do the calibration using encoder ticks per second though; the constants we calibrate will be the same because the framework compensates for the unit change.

A word about "swinging" - You will notice a number of factors that cause the system to not approach the goal without noise. Some of that noise will even be audible, some will only be visible in the plots we will look at later. Likely your motor or wheel is not running entirely round, possibly grinds somewhere very slightly, or has weight differences around its circumference. These will result in periodic audible noise. Also your motor driver is not perfect and it runs at 8 bit resolution driven by the Arduino, and the encoder is resolution limited too. So you will see noise from many influences. You will be able to tell this apart from swinging easily at medium to higher speeds, where swinging will show itself violently if present; but at very low speeds the speed signal is almost drowned in the noise so if there is swinging it will be harder to tell. A good rule of thumb is that at half speed, the speed signal given by the encoders should not vary by more than 5%. If it does, there is likely a problem in your setup.

To calibrate the PID controller, do the following:

  1. set useMotor 1 to select the first motor
  2. set mode 1 to select speed control mode
  3. set goal 0
  4. set speedKp 0
  5. set speedKi 0
  6. set speedKd 0 -- we start with zero values and work ourselves up.
  7. start

At this point it really helps to open Arduino's Serial Plotter from the "Tools" menu. It will plot your goal and your present system state. These should both be zero now.

Now it gets exciting.

Calibrating the proportional constant

  1. Remember the encoder speed we obtained in step 7 above under "Test motor driver and encoder function" for a PWM value of 100? Enter this as a goal now. Simply type the number and press enter. Nothing should happen yet because all constants are zero. If something does move, type stop immediately and check the above steps, typing help to look at the current values of the constants.
  2. Divide 100 by the encoder speed you just entered. Multiply the resulting number by 0.7. Enter set speedKp NUMBER, where NUMBER is the number you just calculated. BE PREPARED! In theory, this should give you a rotation speed of around 70% of the goal, and it usually works, but there is a chance it can do anything from just producing a low whine (the motor gets driven but not fast enough to actually move) through slow / sensible motor motion up to wild swinging!!! If it swings, type stop immediately, and try again with half the value for NUMBER. Repeat until it does not swing anymore.
  3. Now what we want to do is raise the speedKp constant bit by bit until we get to a stage where when we change the motor speed goal, it will overshoot only very slightly or not at all, and definitely not swing, but also not quite approach the goal. We do this by trial and error - just give it different goal values between 0 and the maximum turning speed, watch what goal and present speed do, and raise speedKp slightly, in 10% steps or less. The serial plotter helps a lot here!

Note that swinging and problematic behavior are most pronounced at very low speeds, so make sure that you don't only test in the middle or top of the speed range. You will find out that there is a minimum speed goal under which the motor does not turn at all anymore, and at or slightly above that minimum it may move jerkily. That is completely normal.

Once you are happy with speedKp (meaning the system does not swing at any goal speed, and jumping from goal to goal gives no or minimal overshoot but stabilizes immediately, reduce speedKp by multiplying it with 0.7. This will back off any overshoots, and definitely not allow your controller to approach the speed goal with proportional control at all, but it will also give the integral part something to work with.

Observe also that at this point, you can brake the motor by hand (careful of any gearboxes!) by applying friction to the wheel or cog. This means that under pure proportional control, it will run slower on carpet than on tiles for example, and will run up slopes slower and down slopes faster. We don't want that, so let's look at the error integral.

Calibrating the integral constant

Now we will look at speedKi. As a rule of thumb, we want to start with values around 10% of that of speedKp, so

  1. set speedKi NUMBER where NUMBER is speedKp * 0.1.
  2. start
  3. give the system different goals, high, low, far apart, close together, and see how it fares. It should approach the goal better than under pure proportional control.
  4. If it swings, reduce speedKi by halving it and try again.
  5. If it does not, but takes a long time to approach the goal, increase speedKi in 10% steps (or larger if you're feeling confident).
  6. Stop if it's completely stable, does not swing, takes speed changes elegantly, and reaches the goal swiftly, modulo noise.

You can now play around with speedKp and speedKi, and especially look at very low speeds. How low can you get and still have the motor turn at approximately the speed goal? To better judge this, make sure you have set wheel diameter and ticks per turn as described above, and type set unit 0. This means you are now setting goals, and measuring speed, in millimeters per second instead of encoder ticks per second. As an example, my D-O run by Pololu motors with a 20.4:1 gearbox can control speeds down to 30mm/s relatively fluidly.