Serveur Web de streaming vidéo ESP32-CAM Pan and Tilt

Serveur Web de streaming vidéo ESP32-CAM Pan and Tilt

Dans ce projet, nous allons attacher l’ESP32-CAM à un support panoramique et inclinable avec deux servomoteurs SG90. Avec un support de caméra panoramique et inclinable, vous pouvez déplacer la caméra vers le haut, vers le bas, vers la gauche et la droite, ce qui est idéal pour la surveillance. L’ESP32-CAM héberge un serveur Web qui affiche le streaming vidéo et des boutons pour contrôler les servomoteurs pour déplacer la caméra.

Serveur Web de streaming video ESP32 CAM Pan and Tilt

Compatibilité des cartes : pour ce projet, vous avez besoin d’une carte de développement de caméra ESP32 avec
accès à deux GPIO pour contrôler deux servomoteurs. Vous pouvez utiliser: ESP32-CAM AI-Penseur, T-Journal ou Caméra T TTGO Plus.

Pièces requises

Pour ce projet, nous utiliserons les pièces suivantes :

Support et moteurs panoramiques et inclinables

Pour ce projet, nous utiliserons un support panoramique et inclinable qui est déjà livré avec deux servomoteurs SG90. Le support est illustré dans la figure suivante.

Panoramique et inclinaison avec servomoteurs SG90 ESP32-CAM

Nous avons notre stand chez Banggood, mais vous pouvez obtenir le vôtre dans n’importe quel autre magasin.

Alternativement, vous pouvez obtenir deux servomoteurs SG90 et imprimez en 3D votre propre stand.

Les servomoteurs ont trois fils de couleurs différentes :

Fil Couleur
Pouvoir rouge
GND Noir ou marron
Signal Jaune, orange ou blanc

Comment contrôler un servomoteur ?

Vous pouvez positionner l’arbre du servo à différents angles de 0 à 180º. Les servos sont contrôlés à l’aide d’un signal de modulation de largeur d’impulsion (PWM). Cela signifie que le signal PWM envoyé au moteur détermine la position de l’arbre.

Angles d'arbre servo ESP32-CAM de 0 à 180º

Pour contrôler le servomoteur, vous pouvez utiliser les capacités PWM de l’ESP32 en envoyant un signal avec la largeur d’impulsion appropriée. Ou vous pouvez utiliser une bibliothèque pour simplifier le code. Nous utiliserons le ESP32Servo bibliothèque.

Installation de la bibliothèque ESP32Servo

Pour contrôler les servomoteurs, nous utiliserons le ESP32Servo bibliothèque. Assurez-vous d’installer cette bibliothèque avant de continuer. Dans votre IDE Arduino, accédez à Esquisser > Inclure la bibliothèque > Gérer les bibliothèques. Rechercher ESP32Servo et installez la bibliothèque comme indiqué ci-dessous.

Installer la bibliothèque ESP32Servo Arduino IDE

Code

Copiez le code suivant dans votre IDE Arduino.

/*********
  Rui Santos
  Complete instructions at https://Raspberryme.com/esp32-cam-projects-ebook/
  
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.
  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*********/

#include "esp_camera.h"
#include <WiFi.h>
#include "esp_timer.h"
#include "img_converters.h"
#include "Arduino.h"
#include "fb_gfx.h"
#include "soc/soc.h"             // disable brownout problems
#include "soc/rtc_cntl_reg.h"    // disable brownout problems
#include "esp_http_server.h"
#include <ESP32Servo.h>

// Replace with your network credentials
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

#define PART_BOUNDARY "123456789000000000000987654321"

#define CAMERA_MODEL_AI_THINKER
//#define CAMERA_MODEL_M5STACK_PSRAM
//#define CAMERA_MODEL_M5STACK_WITHOUT_PSRAM
//#define CAMERA_MODEL_M5STACK_PSRAM_B
//#define CAMERA_MODEL_WROVER_KIT

#if defined(CAMERA_MODEL_WROVER_KIT)
  #define PWDN_GPIO_NUM    -1
  #define RESET_GPIO_NUM   -1
  #define XCLK_GPIO_NUM    21
  #define SIOD_GPIO_NUM    26
  #define SIOC_GPIO_NUM    27
  
  #define Y9_GPIO_NUM      35
  #define Y8_GPIO_NUM      34
  #define Y7_GPIO_NUM      39
  #define Y6_GPIO_NUM      36
  #define Y5_GPIO_NUM      19
  #define Y4_GPIO_NUM      18
  #define Y3_GPIO_NUM       5
  #define Y2_GPIO_NUM       4
  #define VSYNC_GPIO_NUM   25
  #define HREF_GPIO_NUM    23
  #define PCLK_GPIO_NUM    22

#elif defined(CAMERA_MODEL_M5STACK_PSRAM)
  #define PWDN_GPIO_NUM     -1
  #define RESET_GPIO_NUM    15
  #define XCLK_GPIO_NUM     27
  #define SIOD_GPIO_NUM     25
  #define SIOC_GPIO_NUM     23
  
  #define Y9_GPIO_NUM       19
  #define Y8_GPIO_NUM       36
  #define Y7_GPIO_NUM       18
  #define Y6_GPIO_NUM       39
  #define Y5_GPIO_NUM        5
  #define Y4_GPIO_NUM       34
  #define Y3_GPIO_NUM       35
  #define Y2_GPIO_NUM       32
  #define VSYNC_GPIO_NUM    22
  #define HREF_GPIO_NUM     26
  #define PCLK_GPIO_NUM     21

#elif defined(CAMERA_MODEL_M5STACK_WITHOUT_PSRAM)
  #define PWDN_GPIO_NUM     -1
  #define RESET_GPIO_NUM    15
  #define XCLK_GPIO_NUM     27
  #define SIOD_GPIO_NUM     25
  #define SIOC_GPIO_NUM     23
  
  #define Y9_GPIO_NUM       19
  #define Y8_GPIO_NUM       36
  #define Y7_GPIO_NUM       18
  #define Y6_GPIO_NUM       39
  #define Y5_GPIO_NUM        5
  #define Y4_GPIO_NUM       34
  #define Y3_GPIO_NUM       35
  #define Y2_GPIO_NUM       17
  #define VSYNC_GPIO_NUM    22
  #define HREF_GPIO_NUM     26
  #define PCLK_GPIO_NUM     21

#elif defined(CAMERA_MODEL_AI_THINKER)
  #define PWDN_GPIO_NUM     32
  #define RESET_GPIO_NUM    -1
  #define XCLK_GPIO_NUM      0
  #define SIOD_GPIO_NUM     26
  #define SIOC_GPIO_NUM     27
  
  #define Y9_GPIO_NUM       35
  #define Y8_GPIO_NUM       34
  #define Y7_GPIO_NUM       39
  #define Y6_GPIO_NUM       36
  #define Y5_GPIO_NUM       21
  #define Y4_GPIO_NUM       19
  #define Y3_GPIO_NUM       18
  #define Y2_GPIO_NUM        5
  #define VSYNC_GPIO_NUM    25
  #define HREF_GPIO_NUM     23
  #define PCLK_GPIO_NUM     22

#elif defined(CAMERA_MODEL_M5STACK_PSRAM_B)
  #define PWDN_GPIO_NUM     -1
  #define RESET_GPIO_NUM    15
  #define XCLK_GPIO_NUM     27
  #define SIOD_GPIO_NUM     22
  #define SIOC_GPIO_NUM     23
  
  #define Y9_GPIO_NUM       19
  #define Y8_GPIO_NUM       36
  #define Y7_GPIO_NUM       18
  #define Y6_GPIO_NUM       39
  #define Y5_GPIO_NUM        5
  #define Y4_GPIO_NUM       34
  #define Y3_GPIO_NUM       35
  #define Y2_GPIO_NUM       32
  #define VSYNC_GPIO_NUM    25
  #define HREF_GPIO_NUM     26
  #define PCLK_GPIO_NUM     21

#else
  #error "Camera model not selected"
#endif

#define SERVO_1      14
#define SERVO_2      15

#define SERVO_STEP   5

Servo servoN1;
Servo servoN2;
Servo servo1;
Servo servo2;

int servo1Pos = 0;
int servo2Pos = 0;

static const char* _STREAM_CONTENT_TYPE = "multipart/x-mixed-replace;boundary=" PART_BOUNDARY;
static const char* _STREAM_BOUNDARY = "rn--" PART_BOUNDARY "rn";
static const char* _STREAM_PART = "Content-Type: image/jpegrnContent-Length: %urnrn";

httpd_handle_t camera_httpd = NULL;
httpd_handle_t stream_httpd = NULL;

static const char PROGMEM INDEX_HTML[] = R"rawliteral(
<html>
  <head>
    <title>ESP32-CAM Robot</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <style>
      body { font-family: Arial; text-align: center; margin:0px auto; padding-top: 30px;}
      table { margin-left: auto; margin-right: auto; }
      td { padding: 8 px; }
      .button {
        background-color: #2f4468;
        border: none;
        color: white;
        padding: 10px 20px;
        text-align: center;
        text-decoration: none;
        display: inline-block;
        font-size: 18px;
        margin: 6px 3px;
        cursor: pointer;
        -webkit-touch-callout: none;
        -webkit-user-select: none;
        -khtml-user-select: none;
        -moz-user-select: none;
        -ms-user-select: none;
        user-select: none;
        -webkit-tap-highlight-color: rgba(0,0,0,0);
      }
      img {  width: auto ;
        max-width: 100% ;
        height: auto ; 
      }
    </style>
  </head>
  <body>
    <h1>ESP32-CAM Pan and Tilt</h1>
    <img src="" id="photo" >
    <table>
      <tr><td colspan="3" align="center"><button class="button" onmousedown="toggleCheckbox('up');" ontouchstart="toggleCheckbox('up');">Up</button></td></tr>
      <tr><td align="center"><button class="button" onmousedown="toggleCheckbox('left');" ontouchstart="toggleCheckbox('left');">Left</button></td><td align="center"></td><td align="center"><button class="button" onmousedown="toggleCheckbox('right');" ontouchstart="toggleCheckbox('right');">Right</button></td></tr>
      <tr><td colspan="3" align="center"><button class="button" onmousedown="toggleCheckbox('down');" ontouchstart="toggleCheckbox('down');">Down</button></td></tr>                   
    </table>
   <script>
   function toggleCheckbox(x) {
     var xhr = new XMLHttpRequest();
     xhr.open("GET", "/action?go=" + x, true);
     xhr.send();
   }
   window.onload = document.getElementById("photo").src = window.location.href.slice(0, -1) + ":81/stream";
  </script>
  </body>
</html>
)rawliteral";

static esp_err_t index_handler(httpd_req_t *req){
  httpd_resp_set_type(req, "text/html");
  return httpd_resp_send(req, (const char *)INDEX_HTML, strlen(INDEX_HTML));
}

static esp_err_t stream_handler(httpd_req_t *req){
  camera_fb_t * fb = NULL;
  esp_err_t res = ESP_OK;
  size_t _jpg_buf_len = 0;
  uint8_t * _jpg_buf = NULL;
  char * part_buf[64];

  res = httpd_resp_set_type(req, _STREAM_CONTENT_TYPE);
  if(res != ESP_OK){
    return res;
  }

  while(true){
    fb = esp_camera_fb_get();
    if (!fb) {
      Serial.println("Camera capture failed");
      res = ESP_FAIL;
    } else {
      if(fb->width > 400){
        if(fb->format != PIXFORMAT_JPEG){
          bool jpeg_converted = frame2jpg(fb, 80, &_jpg_buf, &_jpg_buf_len);
          esp_camera_fb_return(fb);
          fb = NULL;
          if(!jpeg_converted){
            Serial.println("JPEG compression failed");
            res = ESP_FAIL;
          }
        } else {
          _jpg_buf_len = fb->len;
          _jpg_buf = fb->buf;
        }
      }
    }
    if(res == ESP_OK){
      size_t hlen = snprintf((char *)part_buf, 64, _STREAM_PART, _jpg_buf_len);
      res = httpd_resp_send_chunk(req, (const char *)part_buf, hlen);
    }
    if(res == ESP_OK){
      res = httpd_resp_send_chunk(req, (const char *)_jpg_buf, _jpg_buf_len);
    }
    if(res == ESP_OK){
      res = httpd_resp_send_chunk(req, _STREAM_BOUNDARY, strlen(_STREAM_BOUNDARY));
    }
    if(fb){
      esp_camera_fb_return(fb);
      fb = NULL;
      _jpg_buf = NULL;
    } else if(_jpg_buf){
      free(_jpg_buf);
      _jpg_buf = NULL;
    }
    if(res != ESP_OK){
      break;
    }
    //Serial.printf("MJPG: %uBn",(uint32_t)(_jpg_buf_len));
  }
  return res;
}

static esp_err_t cmd_handler(httpd_req_t *req){
  char*  buf;
  size_t buf_len;
  char variable[32] = {0,};
  
  buf_len = httpd_req_get_url_query_len(req) + 1;
  if (buf_len > 1) {
    buf = (char*)malloc(buf_len);
    if(!buf){
      httpd_resp_send_500(req);
      return ESP_FAIL;
    }
    if (httpd_req_get_url_query_str(req, buf, buf_len) == ESP_OK) {
      if (httpd_query_key_value(buf, "go", variable, sizeof(variable)) == ESP_OK) {
      } else {
        free(buf);
        httpd_resp_send_404(req);
        return ESP_FAIL;
      }
    } else {
      free(buf);
      httpd_resp_send_404(req);
      return ESP_FAIL;
    }
    free(buf);
  } else {
    httpd_resp_send_404(req);
    return ESP_FAIL;
  }

  sensor_t * s = esp_camera_sensor_get();
  //flip the camera vertically
  //s->set_vflip(s, 1);          // 0 = disable , 1 = enable
  // mirror effect
  //s->set_hmirror(s, 1);          // 0 = disable , 1 = enable

  int res = 0;
  
  if(!strcmp(variable, "up")) {
    if(servo1Pos <= 170) {
      servo1Pos += 10;
      servo1.write(servo1Pos);
    }
    Serial.println(servo1Pos);
    Serial.println("Up");
  }
  else if(!strcmp(variable, "left")) {
    if(servo2Pos <= 170) {
      servo2Pos += 10;
      servo2.write(servo2Pos);
    }
    Serial.println(servo2Pos);
    Serial.println("Left");
  }
  else if(!strcmp(variable, "right")) {
    if(servo2Pos >= 10) {
      servo2Pos -= 10;
      servo2.write(servo2Pos);
    }
    Serial.println(servo2Pos);
    Serial.println("Right");
  }
  else if(!strcmp(variable, "down")) {
    if(servo1Pos >= 10) {
      servo1Pos -= 10;
      servo1.write(servo1Pos);
    }
    Serial.println(servo1Pos);
    Serial.println("Down");
  }
  else {
    res = -1;
  }

  if(res){
    return httpd_resp_send_500(req);
  }

  httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
  return httpd_resp_send(req, NULL, 0);
}

void startCameraServer(){
  httpd_config_t config = HTTPD_DEFAULT_CONFIG();
  config.server_port = 80;
  httpd_uri_t index_uri = {
    .uri       = "/",
    .method    = HTTP_GET,
    .handler   = index_handler,
    .user_ctx  = NULL
  };

  httpd_uri_t cmd_uri = {
    .uri       = "/action",
    .method    = HTTP_GET,
    .handler   = cmd_handler,
    .user_ctx  = NULL
  };
  httpd_uri_t stream_uri = {
    .uri       = "/stream",
    .method    = HTTP_GET,
    .handler   = stream_handler,
    .user_ctx  = NULL
  };
  if (httpd_start(&camera_httpd, &config) == ESP_OK) {
    httpd_register_uri_handler(camera_httpd, &index_uri);
    httpd_register_uri_handler(camera_httpd, &cmd_uri);
  }
  config.server_port += 1;
  config.ctrl_port += 1;
  if (httpd_start(&stream_httpd, &config) == ESP_OK) {
    httpd_register_uri_handler(stream_httpd, &stream_uri);
  }
}

void setup() {
  WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //disable brownout detector
  servo1.setPeriodHertz(50);    // standard 50 hz servo
  servo2.setPeriodHertz(50);    // standard 50 hz servo
  servoN1.attach(2, 1000, 2000);
  servoN2.attach(13, 1000, 2000);
  
  servo1.attach(SERVO_1, 1000, 2000);
  servo2.attach(SERVO_2, 1000, 2000);
  
  servo1.write(servo1Pos);
  servo2.write(servo2Pos);
  
  Serial.begin(115200);
  Serial.setDebugOutput(false);
  
  camera_config_t config;
  config.ledc_channel = LEDC_CHANNEL_0;
  config.ledc_timer = LEDC_TIMER_0;
  config.pin_d0 = Y2_GPIO_NUM;
  config.pin_d1 = Y3_GPIO_NUM;
  config.pin_d2 = Y4_GPIO_NUM;
  config.pin_d3 = Y5_GPIO_NUM;
  config.pin_d4 = Y6_GPIO_NUM;
  config.pin_d5 = Y7_GPIO_NUM;
  config.pin_d6 = Y8_GPIO_NUM;
  config.pin_d7 = Y9_GPIO_NUM;
  config.pin_xclk = XCLK_GPIO_NUM;
  config.pin_pclk = PCLK_GPIO_NUM;
  config.pin_vsync = VSYNC_GPIO_NUM;
  config.pin_href = HREF_GPIO_NUM;
  config.pin_sscb_sda = SIOD_GPIO_NUM;
  config.pin_sscb_scl = SIOC_GPIO_NUM;
  config.pin_pwdn = PWDN_GPIO_NUM;
  config.pin_reset = RESET_GPIO_NUM;
  config.xclk_freq_hz = 20000000;
  config.pixel_format = PIXFORMAT_JPEG; 
  
  if(psramFound()){
    config.frame_size = FRAMESIZE_VGA;
    config.jpeg_quality = 10;
    config.fb_count = 2;
  } else {
    config.frame_size = FRAMESIZE_SVGA;
    config.jpeg_quality = 12;
    config.fb_count = 1;
  }
  
  // Camera init
  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    Serial.printf("Camera init failed with error 0x%x", err);
    return;
  }
  // Wi-Fi connection
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected");
  
  Serial.print("Camera Stream Ready! Go to: http://");
  Serial.println(WiFi.localIP());
  
  // Start streaming web server
  startCameraServer();
}

void loop() {
  
}

Afficher le code brut

Identifiants du réseau

Insérez vos informations d’identification réseau et le code devrait fonctionner immédiatement.

const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

Comment fonctionne le code

Jetons un coup d’œil aux pièces pertinentes pour contrôler les servomoteurs.

Définissez les broches auxquelles les servomoteurs sont connectés. Dans ce cas, ils sont connectés aux GPIO ESP32-CAM 14 et 15.

#define SERVO_1 14
#define SERVO_2 15

Créer Servomoteur objets pour contrôler chaque moteur :

Servo servoN1;
Servo servoN2;
Servo servo1;
Servo servo2;

Vous vous demandez peut-être pourquoi nous créons quatre Servomoteur objets lorsque nous n’avons que deux servos. Ce qui se passe, c’est que la bibliothèque d’asservissement que nous utilisons attribue automatiquement un canal PWM à chaque servomoteur (servoN1 → canal PWM 0; servoN2 → canal PWM 1; servo1 → canal PWM 2; servo2 → canal PWM 3).

Les premiers canaux sont utilisés par la caméra, donc si nous modifions les propriétés de ces canaux PWM, nous obtiendrons des erreurs avec la caméra. Alors, on contrôlera servo1 et servo2 qui utilisent les canaux PWM 2 et 3 qui ne sont pas utilisés par la caméra.

Définir la position initiale des servos.

int servo1Pos = 0;
int servo2Pos = 0;

Page Web

le INDEX_HTML La variable contient le texte HTML pour construire la page Web. Les lignes suivantes affichent les boutons.

<table>
  <tr><td colspan="3" align="center"><button class="button" onmousedown="toggleCheckbox('up');" ontouchstart="toggleCheckbox('up');">Up</button></td></tr>
  <tr><td align="center"><button class="button" onmousedown="toggleCheckbox('left');" ontouchstart="toggleCheckbox('left');">Left</button></td><td align="center"></td><td align="center"><button class="button" onmousedown="toggleCheckbox('right');" ontouchstart="toggleCheckbox('right');">Right</button></td></tr>
  <tr><td colspan="3" align="center"><button class="button" onmousedown="toggleCheckbox('down');" ontouchstart="toggleCheckbox('down');">Down</button></td></tr>                   
</table>

Lorsque vous cliquez sur les boutons, le toggleCheckbox() La fonction JavaScript est appelée. Il fait une requête sur une URL différente selon le bouton cliqué.

function toggleCheckbox(x) {
  var xhr = new XMLHttpRequest();
  xhr.open("GET", "/action?go=" + x, true);
  xhr.send();
}

Voici les demandes effectuées en fonction du bouton sur lequel on appuie :

En haut:

/action?go=up

Vers le bas:

/action?go=down

À gauche:

/action?go=left

À droite:

/action?go=right

Traiter les demandes

Ensuite, nous devons gérer ce qui se passe lorsque nous recevons ces demandes. C’est ce qui est fait dans les lignes suivantes.

if(!strcmp(variable, "up")) {
  if(servo1Pos <= 170) {
    servo1Pos += 10;
    servo1.write(servo1Pos);
  }
  Serial.println(servo1Pos);
  Serial.println("Up");
}
else if(!strcmp(variable, "left")) {
  if(servo2Pos <= 170) {
    servo2Pos += 10;
    servo2.write(servo2Pos);
  }
  Serial.println(servo2Pos);
  Serial.println("Left");
}
else if(!strcmp(variable, "right")) {
  if(servo2Pos >= 10) {
    servo2Pos -= 10;
    servo2.write(servo2Pos);
  }
  Serial.println(servo2Pos);
  Serial.println("Right");
}
else if(!strcmp(variable, "down")) {
  if(servo1Pos >= 10) {
    servo1Pos -= 10;
    servo1.write(servo1Pos);
  }
  Serial.println(servo1Pos);
  Serial.println("Down");
}

Pour déplacer un moteur, appelez le écrivez() fonction sur le servo1 ou servo2 objets et passez l’angle (0 à 180) comme argument. Par example:

servo1.write(servo1Pos);

mettre en place()

Dans le mettre en place(), définissez les propriétés du servomoteur : définissez la fréquence du signal.

servo1.setPeriodHertz(50); // standard 50 hz servo
servo2.setPeriodHertz(50); // standard 50 hz servo

Utilisez le attacher() méthode pour définir le servo GPIO et la largeur d’impulsion minimale et maximale en microsecondes.

servo1.attach(SERVO_1, 1000, 2000);
servo2.attach(SERVO_2, 1000, 2000);

Réglez les moteurs sur leur position initiale lorsque l’ESP32 démarre pour la première fois.

servo1.write(servo1Pos);
servo2.write(servo2Pos);

C’est à peu près ainsi que fonctionne le code lorsqu’il s’agit de contrôler les servomoteurs.

Tester le code

Après avoir inséré vos informations d’identification réseau, vous pouvez télécharger le code sur votre tableau. Vous pouvez utiliser un programmeur FTDI ou un programmeur ESP32-CAM MB. Lisez l’un des articles suivants :

Après le téléchargement, ouvrez le moniteur série pour obtenir l’adresse IP de la carte.

ESP32-CAM Obtention de l'adresse IP Moniteur série

Noter: si vous utilisez un programmeur FTDI, n’oubliez pas de déconnecter GPIO 0 de GND avant d’ouvrir Serial Monitor.

Ouvrez un navigateur et saisissez l’adresse IP de la carte pour accéder au serveur Web. Cliquez sur les boutons et vérifiez sur le moniteur série si tout semble fonctionner comme prévu.

Moniteur série de serveur Web ESP32-CAM Pan and Tilt

Si tout fonctionne comme prévu, vous pouvez câbler les servomoteurs à l’ESP32-CAM et poursuivre le projet.

Circuit

Après avoir assemblé le support panoramique et inclinable, connectez les servomoteurs à l’ESP32-CAM comme indiqué dans le schéma suivant. Nous connectons les broches de données du servomoteur à GPIO 15 et GPIO 14.

Servomoteurs d'orientation et d'inclinaison ESP32-CAM

Vous pouvez utiliser une mini planche à pain pour assembler le circuit ou construire une mini planche à ruban avec des broches d’en-tête pour connecter l’alimentation, l’ESP32-CAM et les moteurs, comme indiqué ci-dessous.

Carte prototype ESP32-CAM Pan and Tilt

La figure suivante montre à quoi ressemblent les supports panoramiques et inclinables après assemblage.

Panoramique et inclinaison ESP32-CAM assemblés

Manifestation

Mettez votre carte sous tension. Ouvrez un navigateur et saisissez l’adresse IP ESP32-CAM. Une page Web avec un streaming vidéo en temps réel devrait se charger. Cliquez sur les boutons pour déplacer la caméra vers le haut, le bas, la gauche ou la droite.

ESP32-CAM Pan and Tilt Serveur Web Streaming vidéo Smartphone Arduino IDE

Vous pouvez déplacer la caméra à distance à l’aide des boutons de la page Web. Cela vous permet de
surveiller une zone différente en fonction de la position de la caméra. C’est une excellente solution pour les applications de surveillance.

Conclusion

Dans ce didacticiel, vous avez appris à créer un serveur Web panoramique et inclinable avec streaming vidéo pour contrôler l’ESP32-CAM.

Contrôler les servomoteurs avec l’ESP32-CAM revient à les contrôler à l’aide d’un ESP32 « normal ». Vous pouvez lire le tutoriel suivant pour en savoir plus sur les servomoteurs avec l’ESP32 :

Si vous souhaitez contrôler votre robot en dehors de la portée de votre réseau local, vous pouvez envisager de configurer l’ESP32-CAM comme point d’accès. De cette façon, l’ESP32-CAM n’a pas besoin de se connecter à votre routeur. Il crée son propre réseau Wi-Fi et les appareils Wi-Fi à proximité, comme votre smartphone, peuvent s’y connecter.

Pour plus de projets et de tutoriels avec l’ESP32-CAM :