前言
本文专为零基础新手打造,从硬件选型与接线、开发环境准备、代码编写与编译到最终访问摄像头流,全程保姆级教程。基于 ESP-IDF v5.1.6 开发,完美适配 OV3660 摄像头与微软 Edge 浏览器,代码可直接复制使用,一次性解决「编译报错」「SOI 标记缺失」「Edge 无法解析流」等核心问题。
一、核心目标
用 ESP32-CAM 开发板 + OV3660 摄像头,搭建一个 MJPEG 视频流服务器,通过 WiFi 连接后,在浏览器中输入 ESP32 局域网 IP,即可实时查看摄像头画面。
二、硬件架构与选型
1. 核心硬件清单(新手必买,无兼容问题)
| ESP32-CAM 开发板 | 搭载 ESP32-WROOM-32 核心,无 PSRAM 版本即可 | 核心控制单元,负责 WiFi 通信、摄像头驱动、HTTP 服务器运行 |
| OV3660 摄像头模块 | 适配 ESP32-CAM 引脚,自带排线 | 图像采集,输出 JPEG 格式图像 |
| ESP32-CAM 专用烧录座 | 适配 ESP32-CAM 引脚,支持一键烧录 | 替代 USB 转 TTL,简化烧录流程,无需手动接 IO0 |
| USB 数据线 | Micro USB 接口,适配烧录座 | 给烧录座供电,传输烧录数据 |
2. 硬件架构原理
#mermaid-svg-4BmQsvjQuGGxEK48{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-4BmQsvjQuGGxEK48 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-4BmQsvjQuGGxEK48 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-4BmQsvjQuGGxEK48 .error-icon{fill:#552222;}#mermaid-svg-4BmQsvjQuGGxEK48 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-4BmQsvjQuGGxEK48 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-4BmQsvjQuGGxEK48 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-4BmQsvjQuGGxEK48 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-4BmQsvjQuGGxEK48 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-4BmQsvjQuGGxEK48 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-4BmQsvjQuGGxEK48 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-4BmQsvjQuGGxEK48 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-4BmQsvjQuGGxEK48 .marker.cross{stroke:#333333;}#mermaid-svg-4BmQsvjQuGGxEK48 svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-4BmQsvjQuGGxEK48 p{margin:0;}#mermaid-svg-4BmQsvjQuGGxEK48 .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-4BmQsvjQuGGxEK48 .cluster-label text{fill:#333;}#mermaid-svg-4BmQsvjQuGGxEK48 .cluster-label span{color:#333;}#mermaid-svg-4BmQsvjQuGGxEK48 .cluster-label span p{background-color:transparent;}#mermaid-svg-4BmQsvjQuGGxEK48 .label text,#mermaid-svg-4BmQsvjQuGGxEK48 span{fill:#333;color:#333;}#mermaid-svg-4BmQsvjQuGGxEK48 .node rect,#mermaid-svg-4BmQsvjQuGGxEK48 .node circle,#mermaid-svg-4BmQsvjQuGGxEK48 .node ellipse,#mermaid-svg-4BmQsvjQuGGxEK48 .node polygon,#mermaid-svg-4BmQsvjQuGGxEK48 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-4BmQsvjQuGGxEK48 .rough-node .label text,#mermaid-svg-4BmQsvjQuGGxEK48 .node .label text,#mermaid-svg-4BmQsvjQuGGxEK48 .image-shape .label,#mermaid-svg-4BmQsvjQuGGxEK48 .icon-shape .label{text-anchor:middle;}#mermaid-svg-4BmQsvjQuGGxEK48 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-4BmQsvjQuGGxEK48 .rough-node .label,#mermaid-svg-4BmQsvjQuGGxEK48 .node .label,#mermaid-svg-4BmQsvjQuGGxEK48 .image-shape .label,#mermaid-svg-4BmQsvjQuGGxEK48 .icon-shape .label{text-align:center;}#mermaid-svg-4BmQsvjQuGGxEK48 .node.clickable{cursor:pointer;}#mermaid-svg-4BmQsvjQuGGxEK48 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-4BmQsvjQuGGxEK48 .arrowheadPath{fill:#333333;}#mermaid-svg-4BmQsvjQuGGxEK48 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-4BmQsvjQuGGxEK48 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-4BmQsvjQuGGxEK48 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-4BmQsvjQuGGxEK48 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-4BmQsvjQuGGxEK48 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-4BmQsvjQuGGxEK48 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-4BmQsvjQuGGxEK48 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-4BmQsvjQuGGxEK48 .cluster text{fill:#333;}#mermaid-svg-4BmQsvjQuGGxEK48 .cluster span{color:#333;}#mermaid-svg-4BmQsvjQuGGxEK48 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-4BmQsvjQuGGxEK48 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-4BmQsvjQuGGxEK48 rect.text{fill:none;stroke-width:0;}#mermaid-svg-4BmQsvjQuGGxEK48 .icon-shape,#mermaid-svg-4BmQsvjQuGGxEK48 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-4BmQsvjQuGGxEK48 .icon-shape p,#mermaid-svg-4BmQsvjQuGGxEK48 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-4BmQsvjQuGGxEK48 .icon-shape .label rect,#mermaid-svg-4BmQsvjQuGGxEK48 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-4BmQsvjQuGGxEK48 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-4BmQsvjQuGGxEK48 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-4BmQsvjQuGGxEK48 :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
图像采集
数据传输
处理核心JPEG 校验、WiFi 通信、HTTP 服务器
网络中转 无线连接
解析 MJPEG 流并显示画面
OV3660 摄像头
ESP32-CAM 引脚
ESP32 核心
家庭 WiFi 路由器
浏览器 Edge/Chrome
显示画面
3. 硬件实物图(新手直观参考)
以下是 ESP32-CAM + OV3660 + 烧录座的实物图,新手可直接对照购买: 
4. 硬件接线(使用烧录座,零难度)
(1)ESP32-CAM 与烧录座接线
(2)烧录座与 USB 数据线接线
直接将 Micro USB 数据线插入烧录座的 Micro USB 接口,另一端插入电脑 USB 口即可(无需额外接线)。
(3)ESP32-CAM 与 OV3660 接线
直接将 OV3660 自带的排线插入 ESP32-CAM 的摄像头接口即可(注意排线方向,金手指朝向开发板正面)。
三、开发环境准备(ESP-IDF v5.1.6)
已安装 ESP-IDF v5.1.6 的新手可跳过此步骤,直接进入「四、项目搭建」。
1. 下载并安装 ESP-IDF Tools
2. 配置 VS Code 开发环境
四、新手重建项目(核心步骤)
步骤 1:创建新项目
步骤 2:创建核心文件
在项目的 main 目录下,删除默认的 main.c,新建以下 6 个文件: app_main.c、app_wifi.c、app_wifi.h、app_camera.c、app_camera.h、app_httpd.c、app_httpd.h。
步骤 3:复制代码(直接粘贴,无需修改)
所有代码已适配 ESP-IDF v5.1.6,解决「SOI 标记缺失」「Edge 兼容性」「编译报错」问题。
1. app_main.c(程序入口)
#include "esp_log.h"
#include "nvs_flash.h"
#include "app_wifi.h"
#include "app_camera.h"
#include "app_httpd.h"
static const char *TAG = "MAIN";
void app_main(void)
{
// 1. 初始化 NVS(WiFi 依赖)
esp_err_t err = nvs_flash_init();
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
err = nvs_flash_init();
}
ESP_ERROR_CHECK(err);
ESP_LOGI(TAG, "NVS 初始化成功");
// 2. 初始化 WiFi(直接调用,无返回值)
wifi_init_sta();
ESP_LOGI(TAG, "WiFi 初始化完成");
// 3. 初始化 OV3660 摄像头
err = app_camera_main();
if (err != ESP_OK) {
ESP_LOGE(TAG, "摄像头初始化失败!错误码:0x%x", err);
return;
}
ESP_LOGI(TAG, "摄像头初始化成功");
// 4. 启动 HTTP 服务器
httpd_handle_t server = start_webserver();
if (server == NULL) {
ESP_LOGE(TAG, "HTTP 服务器启动失败");
return;
}
ESP_LOGI(TAG, "系统初始化完成!访问:http://ESP32_IP/ 查看摄像头");
}
2. app_wifi.c(WiFi 连接实现) 替换为你的 WiFi 名称和密码
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_log.h"
#include "esp_wifi.h"
#include "esp_netif.h"
#include "esp_event.h"
#include "app_wifi.h"
static const char *TAG = "WIFI";
static EventGroupHandle_t s_wifi_event_group;
// ===================== 新手修改处 =====================
// 替换为你的 WiFi 名称和密码
#define WIFI_SSID "替换为你的 WiFi 名称"
#define WIFI_PASSWORD "替换为你的 WiFi 密码"
// =====================================================
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT BIT1
static void wifi_event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data)
{
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
esp_wifi_connect();
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
ESP_LOGW(TAG, "WiFi 断开,重试连接…");
esp_wifi_connect();
xEventGroupClearBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
ESP_LOGI(TAG, "WiFi 连接成功,IP地址:" IPSTR, IP2STR(&event->ip_info.ip));
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
}
}
void wifi_init_sta(void)
{
s_wifi_event_group = xEventGroupCreate();
// 初始化网络接口
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_create_default_wifi_sta();
// 初始化 WiFi 驱动
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
// 注册事件回调
esp_event_handler_instance_t instance_any_id;
esp_event_handler_instance_t instance_got_ip;
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
ESP_EVENT_ANY_ID,
&wifi_event_handler,
NULL,
&instance_any_id));
ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
IP_EVENT_STA_GOT_IP,
&wifi_event_handler,
NULL,
&instance_got_ip));
// 配置 WiFi 账号密码
wifi_config_t wifi_config = {
.sta = {
.ssid = WIFI_SSID,
.password = WIFI_PASSWORD,
.threshold.authmode = WIFI_AUTH_WPA2_PSK,
},
};
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
ESP_ERROR_CHECK(esp_wifi_start());
// 等待 WiFi 连接完成
xEventGroupWaitBits(s_wifi_event_group,
WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
pdFALSE,
pdFALSE,
portMAX_DELAY);
}
3. app_wifi.h(WiFi 函数声明)
#ifndef _APP_WIFI_H_
#define _APP_WIFI_H_
#ifdef __cplusplus
extern "C" {
#endif
void wifi_init_sta(void);
#ifdef __cplusplus
}
#endif
#endif /* _APP_WIFI_H_ */
4. app_camera.c(OV3660 摄像头驱动,解决 SOI 缺失)
#include "app_camera.h"
#include "esp_camera.h"
#include "driver/gpio.h"
#include "esp_log.h"
// ESP32-CAM 与 OV3660 引脚匹配
#define CAM_PIN_PWDN 32
#define CAM_PIN_RESET –1
#define CAM_PIN_XCLK 0
#define CAM_PIN_SIOD 26
#define CAM_PIN_SIOC 27
#define CAM_PIN_D7 35
#define CAM_PIN_D6 34
#define CAM_PIN_D5 39
#define CAM_PIN_D4 36
#define CAM_PIN_D3 21
#define CAM_PIN_D2 19
#define CAM_PIN_D1 18
#define CAM_PIN_D0 5
#define CAM_PIN_VSYNC 25
#define CAM_PIN_HREF 23
#define CAM_PIN_PCLK 22
static const char *TAG = "CAMERA";
esp_err_t app_camera_main(void)
{
// OV3660 专用配置(解决 SOI 标记缺失)
camera_config_t camera_config = {
.pin_pwdn = CAM_PIN_PWDN,
.pin_reset = CAM_PIN_RESET,
.pin_xclk = CAM_PIN_XCLK,
.pin_sccb_sda = CAM_PIN_SIOD,
.pin_sccb_scl = CAM_PIN_SIOC,
.pin_d7 = CAM_PIN_D7,
.pin_d6 = CAM_PIN_D6,
.pin_d5 = CAM_PIN_D5,
.pin_d4 = CAM_PIN_D4,
.pin_d3 = CAM_PIN_D3,
.pin_d2 = CAM_PIN_D2,
.pin_d1 = CAM_PIN_D1,
.pin_d0 = CAM_PIN_D0,
.pin_vsync = CAM_PIN_VSYNC,
.pin_href = CAM_PIN_HREF,
.pin_pclk = CAM_PIN_PCLK,
.xclk_freq_hz = 10000000, // 降频到 10MHz,稳定编码
.ledc_timer = LEDC_TIMER_0,
.ledc_channel = LEDC_CHANNEL_0,
.pixel_format = PIXFORMAT_JPEG, // 强制 JPEG 格式
.frame_size = FRAMESIZE_QVGA, // 320×240,适配无 PSRAM
.jpeg_quality = 10, // 降低质量,避免数据溢出
.fb_count = 2, // 双缓冲区,防止数据丢失
.fb_location = CAMERA_FB_IN_DRAM,
.grab_mode = CAMERA_GRAB_WHEN_EMPTY
};
// 初始化摄像头
esp_err_t err = esp_camera_init(&camera_config);
if (err != ESP_OK) {
ESP_LOGE(TAG, "摄像头初始化失败!错误码:0x%x", err);
return err;
}
// 强制锁定 JPEG 配置,杜绝 RAW 格式
sensor_t *s = esp_camera_sensor_get();
if (s) {
s->set_framesize(s, FRAMESIZE_QVGA);
s->set_pixformat(s, PIXFORMAT_JPEG);
ESP_LOGI(TAG, "OV3660 JPEG 配置生效");
} else {
ESP_LOGE(TAG, "获取摄像头传感器失败");
return ESP_FAIL;
}
ESP_LOGI(TAG, "摄像头初始化成功!");
return ESP_OK;
}
5. app_camera.h(摄像头函数声明)
#ifndef APP_CAMERA_H
#define APP_CAMERA_H
#include "esp_err.h"
esp_err_t app_camera_main(void);
#endif // APP_CAMERA_H
6. app_httpd.c(HTTP 服务器 + MJPEG 流,适配 Edge)
#include "esp_err.h"
#include "esp_http_server.h"
#include "esp_log.h"
#include "esp_camera.h"
#include "string.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "app_httpd.h"
static const char *TAG = "HTTPD";
// 主页 HTML(自带样式,适配浏览器)
static const char *STREAM_HTML =
"<!DOCTYPE html>"
"<html>"
"<head>"
"<title>ESP32 OV3660 Camera</title>"
"<meta charset=\\"utf-8\\">"
"<style>"
"body { margin: 0; padding: 20px; background-color: #f0f0f0; }"
".camera-container { max-width: 640px; margin: 0 auto; background: white; padding: 10px; border-radius: 8px; box-shadow: 0 0 10px rgba(0,0,0,0.1); }"
"img { width: 100%; height: auto; border-radius: 4px; }"
"h1 { text-align: center; color: #333; font-family: Arial, sans-serif; }"
"</style>"
"</head>"
"<body>"
"<div class=\\"camera-container\\">"
"<h1>ESP32 OV3660 摄像头画面</h1>"
"<img src=\\"/stream\\" alt=\\"Camera Stream\\">"
"</div>"
"</body>"
"</html>";
static esp_err_t index_handler(httpd_req_t *req)
{
httpd_resp_set_type(req, "text/html");
httpd_resp_send(req, STREAM_HTML, strlen(STREAM_HTML));
return ESP_OK;
}
// MJPEG 流核心处理(纯 ESP-IDF v5.1.6 支持 API)
static esp_err_t stream_handler(httpd_req_t *req)
{
camera_fb_t *fb = NULL;
esp_err_t res = ESP_OK;
char part_buf[128];
char conn_hdr[32];
TickType_t start_tick;
// Edge 浏览器兼容配置
httpd_resp_set_type(req, "multipart/x-mixed-replace; boundary=frame");
httpd_resp_set_hdr(req, "Cache-Control", "no-cache");
httpd_resp_set_hdr(req, "Connection", "close");
while (true) {
// 手动超时获取帧(替代高版本函数,避免编译报错)
start_tick = xTaskGetTickCount();
fb = NULL;
while (xTaskGetTickCount() – start_tick < 500 / portTICK_PERIOD_MS) {
fb = esp_camera_fb_get();
if (fb) break;
vTaskDelay(1 / portTICK_PERIOD_MS);
}
if (!fb) {
ESP_LOGE(TAG, "获取帧超时");
continue;
}
// 校验 JPEG 起始标记(0xFFD8),解决 SOI 缺失问题
if (fb->format == PIXFORMAT_JPEG && fb->buf[0] == 0xFF && fb->buf[1] == 0xD8) {
size_t hlen = snprintf(part_buf, sizeof(part_buf),
"–frame\\r\\n"
"Content-Type: image/jpeg\\r\\n"
"Content-Length: %zu\\r\\n\\r\\n",
fb->len);
res = httpd_resp_send_chunk(req, part_buf, hlen);
if (res != ESP_OK) break;
res = httpd_resp_send_chunk(req, (const char *)fb->buf, fb->len);
if (res != ESP_OK) break;
} else {
ESP_LOGE(TAG, "JPEG 数据异常,缺少 SOI 标记");
}
// 释放缓冲区,避免内存泄漏
if (fb) {
esp_camera_fb_return(fb);
fb = NULL;
}
// 检测客户端断开连接(ESP-IDF v5.1.6 标准写法)
ssize_t conn_hdr_len = httpd_req_get_hdr_value_len(req, "Connection");
if (conn_hdr_len > 0 && conn_hdr_len < sizeof(conn_hdr)) {
httpd_req_get_hdr_value_str(req, "Connection", conn_hdr, conn_hdr_len + 1);
if (strcmp(conn_hdr, "close") == 0) {
break;
}
}
}
// 清理资源
if (fb) esp_camera_fb_return(fb);
httpd_resp_send_chunk(req, "\\r\\n–frame–\\r\\n", strlen("\\r\\n–frame–\\r\\n"));
ESP_LOGI(TAG, "流连接断开");
return res;
}
httpd_handle_t start_webserver(void)
{
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
config.max_uri_handlers = 16;
config.server_port = 80;
httpd_handle_t server = NULL;
if (httpd_start(&server, &config) == ESP_OK) {
// 注册主页路由
httpd_uri_t index_uri = {
.uri = "/",
.method = HTTP_GET,
.handler = index_handler,
.user_ctx = NULL
};
httpd_register_uri_handler(server, &index_uri);
// 注册视频流路由
httpd_uri_t stream_uri = {
.uri = "/stream",
.method = HTTP_GET,
.handler = stream_handler,
.user_ctx = NULL
};
httpd_register_uri_handler(server, &stream_uri);
ESP_LOGI(TAG, "HTTP 服务器启动成功,端口:80");
} else {
ESP_LOGE(TAG, "HTTP 服务器启动失败");
}
return server;
}
void stop_webserver(httpd_handle_t server)
{
if (server) {
httpd_stop(server);
ESP_LOGI(TAG, "HTTP 服务器已停止");
}
}
7. app_httpd.h(HTTP 函数声明)
#ifndef APP_HTTPD_H
#define APP_HTTPD_H
#include "esp_http_server.h"
httpd_handle_t start_webserver(void);
void stop_webserver(httpd_handle_t server);
#endif // APP_HTTPD_H
步骤 4:修改 CMakeLists.txt(编译配置)
打开 main 目录下的 CMakeLists.txt,替换为以下内容:
idf_component_register(SRCS "app_main.c" "app_wifi.c" "app_camera.c" "app_httpd.c"
INCLUDE_DIRS ".")
五、编译与烧录(使用烧录座,新手零门槛)
步骤 1:配置串口与目标芯片
步骤 2:清理并编译
在 VS Code 终端中执行以下命令(复制粘贴即可):
# 彻底清理旧缓存(解决编译残留问题)
idf.py fullclean
# 编译项目
idf.py build
编译成功会显示「Project build complete」,无任何报错。
步骤 3:烧录程序(烧录座专用)
idf.py flash
步骤 4:查看串口日志(获取 ESP32 IP)
在终端执行:
idf.py monitor
等待日志输出,找到以下关键信息:
I (5471) WIFI: WiFi 连接成功,IP地址:192.168.31.179
I (5941) MAIN: 系统初始化完成!访问:http://ESP32_IP/ 查看摄像头
记录下你的 ESP32 IP(例如 192.168.31.179)。
六、查看摄像头画面(最终验证)

七、新手常见问题排查
| 编译报错「unknown type name」 | 头文件缺失/顺序错误 | 严格按本文代码的头文件顺序引入 |
| 日志显示「NO-SOI」 | OV3660 编码异常 | 检查 app_camera.c 中 xclk_freq_hz 是否为 10MHz,fb_count 是否为 2 |
| Edge 只显示标题,不显示画面 | 浏览器缓存/流格式不兼容 | 按 Ctrl+F5 强制刷新,关闭 Edge 安全增强模式 |
| WiFi 连接失败 | 账号密码错误/信号差 | 检查 app_wifi.c 中的 WIFI_SSID 和 WIFI_PASSWORD,靠近路由器 |
| 烧录失败 | 烧录座接触不良/串口错误 | 重新插拔 ESP32-CAM,重新选择正确串口 |
结语
本文从硬件到软件,全程为新手打造,代码经过实测验证,完美适配 ESP-IDF v5.1.6、OV3660 摄像头与 Edge 浏览器。新手只需按步骤复制代码、接线、编译,即可快速搭建属于自己的 ESP32 摄像头流服务器,后续可在此基础上扩展人脸识别、视频录制等功能。
源码
已上传Gitee https://gitee.com/yunjingshan/esp32-ov3660-mjpeg-server
网硕互联帮助中心






评论前必须登录!
注册