Hướng dẫn ESP32 45/55 - Máy chủ phát trực tuyến tùy chỉnh với điều khiển LED CAM-3 l Bộ học ESP32 của SunFounder
Trong hướng dẫn này, chúng ta sẽ khám phá cách thiết lập một máy chủ phát trực tuyến tùy chỉnh bằng bảng mở rộng ESP32 từ SunFounder. Dự án cho phép bạn phát video trực tiếp tới trình duyệt của mình đồng thời điều khiển đèn LED trực tiếp từ giao diện. Sự kết hợp các tính năng này mang lại trải nghiệm học tập thực hành với công nghệ IoT và web.
Chúng ta sẽ sử dụng khả năng Wi-Fi tích hợp của ESP32 để tạo một máy chủ web phát video và xử lý các lệnh điều khiển đèn LED. Dự án bao gồm lập trình, đấu dây và hiểu cách các thành phần tương tác với nhau. Nếu bạn muốn hiểu rõ hơn về cách thiết lập, hãy xem video tại (trong video ở 00:00).
Giải thích về Phần cứng
Các thành phần chính cho dự án này bao gồm vi điều khiển ESP32, mô-đun camera, đèn LED và điện trở. ESP32 là một vi điều khiển đa năng với Wi-Fi và Bluetooth tích hợp, rất phù hợp cho các ứng dụng IoT. Mô-đun camera cho phép chúng ta quay video, trong khi đèn LED cung cấp một thiết bị đầu ra đơn giản để điều khiển.
Đèn LED được kết nối qua một điện trở để giới hạn dòng điện, ngăn ngừa hư hỏng cho cả đèn LED và vi điều khiển. Thiết lập này sẽ cho phép chúng ta bật và tắt đèn LED thông qua giao diện web, thể hiện khả năng của ESP32 trong việc xử lý đầu vào và đầu ra qua mạng.
Chi tiết Bảng dữ liệu
| Nhà sản xuất | Espressif |
|---|---|
| Số bộ phận | ESP32-WROOM-32 |
| Điện áp Logic/IO | 3,3 V |
| Điện áp cung cấp | 3,0–3,6 V |
| Dòng điện đầu ra (mỗi kênh) | 12 mA |
| Dòng điện đỉnh (mỗi kênh) | 40 mA |
| Hướng dẫn tần số PWM | 1 kHz |
| Ngưỡng logic đầu vào | 0,2 V (thấp) / 0,8 V (cao) |
| Sụt áp / RDS(on) / bão hòa | 0,2 V (điển hình) |
| Giới hạn nhiệt | Nhiệt độ mối nối tối đa: 125 °C |
| Kiểu đóng gói | QFN48 |
| Ghi chú / biến thể | Có sẵn trong nhiều cấu hình khác nhau |
- Đảm bảo ESP32 được cấp nguồn bằng nguồn 3,3 V đã được điều chỉnh.
- Sử dụng điện trở giới hạn dòng điện (220 Ohm) với đèn LED để tránh hư hỏng.
- Duy trì các kết nối chính xác để tránh đầu vào trôi nổi.
- Kiểm tra thông tin đăng nhập Wi-Fi chính xác và phân biệt chữ hoa chữ thường.
- Sử dụng nguồn điện ổn định để có hiệu suất nhất quán.
- Cân nhắc tản nhiệt trong không gian kín.
Hướng dẫn Đấu dây

Để đấu dây ESP32 và đèn LED, hãy bắt đầu bằng cách kết nối chân dài hơn của đèn LED với một chân GPIO phù hợp, trong trường hợp này, chúng ta sẽ sử dụng chân 14. Chân ngắn hơn nên kết nối với đường nối đất trên bảng mạch breadboard của bạn. Tiếp theo, đặt một điện trở 220 Ohm nối tiếp với đèn LED, kết nối một đầu với chân GPIO (chân 14) và đầu kia với nối đất. Đảm bảo ESP32 được cấp nguồn chính xác, thông qua cổng micro USB hoặc bằng pin lithium 18650.
Đối với mô-đun camera, hãy đảm bảo kết nối các chân cần thiết theo mẫu camera bạn đang sử dụng, vì cách đấu dây có thể thay đổi đôi chút. ESP32 sẽ xử lý luồng video thông qua các khả năng tích hợp của nó, và việc điều khiển đèn LED sẽ được quản lý thông qua giao diện web mà chúng ta sẽ thiết lập trong mã.
Ví dụ Mã & Hướng dẫn Chi tiết
Chương trình bắt đầu bằng việc bao gồm các thư viện cần thiết và định nghĩa thông tin đăng nhập Wi-Fi. Bạn sẽ cần thay thế ssid và password bằng thông tin đăng nhập Wi-Fi thực tế của bạn để kết nối ESP32 với mạng của bạn.
const char* ssid = "SSID";
const char* password = "PASSWORD";
Tiếp theo, chúng ta định nghĩa chân LED và thiết lập cấu hình camera. Chân được sử dụng cho đèn LED được định nghĩa là LED_PIN, sẽ được sử dụng sau này trong mã để điều khiển trạng thái của đèn LED.
#define LED_PIN 14
pinMode(LED_PIN, OUTPUT);
Trong trình xử lý yêu cầu cho việc điều khiển đèn LED, chúng ta kiểm tra lệnh nhận được từ giao diện web. Tùy thuộc vào việc lệnh là "on" hay "off", chúng ta sử dụng digitalWrite(LED_PIN, 1); để bật đèn LED và digitalWrite(LED_PIN, 0); để tắt nó.
if(!strcmp(variable, "on")) {
Serial.println("ON");
digitalWrite(LED_PIN, 1);
}
else if(!strcmp(variable, "off")) {
Serial.println("OFF");
digitalWrite(LED_PIN, 0);
}
Logic này cho phép giao diện web giao tiếp hiệu quả với ESP32, cho phép điều khiển đèn LED theo thời gian thực dựa trên tương tác của người dùng. Mã đầy đủ được tải bên dưới bài viết để khám phá thêm.
Trình diễn / Điều gì sẽ xảy ra
Sau khi mọi thứ được thiết lập và mã được tải lên, bạn sẽ có thể truy cập địa chỉ IP của ESP32 trong trình duyệt web của mình. Video phát trực tiếp sẽ xuất hiện và bạn có thể điều khiển đèn LED bằng các nút trên giao diện. Nhấp vào "ON" sẽ làm sáng đèn LED, trong khi "OFF" sẽ tắt nó. Đảm bảo rằng ESP32 và máy tính của bạn được kết nối với cùng một mạng để đảm bảo hoạt động chính xác (trong video ở 12:30).
Mốc thời gian Video
- 00:00 Bắt đầu
- 1:51 Giới thiệu về dự án
- 2:31 Trang tài liệu
- 3:33 Giải thích về Đấu dây
- 5:08 Giải thích mã Arduino
- 13:28 Chọn Bảng ESP32 và cổng COM trong Arduino IDE
- 15:10 Trình diễn
/*********
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 the next variables with your SSID/Password combination
const char* ssid = "SSID";
const char* password = "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 33
#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 LED_PIN 14
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 ;
transform: rotate(180deg);
}
</style>
</head>
<body>
<h1>ESP32 CAMERA</h1>
<img src="" id="photo" >
<table>
<tr><td align="center"><button class="button" onmousedown="toggleCheckbox('on');" ontouchstart="toggleCheckbox('on');onmouseup="toggleCheckbox('on');" ontouchend="toggleCheckbox('on');">ON</button></td>
<td align="center"><button class="button" onmousedown="toggleCheckbox('off');" ontouchstart="toggleCheckbox('off');onmouseup="toggleCheckbox('off');" ontouchend="toggleCheckbox('off');">OFF</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, "on")) {
Serial.println("ON");
digitalWrite(LED_PIN, 1);
}
else if(!strcmp(variable, "off")) {
Serial.println("OFF");
digitalWrite(LED_PIN, 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(LED_PIN, 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() {
}
Common Course Links
Common Course Files
Tài nguyên & tài liệu tham khảo
-
Tài liệuESP32 Tutorial 45/55 - SunFounder doc page for Custom Streaming Serverdocs.sunfounder.com
Tập tin📁
Không có tệp nào khả dụng.