Introduction: How to Control the Serial Servo Motor Using the OpenCV and ESP32 Microcontroller

About: I am a passionate robotics and electronics enthusiast, working on innovative projects in automation, ROS2, and embedded systems. I enjoy building real-life solutions like differential drive robots, CNC machine…

YouTube Video:

https://youtube.com/shorts/yfd6txKc3kE

Step 1:

GET YOUR ESP32:


Here i am using an ESP32-Wroom board as microcontroller that Serial Data from the Computer and send that data to the Servo motor to move through the Servo driver Below,

Step 2:

This is the main linker that help the servo to work and helping sending the signal to correct servo motor based on its unique #ID by default the ID of all servos are set to #ID 1 so if you want to control the multiple servo motors then please assign different #IDs to them.


this code here will help you change the ID of the servos easily here :



#include <SCServo.h>
SMS_STS st;
int ID_ChangeFrom = 1; // Change the original servo ID, and the factory default is 1
int ID_Changeto = 2; // new ID
void setup(){
Serial1.begin(1000000);
st.pSerial = &Serial1;
while(!Serial1) {}

st.unLockEprom(ID_ChangeFrom); //Unlock EPROM-SAFE
st.writeByte(ID_ChangeFrom, SMS_STS_ID, ID_Changeto);//Change ID
st.LockEprom(ID_Changeto); // EPROM-SAFE is locked
}

void loop(){
}



PINS Connection:


Tx of servo driver ---------> RX2 of Esp32 or GPIO 16

RX of servo Driver ---------> TX2 of Esp32 or GPIO 17

VCC of servo driver ---------> VCC of Esp32

GND of servo driver --------> GND of Esp32


here in ESP32 there are two serial pins so be careful to connect the RX and TX to serial1 or GPIO 16 (RX) and GPIO 17 (TX) are used for connection of the servo driver not the primary ones.


once done:

Step 3:

UPLOAD THIS CODE:


#include <SCServo.h>

SMS_STS st;

#define S_RXD 16
#define S_TXD 17

#define SERVO1_ID 2
#define SERVO2_ID 1

void setup() {
Serial.begin(115200); // USB serial to PC (Python)
Serial1.begin(1000000, SERIAL_8N1, S_RXD, S_TXD); // Servo serial bus
st.pSerial = &Serial1;
delay(1000);

Serial.println("SCServo Control Ready");
}

void loop() {
if (!Serial.available()) return;

String data = Serial.readStringUntil('\n');
data.trim();

int commaIndex = data.indexOf(',');
if (commaIndex < 0) return;

int angle1 = data.substring(0, commaIndex).toInt();
int angle2 = data.substring(commaIndex + 1).toInt();

// Limit safety angle
angle1 = constrain(angle1, 0, 180);
angle2 = constrain(angle2, 0, 180);

// Convert degrees → SCServo position
int pos1 = map(angle1, 0, 180, 0, 2000);
int pos2 = map(angle2, 0, 180, 0, 2000);

// Send high-resolution move command
st.WritePosEx(SERVO1_ID, pos1, 1500, 50);
st.WritePosEx(SERVO2_ID, pos2, 1500, 50);
}

Step 4:

Upload these Code here:

Then Copy Past This Python Code here and Run it in any Code Editors you wanna use :


also Download these Dependencies here:

📦 Python Libraries (Install using pip)

1. OpenCV

For webcam access & image processing


pip install opencv-python

2. Mediapipe

For face mesh tracking


pip install mediapipe

3. NumPy

For mathematical calculations


pip install numpy

4. PySerial

For sending servo data to ESP32 over USB


pip install pyserial


before that select the correct serial port and and exact baud rate you have selected for esp32 to communicate it with.


import cv2
import mediapipe as mp
import numpy as np
import serial
import time

# ✅ Serial to ESP32 (update port if needed)
ser = serial.Serial('/dev/ttyUSB0', 115200, timeout=1)
time.sleep(2)

# Initialize Mediapipe Face Mesh
mp_face_mesh = mp.solutions.face_mesh
mp_draw = mp.solutions.drawing_utils
drawing_spec = mp_draw.DrawingSpec(thickness=1, circle_radius=1)
face_mesh = mp_face_mesh.FaceMesh(min_detection_confidence=0.8,
min_tracking_confidence=0.8)

cap = cv2.VideoCapture(1)
ws, hs = 600, 600
cap.set(3, ws)
cap.set(4, hs)

if not cap.isOpened():
print("Camera error")
exit()

def calculate_head_movement(nose, left_eye, right_eye):
face_center_x = (left_eye[0] + right_eye[0]) / 2
yaw = np.interp(nose[0], [face_center_x - 50, face_center_x + 50], [180, 0])

eye_level_y = (left_eye[1] + right_eye[1]) / 2
pitch = np.interp(nose[1], [eye_level_y - 50, eye_level_y + 90], [0, 180])
return yaw, pitch

def smooth(current, target, factor=0.1):
return current*(1-factor) + target*factor

current_yaw = 180
current_pitch = 180

while cap.isOpened():
success, img = cap.read()
if not success:
break

img = cv2.flip(img, 1)
rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
results = face_mesh.process(rgb)
img = cv2.cvtColor(rgb, cv2.COLOR_RGB2BGR)

if results.multi_face_landmarks:
face = results.multi_face_landmarks[0].landmark

nose = [face[1].x * ws, face[1].y * hs]
left_eye = [face[33].x * ws, face[33].y * hs]
right_eye = [face[263].x * ws, face[263].y * hs]

target_yaw, target_pitch = calculate_head_movement(nose, left_eye, right_eye)

current_yaw = smooth(current_yaw, target_yaw)
current_pitch = smooth(current_pitch, target_pitch)

yaw_i = int(np.clip(current_yaw, 0, 180))
pitch_i = int(np.clip(current_pitch, 0, 180))

# ✅ Send servo commands to ESP32
cmd = f"{yaw_i},{pitch_i}\n"
ser.write(cmd.encode())

cv2.putText(img, f"Yaw:{yaw_i} Pitch:{pitch_i}", (20,50),
cv2.FONT_HERSHEY_SIMPLEX, 1, (0,255,0),2)

cv2.imshow("Head Tracking Control", img)
if cv2.waitKey(1) & 0xFF == ord('q'):
break

cap.release()
cv2.destroyAllWindows()
ser.close()



and thats it when your web cam starts you will see you servos responding accordingly based on your head yaw and roll..



if you like this project please left and heart here ..

its my first post so i am very open for suggestions and typos



i am vikas

i Love Robotics... ; )


byee....