I found some rather handy little 24V BLDC motors on eBay
They do all the work for you, and provide a 400cpr incremental encoder too.
The no-load speed at 24V is 6400rpm, so at 20W they must be about 30mNm
or torque.
So I ordered on and when it arrived I sat down and implemented a Servo-loop to drive
it.
The interface is:
PWM signal (0% = full drive, 100% = no drive, note),
brake signal (active low),
direction signal.
And the quadrature encoder takes a 5V supply and gives AB outputs (no
pullups needed).
Here's my sketch:
// Arduino Sketch turning eBay BLDC motor with integral controller
// into a position-driven servo-motor
//
// eBay motor:
// http://www.ebay.co.uk/itm/DC-24V-20W-High-grade-brushless-motor-brushless-motor-with-encoder-PWM-speed-/261713199819
//
// Mark Tillotson
// 2015-03-13
//
// tested on 328p processor, encoder code needs porting to Mega...
//
// Creative Commons Attribution ShareAlike 2.5:
// http://creativecommons.org/licenses/by-sa/2.5/
//
//////////////////// pin definitions ///////////////////
// encoder pins and interrupt numbers:
#define ENC_A 2
#define ENC_B 3
#define ENC_INTA 0
#define ENC_INTB 1
// motor drive pins
#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
# define PWM_PIN 10 // timer2 controls pin 10 on Mega
# define DIR_PIN 11
# define BRK_PIN 12
#else
# define PWM_PIN 11 // timer2 controls pin 11 on Uno etc
# define DIR_PIN 10
# define BRK_PIN 12
#endif
//////////////////// encoder ///////////////////
// [ This code assumes Uno, needs conditionalizing for Mega still ]
volatile long phase = 0 ;
volatile byte portd = (PIND & 0x0C) >> 2 ;
long read_phase ()
{
noInterrupts () ;
long result = phase ;
interrupts () ;
return result ;
}
// encoder runs at 400 counts per rev, 6000rpm+, so over 40kHz interrupt rate
// thus using direct port manipulation for quadrature decoding.
void handle_enc ()
{
byte pd = (PIND & 0x0C) >> 2 ;
pd ^= (pd >> 1) ; // convert quadrature/Gray to 2 bit binary
byte sig = (pd - portd) & 3 ; // calc difference from last sample
if (sig == 1)
phase -- ;
else if (sig == 3)
phase ++ ;
portd = pd ;
}
void enc_setup ()
{
pinMode (ENC_A, INPUT_PULLUP) ;
pinMode (ENC_B, INPUT_PULLUP) ;
attachInterrupt (ENC_INTA, handle_enc, CHANGE) ; // handle every change
attachInterrupt (ENC_INTB, handle_enc, CHANGE) ;
}
//////////////////// setup ///////////////////
void setup ()
{
TCCR2B = 0;
TCCR2B = (TCCR2B & 0xF8) | _BV(CS20); //use a 1:1 prescaler, PWM at 32kHz
analogWrite (PWM_PIN, 255) ; // low PWM means high drive on this motor
pinMode (BRK_PIN, OUTPUT) ; digitalWrite (BRK_PIN, HIGH) ; // Brake active LOW
pinMode (DIR_PIN, OUTPUT) ; digitalWrite (DIR_PIN, LOW) ;
enc_setup () ;
}
void set_drive (int level)
{
digitalWrite (BRK_PIN, level != 0) ;
digitalWrite (DIR_PIN, level < 0) ;
analogWrite (PWM_PIN, 246 - abs (level)) ;
}
//////////////////// PID loop ///////////////////
long target = 0L ;
long integ = 0L ;
long prev_ph = 0L ;
#define P 0.5 // values derived by experiment
#define I 0.001
#define D 2.5
int pid ()
{
long ph = read_phase () ;
long error = target - ph ;
float res = P * error + D * (prev_ph - ph) + I * integ ;
prev_ph = ph ;
if (abs(error) < 10)
integ += error ;
delayMicroseconds (500) ;
return (int) res ;
}
//////////////////// test code ///////////////////
int t = 0 ;
void loop ()
{
// changes target position every 1/128th time round the loop
t += 1 ;
if ((t & 0x7F) == 0)
{
int amt = (t & 0x1000) == 0 ? 1596 : 4 ;
target += amt ;
}
set_drive (constrain (pid (), -240, 240)) ; // run PID and set drive
}
Some photos:


And video: Servomotor test - YouTube
(and in slow-motion: Servomotor test - YouTube - it moves so fast its hard to tell its going 3/4 turn ![]()