ESP32-S3 + OV3660 摄像头驱动踩坑全记录:从“找不到地址“到“稳定出图“ 📅 发布时间:2026/9/8 16:30:59 👁 浏览次数: 前言最近在玩ESP32-S3搭配OV3660摄像头模块本来以为照着开源例程改改引脚就能跑起来结果前前后后折腾了好几天踩了不少坑我还专门画了一块板子用来测试24pin的接口真的是太难焊了总是连锡。这篇文章记录了我从完全无法驱动到最终稳定拍照的完整过程希望对遇到类似问题的朋友有所帮助。注意以下的代码是运行在我自己画的一块板子板子的文件在https://gitcode.com/wonderful___day/ESP32OV3660可以嘉立创免费打板因为我把ESP32S3的引脚全部引出了IO19IO20给typec了EN也没有引出故而IO引脚的分配有点奇怪有兴趣复刻我的板子你可以自由分配如果是带摄像头的开发板阅读相应引脚后修改对应引脚的定义就可十分要注意开启OPI硬件环境主控ESP32-S3 (带 8MB OPI PSRAM自己画的一块板子)摄像头OV3660 (24Pin DVP接口)开发环境Arduino IDE这块板子设计了一个陀螺仪可以用来实现一些好玩的开发例如避障小车之类的问题一供电电压的迷思现象一开始我参考了 OV2640 的资料给摄像头的 DVDD 引脚提供了1.2V电压结果用万用表测量电压1.5V不对怎么会是1.5V呢。把摄像头一拔再一测就是1.2V。解决这其实是内部电路通过 I2C 信号线的 ESD 保护二极管获得了微弱上拉触发了内部稳压器启动。问题二I2C 扫描不到地址 (0x3C)现象供电没问题后I2C 扫描仍然找不到 OV3660 的默认地址0x3C。排查与发现尝试只连接SDA、SCL、XCLK三条线进行 I2C 扫描失败。意外发现当我额外连接了PCLK引脚后I2C 扫描成功找到了0x3C地址。原因分析PCLK 的存在可能触发了摄像头内部的正确上电时序或满足了软件驱动的初始化前提条件例如驱动中可能等待 PCLK 稳定后才认为硬件就绪。这说明 OV3660 对启动条件有一定要求不仅仅是供电和 I2C 通就能响应。解决确保SDA、SCL、XCLK、PCLK四根线全部正确连接即可正常扫描到 I2C 地址。最终完整驱动需要连接所有数据线和同步信号线但仅为了验证 I2C 通信这四根线是必须的前提是电源线与地线都连接了。问题三esp_camera_init 失败 - frame buffer malloc failed这个问题最要紧浪费了很多时间现象I2C 能扫描到地址了但调用esp_camera_init()时失败报错E (2629) cam_hal: cam_dma_config(300): frame buffer malloc failed E (2629) cam_hal: cam_config(384): cam_dma_config failed E (2630) camera: Camera config failed with error 0xffffffff排查过程摄像头需要大块连续内存来存放图像帧缓冲Frame BufferQVGA 分辨率约需 150KB更高分辨率则需要更大内存。这些内存需要分配在PSRAM上。在代码中添加 PSRAM 检测if(psramFound()){Serial.printf(PSRAM 已找到大小: %d 字节\n,ESP.getPsramSize());}else{Serial.println(PSRAM 未找到!);}结果打印出“PSRAM 未找到!”。原因在 Arduino IDE 中XIAO ESP32-S3 的 PSRAM 默认是关闭的且该板使用的是OPI (Octal) PSRAM与常见的 QSPI PSRAM 配置不同。解决在 Arduino IDE 的「工具」菜单中开发板→ 选择Seeed Studio XIAO ESP32-S3PSRAM→ 选择OPI PSRAM(注意不是Enabled或QSPI PSRAM)配置正确后再次运行psramFound()即可正确识别 8MB PSRAMesp_camera_init()顺利通过。问题四初始化成功但拍照失败现象esp_camera_init()返回成功但调用esp_camera_fb_get()拍照时返回空。排查检查硬件连接后发现我只连接了SDA、SCL、XCLK、PCLK四根线。初始化只需要控制通道I2C和时钟XCLK但拍照需要完整的数据通道DVP包括信号作用状态D0 ~ D78位数据总线❌ 未连接VSYNC垂直同步 (帧同步)❌ 未连接HREF水平同步 (行同步)❌ 未连接PCLK像素时钟✅ 已连接XCLK主时钟✅ 已连接SDA/SCLI2C/SCCB✅ 已连接解决将所有D0-D7 数据线、VSYNC、HREF全部连接到 ESP32-S3 对应的 GPIO并确保代码中的引脚定义与实际接线一致。最终成果接好所有数据线后成功输出 QVGA (320x240) JPEG 照片 ESP32-S3 OV3660/OV2640 摄像头驱动测试 PSRAM 已找到大小: 8386295 字节 初始化摄像头 [OK] esp_camera_init [INFO] 传感器型号: 0x3660 [INFO] 检测到 OV3660 [OK] 摄像头初始化完成 [INFO] 拍照成功: 320x240, 3585 bytes, format4 [OK] JPEG 文件头正确 (FF D8)问题5ESP32S3反复重启问题现象使用 ESP32-S3 配合 OV3660/OV2640 摄像头搭建 Web 视频流服务器时遇到频繁重启的问题。代码编译烧录正常摄像头初始化偶有失败Wi-Fi 连接后访问/stream页面时芯片很快复位。串口监视器能看到Brownout detector was triggered或rst:0xc等复位信息视频流卡顿甚至无法显示。起初我尝试软件层面的优化降低 XCLK 频率从 20MHz 到 10MHz 乃至 8MHz、在视频流循环中加入delay(50)让出 CPU、检查 PSRAM 配置这些调整有一定改善但未能根治。最终定位到根本原因——供电不足。我使用的是 CH340 USB 转串口模块为开发板供电该模块通常只能提供 5V/500mA 甚至更低的电流而 ESP32-S3 开启 Wi-Fi 并传输摄像头 JPEG 数据时瞬时峰值电流可达 600mA~1A电压跌落触发了芯片内部的欠压锁定BOR保护导致反复强制复位。解决还好我在设计开发板的时候设计了USB供电与LDO降压模块把5V降为3.3V为芯片提供了稳定的供电。额外建议若条件允许可将 XCLK 频率恢复至 10MHz 或 20MHz 以提升帧率串口调试时留意是否有 BOR 报错这是供电问题的直接信号。经验教训ESP32 摄像头项目供电永远是第一道防线优先保证 5V/2A 以上的稳定输入远比软件调参更关键。附完整引脚定义 (该引脚配置只说明使用这些引脚可以驱动摄像头不是针对任意一块开发板)我在开发的时候就遇到过这个疑问市面上有好多带ov3660的开发板他们板子对应引脚的IO是不一样的导致我问AI某某引脚一定是要是特定的IO口吗他总是回答的模棱两可导致我一气之下画了一块板子用来开发经过我的测试我认为不是特定的可以自由换但是要注意Spring引脚这玩意阴的很我有次开发时几个Spring引脚接了10k上拉有些放在底层有些放在顶层我焊完顶层烧录发现没毛等后面一焊接死活烧不上给我气的一个个底部元件一个个拆最后发现是某个Spring引脚的上拉导致启动异常解决倒也简单把那个引脚的上拉电阻拆了就可以了。摄像头信号GPIOSDA (SIO_D)17SCL (SIO_C)18XCLK8PCLK48VSYNC21HREF38D0 (Y2)45D1 (Y3)47D2 (Y4)14D3 (Y5)40D4 (Y6)5D5 (Y7)42D6 (Y8)41D7 (Y9)39RESET4PWDN3开发时我认为摄像头的引脚一定是要那个某某IO口其实实际上ESP32S3内置了IO矩阵理论上可以使用任意一个引脚作为摄像头的一个引脚但是要注意最好避免使用Spring引脚可能会影响ESP32S3的运行OV3660的24pin接口图这也是一个大坑比较难找遗留问题与优化方向高分辨率拍照失败目前 VGA 及以上分辨率拍照失败原因是高分辨率 RAW 帧需要更大的连续内存块8MB PSRAM 可能因碎片化无法分配足够大的连续空间。优化建议降低fb_count从 2 改为 1节省一半帧缓冲内存。提高 JPEG 质量数值jpeg_quality从 12 改为 20减少编码时内存占用。调整帧大小按需选择合适分辨率如 800x600 可用FRAMESIZE_SVGA。总结这次调试最大的收获是供电必须严格按数据手册OV3660 的 DVDD 是 1.5V也可以是 1.2V。ESP32-S3 的 PSRAM 配置有坑ESP32-S3 必须选OPI PSRAM不是普通的 Enabled。I2C 扫不到地址时检查所有可能影响芯片启动的信号PCLK 的存在也能影响 I2C 通信说明芯片内部状态机对启动条件有要求。初始化成功 ≠ 能拍照数据总线D0-D7和同步信号VSYNC/HREF一根都不能少。希望这篇文章能帮到正在折腾 OV3660 的你如果有问题欢迎在评论区交流。本次的代码我的代码仓库里面也有/** * ESP32-S3 OV3660/OV2640 摄像头 Web 服务器STA 模式 * * 引脚定义与您的连接完全一致 * PWDN 3 (必须拉低) * RESET -1 (未连接硬件上拉) * XCLK 9 * SIOD 7 * SIOC 6 * D7..D0 10,12,14,47,21,13,48,45 * VSYNC 8 * HREF 46 * PCLK 11 */#includeWiFi.h#includeWebServer.h#includeesp_camera.h// Wi-Fi 配置改为 STA 模式 constchar*ssida;// 改为您路由器的 SSIDconstchar*password12345678;// 改为您路由器的密码// 摄像头引脚定义 #defineCAM_PIN_PWDN3#defineCAM_PIN_RESET-1// 未连接#defineCAM_PIN_XCLK9#defineCAM_PIN_SIOD7#defineCAM_PIN_SIOC6#defineCAM_PIN_D710// Y9#defineCAM_PIN_D612// Y8#defineCAM_PIN_D514// Y7#defineCAM_PIN_D447// Y6#defineCAM_PIN_D321// Y5#defineCAM_PIN_D213// Y4#defineCAM_PIN_D148// Y3#defineCAM_PIN_D045// Y2#defineCAM_PIN_VSYNC8#defineCAM_PIN_HREF46#defineCAM_PIN_PCLK11// Web 服务器 WebServerserver(80);// 摄像头配置 camera_config_tconfig;// 初始化摄像头 boolinitCamera(){config.pin_pwdnCAM_PIN_PWDN;config.pin_resetCAM_PIN_RESET;// 显式设为 -1config.pin_xclkCAM_PIN_XCLK;config.pin_sccb_sdaCAM_PIN_SIOD;config.pin_sccb_sclCAM_PIN_SIOC;config.pin_d7CAM_PIN_D7;config.pin_d6CAM_PIN_D6;config.pin_d5CAM_PIN_D5;config.pin_d4CAM_PIN_D4;config.pin_d3CAM_PIN_D3;config.pin_d2CAM_PIN_D2;config.pin_d1CAM_PIN_D1;config.pin_d0CAM_PIN_D0;config.pin_vsyncCAM_PIN_VSYNC;config.pin_hrefCAM_PIN_HREF;config.pin_pclkCAM_PIN_PCLK;config.xclk_freq_hz10000000;// 尝试 20MHz如不稳定可改回 10000000config.ledc_timerLEDC_TIMER_0;config.ledc_channelLEDC_CHANNEL_0;config.pixel_formatPIXFORMAT_JPEG;config.frame_sizeFRAMESIZE_QVGA;// 320x240config.jpeg_quality12;// 降低画质提高流畅度0~63config.fb_count(psramFound())?2:1;esp_err_terresp_camera_init(config);if(err!ESP_OK){Serial.printf(摄像头初始化失败: 0x%x\n,err);returnfalse;}sensor_t*sesp_camera_sensor_get();s-set_brightness(s,0);s-set_contrast(s,0);s-set_saturation(s,0);s-set_whitebal(s,1);s-set_gain_ctrl(s,1);s-set_exposure_ctrl(s,1);s-set_hmirror(s,0);s-set_vflip(s,0);Serial.println(摄像头初始化成功);returntrue;}// 处理 /stream (MJPEG 视频流) voidhandleStream(){WiFiClient clientserver.client();String boundary123456789000000000000987654321;client.println(HTTP/1.1 200 OK);client.println(Content-Type: multipart/x-mixed-replace; boundaryboundary);client.println(Connection: close);client.println();while(client.connected()){camera_fb_t*fbesp_camera_fb_get();if(!fb){Serial.println(获取帧失败);break;}client.print(--boundary\r\n);client.print(Content-Type: image/jpeg\r\n);client.print(Content-Length: String(fb-len)\r\n\r\n);client.write(fb-buf,fb-len);client.print(\r\n);esp_camera_fb_return(fb);// 如果加了 delay请确保在这里添加// delay(50);}}// --- 这个右大括号不能少// 处理 /capture (单张拍照) voidhandleCapture(){camera_fb_t*fbesp_camera_fb_get();if(!fb){server.send(500,text/plain,拍照失败);return;}WiFiClient clientserver.client();client.println(HTTP/1.1 200 OK);client.println(Content-Type: image/jpeg);client.println(Content-Length: String(fb-len));client.println(Connection: close);client.println();client.write(fb-buf,fb-len);esp_camera_fb_return(fb);}// 根页面显示 STA 信息 voidhandleRoot(){String htmlRrawliteral(!DOCTYPE htmlhtmlheadmeta charsetUTF-8meta nameviewportcontentwidthdevice-width, initial-scale1titleESP32-S3 摄像头(STA)/titlestylebody{font-family:Arial;text-align:center;background:#1e1e2e;color:white;}img{max-width:100%;height:auto;border:2px solid #555;border-radius:10px;}.btn{display:inline-block;margin:10px;padding:12px24px;background:#4CAF50;color:white;text-decoration:none;border-radius:8px;}.status{color:#4CAF50;font-weight:bold;}/style/headbodyh1 实时监控(STA 模式)/h1img src/streamidstreamalt视频流加载中...bra classbtnhref/capturetarget_blank 抓拍/aa classbtnhref/stylebackground:#f44336; 刷新/ap 已连接到)rawliteral String(ssid) Rrawliteral( IP)rawliteral WiFi.localIP().toString() Rrawliteral(/pp classstatus✅ 摄像头已就绪/p/body/html)rawliteral;server.send(200,text/html,html);}// setup voidsetup(){Serial.begin(115200);delay(1000);Serial.println(\n启动摄像头 Web 服务器 (STA 模式));// 检查 PSRAMif(!psramFound()){Serial.println(⚠️ PSRAM 未启用图像可能不稳定请启用 PSRAM。);}else{Serial.printf(✅ PSRAM 大小: %d KB\n,ESP.getPsramSize()/1024);}// 初始化摄像头必须在 Wi-Fi 之前以免影响时序通常无严格要求但建议先初始化if(!initCamera()){Serial.println(摄像头初始化失败停止运行);return;}// -------- 改为 STA 模式连接路由器 --------WiFi.mode(WIFI_STA);WiFi.begin(ssid,password);Serial.printf(正在连接 Wi-Fi %s,ssid);intattempts0;while(WiFi.status()!WL_CONNECTEDattempts30){// 最多等待约 30 秒delay(500);Serial.print(.);attempts;}Serial.println();if(WiFi.status()WL_CONNECTED){IPAddress IPWiFi.localIP();Serial.println(✅ Wi-Fi 连接成功);Serial.printf( SSID: %s\n,ssid);Serial.printf( IP 地址: %s\n,IP.toString().c_str());Serial.printf( 在局域网内访问 http://%s 查看视频流\n,IP.toString().c_str());}else{Serial.println(❌ Wi-Fi 连接失败请检查 SSID 和密码);// 仍可启动服务器但无法访问也可选择停止运行return;}// 注册 Web 路由server.on(/,handleRoot);server.on(/stream,handleStream);server.on(/capture,handleCapture);server.begin();Serial.println(HTTP 服务器已启动);}// loop voidloop(){server.handleClient();// 可添加 Wi-Fi 重连机制非必须if(WiFi.status()!WL_CONNECTED){Serial.println(Wi-Fi 断开尝试重连...);WiFi.reconnect();delay(5000);}delay(1);}