Controlling servos with rotary encoders

Im trying to control the rate that a servo goes back and forth using a rotary encoder and i’m having difficulty making it work. I’m still pretty new to using arduino boards and there is a lot I don’t know about them so any help would be greatly appreciated. My end goal is to be able to control the speed a servo moves back and forth as well as toggle the servo with a rotary encoder. The rotary encoder i’m using has a button and the servo i’m using is an SG90 servo.

Here is what I made so far:

#include <ClickEncoder.h>
#include <Servo.h>
#include <TimerOne.h>
Servo sv;
ClickEncoder *encoder;
int16_t last, value;
int pos = 0;
void timerIsr() {
encoder->service();
}
void setup() {
Serial.begin(9600);
encoder = new ClickEncoder(A0, A1, A2);
sv.attach(9);
Timer1.initialize(1000);
Timer1.attachInterrupt(timerIsr); 
}
void loop() {  
value += encoder->getValue();
  if (value != last) {
  last = value;
  for (pos = 0; pos <= 90; pos += value) {
  sv.write(pos);             
  delay(15);                      
}
for (pos = 90; pos >= 0; pos -= value) { 
  sv.write(pos);              
  delay(15);                       
  }
}
}

This is probably really bad but like I said i’m new so as of now its the best I can do but I am hoping with enough help I do this without much problem. Oh also I couldn’t figure out how to add the button in as a toggle so I left that out.

Sorry im pretty sure i posted this in the wrong place

Hi, Welcome to the forum.

Please read the post at the start of any forum , entitled "How to use this Forum". OR 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?

Do you want to control the RATE or the POSITION of the servo with the POSITION of the encoder? How do you want the servo to react to cw or ccw operation of the encoder.

You would be advised to place some Serial.print statements in your code to help debug.

Tom... :)

So what, if anything, does your program do now?

What exactly does "toggle the servo" mean to you? What sort of numbers do you get from the encoder in 'value'?

When you want to modify the speed of servo moves using that Sweep technique it's more usual too change the delay() between moves rather than the the increment of each move.

Steve

TomGeorge: Hi, Welcome to the forum.

Please read the post at the start of any forum , entitled "How to use this Forum". OR 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?

Do you want to control the RATE or the POSITION of the servo with the POSITION of the encoder? How do you want the servo to react to cw or ccw operation of the encoder.

You would be advised to place some Serial.print statements in your code to help debug.

Tom... :)

I will try to make a sketch of my circuit as soon as possible but in the mean time I can try to elaborate more on where I was too vague. To answer your questions, I want to control the rate of the servo with the position of the encoder, in other words I want to have it so when I rotate the encoder clockwise it speeds up the servos rate and when I turn it counter clockwise is slows down the servos rate. Additionally I want the button on the encoder to turn the servo on/off, like a toggle button. Im sure you already know but just in case, I have the encoder library downloaded. Last I don't fully understand how Serial.print statements work, I tried using Serial.print(value) to see if value was actually displaying the value of the encoder but it only displays 0 regardless of how I move it. I don't know why it does this and I also don't know if "value" is even the value of the encoder.

P.s The board i'm using is the Mega 2560, don't know if that helps

The main problem may be that Servo.h uses Timer 1 and you are also using that timer separately for ClickEncoder.h. There is very likely a clash there stopping both things from working.

For a quick test try using ServoTimer2.h in place of Servo.h and see if that improves things. The servo probably won't work until you make some other changes but see if you start getting sensible numbers in your Serial.print()s of 'value'.

Steve

Some servo test code where you might substitute a pulse detection for receiving a character via the serial port.

//send a character a, b, c, d or string of characters 
//like aaaaaaaaaaabbbbbbccccccddddddddddddr

#include <Servo.h>

Servo myservo1;
Servo myservo2;

int pos1 = 90;
int pos2 = 90;
int delayPeriod = 50;  // increasing this slows down the servo movement
char c;


void setup()
{
  Serial.begin(9600);
  myservo1.attach(8);  // attaches the servo on pin 8 to the servo object
  myservo2.attach(9);  // attaches the servo on pin 9 to the servo object
  myservo1.write(pos1); // center the servo1
  myservo2.write(pos2); // center the servo2
}


void loop()
{
  if (Serial.available())  {

    char c = Serial.read();  //gets one byte from serial buffer
    Serial.print("received character is ");
    Serial.println(c);
    //}

    if (c == 'a')
    {
      // in steps of 1 degree
      if ( pos1 > 0)
        --pos1;
      myservo1.write(pos1);              // tell servo to go to position in variable 'pos'
      Serial.print("servo 1 is at ");
      Serial.println(pos1);
      //delay(delayPeriod);
    }
    if (c == 'b')
    {
      if ( pos1 < 180)
        ++pos1;
      myservo1.write(pos1);        // tell servo to go to position in variable 'pos'
      Serial.print("servo 1 is at ");
      Serial.println(pos1);
      //delay(delayPeriod);
    }

    //

    if (c == 'c')
    {
      // in steps of 1 degree
      if ( pos2 > 0)
        --pos2;
      myservo2.write(pos2);              // tell servo to go to position in variable 'pos'
      Serial.print("servo 2 is at ");
      Serial.println(pos2);
      //delay(delayPeriod);
    }
    if (c == 'd')
    {
      if ( pos2 < 180)
        ++pos2;
      myservo2.write(pos2);              // tell servo to go to position in variable 'pos'
      Serial.print("servo 2 is at ");
      Serial.println(pos2);
      //delay(delayPeriod);
    }

    //

    if (c == 'r')
    {
      myservo1.write(90);
      myservo2.write(90);
      Serial.println("servos reset at 90 deg.");
    }
  }
}

Hi,

Have you written some code just to read the encoder and check that it is counting up and down? If not, you need too, or see if there was any example code with the encoder library.

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

Thanks.... Tom.. :)

Hi,
Try this bit of code, it is the example code edited down to Serial.prints.
You will need to set the IDE monitor to 115200 baud.

#define WITH_LCD 1

#include <ClickEncoder.h>
#include <TimerOne.h>

ClickEncoder *encoder;
int16_t last, value;

void timerIsr()
{
  encoder->service();
}

void setup()
{
  Serial.begin(115200);
  encoder = new ClickEncoder(A1, A0, A2);
  Timer1.initialize(1000);
  Timer1.attachInterrupt(timerIsr);
  last = -1;
}

void loop()
{
  value += encoder->getValue();
  if (value != last)
  {
    last = value;
    Serial.print("Encoder Value: ");
    Serial.println(value);
  }
  ClickEncoder::Button b = encoder->getButton();
  if (b != ClickEncoder::Open)
  {
    Serial.print("Button: ");
#define VERBOSECASE(label) case label: Serial.println(#label); break;
    switch (b)
    {
        VERBOSECASE(ClickEncoder::Pressed);
        VERBOSECASE(ClickEncoder::Held)
        VERBOSECASE(ClickEncoder::Released)
        VERBOSECASE(ClickEncoder::Clicked)
      case ClickEncoder::DoubleClicked:
        Serial.println("ClickEncoder::DoubleClicked");
        encoder->setAccelerationEnabled(!encoder->getAccelerationEnabled());
        Serial.print("  Acceleration is ");
        Serial.println((encoder->getAccelerationEnabled()) ? "enabled" : "disabled");
        break;
    }
  }
}

If I get a chance later I will get my Mega and Encoder hooked up and see what happens.

Tom… :slight_smile:

Here is a circuit diagram, hope this is what you meant and that it makes sense. Also I tried all of the provided code and I can’t seem to get the results I expected, however i’m not sure if I did anything wrong or how to fix it if I did.

P.s I changed the pins for the encoder and made sure to change the code as well

Screen Shot 2020-01-09 at 10.40.20 PM.png

Hi
What did you get on the IDE monitor with my code?
OPs diagram.


Thanks.. Tom.. :slight_smile:

Hi, I have got my mega and encoder out and using my code have a counting process displayed on the IDE monitor. The encoder pins are connected as below; SW == A2 DT == A1 CLK == A0.

Make sure when running my code you have the monitor set to 115200 baud. The monitor updates when you turn the encoder. At the moment the encoder counts in groups of 4's, not sure why as I am no familiar with ClickEncoder library.

Tom.. :)

Ok so i tried the code and it worked but it only went between 1 and -1, also I made some new code that works but when i plug the servo into the breadboard it doesn’t work and I know im not supposed to do this but when I plugged the servo into the arduino itself it works, however I don’t want to keep doing this because I don’t want to stall the arduino.

Heres the code:

 #include <Servo.h>
 #define outputA 6
 #define outputB 7
 int pos = 0;
 int counter = 0; 
 int aState;
 int aLastState; 
 
 Servo sv; 
 
 void setup() { 
   pinMode (outputA,INPUT);
   pinMode (outputB,INPUT);

   Serial.begin (9600);
   aLastState = digitalRead(outputA);
   sv.attach(8);
 } 
 void loop() { 
   aState = digitalRead(outputA);
   if (aState != aLastState){     
     if (digitalRead(outputB) != aState) { 
       counter ++;
     } else {
       counter --;
     }
     Serial.print("Position: ");
     Serial.println(counter);
   } 
   aLastState = aState;
   if (counter == 10) {
     for (pos = 0; pos <= 180; pos += 1) { 
       sv.write(pos);              
       delay(15);
     } 
     for (pos = 180; pos >= 0; pos -= 1) { 
       sv.write(pos);           
       delay(15);           
     }                        
   }
 }

When i figure out the problem with the servo im going to use “else if” to manually add speed settings. Also when i get the rotary encoder value to 10 it stops changing the value regardless of how i turn it

Hi,
Try this code, note where the encoder is connected and that the IDE monitor is 115200 baud.

#include "ClickEncoder.h"
#include <Servo.h>
#include <TimerOne.h>
Servo sv;
ClickEncoder *encoder;
int16_t  value, oldvalue;
int last = 0;
int pos = 0;
void timerIsr()
{
  encoder->service();
}
void setup()
{
  Serial.begin(115200);
  //  encoder = new ClickEncoder(A0, A1, A2);//  CLK, DT, SW
  encoder = new ClickEncoder(7, 6, 5);//  CLK, DT, SW
  sv.attach(9);
  sv.write(0);
  Timer1.initialize(1000);
  Timer1.attachInterrupt(timerIsr);
  Serial.println("encoder = new ClickEncoder(7, 6, 5);//  CLK, DT, SW");
  delay(1000);
}
void loop()
{
  value += encoder->getValue();
  if (value != oldvalue)
  {
    Serial.print("Encoder Value: ");
    Serial.println(value);
    oldvalue = value;
  }
  if (value <= 0)   // stops value being less than 0
  {
    value = 0;
    Serial.print("Encoder Value: ");
    Serial.println(value);
  }
  if (value != 0)    // value > 0 will sweep the servo.
  {
    if (value != last)
    {
      //     last = value;
      for (pos = 0; pos <= 90; pos += value)
      {
        Serial.print("Encoder Value: ");
        Serial.print(value);
        Serial.print(" Servo Pos ");
        Serial.println(pos);
        sv.write(pos);
        delay(15);
      }
      for (pos = 90; pos >= 0; pos -= value)
      {
        Serial.print("Encoder Value: ");
        Serial.print(value);
        Serial.print(" Servo Pos ");
        Serial.println(pos);
        sv.write(pos);
        delay(15);
      }
      last = value;
    }
  }
}

Your main problem was when you had a value = 0, and put that as the increment in the for loops.

Did you get the code in my post#7 to work, did you set the IDE monitor to 115200 baud?
Get the code in post #7 working first.

Tom… :slight_smile:

Not that there’s anything wrong with your original concept,

but If it’s only for speed and/or position, it’s curious to use a relative input device to move between absolute speeds or positions....

You’d typically use an encoder if the initial input position is ‘known’, and movement of the encoder is with respect to that arbitrary starting position.

e.g. the single encoder is used to set both the start and end position, and the speed... When you first read the encoder, it could be an of those three, and as it moves, it will be adjusting three completely different values in different ways.