آموزش ساخت ربات ماشینی دوربین دار با ماژول ESP32-CAM

ESP32-CAM Remote Controlled Car Robot Web Server

امروزه طراحی و ساخت یک ربات ماشینی دوربین دار به یکی از آموزش‌های جذاب و پرکاربرد در زمینه‌های آموزشی شاخه رباتیک تبدیل شده‌است و ما در این پروژه می‌توانیم از ماژول ESP32-CAM کمک بگیریم و آنچه که ربات می‌بیند را با استفاده از دوربین این ماژول در وب سرور نمایش دهیم تا بتوانیم ربات را حتی در صورتی که از دید ما خارج می‌شود نیز کنترل کنیم، همچنین امکان کنترل این ربات از راه دور و از طریق وای‌ فای را داشته‌باشیم.

لازم به ذکر است، ما در این پروژه برای پروگرام کردن ماژول ESP32-CAM از Arduino IDE  استفاده می‌کنیم.

اجزاء و ویژگی‌های مهم در ساخت ربات ماشینی

قبل از شروع به کار اجازه دهید به مهم‌ترین ویژگی‌ها و قطعات مورد استفاده در این پروژه بپردازیم.

1) Wi-Fi

کنترل ربات دوربین دار ما قرار است با استفاده از وای فای و ماژول ESP32-CAM انجام شود. برای کنترل این ربات یک وب سرور رابط می‌سازیم که با هر دستگاه متصل به شبکه محلی قابل دسترس است. این صفحه وب، یک ویدیو زنده هم از آنچه ربات می‌بیند، نمایش می‌دهد. برای گرفتن تصویر بهتر، توصیه می‌شود از ماژول به همراه آنتن خارجی استفاده کنید، چون ممکن است بدون آنتن خارجی ویدیو به کندی پخش شود و همچنین کنترل ربات از طریق وب سرور نیز با کندی و تاخیر همراه باشد.

ماژول ESP32-CAM برای ساخت ربات ماشینی کنترلی
آنتن خارجی برای کنترل ربات ماشینی از راه دور

2) کیت شاسی ربات

در این پروژه از شاسی ربات دو چرخ استفاده می‌کنیم که شامل شاسی پلاستیکی، دو چرخ پلاستیکی، دو موتور گیربکس پلاستیکی، چرخ هرزگرد و سایر قطعات لازم برای ساخت ربات از جمله پیچ و اسپیسرها می‌باشد.

البته شما می‌توانید بنا به خلاقیت خود از شاسی‌های مختلف فلزی، چوبی یا پلاستیکی و از موتور و چرخ‌های متفاوتی برای این ماشین کنترلی دوربین دار استفاده کنید. اما برای شروع به‌ کار، این شاسی کار را بسیار ساده می‌کند.

شاسی ربات با دو چرخ برای ساخت ربات ماشینی

3) درایور موتور L298N

برای کنترل موتورهای DC روش‌های مختلفی وجود دارد. ما در این پروژه از درایور موتور L298N استفاده می‌کنیم و دلیل آن این است که کنترل سرعت و جهت چرخش دو موتور را بسیار ساده می‌کند.

اجزای ماژول درایور موتور L298N

4) منبع تغذیه ربات

به منظور اجتناب از پیچیدگی مدار، ربات (موتورها) و ماژول ESP32 را به تغذیه یکسان متصل می‌کنیم و بدین منظور از یک کیس مینی پاور بانک تک خروجی استفاده شده‌است.
توجه: معمولاً جریان مصرفی موتورها زیاد است، بنابراین اگر احساس کردید ربات شما به درستی حرکت نمی‌کند، به یک منبع تغذیه خارجی نیاز خواهید داشت. به این معنا که باید از دو منبع تغذیه استفاده کنید؛ یکی برای موتورهای DC و دیگری برای ماژول ESP32.

کیس مینی پاور بانک تک خروجی برای ساخت ربات ماشینی با منبع تغذیه قابل شارژ

قطعات مورد نیاز

پس از آشنایی با قطعات در ساخت ربات ماشینی دوربین دار، نوبت به فراهم کردن قطعات زیر می‌باشد که ما برای دسترسی سریع‌تر لینک‌ هرکدام را برای شما قرار داده‌ایم:

نحوه کنترل ربات ماشینی دوربین دار

همان‌طور که متوجه شدید ما برای کنترل از راه دور این ربات کنترلی دوربین دار از وب سرور یا مرورگر گوشی موبایل خود استفاده خواهیم کرد. این وب‌سرور دارای ۵ دکمه است: جلو (Forward)، عقب (Backward)، چپ (Left)، راست (Right) و توقف (Stop). تا وقتی دست ما روی یکی از دکمه‌ها باشد، ربات در جهت متناظر حرکت می‌کند و وقتی دکمه رها شود، ربات می‌ایستد. با این حال برخی اوقات ممکن است با برداشتن دست از روی دکمه، ESP32  فرمان توقف را دریافت نکند. در این مواقع وجود دکمه stop کمک می‌کند بتوانیم ربات را متوقف کنیم.

اتصال به وب سرور با گوشی موبایل و کنترل از راه دور ربات با دکمه های جلو، عقب، چپ، راست و توقف

کد آردوینو

پس از آشنایی با نحوه عملکرد اجزای این ربات ماشینی کنترلی دوربین دار و مشخص شده راه ارتباطی با وب‌سرور، لازم است برنامه زیر را در IDE آردوینو روی کامپیوتر خود، کپی کنید تا به مراحل بعدی یعنی آپلود برنامه روی ماژول ESP32 برسیم.

اگر تاکنون برروی کامپیوتر خود برنامه IDE آردوینو را نصب نکرده‌اید، از طریق دانلود این برنامه در بخش نرم‌افزارهای سایت آردوینو می‌توانید نسخه مناسب سیستم خود را دانلود و نصب کنید.

/*********
  Rui Santos
  Complete instructions at https://RandomNerdTutorials.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"

// 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 MOTOR_1_PIN_1    14
#define MOTOR_1_PIN_2    15
#define MOTOR_2_PIN_1    13
#define MOTOR_2_PIN_2    12

static const char* _STREAM_CONTENT_TYPE = "multipart/x-mixed-replace;boundary=" PART_BOUNDARY;
static const char* _STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n";
static const char* _STREAM_PART = "Content-Type: image/jpeg\r\nContent-Length: %u\r\n\r\n";

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 Robot</h1>
    <img src="" id="photo" >
    <table>
      <tr><td colspan="3" align="center"><button class="button" onmousedown="toggleCheckbox('forward');" ontouchstart="toggleCheckbox('forward');" onmouseup="toggleCheckbox('stop');" ontouchend="toggleCheckbox('stop');">Forward</button></td></tr>
      <tr><td align="center"><button class="button" onmousedown="toggleCheckbox('left');" ontouchstart="toggleCheckbox('left');" onmouseup="toggleCheckbox('stop');" ontouchend="toggleCheckbox('stop');">Left</button></td><td align="center"><button class="button" onmousedown="toggleCheckbox('stop');" ontouchstart="toggleCheckbox('stop');">Stop</button></td><td align="center"><button class="button" onmousedown="toggleCheckbox('right');" ontouchstart="toggleCheckbox('right');" onmouseup="toggleCheckbox('stop');" ontouchend="toggleCheckbox('stop');">Right</button></td></tr>
      <tr><td colspan="3" align="center"><button class="button" onmousedown="toggleCheckbox('backward');" ontouchstart="toggleCheckbox('backward');" onmouseup="toggleCheckbox('stop');" ontouchend="toggleCheckbox('stop');">Backward</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: %uB\n",(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();
  int res = 0;
  
  if(!strcmp(variable, "forward")) {
    Serial.println("Forward");
    digitalWrite(MOTOR_1_PIN_1, 1);
    digitalWrite(MOTOR_1_PIN_2, 0);
    digitalWrite(MOTOR_2_PIN_1, 1);
    digitalWrite(MOTOR_2_PIN_2, 0);
  }
  else if(!strcmp(variable, "left")) {
    Serial.println("Left");
    digitalWrite(MOTOR_1_PIN_1, 0);
    digitalWrite(MOTOR_1_PIN_2, 1);
    digitalWrite(MOTOR_2_PIN_1, 1);
    digitalWrite(MOTOR_2_PIN_2, 0);
  }
  else if(!strcmp(variable, "right")) {
    Serial.println("Right");
    digitalWrite(MOTOR_1_PIN_1, 1);
    digitalWrite(MOTOR_1_PIN_2, 0);
    digitalWrite(MOTOR_2_PIN_1, 0);
    digitalWrite(MOTOR_2_PIN_2, 1);
  }
  else if(!strcmp(variable, "backward")) {
    Serial.println("Backward");
    digitalWrite(MOTOR_1_PIN_1, 0);
    digitalWrite(MOTOR_1_PIN_2, 1);
    digitalWrite(MOTOR_2_PIN_1, 0);
    digitalWrite(MOTOR_2_PIN_2, 1);
  }
  else if(!strcmp(variable, "stop")) {
    Serial.println("Stop");
    digitalWrite(MOTOR_1_PIN_1, 0);
    digitalWrite(MOTOR_1_PIN_2, 0);
    digitalWrite(MOTOR_2_PIN_1, 0);
    digitalWrite(MOTOR_2_PIN_2, 0);
  }
  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
  
  pinMode(MOTOR_1_PIN_1, OUTPUT);
  pinMode(MOTOR_1_PIN_2, OUTPUT);
  pinMode(MOTOR_2_PIN_1, OUTPUT);
  pinMode(MOTOR_2_PIN_2, OUTPUT);
  
  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() {
  
}

بعد از انتقال تکه‌کد بالا لازم است مشخصات شبکه وای فای خود را در این قسمت وارد کنید:

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

توضیحات کد:

بیایید نگاهی بر قسمت‌های مختلف برنامه داشته باشیم:

  • در بخش defineها، پین‌های ورودی خروجی کنترل‌کننده موتور تعریف شده‌اند که هر موتور با دو پین کنترل می‌شود.
#define MOTOR_1_PIN_1    14
#define MOTOR_1_PIN_2    15
#define MOTOR_2_PIN_1    13
#define MOTOR_2_PIN_2    12
  • در قطعه کد زیر، وب سرور شامل دکمه‌ها و صفحه نمایش را ساخته‌ایم و وقتی روی هر دکمه کلیک شود، یک درخواست HTML روی URL مخصوص آن دکمه ارسال خواهد شد.
<table>
      <tr><td colspan="3" align="center"><button class="button" onmousedown="toggleCheckbox('forward');" ontouchstart="toggleCheckbox('forward');" onmouseup="toggleCheckbox('stop');" ontouchend="toggleCheckbox('stop');">Forward</button></td></tr>
      <tr><td align="center"><button class="button" onmousedown="toggleCheckbox('left');" ontouchstart="toggleCheckbox('left');" onmouseup="toggleCheckbox('stop');" ontouchend="toggleCheckbox('stop');">Left</button></td><td align="center"><button class="button" onmousedown="toggleCheckbox('stop');" ontouchstart="toggleCheckbox('stop');">Stop</button></td><td align="center"><button class="button" onmousedown="toggleCheckbox('right');" ontouchstart="toggleCheckbox('right');" onmouseup="toggleCheckbox('stop');" ontouchend="toggleCheckbox('stop');">Right</button></td></tr>
      <tr><td colspan="3" align="center"><button class="button" onmousedown="toggleCheckbox('backward');" ontouchstart="toggleCheckbox('backward');" onmouseup="toggleCheckbox('stop');" ontouchend="toggleCheckbox('stop');">Backward</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>
  • URL درخواست مربوط به هر دکمه در ادامه ذکر شده‌است:

حرکت به جلو

<ESP_IP_ADDRESS>/action?go=forward

حرکت به عقب

/action?go=backward

حرکت به چپ

/action?go=left

حرکت به راست

/action?go=right

توقف

/action?go=stop

وقتی هر دکمه‌ رها می‌کنید نیز باید یک درخواست توقف روی آدرس /action?go=stop ارسال شود تا ربات تنها زمانی که دکمه‌ای را فشار می‌دهید، حرکت کند.

مدیریت درخواست‌ها با دستورات شرطی

برای پاسخگویی به درخواست‌هایی که روی URLهای ذکر شده ارسال می‌شود، از دستورات شرطی استفاده می‌کنیم:

if(!strcmp(variable, "forward")) {
    Serial.println("Forward");
    digitalWrite(MOTOR_1_PIN_1, 1);
    digitalWrite(MOTOR_1_PIN_2, 0);
    digitalWrite(MOTOR_2_PIN_1, 1);
    digitalWrite(MOTOR_2_PIN_2, 0);
 }
 else if(!strcmp(variable, "left")) {
   Serial.println("Left");
   digitalWrite(MOTOR_1_PIN_1, 0);
   digitalWrite(MOTOR_1_PIN_2, 1);
   digitalWrite(MOTOR_2_PIN_1, 1);
   digitalWrite(MOTOR_2_PIN_2, 0);
}
else if(!strcmp(variable, "right")) {
   Serial.println("Right");
   digitalWrite(MOTOR_1_PIN_1, 1);
   digitalWrite(MOTOR_1_PIN_2, 0);
   digitalWrite(MOTOR_2_PIN_1, 0);
   digitalWrite(MOTOR_2_PIN_2, 1);
}
else if(!strcmp(variable, "backward")) {
   Serial.println("Backward");
   digitalWrite(MOTOR_1_PIN_1, 0);
   digitalWrite(MOTOR_1_PIN_2, 1);
   digitalWrite(MOTOR_2_PIN_1, 0);
   digitalWrite(MOTOR_2_PIN_2, 1);
}
else if(!strcmp(variable, "stop")) {
   Serial.println("Stop");
   digitalWrite(MOTOR_1_PIN_1, 0);
   digitalWrite(MOTOR_1_PIN_2, 0);
   digitalWrite(MOTOR_2_PIN_1, 0);
   digitalWrite(MOTOR_2_PIN_2, 0);
}

پس از اضافه کردن نام و رمز عبور شبکه وای فای، می‌توانیم برنامه را روی ماژول ESP32-CAM آپلود کنیم. آپلود کردن کد روی این برد کمی با بردهای دیگر مثل آردوینو یا NodeMCU متفاوت است پس بهتر است ابتدا به نحوه آپلود کد بپردازیم.

نحوه آپلود برنامه بر روی ماژول ESP32-CAM

با این فرض که برنامه آردوینو را نصب داریم، در این مرحله پس از انجام اتصالات گفته شده، باید افزونه ESP32 را روی IDE آردوینو نصب کنیم که برای این کار از تب File، گزینه Preferences را باز می‌کنیم.

انتخاب گزینه  Preferences از منوی File برای نصب افزونه ESP32
  • آدرس زیر را در قسمت Additional Board Manager URLs اضافه می‌کنیم و پنجره Preferences را با کلیک روی دکمه OK می‌بندیم.
https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
انتخاب و کپی آدرس نوشته شده در بخش Additional Boards Manager URLs

توجه: اگر قبلا آدرس دیگری (مثل آدرس افزونه ESP8266) در این بخش وارد کرده‌اید، برای جدا کردن URLها کافیست بین آنها یک کاما (,) قرار دهید. برای مثال:

https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json, http://arduino.esp8266.com/stable/package_esp8266com_index.json
  • اکنون Boards Manager را از مسیر Tools > Board > Boards Manager باز کنید.
انتخاب گزینه Boards Manager
  • در قسمت جستجو واژه ESP32 را تایپ و از میان موارد ظاهر شده، ESP32 by Espressif Systems را نصب کنید.
نصب ESP32 by Espressif Systems
  • پس از چند دقیقه (بسته به سرعت اینترنت) این پکیج نصب می‌شود.
کامل شدن مرحله نصب ESP32 by Espressif Systems

حال نرم افزار ما آماده است.

  • سپس برای آپلود برنامه، ابتدا برد ESP32-CAM خود را توسط یک ماژول مبدل USB به سریال (ماژول مبدل مدل FT232RL) به کامپیوتر متصل کنید. بدین منظور اتصالات خود را مطابق تصویر زیر انجام دهید:
نحوه اتصال ماژول ESP32-CAM به مبدل USB به سریال

مبدل FT232RL یک جامپر دارد که اجازه می‌دهد بین ولتاژ های 3.3 و 5 ولت یکی را انتخاب کنید. اطمینان حاصل کنید که این جامپر روی 5V قرار گرفته است.

توجه: ممکن است ترتیب پین‌های ماژول مبدلی که استفاده می‌کنید با این تصویر متفاوت باشد، در این صورت جای نگرانی نیست و فقط باید به نام پین‌ها توجه کرده و آنها را به درستی متصل کنید.

توجه: لازم است پین GPIO0 هنگام آپلود برنامه به GND متصل شود.

پین‌های ماژول مبدل USB به سریال FT232RL

  اتصالات مبدل به ماژول ESP32 در جدول زیر ذکر شده‌اند:

ESP32-CAMFTDI Programmer
GNDGND
5VVCC (5V)
U0RTX
U0TRX
GPIO 0GND
  • در این مرحله در برنامه آردوینو، از قسمت Tools > Board، برد AI-Thinker ESP32-CAM را انتخاب و سپس از بخش Tools > Port پورتی که مبدل USB به سریال به آن متصل شده را انتخاب نمایید.

اکنون می‌توانیم روی دکمه آپلود کلیک کنیم:

ایکون دکمه آپلود برنامه
  • پس از کامپایل برنامه، عبارت …Connecting در پنجره دیباگ ظاهر می‌شود. در این مرحله باید دکمه ریست (RST) روی ماژول را یک بار فشار دهیم.
پیام در حال اتصال در پنجره دیباگ برنامه
دکمه ریست روی ماژول ESP32-CAM
  • پس از چند ثانیه برنامه با موفقیت آپلود می‌شود:
نمایش پیام "آپلود انجام شد" در IDE
  • حال نیاز است پایه GPIO0 را از GND جدا کنیم و یک بار دیگر دکمه ریست روی برد را فشار دهیم تا برنامه اجرا شود.

تست برنامه

پس از آپلود برنامه، شما نیاز دارید تا برنامه آپلود شده را تست کنید و مطمئن شوید به‌درستی عمل می‌کند، برای این‌کار سریال مانیتور را باز کنید تا آدرس IP ماژول را ببینید.

نمایش آدرس IP برنامه‌ی آپلود شده برروی ماژول ESP32-CAM

همان‌طورکه در تصویر بالا مشاهده می‌کنید آدرس IP برد ما 192.168.1.87 است. در این زمان یک مرورگر باز و آدرس IP را در آن وارد کنید. حال باید وب سرور را مشاهده نمایید:

وارد شدن به وب سرور کنترل از راه دور ربات ماشینی با وارد نمودن آدرس IP در مرورگر

بر روی دکمه‌ها کلیک کنید و با مشاهده سریال مانیتور، بررسی کنید که ارسال دستورات بدون تأخیر و قطعی انجام می‌شود.

صفحه سریال مانیتور پس از چک کردن دکمه‌ها در وب سرور

چنانچه همه دکمه‌ها به خوبی کار کردند، وقت آن است که مدار ربات ماشینی را کامل ببندیم.

تکمیل مدار و ساخت ربات دوربین دار با ماژول ESP32-CAM

پس از سر هم کردن شاسی ربات، می‌توانید با استفاده از شماتیک زیر سیم‌بندی مدار را طبق مراحل گفته‌شده در ادامه انجام دهید.

ابتدا لازم است ESP32-CAM را طبق تصویر به درایور موتور متصل کنید که برای ساخت مدار می‌توانید از یک برد بورد کوچک یا یک بورد سوراخ دار استفاده کنید.

نحوه سیم کشی و اتصالات مدار ربات دوربین دار با ماژول ESP32-CAM و درایور موتور

جدول زیر نحوه اتصال ماژول ESP32-CAM و درایور موتور را نشان می‌دهد:

L298N Motor DriverESP32-CAM
IN1GPIO 14
IN2GPIO 15
IN3GPIO 13
IN4GPIO 12

همان‌طورکه می‌بینید ما همه این اتصالات را روی یک بورد سوراخ دار کوچک انجام دادیم.

بورد سوراخ دار کوچک برای اتصال ماژول ESP32-CAM
  • سپس موتورها را به ترمینال‌های ماژول درایور موتور متصل کنید.

توجه: پیشنهاد می‌شود همان‌طورکه در شماتیک نشان داده شده، یک خازن عدسی 100 نانوفاراد هم روی ترمینال‌های هر موتور لحیم کنید. این خازن پرش‌های ولتاژ را کاهش می‌دهد. همچنین می‌توانید یک کلید در مسیر سیم قرمزی که از ماژول پاوربانک خارج می‌شود قرار دهید و با آن تغذیه مدار را خاموش و روشن کنید.

  • در نهایت، تغذیه را توسط ماژول پاور بانک متصل کنید. برای این کار نیاز است یک سیم با یک سر micro USB را بریده و از سیم‌های مثبت و منفی آن استفاده کنید. ما در این پروژه تغذیه ESP32-CAM و موتورها را از منبع یکسان تأمین کردیم و نتیجه خوب و مطلوبی هم گرفتیم.

توجه: همان‌طورکه اشاره شد موتورها جریان تقریباً زیادی نیاز دارند، بنابراین اگر احساس می‌کنید ربات شما به کندی حرکت می‌کند، شاید نیاز باشد از منبع تغذیه جداگانه‌ای برای موتورها استفاده کنید.

در نهایت ربات ماشینی دوربین دار ما به شکل زیر درمی‌آید:

ربات ماشینی دوربین دار ساخته شده با استفاده از ماژول ESP32-CAM

نمایش عملکرد ربات ماشینی با کنترل از راه دور

پس از اینکه سیم بندی و اتصالات ربات را تکمیل کردید، به مرورگر خود بازگردید و آدرس IP را وارد کنید. حال باید بتوانید ربات ماشینی خود را کنترل کنید و تصویر دوربین را نیز مشاهده کنید. این وب سرور روی موبایل یا دسکتاپ به خوبی کار می‌کند.

استفاده از وب سرور برای کنترل ربات ماشینی و حرکت دادن آن

توجه: این وب سرور را در هر لحظه، فقط روی یک سیستم می‌توانید باز کنید.

جمع بندی

در این آموزش آموختیم چگونه یک ماشین کنترلی دوربین دار بسازیم و از طریق یک وب سرور، آن را از راه دور کنترل کنیم. شما می‌توانید این پروژه را بهبود دهید، مثلاً برنامه را طوری تغییر دهید که ESP32-CAM در مد هات‌اسپات کار کند. بنابراین ربات هر جایی باشد می‌تواند یک شبکه وای فای بسازد و هر وسیله نزدیک آن بتواند به شبکه متصل شده و ربات را کنترل کند.

درپایان امیدواریم با انجام این پروژه جالب با ماژول ESP32-CAM، به نتیجه دلخواه خود رسیده باشید، اما درصورتی که در حین مطالعه یا ساخت این ربات دوربین دار دچار مشکل شدید یا سوالی ذهن شما را درگیر خود کرد، جای هیچ نگرانی نیست چراکه تیم کارشناسان مجموعه روبوایکیو همیشه آماده پاسخگویی به سوالات شما عزیزان هستند. به همین منظور در بخش دیدگاه این صفحه، نظرات و سوالات خود را با ما درمیان بگذارید و پاسخ خود را در اسرع وقت دریافت کنید.

مقالات مشابه

۲۹ دیدگاه. Leave new

  • علیرضا ممویی
    فروردین ۲۵, ۱۴۰۲ ۱۹:۴۸

    سلام
    توسط شیلد ESP32CAM برنامه اصلآ آپلود نشد پس به آدرس
    https://www.electroniclinic.com/esp32-cam-esp32-camera-programming-using-arduino-issues-fixed/
    رفته و توسط arduino uno به عنوان پروگرامر. برنامه BLINK آپلود شد و کار کرد اما برنامه شما آپلود شد ولی پس از قطع اتصال پایه ها ی gnd و IO0 و زدن دکمه RST روی ESP32CAM آدرس IP مربوطه به دست نمی آیدو خطای زیر می آید
    ets Jul 29 2019 12:21:46
    rst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
    configsip: 0, SPIWP:0xee
    clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
    mode:DIO, clock div:2
    load:0x3fff0018,len:4
    load:0x3fff001c,len:1216
    ho 0 tail 12 room 4
    load:0x40078000,len:10944
    load:0x40080400,len:6360
    entry 0x400806b4
    Camera init failed with error 0x20004

    پاسخ
    • درود بر شما علیرضا عزیز
      به نظر میرسه ارور به این خاطر هست که ماژول شما به وای فای متصل نمیشود. لطفا بررسی کنید که اسم و رمز شبکه وای فای رو به درستی وارد میکنید.

      پاسخ
  • سلام
    از نظر اسم و رمز شبکه وای فای درست وارد میکنم.داداشم یک دوربین لامپی وای فای دار قبلآ خریده که توسط نرم افزار مربوط به اون تصویر دیده شده و جهت دوربین کنترل می شود, قبلآ درست کار میکرد اما چند وقتیست که به وای فای وصل نمیشود.داداشم میگه که بعد از قضیه تحریم IP وسایل خارجی این مشکل پیش اومده.آیا درست است یا نه?
    تشکر

    پاسخ
    • درود برشما دوست عزیز
      در پاسخ به سوالتون باید بگیم که، خیر، ما اصلاً تو این پروژه از اینترنت استفاده نکردیم و با شبکه محلی کار میکنیم. به‌علاوه این مقاله طبق روش ذکر شده، تست شده. همانطور که عرض شد ابتدا اتصال وای فای رو بررسی کنید. برای مثال در منوی مودم، یا تنظیمات هات‌اسپات گوشی موبایل می‌تونید دستگاه‌های متصل رو ببینید و چک کنید که ESP32 در بین اونها هست یا خیر.
      موفق باشید 🙂

      پاسخ
    • محمدرضا پوشیده
      شهریور ۱۰, ۱۴۰۲ ۰۶:۵۸

      با سلام
      در تنظیمات مودم خانگی تان Security Mode را wpa2-psk انتخاب فرمایید.

      پاسخ
  • سلام ببخشید میشه این ماژول رو با۳.۷ولت راه اندازی کرد

    پاسخ
    • سلام و درود برشما هادی عزیز
      اصولاً تغذیه پیشنهادی ماژول esp32-cam بازه ۴.۷۵ تا ۵.۲۵ ولت است. اما احتمال داره بتونید با ۳.۷ ولت هم راه‌اندازی کنید که نیاز به تست دارد.

      پاسخ
  • سلام و خسته نباشید
    ممنون از سایت خوبتون و اموزش های مفید که به من خیلی کمک کردن
    لطفاً اموزش راه اندازی و فیلم برداری با ماژول دوربین رنگی ov7670 رو هم بزارید.
    ممنون

    پاسخ
    • سلام بنیامین عزیز
      خیلی ممنونیم ازت که مطالب ما رو دنبال می‌کنی، حتماً این آموزش رو در آینده میذاریم تو لیست موضوعاتمون.

      پاسخ
  • سلام میشه فیلم و تصویر رو توسط بلوتوث به موبایل انتقال داد و یک روبات کنترلی ساخت؟
    ممنان

    پاسخ
    • درود بر شما علیرضا جان
      امکانش هست اما همونطور که احتمالا تجربه کرده‌اید، بلوتوث سرعت مناسبی برای انتقال تصویر و ویدیو نداره. بنابراین در حالت عادی بلوتوث نتیجه خوبی نخواهد داد.

      پاسخ
  • سلام و ممنون از آموزش خوبتون آیا میشه برنامه رو جوری تغییر داد که به اینترنت وصل بشه و مثلا بفرستیمش تو خیابون و از توی خونه اون رو کنترل کنیم حالا برای اینکه ربات رو دزد نبره هم فکری کرد

    پاسخ
    • درود برشما محمدجواد عزیز
      برای این کار نیاز هست یک سرور تهیه کنید و اطلاعات را روی سرور ارسال و دریافت کنید. بنابراین کل برنامه دچار تغییر میشه و برنامه‌نویسی سمت سرور هم به پروژه اضافه میشه که حقیقتا به سادگی عوض کردن چند خط کد نیست و آموزش مجزایی نیاز داره.

      پاسخ
  • سلام میگم متور ها با ولتاژ ۵ کار میکنند

    پاسخ
  • محمد اورک
    آذر ۶, ۱۴۰۲ ۱۲:۴۶

    درود بر شما.
    چطور میشه کد رو جوری تغییر داد تا ماشین با کلید های کیبورد هم حرکت کنه؟

    پاسخ
    • سلام محمد عزیز
      برای انجام این کار تنها کافی است دو تابع زیر رو به تگ script اضافه کنید:
      document.addEventListener(‘keydown’,function(event){
      var pressedkey = event.key;
      if(pressedkey==”ArrowUp”){
      toggleCheckbox(‘forward’);
      }
      if(pressedkey==”ArrowDown”){
      toggleCheckbox(‘backward’);
      }
      if(pressedkey==”ArrowLeft”){
      toggleCheckbox(‘left’);
      }
      if(pressedkey==”ArrowRight”){
      toggleCheckbox(‘right’);
      }
      });
      document.addEventListener(‘keyup’,function(event){
      toggleCheckbox(‘stop’);
      });
      و تگ زیر را بعد از تگ h1 قرار بدهید:
      کد: 

<div id=

      توابع addEventListener هنگام فشرده شدن و رها کردن کلیدهای کیبورد اجرا میشوند. در تابع اول مشخص کردیم وقتی هر یک از کلیدهای جهت نما فشرده شود، تابع toggleCheckbox با دستور متناسب با کلید فراخوانی شود. در تابع دوم نیز وقتی هیچ کلیدی فشرده نشده تابع toogleCheckbox با دستور stop فراخوانی می‌شود.

      پاسخ
  • سلام وقت بخیر
    بسیار ممنونم از اموزش بسیار کامل تون
    ببخشید فقط یه نکته با وایفای گوشی چند متر برد دارد در فضای کاملا باز و در فضایی که مثلا یع دیوار گچی بینشون باشه

    پاسخ
    • سلام درود خدمت شما محمد عزیز
      برد ماژول مشابه هات اسپات گوشی موبایل هست. با وجود دیوار بین ۵ تا ۱۰ متر و در فضای باز تا ۵۰ متر میتونید در نظر بگیرید.

      پاسخ
  • سلام، ارور brownout detector was triggered رو دارم در سریال مانیتور، با تغذیه خارجی این ارور رو رفع کردم ولی باز آی پی آدرس رو برام نمایش نمیده، ولی داخل هات اسپات موبایل که نگاه میکنم، esp32 cam متصل شده هستش.
    از بلند بودن طول کابل هم میشه؟ به نظرتون از کابل mini usb کوتاه تر استفاده کنم مشکل حل میشه؟
    ممنون

    پاسخ
    • درود برشما دوست و همراه عزیز
      بنده تجریه‌ای در خصوص این ارور ندارم اما ارور bownout مربوط به تغذیه است. طبیعتاً کابل بلند یا بی کیفیت می‌تواند ایجاد مشکل کند. همچنین بهتر است تغذیه ۵ ولت ماژول را از یک منبع خارجی مطمئن تامین کنید. می‌توانید ولتاژ تغذیه ماژول را با استفاده از مولتی‌متر یا اسیلوسکوپ دنبال کنید.
      موفق باشید 🙂

      پاسخ
  • سلام
    موقع آپلود برنامه در ماژول این خطا رو نشان می ده مشکلش چیه؟
    Sketch uses 844637 bytes (26%) of program storage space. Maximum is 3145728 bytes.
    Global variables use 55064 bytes (16%) of dynamic memory, leaving 272616 bytes for local variables. Maximum is 327680 bytes.
    esptool.py v4.5.1
    Serial port COM9

    A fatal error occurred: Could not open COM9, the port doesn’t exist
    Failed uploading: uploading error: exit status 2

    پاسخ
    • سلام یاسمین عزیز
      این ارور مربوط به این است که پورت COM را داخل برنامه آردوینو به درستی انتخاب نکرده‌اید. ابتدا از device manager ویندوز، شماره پورتی که ماژول مبدل سریال به آن متصل است را پیدا کنید و سپس همان پورت را در آردوینو انتخاب کنید.

      پاسخ
      • توی آردینو فقط یک پورت برای من میاد که Com هستش و منیجر کامپیوتر رو هم‌چک کردم و همه این هارو درست انجام دادم اما بازم ارور میگیره

        پاسخ
        • درود بر شما یاسمین عزیز
          اگه مطمئن هستید همه اقدامات را صحیح و کامل انجام می دهید؛ یا پورت USB کامپیوتر شما مشکلی دارد (می‌تونید با پورت‌های مختلف تست کنید) یا مبدل USB به سریال ایراد دارد (برای بررسی سلامت مبدل، می‌توانید RX و TX مبدل را به هم وصل کنید و در نرم‌افزارهای ارتباط سریال مانند SSCOM، HERCULES یا سایر نرم‌افزارهای مشابه ارسال و دریافت اطلاعات رو بررسی کنید.)

          پاسخ
          • سلام و عرض خسته نباشید خیلی ممنون از آموزش های خوبتون تمام وسایل های این پروژه را از فروشگاه خودتون خریدم که کیفیت خیلی خوبی داشت با راهنمایی های شما تونستم ماژول esp32 cam رو راه اندازی کنم
            فقط من هم موقع آپلود کد به همین ارور برخوردم اما وقتی GND رو به پایه GND کنار ۵V وصل کردم درست شد

          • درود بر شما هادی عزیز
            سپاس از اشتراک گذاری تجربتون 🙂

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

Fill out this field
Fill out this field
لطفاً یک نشانی ایمیل معتبر بنویسید.
You need to agree with the terms to proceed

پر بازدید ترین مقالات