简介:ESP32采集图片程序是一套基于Arduino框架编写的示例工程,面向物联网开发者、电子爱好者和刚接触ESP32图像采集的入门用户。程序解决的核心问题是:利用ESP32本身集成的Wi-Fi与摄像头接口,完成JPEG图像采集、通过浏览器远程预览,并将图片保存到SD卡。代码模块分工明确:3个.h头文件分别负责摄像头引脚定义、图像参数配置和Web页面结构,1个.ino主程序则串联摄像头初始化、抓拍、HTTP请求响应以及SD卡写入流程,用户只需按注释调整引脚即可适配OV2640等常见摄像头。压缩包共4个文件,大小仅45KB,轻量精简,便于阅读移植。该项目已有1296人学习下载,对希望快速实现小体积图像采集方案、了解ESP32摄像头与网络服务联动机制的人而言,是一份可以直接编译验证的高质量参考代码,也可在此基础上扩展定时抓拍、图片筛选等实用功能。
1. ESP32采集图片程序是什么:一个小芯片,也能稳定拍下VGA照片
最早我接触 ESP32 采集图片程序,是因为要给一个设备做定时拍照留档。当时团队分成两派,一派主张用带系统的板子,另一派说 ESP32 就够了。实际做完发现,ESP32 虽然不能像专用图像处理器那样流畅跑算法,但配合一颗摄像头模组,它完全能完成 JPEG 图采集、存 SD 卡、走 WiFi 上传这套流程,功耗还低,适合电池供电的巡检、门禁、环境监测这类场景。这篇文章就顺着一个最小可用的 ESP32 采集图片程序讲透:摄像头方案怎么选、驱动怎么初始化、图片数据怎么落地,以及我踩过的那些坑。新手能照着接上就跑,熟手也能对比一下参数和边界。
2. 选型与硬件连接:摄像头模组、引脚分配与供电
2.1 摄像头模组选型:OV2640为什么是默认选择
做 ESP32 采集图片程序,选摄像头不是看像素越高越好,而是看接口和输出格式。常见的小型摄像头模组分成两类:一类是直接输出 YUV/RGB 的并口摄像头,比如 OV7670;另一类是内置图像处理、可以直接出 JPEG 的,比如 OV2640。ESP32 本身没有硬件编码器,JPEG 压缩靠软件或者摄像头模组内部完成,所以选能直接输出 JPEG 的模组能省掉大量 CPU 资源和内存运行空间。
我一般首选 OV2640 模组,原因是它输出 JPEG 时 ESP32 几乎只做搬运,CPU 占用率低,抓帧速度也快。OV7670 没有 JPEG 编码,输出 RGB565 或 YUV422 时数据量非常大,一张 VGA 分辨率的 RGB565 图片就需要 614400 字节,而 ESP32 内部 SRAM 通常只有 320KB 左右,你不外挂 PSRAM 根本存不下;即使有 PSRAM,后续传输也要处理更大体积。OV7740 价格低但同样不输出 JPEG,性能和兼容性都不如 OV2640 好。所以如果不是特殊需求,就直接用 OV2640,省心。
下表是常用几款摄像头的对比,方便你结合预算和场景选型:
| 型号 | 最大分辨率 | JPEG输出 | 接口 | 适合场景 |
|---|---|---|---|---|
| OV2640 | 1600x1200 | 支持 | DVP并口 | ESP32 采集程序首选 |
| OV7670 | 640x480 | 不支持 | DVP并口 | 入门学习,出 BMP/RGB 大图 |
| OV7740 | 640x480 | 不支持 | DVP并口 | 低分辨率低成本场景 |
| OV5640 | 2592x1944 | 支持 | DVP并口 | 需要高清时用,但内存压力更大 |
选 OV2640 时还要注意模组上的晶振和供电。很多廉价的模组是 24MHz 晶振,ESP32 提供 XCLK 时钟时,20MHz 作为参考即可,不要给太高;供电电压必须是 3.3V,不能接 5V,否则会直接烧坏感光芯片。
2.2 关键硬件连接:引脚分配、PSRAM与供电
ESP32 与摄像头之间是 DVP 并口连接,包含时钟、帧同步、行同步、像素时钟、并行数据线以及 SCCB(类似 I2C)控制线。不同开发板的 GPIO 定义不同,但连线逻辑基本一致。下面是一套常用的引脚映射,大多数 ESP32 摄像头开发板都接近这个配置:
| 摄像头信号 | 方向 | ESP32 GPIO |
|---|---|---|
| XCLK | 时钟输出 | GPIO 21 |
| SIOD | SCCB数据 | GPIO 26 |
| SIOC | SCCB时钟 | GPIO 27 |
| D7 | 数据 | GPIO 35 |
| D6 | 数据 | GPIO 34 |
| D5 | 数据 | GPIO 39 |
| D4 | 数据 | GPIO 36 |
| D3 | 数据 | GPIO 15 |
| D2 | 数据 | GPIO 37 |
| D1 | 数据 | GPIO 38 |
| D0 | 数据 | GPIO 5 |
| VSYNC | 帧同步 | GPIO 18 |
| HREF | 行同步 | GPIO 23 |
| PCLK | 像素时钟 | GPIO 19 |
注意:上面引脚数字不是万能的,不同板子的 D0/D3 可能交换,甚至 GPIO36、39 这类输入只能做输入,不能输出控制信号。你在抄配置文件前一定要查自己板子的原理图,否则会出现“初始化偶尔成功、一拍就花屏”这种玄学问题。
硬件上还有两个容易被忽略的地方:一是 PSRAM,二是供电。OV2640 输出 JPEG 时一张 UXGA 照片约几十到两百 KB,虽然比 RGB 小,但 ESP32 内部 RAM 同时跑 WiFi、SD 卡驱动后剩余不多,所以必须选带 PSRAM 的模组(通常封装在板子背面)或者外接 SPI PSRAM。供电方面,摄像头上电瞬间电流很大,直接从 ESP32 的 3.3V LDO 引脚取电容易导致电压跌落,触发摄像头复位或者画面条纹。常见做法是提供独立的 3.3V 稳压输出,并在引脚旁放一个 100uF 电容稳压。
2.3 开发环境与编译参数:别忽略 PSRAM 开关
编写 ESP32 采集图片程序,我常用的是 ESP-IDF 或 Arduino-ESP32 环境。Arduino 上手快,采集图片的逻辑写起来直观;但无论哪个环境,都要在编译配置里启用 PSRAM,否则摄像头驱动在分配帧缓冲时会退回内部 RAM,最终导致初始化失败或花屏。
在 Arduino IDE 中,选择开发板型号时注意选带 PSRAM 选项的型号,例如带“PSRAM”字样的版本;另外还要确认“Partition Scheme”没有选成默认的最小分区。在 ESP-IDF 中,需要执行idf.py menuconfig,进入Component config → ESP32-Specific → Support for external, SPI-connected RAM勾选启用。这一步不做,你后续开esp_camera_fb_get()大概率只能用小分辨率, 高一点的设置直接返回空指针。
3. 驱动初始化与抓帧:从esp_camera到一张JPEG图片
3.1 camera_config_t 参数解析:别照抄,逐项理解
ESP32 官方摄像头驱动esp_camera把底层复杂度隐藏起来了,你只需要填一个camera_config_t结构体。下面是实际项目里我常用的初始化参数,先看代码再解释每项含义:
#include <esp_camera.h> #include <soc/soc.h> #include <soc/gpio_reg.h> static camera_config_t camera_config = { .pin_pwdn = -1, // 电源端引脚,不使用就填 -1 .pin_reset = -1, // 复位引脚,不使用就填 -1 .pin_xclk = 21, // 摄像头主时钟 .pin_sccb_sda = 26, // SCCB 数据线 .pin_sccb_scl = 27, // SCCB 时钟线 .pin_d7 = 35, // 并行数据线 D7 .pin_d6 = 34, .pin_d5 = 39, .pin_d4 = 36, .pin_d3 = 15, // 注意:很多板子 D3 与 PSRAM 复用 .pin_d2 = 37, .pin_d1 = 38, .pin_d0 = 5, .pin_vsync = 18, // 帧同步信号 .pin_href = 23, // 行同步信号 .pin_pclk = 19, // 像素时钟 .xclk_freq_hz = 20000000, // XCLK 20MHz .ledc_timer = LEDC_TIMER_0, .ledc_channel = LEDC_CHANNEL_0, .pixel_format = PIXFORMAT_JPEG, // 关键:直接输出 JPEG .frame_size = FRAME_SIZE_UXGA, // 1600x1200 .jpeg_quality = 12, // JPEG 质量,值越小质量越高 .fb_count = 2, // 帧缓冲数量 .grab_mode = CAMERA_GRAB_LATEST, // 始终抓取最新帧 };逻辑说明:pin_pwdn和pin_reset如果不接控制线,必须填-1,否则驱动会去操作一个不存在的 GPIO 导致初始化卡死。xclk_freq_hz给 20MHz 比较稳妥,频率太高会让摄像头输出不稳定。pixel_format用PIXFORMAT_JPEG,这样esp_camera_fb_get()返回的帧数据直接就是 JPEG 字节流,不需要你再去编码。frame_size决定了采集分辨率,但 UXGA 需要较大 PSRAM,如果只有 2MB PSRAM,建议先用FRAME_SIZE_SVGA(800x600)跑通,再逐步调高。jpeg_quality的取值范围一般是 0 到 63,实际项目中 12 是一个平衡点:太高文件大,太低画面会出明显块状噪声。fb_count设为 2 表示双缓冲,第一部分片正在上传时,驱动能继续用第二片缓冲采集,提高帧率;内存紧张时改为 1 最保险。grab_mode用CAMERA_GRAB_LATEST可以丢弃还没被取走的旧帧,避免拍照时等太久。
3.2 初始化与抓帧代码骨架
初始化摄像头是执行esp_camera_init(),这一步会把上面配置写进驱动并启动像素时钟。初始化成功后,抓一帧照片只需要三步:取帧、使用、释放。
esp_err_t init_camera() { esp_err_t err = esp_camera_init(&camera_config); if (err != ESP_OK) { Serial.printf("camera init failed: 0x%x\n", err); return err; } return ESP_OK; } bool take_photo(camera_fb_t **fb) { *fb = esp_camera_fb_get(); if (*fb == NULL) { Serial.println("fb_get returned NULL"); return false; } return true; } void release_photo(camera_fb_t *fb) { esp_camera_fb_return(fb); }逻辑说明:esp_camera_fb_get()返回的camera_fb_t结构体里有data和len两个关键字段,分别指向 JPEG 数据起始地址和字节长度。用完必须调用esp_camera_fb_return(fb)释放缓冲,否则做长周期采集程序时每拍一张就漏一点内存,几百张后必然出现fb_get返回空指针。取帧失败的原因通常是硬件不稳定或者上一次缓冲没释放,所以实际产品里我会加一个重试逻辑:连续失败三次就调用esp_camera_deinit()再重新初始化。
3.3 三种采集模式:单拍、定时和外部触发
程序不仅要会抓一帧,还要适应不同使用方式。常见的 ESP32 采集图片程序有三种模式。
单拍模式最简单:收到串口指令或者按键按下后,取一帧、保存、释放,流程像上面骨架代码那样。定时模式适合巡检设备,用millis()做间隔判断即可;如果要求精确秒级周期,用esp_timer_create创建定时器更可靠。外部触发模式适合接人体红外、门磁开关等传感器,但要注意不能在 GPIO 中断函数里调用esp_camera_fb_get(),因为取帧可能涉及 PSRAM 和 SCCB 操作,耗时不可控,会导致系统崩溃。正确做法是中断里只置一个标志位,主循环里检测到标志再取帧。
定时采集的核心代码如下:
unsigned long previousMillis = 0; const unsigned long interval = 30000; // 每 30 秒拍一张 void loop() { if (millis() - previousMillis >= interval) { previousMillis = millis(); camera_fb_t *fb = NULL; if (take_photo(&fb)) { // 这里可以写 SD 卡、上传到服务器或做其他处理 release_photo(fb); } } }这段代码注意:millis()累计超过 49 天会回绕,长周期运行建议用esp_timer_get_time()返回微秒计数值来替代。另外,如果拍照处理逻辑本身耗时超过interval,下次触发会立即执行,造成连环拍照,这种情况需要加入“忙”标志跳过拍不动的周期。
4. 图片数据出去的路:SD卡、WiFi和串口三种落地方式
采集到的 JPEG 数据如果没有出路,程序就没有意义。实际项目中,图片一般有三条出口:存进 SD 卡、通过 WiFi 上传到服务器、走串口输出给上位机。三种方式各有适用场景,也可以并存。
4.1 存入SD卡:文件名与格式策略
SD 卡方案适合离线采集,比如农业田间记录、设备状态照片留档。驱动可以用SD_MMC(走 SDMMC 外设,速度更快)或SD库(走 SPI,兼容性更好)。我用 ESP32 时优先用SD_MMC,因为它有大带宽,4 线模式比 SPI 快好几倍;如果板子上没有集成 SDMMC 引脚或引脚冲突,才退到 SPI。
#include <SD_MMC.h> #include <time.h> bool save_jpeg_to_sd(camera_fb_t *fb) { if (!SD_MMC.begin()) { Serial.println("SD Card Mount Failed"); return false; } time_t now = time(NULL); struct tm *timeinfo = localtime(&now); char path[64]; strftime(path, sizeof(path), "/IMG_%Y%m%d_%H%M%S.jpg", timeinfo); FILE *file = fopen(path, "wb"); if (!file) { Serial.printf("open file failed: %s\n", path); SD_MMC.end(); return false; } fwrite(fb->data, 1, fb->len, file); fclose(file); Serial.printf("saved: %s, size=%u\n", path, fb->len); SD_MMC.end(); return true; }逻辑说明:这段代码先把 SD 卡挂载,然后根据时间戳生成文件名,再以二进制写方式打开文件。使用strftime的好处是文件名完全可预测,按时间排序就是拍摄顺序。需要注意,如果你的程序没有联网校正时间,time(NULL)返回的是编译时或开机默认时间,所有文件名都会重叠,第二天会把前一天的文件覆盖。解决办法是开机后先做 NTP 对时,或者接一个外部 RTC。另一个细节是SD_MMC.end()每次拍照都调用会比较耗时,如果你每 30 秒拍一张,可以只在启动时挂载一次,主循环里不卸载,保持文件系统打开状态。
4.2 WiFi上传:让服务器收到JPEG
需要实时远程看图时,WiFi 上传是首选。做法是用HTTPClient库把 JPEG 二进制数据作为 POST body 发给服务器。上传之前要让 ESP32 连上 WiFi,并且服务器的接口能接收image/jpeg类型数据。
#include <WiFi.h> #include <HTTPClient.h> bool upload_jpeg(camera_fb_t *fb, const char *url) { HTTPClient http; http.setTimeout(10000); if (!http.begin(url)) { Serial.println("http begin failed"); return false; } http.addHeader("Content-Type", "image/jpeg"); int code = http.POST(fb->data, fb->len); bool ok = false; if (code > 0) { Serial.printf("upload status code=%d\n", code); ok = (code == 200 || code == 201); } else { Serial.printf("upload failed: %s\n", http.errorToString(code).c_str()); } http.end(); return ok; }逻辑说明:http.POST(fb->data, fb->len)可以发送任意二进制数据,不必先把 JPEG 转成 Base64,省掉很多转换开销和内存占用。setTimeout很重要,上传一张 100KB 的图片如果网络信号差,可能耗时几秒到几十秒,不设超时程序容易一直卡在 POST 调用里。服务器端如果只是接收并保存文件,注意接口要能读取原始 body 而不是表单字段。另外,上传过程不能拿到锁或者被其他任务打断,建议这个函数只在主循环中调用,避免重入。
更高阶的做法是分块或断点续传,但多数场景不需要。如果每次拍照后上传耗时长,可以先把 JPEG 存在 PSRAM 里的队列中,由另一个任务负责上传,这样定时拍照的节奏不会被打乱。
4.3 串口传输:调试和低速场景
串口传输看起来简单,但在正式项目里适合两类情况:一类是调试时把图片拉到电脑上确认画面内容,另一类是整机型产品里 ESP32 只负责采集,主控 MCU 通过串口收图并打包处理。ESP32 的 UART 硬件缓冲区只有 256 字节左右,直接Serial.write(fb->data, fb->len)会把数据耗干,所以串口传输必须带帧协议,让接收方知道一帧从哪里开始、到哪里结束。
const uint8_t FRAME_HEADER[] = {0xAA, 0x55, 0x10}; const uint8_t FRAME_FOOTER[] = {0x55, 0xAA}; void send_jpeg_over_serial(camera_fb_t *fb) { // 发送4字节文件长度(小端),然后发图片数据,最后发帧尾 uint32_t len = fb->len; Serial.write(FRAME_HEADER, sizeof(FRAME_HEADER)); Serial.write((uint8_t *)&len, 4); size_t sent = 0; while (sent < fb->len) { size_t chunk = min(256, fb->len - sent); Serial.write(fb->data + sent, chunk); sent += chunk; delay(1); // 给 UART 一点时间刷缓冲 } Serial.write(FRAME_FOOTER, sizeof(FRAME_FOOTER)); }逻辑说明:帧头使用固定字节AA 55 10,接收方收到这个模式就认为一帧开始,再读 4 字节小端长度,然后连读len字节。在 while 循环里分块发送是因为Serial.write一次发大量数据时会被阻塞,发送 256 字节后主动延时 1ms,让 UART FIFO 有足够时间搬数据。波特率 921600 下,一张 100KB JPEG 大约耗时 1 秒,如果只是调试可以用,但不要指望用它做高吞吐传输。上位机的串口接收模块需要按协议解析,并处理可能的串口丢字节情况,这里就不展开了。
5. 避坑指南:ESP32采集图片最常见的5个翻车现场
5.1 花屏、绿屏:PSRAM未正确启用
现象:摄像头初始化成功,小分辨率能出图,分辨率调高后画面出现大块绿屏、彩色条纹,或者输出一半图像后全黑。
原因:摄像头的帧缓冲需要较大连续内存,ESP32 内部 SRAM 不足时,驱动会退化到低分辨率或输出损坏的数据。更常见的是板子明明带 PSRAM,但你的工程没启用,默认使用内部内存。
解决:在编译配置里打开 PSRAM 支持。Arduino 环境选择带 PSRAM 的开发板选项;ESP-IDF 环境在menuconfig里开启Support for external, SPI-connected RAM,并且确认CONFIG_SPIRAM_USE_MALLOC是打开的,因为esp_camera通过堆分配帧缓冲。另外,有些开发板需要先把 GPIO16/17 用于 PSRAM 访问,不能同时复用到摄像头数据线,检查引脚配置是否冲突。
5.2 初始化失败:GPIO引脚冲突与摄像头供电不足
现象:esp_camera_init()返回ESP_ERR_NOT_FOUND,或者返回成功但Serial打印里 SCCB 通信失败,摄像头像死了一样没有反应。
原因:摄像头模组的 SIOD/SIOC 脚没有正确上拉,或者这两根脚被 SD 卡、LED 等其他外设占用,SCCB 信号无法送达感光芯片。供电不足也是高频原因,摄像头工作时所需电流峰值可达 100mA 以上,直接由开发板的 AMS1117 供电时电压可能掉到 3.0V 以下。
解决:先用万用表量摄像头电源引脚是否稳定在 3.3V 附近;如果电压低于 3.1V,就外接单独稳压模块。再检查原理图确认所有 GPIO 定义没被飞线接反,特别是 SIOD 和 SIOC 两者不能互换。最后给 XCLK 引脚加一个外部时钟验证信号:如果不做,可以用逻辑分析仪看是否有 20MHz 方波。
5.3 fb_get 返回空指针:帧缓冲未及时释放
现象:刚开机时拍照正常,连续拍几十张后esp_camera_fb_get()返回 NULL,程序如果能重试可能又恢复,但过一会儿再次失败。
原因:内存泄漏或帧缓冲被旧帧占满。很多新手在拍照后忘了调用esp_camera_fb_return(),或者在使用camera_fb_t时把fb指向了新地址而原始地址没返回,导致驱动没有可用缓冲。
解决:严格做到一条规则:每次esp_camera_fb_get()成功,无论你有没有保存完,都必须对应一次esp_camera_fb_return()。在测试程序里,每拍一张就打印空闲堆大小ESP.getFreeHeap(),如果数值持续下降,说明还有没释放的缓冲。同时把fb_count从 1 调到 2,并使用CAMERA_GRAB_LATEST,能减少因为上一帧还在使用而无法抓新帧的概率。
5.4 JPEG文件尺寸超预期:quality参数并不是文件大小旋钮
现象:把jpeg_quality从 12 改成 30,以为文件会缩小一半,结果有些照片反而更大;在白色背景的场景下文件很大,在复杂纹理下又很小。
原因:JPEG 压缩质量和最终文件大小不是简单正比。质量参数控制的是量化步长,画面本身的高频信息量决定输出字节数。在纯色背景下,即使质量调很低,图片会迅速压缩,但画质损失明显;而复杂纹理下,质量调高会让文件爆炸。
解决:不把jpeg_quality当文件大小调节旋钮。你要限制文件大小时,先拍一帧 JPEG,如果fb->len超过阈值,就降低frame_size或者主动把图片缩小后再编码。如果必须要固定文件大小,考虑保存 BMP 或者统一缩放到固定分辨率,在 ESP32 上做缩放可以用img_convert或者先以低frame_size采集。质量参数保持 8~15 区间即可,不要为了存储空间盲目调低。
5.5 SD卡写入失败且卡死:SPI模式选错CS或供电不稳
现象:SD_MMC.begin()偶尔失败,或者拍照保存前 SD 卡还能识别,执行fopen写文件时程序卡住,最后看门狗重启。
原因:SD_MMC 库在 4 线 SDMMC 模式下占用固定的 GPIO 引脚(比如 CMD、CLK、D0-D3),如果这些引脚被摄像头或 LED 占用,卡检测会失败。另一个原因是供电,SD 卡写入电流大,而且瞬态波动明显,3.3V 稳压不足会触发卡内部电压监控而超时。
解决:先用单独的 SD 读卡器格式化卡为 FAT32,分配单元选默认,不要用 exFAT,很多库不支持。然后在 ESP32 上仅接 SD 卡不接摄像头,跑官方例程验证读写,排除摄像头引脚冲突。如果引脚不够用才用 SPI 模式,此时必须明确指定 CS 引脚,例如SD.begin(SS),注意 CS 不能是输入-only 引脚(如 GPIO34-39)。供电上给 SD 卡在 VCC 引脚加 22uF 陶瓷电容,或者用独立 3.3V 电源供电。
6. 进阶技巧:定时抓拍与图片时间戳的完整处理
定时抓拍是 ESP32 采集图片程序最常见的需求,但真正做好需要三个细节:时间同步、周期准、图片命名可追溯。我现在的做法是开机先连 WiFi,用 NTP 同步时间,然后创建定时器,每次拍完把时间戳和图片一并上报或存储。这样即使断电重启,文件名也不会乱。
NTP 对时和定时器的核心代码可以这样组织:
#include <esp_sntp.h> #include <esp_timer.h> void setup_time() { configTime(8 * 3600, 0, "ntp.aliyun.com", "pool.ntp.org"); // 等待时间同步完成 time_t now = 0; while (now < 100000) { delay(500); time(&now); } } void take_photo_task(void *arg) { for (;;) { camera_fb_t *fb = NULL; if (take_photo(&fb)) { char path[64]; time_t now = time(NULL); struct tm *t = localtime(&now); strftime(path, sizeof(path), "/IMG_%Y%m%d_%H%M%S.jpg", t); save_jpeg_to_sd_with_path(fb, path); release_photo(fb); } vTaskDelay(pdMS_TO_TICKS(30000)); // 30 秒后拍下一张 } }逻辑说明:configTime的第一个参数是时区秒数,东八区传8 * 3600;第三个之后的参数是 NTP 服务器域名。等待时间同步的 while 循环里,用now < 100000判断时间戳是否已经是 1970 年之后,如果没有同步成功,文件名的年份会是 1970。vTaskDelay比delay更可靠,因为它会让出 CPU 给 WiFi 任务。定时任务里我做了一个优化:先把照片存到 SD 卡,再在另一个任务里读取并上传,这样拍完不会卡住定时周期。
还有一个值得养成的习惯:在每次拍照前后用esp_timer_get_time()记录实际时间戳,和 NTP 时间做差值,如果在野外长时间运行出现漂移,就在凌晨空闲时段重新对时。我之前有一台设备因为没做时间校准,一天中拍的照片全部用了同一个时间基准,后来排查发现所有文件名只差秒数,覆盖得让人心疼。另一个教训是不要把保存路径写死在程序里,最好把日期作为目录名,例如/20240612/xxx.jpg,方便按天归档。
如果你在做类似设备,建议保存完一张图片后先验证文件大小,比如fseek到末尾取长度,跟fb->len对比。如果匹配不上,大概率是写入中途掉了数据,这时可以重新补拍一次。这套习惯帮我少跑了很多现场,也让我敢把 ESP32 采集图片程序放在无人值守的环境里跑。希望帮到你。
本文还有配套的精品资源,点击获取