项目介绍
本次活动的主题是嵌入式系统中的计算机视觉。使用两个ESP32实现。
第一块ESP32开发板被配置为专用的图像采集节点,使用0.3百万像素的GC0308摄像头,支持图像采集和红外传输控制。
第二块ESP32开发板则担任智能显示终端的角色。接收来自采集节点的压缩数据流,进行实时解码,并驱动外接的LCD屏幕进行画面还原。
此外,为了满足不同应用场景的多样化需求,ESP开发板不仅自己支持扩展,还为该整个系统绘制了一块扩展板
硬件原理图PCB介绍
下面就是使用到的两个ESP32-S3-PICO-1-N8R8,性能和功能都非常的强大


ESP32组成模组的功能如下:

通过扩展板将它们连接在一起,更方便的使用,并且新增了电源保护、两个用户按键和一个复位按键。
其中F1保险丝使用的是四大原厂供应商之一的Fittelfuse、U1是四大原厂供应商之一的ADI。




设计思路
本次“基于双ESP32的嵌入式计算机视觉系统”的设计,核心思路可以概括为:“软硬解耦、分布协同、模块化扩展”。在资源受限的嵌入式微控制器上实现计算机视觉,最大的挑战在于算力瓶颈与内存限制。单颗ESP32很难同时高质量地完成“高分辨率图像采集 + 复杂图像处理/压缩 + 无线传输 + 屏幕高帧率刷新”这一整套流程。
- 采集端:摄像头 → ESP32压缩编码 → 无线发送
- 传输层:UDP协议 + JPEG压缩流 (带帧序号)
- 显示端:无线接收 → ESP32解码 → 屏幕渲染

软件流程
ESP32摄像头的系统启动流程
┌─────────────────────────────────────────────────────────────────┐
│ 系统上电启动 │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ app_main() 入口 │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ 1. 配置日志格式 │ │
│ │ spdlog::set_pattern("[%H:%M:%S] [%L] %v") │ │
│ └───────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 依赖注入 (DI) │
│ ┌─────────────────────────┐ ┌─────────────────────────────┐ │
│ │ shared_data_injection() │ │ asset_pool_injection() │ │
│ │ - SharedData::Inject() │ │ - 映射 AssetPool 分区 │ │
│ │ - 创建共享数据单例 │ │ - AssetPool::InjectStatic │ │
│ └─────────────────────────┘ └─────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 硬件初始化 │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │enable_camera │ │ i2c_init() │ │ imu_init() │ │
│ │ power() │ │ - I2C0: IMU │ │ - BMI270 │ │
│ │ - GPIO18 │ │ - I2C1: 预留 │ │ - BMM150 │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ ir_init() │ │ camera_init()│ │
│ │ - GPIO47 │ │ - XCLK, PCLK│ │
│ │ - NEC 协议 │ │ - DVP 接口 │ │
│ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 启动服务 │
│ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │ start_service_uvc() │ │start_service_web_ │ │
│ │ - UVC 设备初始化 │ │ server() │ │
│ │ - 注册回调函数 │ │ - 创建 AP (CAM-WiFi)│ │
│ │ - 启动 UVC 任务 │ │ - 注册 Web API │ │
│ └─────────────────────┘ └─────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 主循环 (Main Loop) │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ while(1) { │ │
│ │ vTaskDelay(1000); │ │
│ │ cleanup_imu_ws_client(); // 清理断开的 WebSocket 客户端 │ │
│ │ } │ │
│ └───────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
ESP32摄像头的关键代码,程序的入口(usb_webcam_main.cpp)
extern "C" int app_main(void)
{
spdlog::set_pattern("[%H:%M:%S] [%L] %v");
/* 依赖注入 */
shared_data_injection(); // SharedData 单例
asset_pool_injection(); // AssetPool 资源池
/* 硬件初始化 */
enable_camera_power(); // GPIO18 摄像头电源
i2c_init(); // I2C 总线 (IMU)
imu_init(); // BMI270 + BMM150
ir_init(); // IR 发射 (GPIO47)
camera_init(); // 摄像头初始化
/* 启动服务 */
start_service_uvc(); // UVC 摄像头服务
start_service_web_server(); // Web 服务器
/* 主循环 */
while (1) {
vTaskDelay(pdMS_TO_TICKS(1000));
cleanup_imu_ws_client(); // 清理 WebSocket 客户端
}
}
ESP32显示屏的系统启动流程
┌─────────────────────────────────────────────────────────────────┐
│ 上电启动 (Power On) │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ ESP32-S3 Bootloader │
│ (ESP-IDF 二级引导程序) │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ app_main() 入口 │
│ - 设置日志格式 spdlog::set_pattern() │
│ - 创建 SetupCallback_t 回调结构 │
│ - 定义 AssetPoolInjection 回调 │
│ - 定义 HalInjection 回调 │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ AssetPoolInjection 回调执行 │
│ - nvs_flash_init() 初始化 NVS │
│ - 查找 assetpool 分区 (type=0x233, subtype=0x23) │
│ - esp_partition_mmap() 内存映射 2MB 分区 │
│ - AssetPool::InjectStaticAsset() 注入静态资源 │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ HalInjection 回调执行 │
│ HAL::Inject(new HAL_AtomS3R) │
│ - 创建 HAL_AtomS3R 实例 │
│ - 调用 init() 初始化硬件: │
│ · watch_dog_init() 看门狗 │
│ · i2c_init() I2C 总线 │
│ · led_controller_init() LP5562 LED 控制器 │
│ · imu_init() BMI270 IMU 传感器 │
│ · disp_init() 显示屏驱动 │
│ · ir_init() 红外发射器 │
│ · initArduino() Arduino 核心 │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ APP::Setup(callback) │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ 1. AssetPool 注入 (已执行) │ │
│ │ 2. SharedData 注入 (默认或自定义) │ │
│ │ 3. HAL 注入 (已执行) │ │
│ │ 4. HAL::LvglInit() 初始化 LVGL 图形库 │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ Mooncake 应用框架初始化 │ │
│ │ - new Mooncake() 创建框架实例 │ │
│ │ - mooncake->init() 初始化 │ │
│ │ - app_run_startup_anim() 运行启动动画 │ │
│ │ - app_install_default_startup_app() 安装主应用 │ │
│ │ · AppUserDemo (用户演示应用) │ │
│ └───────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 主循环 (while(1)) │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ APP::Loop() │ │
│ │ ↓ │ │
│ │ mooncake->update() 更新所有应用状态 │ │
│ │ - 检查当前应用生命周期 │ │
│ │ - 调用 onResume()/onRunning()/onDestroy() │ │
│ │ ↓ │ │
│ │ HAL::FeedTheDog() 喂看门狗 │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
ESP32显示屏的关键代码,程序的入口(app_main.cpp)
extern "C" void app_main(void)
{
// 1. 配置日志格式
spdlog::set_pattern("[%H:%M:%S] [%L] %v");
// 2. 创建回调结构
APP::SetupCallback_t callback;
// 3. AssetPool 注入回调 - 从 Flash 分区加载资源
callback.AssetPoolInjection = []() {
char* static_asset;
const esp_partition_t* part;
spi_flash_mmap_handle_t handler;
// 初始化 NVS
nvs_flash_init();
// 查找 assetpool 分区 (type=0x233, subtype=0x23)
part = esp_partition_find_first(
(esp_partition_type_t)233,
(esp_partition_subtype_t)0x23,
NULL
);
// 内存映射 2MB 分区到地址空间
err = esp_partition_mmap(
part, 0, 2 * 1024 * 1024,
ESP_PARTITION_MMAP_DATA,
(const void**)&static_asset,
&handler
);
// 注入到 AssetPool 单例
AssetPool::InjectStaticAsset((StaticAsset_t*)static_asset);
};
// 4. HAL 注入回调 - 创建 AtomS3R 专用 HAL
callback.HalInjection = []() {
HAL::Inject(new HAL_AtomS3R);
};
// 5. 初始化应用框架
APP::Setup(callback);
// 6. 主循环
while (1) {
APP::Loop();
}
}
功能展示
- 硬件功能展示图及说明


心得体会
此次活动的基础非常简单,进阶活动非常的难,有点两极分化的感觉。进阶的创意非常的先进很贴合当前的热门主题,但是似乎有点太超前了,本来看到无线的WIFI-7挺感兴趣的,但是搜索了一下这类芯片和模组对个人现在似乎还挺少的。
没有使用过ESP-IDF,本次想通过AI完成全部代码,发现还是有点小问题,来来回回对话改了相似的问题得改好长一段时间。
代码附件下载
链接:https://www.123684.com/s/FbfSvd-R6KF3?pwd=zV4I#
提取码:zV4I