项目:
esp32-cam| ESP-IDF: v5.5.4 | 芯片: ESP32 (Xtensa LX6 @240MHz) 固件大小: 946 KB | 编译步骤: 1063/1063 通过
目录
1. 整体架构概览
┌──────────────────────────────────────────────────────────────┐
│ 客户端 (浏览器) │
│ http://<ESP32-IP>/ /capture /stream │
└──────────────────────────┬───────────────────────────────────┘
│ HTTP over WiFi
┌──────────────────────────▼───────────────────────────────────┐
│ 应用层 (Application) │
│ app_main() → wifi_event_handler → HTTP Server │
│ ├── GET / 首页 HTML (内嵌流预览) │
│ ├── GET /capture 单帧 JPEG 拍照 │
│ └── GET /stream MJPEG 视频流 (multipart/x-mixed-replace) │
└──────────────────────────┬───────────────────────────────────┘
│
┌──────────────────────────▼───────────────────────────────────┐
│ esp32-camera 驱动层 │
│ Component Registry: espressif/esp32-camera │
│ ├── esp_camera.c 核心驱动 (cam_hal / sccb-ng / xclk) │
│ ├── ov2640.c 传感器驱动 (OV3640 / OV5640 / ...) │
│ ├── to_jpg / to_bmp 图像格式转换 │
│ └── ll_cam.c ESP32 底层 DVP 寄存器操作 │
└──────────────────────────┬───────────────────────────────────┘
│
┌──────────────────────────▼───────────────────────────────────┐
│ ESP-IDF 系统层 (v5.5.4) │
│ FreeRTOS │ NVS Flash │ LWIP TCP/IP │ esp_wifi │ esp_http_server │
│ esp_psram │ esp_event │ esp_netif │ esp_timer │ spi_flash │
└──────────────────────────┬───────────────────────────────────┘
│
┌──────────────────────────▼───────────────────────────────────┐
│ 硬件层 (ESP32-CAM) │
│ ESP32 SoC │ OV2640 Sensor │ 4MB PSRAM │ 4MB SPI Flash │ LED │
└──────────────────────────────────────────────────────────────┘
2. 硬件层
2.1 ESP32-CAM 开发板
典型的 AI-Thinker ESP32-CAM 开发板硬件资源:
| 组件 | 型号/规格 | 用途 |
|---|---|---|
| 主控 | ESP32 (Xtensa LX6 @240MHz) | 双核处理器,集成 WiFi / BT |
| 摄像头 | OV2640 (200万像素) | JPEG/YCbCr/RGB 输出,最高 UXGA |
| PSRAM | 4 MB SPI Pseudo-SRAM | 帧缓冲区 (framebuffer) |
| Flash | 4 MB SPI NOR Flash | 固件存储 + 分区表 |
| LED | GPIO4 控制 | 闪光灯 (可程序控制) |
2.2 OV2640 传感器
OV2640 是经典的 1/4 英寸 CMOS 图像传感器:
- 分辨率: UXGA (1600×1200) 向下兼容 SVGA/XGA
- 输出格式: JPEG、YCbCr422、RGB565
- 控制接口: SCCB (兼容 I2C),通过 SIOD/SIOC 引脚
- 数据接口: DVP 8 位并行总线 (D0-D7) + PCLK/HREF/VSYNC 同步信号
- 时钟: XCLK 20 MHz (由 ESP32 LEDC 提供)
3. ESP-IDF 系统层
3.1 组件依赖图
esp32-cam (main)
│
├── espressif/esp32-camera (Component Registry) ── 外部组件
│ └── espressif/esp_jpeg 外部组件
│
├── esp_http_server ── HTTP 服务框架
│ ├── esp_event ── 事件循环
│ └── http_parser ── HTTP 协议解析
│
├── esp_wifi ── WiFi 驱动
│ ├── esp_netif ── 网络抽象
│ └── esp_phy ── 射频物理层
│
├── lwip ── TCP/IP 协议栈
├── nvs_flash ── 非易失存储
├── esp_psram ── PSRAM 支持
├── esp_timer ── 高精度定时器
│
├── FreeRTOS ── 实时操作系统
│ ├── soc ── SoC 抽象
│ ├── hal ── 硬件抽象层
│ └── esp_hw_support ── 硬件支持函数
│
└── 公共基础 (自动链接)
├── log ── 日志系统
├── heap ── 内存管理
├── newlib ── C 标准库
└── spi_flash ── Flash 操作
3.2 关键配置 (sdkconfig.defaults)
# PSRAM 支持
CONFIG_SPIRAM=y
CONFIG_SPIRAM_USE_CAPS_ALLOC=y
# WiFi 缓冲区
CONFIG_ESP_WIFI_STATIC_RX_BUFFER_NUM=10
CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM=32
# 主任务栈 4KB
CONFIG_ESP_MAIN_TASK_STACK_SIZE=4096
# 单应用分区 (简化分区表)
CONFIG_PARTITION_TABLE_SINGLE_APP=y
4. esp32-camera 驱动层
4.1 组件结构
espressif/esp32-camera 通过 ESP-IDF Component Registry 引入,自动下载到 managed_components/:
managed_components/espressif__esp32-camera/
├── driver/
│ ├── esp_camera.c # 核心 API: esp_camera_init() / esp_camera_fb_get()
│ ├── cam_hal.c # 硬件抽象层 (HAL),封装 DVP / CSI 操作
│ ├── sccb-ng.c # SCCB (I2C) 控制器驱动
│ └── esp_camera_af.c # 自动对焦 (仅 OV5640 AF)
├── sensors/
│ ├── ov2640.c # OV2640 传感器驱动 ← 本项目使用
│ ├── ov3660.c # OV3660 传感器
│ ├── ov5640.c # OV5640 传感器 (含 AF)
│ ├── ov7670.c # OV7670 传感器
│ ├── gc032a.c # 格科微 GC032A
│ └── ... # 其他传感器
├── conversions/
│ ├── to_jpg.cpp # RGB/YUV → JPEG 转换
│ ├── to_bmp.c # RGB → BMP 转换
│ └── yuv.c # YUV 色彩空间转换
└── target/
└── esp32/
└── ll_cam.c # ESP32 底层 DVP 寄存器操作
4.2 核心 API
// 初始化摄像头
esp_err_t esp_camera_init(const camera_config_t *config);
// 获取一个帧缓冲区 (从 DMA 队列中取出)
camera_fb_t *esp_camera_fb_get(void);
// 归还帧缓冲区 (放回 DMA 队列)
void esp_camera_fb_return(camera_fb_t *fb);
// 格式转换 (非 JPEG 传感器需要)
bool frame2jpg(camera_fb_t *fb, int quality, uint8_t **out, size_t *out_len);
4.3 摄像头配置结构体
camera_config_t config = {
// -- 引脚定义 (DVP 并行接口) --
.pin_pwdn = 32, .pin_reset = -1,
.pin_xclk = 0, .pin_sccb_sda = 26, .pin_sccb_scl = 27,
.pin_d0 = 5, .pin_d1 = 18, .pin_d2 = 19, .pin_d3 = 21,
.pin_d4 = 36, .pin_d5 = 39, .pin_d6 = 34, .pin_d7 = 35,
.pin_vsync = 25, .pin_href = 23, .pin_pclk = 22,
// -- 时钟 --
.xclk_freq_hz = 20000000, // 20 MHz XCLK
// -- 图像参数 --
.pixel_format = PIXFORMAT_JPEG, // JPEG 直出 (OV2640 硬件编码)
.frame_size = FRAMESIZE_SVGA, // 800×600
.jpeg_quality = 12, // 0(最高)~63(最低)
// -- 帧缓冲 --
.fb_count = 2, // 双缓冲 (乒乓)
.fb_location = CAMERA_FB_IN_PSRAM, // PSRAM
.grab_mode = CAMERA_GRAB_WHEN_EMPTY,
};
5. 应用层
5.1 执行流程
app_main()
│
├─ [1] nvs_flash_init() ← 初始化 NVS 分区
│
├─ [2] esp_camera_init() ← 初始化 OV2640
│ ├─ 配置 SCCB (I2C) 引脚
│ ├─ 配置 DVP 8 位并行数据引脚
│ ├─ 启动 XCLK 20MHz
│ ├─ 通过 SCCB 写入 OV2640 寄存器
│ │ ├─ 设置为 JPEG 模式
│ │ ├─ 分辨率 SVGA (800×600)
│ │ ├─ JPEG 质量等级 12
│ │ └─ 帧率 ~30fps
│ ├─ 配置 DMA 引擎
│ └─ 在 PSRAM 中分配 framebuffer (fb_count=2)
│
├─ [3] wifi_init_sta() ← 初始化 WiFi STA
│ ├─ esp_netif_init()
│ ├─ esp_event_loop_create_default()
│ ├─ esp_wifi_init() + esp_wifi_set_config(SSID, PWD)
│ ├─ 注册事件回调:
│ │ ├─ WIFI_EVENT_STA_START → esp_wifi_connect()
│ │ ├─ WIFI_EVENT_STA_DISCONNECTED → esp_wifi_connect()
│ │ └─ IP_EVENT_STA_GOT_IP → start_webserver()
│ └─ esp_wifi_start()
│
└─ [4] (等待 WiFi 连接...)
│
└─ WiFi 连接成功 → start_webserver()
├─ httpd_start() 在端口 80 启动 HTTP 服务
├─ 注册 URI: GET /capture → capture_handler
├─ 注册 URI: GET /stream → stream_handler
└─ 注册 URI: GET / → index_handler
5.2 三个 HTTP 端点
| 端点 | 方法 | Content-Type | 说明 |
|---|---|---|---|
/ |
GET | text/html |
首页:显示实时流 <img src="/stream"> + 拍照按钮 |
/capture |
GET | image/jpeg |
单帧 JPEG 拍照并下载 |
/stream |
GET | multipart/x-mixed-replace; boundary=frame |
MJPEG 视频流 |
6. 数据流详解
6.1 摄像头采集流程
OV2640 传感器
│
DVP 8bit 并行总线
(PCLK + HREF + VSYNC + D0~D7)
│
▼
ESP32 DMA 引擎
(GDMA Channel)
│
▼
PSRAM 帧缓冲
┌────────────────────────┐
│ fb[0] │ fb[1] │ ← 双缓冲乒乓
│ 150KB │ 150KB │ JPEG SVGA≈30-150KB
└────────────────────────┘
│
esp_camera_fb_get()
│
▼
camera_fb_t 结构体
┌─────────────────────┐
│ .buf → JPEG 数据 │
│ .len → 数据长度 │
│ .width → 800 │
│ .height→ 600 │
│ .format→ JPEG │
└─────────────────────┘
│
┌─────────┴──────────┐
▼ ▼
capture_handler stream_handler
(单帧 HTTP 响应) (MJPEG 循环推送)
6.2 MJPEG 流帧时序
时间 ────────────────────────────────────────────────→
传感器: [帧1捕获] [帧2捕获] [帧3捕获]
│ │ │
DMA: │ 传输到PSRAM │ │
│ │ │
fb_get(): ▼ 获取fb ▼ 获取fb ▼
T0 T1 T2
HTTP chunk: [--frame + JPEG][--frame + JPEG][--frame + JPEG]
│ │ │
fb_return(): ▼ 归还fb ▼ ▼
T0+1 T1+1 T2+1
客户端: 流式渲染帧1 流式渲染帧2 流式渲染帧3
帧间隔: ~50ms (20 FPS, 软件控制 vTaskDelay)
6.3 HTTP MJPEG 流协议
HTTP/1.1 200 OK
Content-Type: multipart/x-mixed-replace; boundary=frame
--frame
Content-Type: image/jpeg
Content-Length: 28456
<binary JPEG data ...>
--frame
Content-Type: image/jpeg
Content-Length: 29102
<binary JPEG data ...>
--frame
Content-Type: image/jpeg
Content-Length: 27634
<binary JPEG data ...>
...
这是 MJPEG over HTTP 的标准协议——浏览器原生支持 <img src="/stream"> 即可显示实时画面,无需 JavaScript。
7. HTTP 接口说明
GET / — 首页
返回一个自包含的 HTML 页面:
- 嵌入
<img id="stream" src="/stream">实时显示视频流 - 提供 “📸 拍照” 链接到
/capture - 提供 “🎥 全屏流” 链接到
/stream - 响应式设计,适配手机和桌面
GET /capture — 拍照
- 调用
esp_camera_fb_get()获取当前帧 - 返回
Content-Type: image/jpeg - 带
Content-Disposition: inline; filename=capture.jpg方便保存 - 每次请求都是独立帧,响应完成后立即归还 framebuffer
GET /stream — 视频流
- 无限循环获取帧并发送
- 每帧间隔 50ms 的
vTaskDelay(约 20 FPS) - 浏览器端通过
<img>标签自动递进渲染 - 断开连接时,
httpd_resp_send_chunk返回错误,循环退出
8. 引脚配置
AI-Thinker ESP32-CAM 标准引脚
| 功能 | GPIO | 方向 | 说明 |
|---|---|---|---|
| SCCB (I2C 控制) | |||
| SIOD | 26 | 双向 | SCCB 数据线 |
| SIOC | 27 | 输出 | SCCB 时钟线 |
| DVP 数据 | |||
| D0 (Y2) | 5 | 输入 | 数据位 0 |
| D1 (Y3) | 18 | 输入 | 数据位 1 |
| D2 (Y4) | 19 | 输入 | 数据位 2 |
| D3 (Y5) | 21 | 输入 | 数据位 3 |
| D4 (Y6) | 36 | 输入 | 数据位 4 |
| D5 (Y7) | 39 | 输入 | 数据位 5 |
| D6 (Y8) | 34 | 输入 | 数据位 6 |
| D7 (Y9) | 35 | 输入 | 数据位 7 |
| DVP 同步 | |||
| PCLK | 22 | 输入 | 像素时钟 |
| HREF | 23 | 输入 | 行有效 |
| VSYNC | 25 | 输入 | 帧同步 |
| 控制 | |||
| XCLK | 0 | 输出 | 主时钟 20MHz (LEDC) |
| PWDN | 32 | 输出 | 摄像头掉电 (低有效) |
| RESET | -1 | — | 不使用 (硬件上拉) |
| 外设 | |||
| LED (闪光灯) | 4 | 输出 | 高电平点亮 |
9. 存储器布局
Flash 分区 (单应用模式)
Flash 4MB ────────────────────────────── 0x400000
│ (空闲) │
│ factory 分区 1MB │
│ ├── esp32-cam.bin 946 KB (0xEC890) │ ← 应用固件
│ └── 空闲 78 KB │
│ 0x100000 ─────────── 0x200000
│ NVS 分区 │
│ └── WiFi 配置、摄像头参数 │
│ 0x9000 ──────────── 0x10000
│ partition-table.bin 3 KB │
│ 0x8000 ──────────── 0x9000
│ bootloader.bin 26 KB │
│ 0x1000 ──────────── 0x8000
└────────────────────────────────────────── 0x0000
运行时内存
内部 SRAM (520 KB)
├── 指令 RAM: 128 KB (IRAM, 缓存 + 驻留代码)
├── 数据 RAM: 384 KB (DRAM, 堆 + 栈 + .bss + .data)
│ ├── FreeRTOS 堆: ~200 KB
│ ├── WiFi 栈: ~50 KB
│ ├── LWIP: ~40 KB
│ └── 其他: ~94 KB
外部 PSRAM (4 MB)
├── framebuffer[0]: ~150 KB (JPEG SVGA 帧)
├── framebuffer[1]: ~150 KB (备用帧)
└── 空闲: ~3.7 MB (可分配)
10. 构建与烧录
开发环境
# 初始化 IDF 环境 (每次新终端)
. D:\Espressif\frameworks\esp-idf-v5.5.4\export.ps1
# 进入项目目录
cd C:\Users\HUAWEI\.openclaw\workspace\esp32-cam
编译
idf.py build
# 产物: build/esp32-cam.bin (946 KB)
# 首次编译约 3-5 分钟,增量编译 <1 分钟
烧录
# 烧录到 ESP32-CAM (替换 COMx 为实际串口号)
idf.py -p COM3 flash
# 或使用 esptool 手动烧录
python -m esptool --chip esp32 -p COM3 -b 460800 \
--before default_reset --after hard_reset \
write_flash --flash_mode dio --flash_size 2MB --flash_freq 40m \
0x1000 build/bootloader/bootloader.bin \
0x8000 build/partition_table/partition-table.bin \
0x10000 build/esp32-cam.bin
串口监控
idf.py -p COM3 monitor
# 退出: Ctrl + ]
使用前配置
务必先修改 WiFi SSID 和密码 (文件: main/esp32-cam.c):
#define WIFI_SSID "你的WiFi名"
#define WIFI_PASSWORD "你的WiFi密码"
修改后重新编译即可。
附录 A: 项目文件清单
esp32-cam/
├── CMakeLists.txt # 根 CMake 项目文件
├── sdkconfig # 当前 Kconfig 配置 (自动生成)
├── sdkconfig.defaults # 默认 Kconfig 配置
├── main/
│ ├── CMakeLists.txt # 主组件 CMake
│ ├── idf_component.yml # 组件依赖声明 (esp32-camera)
│ └── esp32-cam.c # 应用代码 (约 310 行)
├── managed_components/ # 组件仓库下载 (自动)
│ ├── espressif__esp32-camera/
│ └── espressif__esp_jpeg/
├── build/ # 编译产物 (自动)
│ ├── esp32-cam.bin # 最终固件
│ ├── esp32-cam.elf # ELF 可执行文件
│ ├── bootloader/ # 二级 bootloader
│ └── partition_table/ # 分区表
└── docs/
├── architecture.md # 架构文档
├── architecture.mmd # 架构 Mermaid 图
└── dataflow.mmd # 数据流 Mermaid 图
附录 B: 常见问题
Q: 摄像头初始化失败?
A: 检查摄像头排线是否插紧,确认 PSRAM 已使能 (CONFIG_SPIRAM=y)。
Q: 画面颜色偏色/绿屏? A: 检查 XCLK 频率是否稳定 (建议 20MHz),尝试降低帧大小到 QQVGA。
Q: WiFi 连接不上? A: 确认 SSID 和密码正确,ESP32 仅支持 2.4GHz WiFi。
Q: 网页加载但无画面?
A: 检查浏览器是否支持 MJPEG (Chrome/Firefox/Safari 均支持),确认 /stream 端点正常响应。
Q: 编译时找不到 esp32-camera 组件?
A: 确认网络连接正常,首次编译会自动下载。也可手动 idf.py reconfigure。