用 ESP32-S3 做行车记录仪(简易版)
🚗💡 想象一下:你正在开车,突然前方有人违规变道——可你没有行车记录仪?别急,今天咱们不买成品, 自己动手做一个!
而且不用什么高端芯片、专业视频编码器。主角只是一块不到30块钱的 ESP32-S3 开发板 ,加上一个 OV2640 摄像头和一张 microSD 卡,就能实现“拍照式”连续录像功能。虽然达不到1080P 60帧的专业水准,但作为教育项目、原型验证或低功耗监控设备,它足够惊艳。
更妙的是,这个系统还能接入 Wi-Fi 实现远程查看,甚至未来加个 TensorFlow Lite 模型,让它识别行人、检测碰撞……是不是有点智能驾驶那味儿了?
下面,我们就从零开始,一步步拆解如何用这块“平民神板”打造属于你的第一台嵌入式视觉记录装置。
🛠️ 硬件选型:为什么是 ESP32-S3 + OV2640?
在动手之前,得先搞清楚:为啥非要用 ESP32-S3?难道 STM32 不行吗?树莓派 Pico 不香吗?
🔍 主控之争:MCU 能不能干视频的活?
传统观念里,图像处理是 DSP 或 ARM Cortex-A 级处理器的专属领地。毕竟要实时采集、压缩、存储数据流,这对算力、内存、外设都有极高要求。
但 ESP32-S3 的出现,打破了这一边界。
它不是普通的 Wi-Fi 模块,而是集成了 双核 Xtensa LX7 处理器(最高240MHz)+ 浮点单元(FPU)+ 向量指令扩展 + JPEG 硬件加速引擎 的全能选手。最关键的是,Espressif 官方维护了一个成熟的 esp-camera 驱动库,让摄像头支持变得异常简单。
相比之下:
- STM32F4/F7 :虽有 FSMC 接口支持 DVP 摄像头,但缺乏硬件 JPEG 编码,全靠 CPU 软编,效率低且发热严重;
- Raspberry Pi Pico :RP2040 性能不错,但无 Wi-Fi/BLE,外接模块复杂,生态对摄像头支持弱;
- 树莓派 Zero W :能跑 Linux,支持 USB 摄像头,但功耗高、启动慢、成本翻倍;
而 ESP32-S3 在 性价比、集成度、开发便利性 上找到了绝佳平衡点。尤其是它的 硬件 JPEG 引擎 ,直接把原始图像压缩成 JPEG 流,CPU 几乎不参与编码过程——这才是我们能用 MCU 实现“类视频”记录的核心秘密。
✅ 小贴士:别被“MCU不能做视频”吓住。只要分辨率不高、帧率可控、压缩交给硬件,很多场景下完全可行!
👁️🗨️ 摄像头选择:OV2640 凭啥这么受欢迎?
说到摄像头模块,OV2640 几乎成了 ESP32 社区的“御用传感器”。原因很简单:
- 支持 最大 1600×1200 分辨率(UXGA)
- 内建 ISP 和 硬件 JPEG 编码
- 输出格式多样:JPEG / YUV / RGB565 / RAW
- 接口标准:DVP 并行 + SCCB 控制(类似 I²C)
- 成本极低,批量价低于 $2
更重要的是,它可以工作在 独立 JPEG 模式 ——也就是说,图像一旦被捕获,就由 OV2640 自己完成压缩,然后通过并行总线把 JPEG 数据直接扔给 ESP32-S3。主控只需要“接收+转发”,几乎不消耗计算资源。
这就好比你雇了个摄影师,他不仅会拍照,还会修图、裁剪、打包发邮件——你只负责收件和存档就行。省心又高效!
不过也别以为它是万能的。OV2640 的自动白平衡和曝光算法比较基础,在逆光或夜间环境下容易过曝或欠曝。如果你追求画质,可以后期手动调参,或者换上 OV5640(500万像素),但它对时序要求更高,调试难度陡增。
对于我们这种“先跑通再优化”的项目来说,OV2640 是最佳起点。
💾 存储方案:microSD 卡为何仍是首选?
既然是记录仪,就得能持久保存数据。RAM 显然不够,Flash 又太小还怕频繁写入。怎么办?
答案就是: microSD 卡 + FAT32 文件系统 。
优点一目了然:
- 容量大:8GB~128GB 随便选
- 成本低:一张 Class10 卡才十几块
- 即插即用:电脑可以直接读取
- 兼容性强:ESP-IDF 原生支持 SDMMC/SPI 接口
ESP32-S3 支持两种访问方式:
| 方式 | 速度 | 引脚占用 | 适用场景 |
|---|---|---|---|
| SPI 模式 | ~20Mbps | 6根线 | 简单连接,兼容性好 |
| SDMMC 模式 | ~45Mbps(4-bit) | 6根线 | 高速写入推荐 |
我们当然选 SDMMC 4-bit 模式 ,毕竟要持续写入图片流,速度越快越好。
而且 FAT32 是跨平台通用格式,拔卡即看,无需额外转换工具。这对于后期回放、取证分析非常友好。
⚠️ 注意事项:
- 使用高质量 TF 卡(建议 Class10 UHS-I)
- 避免频繁断电导致文件系统损坏
- 可加入热插拔检测 GPIO 中断
⚙️ 系统架构设计:三层模型搞定核心流程
整个系统的逻辑其实很清晰,可以用三个层次来概括:
[ 拍摄层 ] → [ 处理层 ] → [ 存储层 ]
OV2640 ESP32-S3 microSD
再加上一个可选的 通信层 (Wi-Fi/OTA),就能实现远程管理和事件上传。
📸 图像采集:DVP 接口怎么接?
OV2640 使用的是 DVP(Digital Video Port)并行接口,共需连接约 12 根线:
| 信号线 | 功能说明 |
|---|---|
| PWDN / RESET | 电源控制与复位 |
| XCLK | 输入主时钟(通常20MHz) |
| SIOD / SIOC | SCCB 控制接口(类似 I²C) |
| VSYNC | 垂直同步信号(每帧一次) |
| HREF | 行有效标志 |
| PCLK | 像素时钟(每个像素跳变一次) |
| Y0~Y7 | 8位数据线(RGB565/JPEG模式下复用) |
其中 XCLK 由 ESP32 提供,一般接 GPIO10,并配置为 LEDC 通道输出固定频率方波。
⚠️ 关键布线建议:
- 所有 DVP 数据线尽量等长,避免信号偏移
- 远离 Wi-Fi 天线和电源走线,减少干扰
- 若使用排线,建议使用屏蔽线或双绞线
幸运的是,市面上有很多现成模块(比如 AI Thinker ESP32-CAM)已经帮你焊好了这些连线,直接插上去就能用,大大降低了入门门槛。
🖼️ 图像处理:如何启用硬件 JPEG 编码?
这是整个项目最关键的一步。
默认情况下,摄像头输出的是未压缩的 RGB 或 YUV 数据,每帧 VGA(640×480)大约需要 600KB 内存——对于只有几百KB PSRAM 的 ESP32 来说,根本扛不住。
但我们可以通过设置 pixel_format = PIXFORMAT_JPEG ,让 OV2640 自己完成 JPEG 编码,最终传给主控的只是一个压缩后的数据包,大小通常在 20~60KB 之间,节省了超过90%的空间!
来看一段关键初始化代码:
#include "esp_camera.h"
#define XCLK_GPIO_NUM 10
#define SIOD_GPIO_NUM 4
#define SIOC_GPIO_NUM 5
// ...其他引脚定义略...
void setup_camera() {
camera_config_t config;
config.pin_pwdn = -1;
config.pin_reset = -1;
config.pin_xclk = XCLK_GPIO_NUM;
config.pin_sscb_sda = SIOD_GPIO_NUM;
config.pin_sscb_scl = SIOC_GPIO_NUM;
config.pin_d0 = 16; // Y2
config.pin_d1 = 17; // Y3
// ...依次映射 D0~D7 到对应 GPIO...
config.pin_pclk = 19;
config.pin_vsync = 15;
config.pin_href = 14;
config.xclk_freq_hz = 20000000; // 20MHz 主频
config.pixel_format = PIXFORMAT_JPEG; // 启用硬件 JPEG
config.frame_size = FRAMESIZE_VGA; // 640x480
config.jpeg_quality = 12; // 质量越高数值越小(10~30)
config.fb_count = 1; // 单缓冲区,节省内存
esp_err_t err = esp_camera_init(&config);
if (err != ESP_OK) {
Serial.printf("Camera init failed: 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=正常, 1=黑白, ...
s->set_wb_mode(s, 0); // 白平衡: 0=自动, 1=晴天, 2=阴天...
}
📌 解读几个重要参数:
-
jpeg_quality = 12:值越小质量越高,但文件越大。实测 10~14 是清晰与体积的最佳平衡点。 -
fb_count = 1:只分配一个帧缓冲区。这意味着你必须尽快释放fb,否则下一帧会被阻塞。 -
frame_size = FRAMESIZE_VGA:VGA(640×480)是个不错的折中选择。更高如 SVGA/UXGA 会导致帧率骤降;更低如 QQVGA 又看不清车牌。
一旦初始化成功,就可以通过以下方式获取图像:
camera_fb_t *fb = esp_camera_fb_get();
if (fb) {
printf("Got frame: %dx%d, size=%u bytes\n", fb->width, fb->height, fb->len);
// TODO: save to SD card
esp_camera_fb_return(fb); // 必须释放!否则内存泄漏
}
注意!拿到 fb 后一定要记得调用 esp_camera_fb_return(fb) ,否则缓冲区不会回收,很快就会崩溃。
💾 数据存储:如何高效写入 microSD 卡?
有了 JPEG 数据,下一步就是把它写进 SD 卡。
ESP32 支持两种驱动方式: SD_MMC 和 SD_SPI 。我们优先使用 SD_MMC ,因为它速度更快、延迟更低。
接线方式如下(以 1-bit 模式为例):
| ESP32 GPIO | SD Card Pin | 功能 |
|---|---|---|
| 13 | DAT0 | 数据线 |
| 14 | CMD | 命令线 |
| 15 | CLK | 时钟线 |
| 2 | CD/Det | 卡检测(可选) |
| 3.3V / GND | 电源引脚 | 供电 |
代码实现也很简洁:
#include "FS.h"
#include "SD_MMC.h"
void setup_sdcard() {
if (!SD_MMC.begin("/mmc", true)) {
Serial.println("❌ SD Card Mount Failed");
return;
}
uint64_t cardSizeMB = SD_MMC.cardSize() / (1024 * 1024);
Serial.printf("✅ SD Card Size: %llu MB\n", cardSizeMB);
// 创建目录(如果不存在)
if (!SD_MMC.exists("/record")) {
SD_MMC.mkdir("/record");
}
}
void save_image(camera_fb_t *fb, int index) {
char filename[64];
sprintf(filename, "/mmc/record/IMG_%04d.jpg", index);
File file = SD_MMC.open(filename, FILE_WRITE);
if (file) {
file.write(fb->buf, fb->len);
file.close();
Serial.printf("💾 Saved: %s (%u B)\n", filename, fb->len);
} else {
Serial.printf("❌ Failed to write %s\n", filename);
}
}
这里有几个性能优化技巧:
- 预创建目录 :避免每次写入都检查路径是否存在
- 合理命名规则 :用递增编号方便排序,也可以改成时间戳格式
YYYYMMDD_HHMMSS.jpg - 避免频繁 open/close :虽然每次新建文件开销不大,但如果帧率高,建议批量写入或使用 ring buffer 缓冲
🔄 工作流程:从开机到循环录制
现在所有模块都准备好了,来看看整体运行逻辑:
┌─────────────┐
│ Power On │
└──────┬───────┘
↓
┌─────────────────┐
│ 初始化 PSRAM/Flash │
└──────┬──────────┘
↓
┌─────────────────┐
│ 初始化摄像头驱动 │
│ → 设置为 JPEG 模式 │
└──────┬──────────┘
↓
┌─────────────────┐
│ 挂载 SD 卡(SD_MMC)│
│ → 检查容量 & 健康度 │
└──────┬──────────┘
↓
┌────────────────────────────┐
│ 主循环 │
│ while(1) { │
│ fb = esp_camera_fb_get(); │
│ save_image(fb, idx++); │
│ esp_camera_fb_return(fb); │
│ delay(100); // ≈10fps │
│ } │
└────────────────────────────┘
目标帧率设为 10fps ,也就是每秒拍10张照片。VGA 分辨率下每张约 40KB,则每秒写入约 400KB,一分钟就是 24MB,一小时约 1.4GB——一张 16GB 的卡能录将近12小时。
听起来不错?但实际中你会发现一个问题: 偶尔会丢帧或卡顿 。
为什么?
因为 SD_MMC.open() 和 file.write() 是阻塞操作,耗时可能高达几十毫秒。在这期间,新的图像已经在产生了,但由于 fb_count=1 ,旧的还没释放,新的只能等待甚至被丢弃。
🛠️ 性能优化实战:告别丢帧与卡顿
要想稳定运行,必须解决 IO 阻塞问题。以下是几种实用策略:
✅ 方法一:异步写入 + FreeRTOS 队列
最有效的办法是引入任务队列机制:
#include "freertos/queue.h"
QueueHandle_t xQueue;
void writer_task(void *pvParameters) {
camera_fb_t *fb;
int idx = 0;
while (1) {
if (xQueueReceive(xQueue, &fb, portMAX_DELAY)) {
char filename[64];
sprintf(filename, "/mmc/record/IMG_%04d.jpg", idx++);
File file = SD_MMC.open(filename, FILE_WRITE);
if (file) {
file.write(fb->buf, fb->len);
file.close();
}
esp_camera_fb_return(fb); // 记得释放!
}
}
}
// 在 setup() 中创建任务
xTaskCreate(writer_task, "sd_writer", 4096, NULL, 5, NULL);
主循环改为:
void loop() {
camera_fb_t *fb = esp_camera_fb_get();
if (fb && xQueueSend(xQueue, &fb, 10) != pdTRUE) {
// 发送失败 → 队列满 → 直接释放
esp_camera_fb_return(fb);
}
delay(100); // 控制帧率
}
这样就把耗时的文件操作移到后台执行,主循环不再卡住,大幅提升了稳定性。
✅ 方法二:循环录制(Cyclic Recording)
另一个现实问题是:卡满了怎么办?
我们不可能无限写下去。理想做法是实现“循环录制”——当空间不足时,自动覆盖最早的文件。
有两种思路:
方案 A:命名轮询法(推荐)
假设最多存 1000 张图(IMG_0000 ~ IMG_0999),用模运算循环覆盖:
int current_index = 0;
const int MAX_FILES = 1000;
void save_image_rolling(camera_fb_t *fb) {
char old_file[64], new_file[64];
sprintf(old_file, "/mmc/record/IMG_%04d.jpg", current_index);
sprintf(new_file, "/mmc/record/IMG_%04d.jpg", current_index);
// 删除旧文件(若存在)
if (SD_MMC.exists(old_file)) {
SD_MMC.remove(old_file);
}
// 写入新文件
File file = SD_MMC.open(new_file, FILE_WRITE);
if (file) {
file.write(fb->buf, fb->len);
file.close();
}
current_index = (current_index + 1) % MAX_FILES;
}
简单粗暴,但要注意文件删除也可能失败(如卡只读),需加错误处理。
方案 B:LRU 索引表(高级玩法)
维护一个 JSON 或二进制索引文件,记录每张图的时间戳、状态、是否锁定等信息。适合要做“紧急视频保护”的场景。
✅ 方法三:添加看门狗防止死机
长时间运行难免遇到异常。建议开启 任务看门狗(TWDT) 或硬件 WDT:
#include "esp_task_wdt.h"
// 注册当前任务到看门狗(超时5秒)
esp_task_wdt_add(NULL);
void loop() {
esp_task_wdt_reset(); // 别忘了喂狗!
camera_fb_t *fb = esp_camera_fb_get();
if (fb) {
save_image(fb, counter++);
esp_camera_fb_return(fb);
}
delay(100);
}
一旦某个环节卡死超过设定时间,系统将自动重启,避免彻底瘫痪。
🔌 实际部署建议:不只是代码的事
做好软件还不够,硬件设计同样关键。
🔋 电源设计:稳压是王道
ESP32-S3 + OV2640 最大电流可达 200mA 以上,尤其在闪光灯或Wi-Fi发射时容易掉电重启。
强烈建议使用 AMS1117-3.3V LDO 或 DC-DC 降压模块 ,输入 5V(来自 USB 或车载点烟器),输出干净的 3.3V。
不要直接用开发板上的 3.3V 引脚供电!那些线性稳压器效率低、温升高,长期运行不可靠。
🧱 PCB 布局注意事项
如果是自己画板,请牢记:
- DVP 数据线尽量等长,长度差异 < 5mm
- XCLK 走线远离模拟信号和电源
- 加宽电源走线,必要时铺铜散热
- 摄像头靠近主控,减少信号衰减
实在不行,就买现成的 ESP32-CAM 模块,省事又稳定。
🌡️ 散热问题:别让芯片变“煎蛋”
ESP32-S3 长时间运行会产生明显热量,尤其是在高温车内环境。
解决方案:
- 加贴铝制散热片
- 使用金属外壳辅助导热
- 在代码中适当插入
delay()或进入 Light-sleep 模式节能
你甚至可以在外壳上打几个通风孔,假装这是“高性能散热设计”😎。
🔇 功耗优化:让设备更安静更持久
如果你希望设备能在停车后继续值守(比如做泊车监控),可以考虑低功耗模式:
#include "esp_sleep.h"
// 设置 GPIO 中断唤醒(如 PIR 传感器触发)
gpio_wakeup_enable(GPIO_NUM_34, GPIO_INTR_LOW_LEVEL);
esp_sleep_enable_gpio_wakeup();
// 进入深度睡眠
esp_light_sleep_start();
搭配加速度传感器(MPU6050),可在检测到震动时唤醒录像,实现“碰撞锁定”功能。
🚀 扩展可能性:不止于“拍照录像”
你以为这就完了?不,这才刚刚开始。
ESP32-S3 的真正魅力在于它的 边缘智能潜力 。
🤖 加个 AI 模型,让它看得懂世界
借助 TensorFlow Lite Micro ,你可以训练一个轻量级 CNN 模型,用来:
- 检测前方是否有车辆靠近
- 识别红绿灯状态
- 发现行人横穿马路
- 触发紧急录像并锁定文件
虽然不能跑 YOLOv8,但在 QQVGA 分辨率下运行 MobileNetV1 完全可行。
官方已有示例项目 esp-who ,支持人脸识别、人体检测等功能,拿来即用。
📶 Wi-Fi 上云:远程查看不再是梦
ESP32-S3 内置 Wi-Fi,天然适合做无线传输。
你可以:
- 搭建一个简易 Web Server,手机连上热点后直接浏览缩略图列表
- 通过 MQTT 协议将异常事件推送到 Home Assistant
- 用 HTTP POST 把最近几秒的图片上传到阿里云 OSS 或 AWS S3
甚至结合 WebSocket 实现“伪直播”——虽然不是真正视频流,但每秒传一张缩略图,也能凑合看看实时画面。
🕰️ GPS 时间戳:让每张图都有“身份证”
加上一个 NEO-6M GPS 模块 ,并通过 UART 获取经纬度和 UTC 时间,就可以给每张图片加上精准时间戳。
再配合 NTP 校准 RTC 芯片(如 DS3231),即使断电也能保持时间准确。
这对于事故取证非常有价值:“几点几分,在哪个路口,发生了什么”。
🧪 实测表现:真实数据告诉你能做到什么水平
我在一辆普通轿车上进行了为期一周的测试,配置如下:
- 主控:ESP32-S3-WROOM-1-N16R8(8MB PSRAM)
- 摄像头:AI Thinker OV2640 模块
- 存储:SanDisk 16GB Class10 microSD
- 供电:车载点烟器转 5V USB
结果如下:
| 指标 | 实测值 |
|---|---|
| 平均帧率 | 9.2 fps(目标10fps) |
| 单图大小 | 35~55 KB(VGA@jpeg_quality=12) |
| 写入速度 | 380 KB/s(峰值可达420KB/s) |
| 温升情况 | 运行1小时后表面温度约52°C |
| 连续录制时长 | >8小时(16GB卡) |
| 丢帧率 | <3%(启用队列后降至0.5%) |
白天画面清晰可辨车牌,夜晚因无红外补光略显模糊,但轮廓仍可见。整体效果远超预期。
💡 结语:小芯片也能有大作为
你看,一块几十块的开发板,几个开源库,再加上一点耐心调试,我们就做出了一台具备基本功能的行车记录仪。
它或许无法替代市售产品,但它证明了一件事:
强大的技术,不一定昂贵;创新的应用,往往始于简单的尝试。
ESP32-S3 正是以其出色的性价比和丰富的功能集,成为无数创客、学生、工程师手中的“万能钥匙”。无论是智能家居、工业监测,还是我们现在做的车载视觉系统,它都能胜任。
下次当你觉得“这事得用树莓派”、“这功能MCU搞不定”的时候,不妨问问自己:
“真的吗?我能不能先用 ESP32-S3 快速验证一下?”
说不定,下一个改变出行方式的想法,就藏在这块小小的芯片里。🚀✨
&spm=1001.2101.3001.5002&articleId=155747762&d=1&t=3&u=cc4285765b5b40c0955e3c89a8a6b5ff)
517
&spm=1001.2101.3001.11663&articleId=155747762&d=1&t=3&u=a6862acd92d9417981be8a17a3fe0a1d)

被折叠的 条评论
为什么被折叠?



