Search Code

Lesson 97-3: Controlling a Servo Motor Using a Rotary Encoder and Display Angle on LCD

Lesson 97-3: Controlling a Servo Motor Using a Rotary Encoder and Display Angle on LCD

This project demonstrates how to build a precise servo motor position controller using a rotary encoder, with real-time angle feedback displayed on an I2C LCD. Instead of using a potentiometer knob, which has physical limits and can wear out over time, this design uses a mechanical rotary encoder that rotates continuously, offering greater durability and more flexible control. This project is perfect for anyone who needs accurate, repeatable positioning in their Arduino projects.

This setup is ideal for a wide range of practical applications:

  • Building a remote-controlled camera pan-and-tilt mount where you need precise angular control.
  • Creating a robotic arm joint with a manual override for fine positioning.
  • Developing a model sailboat or aircraft control surface tester.
  • Constructing an adjustable solar panel tracker that can be manually positioned.
  • Making a custom dial or input device for a 3D printer or CNC machine to adjust settings.

Hardware Required

  • Arduino Uno (or similar board)
  • Rotary encoder module (PCB version or bare encoder with resistors)
  • Servo motor (any standard type, e.g., metal gear or small plastic gear)
  • I2C LCD (16x2 characters)
  • Jumper wires
  • Breadboard (optional, for non-PCB encoder)
  • Three 1kΩ to 100kΩ resistors (if using a bare rotary encoder)

Wiring Guide

Wirign for LCD, servo motor and rotary encoder without PCB. adding your own resistors
Wirign for LCD, servo motor and rotary encoder with PCB. 

This project has three main components to connect: the rotary encoder, the servo motor, and the I2C LCD. The wiring diagram below shows all the necessary connections. (in video at 05:35)

The rotary encoder module is the primary input. Its CLK and DT pins connect to Arduino digital pins 2 and 3. The "+" pin connects to 3.3V, and the "GND" pin connects to ground. If you are using a bare encoder, you must add three pull-up resistors (between 1kΩ and 100kΩ) from the encoder's two data pins and its switch pin to 3.3V, as shown in the diagram. (in video at 07:03)

The servo motor has three wires. The signal wire (typically yellow or orange) connects to a PWM-capable pin, which is pin 9 in this example. The power wire (typically red) connects to 5V, and the ground wire (typically brown or black) connects to ground. For high-torque servos, it's recommended to use an external 5V power supply to avoid overloading the Arduino's voltage regulator. (in video at 06:03)

The I2C LCD requires four connections. The VCC pin connects to 5V, GND to ground, SDA to the Arduino's A4 pin, and SCL to the A5 pin. Alternatively, you can power the LCD from the 5V pin on the ICSP header. (in video at 06:31)

Installing the Required Libraries

Before uploading the code, you need to install two libraries. The first is the LiquidCrystal_I2C library, which you can add by downloading the provided ZIP file and using the Sketch → Include Library → Add .ZIP Library menu. (in video at 13:28)

The second is the Encoder library by Paul Stoffregen. This can be installed directly through the Library Manager by navigating to Sketch → Include Library → Manage Libraries, searching for "encoder", and clicking install. (in video at 13:59)

Code Explanation

The code is designed to be highly configurable. The key parameters are all defined at the top of the sketch, allowing you to easily adapt the behavior without digging into the logic. (in video at 17:52)

const int SW_PIN = 4;//define a pin for rotary encode switch
const int PIN_A =2;
const int PIN_B =3;
const int homePosition = 90; //initial position
const int stepValue = 3;//how fast the servo should rotate when turning the knob
const int servoPin = 9;//~must be a pin that is labeled with ~
  • SW_PIN, PIN_A, and PIN_B: These constants define the Arduino pins connected to the encoder's switch, CLK, and DT pins, respectively. If you use different pins, change these values.
  • homePosition: This is the angle (in degrees) the servo will move to when the encoder's push-button switch is pressed. You can change this to any value between 0 and 180.
  • stepValue: This controls the sensitivity of the control. It determines how many degrees the servo angle changes for each detent (click) of the encoder. A higher value makes the servo move faster, while a lower value allows for finer control.
  • servoPin: This is the PWM pin to which the servo's signal wire is connected. Ensure this pin has a "~" symbol next to it on your Arduino board, as only these pins can generate the necessary PWM signal.

The code also includes a custom function, lcdAngel(int angel). This function is responsible for updating the angle value on the LCD. It first clears the previous number from the display, then prints the new angle followed by a degree symbol (°). This function is called automatically whenever the servo angle changes, so you don't need to interact with it directly. (in video at 27:56)

Driving the servo from the encoder is covered in Lesson 97-2: Controlling a Servo Motor Using a Rotary Encoder.

Live Project Demonstration

Once the code is uploaded, the LCD will first display a "Robojax Encoder" and "Servo Test" splash screen for two seconds before clearing and showing the current angle. (in video at 24:10)

When you rotate the encoder, the servo will smoothly move to its new position, and the angle will update on the LCD. The rotation direction determines whether the angle increases or decreases. If the direction feels reversed, you can simply swap the PIN_A and PIN_B connections or their definitions in the code. (in video at 17:32)

The servo's movement is bounded between 0 and 180 degrees. If you continue to rotate the encoder past these limits, the servo will stop at the boundary, preventing any mechanical damage. (in video at 25:54)

Pressing the encoder's built-in push-button switch will instantly return the servo to the homePosition you defined. This is useful for setting a known reference point or "home" position. (in video at 27:13)

Related projects from this video

Chapters

  • [00:01] Introduction to Controlling a Servo with a Rotary Encoder
  • [01:46] Why Use a Rotary Encoder Instead of a Potentiometer
  • [05:35] Wiring the PCB Version of the Rotary Encoder
  • [07:03] Wiring a Bare Rotary Encoder with Resistors
  • [12:05] Connecting the I2C LCD Display
  • [13:28] Installing the Required Libraries
  • [14:54] Understanding the Basic Encoder Example Code
  • [17:52] Modifying the Code for the Servo and Switch
  • [20:20] Full Code Explanation for Servo and LCD Control
  • [30:01] Live Demonstration of the Project

Images

Rotary encoder PCB wiring breadboard
Rotary encoder PCB wiring breadboard
Rotary encoder wiring breadboard
Rotary encoder wiring breadboard
433-Lesson 97: Controlling a Servo Motor using a Rotary Encoder and Display Angle on an LCD
Language: C++
/*
 * Lesson 97-3: Controlling Servo Motor using Rotary Encoder and Display Angle on LCD
 Download and resource page https://robojax.com/RJT453

 *  Watch full details video: https://youtu.be/6xVLbNlmK-g
  This video is part of Arduino Step by Step Course which starts here: https://youtu.be/-6qSrDUA5a8
 

If you found this tutorial helpful, please support me so I can continue creating 
content like this. Make a donation using PayPal by credit card https://bit.ly/donate-robojax
  
 * This code is "AS IS" without warranty or liability. Free to be used as long as you keep this note intact.* 
 * This code has been downloaded from Robojax.com
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *  Updated by Ahmad Shamshiri on Jan 23, 2022
 *  Encoder Library - Basic Example
 * http://www.pjrc.com/teensy/td_libs_Encoder.html
 *
 * This example code is in the public domain.
 */
const int SW_PIN = 4;//define a pin for rotary encode switch
const int PIN_A =2;
const int PIN_B =3;

#include <Encoder.h>

// Change these two 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 myEnc(PIN_A, PIN_B);
//   avoid using pins with LEDs attached

const int homePosition = 90; //initial position
const int stepValue = 3;//how fast the servo should rotate when turning the knob
const int servoPin = 9;//~must be a pin that that is labeled with ~
#include <Servo.h>

Servo myservo;  // create servo object to control a servo
int servoAngel =homePosition;


#include <Wire.h>
#include <LiquidCrystal_I2C.h>
// Set the LCD address to 0x27 or 0x3F for a 16 chars and 2 line display
LiquidCrystal_I2C lcd(0x3F, 16, 2);

void setup() {
  Serial.begin(9600);
  pinMode(SW_PIN, INPUT);
  Serial.println("Robojax Encoder with LCD and Servo");
   myservo.attach(servoPin);  // attaches the servo on pin
   myservo.write(servoAngel);//move servo to initial position

  lcd.begin();
  // Turn on the blacklight and print a message.
  lcd.backlight();     
  lcd.print("Robojax Encoder");
  lcd.setCursor (0,1); // go to start of 2nd line
  lcd.print("Servo Test");   
  delay(2000);   
   lcd.clear();// clear previous values from screen   
  lcd.print("Robojax Encoder");
  lcd.setCursor (0,1); // go to start of 2nd line  
  lcd.print("Angle: ");   
}

long oldPosition  = -999;

void loop() {
  long newPosition = myEnc.read();
  if (newPosition != oldPosition) {

    if(newPosition >  oldPosition)
    {
    int newStep = abs(newPosition - oldPosition);
      Serial.print("Angle ");
      Serial.println(servoAngel);      
      servoAngel -= stepValue;
      if(servoAngel <0)
          servoAngel =0;      
      myservo.write(servoAngel); //move servo to new angle
      lcdAngel(servoAngel);//print on LCD
    }

    if(newPosition <  oldPosition )
    {
    int newStep = abs(newPosition - oldPosition);
      Serial.print("Angle ");
      Serial.println(servoAngel);        
      servoAngel += stepValue;
      if(servoAngel >180)
          servoAngel =180;
      myservo.write(servoAngel); //move servo to new angle
      lcdAngel(servoAngel);//print on LCD
    }
   oldPosition = newPosition;//remember the new position
  }

  if( digitalRead(SW_PIN) == LOW)
  {
    Serial.print("Home: ");
    Serial.println(homePosition);
    servoAngel =homePosition;
      myservo.write(servoAngel); //move servo to new angle
      lcdAngel(servoAngel);//print on LCD
  
  }

  //delay(200);
}

/*
 * lcdAngel(int angel)
 * prints the angle on LCD
 * written by Ahmad Shamshiri
 * www.robojax.com
 * Jan 27, 2022 at 13:18
 * Watch full details video: https://youtu.be/6xVLbNlmK-g
 */
void lcdAngel(int angel)
{
  
  int startChar =7;
  for(int i=startChar; i<16; i++)
  {
   lcd.setCursor (i,1);    
    lcd.print(" ");   
  }
 
   lcd.setCursor (startChar,1); // line 1 
   lcd.print(angel); 
   lcd.print((char)223);
}

Resources & references

Files📁

No files available.