文档
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_SSID和WIFI_PASS替换为自己的 Wi-Fi 信息。上电后通过串口监视器获取 IP 地址,然后在浏览器中打开http://<IP>/即可看到实时 MJPEG 视频流。