DC Servo position control

Hi,
New to Arduinos.
I have built a largish servo, 12v double gear reduction driving a leadscrew. Approx 150mm travel
No load current is 10A so its rather powerful.

I need to control the position of the driven nut via a potentiometer.

I have a Mega 2560, BTS7960 H bridge driver, and an Optical Rotary Encoder 400P/R, which is mounted to the leadscrew.

I have downloaded a code to run the motor fwd and rev via the potentiometer, works well, but as far as position control im way out of my depth.

Ive searched but unable to find any code suitable.

Anybody able to help in any way?

Thanks
Ken

Hi,
Welcome to the forum.

Please read the first post in any forum entitled how to use this forum.
http://forum.arduino.cc/index.php/topic,148850.0.html then look down to item #7 about how to post your code.
It will be formatted in a scrolling window that makes it easier to read.

Can you please post a copy of your circuit, in CAD or a picture of a hand drawn circuit in jpg, png?

Can you post the code you have that works to drive the motor?

Thanks.. Tom... :slight_smile:

Hi Tom, Thanks for the welcome.

This is the circuit and code i used.
http://www.hessmer.org/blog/2013/12/28/ibt-2-h-bridge-with-arduino/

Motor runs forwards and backwards with pot centre being neutral.

Basically i need it to operate like an RCservo. I cant use a pot on the leadscrew as it is multi turn.
I have fitted the optical encoder.

Ken

A couple of pics to see what i have.

The optical encoder is gear driven and it is offset from the shaft.

Ken

Servo1.jpg

Servo2.jpg

Hi,
OPs Circuit;


OPs Pics.
f5f7c96ce12aba28dd915c7104b756191f468284.jpg
e7beb793360684b529aaac727d01debf1e993ce2.jpg

Thanks.. Tom... :slight_smile:
Please post your code in </> tags.

Nice build. It looks like you have a great piece of hardware there.

The first problem to be solved is the absolute-positioning problem. You didn't say clearly but I suspect that your encoder is a relative encoder. It doesn't know where it is when it's switched on but it can precisely count steps from there. There's a couple of strategies you can use but the most common is a 'home switch' at one end of the travel. Drive towards home until the switch switches, then call that position zero and count steps from there.

Then you need to properly count the encoder steps and be sure that you never miss one. Use Paul Stoffregen's Encoder library and try to use the Arduino's "external interrupt" pins. For the Mega, that's pin 2, 3, 18, 19, 20 and 21. At least one of the two encoder wires should use one of those pins, preferably both wires.

So when you know where you are and where you're going, you can start to drive the motor towards the target. If you are further away, drive harder. When you get close, reduce the drive effort. This is the idea of "proportional control".

You can use PID control, which does things like looks at how fast you're rushing up on your target and slows down in anticipation of arriving at the target at zero speed. There's libraries for it but it's usually not much better than pure proportional, for this kind of positioning.

Thanks!
Im fairly certain it is a relative encoder.
A home switch is easy enough for me to add.

Great link, looks like i have a bit of reading to do, Thanks.

Ok, Home microswitch fitted. If motor overrun goes past mircoswitch no damage can occur.

Spent some time reading up on the link, but im a bit, ok alot lost.

So many codes that i dont know which would be suitable. Unlikely there is one available that does exactly what i am after, but thats ok. If i have a code that gets me started i can modify to suit.

Can i start with the motor driver code i have and add the homing and proportional code to it?

Anyone have a good starting point? Its well above my head to create one from scratch.

Cheers,
Ken

Start by getting all the subsystems tested. Take your basic potentiometer code and see if you can print encoder values to the Serial Monitor.

Then test the home switch and grow that into positioning by millimeters or whatever.

Once you know where you are and where you are going then the code to decide which way to drive the motor will be just a few lines.

OK, i can see the encoder pulses with the serial input, And the poteniometer code works but no luck combining them.

Error is recvOneChar was not declared in this scope ??

int SENSOR_PIN = 0; // center pin of the potentiometer
 
int RPWM_Output = 5; // Arduino PWM output pin 5; connect to IBT-2 pin 1 (RPWM)
int LPWM_Output = 6; // Arduino PWM output pin 6; connect to IBT-2 pin 2 (LPWM)

// Example 1 - Receiving single characters

char receivedChar;
boolean newData = false;


void setup() {
    Serial.begin(9600);
    Serial.println("<Arduino is ready>");
    pinMode(RPWM_Output, OUTPUT);
    pinMode(LPWM_Output, OUTPUT);
}

void loop() 

{
  int sensorValue = analogRead(SENSOR_PIN);
 
  // sensor value is in the range 0 to 1023
  // the lower half of it we use for reverse rotation; the upper half for forward rotation
  if (sensorValue < 512)
  {
    // reverse rotation
    int reversePWM = -(sensorValue - 511) / 2;
    analogWrite(LPWM_Output, 0);
    analogWrite(RPWM_Output, reversePWM);
  }
  else
  {
    // forward rotation
    int forwardPWM = (sensorValue - 512) / 2;
    analogWrite(LPWM_Output, forwardPWM);
    analogWrite(RPWM_Output, 0);
  }

 recvOneChar();
    showNewData();
   



void recvOneChar() {
    if (Serial.available() > 0) {
        receivedChar = Serial.read();
        newData = true;
    }
}

void showNewData() {
    if (newData == true) {
        Serial.print("This just in ... ");
        Serial.println(receivedChar);
        newData = false;
    }
}

You are missing a } to complete the loop() function - before you start defining the other functions.

Steve

slipstick,

Can you please show what i am missing and where?

I have tried a few things but still have errors

thanks!

I will slowly pick this up!

In the IDE use Ctrl-T to indent the code properly. You need to know when loop() ends because you can't nest functions inside one another.

Your function definitions void recvOneChar() and void showNewData() MUST show as starting at the beginning of a line, no indents. To achieve that you just need a } immediately before the void recvOneChar() line to finish loop() function block.

Steve

Thankyou Steve.

I would have never worked that out myself.

Codes works now!

ATM im working on the home mirco switch. Thinking along the lines of a digital pin, input pullup.

Next brain teaser is being able to count the encoder pulses. The serial monitor only show when it receives a pulse for now.

Any tips?

Cheers
Ken

MorganS:
Use Paul Stoffregen's Encoder library

MorganS:

Are you able to narrow it down a little?

Only a beginner your dealing with.

Ken

  1. Install the library using the library manager tool.

  2. Load one of the examples and modify it to your input pins.

  3. Run the example and turn the motor to see the results.

  4. Copy the relevant parts of the example into your main code.

  5. Test and modify as required.

This is the code i have from the site you linked.

I have error Encoder.h: no such file or directory.

fairly certain there should be another file here would the file Encoder h be it? its a large code.

I have successfully added the home switch into my other code.

/* Encoder Library - TwoKnobs Example
 * http://www.pjrc.com/teensy/td_libs_Encoder.html
 *
 * This example code is in the public domain.
 */

#include <Encoder.h>

// Change these pin numbers to the pins connected to your encoder.
//   Best Performance: both pins have interrupt capability
//   Good Performance: only the first pin has interrupt capability
//   Low Performance:  neither pin has interrupt capability
Encoder knobLeft(2, 3);
Encoder knobRight(7, 8);
//   avoid using pins with LEDs attached

void setup() {
  Serial.begin(9600);
  Serial.println("TwoKnobs Encoder Test:");
}

long positionLeft  = -999;
long positionRight = -999;

void loop() {
  long newLeft, newRight;
  newLeft = knobLeft.read();
  newRight = knobRight.read();
  if (newLeft != positionLeft || newRight != positionRight) {
    Serial.print("Left = ");
    Serial.print(newLeft);
    Serial.print(", Right = ");
    Serial.print(newRight);
    Serial.println();
    positionLeft = newLeft;
    positionRight = newRight;
  }
  // if a character is sent from the serial monitor,
  // reset both back to zero.
  if (Serial.available()) {
    Serial.read();
    Serial.println("Reset both knobs to zero");
    knobLeft.write(0);
    knobRight.write(0);
  }
}

MorganS:

  1. Install the library using the library manager tool.

No luck with that library. i only get Encoder.h in the sketch window.

I have checked the encoders via the receive pin and do get output in the serial monitor.

I have this code atm, i get a timestamp and ready but no output.

Anything im missing ?

#define ENCODER0PINA         A2      // this pin needs to support interrupts
#define ENCODER0PINB         A4      // no interrupt required
#define CPR                  400     // encoder cycles per revolution
#define CLOCKWISE            1       // direction constant
#define COUNTER_CLOCKWISE    2       // direction constant
 
// variables modified by interrupt handler must be declared as volatile
volatile long encoder0Position = 0;
volatile long interruptsReceived = 0;
 
// track direction: 0 = counter-clockwise; 1 = clockwise
short currentDirection = CLOCKWISE;
 
// track last position so we know whether it's worth printing new output
long previousPosition = 0;
 
void setup()
{
 
  // inputs    
  pinMode(ENCODER0PINA, INPUT);
  pinMode(ENCODER0PINB, INPUT);
 
  // interrupts
  attachInterrupt(20, onInterrupt, RISING);
 
  // enable diagnostic output
  Serial.begin (9600);
  Serial.println("\n\n\n");
  Serial.println("Ready.");
}
 
void loop()
{
  // only display position info if has changed
  if (encoder0Position != previousPosition )
  {
    Serial.print(encoder0Position, DEC);
    Serial.print("\t");
    Serial.print(currentDirection == CLOCKWISE ? "clockwise" : "counter-clockwise");
    Serial.print("\t");
    Serial.println(interruptsReceived, DEC);
    previousPosition = encoder0Position;
  }
}
 
// interrupt function needs to do as little as possible
void onInterrupt()
{
  // read both inputs
  int a = digitalRead(ENCODER0PINA);
  int b = digitalRead(ENCODER0PINB);
 
  if (a == b )
  {
    // b is leading a (counter-clockwise)
    encoder0Position--;
    currentDirection = COUNTER_CLOCKWISE;
  }
  else
  {
    // a is leading b (clockwise)
    encoder0Position++;
    currentDirection = CLOCKWISE;
  }
 
  // track 0 to 1249
  encoder0Position = encoder0Position % CPR;
 
  // track the number of interrupts
  interruptsReceived++;
}