# ESP32CAM WiFi car **Repository Path**: jun-tian/esp32-cam-wi-fi-car ## Basic Information - **Project Name**: ESP32CAM WiFi car - **Description**: WiFi小车 - **Primary Language**: Arduino - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 15 - **Forks**: 4 - **Created**: 2022-02-17 - **Last Updated**: 2025-03-12 ## Categories & Tags **Categories**: Uncategorized **Tags**: ESP32CAM ## README # ESP32CAM WiFi car #### 介绍 WiFi小车 #### 软件架构 使用 ESP32-CAM 构建 Wi-Fi 遥控汽车机器人。您将能够使用Web服务器控制机器人,该服务器显示机器人"看到"的视频流。您可以远程控制您的机器人,即使它不在你的视线范围内。ESP32-CAM 将使用 Arduino IDE 进行编程。 ESP32-CAM 远程控制车载机器人 Web 服务器 Arduino IDE 电路板兼容性:此项目需要 4 个 GPIO 来控制直流电机。因此,您可以使用任何带有 4 个可用 GPIO 的 ESP32 相机板,如 ESP32-CAM Ai-Thinker 板。 #### 安装教程 在开始项目之前,我们将重点介绍用于构建机器人的最重要功能和组件。 无线网络 机器人将使用您的 ESP32-CAM 通过 Wi-Fi 进行控制。我们将创建一个基于Web的界面来控制机器人,可以在本地网络内的任何设备中访问。 该网页还显示了机器人"看到"的视频流。为了获得良好的视频流效果,我们建议使用带外部天线的 ESP32-CAM。 重要提示:如果没有外部天线,视频流会滞后,并且Web服务器控制机器人的速度非常慢。 ![输入图片说明](ESP32CAM-CARESP32-CAM-Remote-Controlled-Robot-Web-Server-Video-Streaming-2.jpg) 机器人控制 Web 服务器有 5 个控件:"前进"、"向后"、"向左"、"向右"和"停止"。 ESP32-CAM 远程控制机器人 Web 服务器 Arduino IDE 只要您按下按钮,机器人就会移动。当您松开任何按钮时,机器人就会停止。但是,我们包含了"停止"按钮,如果您释放按钮时 ESP32 未收到停止命令,该按钮非常有用。 您可以使用任何其他机箱套件,只要它带有两个直流电机即可。 [智能机器人底盘套件](https://s.click.taobao.com/hNoVmau) [Arduino套件ESP32-CAM(原厂,不带外置天线)](https://s.click.taobao.com/f3Mmaau), 带[外置天线](https://s.click.taobao.com/g2pVmau) [L298N 电机驱动器](https://s.click.taobao.com/vtDWmau) 控制直流电机的方法有很多种。我们将使用L298N电机驱动器,它提供了一种简单的方法来控制2个直流电机的速度和方向。 L298N 电机驱动器 直流 ESP32-CAM 我们不会解释L298N电机驱动器的工作原理。您可以阅读以下文章,了解有关 L298N 电机驱动器的深入教程: ESP32 带直流电机和 L298N 电机驱动器 – 控制速度和方向 为了保持电路简单,我们将使用相同的电源为机器人(电机)和 ESP32 供电。我们使用了移动电源/便携式充电器(就像用于为智能手机充电的充电器一样),效果很好。 便携式移动电源为 ESP32-CAM 机器人供电 注意:电机消耗大量电流,因此,如果您觉得机器人无法正常移动,则可能需要为电机使用外部电源。这意味着您需要两种不同的电源。一个用于为直流电机供电,另一个用于为 ESP32 供电。 所需部件 对于此项目,我们将使用以下部分: ESP32-CAM AI-Thinker,带外置天线 L298N 电机驱动器 机器人汽车底盘套件 移动电源或其他5V电源 原型电路板(可选) 测试代码 插入网络凭据后,您可以将代码上传到 ESP32-CAM 开发板。如果你不知道如何将代码上传到开发板,请按照下一其他教程操作 上传后,打开串行监视器以获取其IP地址。 ESP32-CAM 获取 IP 地址串行监视器 打开浏览器并键入 ESP IP 地址。类似的网页应加载: ESP32-CAM 网络服务器远程控制机器人 2演示 按下按钮并查看串行监视器,以查看它是否正在流式传输而没有延迟,以及它是否正在接收命令而不会崩溃。 ESP32-CAM 远程控制机器人串行监视器命令 如果一切正常,是时候组装电路了。 #### 代码 ``` #include "esp_camera.h" #include #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";//你的WiFi名称 const char* password = "REPLACE_WITH_YOUR_PASSWORD";//你的WiFi密码 #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( ESP32-CAM Robot

ESP32-CAM Robot

)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() { } ``` #### 使用说明 电路 组装机器人底盘后,您可以按照下一个原理图连接电路。 ![输入图片说明](ESP32CAM-CAR%E6%8E%A5%E7%BA%BF%E5%9B%BE.jpg) ESP32-CAM遥控机器人直流电机接线电路图 首先将 ESP32-CAM 连接到电机驱动器,如原理图所示。您可以使用迷你试验板或脱衣板来放置 ESP32-CAM 并构建电路。 下表显示了 ESP32-CAM 和 L298N 电机驱动器之间的连接。 L298N 电机驱动器 ESP32-CAM IN1 GPIO 14 IN2 GPIO 15 IN3 GPIO 13 IN4 GPIO 12 !!!这块需要测试好线序,不然控制会混乱,电机网页按钮,小车会“晕头乱跑”。 我们将所有连接组装在一个迷你条板上,如下所示。 ESP32-CAM 远程控制机器人跑道电路 之后,将每个电机连接到其接线端子。 注意:我们建议将0.1 uF陶瓷电容器焊接到每个电机的正极和负极,如图所示,以帮助平滑任何电压尖峰。此外,您可以将滑块开关焊接到来自移动电源的红线上。这样,您可以打开和关闭电源。 最后,使用移动电源供电,如图示意图所示。您需要剥离USB电缆。在这个例子中,ESP32-CAM和电机使用相同的电源供电,并且运行良好。 注意:电机消耗大量电流,因此,如果您觉得机器人的移动速度不够快,则可能需要为电机使用外部电源。这意味着您需要两种不同的电源。一个用于为直流电机供电,另一个用于为 ESP32 供电。您可以使用 4 AA 电池组为电机供电。当您获得机器人底盘套件时,您通常会获得一个用于4节AA电池的电池座。 不要忘记,您应该将外部天线与 ESP32-CAM 配合使用,否则 Web 服务器可能会非常慢。 在 ESP32-CAM IP 地址上打开浏览器,您应该能够控制您的机器人。Web服务器在笔记本电脑或智能手机上运行良好。 #### 参与贡献 #### 特技 使用 ESP32-CAM 控制直流电机与使用"常规"ESP32 控制直流电机相同。阅读本教程以了解更多信息:ESP32 带直流电机和 L298N 电机驱动器 – 控制速度和方向。 如果要在本地网络范围之外控制机器人,可以考虑将 ESP32-CAM 设置为接入点。这样,ESP32-CAM 就不需要连接到您的路由器,它会创建自己的 Wi-Fi 网络,附近的 Wi-Fi 设备(如智能手机)可以连接到它。