ESP32-CAM 摄像头 & Wi-Fi 图传代码例程

知识库
知识库文档
/firmware/开发板/ESP32-CAM/ESP32-CAM 摄像头 & Wi-Fi 图传代码例程.md

文档

ESP32-CAM 摄像头 & Wi-Fi 图传代码例程

环境准备

使用 ESP-IDF 框架,需引入 esp32-camera 组件:

# 克隆摄像头驱动
cd components
git clone https://github.com/espressif/esp32-camera.git
cd ..

idf.py set-target esp32
idf.py build flash monitor

1. 摄像头初始化

#include "esp_camera.h"
#include "esp_log.h"

static const char *TAG = "camera";

// ESP32-CAM (AI-Thinker) 摄像头引脚配置
static camera_config_t camera_config = {
    .pin_pwdn       = 32,
    .pin_reset      = -1,       // 软件复位,不使用硬件复位引脚
    .pin_xclk       = 0,
    .pin_sccb_sda   = 13,       // SIOD
    .pin_sccb_scl   = 12,       // SIOC

    .pin_d7 = 39,
    .pin_d6 = 36,
    .pin_d5 = 21,
    .pin_d4 = 19,
    .pin_d3 = 18,
    .pin_d2 = 5,
    .pin_d1 = 34,               // 注意:部分版本为 GPIO35
    .pin_d0 = 35,               // 注意:部分版本为 GPIO34
    .pin_vsync = 25,
    .pin_href  = 23,
    .pin_pclk  = 22,

    .xclk_freq_hz   = 20000000,     // 20MHz XCLK
    .ledc_timer     = LEDC_TIMER_0,
    .ledc_channel   = LEDC_CHANNEL_0,

    .pixel_format   = PIXFORMAT_JPEG,   // JPEG 输出
    .frame_size     = FRAMESIZE_SVGA,    // 800x600
    .jpeg_quality   = 12,               // 0-63,越小质量越高
    .fb_count       = 2,                // 双帧缓冲
    .grab_mode      = CAMERA_GRAB_WHEN_EMPTY,
};

esp_err_t camera_init(void)
{
    // 初始化摄像头
    esp_err_t err = esp_camera_init(&camera_config);
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "摄像头初始化失败: 0x%x", err);
        return err;
    }

    // 可选:调整图像参数
    sensor_t *s = esp_camera_sensor_get();
    if (s) {
        s->set_brightness(s, 0);     // -2 到 2
        s->set_contrast(s, 0);       // -2 到 2
        s->set_saturation(s, 0);     // -2 到 2
        s->set_whitebal(s, 1);       // 自动白平衡
        s->set_awb_gain(s, 1);       // 自动增益
        s->set_exposure_ctrl(s, 1);  // 自动曝光
        s->set_gain_ctrl(s, 1);      // 自动增益控制
    }

    ESP_LOGI(TAG, "摄像头初始化成功");
    return ESP_OK;
}

2. 闪光灯 GPIO 驱动

#include "driver/gpio.h"

#define FLASH_GPIO  4

void flash_init(void)
{
    gpio_config_t conf = {
        .pin_bit_mask = (1ULL << FLASH_GPIO),
        .mode = GPIO_MODE_OUTPUT,
        .pull_up_en = GPIO_PULLUP_DISABLE,
        .pull_down_en = GPIO_PULLDOWN_DISABLE,
        .intr_type = GPIO_INTR_DISABLE,
    };
    gpio_config(&conf);
    gpio_set_level(FLASH_GPIO, 0);
}

void flash_on(void)  { gpio_set_level(FLASH_GPIO, 1); }
void flash_off(void) { gpio_set_level(FLASH_GPIO, 0); }

// 拍照闪光:短暂脉冲
void flash_pulse(void)
{
    flash_on();
    vTaskDelay(pdMS_TO_TICKS(100));
    flash_off();
}

3. Wi-Fi 图传 HTTP 服务器 (MJPEG Stream)

#include "esp_http_server.h"
#include "esp_camera.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "nvs_flash.h"

static const char *TAG = "stream";

#define WIFI_SSID   "your_ssid"
#define WIFI_PASS   "your_password"

// ---- MJPEG 流处理 ----
static esp_err_t stream_handler(httpd_req_t *req)
{
    camera_fb_t *fb = NULL;
    esp_err_t res = ESP_OK;
    char part_buf[64];

    res = httpd_resp_set_type(req, "multipart/x-mixed-replace; boundary=frame");
    if (res != ESP_OK) return res;

    while (true) {
        fb = esp_camera_fb_get();
        if (!fb) {
            ESP_LOGE(TAG, "获取帧失败");
            res = ESP_FAIL;
            break;
        }

        size_t hlen = snprintf(part_buf, sizeof(part_buf),
            "\r\n--frame\r\nContent-Type: image/jpeg\r\nContent-Length: %u\r\n\r\n",
            fb->len);

        res = httpd_resp_send_chunk(req, part_buf, hlen);
        if (res == ESP_OK) {
            res = httpd_resp_send_chunk(req, (const char *)fb->buf, fb->len);
        }

        esp_camera_fb_return(fb);

        if (res != ESP_OK) break;
        vTaskDelay(pdMS_TO_TICKS(50)); // ~20fps
    }
    return res;
}

// ---- 拍照接口 /capture ----
static esp_err_t capture_handler(httpd_req_t *req)
{
    camera_fb_t *fb = esp_camera_fb_get();
    if (!fb) {
        httpd_resp_send_500(req);
        return ESP_FAIL;
    }

    httpd_resp_set_type(req, "image/jpeg");
    httpd_resp_set_hdr(req, "Content-Disposition", "inline; filename=capture.jpg");
    httpd_resp_send(req, (const char *)fb->buf, fb->len);

    esp_camera_fb_return(fb);
    return ESP_OK;
}

// ---- 启动 HTTP 服务器 ----
httpd_handle_t start_camera_server(void)
{
    httpd_config_t config = HTTPD_DEFAULT_CONFIG();
    config.server_port = 80;
    config.max_uri_handlers = 8;

    httpd_handle_t server = NULL;
    if (httpd_start(&server, &config) == ESP_OK) {
        httpd_uri_t stream_uri = {
            .uri       = "/",
            .method    = HTTP_GET,
            .handler   = stream_handler,
        };
        httpd_register_uri_handler(server, &stream_uri);

        httpd_uri_t capture_uri = {
            .uri       = "/capture",
            .method    = HTTP_GET,
            .handler   = capture_handler,
        };
        httpd_register_uri_handler(server, &capture_uri);

        ESP_LOGI(TAG, "MJPEG 流: http://<IP>/");
        ESP_LOGI(TAG, "拍照: http://<IP>/capture");
    }
    return server;
}

4. 完整示例:Wi-Fi 智能摄像头

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_camera.h"
#include "esp_http_server.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "nvs_flash.h"
#include "driver/gpio.h"
#include "esp_log.h"

static const char *TAG = "esp32cam";
#define FLASH_GPIO  4
#define WIFI_SSID   "your_ssid"
#define WIFI_PASS   "your_password"

// ---- Wi-Fi ----
static void wifi_cb(void *arg, esp_event_base_t base, int32_t id, void *data)
{
    if (base == WIFI_EVENT && id == WIFI_EVENT_STA_START) esp_wifi_connect();
    else if (base == IP_EVENT && id == IP_EVENT_STA_GOT_IP) {
        ip_event_got_ip_t *ev = (ip_event_got_ip_t *)data;
        ESP_LOGI(TAG, "Wi-Fi OK! IP:" IPSTR, IP2STR(&ev->ip_info.ip));
        ESP_LOGI(TAG, "视频流: http://" IPSTR "/", IP2STR(&ev->ip_info.ip));
    }
}

static void wifi_init(void)
{
    nvs_flash_init();
    esp_netif_init();
    esp_event_loop_create_default();
    esp_netif_create_default_wifi_sta();
    wifi_init_config_t c = WIFI_INIT_CONFIG_DEFAULT();
    esp_wifi_init(&c);
    esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, wifi_cb, NULL);
    esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, wifi_cb, NULL);
    wifi_config_t w = {.sta={.ssid=WIFI_SSID,.password=WIFI_PASS}};
    esp_wifi_set_mode(WIFI_MODE_STA);
    esp_wifi_set_config(WIFI_IF_STA, &w);
    esp_wifi_start();
}

// ---- 摄像头 (同 3.1) ----
static camera_config_t cam_cfg = {
    .pin_pwdn=32, .pin_reset=-1, .pin_xclk=0,
    .pin_sccb_sda=13, .pin_sccb_scl=12,
    .pin_d7=39,.pin_d6=36,.pin_d5=21,.pin_d4=19,
    .pin_d3=18,.pin_d2=5,.pin_d1=34,.pin_d0=35,
    .pin_vsync=25,.pin_href=23,.pin_pclk=22,
    .xclk_freq_hz=20000000,
    .ledc_timer=LEDC_TIMER_0,.ledc_channel=LEDC_CHANNEL_0,
    .pixel_format=PIXFORMAT_JPEG,.frame_size=FRAMESIZE_SVGA,
    .jpeg_quality=12,.fb_count=2,
};

static esp_err_t stream_handler(httpd_req_t *req)
{
    camera_fb_t *fb = NULL;
    char buf[64];
    httpd_resp_set_type(req, "multipart/x-mixed-replace; boundary=frame");
    while (1) {
        fb = esp_camera_fb_get();
        if (!fb) break;
        size_t h = snprintf(buf, 64,
            "\r\n--frame\r\nContent-Type: image/jpeg\r\nContent-Length: %u\r\n\r\n", fb->len);
        if (httpd_resp_send_chunk(req, buf, h) != ESP_OK ||
            httpd_resp_send_chunk(req, (const char*)fb->buf, fb->len) != ESP_OK) {
            esp_camera_fb_return(fb);
            break;
        }
        esp_camera_fb_return(fb);
        vTaskDelay(pdMS_TO_TICKS(50));
    }
    return ESP_OK;
}

// ---- 主入口 ----
void app_main(void)
{
    ESP_LOGI(TAG, "ESP32-CAM 智能摄像头启动");

    // 闪光灯
    gpio_config_t f = {.pin_bit_mask=(1ULL<<FLASH_GPIO),.mode=GPIO_MODE_OUTPUT};
    gpio_config(&f);

    // Wi-Fi
    wifi_init();

    // 摄像头
    ESP_ERROR_CHECK(esp_camera_init(&cam_cfg));
    ESP_LOGI(TAG, "摄像头就绪");

    // HTTP 视频流
    httpd_handle_t srv;
    httpd_config_t hc = HTTPD_DEFAULT_CONFIG();
    httpd_start(&srv, &hc);
    httpd_uri_t u = {.uri="/",.method=HTTP_GET,.handler=stream_handler};
    httpd_register_uri_handler(srv, &u);

    ESP_LOGI(TAG, "服务已启动,浏览器访问 http://<IP>/ 查看视频流");
}

编译与烧录

# 1. 克隆摄像头组件
mkdir -p components &;& cd components
git clone https://github.com/espressif/esp32-camera.git
cd ..

# 2. 配置
idf.py set-target esp32
idf.py menuconfig  # 设置 Flash 4MB, PSRAM enabled

# 3. 烧录 (先接好 GPIO0→GND 跳线,上电后烧录)
idf.py build
idf.py -p /dev/ttyUSB0 flash

# 4. 烧录完成后,移除 GPIO0-GND 跳线,重新上电
idf.py -p /dev/ttyUSB0 monitor

提示:将 WIFI_SSIDWIFI_PASS 替换为自己的 Wi-Fi 信息。上电后通过串口监视器获取 IP 地址,然后在浏览器中打开 http://<IP>/ 即可看到实时 MJPEG 视频流。

信息

路径
/firmware/开发板/ESP32-CAM/ESP32-CAM 摄像头 & Wi-Fi 图传代码例程.md
更新时间
2026/5/26