ESP32 CAM Car – Điều khiển robot từ xa qua Web Server
Trong dự án ESP32 CAM Car lần này, cùng tìm hiểu cách chế tạo và lập trình robot Car điều khiển từ xa qua WiFi với ESP32 CAM. Chúng ta có thể dùng Web Server để hiển thị video về khung cảnh mà robot “nhìn thấy”. Từ đó, bạn có thể điều khiển robot từ xa và quan sát những gì đang có xung quanh nó, kể cả khi nó nằm ngoài tầm nhìn.
Lưu ý: Dự án ESP32 CAM Car này yêu cầu mạch phải có 4 GPIO điều khiển động cơ DC. Do đó, bạn có thể dùng các loại mạch ESP32 CAM có sẵn 4 GPIO như ESP32 CAM AI-Thinker hoặc TTGO TJournal.
Tổng quan dự án ESP32 CAM Car
Trước khi đi vào hướng dẫn chi tiết, mời bạn cùng xem qua các tính năng và thành phần quan trọng trong việc chế tạo robot với ESP32 trong phần này:
WiFi
Robot sẽ được điều khiển từ xa qua WiFi, thông qua mạch ESP32-CAM của bạn.
Trong dự án này, IoTZone sẽ tạo một giao diện Web Server để điều khiển robot, bạn có thể truy cập vào Web Server từ bất kỳ thiết bị nào trong mạng cục bộ.
Trên Web Server cũng hiển thị video về những gì robot “nhìn thấy”. Để video có tốc độ truyền phát tốt nhất, bạn nên cân nhắc sử dụng ESP32-CAM có ăng ten bên ngoài nhé!
Cách điều khiển robot qua Web Server
Trên Server chúng ta tạo sẽ có 5 nút nhấn khác nhau, đại diện cho việc di chuyển 4 hướng và dừng lại. Khi bạn nhấn vào nút nào, robot sẽ hoạt động tương ứng:
Mình có tạo thêm nút dừng, vì chúng sẽ giúp ích trong trường hợp ESP32 không nhận được lệnh dừng, dù cho bạn đã thả nút điều khiển di chuyển.
Bộ khung robot
Bạn cần có 1 bộ khung robot phù hợp, gồm thân robot và bánh xe. Đa số các cửa hàng trực tuyến trên mạng hiện nay đều có bán bộ khung này, chúng có giá khá rẻ và cũng rất dễ lắp ráp.
Bạn có thể sử dụng bất kỳ bộ khung nào (không nhất thiết là bộ giống y chang của mình), miễn sao chúng có 2 động cơ DC là được:
L298N Motor Driver
Đây là module giúp chúng ta dễ dàng điều khiển động cơ DC trong các dự án chế tạo robot với ESP32. Bạn có thể điều khiển tốc độ và hướng của 2 động cơ:
Trên Website IoTZone mình đã có bài viết khá chi tiết về cách hoạt động và cách sử dụng trình điều khiển động cơ L298N rồi, nên mình sẽ không nhắc lại ở đây. Bạn có thể xem qua bài viết đó ở link dưới để hiểu hơn về cách dùng L298N Motor Driver nha:
Nguồn điện
Để đơn giản hóa dự án ESP32 CAM Car, chúng ta sẽ cấp nguồn cho robot (động cơ) và ESP32 bằng cùng 1 nguồn điện. Mình dùng bộ sạc dự phòng và chúng hoạt động khá tốt.
Lưu ý: Động cơ tiêu thụ lượng điện cao, do đó nếu thấy robot di chuyển chập chờn hoặc không ổn định, bạn có thể sử dụng nguồn điện bên ngoài cho động cơ. Khi đó đồng nghĩa với việc bạn phải dùng 2 nguồn điện khác nhau, 1 cái cấp nguồn cho ESP32 và 1 cái cấp nguồn cho động cơ DC.
Không lan man nữa, bây giờ hãy cùng vào phần hướng dẫn chính về dự án ESP32 CAM Car nào!
Chuẩn bị
- ESP32 CAM AI Thinker có ăng ten bên ngoài
- L298N Motor Driver
- Bộ khung robot
- Sạc dự phòng hoặc nguồn điện 5V khác
- Prototyping circuit board (tùy chọn)
Chương trình lập trình
Bạn copy đoạn code sau vào Arduino IDE của bạn, sau đó thay đổi thông tin mạng WiFi thành của bạn rồi nạp vào ESP32. Code sẽ hoạt động ngay lập tức!
#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" // Thay thế thành mạng WiFi của bạn 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() { }
Trong đoạn code ESP32 CAM Car trên, bạn thay đổi thông tin mạng WiFi ở đoạn sau:
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";
Giải thích chương trình
Khai báo 4 chân GPIO điều khiển động cơ, mỗi động cơ được điều khiển bởi 2 chân:
#define MOTOR_1_PIN_1 14
#define MOTOR_1_PIN_2 15
#define MOTOR_2_PIN_1 13
#define MOTOR_2_PIN_2 12
Khi nhấp vào các nút trên Web Server, bạn sẽ đưa ra yêu cầu trên một URL khác:
<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>
Dưới đây là các yêu cầu được thực hiện, tùy thuộc vào nút mà bạn nhấn trên Web Server:
- Forward (đi tới): <ESP_IP_ADDRESS>/action?go=forward
- Backward (đi lùi): /action?go=backward
- Left (rẽ trái): /action?go=left
- Right (rẽ phải): /action?go=right
- Stop (dừng lại): /action?go=stop
Khi bạn nhả nút, một yêu cầu sẽ được thực hiện trên URL /action?go=stop. Do đó, robot chỉ di chuyển khi bạn nhấn nút.
Và dưới đây là đoạn code về các hành động của robot khi nhận được những yêu cầu trên URL đó. Mình sử dụng câu lệnh if else:
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);
}
Chạy thử code
Sau khi thay đổi thông tin đăng nhập mạng, bạn hãy upload đoạn code ESP32 CAM Car trên vào mạch của mình.
Sau đó, bạn mở Serial Monitor để lấy địa chỉ IP của mạch ESP32.
Kế tiếp, mở trình duyệt và nhập địa chỉ IP của ESP32, một trang Web sẽ xuất hiện để bạn điều khiển ESP32 CAM Car:
Bạn hãy thử nhấn các nút trên Web Server rồi nhìn vào màn hình Serial Monitor, để xem có bị lag gì không, liệu chúng có nhận lệnh kịp thời và không gặp sự cố nào khác không.
Nếu mọi thứ hoạt động bình thường, chúng ta sẽ chuyển qua bước tiếp theo trong chế tạo robot với ESP32 – Lắp ráp mạch điện.
Lắp ráp mạch điện phần cứng
Sau khi lắp xong khung robot, bạn có thể kết nối dây trong hệ thống như sơ đồ sau:
Đầu tiên bạn cần kết nối ESP32 CAM với L298N Motor Driver như sơ đồ. Dưới đây là chi tiết cách kết nối các cổng:
L298N Motor Driver | ESP32-CAM |
IN1 | GPIO 14 |
IN2 | GPIO 15 |
IN3 | GPIO 13 |
IN4 | GPIO 12 |
Chúng ta có thể kết nối tất cả trên một tấm ván nhỏ như hình bên dưới, để chúng trông gọn, đẹp mắt và chắc chắn hơn:
Sau đó, bạn nối dây của từng động cơ vào terminal block của nó.
Ghi chú: Mình khuyên bạn nên hàn 0.1 uF ceramic capacitor vào cực dương và cực âm của mỗi động cơ, điều đó giúp làm giảm các xung điện áp. Ngoài ra, bạn có thể hàn công tắc trượt vào dây màu đỏ lấy từ sạc dự phòng (để chúng ta có thể bật và ngắt nguồn điện).
Cuối cùng, bạn cấp nguồn cho hệ thống bằng sạc dự phòng như sơ đồ kết nối ESP32 CAM Car trên. Trong dự án này, cả ESP32 và động cơ đều được cấp từ 1 nguồn và chúng hoạt động khá ổn.
Lưu ý: Động cơ tiêu thụ khá nhiều điện, nên nếu bạn thấy robot chạy không đủ nhanh hoặc không ổn định, bạn nên cân nhắc dùng nguồn điện ngoài cho động cơ. Điều này đồng nghĩa việc bạn cần 2 nguồn pin khác nhau, 1 cái cho ESP32 và 1 cái cho động cơ. Bạn có thể dùng bộ pin 4xAA để cấp nguồn cho động cơ.
Dưới đây là hình ảnh robot trong dự án ESP32 CAM Car lần này, sau khi lắp xong:
Ngoài ra, bạn đừng quên dùng loại ESP32 CAM có gắn ăng ten ben ngoài nhé! Nếu không thì video hiển thị trên Web Server sẽ khá là giật lag đó.
Demo dự án ESP32 CAM Car
Mở trình duyệt, nhập địa chỉ IP của ESP32 CAM và điều khiển robot thôi nào! Theo mình thấy thì Web Server hoạt động ổn trên cả điện thoại lẫn laptop / PC.
Lưu ý: Bạn chỉ có thể mở 1 Web Server trên cùng 1 thiết bị tại 1 thời điểm.
Lời kết
Trên đây là các hướng dẫn chi tiết về chủ đề ESP32 CAM Car – Điều khiển robot từ xa qua Web Server, có gửi video nhận diện từ robot lên trang Web. Đây là một dự án khá thú vị phải không nào? Chúc bạn thành công nhé!
Ngoài ra, trên IoTZone cũng có một số bài viết khác về chủ đề ESP32 CAM, bạn có thể tham khảo nếu thích nhé!
IoTZone – Chuyên cung cấp thiết bị điện tử & tài liệu cho Makers
- Website: https://www.iotzone.vn/
- Fanpage: https://www.facebook.com/Iotzonemaker
- SDT: 0364174499
- Zalo: https://zalo.me/0364174499