Pyörimisnopeuden mittaaminen back-EMF-menetelmällä

Jos moottorille halutaan tehdä takaisinkytketty säätö, esim. PID, täytyy pyörimisnopeutta pystyä mittamaan. Oheisessa artikkelissa esitellään eräs tapa tähän: back-EMF-mittaus. Sen etu on, että minkäänlaista mittausanturia ei tarvita.



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.


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.

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:

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().

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.

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() 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.

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.

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.

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

Filename: BackEmf.c
// Date: 8/20/04
// Version: 1.0
// Author: David Hygh
Processor frequency: 20 MHz
// Notes:
// Demonstrates PID control of a motor, using Back EMF as the
// control feedback. This code implements a positional PID
// controller, i.e. the target is a position or distance.

The code sequences from a trapezoid position trajectory to either
// a constant velocity or to holding.

Implemented in a PIC16F88 using the CCS C compiler

#include <16F88.h>
#device ADC=8
#use delay (clock=20000000)
#use rs232(baud=19200, xmit=PIN_B5, rcv=PIN_B2, parity=n)
#use fast_io(A)
#use fast_io(B)

#define END_POSITION 40000L

// Function Prototypes
void EMFfb(void);
void PIDfb(void);
void SetPWM(signed long);
void Tick(void);
void Move(int, long, long, int32);
void CalcTraj(void);

enum run_types {

// Global vars
const long pGain = 3L; // proportional gain
const long dGain = 10L; // derivative gain
const long iGain = 0L; // integral gain

int32 tEndPos; // target end position
long tVel, tAcc; // target velocity, target acceleration
signed long currPos = 0; // current position based on EMF feedback
int32 genPos = 0; // generated postion from trapeziod function
signed long genVel = 0; // generated velocity from trapeziod function
int32 tDecPos; // calculated deceleration position

int runState = R_OFF; // current run state
signed long eInteg = 0L; // integral accumulator

void main()

set_tris_a(0b11110011); // A2-3 outputs
set_tris_b(0b11010100); // B0-1,B3,B5 outputs
setup_adc_ports(sAN0 | sAN1 | VSS_VDD); // setup A/D inputs
setup_adc( ADC_CLOCK_INTERNAL ); // setup A/D clock
set_adc_channel(0); // init A/D channel

setup_timer_2(T2_DIV_BY_4, 255, 1); // 4.88 kHz PWM
set_pwm1_duty(0); // initialize PWM off

output_low(PIN_A2); // motor side A low
output_low(PIN_A3); // motor side B low

// printf("Starting\n\r"); // debug, only if UART connected

/* Trapeziodal Speed */
while (runState != R_OFF)

/* Hold or Constant Speed */
Move(R_HOLD, 0, 0, END_POSITION); // hold position
// Move(R_CONST, 30, 0, 0); // constant speed
while (TRUE)

/* copies run state, target position, velocity and acceleration to globals */
void Move(int run, long vel, long acc, int32 endPos)
tEndPos = endPos;
tVel = vel;
tAcc = acc;
// the deceleration position is approximated by calculating the ramp up
// position and subtracting it from the end position.
// the ramp up position is vel^2/acc*2 + vel/2
// this routine skips '+ vel/2' in order to handle non-evenly divisible
// vel/acc/endPos values
tDecPos = endPos - ((vel * vel)/(acc*2));
runState = run;
//printf("endpos=%lu decpos=%lu\n\r", tEndPos, tDecPos);

void Tick(void)
CalcTraj(); // calculate the trajectory
EMFfb(); // get motor position using back EMF
PIDfb(); // calculate PID control

/* calculate trajectory based on runState */
void CalcTraj(void)

switch (runState)
case R_OFF:
case R_HOLD:
genPos = tEndPos;
case R_CONST:
genPos += tVel;
if (genPos >= tDecPos)
genVel -= tAcc; // decelerate
else if (genVel < tVel)
genVel += tAcc; // accelerate
if (genVel > tVel) genVel = tVel;
// position is at/beyond target endpoint, or velocity was negative
if ((genPos + tAcc) >= tEndPos || genVel < 0)
genPos = tEndPos;
genVel = 0;
runState = R_OFF;
genPos += genVel;

// printf("gtp=%lu gtv=%ld\n\r", genPos, genVel);

/* implements EMF feedback */
void EMFfb(void)
long adcMa = 0; // holds ADC value from motor side A
long adcMb = 0; // holds ADC value from motor side B

setup_ccp1(CCP_OFF); // shut off PWM

output_low(PIN_B3); // sync signal for scope
set_adc_channel( 0 );
adcMa = read_adc(); // get ADC value from motor side A
set_adc_channel( 1 );
adcMb = read_adc(); // get ADC value from motor side B
output_high(PIN_B3); // sync signal for scope

setup_ccp1(CCP_PWM); // activate PWM

currPos += adcMa - adcMb; // calculate current position

/* implements PID control */
void PIDfb(void)
signed long pwmSet;
static signed long ePrev;
signed long error;

if (runState != R_OFF) // only calculate PWM values if moving or holding
error = genPos - currPos; // error is desired-current position
pwmSet = pGain * error + // standard PID equation
iGain * eInteg +
dGain * (error - ePrev); // derivative is current-previous
eInteg += error; // integral is simply a summation over time
ePrev = error; // save previous for derivative
pwmSet = 0;

SetPWM(pwmSet); // set PWM value to motor

// printf("e%ld cp%ld 1V%ld 2V%ld\n\r", error, currPos, adcV1, adcV2);

/* sets hardware PWM based on sign and value of pwm */
void SetPWM(signed long pwm)
long pset;

if (pwm < 0)
output_low(PIN_A2); // motor side A low
output_high(PIN_A3); // motor side B high
} else {
output_low(PIN_A3); // motor side B low
output_high(PIN_A2); // motor side A high

pset = (abs(pwm)); // take absolute value of pwm
// clip PWM signal to hardware max of 1023
if (pset > 1023L)
pset = 1023;
//printf("ps%ld pwm%ld\n\r", pset, pwm);
set_pwm1_duty(pset); // CCS function to set hardware PWM
The code :

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License