Introduction: PANTILT Camera With ESP32

About: Do you like technology? Follow my channel on Youtube and my Blog. In them I put videos every week of microcontrollers, arduinos, networks, among other subjects.

Today, I will present the PAN TILT, which is a device that enables the movement of a camera for the directions of up, down, and to the sides. I myself produced this device through 3D printed parts, using two servos and the ESP32, which makes it possible to control this mechanism through WiFi. Let us then take readings using the AD channels of the ESP32, as well as an analog operation using the controller LED_PWM. Also, we apply the control over a TCP / IP connection.

In the video, you can see that I have an ESP32 reading the values of the two potentiometers, which are sent (via WiFi) to another ESP32. It is connected to the two servo motors. The camera moves (and is attached to the PAN TILT) in the directions of up, down, or sideways, depending on the control you make through the pots.

The link to the PAN TILT 3D print design can be found here:

Step 1: Resources Used

• Multiple jumpers for connection

• Two Node MCU ESP32s

• Two USB cables for the ESP32

• A WebCam for control

• Two control pots

• A protoboard

• A source for the servos

Step 2: NodeMCU ESP32S - Pinout

Step 3: ESP32 Peripherals

PWM Peripherals The ESP32 has two peripherals capable of generating PWM signals. These include the Pulse Width Modulator (MCPWM) engine designed for power and motor control, and the LED_PWM, developed for LED intensity control. But they can also be used in a generic way.

We will use the LED_PWM, which can generate 16 independent PWM channels with configurable periods and work cycles. It has up to 16 bits of resolution.

Step 4: Servo Motor Control PWM

The servo motor control is performed by adjusting the pulse width modulation of a square with specific frequency.

For the servo used (as well as for most), the frequency is at 50Hz. Also, a width of 1 to 2ms pulse length determines the angular position of the servo.

We will route channel 0 of LED_PWM to GPIO13, and channel 1 to GPIO12, using this information to perform the control.

Step 5: Analog Capture

Analog to digital conversion peripheral

The ESP32 has analogue-to-digital converters that can be applied in up to 18 channels, but only in analog-enabled GPIOs.

The applied voltage must not exceed the 0 to 3V range.

The conversion performed does not maintain a constant error for all voltages sampled, and this all depends on the configured range. For a range of 150mV at 2,450V, a behavior check is required for more critical applications.

For the capture, we will use a potentiometer of 10k as a voltage divider. The capture will be done in channel ADC0 and ADC3, accessible by GPIO36 and GPIO39.

Step 6: Circuit - Server and Client

Step 7: Source Code of the Access Point and Server


I include the WiFi library, and I define some variables.

#include <WiFi.h> // inclusão da biblioteca WiFi
const int freq = 50; //frequência do PWM const int canal_A = 0; //primeiro canal do controlador LED_PWM const int canal_B = 1; //segundo canal do controlador LED_PWM const int resolucao = 12; //Resolução usado no controlador LED_PWM const int pin_Atuacao_A = 13; //Pino para onde o canal 0 será redirecionado const int pin_Atuacao_B = 12; //Pino para onde o canal 1 será redirecionado const char* ssid = "ESP32ap" ; //constante com o SSID do WiFi do ponto de acesso ESP32 const char* password = "12345678" ; //senha para confirmação de conexão no ponto de acesso const int port = 2; //porta na qual o servidor receberá as conexões int ciclo_A = 0; //variável que receberá o ciclo de atuação do canal A int ciclo_B = 0; //variável que receberá o ciclo de atuação do canal A WiFiServer server(port); //declaração do objeto servidor IPAddress myIP; //declaração da variável de IP

Setup ()

Here, we define the output pins. We set the channels to the desired frequency and set the PWM value.

void setup()
{ pinMode(pin_Atuacao_A, OUTPUT); //definindo o pino de atuação A como saída pinMode(pin_Atuacao_B, OUTPUT); //definindo o pino de atuação B como saída ledcSetup(canal_A, freq, resolucao); //Ajustando o canal 0 para frequência de 50 Hz e resolução de 12bits ledcSetup(canal_B, freq, resolucao); //Ajustando o canal 1 para frequência de 50 Hz e resolução de 12bits ledcAttachPin(pin_Atuacao_A, canal_A); //redirecionando o canal 0 para o pino 13 ledcAttachPin(pin_Atuacao_B, canal_B); //redirecionando o canal 1 para o pino 12 ledcWrite(canal_A, ciclo_A); //definindo o valor do PWM para 0 ledcWrite(canal_B, ciclo_B); //definindo o valor do PWM para 0

We started the serial, access point with SSID ESP32ap, and password. We then get the IP of the server and start the server.

Serial.begin(115200); //iniciando a Serial
Serial.println("Iniciando ponto de acesso: " + String(ssid)); //mensagem WiFi.softAP (ssid, password); //iniciando o ponto de acesso com SSID ESP32ap e senha 12345678 Serial.println("Obtendo IP"); //mensagem myIP = WiFi.softAPIP (); //obtendo o IP do servidor (como não foi configurado deverá ser o padrão de fábrica) Serial.println("IP: " + WiFi.localIP()); //mensagem Serial.println("Iniciando servidor em: " + String(port)); //mensagem server.begin(); //iniciando o servidor }

Loop ()

In Loop, the first thing we're going to do is instantiate the client, connecting and binding to the client variable. Check if the client is connected. If so, we start the variable that will receive the data. As long as the connection is established, and if data is received, we read the characters for the variable c. Finally, we concatenate c in the data variable.

void loop() {
WiFiClient cliente = server.available(); //se um cliente conectar, associe a variável cliente if (cliente.connected()) { //se há um cliente conectado String dados = ""; //inicia a variável que receberá os dados Serial.println("Cliente conectado."); //mensagem while (cliente.connected()) { //enquanto a conexão estiver estabelecida if (cliente.available()) { //e se houver dados a receber char c =; //leia os caracteres para a variável c dados = dados + c; //concatene c na variável dados

If a newline character is received, we look for the index of the character ',' in the string in the data. We get the substrings until right before the comma, and then we convert them to integer. We set the PWM of channels A and B. We clear the variable.

if (c == '\n') { //se um caracter de nova linha for recebido
int virgula = dados.indexOf(','); //procure pelo índice do caracter ',' na string em dados ciclo_A = (dados.substring(0, virgula)).toInt(); //obtenha a substring até antes da vírgula e converta para inteiro ciclo_B = dados.substring(virgula + 1, dados.length()).toInt();//obtenha a substring após a vírgula e converta para inteiro ledcWrite(canal_A, ciclo_A); //Ajusta o PWM do canal A ledcWrite(canal_B, ciclo_B); //Ajusta o PWM do canal B dados = ""; //Limpa a variável } } } }

If the client disconnects, we confirm the end of the connection. We wait for a moment and print "No client connected". We then wait another second before restarting.

// caso o cliente se desconecte, confirma o fim da conexão
delay(50); //aguarda um momento cliente.stop(); Serial.println("Nenhum cliente conectado."); //mensagem delay(1000); //aguarda um segundo antes de reiniciar }

Step 8: Customer Source Code


We have included the WiFi library again, this time on the client. Also, we define the variables.

#include <WiFi.h>
const char* ssid = "ESP32ap"; //SSID do ponto de acesso ESP32 const char* password = "12345678"; //Senha para acessar o ponto de acesso const uint16_t port = 2; //Porta de escuta do servidor const char * host = ""; //endereço IP do servidor const int pin_Leitura_A = 36; //GPIO de leitura do ADC0 const int pin_Leitura_B = 39; //GPIO de leitura do ADC3 int ciclo_A = 0; //variável que receberá o valor do ciclo do PWM A int ciclo_B = 0; //Variável que receberá o valor do ciclo do PWM B WiFiClient cliente; //declaração do objeto cliente

Setup ()

We define the GPIOs as input, start the serial, and connect to the access point.

void setup()
{ pinMode(pin_Leitura_A, INPUT); //define o GPIO como entrada pinMode(pin_Leitura_B, INPUT); //define o GPIO como entrada Serial.begin(115200); //inicia a comunicação serial WiFi.begin(ssid, password); //conecta ao ponto de acesso }

Loop ()

In this Loop, we will connect to the server, meaning the other ESP.

void loop()
{ //se não conectado ao ponto de acesso, tenta se conectar while (WiFi.status() != WL_CONNECTED) { Serial.println(String(millis()) + " - Conectando no WiFi " + ssid + "..."); //mensagem WiFi.begin(ssid, password); delay(2000); } Serial.println(String(millis()) + " - Conectado..."); //mensagem //se não conectado ao servidor, tenta se conectar while (!cliente.connect(host, port)) { Serial.println(String(millis()) + " - Conectando no Servidor " + host + ":" + port + "..."); //mensagem delay(1000); }

In this step, while connected to the server, we execute the variables to store the reading of ADC0 and ADC3. Also, we performed the reading of 500 samples and averaged the readings. We mapped the reading to create the correct duration for the control of the servos, and concatenate and send it to the server.

//enquanto estiver conectado ao servidor
while (cliente.connected()) { int leitura_A = 0; //variável para armazenar a leitura do ADC0 int leitura_B = 0; //variável para armazenar a leitura do ADC3 int amostras = 500; //número de amostras int contador = 0; //contador de amostras while (contador < amostras) { //acumua várias leituras leitura_A = leitura_A + analogRead(pin_Leitura_A); leitura_B = leitura_B + analogRead(pin_Leitura_B); contador++; } leitura_A = leitura_A / amostras; //média das leituras leitura_B = leitura_B / amostras; ciclo_A = map(leitura_A, 0, 4095, 140, 490); //mapeia a leitura para criar a duração correta para controle do servo ciclo_B = map(leitura_B, 0, 4095, 140, 490); //mapeia a leitura para criar a duração correta para controle do servo //concatena e envia para o servidor cliente.println(String(ciclo_A) + "," + String(ciclo_B)); }

Finally, if not connected, we ensure that the connection has been terminated by displaying the equivalent message.

//se não coonectado, garante que a conexão foi finalizada
cliente.stop(); Serial.println(String(millis()) + " - cliente desconectado..."); //mensagem }

Step 9: Files

Download the files: