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


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

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

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

قطعات لازم
اکنون که با قطعات ساخت ماشین ربات دوربین دار آشنا شدید، نوبت به ارائه قطعات زیر می رسد که برای دسترسی سریعتر لینک آن را در اختیار شما قرار داده ایم:
نحوه رانندگی با ماشین ربات با دوربین
همانطور که می بینید، ما از وب سرور یا مرورگر تلفن همراه خود برای کنترل از راه دور این ربات کنترلی استفاده خواهیم کرد. این وب سرور دارای 5 دکمه است: رو به جلو، بازگشت، به سمت چپ، درست است، واقعی و خودت را متوقف کن. تا زمانی که دست ما روی یکی از دکمه ها باشد، ربات در جهت مربوطه حرکت می کند و با رها شدن دکمه، ربات می ایستد. با این حال، گاهی اوقات ESP32 ممکن است وقتی دست خود را از روی دکمه بردارید، دستور توقف را دریافت نکند. در این مواقع داشتن دکمه توقف به ما کمک می کند تا ربات را متوقف کنیم.

کد آردوینو
پس از یادگیری نحوه کار اجزای این ربات ماشینی و مشخص کردن مسیر ارتباطی با وب سرور، لازم است برنامه زیر را در Arduino IDE کامپیوتر خود کپی کنید تا به مراحل بعدی یعنی. بارگذاری برنامه در ماژول ESP32.
اگر هنوز برنامه Arduino 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 = "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 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: %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();
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 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=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 را باز می کنیم.

- آدرس زیر را در بخش URLهای مدیر تابلوی اضافی اضافه کنید و با کلیک بر روی دکمه OK پنجره تنظیمات را ببندید.
https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json

توجه: اگر قبلاً آدرس دیگری (مانند آدرس پلاگین 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 باز کنید.

- کلمه ESP32 را در کادر جستجو تایپ کنید و ESP32 by Espressif Systems را از آیتم های ظاهر شده نصب کنید.

- پس از چند دقیقه (بسته به سرعت اینترنت شما) این بسته نصب می شود.

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

مبدل FT232RL دارای جامپر است که به شما امکان می دهد بین 3.3 ولت و 5 ولت یکی را انتخاب کنید. مطمئن شوید که این جامپر روی 5 ولت تنظیم شده است.
توجه: ممکن است ترتیب پین ماژول مبدلی که استفاده می کنید با این تصویر متفاوت باشد، در این صورت جای نگرانی نیست و فقط باید به نام پین ها دقت کنید و آنها را به درستی وصل کنید.
توجه: لازم است هنگام آپلود برنامه، پین GPIO0 را به GND متصل کنید.

اتصالات مبدل به ماژول ESP32 در جدول زیر آمده است:
ESP32-CAM | برنامه نویس FTDI |
GND | GND |
5 ولت | VCC (5 ولت) |
U0R | TX |
U0T | RX |
GPIO 0 | GND |
- در این مرحله از برنامه آردوینو، برد AI-Thinker ESP32-CAM را از قسمت Tools > Board انتخاب کنید و سپس از قسمت Tools > Port پورتی که مبدل USB به سریال به آن متصل است را انتخاب کنید.
اکنون می توانیم روی دکمه آپلود کلیک کنیم:

- پس از کامپایل شدن برنامه، عبارت …Link در پنجره اشکال زدایی ظاهر می شود. در این مرحله باید دکمه ریست (RST) روی ماژول را یک بار فشار دهید.


- پس از چند ثانیه، برنامه با موفقیت آپلود می شود:

- حالا باید پین GPIO0 را از GND جدا کنیم و دوباره دکمه ریست روی برد را فشار دهیم تا برنامه شروع شود.
تست کردن برنامه
پس از آپلود برنامه، باید برنامه آپلود شده را تست کنید و مطمئن شوید که درست کار می کند، برای این کار مانیتور سریال را باز کنید تا آدرس IP ماژول را ببینید.

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

روی دکمه ها کلیک کنید و با دیدن مانیتور سریال بررسی کنید که ارسال دستورات بدون تاخیر و وقفه انجام شود.

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

جدول زیر نحوه اتصال ماژول ESP32-CAM و درایور موتور را نشان می دهد:
درایور موتور L298N | ESP32-CAM |
در 1 | GPIO 14 |
در 2 | GPIO 15 |
IN3 | GPIO 13 |
IN4 | GPIO 12 |
همانطور که می بینید، ما تمام این اتصالات را روی یک تخته پانچ کوچک انجام دادیم.

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

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

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