Compare commits
2 Commits
e95d0c414e
...
f1c2bfce93
| Author | SHA1 | Date | |
|---|---|---|---|
| f1c2bfce93 | |||
| fc07d3806d |
@ -422,7 +422,8 @@ ESP32 通过 UART 发送状态字符串给 RP2040:
|
|||||||
|
|
||||||
### 4.9 舵机选型说明(重要)
|
### 4.9 舵机选型说明(重要)
|
||||||
|
|
||||||
> **实测踩坑记录**:使用 MG90S 360° 连续旋转版舵机后,耳朵舵机转到目标角度后无法停止,持续堵转导致齿轮发出刺耳声音、舵机严重发烫,有烧毁风险。更换为 180° 标准舵机后问题解决。
|
> **实测踩坑记录**:使用 MG90S 360° 连续旋
|
||||||
|
转版舵机后,耳朵舵机转到目标角度后无法停止,持续堵转导致齿轮发出刺耳声音、舵机严重发烫,有烧毁风险。更换为 180° 标准舵机后问题解决。
|
||||||
|
|
||||||
#### 必须使用 180° 标准舵机的原因
|
#### 必须使用 180° 标准舵机的原因
|
||||||
|
|
||||||
@ -585,6 +586,229 @@ MicroPython 固件刷入方式与摄像头版本相同(参见 4.6),但 **R
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 六点五、Phase 01 单摄像头人脸追踪 踩坑经验(2026-04-17 ~ 04-20)
|
||||||
|
|
||||||
|
> **背景**:尝试用 ESP32 上的 OV3660 摄像头做人脸检测,通过 UART 发送人脸坐标给 RP2040,驱动眼球/YAW 舵机追踪。详细规划/执行文档见 [docs/phase-01-face-tracking/](docs/phase-01-face-tracking/)。
|
||||||
|
>
|
||||||
|
> **现状**:代码骨架全部完成(15 个任务 T01-T15),ESP32 端 + RP2040 端端到端联通,但摄像头帧数据解析仍有问题(box 固定伪激活),未完全通过验收。
|
||||||
|
|
||||||
|
### 6.5.1 编译 / 配置类踩坑
|
||||||
|
|
||||||
|
| # | 坑 | 原因 | 解决方案 |
|
||||||
|
|---|---|-----|---------|
|
||||||
|
| 1 | 板级配置被重置为 `BREAD_COMPACT_WIFI`(无摄像头版) | `idf.py fullclean` / `set-target` 可能触发 sdkconfig 重置到 defaults | 显式编辑 `sdkconfig` 确认 `CONFIG_BOARD_TYPE_BREAD_COMPACT_WIFI_CAM=y` |
|
||||||
|
| 2 | `esp-dl 3.3.0` 与 `esp-sr 2.2.x` 版本冲突 | esp-dl 需要 esp-dsp 1.7.0,esp-sr 2.2 钉 esp-dsp 1.6.0 | 升级 `esp-sr` 到 `~2.3.1`(已切到 esp-dsp 1.7.0) |
|
||||||
|
| 3 | Bootloader CMake 缓存不匹配,编译报 `source does not match` | ESP-IDF 路径历史变更(如 `esp-idf/v5.4.2/esp-idf/` → `esp-idf/`) | `rm -rf build/bootloader build/bootloader-prefix` 重新编译 |
|
||||||
|
| 4 | `ESP_LOGI` 用 `%lld` 输出变成字符串 `"ldus"` | ESP-IDF nano newlib 不完全支持 `%lld` | 改用 `%lu` + `(unsigned long)` 强转(probe 用时 < 71 分钟,uint32 足够) |
|
||||||
|
| 5 | `esp_video 1.3.1/1.4.1` 的 DVP 启动报 `xclk_freq is not set` | esp_video 源码 `dvp_video_device.c` 构造 `esp_cam_ctlr_dvp_config_t` 时缺失 `xclk_freq` 字段(IDF 5.4.2 不支持 `external_xtal`) | 升 esp_video 到 1.4.1 后**手动 patch** managed_components 源码,在 `#else` 分支加 `.xclk_freq = 20000000,` |
|
||||||
|
|
||||||
|
### 6.5.2 摄像头数据链路踩坑
|
||||||
|
|
||||||
|
| # | 坑 | 原因 | 解决方案 |
|
||||||
|
|---|---|-----|---------|
|
||||||
|
| 6 | `/dev/video2` 打不开,errno=2 No such file | sdkconfig 中没有启用任何 sensor driver(`CONFIG_CAMERA_OV3660` 默认 `n`) | sdkconfig 启用对应摄像头:`CONFIG_CAMERA_OV3660=y` + 具体分辨率/格式 |
|
||||||
|
| 7 | V4L2 REQBUFS count 默认为 1(DVP 路径),DMA 饥饿陈旧数据 | `esp32_camera.cc` 中 `req.count = strcmp(...) == 0 ? 2 : 1;` 对 DVP 用单 buffer | 改为 `req.count = 2;` 让 DMA 有双缓冲持续更新 |
|
||||||
|
| 8 | 分区表 OTA 分区 3.94MB 容纳不下 4.23MB 固件 | 加了 esp-dl + human_face_detect 后固件膨胀 | 修改 `partitions/v2/16m.csv`:ota_0/ota_1 各 5MB,assets 缩到 5.875MB(16MB 模组够用) |
|
||||||
|
| 9 | 摄像头输出画面全黑,Y 值只有 0~8 | **镜头表面出厂保护膜未撕掉** | 用指甲抠边缘撕掉透明保护膜(极薄不易察觉) |
|
||||||
|
| 10 | 画面持续偏暗 | 室内光照不足 / 镜头指纹灰尘 | 白天窗边或明亮台灯下测试 |
|
||||||
|
|
||||||
|
### 6.5.3 esp-dl 人脸检测踩坑
|
||||||
|
|
||||||
|
| # | 坑 | 原因 | 解决方案 |
|
||||||
|
|---|---|-----|---------|
|
||||||
|
| 11 | MSR_S8_V1 模型固定在 `box=[0, y, 40, y+40]` 误识别 | 模型输入 160×120(4:3),摄像头输出 240×240(1:1),ImagePreprocessor letterbox 填充左右各 20 列灰条,模型在灰条上产生假激活 | 改用输入正方形的 `ESPDET_PICO_224_224_FACE`(224×224) |
|
||||||
|
| 12 | ESPDET 模型仍然输出固定 box `[233, 158, 94, 239]`(数学上不可能:x1 > x2) | 怀疑模型对 out-of-distribution 输入 fallback 到默认 anchor | 尝试中:手动 YUYV→RGB888 转换 + 字节序验证 |
|
||||||
|
| 13 | 字节序判断反复:RGB565LE / BE / YUYV 都试过 | OV3660 FORMAT_CTRL00=0x61 设置是"RGB565 byte-swapped",但实测数据更像 YUYV | 通过 `frame debug` 打印前 16 字节诊断,按多种格式对比解读 |
|
||||||
|
|
||||||
|
### 6.5.4 任务调度踩坑(Core 亲和 & 崩溃)
|
||||||
|
|
||||||
|
| # | 坑 | 原因 | 解决方案 |
|
||||||
|
|---|---|-----|---------|
|
||||||
|
| 14 | 唤醒词检测后触发 LED 时崩溃(`Interrupt wdt timeout`)| face_tracker 绑 Core 0,esp-dl 推理占 150ms,RMT LED 驱动在同核抢不到 spinlock 超 300ms | face_tracker 改绑 **Core 1**(音频空闲时让出 CPU) |
|
||||||
|
| 15 | GDMA ISR `InstrFetchProhibited` at PC=0x00000000 | esp_video 1.4.1 底层 DMA 回调指针变 NULL(疑似 ESP-IDF 5.4.2 兼容性 bug) | 未完全修复。降低 FPS 到 5 能推迟崩溃。待升级 ESP-IDF 或改用低级 esp_cam_ctlr API |
|
||||||
|
| 16 | 弱符号 `uart_send_face` 链接失败 | C++ 名字修饰问题 | `extern "C"` 包裹 weak 前置声明 + strong 定义,确保 C 链接名一致 |
|
||||||
|
|
||||||
|
### 6.5.5 RP2040 端踩坑
|
||||||
|
|
||||||
|
| # | 坑 | 原因 | 解决方案 |
|
||||||
|
|---|---|-----|---------|
|
||||||
|
| 17 | 进入 idle 后眼球卡在最后追踪位置不回中 | 原 `facetrack()` idle 时直接 return,没有回中机制 | 新增 `_recenter_tracking_servos()`,在 `grove_active` 从 True→False 或首次进入 idle 时一次性回中到 90° |
|
||||||
|
| 18 | ESP32 持续发误识别坐标(-95/+40 等固定值) | 眼球持续累加偏移直到撞到机械限位(30°/150°) | 属 ESP32 侧 bug,但 RP2040 侧加 `staticflag` 去重 + 回中机制缓解 |
|
||||||
|
| 19 | mpremote `could not enter raw repl` | Pico 主循环阻塞或端口被旧 monitor 占用 | 拔插 USB 复位 Pico → 立刻 `mpremote cp` 抢时间窗 |
|
||||||
|
| 20 | 两个分支 RP2040 代码不同 | `CogletESP` 分支 4 个 .py(含 `coms.py`),`camera-version` 分支 3 个(无 `coms.py`) | 使用时对应分支代码,不要混用 |
|
||||||
|
|
||||||
|
### 6.5.6 硬件踩坑
|
||||||
|
|
||||||
|
| # | 坑 | 原因 | 解决方案 |
|
||||||
|
|---|---|-----|---------|
|
||||||
|
| 21 | 3 根飞线(35→14, 36→41, 37→42)是否到位 | CogNog V1.0 PCB 设计缺陷,PSRAM 和摄像头 D7/VSYNC/HREF 冲突 | 万用表测导通,确保原 GPIO 35/36/37 与摄像头断开,新 GPIO 14/41/42 接通 |
|
||||||
|
| 22 | 舵机堵转、发烫、齿轮刺耳声 | 误用 **360° 连续旋转舵机**(MG90S 360° 版外观和 180° 一样) | 更换为 **KPower M0090** 或 **MG90S 180° 金属齿轮版**(详见 4.9 节) |
|
||||||
|
| 23 | 烧录固件后 `Compile time` 仍是旧版 | 用 IDE 内置烧录工具可能烧到错误路径 / 没完整烧录 | 改用命令行 `idf.py -p /dev/cu.usbmodemXXX flash monitor`,烧完立即 monitor 看 `Compile time` 验证 |
|
||||||
|
|
||||||
|
### 6.5.7 调试方法论
|
||||||
|
|
||||||
|
| 方法 | 用途 |
|
||||||
|
|------|-----|
|
||||||
|
| **frame debug 打印前 16B 字节值** | 按多种格式(YUYV / RGB565LE / RGB565BE)对比解读,判断 sensor 实际输出 |
|
||||||
|
| **zero_bytes 比例统计** | 判断画面亮度(>30% 大概率镜头遮挡或极暗;<5% 画面正常) |
|
||||||
|
| **box 数学合法性** | `box[0] > box[2]` 或 `box[2] == width` 这种异常值说明模型崩了(OOD 默认输出) |
|
||||||
|
| **对比不同画面输入下 box 是否变化** | 遮住摄像头 / 白墙 / 人脸 → 如果 box 完全一致,一定是模型输入路径异常 |
|
||||||
|
| **Compile time 验证烧录生效** | 烧录后 monitor 看 `I (xxx) app_init: Compile time: ...` 必须是最新编译时间 |
|
||||||
|
| **xtensa-esp32s3-elf-addr2line** | 把 crash backtrace 的地址转成源码文件:行号 |
|
||||||
|
|
||||||
|
### 6.5.8 Phase 01 未解决问题(遗留)
|
||||||
|
|
||||||
|
1. ❌ **box 固定伪激活**:无论遮住摄像头、白墙、真实人脸,box 都稳定输出 `[233, 158, 94, 239]`(或 MSR 模型的 `[0, *, 40, *]`)。已验证:
|
||||||
|
- pix_type 不是根因(YUYV / RGB565LE / RGB565BE / RGB888 手动转换都不行)
|
||||||
|
- 双 buffer 修复了数据更新问题,但 box 仍固定
|
||||||
|
- 模型切换(MSR → ESPDET)改变了 box 坐标但仍固定
|
||||||
|
- 推断:模型对某种 out-of-distribution 输入 fallback 到默认 anchor
|
||||||
|
|
||||||
|
2. ❌ **GDMA ISR 崩溃**:每 ~30 秒触发一次 `InstrFetchProhibited` at PC=0x00000000,属 esp_video 1.4.1 / ESP-IDF 5.4.2 底层 bug
|
||||||
|
|
||||||
|
3. ⚠️ **端到端未验收**:RP2040 端 Bug 3(回中机制)已修,但因 ESP32 侧 box 仍固定,整条链路的"眼球跟随真实人脸"未通过
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六点六、Phase 01 核心矛盾与解决方案分析(2026-04-21 ~ 04-22)
|
||||||
|
|
||||||
|
> 基于 JPEG Dump 诊断工具的大量实验(9 次迭代尝试),本节汇总当前主要矛盾、根本原因和方案选择。
|
||||||
|
|
||||||
|
### 6.6.1 主要矛盾:画面可辨识 ≠ 模型可识别
|
||||||
|
|
||||||
|
**诊断工具**:在 face_tracker.cc 里加 JPEG Dump 代码,每次启动 base64 打印一帧 JPEG,Mac 端 Python 脚本抓取保存为 `.jpg` 文件肉眼验证。
|
||||||
|
|
||||||
|
**验证结果**:
|
||||||
|
|
||||||
|
| 观察 | 事实 |
|
||||||
|
|------|------|
|
||||||
|
| 手动撕掉镜头保护膜后,JPEG 画面可**清晰看到戴眼镜的人脸、手部、背景** | ✅ 摄像头硬件 + 飞线 + DVP 通路完全正常 |
|
||||||
|
| 画面**整体偏紫绿**(RGB565 模式)或**偏绿**(YUV422 模式) | 🟡 软件层 YUV→RGB 色彩矩阵偏差,不是硬件问题 |
|
||||||
|
| 同样的摄像头输入,esp-dl `HumanFaceDetect` **无论什么 pix_type 都输出固定 box** | ❌ 深层集成问题 |
|
||||||
|
|
||||||
|
**核心矛盾**:
|
||||||
|
|
||||||
|
> 人眼能辨识的画面(因为有上下文知识"绿色的这个 = 人脸"),轻量级 CNN 模型无法识别(只看像素数值分布)。esp-dl 官方模型用**正常色彩的标准人脸数据集**训练,我们的偏色画面在训练集里找不到对应模式 → 模型 fallback 到默认 anchor → box 恒定。
|
||||||
|
|
||||||
|
### 6.6.2 YUV→RGB 色偏的三层原因
|
||||||
|
|
||||||
|
#### 第 1 层:**YUV→RGB 色彩矩阵公式不完全匹配 BT.601**
|
||||||
|
|
||||||
|
- OV3660 输出 YUV **限幅范围**:Y ∈ [16, 235], U/V ∈ [16, 240](中值 128)
|
||||||
|
- 手写转换公式假定 Y ∈ [0, 255] **全范围**(JFIF 标准):
|
||||||
|
```cpp
|
||||||
|
int r = Y + 1.402 * (V - 128); // 错:没有黑电平偏移,整体偏暗
|
||||||
|
```
|
||||||
|
- 正确应为(BT.601):
|
||||||
|
```cpp
|
||||||
|
int y_scaled = 1.164 * (Y - 16); // 减黑电平、放大到全范围
|
||||||
|
int r = y_scaled + 1.596 * (V - 128);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 第 2 层:**OV3660 AWB(自动白平衡)未启用或响应慢**
|
||||||
|
|
||||||
|
- 默认寄存器序列中,AWB 可能关闭或慢响应
|
||||||
|
- 导致 U/V 有**全局偏移**:画面整体偏绿/紫
|
||||||
|
- Grove Vision AI V2 内置 ISP 硬件自动白平衡,**我们读原始 YUV buffer 没有**
|
||||||
|
|
||||||
|
#### 第 3 层:**OV3660 FORMAT_CTRL00 = 0x61 的实际含义**
|
||||||
|
|
||||||
|
- `bit[7:4] = 0x6` = RGB565
|
||||||
|
- `bit[3:0] = 0x1` = byte-swap 序列
|
||||||
|
- 在 Kconfig RGB565 模式下,sensor 实际输出可能是 **YVYU sequence**(Y-V-Y-U)而非标准 YUYV,导致 U/V 解读时互换
|
||||||
|
- 修正方向:在 Kconfig 改用 YUV422 模式(FORMAT_CTRL00=0x30,标准 YUYV)
|
||||||
|
|
||||||
|
### 6.6.3 esp-dl 模型输入分布敏感
|
||||||
|
|
||||||
|
即使色彩完全校正正确,轻量级模型(MSR_S8_V1 仅 60KB、ESPDET_PICO_224_224_FACE 约 500KB)对 RGB 分布偏差**极其敏感**。具体要求:
|
||||||
|
|
||||||
|
| 要求 | 解释 |
|
||||||
|
|------|------|
|
||||||
|
| RGB 通道均值接近训练集 | ImageNet 类数据集 RGB 均值约 (124, 116, 104) |
|
||||||
|
| 归一化范围精确 | ESPDET 用 `(pixel-0)/255`,要求 pixel ∈ [0, 255] 全范围 |
|
||||||
|
| 无严重色偏 | 偏绿会让模型前几层卷积产生"异常激活",后续全部 fallback |
|
||||||
|
| 无边缘伪影 | letterbox 填充不能和画面内容对比度过强 |
|
||||||
|
|
||||||
|
> **为什么 Grove Vision AI V2 一定能行**:Grove 用 Himax WiseEye HX6538 专用 AI 视觉处理器,内置 ISP + 针对自己 sensor 训练的**专用人脸检测模型**,从硬件到模型端到端自闭环。esp-dl 是通用框架,需要用户自己保证数据质量。
|
||||||
|
|
||||||
|
### 6.6.4 sensor 硬件 JPEG 模式的局限
|
||||||
|
|
||||||
|
OV3660 支持硬件 JPEG 编码(`CAMERA_OV3660_DVP_JPEG_1280X720_12FPS`),但实测失败:
|
||||||
|
|
||||||
|
- `Esp32Camera::Capture()` 默认不协商 JPEG pix_fmt,报 `no supported pixel format found`
|
||||||
|
- 启用 `CONFIG_XIAOZHI_CAMERA_ALLOW_JPEG_INPUT=y` 后能协商,但 `bytesused=0` —— DMA 没采到 JPEG 帧
|
||||||
|
- 推测 sensor 硬件 JPEG 需要特殊的 DVP 帧同步处理,xiaozhi 的 V4L2 mmap 路径不兼容
|
||||||
|
|
||||||
|
结论:硬件 JPEG 路径此项目未打通,**需走软件 JPEG 编解码**。
|
||||||
|
|
||||||
|
### 6.6.5 延迟分析:JPEG 中转路径
|
||||||
|
|
||||||
|
走 `xiaozhi Capture() → JPEG → esp-dl sw_decode_jpeg → RGB888 → HumanFaceDetect` 路径的延迟估算:
|
||||||
|
|
||||||
|
| 阶段 | 耗时 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| 摄像头采集一帧 | ~40ms | 24 FPS 间隔 |
|
||||||
|
| xiaozhi Capture() 软件 JPEG 编码 | 50-80ms | 240×240 YUV→RGB→JPEG |
|
||||||
|
| esp-dl sw_decode_jpeg 解码 | 30-50ms | JPEG → RGB888 |
|
||||||
|
| HumanFaceDetect 模型推理 | 150ms | ESPDET_PICO_224 |
|
||||||
|
| UART 发送坐标 | 1ms | 240 bytes @ 115200 |
|
||||||
|
| **ESP32 端总延迟** | **~270ms** | |
|
||||||
|
| RP2040 UART RX + parse | 2ms | |
|
||||||
|
| 舵机 PWM + 物理转动 | 20-80ms | 机械响应时间 |
|
||||||
|
| **端到端总延迟(脸动→眼球动)** | **~300-350ms** | |
|
||||||
|
|
||||||
|
**对比**:
|
||||||
|
- **人眼感知"流畅跟随"阈值**:< 500ms
|
||||||
|
- **Grove Vision AI V2**:~100-150ms(专用硬件)
|
||||||
|
- **JPEG 中转方案**:~300ms ✅ 可接受
|
||||||
|
- **人眨眼速度**:~400ms
|
||||||
|
|
||||||
|
### 6.6.6 三个路径选择
|
||||||
|
|
||||||
|
| 方案 | 预计工时 | 成功率 | 备注 |
|
||||||
|
|------|---------|-------|------|
|
||||||
|
| **A. 继续深挖 esp-dl(改色彩矩阵、启 AWB、fork 预处理)** | 8-10 小时 | ⭐⭐(20-30%)| 涉及 ov3660 寄存器调优 + 模型内部调试 |
|
||||||
|
| **B. JPEG 中转路径(走 xiaozhi 完整 Capture + esp-dl sw_decode_jpeg)** | 2-3 小时 | ⭐⭐⭐⭐(70-80%)| **推荐**。`take_photo` 已证明 Capture() 色彩正常 |
|
||||||
|
| **C. 退回 Grove Vision AI V2(项目原设计)** | 2 小时 + ¥200 | ⭐⭐⭐⭐⭐(100%)| 官方 turnkey 方案,稳妥 |
|
||||||
|
|
||||||
|
### 6.6.7 方案 B 的关键突破口
|
||||||
|
|
||||||
|
**关键发现**:xiaozhi 的 `self.camera.take_photo` MCP 功能拍的照片**云端 AI 能清晰识别**,说明 `Capture()` 函数内部有正确的色彩处理(白平衡、色彩矩阵、JPEG 标准编码)。
|
||||||
|
|
||||||
|
**未尝试的真正路径**:
|
||||||
|
```
|
||||||
|
Esp32Camera::Capture()
|
||||||
|
↓ 内部完整 pipeline(色彩正常的 JPEG)
|
||||||
|
JPEG buffer
|
||||||
|
↓ esp-dl sw_decode_jpeg(esp-dl 官方 example 路径)
|
||||||
|
标准 RGB888 画面(色彩 100% 匹配训练集)
|
||||||
|
↓
|
||||||
|
HumanFaceDetect → 真正的 box
|
||||||
|
```
|
||||||
|
|
||||||
|
**之前的失败路径**:我一直绕过 `Capture()` 用 `CaptureForDetection()` 直接拿 V4L2 mmap 的原始 YUV buffer,缺少 xiaozhi 的色彩校正。
|
||||||
|
|
||||||
|
### 6.6.8 验证方案 B 可行性的最简方法
|
||||||
|
|
||||||
|
**不用改代码**:
|
||||||
|
|
||||||
|
1. 烧录当前 YUV422 模式的固件
|
||||||
|
2. 通过小智对话说"**帮我拍张照看看**"或"**你看见什么了**"
|
||||||
|
3. AI 云端返回画面描述
|
||||||
|
|
||||||
|
- 如果 AI 说能**清晰看到人脸/房间物体** → 证明 `Capture()` 色彩正常 → **方案 B 可行性 80%+**
|
||||||
|
- 如果 AI 说看不清或描述错乱 → `Capture()` 也有色偏 → 需考虑方案 C
|
||||||
|
|
||||||
|
### 6.6.9 当前代码状态快照(2026-04-22)
|
||||||
|
|
||||||
|
- `main/face_tracker.cc`:手动 YUYV→RGB888 转换 + pix_type=RGB888(失败路径,保留代码)
|
||||||
|
- `main/face_tracker.cc`:JPEG Dump 诊断代码(每次启动拍一张 YUYV JPEG)
|
||||||
|
- `sdkconfig`:`CAMERA_OV3660_DVP_YUV422_240X240_24FPS=y`(画面偏绿但结构清晰)
|
||||||
|
- 固件编译通过,烧录正常,face_tracker 启动正常
|
||||||
|
- 症状:box 恒定 `[233, 158, 94, 239]`,眼球卡在极限位置不跟随
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 七、参考资源
|
## 七、参考资源
|
||||||
|
|
||||||
| 资源 | 地址 |
|
| 资源 | 地址 |
|
||||||
|
|||||||
2666
docs/ESP32-S3-CAM:接ov3660摄像头-CSDN博客.html
Normal file
2666
docs/ESP32-S3-CAM:接ov3660摄像头-CSDN博客.html
Normal file
File diff suppressed because one or more lines are too long
@ -12,9 +12,9 @@
|
|||||||
- [x] T05 face_tracker.{h,cc} 骨架 + CMake 条件编译
|
- [x] T05 face_tracker.{h,cc} 骨架 + CMake 条件编译
|
||||||
- [x] T06 集成 HumanFaceDetect 推理 + 坐标归一化(代码部分;实测待 T12)
|
- [x] T06 集成 HumanFaceDetect 推理 + 坐标归一化(代码部分;实测待 T12)
|
||||||
- [x] T07 uart_send_face + uart mutex
|
- [x] T07 uart_send_face + uart mutex
|
||||||
- [ ] T08 RP2040 parse_face + static 去重
|
- [x] T08 RP2040 parse_face + static 去重
|
||||||
- [ ] T09 RP2040 main.py incoming_commands 识别 face:
|
- [x] T09 RP2040 main.py incoming_commands 识别 face:
|
||||||
- [ ] T10 RP2040 facetrack() 改造(D-07 idle return)
|
- [x] T10 RP2040 facetrack() 改造(D-07 idle return)
|
||||||
- [ ] T11 application.cc 接入 face_tracker_start
|
- [ ] T11 application.cc 接入 face_tracker_start
|
||||||
- [ ] T12 端到端联调
|
- [ ] T12 端到端联调
|
||||||
- [ ] T13 性能调优
|
- [ ] T13 性能调优
|
||||||
@ -107,3 +107,46 @@
|
|||||||
T11 接入后会自动拉入 uart_send_face 的 strong 实现。
|
T11 接入后会自动拉入 uart_send_face 的 strong 实现。
|
||||||
- 未添加 test hook(PLAN DoD 中提到的 `uart_send_face(42,-30)` 临时调用),
|
- 未添加 test hook(PLAN DoD 中提到的 `uart_send_face(42,-30)` 临时调用),
|
||||||
留给 T12 端到端联调时用真实 face_tracker 数据验证
|
留给 T12 端到端联调时用真实 face_tracker 数据验证
|
||||||
|
|
||||||
|
- [x] T08 完成:2026-04-20 - 修改文件: /Users/rdzleo/Desktop/CogletESP-CogletESP/RP2040/coms.py
|
||||||
|
- `Comms.__init__` 末尾新增三个成员:
|
||||||
|
* `self.last_face_offset = None`(ESP32 人脸坐标最新值,main.py 消费后清空)
|
||||||
|
* `self.last_face_raw = None`(static 去重用的"上一次原始坐标")
|
||||||
|
* `self.FACE_STATIC_THRESHOLD = 3`(±3 像素阈值,经验值)
|
||||||
|
- 类内新增 `parse_face(line)` 方法:
|
||||||
|
* 兼容 `bytes` / `str` 输入(自动 decode + strip),防御性处理
|
||||||
|
* 非 `face:` 前缀或解析失败返回 None(不影响 staticflag)
|
||||||
|
* 与 `last_face_raw` 对比:差异 ≤ 3 像素 → `staticflag = True`;否则 → `staticflag = False` 并更新 `last_face_raw`
|
||||||
|
* 首次收到坐标视为"非静态"(确保眼球初始化时会响应)
|
||||||
|
- **未新增** 独立的 `read_face()` 方法:PLAN T08 规范只要求 `parse_face` + `last_face_offset`,不要求单独的 UART 读取方法——ESP32 UART 数据仍由 `esp_read()` 统一读入 `rx_buffer`,main.py 遍历 `commands` 时逐个调 `parse_face`(见 T09)。如果单独再提供 `read_face()` 会导致 UART 缓冲区被两个方法争抢,造成行撕裂。
|
||||||
|
- Python 语法检查:`python3 -c "import ast; ast.parse(...)"` 通过
|
||||||
|
|
||||||
|
- [x] T09 完成:2026-04-20 - 修改文件: /Users/rdzleo/Desktop/CogletESP-CogletESP/RP2040/main.py
|
||||||
|
- 主循环 `for data in incoming_commands` 分支改造:
|
||||||
|
* 优先调 `external.parse_face(data)`,成功则设 `external.last_face_offset`、
|
||||||
|
`animation.grove_active = True`、`animation.grove_last_seen = time.ticks_ms()`,`continue`
|
||||||
|
* 否则走原 `action_map` / `state_map` 分发(完全不影响现有 state 指令行为)
|
||||||
|
- `grove_active` / `grove_last_seen` 在收到 `face:` 消息时立即 set True(即时响应),与 T10 的 `facetrack()` 兜底刷新配合,不产生状态污染
|
||||||
|
- Python 语法检查通过
|
||||||
|
|
||||||
|
- [x] T10 完成:2026-04-20 - 修改文件: /Users/rdzleo/Desktop/CogletESP-CogletESP/RP2040/main.py
|
||||||
|
- `facetrack()` 按 PLAN T10 v1.1 完整重构,执行顺序:
|
||||||
|
1. **始终读 Grove**(`grove_offset = external.grove_read()`):即便无 Grove 硬件也要消费 UART 防止缓冲区溢出
|
||||||
|
2. **读 ESP32 face 数据**(`esp_offset = external.last_face_offset`),消费后立即 `last_face_offset = None`
|
||||||
|
3. **数据源优先级**:Grove 有效时用 Grove,否则 fallback 到 ESP32
|
||||||
|
4. **D-07 修复**:`if animation.current_state == "idle": return`,idle 下只消费数据不驱动舵机
|
||||||
|
5. 非 idle 分支:原有 eyl/eyr/pit/yaw 的 scale + set_target 逻辑一字不动
|
||||||
|
- **BLOCKER #2 修复**:`staticflag` 不再在 `facetrack()` 硬编码设置——完全由 T08 `parse_face()` / 原有 `grove_read()` 各自管理
|
||||||
|
- **grove_active 合并策略**:T09 在消息到达时 set True(实时);T10 兜底在 `facetrack()` 里根据本帧最终 offset 再次刷新并处理 3 秒超时。两边都 set 到"当前时间戳"或 set False,语义一致,避免 BLOCKER #2 所描述的"双处更新导致污染"问题——只要 T09 先 set 到 True,T10 进入时 `offset` 非空也会刷到同一时间戳,不会发生回退。
|
||||||
|
- Python 语法检查通过
|
||||||
|
|
||||||
|
- [!] T10 偏差说明(轻度):
|
||||||
|
- PLAN v1.1 在 T10 里写了"grove_active/grove_last_seen **不在本任务主动 set True**",但 `facetrack()` 在 Grove 硬件活跃时仍需要把 Grove 自身的 offset 反映到 grove_active 时间戳——本次实现在 `facetrack()` 里保留了"`if offset: grove_active=True`"写法,原因是 T09 只在收到 ESP32 `face:` 消息时 set True,**不处理 Grove 来源**。如果严格按 PLAN 字面删掉,Grove 模式下 `grove_active` 永远为 False,3 秒超时总是会触发,自动动画会不停抢夺 Grove 追踪权。
|
||||||
|
- 处理方式:T09 设 Grove_active 的职责是"ESP32 数据源",T10 保留的是"Grove 数据源"——两边是 OR 关系而非重复更新(BLOCKER #2 所警告的是"两边都 set 到同一数据源",这里不是)。已在代码注释中记录。
|
||||||
|
- 此偏差属于 Rule 2(补完 PLAN 遗漏的 Grove 分支),不需要用户决策。
|
||||||
|
|
||||||
|
- [x] 批次 4 (T08/T09/T10) Python 语法检查结果:
|
||||||
|
- `coms.py` OK
|
||||||
|
- `main.py` OK
|
||||||
|
- `animation.py` OK(未改动,做回归确认)
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@ -25,6 +25,12 @@
|
|||||||
#include "boards/common/esp32_camera.h"
|
#include "boards/common/esp32_camera.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// [T11] 人脸追踪任务启动接口(Phase 01)
|
||||||
|
// 三重保护:Kconfig + CMake 排除 + 代码层 #if 守卫;此处 include 也用同守卫避免非 S3 目标报错
|
||||||
|
#if defined(CONFIG_XIAOZHI_ENABLE_FACE_TRACKING) && defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||||
|
#include "face_tracker.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#define TAG "Application"
|
#define TAG "Application"
|
||||||
|
|
||||||
|
|
||||||
@ -72,6 +78,11 @@ Application::Application() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Application::~Application() {
|
Application::~Application() {
|
||||||
|
// [T11] 请求停止人脸检测任务(异步,任务会在下一帧自行退出)
|
||||||
|
#if defined(CONFIG_XIAOZHI_ENABLE_FACE_TRACKING) && defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||||
|
face_tracker_stop();
|
||||||
|
#endif
|
||||||
|
|
||||||
if (clock_timer_handle_ != nullptr) {
|
if (clock_timer_handle_ != nullptr) {
|
||||||
esp_timer_stop(clock_timer_handle_);
|
esp_timer_stop(clock_timer_handle_);
|
||||||
esp_timer_delete(clock_timer_handle_);
|
esp_timer_delete(clock_timer_handle_);
|
||||||
@ -558,14 +569,19 @@ void Application::Start() {
|
|||||||
if (cam) {
|
if (cam) {
|
||||||
int64_t elapsed = 0;
|
int64_t elapsed = 0;
|
||||||
bool ok = cam->ProbeFrameCapture(&elapsed);
|
bool ok = cam->ProbeFrameCapture(&elapsed);
|
||||||
ESP_LOGI("T01_Probe", "V4L2 probe result=%d elapsed=%lldus",
|
ESP_LOGI("T01_Probe", "V4L2 probe result=%d elapsed=%luus",
|
||||||
ok, (long long)elapsed);
|
ok, (unsigned long)elapsed);
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGW("T01_Probe", "no camera instance (board.GetCamera() returned null or non-Esp32Camera)");
|
ESP_LOGW("T01_Probe", "no camera instance (board.GetCamera() returned null or non-Esp32Camera)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// [T11] 启动人脸检测任务(Kconfig 未开启 / 非 S3 目标时本段不编译)
|
||||||
|
#if defined(CONFIG_XIAOZHI_ENABLE_FACE_TRACKING) && defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||||
|
face_tracker_start();
|
||||||
|
#endif
|
||||||
|
|
||||||
SystemInfo::PrintHeapStats();
|
SystemInfo::PrintHeapStats();
|
||||||
SetDeviceState(kDeviceStateIdle);
|
SetDeviceState(kDeviceStateIdle);
|
||||||
|
|
||||||
|
|||||||
@ -51,7 +51,10 @@
|
|||||||
#define CAMERA_PIN_SIOD GPIO_NUM_48 // checked for CogNog V1.0 - original NUM_4
|
#define CAMERA_PIN_SIOD GPIO_NUM_48 // checked for CogNog V1.0 - original NUM_4
|
||||||
#define CAMERA_PIN_PWDN GPIO_NUM_NC
|
#define CAMERA_PIN_PWDN GPIO_NUM_NC
|
||||||
#define CAMERA_PIN_RESET GPIO_NUM_NC
|
#define CAMERA_PIN_RESET GPIO_NUM_NC
|
||||||
#define XCLK_FREQ_HZ 20000000
|
// [2026-04-21 方案 B] 原 20MHz 在飞线路径上产生 DVP 数据线位错位
|
||||||
|
// (画面彩色马赛克撕裂)。降到 10MHz 给飞线信号完整性 2x 裕度
|
||||||
|
// 代价:sensor 帧率从 24fps 减半到 ~12fps(足够人脸追踪)
|
||||||
|
#define XCLK_FREQ_HZ 10000000
|
||||||
|
|
||||||
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_NC // checked
|
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_NC // checked
|
||||||
#define DISPLAY_MOSI_PIN GPIO_NUM_NC // checked - original NUM_20
|
#define DISPLAY_MOSI_PIN GPIO_NUM_NC // checked - original NUM_20
|
||||||
|
|||||||
@ -280,8 +280,11 @@ Esp32Camera::Esp32Camera(const esp_video_init_config_t& config) {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// 申请缓冲并mmap
|
// 申请缓冲并mmap
|
||||||
|
// [2026-04-20 修复] 原 DVP 单 buffer 导致 face_tracker 每次 DQBUF 拿到
|
||||||
|
// 的都是同一帧陈旧数据(DMA 无 buffer 可写),模型输出"固定伪激活"。
|
||||||
|
// 改为双 buffer 让 DMA 始终有空 buffer 可写,保证每次 DQBUF 取到新帧。
|
||||||
struct v4l2_requestbuffers req = {};
|
struct v4l2_requestbuffers req = {};
|
||||||
req.count = strcmp(video_device_name, ESP_VIDEO_MIPI_CSI_DEVICE_NAME) == 0 ? 2 : 1;
|
req.count = 2;
|
||||||
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||||
req.memory = V4L2_MEMORY_MMAP;
|
req.memory = V4L2_MEMORY_MMAP;
|
||||||
if (ioctl(video_fd_, VIDIOC_REQBUFS, &req) != 0) {
|
if (ioctl(video_fd_, VIDIOC_REQBUFS, &req) != 0) {
|
||||||
@ -420,8 +423,10 @@ bool Esp32Camera::ProbeFrameCapture(int64_t* elapsed_us) {
|
|||||||
}
|
}
|
||||||
int64_t t1 = esp_timer_get_time();
|
int64_t t1 = esp_timer_get_time();
|
||||||
if (elapsed_us) *elapsed_us = t1 - t0;
|
if (elapsed_us) *elapsed_us = t1 - t0;
|
||||||
ESP_LOGI(TAG, "[T01] Probe 成功:bytesused=%u elapsed=%lldus",
|
// 修复:ESP-IDF nano newlib printf 不完全支持 %lld,改为 %lu + 强制 uint32
|
||||||
(unsigned)bytes_used, (long long)(t1 - t0));
|
// probe 时间 < 100ms << uint32 上限(~71 分钟),完全安全
|
||||||
|
ESP_LOGI(TAG, "[T01] Probe 成功:bytesused=%u elapsed=%luus",
|
||||||
|
(unsigned)bytes_used, (unsigned long)(t1 - t0));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -12,6 +12,8 @@
|
|||||||
#include "dl_detect_define.hpp"
|
#include "dl_detect_define.hpp"
|
||||||
#include "board.h"
|
#include "board.h"
|
||||||
#include "esp32_camera.h"
|
#include "esp32_camera.h"
|
||||||
|
#include "display/lvgl_display/jpg/image_to_jpeg.h"
|
||||||
|
#include <linux/videodev2.h>
|
||||||
|
|
||||||
#include <esp_heap_caps.h>
|
#include <esp_heap_caps.h>
|
||||||
#include <esp_log.h>
|
#include <esp_log.h>
|
||||||
@ -20,6 +22,7 @@
|
|||||||
#include <freertos/task.h>
|
#include <freertos/task.h>
|
||||||
#include <list>
|
#include <list>
|
||||||
#include <new>
|
#include <new>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
static const char* TAG = "FaceTracker";
|
static const char* TAG = "FaceTracker";
|
||||||
static TaskHandle_t s_handle = nullptr;
|
static TaskHandle_t s_handle = nullptr;
|
||||||
@ -31,6 +34,35 @@ static float s_last_fps = 0.0f;
|
|||||||
// T07 完成后该弱符号被真实实现覆盖,无需改动本文件
|
// T07 完成后该弱符号被真实实现覆盖,无需改动本文件
|
||||||
extern "C" __attribute__((weak)) void uart_send_face(int x_offset, int y_offset);
|
extern "C" __attribute__((weak)) void uart_send_face(int x_offset, int y_offset);
|
||||||
|
|
||||||
|
// YVYU → RGB888 手动转换(OV3660 FORMAT_CTRL00=0x61 实际输出 Y V Y U 序列)
|
||||||
|
// 每 4 字节 YVYU 生成 2 像素 6 字节 RGB888
|
||||||
|
// 公式(BT.601 JFIF):R = Y + 1.402*(V-128); G = Y - 0.344*(U-128) - 0.714*(V-128); B = Y + 1.772*(U-128)
|
||||||
|
// [2026-04-21 修正] 之前按 YUYV (Y U Y V) 读取导致色彩偏绿紫,JPEG dump 测试证实
|
||||||
|
// sensor 实际是 YVYU sequence,byte[1]=V, byte[3]=U(顺序反了)
|
||||||
|
static inline void yuyv_to_rgb888_line(const uint8_t* yuyv, uint8_t* rgb, int pixels) {
|
||||||
|
for (int i = 0; i < pixels; i += 2) {
|
||||||
|
int y1 = yuyv[0];
|
||||||
|
int v = yuyv[1] - 128; // 修正:byte[1] = V(原本误当 U)
|
||||||
|
int y2 = yuyv[2];
|
||||||
|
int u = yuyv[3] - 128; // 修正:byte[3] = U(原本误当 V)
|
||||||
|
yuyv += 4;
|
||||||
|
// 像素 1
|
||||||
|
int r1 = y1 + (359 * v) / 256;
|
||||||
|
int g1 = y1 - (88 * u + 183 * v) / 256;
|
||||||
|
int b1 = y1 + (454 * u) / 256;
|
||||||
|
// 像素 2
|
||||||
|
int r2 = y2 + (359 * v) / 256;
|
||||||
|
int g2 = y2 - (88 * u + 183 * v) / 256;
|
||||||
|
int b2 = y2 + (454 * u) / 256;
|
||||||
|
*rgb++ = (uint8_t)(r1 < 0 ? 0 : r1 > 255 ? 255 : r1);
|
||||||
|
*rgb++ = (uint8_t)(g1 < 0 ? 0 : g1 > 255 ? 255 : g1);
|
||||||
|
*rgb++ = (uint8_t)(b1 < 0 ? 0 : b1 > 255 ? 255 : b1);
|
||||||
|
*rgb++ = (uint8_t)(r2 < 0 ? 0 : r2 > 255 ? 255 : r2);
|
||||||
|
*rgb++ = (uint8_t)(g2 < 0 ? 0 : g2 > 255 ? 255 : g2);
|
||||||
|
*rgb++ = (uint8_t)(b2 < 0 ? 0 : b2 > 255 ? 255 : b2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void face_tracker_task(void* arg) {
|
static void face_tracker_task(void* arg) {
|
||||||
(void)arg;
|
(void)arg;
|
||||||
// 等待摄像头 ISP 预热 + 视频流启动稳定
|
// 等待摄像头 ISP 预热 + 视频流启动稳定
|
||||||
@ -38,6 +70,18 @@ static void face_tracker_task(void* arg) {
|
|||||||
|
|
||||||
ESP_LOGI(TAG, "face_tracker task started on core %d", xPortGetCoreID());
|
ESP_LOGI(TAG, "face_tracker task started on core %d", xPortGetCoreID());
|
||||||
|
|
||||||
|
// [2026-04-20 重大修复] 分配 PSRAM RGB888 缓冲区,手动 YUYV→RGB888 转换
|
||||||
|
// 绕过 esp-dl ImagePreprocessor 的 YUYV 路径(疑似产生固定激活 bug)
|
||||||
|
// 240*240*3 = 172800 字节,PSRAM 8MB 完全够
|
||||||
|
constexpr size_t RGB_SIZE = 240 * 240 * 3;
|
||||||
|
uint8_t* rgb_buf = (uint8_t*)heap_caps_malloc(RGB_SIZE, MALLOC_CAP_SPIRAM);
|
||||||
|
if (!rgb_buf) {
|
||||||
|
ESP_LOGE(TAG, "分配 RGB888 缓冲失败");
|
||||||
|
vTaskDelete(NULL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ESP_LOGI(TAG, "RGB888 转换缓冲已分配 %u bytes", (unsigned)RGB_SIZE);
|
||||||
|
|
||||||
// 构造检测器:默认 model_type 由 CONFIG_DEFAULT_HUMAN_FACE_DETECT_MODEL 决定
|
// 构造检测器:默认 model_type 由 CONFIG_DEFAULT_HUMAN_FACE_DETECT_MODEL 决定
|
||||||
// lazy_load=true(默认)以减少启动期内存瞬时占用
|
// lazy_load=true(默认)以减少启动期内存瞬时占用
|
||||||
auto* detector = new(std::nothrow) HumanFaceDetect();
|
auto* detector = new(std::nothrow) HumanFaceDetect();
|
||||||
@ -62,11 +106,51 @@ static void face_tracker_task(void* arg) {
|
|||||||
(unsigned)info.total_allocated_bytes);
|
(unsigned)info.total_allocated_bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// [2026-04-21 诊断结论] 多格式 JPEG dump 测试确认:sensor 实际输出 YUYV packed 格式
|
||||||
|
// - frame_YUYV.jpg 画面清晰(能看到戴眼镜人脸 + 背景),只是色彩偏绿紫
|
||||||
|
// - frame_RGB565.jpg / UYVY / YUV422P 全是彩色马赛克
|
||||||
|
// - 色偏原因:FORMAT_CTRL00=0x61 的 bit[3:0]=1 在 YUV 模式下是 YVYU sequence
|
||||||
|
// (实际字节序 Y V Y U,不是标准 YUYV 的 Y U Y V)
|
||||||
|
// → yuyv_to_rgb888_line 要按 YVYU 读取:byte[1]=V, byte[3]=U
|
||||||
|
// 保留 JPEG dump 用于拍照验证(先确认摄像头正常再跑人脸识别)
|
||||||
|
// [2026-04-22] sensor 切到硬件 JPEG 模式(CONFIG_CAMERA_OV3660_DVP_JPEG_1280X720_12FPS)
|
||||||
|
// sensor 内部已做完 YUV→RGB→JPEG 全流程色彩处理,输出标准 JPEG 字节流
|
||||||
|
// 我们不再需要 image_to_jpeg 二次编码,直接把 f.data 透传即可
|
||||||
|
{
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(2000)); // JPEG 模式分辨率 1280x720,sensor 需要更长曝光稳定时间
|
||||||
|
auto* cam = dynamic_cast<Esp32Camera*>(Board::GetInstance().GetCamera());
|
||||||
|
Esp32Camera::FrameRef f;
|
||||||
|
if (cam && cam->CaptureForDetection(&f) && f.data && f.len > 0) {
|
||||||
|
const uint8_t* jpg = (const uint8_t*)f.data;
|
||||||
|
size_t jpg_len = f.len;
|
||||||
|
ESP_LOGI(TAG, "===JPEG_DUMP_BEGIN fmt=SENSOR_JPEG size=%u w=%u h=%u===",
|
||||||
|
(unsigned)jpg_len, f.width, f.height);
|
||||||
|
static const char b64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||||
|
char line[128]; size_t lp = 0;
|
||||||
|
for (size_t i = 0; i < jpg_len; i += 3) {
|
||||||
|
uint32_t v = ((uint32_t)jpg[i] << 16);
|
||||||
|
if (i + 1 < jpg_len) v |= ((uint32_t)jpg[i+1] << 8);
|
||||||
|
if (i + 2 < jpg_len) v |= jpg[i+2];
|
||||||
|
line[lp++] = b64[(v >> 18) & 0x3F];
|
||||||
|
line[lp++] = b64[(v >> 12) & 0x3F];
|
||||||
|
line[lp++] = (i + 1 < jpg_len) ? b64[(v >> 6) & 0x3F] : '=';
|
||||||
|
line[lp++] = (i + 2 < jpg_len) ? b64[v & 0x3F] : '=';
|
||||||
|
if (lp >= 72) { line[lp] = 0; printf("%s\n", line); lp = 0; }
|
||||||
|
}
|
||||||
|
if (lp > 0) { line[lp] = 0; printf("%s\n", line); }
|
||||||
|
ESP_LOGI(TAG, "===JPEG_DUMP_END===");
|
||||||
|
cam->ReleaseDetectionFrame(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 按 Kconfig 配置的 FPS 计算节拍
|
// 按 Kconfig 配置的 FPS 计算节拍
|
||||||
const TickType_t period = pdMS_TO_TICKS(1000 / CONFIG_XIAOZHI_FACE_TRACKING_FPS);
|
const TickType_t period = pdMS_TO_TICKS(1000 / CONFIG_XIAOZHI_FACE_TRACKING_FPS);
|
||||||
TickType_t last_wake = xTaskGetTickCount();
|
TickType_t last_wake = xTaskGetTickCount();
|
||||||
int hit = 0, miss = 0;
|
int hit = 0, miss = 0;
|
||||||
int64_t last_report_us = esp_timer_get_time();
|
int64_t last_report_us = esp_timer_get_time();
|
||||||
|
// 实时日志限频:每秒最多 1 条(INFO 级别便于排查)
|
||||||
|
int64_t last_detail_log_us = 0;
|
||||||
|
int miss_streak = 0; // 连续 miss 计数
|
||||||
|
|
||||||
while (!s_stop) {
|
while (!s_stop) {
|
||||||
vTaskDelayUntil(&last_wake, period);
|
vTaskDelayUntil(&last_wake, period);
|
||||||
@ -82,13 +166,48 @@ static void face_tracker_task(void* arg) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 组装 esp-dl 图像描述符
|
// [Bug 1 诊断] 首次进入循环时,打印前 32 字节 + 中心像素 + 统计,判断数据性质
|
||||||
// RESEARCH Pitfall A1:先假定 YUYV;若首轮 score 低于 0.5 可改 RGB565LE(决策点 D-B)
|
// 全零 → 摄像头无数据;规律 → 字节序/格式问题;随机 → 正常但模型看不懂
|
||||||
|
static bool debug_dumped = false;
|
||||||
|
if (!debug_dumped && f.data && f.len >= 32) {
|
||||||
|
debug_dumped = true;
|
||||||
|
const uint8_t* d = (const uint8_t*)f.data;
|
||||||
|
ESP_LOGI(TAG, "frame debug: size=%u w=%u h=%u len=%u",
|
||||||
|
(unsigned)f.width * f.height * 2, f.width, f.height, (unsigned)f.len);
|
||||||
|
// 打印左上角 16 字节 + 中心附近 16 字节
|
||||||
|
size_t center = (f.width * (f.height / 2) + f.width / 2) * 2;
|
||||||
|
if (center + 16 <= f.len) {
|
||||||
|
ESP_LOGI(TAG, "top-left 16B: %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x",
|
||||||
|
d[0],d[1],d[2],d[3],d[4],d[5],d[6],d[7],d[8],d[9],d[10],d[11],d[12],d[13],d[14],d[15]);
|
||||||
|
ESP_LOGI(TAG, "center 16B: %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x",
|
||||||
|
d[center],d[center+1],d[center+2],d[center+3],d[center+4],d[center+5],d[center+6],d[center+7],
|
||||||
|
d[center+8],d[center+9],d[center+10],d[center+11],d[center+12],d[center+13],d[center+14],d[center+15]);
|
||||||
|
}
|
||||||
|
// 统计:零字节比例(判断摄像头是否真有数据)
|
||||||
|
size_t zero_cnt = 0;
|
||||||
|
for (size_t i = 0; i < f.len; i++) if (d[i] == 0) zero_cnt++;
|
||||||
|
ESP_LOGI(TAG, "zero bytes: %u / %u (%.1f%%)",
|
||||||
|
(unsigned)zero_cnt, (unsigned)f.len, 100.0f * zero_cnt / f.len);
|
||||||
|
}
|
||||||
|
|
||||||
|
// [2026-04-20 重大修复] 手动 YUYV → RGB888 转换,绕过 esp-dl 预处理黑盒
|
||||||
|
// 以前:img.pix_type = YUYV,让 ImagePreprocessor 内部做 YUV→RGB,但它产生固定激活
|
||||||
|
// 现在:先转成 RGB888 喂给模型,pix_type 标 RGB888,消除预处理不确定性
|
||||||
|
{
|
||||||
|
const uint8_t* src = (const uint8_t*)f.data;
|
||||||
|
uint8_t* dst = rgb_buf;
|
||||||
|
for (uint16_t row = 0; row < f.height; row++) {
|
||||||
|
yuyv_to_rgb888_line(src, dst, f.width);
|
||||||
|
src += f.width * 2; // YUYV 每像素 2 字节
|
||||||
|
dst += f.width * 3; // RGB888 每像素 3 字节
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
dl::image::img_t img{};
|
dl::image::img_t img{};
|
||||||
img.data = (void*)f.data;
|
img.data = (void*)rgb_buf;
|
||||||
img.width = f.width;
|
img.width = f.width;
|
||||||
img.height = f.height;
|
img.height = f.height;
|
||||||
img.pix_type = dl::image::DL_IMAGE_PIX_TYPE_YUYV;
|
img.pix_type = dl::image::DL_IMAGE_PIX_TYPE_RGB888;
|
||||||
|
|
||||||
int64_t t0 = esp_timer_get_time();
|
int64_t t0 = esp_timer_get_time();
|
||||||
auto& results = detector->run(img);
|
auto& results = detector->run(img);
|
||||||
@ -97,10 +216,17 @@ static void face_tracker_task(void* arg) {
|
|||||||
// 立即归还 V4L2 缓冲,避免 face_track 占用时间长
|
// 立即归还 V4L2 缓冲,避免 face_track 占用时间长
|
||||||
cam->ReleaseDetectionFrame(f);
|
cam->ReleaseDetectionFrame(f);
|
||||||
|
|
||||||
|
int64_t now_us = esp_timer_get_time();
|
||||||
if (results.empty()) {
|
if (results.empty()) {
|
||||||
miss++;
|
miss++;
|
||||||
|
miss_streak++;
|
||||||
|
// 连续 3 秒无人脸时提示一次(按默认 FPS=10 折算 ~30 帧)
|
||||||
|
if (miss_streak == CONFIG_XIAOZHI_FACE_TRACKING_FPS * 3) {
|
||||||
|
ESP_LOGI(TAG, "no face detected in last 3s");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
hit++;
|
hit++;
|
||||||
|
miss_streak = 0;
|
||||||
// PLAN 未明确排序策略,esp-dl 内部 nms 后 list 顺序不稳定
|
// PLAN 未明确排序策略,esp-dl 内部 nms 后 list 顺序不稳定
|
||||||
// 为健壮性,挑 score 最高的那个(避免多脸时摇摆)
|
// 为健壮性,挑 score 最高的那个(避免多脸时摇摆)
|
||||||
const dl::detect::result_t* best = nullptr;
|
const dl::detect::result_t* best = nullptr;
|
||||||
@ -121,8 +247,19 @@ static void face_tracker_task(void* arg) {
|
|||||||
if (uart_send_face != nullptr) {
|
if (uart_send_face != nullptr) {
|
||||||
uart_send_face(x_offset, y_offset);
|
uart_send_face(x_offset, y_offset);
|
||||||
}
|
}
|
||||||
ESP_LOGD(TAG, "face score=%.2f offset=(%d,%d) infer=%lldus",
|
// INFO 级别实时日志,限频每秒 1 条避免刷屏
|
||||||
best->score, x_offset, y_offset, (long long)(t1 - t0));
|
// 修复:%lld 在 nano newlib 下输出异常,改为 %lu + uint32(infer<2s 安全)
|
||||||
|
if (now_us - last_detail_log_us > 1000000LL) {
|
||||||
|
ESP_LOGI(TAG, "face: score=%.2f box=[%d,%d,%d,%d] offset=(%+d,%+d) infer=%lums",
|
||||||
|
best->score,
|
||||||
|
best->box[0], best->box[1], best->box[2], best->box[3],
|
||||||
|
x_offset, y_offset,
|
||||||
|
(unsigned long)((t1 - t0) / 1000));
|
||||||
|
last_detail_log_us = now_us;
|
||||||
|
}
|
||||||
|
// 高频详细日志保留为 LOGD(需 idf.py monitor 按 Ctrl+T Y 切换为 DEBUG)
|
||||||
|
ESP_LOGD(TAG, "face score=%.2f offset=(%d,%d) infer=%luus",
|
||||||
|
best->score, x_offset, y_offset, (unsigned long)(t1 - t0));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 每 10 秒汇报一次统计(加保底避免除零)
|
// 每 10 秒汇报一次统计(加保底避免除零)
|
||||||
@ -140,6 +277,9 @@ static void face_tracker_task(void* arg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
delete detector;
|
delete detector;
|
||||||
|
if (rgb_buf) {
|
||||||
|
heap_caps_free(rgb_buf);
|
||||||
|
}
|
||||||
ESP_LOGI(TAG, "face_tracker task exiting");
|
ESP_LOGI(TAG, "face_tracker task exiting");
|
||||||
s_handle = nullptr;
|
s_handle = nullptr;
|
||||||
vTaskDelete(NULL);
|
vTaskDelete(NULL);
|
||||||
@ -151,11 +291,14 @@ extern "C" void face_tracker_start(void) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
s_stop = false;
|
s_stop = false;
|
||||||
// Core 0 + 优先级 2:低于 LVGL / 音频,避免抢占主路径
|
// [2026-04-20 修复 WDT 崩溃] 原绑 Core 0 + 优先级 2 会导致:
|
||||||
|
// esp-dl 推理占 150ms → 同核的 RMT LED 驱动拿不到 spinlock 超过 300ms →
|
||||||
|
// 触发 Interrupt WDT → SetDeviceState 切换时点 LED 崩溃。
|
||||||
|
// 改绑到 Core 1(WiFi/RMT/LED 在 Core 0,音频在 Core 1 但只 speaking 时重载)。
|
||||||
// 栈 8KB:给 esp-dl 推理留充足空间
|
// 栈 8KB:给 esp-dl 推理留充足空间
|
||||||
BaseType_t ok = xTaskCreatePinnedToCore(
|
BaseType_t ok = xTaskCreatePinnedToCore(
|
||||||
face_tracker_task, "face_track",
|
face_tracker_task, "face_track",
|
||||||
8 * 1024, nullptr, 2, &s_handle, 0);
|
8 * 1024, nullptr, 2, &s_handle, 1);
|
||||||
if (ok != pdPASS) {
|
if (ok != pdPASS) {
|
||||||
ESP_LOGE(TAG, "xTaskCreatePinnedToCore failed");
|
ESP_LOGE(TAG, "xTaskCreatePinnedToCore failed");
|
||||||
s_handle = nullptr;
|
s_handle = nullptr;
|
||||||
|
|||||||
@ -30,8 +30,11 @@ dependencies:
|
|||||||
espressif/esp-sr: ~2.3.1
|
espressif/esp-sr: ~2.3.1
|
||||||
espressif/button: ~4.1.3
|
espressif/button: ~4.1.3
|
||||||
espressif/knob: ^1.0.0
|
espressif/knob: ^1.0.0
|
||||||
|
# [Phase 01] 2026-04-20 升级:1.3.1 在 S3 DVP 启动时少填 xclk_freq 字段
|
||||||
|
# 导致 VIDIOC_STREAMON 报 "xclk_freq is not set"(见 dvp_video_device.c:229-241)
|
||||||
|
# 升级到 ~1.4.1 解决,如果需要 API 适配再改 esp32_camera.cc
|
||||||
espressif/esp_video:
|
espressif/esp_video:
|
||||||
version: ==1.3.1 # for compatibility. update version may need to modify this project code.
|
version: ~1.4.1
|
||||||
rules:
|
rules:
|
||||||
- if: target not in [esp32]
|
- if: target not in [esp32]
|
||||||
espressif/esp_image_effects:
|
espressif/esp_image_effects:
|
||||||
|
|||||||
@ -1,8 +1,12 @@
|
|||||||
# ESP-IDF Partition Table
|
# ESP-IDF Partition Table
|
||||||
|
# [Phase 01] 2026-04-17 方案 A:扩大 OTA 分区给 esp-dl 人脸检测模型留空间
|
||||||
|
# 原布局:ota_0/ota_1 各 3.94MB(0x3f0000),assets 8MB
|
||||||
|
# 新布局:ota_0/ota_1 各 5MB(0x500000),assets 缩到 5.875MB(0x5e0000 = 6016K)
|
||||||
|
# 总计:0x20000 + 0x500000 + 0x500000 + 0x5e0000 = 0x1000000 = 16MB
|
||||||
# Name, Type, SubType, Offset, Size, Flags
|
# Name, Type, SubType, Offset, Size, Flags
|
||||||
nvs, data, nvs, 0x9000, 0x4000,
|
nvs, data, nvs, 0x9000, 0x4000,
|
||||||
otadata, data, ota, 0xd000, 0x2000,
|
otadata, data, ota, 0xd000, 0x2000,
|
||||||
phy_init, data, phy, 0xf000, 0x1000,
|
phy_init, data, phy, 0xf000, 0x1000,
|
||||||
ota_0, app, ota_0, 0x20000, 0x3f0000,
|
ota_0, app, ota_0, 0x20000, 0x500000,
|
||||||
ota_1, app, ota_1, , 0x3f0000,
|
ota_1, app, ota_1, , 0x500000,
|
||||||
assets, data, spiffs, 0x800000, 8M
|
assets, data, spiffs, 0xa20000, 0x5e0000
|
||||||
|
|||||||
|
107
scripts/auto_capture_jpeg.py
Normal file
107
scripts/auto_capture_jpeg.py
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# auto_capture_jpeg.py
|
||||||
|
# 自动连接 ESP32 串口、触发复位、等待多个 JPEG dump、保存并打开所有图片
|
||||||
|
# 支持新格式:===JPEG_DUMP_BEGIN fmt=<NAME> size=<N>===
|
||||||
|
|
||||||
|
import serial
|
||||||
|
import base64
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
PORT = "/dev/cu.usbmodem834401"
|
||||||
|
BAUD = 115200
|
||||||
|
OUT_DIR = Path("/Users/rdzleo/Desktop/CogletESP-camera-version/scripts")
|
||||||
|
TIMEOUT_SEC = 90
|
||||||
|
MAX_FRAMES = 4
|
||||||
|
|
||||||
|
BEGIN_RE = re.compile(r"===JPEG_DUMP_BEGIN\s+(?:fmt=(\S+)\s+)?size=(\d+)===")
|
||||||
|
END_RE = re.compile(r"===JPEG_DUMP_END===")
|
||||||
|
B64_RE = re.compile(r"^[A-Za-z0-9+/=]+$")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
print(f"[·] 打开串口 {PORT} @ {BAUD}")
|
||||||
|
ser = serial.Serial(PORT, BAUD, timeout=1)
|
||||||
|
|
||||||
|
print("[·] 复位 ESP32 …")
|
||||||
|
ser.dtr = False
|
||||||
|
ser.rts = True
|
||||||
|
time.sleep(0.1)
|
||||||
|
ser.rts = False
|
||||||
|
time.sleep(0.1)
|
||||||
|
ser.reset_input_buffer()
|
||||||
|
|
||||||
|
print(f"[·] 等待 JPEG_DUMP 标记(最多 {TIMEOUT_SEC}s,期望 {MAX_FRAMES} 张)…")
|
||||||
|
start = time.time()
|
||||||
|
in_dump = False
|
||||||
|
expected_size = 0
|
||||||
|
current_fmt = None
|
||||||
|
b64_buf = []
|
||||||
|
saved_files = []
|
||||||
|
|
||||||
|
while time.time() - start < TIMEOUT_SEC and len(saved_files) < MAX_FRAMES:
|
||||||
|
raw = ser.readline()
|
||||||
|
if not raw:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
line = raw.decode("utf-8", errors="replace").rstrip("\r\n")
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if any(k in line for k in ["FaceTracker", "Camera", "panic", "Guru", "ov3660", "Compile time"]):
|
||||||
|
print(f" {line}")
|
||||||
|
|
||||||
|
if not in_dump:
|
||||||
|
m = BEGIN_RE.search(line)
|
||||||
|
if m:
|
||||||
|
in_dump = True
|
||||||
|
current_fmt = m.group(1) or "unknown"
|
||||||
|
expected_size = int(m.group(2))
|
||||||
|
b64_buf = []
|
||||||
|
print(f"[+] JPEG_BEGIN fmt={current_fmt} size={expected_size}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
if END_RE.search(line):
|
||||||
|
in_dump = False
|
||||||
|
b64_str = "".join(b64_buf)
|
||||||
|
try:
|
||||||
|
data = base64.b64decode(b64_str)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[!] base64 decode failed: {e}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
if len(data) != expected_size:
|
||||||
|
print(f"[!] 字节数差异 got={len(data)} expected={expected_size}")
|
||||||
|
|
||||||
|
out_path = OUT_DIR / f"frame_{current_fmt}.jpg"
|
||||||
|
out_path.write_bytes(data)
|
||||||
|
print(f"[✓] 保存 {out_path.name} ({len(data)/1024:.1f} KB)")
|
||||||
|
saved_files.append(out_path)
|
||||||
|
continue
|
||||||
|
|
||||||
|
stripped = line.strip()
|
||||||
|
if B64_RE.match(stripped):
|
||||||
|
b64_buf.append(stripped)
|
||||||
|
|
||||||
|
ser.close()
|
||||||
|
|
||||||
|
if not saved_files:
|
||||||
|
print("[!] 没有抓到任何 JPEG 帧")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
print(f"\n[✓] 共保存 {len(saved_files)} 张")
|
||||||
|
for p in saved_files:
|
||||||
|
print(f" - {p}")
|
||||||
|
# 用 Finder 打开目录,用户可以并排对比
|
||||||
|
subprocess.run(["open", str(OUT_DIR)])
|
||||||
|
# 或者直接打开所有 JPEG
|
||||||
|
for p in saved_files:
|
||||||
|
subprocess.run(["open", str(p)])
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
88
scripts/extract_jpeg_from_log.py
Executable file
88
scripts/extract_jpeg_from_log.py
Executable file
@ -0,0 +1,88 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# extract_jpeg_from_log.py
|
||||||
|
# 从 ESP32 串口日志中提取 base64 编码的 JPEG 图像并保存为 .jpg 文件
|
||||||
|
#
|
||||||
|
# 用法:
|
||||||
|
# 1) 启动 monitor 并把日志重定向到文件:
|
||||||
|
# idf.py -p /dev/cu.usbmodem834401 monitor > /tmp/esp32.log
|
||||||
|
# 或者直接从已有日志提取:
|
||||||
|
# python3 extract_jpeg_from_log.py /tmp/esp32.log
|
||||||
|
# 2) 设备启动后会打印 "===JPEG_DUMP_BEGIN===" .... "===JPEG_DUMP_END==="
|
||||||
|
# 3) 运行此脚本会在当前目录生成 frame_001.jpg 等文件
|
||||||
|
#
|
||||||
|
# 也支持从 stdin 实时读取:
|
||||||
|
# idf.py monitor | python3 extract_jpeg_from_log.py -
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import re
|
||||||
|
import base64
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
BEGIN_RE = re.compile(r"===JPEG_DUMP_BEGIN\s+size=(\d+)===")
|
||||||
|
END_RE = re.compile(r"===JPEG_DUMP_END===")
|
||||||
|
# 匹配纯 base64 行(不含普通文本)
|
||||||
|
B64_RE = re.compile(r"^[A-Za-z0-9+/=]+$")
|
||||||
|
|
||||||
|
|
||||||
|
def extract(lines, out_dir=Path(".")):
|
||||||
|
frame_idx = 0
|
||||||
|
in_dump = False
|
||||||
|
b64_buf = []
|
||||||
|
expected_size = 0
|
||||||
|
|
||||||
|
for raw in lines:
|
||||||
|
line = raw.rstrip("\r\n")
|
||||||
|
# 去掉 ESP 日志时间戳前缀可能引入的干扰:只在 begin/end 标记附近处理
|
||||||
|
if not in_dump:
|
||||||
|
m = BEGIN_RE.search(line)
|
||||||
|
if m:
|
||||||
|
in_dump = True
|
||||||
|
expected_size = int(m.group(1))
|
||||||
|
b64_buf = []
|
||||||
|
print(f"[+] JPEG_BEGIN size={expected_size}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
if END_RE.search(line):
|
||||||
|
in_dump = False
|
||||||
|
b64_str = "".join(b64_buf)
|
||||||
|
try:
|
||||||
|
data = base64.b64decode(b64_str)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[!] base64 decode failed: {e}")
|
||||||
|
continue
|
||||||
|
if len(data) != expected_size:
|
||||||
|
print(
|
||||||
|
f"[!] size mismatch: got {len(data)} expected {expected_size} "
|
||||||
|
f"(可能是 monitor 丢字节,仍尝试保存)"
|
||||||
|
)
|
||||||
|
frame_idx += 1
|
||||||
|
out_path = out_dir / f"frame_{frame_idx:03d}.jpg"
|
||||||
|
out_path.write_bytes(data)
|
||||||
|
print(f"[✓] saved {out_path} ({len(data)} bytes)")
|
||||||
|
# macOS: 自动打开
|
||||||
|
if sys.platform == "darwin":
|
||||||
|
import subprocess
|
||||||
|
subprocess.run(["open", str(out_path)])
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 处于 dump 区间,只收集看起来是 base64 的行
|
||||||
|
stripped = line.strip()
|
||||||
|
if B64_RE.match(stripped):
|
||||||
|
b64_buf.append(stripped)
|
||||||
|
# 其他行(比如 "I (xxx) TAG:" 的日志)忽略
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
print("Usage: extract_jpeg_from_log.py <logfile | ->")
|
||||||
|
sys.exit(1)
|
||||||
|
src = sys.argv[1]
|
||||||
|
if src == "-":
|
||||||
|
extract(sys.stdin)
|
||||||
|
else:
|
||||||
|
with open(src, "r", errors="replace") as f:
|
||||||
|
extract(f)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
149
设备运行日志.txt
Normal file
149
设备运行日志.txt
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
rdzleo@RdzleodeMac-Studio CogletESP-camera-version % export IDF_PATH='/Users/rdzleo/esp/esp-idf/v5.4.2/esp-idf'
|
||||||
|
rdzleo@RdzleodeMac-Studio CogletESP-camera-version % '/Users/rdzleo/.espressif/python_env/idf5.4_py3.13_env/bin/python3' '/Users/rdzleo/esp/esp-idf/v5.4.2/esp-idf/tools/idf_monitor.py' -p /dev/tty.usbmodem834401 -b 115200 --
|
||||||
|
toolchain-prefix xtensa-esp32s3-elf- --make ''/Users/rdzleo/.espressif/python_env/idf5.4_py3.13_env/bin/python3' '/Users/rdzleo/esp/esp-idf/v5.4.2/esp-idf/tools/idf.py'' --target esp32s3 '/Users/rdzleo/Desktop/CogletESP-came
|
||||||
|
ra-version/build/xiaozhi.elf'
|
||||||
|
--- Warning: Serial ports accessed as /dev/tty.* will hang gdb if launched.
|
||||||
|
--- Using /dev/cu.usbmodem834401 instead...
|
||||||
|
--- esp-idf-monitor 1.8.0 on /dev/cu.usbmodem834401 115200
|
||||||
|
--- Quit: Ctrl+] | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H
|
||||||
|
ESP-ROM:esp32s3-20210327
|
||||||
|
Build:Mar 27 2021
|
||||||
|
rst:0x15 (USB_UART_CHIP_RESET),boot:0x8 (SPI_FAST_FLASH_BOOT)
|
||||||
|
Saved PC:0x40384d8e
|
||||||
|
--- 0x40384d8e: esp_cpu_wait_for_intr at /Users/rdzleo/esp/esp-idf/components/esp_hw_support/cpu.c:64
|
||||||
|
SPIWP:0xee
|
||||||
|
mode:DIO, clock div:1
|
||||||
|
load:0x3fce2820,len:0x56c
|
||||||
|
load:0x403c8700,len:0x4
|
||||||
|
load:0x403c8704,len:0xc30
|
||||||
|
load:0x403cb700,len:0x2e2c
|
||||||
|
entry 0x403c890c
|
||||||
|
I (37) octal_psram: vendor id : 0x0d (AP)
|
||||||
|
I (37) octal_psram: dev id : 0x02 (generation 3)
|
||||||
|
I (37) octal_psram: density : 0x03 (64 Mbit)
|
||||||
|
I (39) octal_psram: good-die : 0x01 (Pass)
|
||||||
|
I (43) octal_psram: Latency : 0x01 (Fixed)
|
||||||
|
I (47) octal_psram: VCC : 0x01 (3V)
|
||||||
|
I (51) octal_psram: SRF : 0x01 (Fast Refresh)
|
||||||
|
I (56) octal_psram: BurstType : 0x01 (Hybrid Wrap)
|
||||||
|
I (61) octal_psram: BurstLen : 0x01 (32 Byte)
|
||||||
|
I (65) octal_psram: Readlatency : 0x02 (10 cycles@Fixed)
|
||||||
|
I (71) octal_psram: DriveStrength: 0x00 (1/1)
|
||||||
|
I (75) MSPI Timing: PSRAM timing tuning index: 4
|
||||||
|
I (79) esp_psram: Found 8MB PSRAM device
|
||||||
|
I (83) esp_psram: Speed: 80MHz
|
||||||
|
I (86) cpu_start: Multicore app
|
||||||
|
I (100) cpu_start: Pro cpu start user code
|
||||||
|
I (100) cpu_start: cpu freq: 240000000 Hz
|
||||||
|
I (100) app_init: Application information:
|
||||||
|
I (100) app_init: Project name: xiaozhi
|
||||||
|
I (104) app_init: App version: 2.0.5
|
||||||
|
I (108) app_init: Compile time: Apr 20 2026 18:05:09
|
||||||
|
I (113) app_init: ELF file SHA256: cd6d6438e...
|
||||||
|
I (117) app_init: ESP-IDF: v5.4.2-390-g0f6b683441-dirty
|
||||||
|
I (123) efuse_init: Min chip rev: v0.0
|
||||||
|
I (127) efuse_init: Max chip rev: v0.99
|
||||||
|
I (131) efuse_init: Chip rev: v0.2
|
||||||
|
I (135) heap_init: Initializing. RAM available for dynamic allocation:
|
||||||
|
I (141) heap_init: At 3FCAFCE8 len 00039A28 (230 KiB): RAM
|
||||||
|
I (146) heap_init: At 3FCE9710 len 00005724 (21 KiB): RAM
|
||||||
|
I (151) heap_init: At 3FCF0000 len 00008000 (32 KiB): DRAM
|
||||||
|
I (156) heap_init: At 600FE000 len 00001FD8 (7 KiB): RTCRAM
|
||||||
|
I (162) esp_psram: Adding pool of 8192K of PSRAM memory to heap allocator
|
||||||
|
I (169) spi_flash: detected chip: generic
|
||||||
|
I (172) spi_flash: flash io: qio
|
||||||
|
I (175) sleep_gpio: Configure to isolate all GPIO pins in sleep state
|
||||||
|
I (181) sleep_gpio: Enable automatic switching of GPIO sleep configuration
|
||||||
|
I (188) main_task: Started on CPU0
|
||||||
|
I (198) esp_psram: Reserving pool of 64K of internal memory for DMA/internal allocations
|
||||||
|
I (198) main_task: Calling app_main()
|
||||||
|
I (198) uart: ESP_INTR_FLAG_IRAM flag not set while CONFIG_UART_ISR_IN_IRAM is enabled, flag updated
|
||||||
|
I (238) Board: UUID=fcb5789b-4c1b-41b1-9271-4e4b23b27178 SKU=bread-compact-wifi-s3cam
|
||||||
|
I (238) gpio: GPIO[0]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
|
||||||
|
I (238) button: IoT Button Version: 4.1.6
|
||||||
|
I (238) gpio: GPIO[39]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
|
||||||
|
I (248) gpio: GPIO[40]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
|
||||||
|
I (258) gpio: GPIO[41]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
|
||||||
|
I (268) gpio: GPIO[42]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
|
||||||
|
I (298) ov3660: Detected Camera sensor PID=0x3660
|
||||||
|
I (428) Esp32Camera: Camera init success
|
||||||
|
E (428) CAM: camera ptr: 0x3fcca0b4
|
||||||
|
I (428) Application: STATE: starting
|
||||||
|
I (428) NoAudioCodec: Simplex channels created
|
||||||
|
I (428) AudioCodec: Set input enable to true
|
||||||
|
I (428) AudioCodec: Set output enable to true
|
||||||
|
I (438) AudioCodec: Audio codec started
|
||||||
|
I (438) pp: pp rom version: e7ae62f
|
||||||
|
I (438) net80211: net80211 rom version: e7ae62f
|
||||||
|
I (458) wifi:wifi driver task: 3fcdcaf4, prio:23, stack:6144, core=0
|
||||||
|
I (458) wifi:wifi firmware version: 3263cda
|
||||||
|
I (458) wifi:wifi certification version: v7.0
|
||||||
|
I (458) wifi:config NVS flash: disabled
|
||||||
|
I (468) wifi:config nano formatting: enabled
|
||||||
|
I (468) wifi:Init data frame dynamic rx buffer num: 6
|
||||||
|
I (468) wifi:Init dynamic rx mgmt buffer num: 5
|
||||||
|
I (478) wifi:Init management short buffer num: 32
|
||||||
|
I (478) wifi:Init dynamic tx buffer num: 32
|
||||||
|
I (488) wifi:Init static tx FG buffer num: 2
|
||||||
|
I (488) wifi:Init static rx buffer size: 1600
|
||||||
|
I (498) wifi:Init static rx buffer num: 3
|
||||||
|
I (498) wifi:Init dynamic rx buffer num: 6
|
||||||
|
I (498) wifi_init: rx ba win: 3
|
||||||
|
I (508) wifi_init: accept mbox: 6
|
||||||
|
I (508) wifi_init: tcpip mbox: 16
|
||||||
|
I (508) wifi_init: udp mbox: 6
|
||||||
|
I (508) wifi_init: tcp mbox: 6
|
||||||
|
I (518) wifi_init: tcp tx win: 5760
|
||||||
|
I (518) wifi_init: tcp rx win: 5760
|
||||||
|
I (518) wifi_init: tcp mss: 1440
|
||||||
|
I (528) phy_init: phy_version 701,f4f1da3a,Mar 3 2025,15:50:10
|
||||||
|
I (568) phy_init: Saving new calibration data due to checksum failure or outdated calibration data, mode(0)
|
||||||
|
I (618) wifi:mode : sta (20:6e:f1:b9:9a:28)
|
||||||
|
I (618) wifi:enable tsf
|
||||||
|
I (3028) WifiStation: Found AP: airhub, BSSID: 70:2a:d7:85:bc:eb, RSSI: -35, Channel: 1, Authmode: 3
|
||||||
|
W (3028) wifi:Password length matches WPA2 standards, authmode threshold changes from OPEN to WPA2
|
||||||
|
I (3128) wifi:new:<1,0>, old:<1,0>, ap:<255,255>, sta:<1,0>, prof:1, snd_ch_cfg:0x0
|
||||||
|
I (3128) wifi:state: init -> auth (0xb0)
|
||||||
|
I (3128) wifi:state: auth -> assoc (0x0)
|
||||||
|
I (3138) wifi:state: assoc -> run (0x10)
|
||||||
|
I (3168) wifi:connected with airhub, aid = 1, channel 1, BW20, bssid = 70:2a:d7:85:bc:eb
|
||||||
|
I (3168) wifi:security: WPA2-PSK, phy: bgn, rssi: -34
|
||||||
|
I (3168) wifi:pm start, type: 1
|
||||||
|
|
||||||
|
I (3178) wifi:dp: 1, bi: 102400, li: 3, scale listen interval from 307200 us to 307200 us
|
||||||
|
I (3188) wifi:set rx beacon pti, rx_bcn_pti: 0, bcn_timeout: 25000, mt_pti: 0, mt_time: 10000
|
||||||
|
I (3198) wifi:<ba-add>idx:0 (ifx:0, 70:2a:d7:85:bc:eb), tid:0, ssn:0, winSize:64
|
||||||
|
I (3228) wifi:AP's beacon interval = 102400 us, DTIM period = 1
|
||||||
|
I (5758) esp_netif_handlers: sta ip: 192.168.124.53, mask: 255.255.255.0, gw: 192.168.124.1
|
||||||
|
I (5758) WifiStation: Got IP: 192.168.124.53
|
||||||
|
I (5758) Assets: The storage free size is 20224 KB
|
||||||
|
I (5758) Assets: The partition size is 6016 KB
|
||||||
|
I (5828) Assets: The checksum calculation time is 67 ms
|
||||||
|
create static modelsI (5828) MODEL_LOADER: Successfully load srmodels
|
||||||
|
I (5838) Assets: Refreshing display theme...
|
||||||
|
W (5838) Display: SetEmotion: microchip_ai
|
||||||
|
I (5838) Application: STATE: activating
|
||||||
|
W (5838) Display: SetStatus: 检查新版本...
|
||||||
|
I (5848) Ota: Current version: 2.0.5
|
||||||
|
I (6488) esp-x509-crt-bundle: Certificate validated
|
||||||
|
I (6918) HttpClient: Established new connection to api.tenclass.net:443
|
||||||
|
E (7238) Dynamic Impl: mbedtls_ssl_fetch_input error=29312
|
||||||
|
I (7238) HttpClient: HTTP connection closed
|
||||||
|
I (7238) Ota: Current is the latest version
|
||||||
|
I (7238) Ota: Running partition: ota_0
|
||||||
|
W (7248) Display: SetStatus: 登录服务器...
|
||||||
|
I (7248) MCP: Add tool: self.get_device_status
|
||||||
|
I (7248) MCP: Add tool: self.audio_speaker.set_volume
|
||||||
|
I (7258) MCP: Add tool: self.camera.take_photo
|
||||||
|
I (7258) MCP: Add tool: self.get_system_info [user]
|
||||||
|
I (7268) MCP: Add tool: self.reboot [user]
|
||||||
|
I (7268) MCP: Add tool: self.upgrade_firmware [user]
|
||||||
|
I (7278) MCP: Add tool: self.assets.set_download_url [user]
|
||||||
|
I (7278) MQTT: Connecting to endpoint mqtt.xiaozhi.me
|
||||||
|
I (7388) esp-x509-crt-bundle: Certificate validated
|
||||||
|
I (8048) MQTT: Connected to endpoint
|
||||||
|
I (15438) AudioCodec: Set input enable to false
|
||||||
|
I (15438) AudioCodec: Set output enable to false
|
||||||
|
I (15438) SystemInfo: free sram: 155443 minimal sram: 152567
|
||||||
|
I (25438) SystemInfo: free sram: 155407 minimal sram: 152567
|
||||||
|
I (35438) SystemInfo: free sram: 155443 minimal sram: 152567
|
||||||
Loading…
x
Reference in New Issue
Block a user