DIY Servo with Arduino, DC Motor, and Potentiometer

Not being happy with the ability to only move a minimum of 1 degree and only at a single speed for my time-lapse automaton project, decided to hack up my pan servo and get a little more control over it via the arduino directly.

The basic idea is to remove the controller from a servo entirely, modify the gears for continuous movement by removing any stop pins, and attach the potentiometer directly to the arduino. If we then attach the motor to a motor controller (in my case, an L298 Compact Motor Controller) – we can now control direction and speed. By using analogRead() with the wiper of the potentiometer, we’ll have 1024 discreet positions we can read in a single rotation. Or, about 0.35 degrees as a minimum movement.

It’s possible to use this same code and wiring to turn any old DC motor and a potentiometer into a servo by attaching the final drive-gear of your motor or project to the shaft of the potentiometer in such a way that it rotates perfectly with the final drive gear.

This code uses a basic method to avoid the jitter you’ll get with the analog potentiometer of averaging and canceling out repetition. A number of readings are averaged, and if the same average occurs in either of the previous two readings, then the difference in position is not recorded. Otherwise, we use the average reading to determine our current position, and the difference to determine how far we have moved since our last valid reading.

For mine, I modified an HS-645MG motor that came with the SPG645S kit. I used the potentiometer with the kit, as it mounts in the shaft with a slip-collar to prevent damaging the pot if you attempt to go past its stop. If you were to use a continuous-rotation pot, you could, perceivably, rotate infinitely. For my purposes, one rotation is enough, and the point is to be able to rotate very slowly while still being aware of our position. I find that I am able to move exactly one reading value (1024 steps) between each delay in movement, and delay up to nine hours. Your mileage may vary based on your hardware and other code running.

The code and a wiring diagram are behind the cut:

Here’s a diagram of how everything is wired for this sketch. You’ll need to click on it to see the entire image.

Edit: 7/25/08 — It seems that WordPress had completely eaten my source code when posted. After some searching, I found a new way of doing it. While it isn’t perfect, code portions are no longer eaten up. For best results click on ‘view plain’ below.

Addendum: there were two bugs preventing full READ_AVG resolution, one was in the division of the total ( “tot / READ_AVG” should’ve been “tot / READ_AVG + 1”), and the other was when re-setting step counter upon reaching READ_AVG, it had to be re-set to -1 to account for the post-increment operation at the end of read_pot().


	DIYServo 1.0
	Controller Motor Speed and Movement with Absolute Positioning
	A sketch for turning a standard DC gear motor into a servo
	using a potentiometer.  Can also be used to add finer-grained
	control over existing servos.
	Control over motor :
		Speed (PWM)
		Number of degrees to move (up to 1024) per move
		How to long to wait between moves
	To get finer control (1024 degrees, instead of 360) of an existing
	servo, remove the controller and any stop-pins, disconnect the 
	potentiometer wires.  Connect power lines from servo motor to a 
	DC motor controller, potentiometer wiper to an input pin on the arduino,
	and the ouside potentiometer pins to 5v/GND.  
	Serial monitor:
		use 'l' to tell the servo to move left
		use 'r' to tell the servo to move right
		use 's' to stop the servo
		use 'g' to go (run the servo)
	(c) 2008 C. A. Church -
	7/24/08 - initial version

	// USING_CONTROLLER says whether we have to bring an
	// enable pin high (such as for the Compact L298 controller)
	// before sending PWM down the LT/RT pins
	// set this to 0 if you don't need an enable pin

	// enable pin 
#define MOTOR_EN_PIN     8
	// right direction pin
#define MOTOR_RT_PIN     6
	// left direction pin
#define MOTOR_LT_PIN     9

	// READ_AVG is how many readings to average
	// set it to one less than the actual #
	// e.g.: 10 readings = set to 9
	// the more you average, the more accurate your reading is likely to
	// be -- too many though, and you'll start missing changes if the motor
	// is moving quickly
#define READ_AVG       9

	// motor speed is from 0-255, test with low values
	// as not all will move consistently for you
int motor_speed    = 75;

	// how many ms to pause between allowed movements
int motor_pause_tm = 1000;

	// how many 'degrees' (absolute differences between
	// potentiometer readings) to move before pausing
int motor_move_deg = 5;

	// a counter for how many degrees we have moved
int move_deg_cnt   = 0;

	// setting to a default value
int  motor_cur_pin = MOTOR_RT_PIN;

	// control indicators
bool motor_started = false;
bool motor_paused  = false;
bool first_run     = true;
bool motor_run     = false;

long paused_tm;

	// our current and previous potentiometer readings
int cur_reading = 0;
int pre_reading = 0;

int steps   = 0;

	// our current readings array, and our previous average readings array
int vals[READ_AVG + 1];
int prev_posts[2]  = { 0, 0 };


void setup() {

  memset( (char *)vals, 0, sizeof(int) * (READ_AVG + 1) );

  	// set motor control pins
      digitalWrite(MOTOR_EN_PIN, HIGH);
  digitalWrite(MOTOR_LT_PIN, LOW);
  digitalWrite(MOTOR_RT_PIN, LOW);

void loop() {

	// see if any input has come in the serial port
   	// figure out how many degrees we've moved (if at all)
   move_deg_cnt = move_deg_cnt + read_pot();
   if( motor_run == true ) {
        // if the motor is supposed to be running

	// the following check is to prevent attempting to rotate all the
	// way around on a potentiometer that has a stop.  If yours doesn't 
	// have a stop in it, ou can remove this check
     if( (motor_cur_pin == MOTOR_RT_PIN && prev_posts[1] >= 1020 ) || 
         ( motor_cur_pin == MOTOR_LT_PIN && prev_posts[1] <= 3   ) ) {
		 // we've reached our maximum point (don't want to harm our
		 // potentiometer
       motor_run     = false;
       motor_started = false;
       motor_paused  = false;
       		// bring pin low
       digitalWrite(motor_cur_pin, LOW);
       		// print status
       Serial.println("Have Reached Edge of Movement");
     else if( motor_started == false ) {
	     // the motor is supposed to be running, but we haven't started
	     // it yet
	     // PWM output
       analogWrite(motor_cur_pin, motor_speed);
       	     // set status values
       motor_started = true;
       motor_paused  = false;
       first_run     = true;
     else if( move_deg_cnt >= motor_move_deg && motor_paused == false && first_run == false) {
	     // we've gone our specific # of degrees, pause by stopping the
	     // motor
       digitalWrite(motor_cur_pin, LOW);
       motor_paused = true;
       		// record when we started our pause (so we know when to stop)
       paused_tm     = millis();
     else if( motor_paused == true && (millis() - paused_tm) > motor_pause_tm ) {
	     	// if enough time has passed to stop pausing
	     motor_paused = false;
	     paused_tm     = millis();
	     	// set move_deg_cnt to zero when re-starting to avoid any
		// jitter while paused
	     move_deg_cnt = 0;
	     	// generate PWM
	     analogWrite(motor_cur_pin, motor_speed);


int read_pot() {

//read the voltage on the potentiometer:
  cur_reading = analogRead(0);
  int diff = 0;

  	// we're going to average the last READ_AVG reads
	// put in a value for our current step
  vals[steps] = cur_reading;
  	// if we've saved enough values to go ahead and perform an average...
 if( steps == READ_AVG ) {
	 // reset our read counter
   steps = -1;
   	// determine the average value read
	// -- this is mostly to deal with big jitter
   int tot = 0;
   int avg = 0;
   	// sum up totals
   for (int i = 0; i <= READ_AVG; i++) 
     tot += vals&#91;i&#93;;
   avg = tot / READ_AVG + 1;

   	// ignore current reading if it was either of our last two readings
	// avoid bouncing back and forth between two readings (slight voltage
	// variation in the same range)
   if( avg == prev_posts&#91;0&#93; || avg == prev_posts&#91;1&#93; ) { 
   	// determine the absolute difference between the current average
	// and the previous average
   diff = avg > prev_posts[1] ? avg - prev_posts[1] : prev_posts[1] - avg;

   	// if there's a difference between the averages
   if( diff > 0 ) {
	// print our new reading
    Serial.println(avg, DEC);

  	// move our last reading back, and put our current reading in
	// our array to track the last two positions
    prev_posts[0] = prev_posts[1];
    prev_posts[1] = avg;

	// update this so the pause check knows that we have changed a position
	// (otherwise, starting in a position oher than 0 will mess up our
	// pause check)
    first_run = false;


 	// increment our saved value # for the next loop

	// return the difference recorded 

void check_input() {
  if ( Serial.available()) {
    char ch =;

    switch(ch) {        
      case 'g':
        Serial.println("Go - Running Motor");
        motor_run = true;
        digitalWrite(13, HIGH);
      case 's':
        Serial.println("Stopping Motor");
        motor_run = false;
        motor_started = false;
        analogWrite(motor_cur_pin, 0);
        digitalWrite(13, LOW);
      case 'l':
        motor_cur_pin = MOTOR_LT_PIN;
        Serial.println("Direction = LEFT");
      case 'r':
        motor_cur_pin = MOTOR_RT_PIN;
        Serial.println("Direction = RIGHT");

~ by c.a. church on July 24, 2008.

10 Responses to “DIY Servo with Arduino, DC Motor, and Potentiometer”

  1. What happens to the pot when you go ’round the clock’?


  2. I’m kinda hoping that my project will also allow time lapse work… with 8 microsteps, I get .8° resolution on the stepper which is then geared to my drive wheel (not sure of ratio).

    I’m thinking of also doing multipass video shots and mixing them. We have a massive 10m tidal range (low to high water). I’d really like to shoot a mo’co shot of the tide coming in and out.

  3. Marcel –

    A couple of points that may help you a good deal (I’ll be posting this info up in much more detail later this week, with photos and instructions when my new gears come in the mail.) —

    If you hook up your potentiometer to your final drive gear, you’ll divide your “measurable” motion points by 1024 – so, at 360 degrees / 1024 == 0.35 deg minimum movement. However, I can tell you with quite a bit of authority (ran the test this weekend) @ 17mm F/L 0.35 deg is WAY too big of a movement to be smooth. In fact, it’s quite jumpy.

    However, if you gear down (slow down) the final output gear relative to the motor’s shaft or input gear, you can increase your resolution by measuring from the source instead.

    Say, for example, using my SPG645S kit from ServoCity, it’s geared at 5:1 – so, if I can measure the rotation of the servo’s output, instead of the final drive gear, I get: 360 / 1024 / 5 == 0.07 degrees, a lot finer-grained. I’ll be testing later this week as to how functional that is, but I have plans to move to 15:1 gearing, giving me 0.02 degrees measurable. The reason that works is that in 5:1 gearing, it would take five rotations of the source to equal 1 rotation on the output. So, every individual measurement of the source rotation is 1/5th the distance (or degrees) moved on the final drive.

    You need to know the final gear ratio, otherwise you won’t have anywhere near accurate measurement of distance, just count the teeth on all gears to determine gear ratio. (Or, if you know they’re the same pitch, you can get close by measuring the diameter of each. 48P == 48 teeth for each inch of diameter.)

    It’s important, for the highest resolution, to measure movement off the point in the drive with the highest number of rotations. If I’m running 5:1, I read off the source, if it’s 1:5, I read off the final drive gear.

    As to the pot going “round the clock”, if your pot has a stop, you don’t want to do that, no sir =) You’ll likely damage the pot. That’s why in the code above it has a hard stop on each end of movement (see around line 136). Obviously, this is a problem if we’re measuring off the source gear — we’d reduce our final movement to 1/5th of a rotation in 5:1 (we increased resolution, but decreased movement). You’ll need to replace the pot w/ a continuous rotation version, or cut the stop tab out. I’ll be posting an example of doing that with the HS645MG and it’s built-in pot in a few days. Presuming I don’t destroy the pot in the process! No worries if I do though, I’ll just run one external using a horn in addition to the gear.

    If you have a continuous rotation pot, you can just count the times you’ve gone around to determine total distance. E.g. :

    if ( cur_value < last_value ) { // calculate the absolute difference in values read diff = (1024 - last_value) + cur_value; // update count of total rotations rotations++; } [/sourcecode] Does that help? I think my thoughts were a little rambling there, but it starts to make a lot of sense after a while. By measuring the rotation with potentiometers, you get out of the realm of continuous motion, and into a specified amount of motion between shots. !c

  4. Does the round-the-clock count work out accurate (precise) enough? Why not use a stepper or an encoder of some sort? Cost?

    Trying to get my buttons and LCD into a case of some description. I’m using a VGA cable to communicate back to the Arduino on the dolly. That way i can unplug once it’s programmed if needed or use a longer VGA cable if needed. I’ve been stripping a scanner to get some wires to hook up the LCD – what fun!


  5. Marcel, yes, it was all about cost and utilizing the equipment I had on-hand. Unfortunately, the tiny pots they use inside servos cannot effectively be modified (without serious effort, at least!) due to the ‘wire-arms’ they use as wipers – they end up shorting the +5v to GND when one goes all the way around.

    There would definitely be some lack of accuracy in the 10-20 degrees between the left and right contacts, in a best case scenario you’d have a floating input leading to random reads, which could easily be compensated for in software, if need be. (You would simply know “I’m in this region, heading for 0 or 1024 depending on direction.” )

    It’s funny that you should mention steppers, I found some attached to some cool pumps I picked up recently at the electronics store, and I might use them for pan/tilt. Picked up a more powerful one as well for $9 used. Went ahead and ordered that stepper controller you mentioned. Essentially, I’ll be replacing all of the servos and DC motors w/ steppers, as the rest of this stuff is too much work, and my September deadline is approaching =) So, w/ 1.8 deg stepper, 8 microstepping and 3-1 gearing (what I’ll have available for this setup), that gets me 1.8 / 8 / 3 = 0.075 degrees/movement. Which should be acceptable. With a few more bucks and a little more fabricating, I can go to 9:1 gearing, giving 0.025 deg/move.

    The VGA cable is a good idea – I have a second arduino coming in, there’ll be one driving the motors / camera, and one driving the UI. I’m having the UI part in a separate hand-held case, and was considering using a USB cable to connect the two. I don’t need many wires between, as I intend to use i2c to communicate between the arduino’s. But the UI needs to be connected as stop/start is handled by it, and status (position of each motor) will be displayed on the UI.

    The motor controllers, all power distribution, etc. will be housed inside of the case holding the truck motor on one end of the track.

    It’s funny you mention cables, as I spent some time making a few last night for the LCD, keypad, and power on the UI board. I used the straight, friction-lock male PCB ends like these :
    Just soldered the wires onto the PCB end, and then used some shrink-wrap to tidy them up. They look very good and only cost me a few cents a piece. The connectors stay in pretty tight as well, as the friction-lock plates press on the arduino’s female headers. For the arduino side, I just got 1 six pins, 2 8 pins, and 2 2 pins (for vin/gnd and 5v/gnd).


  6. I’m glad things are moving forward for you c. (is that Chris, Cecil, Cuthbert??)

    I spent a while yesterday rewiring my LCD. I had it all sorted originally but when I came to think about casing my display and controls I found I was about an inch short. Damn! It took me ages to unsolder and re-solder it all. I’m not very fast. Next job is to put the pair in a case of some sort. It’s going to be rough! 😉

    You may find that with the steppers you will be able to run them fast during your setup – so you can preview your start and end points – which could be handy, then flip it over to your super slow mode at runtime.

    This is the first time I’ve used steppers and I’ve been blown away by them. The EasyDriver board (glad that’s been of use to you) makes it SO easy(!) to use them.

    I had a couple of old Lexmark Optra C laser printers a little while back. Printers are a great source of steppers! I just don’t have a lot of options for swapping gears in and out – the motor has a cog fixed to the shaft.

    I’ve not got much of a budget for this so like you I’m trying to use what’s to hand!

    All the best.

    May all your solder joints be good ones!


  7. […] DIY servo with Arduino, DC Motor and the L298 motor driver […]

  8. hi how can i attach the POT to the output shaft please?

    • Maria, sorry for the slow response, your comment got locked up in my spam folder (dunno why – that’s a first!).

      Anyhow, you have numerous options, you could have a supported shaft, with one end going beyond the support, and then either couple your potentiometer to the shaft, or use a hollow shaft with an ID larger than the OD of your POT shaft and use a small amount of rubber material to create a tight fit.

      Alternatively, you can use a shaft with one end drilled and tapped, and then match it to a POT with a threaded shaft, using a little low-strength thread-sealer to prevent it from working loose.

      Of course, there’s yet another option of the shaft being the actual POT shaft, if it can handle the load.


  9. […] Winch – for applying the system to an atonomous full sized boat […]

Leave a Reply to Useful Links | Anacortes RC Sailors Cancel reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: