Arduino as a servo controller (for a DC motor)

Hi all. Working on my project, I wanted to extract more than 360 degrees of movement (e.g. 1 degree increments) from my servo, and also control the speed and time between movements. I'm pretty sure it's a common topic in general, but to do this, I ended up gutting the servo and using the arduino as a servo controller.

By using the original potentiometer that came with the servo, and analogRead() I was able to get control down to 1024 discreet steps. Obviously, this means you can convert any DC motor/pot combo into a servo (or just get position on a DC motor).

I just wanted to post the code here as a working example of:

Averaging the analog values and reducing jitter by eliminating series of duplicate moves (my pot likes to jump back and forth between two values) Controlling speed, reading position, delaying movement for a period of time, and stopping running when reaching the limits of the potentiometer.

(The potentiometer example in the playground didn't go into too much detail on dealing with these issues)

More info and a wiring diagram of how I hooked it all up i here: http://roamingdrone.wordpress.com/2008/07/24/diy-servo-with-arduino-dc-motor-and-potentiometer/

Here’s the code:

/*

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

}

Part 2:

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 = 0;
   
         // 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;

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

Oh, and here's the wiring diagram:

!c

Nice project! I see we have a few components in common. :)

Phil

Nice project! I see we have a few components in common. :)

Phil

Hehe, we sure do. Although, I hate to say it, but we don't any more =( I had to replace the servo kits with stepper motors, as even 0.3 degrees is too coarse of a movement between shots for time-lapse! Works good for panos, but it results in very jerky movement in the TL sequences.

sigh I will use the kits on a pano bot later though =)

!c