PID controller using Back EMF as the Control Feedback

Usually when implementing a PID motor controller you would need an external opto-mechanical encoder or magnetic sensor to determine the position of the motor. This project uses no external position detecting parts, but instead uses the back EMF generated by a motor in order to determine position.

Inspired by an article in the August 2004 Circuit Cellar magazine by Rich LeGrand, the following project demonstrates positional PID control of a single motor. It's implemented using a PIC16F88, an L293D four channel driver and a few resistors.


Click drawing for larger image

The code was written using the CCS C compiler.
 Click here for Back EMF motor source code
 Click here for the compiled code in HEX ready to load into a PIC 16F88
 
The code sequences from a trapezoid position trajectory to either a constant velocity or a holding (locked) state. One of 2 Move() functions can be commented in or out to choose the final state of the motor. The #define END_POSITION constant determines where the motor stops for the trapezoid and hold states.

Back EMF detection is accomplished by floating the inputs to the motor briefly (500us), then measuring the voltage from each side or the motor to ground. The project uses the A/D converters of the 16F88 to read these voltages and calculate the current position. See the EMFfb() function for details.
The Acroname web site has a great article on Back EMF.
See  http://www.acroname.com/robotics/info/articles/back-emf/back-emf.html
Microchip also has an application note that goes in to great detail on a Back EMF project.
 http://ww1.microchip.com/downloads/en/AppNotes/00893a.pdf

PID control is implemented in PIDfb() and uses the standard proportional, derivative and integral calculations. This project was used to control a Lego(tm) 9V motor and was found to be stable using PGain=3 DGain=10 and IGain=0 (i.e. no integral term). See Mr. LeGrand's excellent article in the Circuit Cellar magazine for more information on how to tune a PID system for a particular motor.

Here are some details on the functions of the C program:

main()
This function disables the HW comparator, sets up the digital I/O directions and initialized two Analog to Digital channels. Next, it sets the hardware PWM to 4.88Khz and stops the motor by applying a low to each side. After a 1 second delay, it calls Move() with a trapezoid request. See the Move() function details for what this does. Then it goes into a while loop with a 5ms delay and a call to Tick() which implements both the Back EMF and PID processes. When the trapezoid function completes, it calls Move() again with either a constant speed or a hold request, depending on which call is commented in. Lastly it goes into an infinite loop which again calls Tick().

Move()
This function takes 4 parameters,
 1) the operation requested (trapezoid, hold, constant, etc.)
 2) the target velocity
 3) the desired acceleration to the target velocity
 4) the end position
This function's main job is to copy the parms to global variables needed by Tick(), but it also calculates the point at which the trapezoid function starts to decelerate (tDecPos).  The deceleration position is approximated by calculating the ramp up position and subtracting it from the end position. This then gives the ramp down position.

Tick()
This function calls the three main functions in the code. First it calculates the next position for the motor to seek to, using CalcTraj(). Next it determines the motor position by calling EMFfb(). Lastly it calls PIDfb() which generates a new PWM value for the motor.

CalcTraj()
CalcTraj() calculates a new target position every tick based on which function Move() requested (trapezoid, hold, constant, etc.) and puts the results in the genPos variable. For Hold, the target position is simply the end position of Move(). For Constant Velocity, it increments genPos by the velocity requested by Move(). A Trapezoid request by Move() puts genPos in one of three states; either accelerating (the upward slope of the trapezoid), holding constant velocity (the plateau of the trapezoid), or decelerating (the final downward slope of the trapezoid). It also shuts off the motor when the trapezoid function is done by setting the runState to OFF.

EMFfb()
The Back EMF function shuts off the PWM signal to the motor for 500uS, then reads the voltage on each side of the motor using two A/D channels on the 16F88. The PWM is restored and a current position is calculated. This position is simply the position it was before, plus the A/D value from the A side of the motor and minus the A/D value from the B side. By doing this, the currPos variable is increased for clockwise (CW) motor rotation and decreased for counter-clockwise (CCW) rotation. This is due to the voltage on side A being positive for CW rotation and zero for CCW rotation. Similarly side B's voltage is positive for CCW rotation and zero for CW rotation.

PIDfb()
The PID function uses the target position calculated by CalcTraj() and the current position from EMFfb() and generates an error value. This error is used to create a PWM value based on the Proportional difference (pGain * error), the integral (iGain * eInteg), and the derivative (dGain * (error - ePrev)). The Web has many fine examples on why these 3 values are used in feedback control systems. Basically, the proportional part is used to mostly seek to the desired position, the integral is used to get to the exact desired position, and the derivative part is used to keep from overshooting or oscillating at the desired position.

SetPWM()
This function uses a signed PWM value to set the direction of the motor and set the hardware PWM value sent. It also clips the PWM value if it is larger than the hardware can handle.

This project was designed to show how Back EMF and PID control can work on a single motor. It was kept simple in order to show the main concepts needed for Back EMF and PID. For a practical robot control, it would need to be expanded to at least two motors and also allow for negative end positions (moving backward) and/or velocities.

Using this controller, a Lego(tm) motor can be ramped up and down, held static or held at a constant speed despite force in either direction. When I first got it working it was amazing to see it in action. Give it a try!

David Hygh
If you have any questions, send me email through my YahooID - davidhy11