DIY Servo with Arduino, DC Motor, and Potentiometer

Posted in Projects | Robots 15

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)
		Direction
		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 - www.dronecolony.com
	
	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
	
	
#define USING_CONTROLLER 1

	// 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() {
	
  Serial.begin(19200);
  Serial.println("Ready");

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

  	// set motor control pins
	
  if( USING_CONTROLLER )
      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
	
   check_input();
   
   	// 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
	     
       Serial.println("Pausing");
       
       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
		
	     Serial.println("Unpausing");
	     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[i];
   
   
   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[0] || avg == prev_posts[1] ) { 
	  return(0);
   }
   
   	// 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
	
 steps++;

	// return the difference recorded 
 return(diff);
 
 
}

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

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

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

    [m]

  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++;
    }
    

    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!

    [m]

  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 : http://www.jameco.com/webapp/wcs/stores/servlet/ProductDisplay?langId=-1&storeId=10001&catalogId=10001&productId=687885
    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).

    !c

  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!

    [m]

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

      !c

  8. c_gel - May 16, 2011

    hello,
    does your self made servo motor have ‘brake’ function? I mean when certain angle is achieved, the shaft stops and freezes at that particular angle in firm way; which cannot be affected if someone tries to push using finger? sorry for my english.. tq

  9. Hey, check out my Open Source DIY Servo project: http://www.diyservo.com
    We use Arduino for the brain, Pololu 30A for the motor controller, a Magnetic Rotary Encoder (MRE) from the Mad Scientist Hut, and 3d Printed plastics! We plan to make kits so everyone can use DIYServo with a motor of their choice, or one of our pre selected motors. Come join the fun!

  10. Nooman Mufti - September 1, 2013

    Hi guys,

    My apologies, I know this is an old post. I am working on a project in which I turn small mirrors to face the sun using sensors. I was using servos, but I needed more than the usual voltage to drive them to move the heavy mirrors, so I burnt out the internal control chip. 
    I used an L298N chip to drive the motor + gear box directly and then use the external solar sensors to tell the motor to go clockwise/anti-clockwise, stop, etc. What I can’t make it do is resist a change in the desired position of the motor shaft once the position has been attained (like a normal Servo does if you try to force it by hand to change position once an angle is set). This is important for me, since the mirrors try to fall down because of their own weight and I need the modified Servo to stay and resist this change unless I give it a command to change position.

    Thanks so much,
    Kind regards,
    Nooman 

    • Nooman,

      Are you using the braking mode of the L298N when stopped? If not, try enabling it – nearly all H-Bridge drivers, AFAIK, have a braking mode. If your payload is still too heavy to prevent movement when braking, you can always do like a normal servo controller, and constantly check the position of the motor via a PID loop (it’s possible to not use a PID loop, but you may end up with jitter and lots of back-and-forth movement). There is a PID library for Arduino here: Arduino PID Library

      • Nooman Mufti - September 2, 2013

        Dear Church,

        Thanks so much for your super prompt advise. I really appreciate it. The brake function is what I initially thought would work,…essentially I set the Enable pin as high and both inputs to the motor as High, but it doesn’t freeze the motor, which essentially just freely moves. I see some other people on the net have had issues with L293D as well, so it may be a similar problem. Some people suggest using L6202 (MOSFET based) which definitely does a brake to address this issue (http://www.avrfreaks.net/index.php?name=PNphpBB2&file=printview&t=22646&start=0)
        I was thinking I could just get away with the motor doing an infinite oscillation (clock, anticlockwise motion) once it reaches the desired position (using the solar angle sensors I have). But I think at the entire setup’s tipping point, the thing would fall then come back and so forth…lots of big jitters, unless of course, I can do a full brake somehow….maybe I can keep a very low tolerance (super small error) in my solar angle sensors, so that it can do these infinite clockwise/anticlocks at the desired position/angle (sounds terrible, I know)..maybe I need to try another motor controller chip ?

        I will try your other suggestion about the PID as well. I have also ordered another continuous rotation servo, but from past experience, I need more torque than they can provide at 7 V, so I would need to overvolt them, thereby burning the control chip again…so an external motor control chip with a heat sink is perhaps the only way to go.

        Thanks so much again,
        Kind regards,
        Nooman

Leave a Reply