0x6A Logbook

0x6A Logbook
Shi6a的筆記本
  1. 首頁
  2. 程式開發
  3. 正文

ESP32-CAM 攝影機(OV2640)完整教學:DVP 並列介面、SCCB 暫存器配置、JPEG 編碼管線與影像串流

2026 年 6 月 16 日 6點熱度 0人點贊 0條評論

ESP32-CAM 與 OV2640 攝影機

ESP32-CAM 是 AI-Thinker 推出的低成本攝影機模組,內建 ESP32 晶片與 OV2640 200 萬像素 CMOS 感測器,支援 Wi-Fi 和藍牙,廣泛應用於視覺 IoT、人臉辨識、遠端監控與影像串流。本文將深入探討 OV2640 的硬體介面(DVP + SCCB)、JPEG 編碼管線、ESP32 的 Camera Driver 程式設計,以及常見問題排查。

ESP32-CAM 模組方塊圖

OV2640 與 DVP 並列介面

OV2640 是 OmniVision 生產的 1/4 吋 CMOS 影像感測器,最大解析度 1632x1232(UXGA),支援自動曝光控制(AEC)、自動白平衡(AWB)、自動增益控制(AGC)與內建 JPEG 壓縮引擎。

與 ESP32 的連接採用 DVP(Digital Video Port)並列介面:

訊號 方向 說明
PCLK OV2640 → ESP32 像素時鐘,通常 12~24 MHz
VSYNC OV2640 → ESP32 垂直同步(LOW 表示幀開始)
HREF OV2640 → ESP32 水平參考(HIGH 表示一行資料有效)
D[7:0] OV2640 → ESP32 8 位元像素資料匯流排
XCLK ESP32 → OV2640 主時鐘(ESP32 提供,常見 10~20 MHz)
SIOC ESP32 → OV2640 SCCB 時鐘(類似 I2C SCL)
SIOD 雙向 SCCB 資料(類似 I2C SDA)

OV2640 DVP 並列介面幀擷取時序

DVP 時序解析:

  • VSYNC 從 HIGH 變 LOW 表示一幀開始(某些配置為正邏輯,視暫存器設定)
  • HREF 為 HIGH 時,D[7:0] 上每 PCLK 週期輸出一個像素
  • VGA(640x480)一行有 640 個像素,HREF 在 PCLK 下維持 640 個週期
  • 一幀有 480 行(加上 blanking 約 510 行),VSYNC 在行空白期間觸發
  • PCLK 頻率由 XCLK 和 OV2640 內部 PLL 決定:PCLK = XCLK x PLL_mult / PLL_div

OV2640 SCCB 暫存器配置

OV2640 使用 SCCB(Serial Camera Control Bus)協定進行暫存器存取,與標準 I2C 高度相容但略有差異:

  • 寫入:Start + 裝置位址 0x42 (W) + 暫存器位址 + 資料 + Stop
  • 讀取:Start + 0x42 (W) + 暫存器位址 + Restart + 0x43 (R) + 資料 + Stop
  • 注意:SCCB 使用 Restart 條件,不是 I2C 的 Repeated Start(雖然多數實作相容)
  • 預設位址:0x60(7-bit)/ 0x30(8-bit, OV2640 使用)

SCCB 暫存器讀取時序

關鍵暫存器速查

暫存器 位址 預設值 功能
PID 0x0A 0x26 Product ID Number(辨識 OV2640)
VER 0x0B 0x42 Revision Number
CLKRC 0x08 0x80 內部時鐘分頻
COM3 0x0C 0x00 JPEG 輸出啟用(Bit 5: SWAP_YUV)
COM7 0x12 0x00 格式設定:Bit 6=1 RAW, Bit 5=1 RGB, Bit 4=1 YUV, Bit 3=1 JPEG
COM4 0x0D 0x40 PLL 倍頻(Bit 1: PLL enable)
DM_LNL 0x2D 0x00 DVP 驅動強度調整
AEC 0x10 0x00 曝光時間低位元組
AGC 0x00 0x04 增益控制高位元組

JPEG 編碼管線

OV2640 最大的優勢是 內建硬體 JPEG 編碼器,不需佔用 ESP32 的 CPU 資源進行壓縮。

OV2640 JPEG 編碼管線

管線流程:

  1. Bayer RAW10 數據從感測器陣列讀出
  2. 透過 ISP 管線(Bayer interpolation、AWB、AEC、AGC、CSC)
  3. 以 YUV422 格式送入 JPEG 編碼器
  4. JPEG 編碼器以 8x8 區塊進行 DCT + Huffman 壓縮
  5. 壓縮後的 JPEG 資料存入 OV2640 內部 FIFO
  6. ESP32 透過 SPI 或 DVP + FIFO_RD 訊號讀取 JPEG 資料
  7. 讀取的資料可存入 SD 卡、透過 Wi-Fi 串流出去,或直接傳送到瀏覽器

OV2640 解析度與 FPS 比較

ESP32 Camera Driver 程式設計

Arduino IDE 設定(ESP32-CAM)

首先在 Arduino IDE 的 "開發板管理員" 安裝 ESP32 支援套件,然後選取 "AI Thinker ESP32-CAM" 開發板。

基礎程式:攝影與 Wi-Fi 串流

// ESP32-CAM Wi-Fi 影像串流伺服器
#include <WiFi.h>
#include "esp_camera.h"
#include "esp_timer.h"

// Wi-Fi 設定
const char* ssid     = "你的WiFi名稱";
const char* password = "你的WiFi密碼";

// AI-Thinker ESP32-CAM 腳位定義
#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

// 簡易 HTTP 伺服器
WiFiServer server(80);
unsigned long lastFrame = 0;
bool streamActive = false;

void startCameraServer();
void handleCameraClient();

frame_size_t currentResolution = FRAMESIZE_VGA;  // 640x480
pixformat_t currentFormat = PIXFORMAT_JPEG;      // JPEG 輸出

void setup() {
    Serial.begin(115200);
    Serial.println("ESP32-CAM 啟動中...");

    // 設定攝影機參數
    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;        // XCLK = 20 MHz
    config.pixel_format = currentFormat;
    config.frame_size   = currentResolution;
    config.jpeg_quality = 12;              // JPEG 品質 0~63(越小越好)
    config.fb_count     = 1;               // 單一 frame buffer

    // 初始化攝影機
    esp_err_t err = esp_camera_init(&config);
    if (err != ESP_OK) {
        Serial.printf("攝影機初始化失敗: 0x%x
", err);
        return;
    }

感測器參數調校

    // 取得感測器控制物件
    sensor_t *s = esp_camera_sensor_get();

    // 常見調校參數
    s->set_brightness(s, 0);    // 亮度: -2~2
    s->set_contrast(s, 0);      // 對比度: -2~2
    s->set_saturation(s, 0);    // 飽和度: -2~2
    s->set_special_effect(s, 0);// 特效: 0~6
    s->set_whitebal(s, 1);      // 自動白平衡
    s->set_awb_gain(s, 1);      // AWB 增益
    s->set_wb_mode(s, 0);       // 白平衡模式: 0~4
    s->set_exposure_ctrl(s, 1); // 自動曝光控制
    s->set_aec2(s, 0);          // AEC2 啟用
    s->set_ae_level(s, 0);      // AE 補償: -2~2
    s->set_aec_value(s, 300);   // 手動曝光時間: 0~1200
    s->set_gain_ctrl(s, 1);     // 自動增益控制
    s->set_agc_gain(s, 0);      // AGC 增益: 0~30
    s->set_gainceiling(s, 0);   // 增益上限: 0~6
    s->set_bpc(s, 0);           // Black Pixel Correction
    s->set_wpc(s, 1);           // White Pixel Correction
    s->set_raw_gma(s, 1);       // 伽瑪校正
    s->set_lenc(s, 1);          // 鏡頭像差校正
    s->set_hmirror(s, 0);       // 水平翻轉
    s->set_vflip(s, 0);         // 垂直翻轉
    s->set_dcw(s, 1);           // Downsize EN

    Serial.println("感測器初始化完成");

拍攝靜態照片並透過序列埠輸出

// ESP32-CAM 拍攝照片範例
void capturePhoto() {
    // 擷取一張照片
    camera_fb_t *fb = esp_camera_fb_get();
    if (!fb) {
        Serial.println("擷取失敗!");
        return;
    }

    Serial.printf("照片尺寸: %d x %d, 格式: %d, 大小: %zu bytes
",
                  fb->width, fb->height, fb->format, fb->len);

    // JPEG 二進位資料在 fb->buf, 長度在 fb->len
    // 範例:計算平均亮度(僅 JPEG 頭部粗略估算)
    uint32_t sum = 0;
    for (size_t i = 0; i < min(fb->len, (size_t)1000); i++) {
        sum += fb->buf[i];
    }
    Serial.printf("平均亮度 (前 1KB): %d
", sum / min(fb->len, (size_t)1000));

    // 歸還 frame buffer(必須!)
    esp_camera_fb_return(fb);
}

MJPEG 串流伺服器

// ESP32-CAM簡易 MJPEG 串流
void streamJPEG(WiFiClient &client) {
    String header = "HTTP/1.1 200 OK
";
    header += "Content-Type: multipart/x-mixed-replace; boundary=frame
";
    header += "Cache-Control: no-cache
";
    header += "Connection: close

";
    client.print(header);

    while (client.connected()) {
        camera_fb_t *fb = esp_camera_fb_get();
        if (!fb) {
            Serial.println("取得 frame 失敗");
            break;
        }

        client.printf("--frame
");
        client.printf("Content-Type: image/jpeg
");
        client.printf("Content-Length: %zu

", fb->len);
        client.write(fb->buf, fb->len);
        client.printf("
");

        esp_camera_fb_return(fb);
    }
}

動態切換解析度

// 動態改變攝影機解析度
bool setResolution(framesize_t newSize) {
    sensor_t *s = esp_camera_sensor_get();
    if (s == NULL) return false;

    // 暫停串流後切換
    int ret = s->set_framesize(s, newSize);
    if (ret == 0) {
        Serial.printf("解析度切換成功: %d
", newSize);
        return true;
    }
    Serial.printf("解析度切換失敗: %d
", ret);
    return false;
}

// 支援的解析度列舉
// FRAMESIZE_96X96, FRAMESIZE_QQVGA (160x120),
// FRAMESIZE_QVGA (320x240), FRAMESIZE_VGA (640x480),
// FRAMESIZE_SVGA (800x600), FRAMESIZE_XGA (1024x768),
// FRAMESIZE_SXGA (1280x1024), FRAMESIZE_UXGA (1600x1200)

常見問題與排查

問題 原因 解法
攝影機初始化失敗 (0x101) PSRAM 未啟用 Tools → Partition Scheme → Huge APP (3MB NO OTA / 1MB SPIFFS)
畫面全黑 PWDN 腳位未正確設置 確認 PWDN_GPIO_NUM = 32 且為 LOW
影像雜訊/條紋 電源供應不足 ESP32-CAM 需要 5V / 2A 以上電源,勿用 USB 線供電
FPS 過低 JPEG 品質太高或 XCLK 太低 降低 xclk_freq_hz 至 10MHz 或提高 jpeg_quality 值
Wi-Fi 連線後崩潰 記憶體不足 降低解析度至 VGA,關閉 OTA,使用 PSRAM
顏色偏綠/偏藍 白平衡設定不當 啟用 AWB 或手動設定 wb_mode
影像過曝/過暗 曝光或增益設定不當 調整 aec_value 或 ae_level
無法辨識 OV2640 PID SCCB 時序不匹配 確認 SIOC/SIOD 上拉電阻,降低 SCCB 頻率
ESP32-CAM 過熱 長時間運轉無散熱 添加散熱片,降低解析度,或啟用睡眠模式

ESP-IDF vs Arduino

ESP32 Camera Driver 底層使用 ESP-IDF 框架,Arduino 的 esp_camera.h 是對 ESP-IDF driver 的封裝。如果需要更深度的控制,可以直接使用 ESP-IDF:

// ESP-IDF 直接操作 OV2640 SCCB 暫存器(不透過 Camera Driver)
#include "driver/i2c.h"
#include "soc/i2c_reg.h"
#include "hal/i2c_hal.h"

#define OV2640_ADDR     0x30  // SCCB 7-bit 地址左移一位
#define I2C_MASTER_NUM  I2C_NUM_0

esp_err_t ov2640_write_reg(uint8_t reg, uint8_t val) {
    i2c_cmd_handle_t cmd = i2c_cmd_link_create();
    i2c_master_start(cmd);
    i2c_master_write_byte(cmd, OV2640_ADDR | I2C_MASTER_WRITE, true);
    i2c_master_write_byte(cmd, reg, true);
    i2c_master_write_byte(cmd, val, true);
    i2c_master_stop(cmd);
    esp_err_t ret = i2c_master_cmd_begin(I2C_MASTER_NUM, cmd, 1000 / portTICK_PERIOD_MS);
    i2c_cmd_link_delete(cmd);
    return ret;
}

uint8_t ov2640_read_reg(uint8_t reg) {
    uint8_t val = 0;
    i2c_cmd_handle_t cmd = i2c_cmd_link_create();
    i2c_master_start(cmd);
    i2c_master_write_byte(cmd, OV2640_ADDR | I2C_MASTER_WRITE, true);
    i2c_master_write_byte(cmd, reg, true);
    i2c_master_start(cmd);  // SCCB Restart!
    i2c_master_write_byte(cmd, OV2640_ADDR | I2C_MASTER_READ, true);
    i2c_master_read_byte(cmd, &val, I2C_MASTER_NACK);
    i2c_master_stop(cmd);
    i2c_master_cmd_begin(I2C_MASTER_NUM, cmd, 1000 / portTICK_PERIOD_MS);
    i2c_cmd_link_delete(cmd);
    return val;
}

// 驗證感測器
void check_ov2640() {
    uint8_t pid = ov2640_read_reg(0x0A);  // Product ID
    uint8_t ver = ov2640_read_reg(0x0B);  // Revision
    printf("OV2640 PID=0x%02X VER=0x%02X
", pid, ver);
    // PID 應為 0x26 (OV2640)
}

投影機應用:ESP32-CAM 低功耗定時拍照

// ESP32-CAM 低功耗定時拍照 + Deep Sleep
// 每 30 分鐘拍照一次,發送 HTTP POST 到伺服器
#include <WiFi.h>
#include "esp_camera.h"
#include "esp_sleep.h"

#define uS_TO_S_FACTOR 1000000ULL
#define TIME_TO_SLEEP  1800  // 30 分鐘

void setup() {
    Serial.begin(115200);
    esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);

    // 初始化攝影機(同上)
    // ...

    // 連線 Wi-Fi
    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
    }

    // 拍照
    camera_fb_t *fb = esp_camera_fb_get();
    if (fb) {
        // 上傳照片到伺服器
        WiFiClient client;
        // HTTP POST ...
        esp_camera_fb_return(fb);
    }

    // 關閉 Wi-Fi 進入深睡
    WiFi.disconnect(true);
    esp_deep_sleep_start();
}

void loop() {}  // 永不執行

總結

ESP32-CAM 搭配 OV2640 是建構視覺 IoT 最經濟實惠的方案之一。透過 DVP 並列介面和內建 JPEG 編碼器,可在 ESP32 上實現即時 MJPEG 串流、定時拍照上傳、人臉辨識等應用。

選型參考:

  • VGA 即時串流(30 FPS):OV2640 + ESP32-CAM + JPEG + Wi-Fi 串流
  • 高解析度拍照(UXGA):OV2640 + SD 卡儲存 + 事後讀取
  • 低功耗定時監控:OV2640 + ESP32 Deep Sleep + HTTP 上傳
  • 人臉/物件辨識:OV2640 VGA + ESP32 + TensorFlow Lite(幀率約 1~3 FPS)
  • 更高畫質(5MP+):換用 OV5640 或支援 MIPI CSI 的 ESP32-S3
標籤: 教學
最後更新:2026 年 6 月 16 日

shi6a

這個人很懶,什麼都沒留下

點贊
< 上一篇

文章評論

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回覆

COPYRIGHT © 2026 0x6A Logbook. ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang