ESP32-S3 摄像头图传:固件 + Slint 客户端 + 共享协议

两块 ESP32-S3-WROOM-1-N16R8 图传板(DVP 接口接 OV2640/OV3660/OV5640),
向队伍客户端与裁判终端推 UDP 图传。固件是跑在 ESP-IDF 上的 Rust,用官方
esp32-camera 组件采集;客户端是 Rust + Slint 桌面程序。

三个 crate 各自独立构建:
  .            firmware,交叉编译到 Xtensa
  protocol/    线路格式与帧重组,两端共用,无 ESP-IDF 依赖,有单元测试
  client/      Slint 桌面客户端

视频走 UDP 而非 TCP:实测传感器产出 22 fps,TCP MJPEG 只送出 1.9 fps,
且只占用链路能力的 1%——链路是空的,帧堵在队头阻塞后面等一个实时画面
等不起的重传计时器。改为独立数据报、不重传、缺片丢整帧后达到 20.8 fps。

发现是双向的:板子每 2 秒广播 Hello,客户端广播 Subscribe,板子据此定向
推流。板子身份由 MAC 派生,重启不变,所以客户端可以把绑定存进配置。

裁判端同时收两块板子(每个发送方一个重组器),队伍端绑定其中一块并只显示
自己那一边的血量。装甲板接口已预留(ArmorSource trait + ARMOR_PORT),
当前无数据源,界面显示占位符。

vendor 了 esp32-camera 2.1.7:组件注册表的版本求解器找不到它,且需要打一处
补丁(见 components/esp32-camera/target/esp32s3/ll_cam.c)。

.gitignore 三条 target/ 都锚定在根目录,因为 protocol/ 与 client/ 各自也是
独立 crate;而 components/esp32-camera/target/ 是上游的按芯片分目录,里面是
源码,必须保留。
This commit is contained in:
Horldsence
2026-10-05 18:22:19 +08:00
commit 4f893ce140
134 changed files with 41128 additions and 0 deletions
+25
View File
@@ -0,0 +1,25 @@
[build]
target = "xtensa-esp32s3-espidf"
[target.'cfg(target_os = "espidf")']
linker = "ldproxy"
runner = "espflash flash --monitor"
rustflags = ["--cfg", "espidf_time64"]
[unstable]
build-std = ["std", "panic_abort"]
[env]
MCU = "esp32s3"
# Use the ESP-IDF installation and toolchains that are already on the machine
# (eim's install under ~/.espressif) instead of downloading another copy into
# ./.embuild. Without this the first build re-downloads several hundred MB of
# Xtensa GCC/LLVM.
#
# `global` means ~/.espressif, which is where eim installs.
#
# Drop these two lines to let esp-idf-sys clone and fetch the ESP-IDF version it
# pins itself (v5.5.5 by default), into ~/.espressif.
IDF_PATH = { value = "/Users/peng/.espressif/esp-idf/v5.5.5", force = true }
ESP_IDF_TOOLS_INSTALL_DIR = { value = "global", force = true }
+11
View File
@@ -0,0 +1,11 @@
# Copy to .env and fill in. Never commit .env - it holds the WiFi password.
# Required
WIFI_SSID=your-ssid
WIFI_PASS=your-password
# Optional
# Capture resolution: qqvga | qvga | cif | hvga | vga | svga | xga | sxga | uxga
#CAM_RES=vga
# JPEG quality 0-63, lower is better quality
#CAM_QUALITY=12
+15
View File
@@ -0,0 +1,15 @@
# 构建产物。
#
# 三条都必要:根目录是 firmware crate,protocol/ 和 client/ 各自也是独立的
# crate(各有自己的 .cargo/config.toml 与 target 目录),所以只写 `target/`
# 匹配不到它们。
#
# 全部锚定在根目录(前置 /),这是有意的:components/esp32-camera/target/ 看着
# 像构建产物,其实是上游组件的**按芯片分目录**(esp32/ esp32s2/ esp32s3/ …),
# 里面是 ll_cam.c 等源码,包含本项目打的补丁。不要把它一起忽略掉。
/target/
/protocol/target/
/client/target/
# WiFi 凭据,编译期由 env! 读取。不要提交。
.env
Generated
+1711
View File
File diff suppressed because it is too large Load Diff
+45
View File
@@ -0,0 +1,45 @@
[package]
name = "esp32"
version = "0.1.0"
edition = "2021"
resolver = "2"
rust-version = "1.82"
[[bin]]
name = "esp32"
harness = false # no built-in test harness, keeps the generated project simple
[dependencies]
log = "0.4"
anyhow = "1"
embedded-svc = "0.29"
esp-idf-svc = "0.53"
# The UDP wire format, shared with the desktop client in client/. It is built for
# the host too (see protocol/.cargo/config.toml), which is what lets the protocol
# have unit tests: the frame assembler and the encoder are the parts most likely to
# be subtly wrong, and neither can be exercised from the firmware.
protocol = { path = "protocol" }
[build-dependencies]
embuild = "0.33.5"
# The official `esp32-camera` component is not part of ESP-IDF itself. It is
# vendored under components/esp32-camera so the build does not depend on the
# component registry, and so that one IDF v6 incompatibility can be patched -
# see the "LOCAL PATCH" comment in components/esp32-camera/target/esp32s3/ll_cam.c.
#
# `cam_wrapper` is a tiny local component (see components/cam_wrapper) that
# wraps `camera_config_t` in a union-free plain struct, which makes the Rust
# side safe to construct.
[[package.metadata.esp-idf-sys.extra_components]]
component_dirs = ["components/esp32-camera", "components/cam_wrapper"]
bindings_header = "bindings.h"
bindings_module = "camera"
[profile.release]
opt-level = "s"
[profile.dev]
debug = true
opt-level = "z"
+402
View File
@@ -0,0 +1,402 @@
# ESP32-S3 摄像头图传(Rust / ESP-IDF / UDP)
**ESP32-S3-WROOM-1-N16R8** + OV2640 / OV3660 / OV5640(DVP 接口)实时图传,
客户端是 Rust + Slint 桌面程序。固件是跑在 ESP-IDF 上的 Rust,用官方
`esp32-camera` 组件做采集。
```
protocol/ 线路格式 + 帧重组 (有单元测试,为宿主架构编译)
. 固件 -> ESP32-S3 (交叉编译,Xtensa)
client/ 桌面客户端 (Slint,macOS/Linux/Windows)
```
`protocol/` 被两端共用,所以线路格式不可能各自漂移。
## 实测性能
VGA 640x480,JPEG 质量 12,板子与 2.4GHz AP 相距约一间房:
| | TCP(HTTP MJPEG) | UDP |
|---|---|---|
| 帧率 | 1.9 fps | **20.8 fps** |
| 丢帧 | 大量超时 | **0.0%**(146 帧 / 1270 个数据报) |
| 帧间隔 | 测不出来 | 35 ms |
| 实际吞吐 | 0.28 Mbit/s | 1.72 Mbit/s |
| 传感器自身上限 | 22.2 fps | 22.2 fps |
UDP 这条路已经跑到传感器的极限,网络不再是瓶颈——即便如此也只用掉链路的
6–9%,说明瓶颈从头到尾都不在带宽上。
(为什么用 UDP、以及 TCP 那组数字到底说明什么,见
[`protocol/README.md`](protocol/README.md)。)
---
# 固件
## 环境要求
- **Rust 的 `esp` 工具链。** 仓库里有 `rust-toolchain.toml`,所以
`rustup toolchain install esp` 就够;否则用 `espup install`。
- **ESP-IDF。** `.cargo/config.toml` 通过 `IDF_PATH` 指向已有的安装。删掉那两行
的话,`esp-idf-sys` 会自己 clone 并构建它固定的那个版本——能用,但要下载几百 MB。
- **Python 3**(ESP-IDF 构建系统要用)。
- **esptool** 或 **espflash**,用来烧录。
先确认本机已具备:
```sh
rustc --version # 必须是 esp 工具链
espflash --version
python3 --version
```
## 配置
WiFi 凭据在**编译期**由 `env!` 读取,所以放在 `.env` 里,不进源码。`.env` 已在
gitignore 中。
```sh
cp .env.example .env
```
```sh
# .env
WIFI_SSID=你的2.4G频段ssid
WIFI_PASS=你的密码
# 以下都有默认值:
#CAM_RES=vga # qqvga | qvga | cif | hvga | vga | svga | xga | sxga | uxga
#CAM_QUALITY=12 # 0-63,数字越小画质越好、字节越少
#CAM_VSYNC_INVERT=1 # 若板子在硬件上反相了 VSYNC,填 0
```
SSID **必须是 2.4GHz**。ESP32-S3 的射频不支持 5GHz,5GHz 的 SSID 对它完全不可见,
板子只会一直超时等待。固件在关联前会先扫描并打印可见网络,正是为了让这个问题
能被一眼看出:
```
I esp32: scanning for 2.4 GHz networks...
I esp32: SR-602-2G4 ch 6 -27 dBm Some(WPAWPA2Personal) <- target
E esp32: "SR-602-5G" was not in the scan results
```
## 编译
```sh
./scripts/build.sh
```
这就是"读取 `.env` 后执行 `cargo build --release`"。产物在
`target/xtensa-esp32s3-espidf/release/esp32`(约 1.1 MB)。
> **改了 C 文件?**
> `components/esp32-camera/**` 和 `components/cam_wrapper/**` 不在 `esp-idf-sys`
> 的依赖追踪里,所以改它们**不会**触发重新编译,会静默地用旧代码。先清缓存:
>
> ```sh
> rm -rf target/xtensa-esp32s3-espidf/release/build/esp-idf-sys-*
> ./scripts/build.sh
> ```
## 烧录并看日志
```sh
./scripts/flash.sh --monitor 30
```
它会编译、烧录、打印串口日志。预期输出:
```
I (2790) cam_wrapper: fps probe: 20 frames in 900 ms -> 22.2 fps, avg 9246 bytes
I (9817) esp32::udp: UDP sender listening on port 5600, streaming on request
I (9817) esp32: ESP32 camera is live on 192.168.1.139
I (9817) esp32: point the client at 192.168.1.139:5600 (UDP)
```
`fps probe` 那一行是**传感器上限**,在 WiFi 启动之前测的。排查流速慢时要拿它做
对照:如果 probe 说 22fps 而实际流不到,问题就在两者之间。
### 手动烧录
`scripts/flash.sh` 存在的原因之一是有一块坏 flash 字节(见
[硬件相关](#硬件相关))。它用 `esptool` 写三段:
```sh
B=$(ls -d target/xtensa-esp32s3-espidf/release/build/esp-idf-sys-*/out/build)
espflash save-image --chip esp32s3 --format esp-idf \
target/xtensa-esp32s3-espidf/release/esp32 /tmp/app.bin
~/.espressif/tools/python/*/venv/bin/python -m esptool \
--chip esp32s3 --port /dev/cu.usbmodemXXX --baud 460800 write-flash \
0x0 "$B/bootloader/bootloader.bin" \
0x8000 "$B/partition_table/partition-table.bin" \
0x10000 /tmp/app.bin
```
注意 app 偏移必须和 `partitions.csv` 里的 `factory` 一致。这里不能用
`cargo espflash flash`:它只写 app 段,而且会在没有 `.env` 的情况下重新编译,
导致 `main.rs` 里的 `env!` 失败。
### 找串口
```sh
ls /dev/cu.usbmodem* # 这块板子显示为 /dev/cu.usbmodem111301
```
`scripts/flash.sh` 会取第一个匹配项,也可以 `ESP_PORT=/dev/cu.usbmodemXXX` 指定。
---
# 客户端
## 环境要求
- Rust(普通 stable 或 nightly,**不要**用固件那个 `esp` 工具链)
- 不需要额外的系统库:macOS 和 Linux 上 Slint 用 `femtovg` 渲染,也不需要 `skia`
> `client/.cargo/config.toml` 把 `target` 钉成 `aarch64-apple-darwin`,因为这个
> 目录位于固件源码树内部,否则会继承 Xtensa 交叉编译配置。**要按你的机器改**——
> Intel Mac 用 `x86_64-apple-darwin`,Linux 用 `rustc -vV | grep host` 的输出。
## 编译与运行
```sh
cd client
cargo build --release
./target/aarch64-apple-darwin/release/cam-client # 用下面的默认地址
./target/aarch64-apple-darwin/release/cam-client 192.168.1.139
```
```
用法: cam-client [CAMERA_IP] [PORT]
CAMERA_IP ESP32 摄像头地址(默认 192.168.1.139)
PORT UDP 视频端口 (默认 5600)
```
改 `ui/camera.slint` 时用一步到位的方式:
```sh
cd client && cargo run --release -- 192.168.1.139
```
## 界面读数
```
streaming 20.8 fps 10.1 kB per frame 35 ms between frames
640x480 shown 146 dropped 0 (0.0%) 1270 datagrams
```
**丢帧率和帧率同等重要**,这正是把统计放在界面上的原因。这条传输链路是**整帧丢弃
而不重传**,所以单看帧率是有歧义的:
| 帧率 | 丢帧 | 含义 |
|---|---|---|
| 达到传感器上限 | 0 | 链路没问题,瓶颈是传感器 |
| 远低于上限 | 有 | 瓶颈是网络 |
| 低 | 0,且显示 "no frames - camera?" | 板子没在推流 |
状态文字的颜色是同一信息的粗略版本:推流中绿色,否则琥珀色。
### 帧率是"计数"算出来的,不是"平均间隔"
不是对帧间隔取平均。一次 UI 轮询之间可能已完成好几帧,所以平均间隔量的是
**显示**的刷新快慢,而不是**相机**的出帧快慢——那曾把上限 22fps 的传感器报成
36fps。一个笃定的错误数字比没有数字更糟。
## 诊断
```sh
RUST_LOG=info ./target/aarch64-apple-darwin/release/cam-client 192.168.1.139
```
```
frames shown 146 | dropped 0 (0.0%) | datagrams 1270 | undecodable 0 | short 0
```
| 计数器 | 含义 |
|---|---|
| `frames shown` | 完整重组并交给界面的帧 |
| `dropped` | 因分片缺失而被放弃的帧 |
| `datagrams` | 能解析为分片的数据报 |
| `undecodable` | 收到了但解析不了的数据报 |
`undecodable` 非零意味着板子的数据报**到了但没看懂**——是协议或端口对不上,不是网络
问题。这两者很容易混淆,而这个计数器正是用来区分它们的。
## 排查
| 现象 | 原因 |
|---|---|
| 一直显示 `connecting to …` | 板子不在网上,或防火墙拦了入站 UDP 5600 |
| `no frames - camera?` | 板子停止推流:重启了,或掉线了 |
| 完全没有输出,也没有日志 | host triple 不对,见[目标架构](#目标架构) |
| 只有第一帧然后卡住 | 另一个客户端连在别的端口上;板子只推给最后宣告的那个 |
| 颜色错乱 | 通道顺序。当前构建用 `Rgba8Pixel`,另一个选项是 `Bgra8Pixel` |
| 编译在 `libc` / `memmap2` 报错 | 用的是 `esp` 工具链,或缺少 target triple 覆盖 |
---
# 工作原理
## 采集 → UDP
```
OV5640 ──DVP──> LCD_CAM/GDMA ──> PSRAM 帧缓冲
│
frame::next_frame() (阻塞)
│
切成 1400 字节一个的数据报
│
对每个 peer sendto()
```
`esp_camera_fb_get` 会阻塞到传感器出帧,所以帧率跟随传感器,中间没有任何可能被
积压 overrun 的队列。数据报一进内核,帧缓冲立刻还给驱动——多占着只会增加延迟。
## 对端
板子是服务端,客户端是对等端。客户端绑定 UDP 5600 并发一个宣告数据报;板子随后
就往它看到的源地址推流,当存活探测开始失败时移除该对端。没有握手、没有发现协议,
而且客户端晚连(或板子重启)都能自恢复——因为宣告每 500ms 重发一次。
最多并发 4 个对端。
## 为什么要 `cam_wrapper`
`esp_camera.h` 里的 `camera_config_t` 对已废弃的 SCCB 引脚名用了匿名 union,bindgen
会生成成 unsafe union 字段,从 Rust 构造很别扭。`components/cam_wrapper` 接收一个
纯 int 结构体,在 C 侧填 union,于是 Rust 侧的绑定简单到可以安全构造。
## 客户端线程模型
三个线程,且**没有任何 worker 线程碰 Slint 组件**:
- `udp-reader` —— 收包与重组
- `jpeg-decode` —— JPEG 解码成 RGBA
- 主线程 / 事件循环 —— 转成 `slint::Image` 并更新属性,每 30ms 一次
这是 Slint 的硬性约束而非风格偏好:组件只能在拥有它的线程上使用,
`Weak<App>` 是 `!Send` 就是为了强制这一点。worker 线程去 upgrade 一个 weak handle
会拿到 `None` 然后什么都不做——现象和"界面卡死"完全一样。
---
# 硬件相关
## 引脚表
默认匹配 **Freenove ESP32-S3-WROOM CAM** 的摄像头座(`src/camera.rs` 的 `pins`
模块):
| 信号 | GPIO | | 信号 | GPIO |
|---|---|---|---|---|
| RESET | 0 | | D0 (Y2) | 11 |
| XCLK | 15 | | D1 (Y3) | 9 |
| PCLK | 13 | | D2 (Y4) | 8 |
| VSYNC | 6 | | D3 (Y5) | 10 |
| HREF | 7 | | D4 (Y6) | 12 |
| SIOD | 4 | | D5 (Y7) | 18 |
| SIOC | 5 | | D6 (Y8) | 17 |
| | | | D7 (Y9) | 16 |
数据总线是传感器的 **Y2..Y9**,即 10 位像素字的低字节,所以 Y2 是 D0、Y9 是 D7。
### 千万不要套用别的板子的引脚表
上面每个引脚都是 ESP32-S3 空出来的。下面这些**不是**:
| GPIO | 被什么占用 | 拿来用会怎样 |
|---|---|---|
| 26–32 | 模组的 SPI flash | `PIN_FUNC_SELECT` 改掉 flash 的复用,CPU **在传输中途挂死** |
| 33–37 | octal PSRAM | 同上,对内存总线而言 |
| 19, 20 | 原生 USB | 串口 console 断掉 |
| 43, 44 | console UART | 日志断掉 |
| 45, 46 | 模组内部 | 保留 |
在 ESP32-S3 上套用 AI-Thinker ESP32-CAM 的引脚表是踩这个坑的经典方式:15 根信号里
有 7 根落在 flash、PSRAM 或 USB 上。现象是板子能正常枚举、摄像头也能配置成功,
然后在第一次 `PIN_FUNC_SELECT` 时锁死。
## Flash:DIO @ 40MHz
`sdkconfig.defaults` 用 DIO/40MHz 而不是这个片子标称的 QIO/80MHz,因为测试用的模组
有一块坏 flash 字节(见下),四线模式会把它推进启动循环。硬件正常的话可以换回
QIO + 80MHz——程序是从 flash 运行的,这会明显加快**代码**执行。
## 坏的那块 flash 字节
用于点亮的这块模组,其 SPI flash 在 **`0xFADE7`** 有一个字节的 bit5–7 写不进去。
镜像放在该地址上的任何值都会被破坏,校验和失败,bootloader 拒绝启动。
镜像内容不受它在 flash 中**位置**的影响,所以 `scripts/flash.sh` 会挑一个 64KB 对齐
的 app 偏移,使该地址上的字节是这块 flash 存得下的,相应改写 `partitions.csv`,并在
烧录前校验。
**这是为坏硬件准备的绕行方案,不是设计选择。** 换到正常模组时:删掉
`scripts/flash.sh` 里的 `BAD_FLASH_ADDR` 逻辑,把 `partitions.csv` 的 `factory` 改回
`0x10000`,之后就可以用 `cargo espflash flash`。
## `PSRAM DMA` 是故意关掉的
`sdkconfig.defaults` 里 `CONFIG_CAMERA_PSRAM_DMA=n`,原因就写在那个文件里。简要说:
开着时 `esp32-camera` 只在最后 1KB 的 DMA 节点里找 JPEG 结束标记(EOI)。标记一旦
落在窗口外,这一帧就被 `NO-EOI` 丢弃——在这块板子上是大部分帧,之后驱动彻底不再
交出任何帧且不会恢复。关掉后每帧多一次约 20KB 的 `memcpy`,但搜索范围是整个缓冲。
## CPU 频率
显式设置了 `CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y`,因为 **ESP-IDF v5.5 把 ESP32-S3
的默认值设成 160MHz**(`components/esp_system/port/soc/esp32s3/Kconfig.cpu`)。默认
构建会白白浪费一半 CPU,而采集、JPEG 搜索、lwIP、HTTP 全是 CPU 密集的。启动时确认:
```
I (891) cpu_start: cpu freq: 240000000 Hz
```
## WiFi 缓冲留在内部 RAM
`CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP=n`。这里原本是 `y`,理由是"PSRAM 空间充足"——
恰好搞反了。WiFi 描述符和 TCP 段每个包都要碰,而内部 SRAM 比隔着内存总线的 PSRAM
快一个数量级。帧缓冲体积大、相对少碰,仍然留在 PSRAM。
运行时也关掉了 WiFi 省电模式:开着时空闲的信标间隔要花几百毫秒,表现为卡顿和
200–300ms 的 ping。
---
# 可调项
| 项目 | 位置 | 说明 |
|---|---|---|
| 分辨率 | `.env` 的 `CAM_RES` | QVGA..UXGA。CIF 以上需要 PSRAM。 |
| JPEG 质量 | `.env` 的 `CAM_QUALITY` | 0–63,越小越好。VGA 下 12 约 10–20KB/帧。 |
| 帧缓冲数 | `camera.rs` 的 `fb_count` | 3:一个在填、一个在发、一个备用。 |
| 指定传感器 | `main.rs` 的 `SensorHint` | 除非探测失败,否则用 `Auto`。 |
| XCLK | `camera.rs` 的 `xclk_freq_hz` | 20MHz;OV5640 的 PLL 只为此调校过。 |
| VSYNC 极性 | `.env` 的 `CAM_VSYNC_INVERT` | 仅当板子在硬件上反相时需要改。 |
| 采集核心 | `CONFIG_CAMERA_CORE0` | `n` 把采集放到 CPU1,避开 CPU0 上的 WiFi 任务。 |
| 分片大小 | `protocol/` 的 `MAX_FRAGMENT_PAYLOAD` | 1400 保持在 1500 MTU 以内。再大就有 IP 分片风险。 |
## 继续提高帧率
传感器在 VGA 下大约封顶 22fps,而链路现在已经跟得上,所以下一个杠杆在传感器侧:
- **SVGA(800x600)** 牺牲的是分辨率而非帧率——OV5640 的帧率基本由像素时钟决定。
- **调大 `CAM_QUALITY` 的数字** 会缩小帧体积、减少字节数,但瓶颈是每秒帧数而不是
带宽。
- 如果要的是低延迟而不是细节,用 **QQVGA/QVGA**:DVP 总线和 PSRAM 带宽都随像素数
线性变化。
## 协议测试
`protocol/` 是最容易出现细微错误、又无法在固件上验证的部分,所以它有真正的单元测试
并且为宿主架构编译:
```sh
cd protocol && cargo test
```
```
7 passed
```
+29
View File
@@ -0,0 +1,29 @@
/* Bindings header for the extra components of this project.
*
* esp-idf-sys runs bindgen on this file and puts the result into
* `esp_idf_sys::camera`. The camera headers are guarded so the build keeps working
* even if the component is not enabled in a given sdkconfig.
*/
#pragma once
#if defined(ESP_IDF_COMP_ESPRESSIF__ESP32_CAMERA_ENABLED) || __has_include("esp_camera.h")
#include "esp_camera.h"
#endif
#include "cam_wrapper.h"
/* Raw lwIP sockets, for the UDP video sender.
*
* esp-idf-svc has no UDP socket type, and its HTTP server is TCP-only, which is
* exactly the transport that measured badly on this board: the sensor produces
* ~22 fps at VGA, but a TCP MJPEG stream over the same WiFi link managed ~2 fps
* while using less than 1% of the available bandwidth. The link was idle and the
* frames were stuck behind TCP head-of-line blocking, waiting on a retransmit
* timer. Sending the same JPEGs as independent datagrams removes that entirely.
*
* Including lwip's own header is all that is needed: it declares the socket calls
* and the real `sockaddr_in`. Declaring them here instead would both duplicate the
* declarations (a hard error) and produce an opaque struct that shadows the layout
* bindgen generates from the real header. */
#include <lwip/sockets.h>
+3
View File
@@ -0,0 +1,3 @@
fn main() {
embuild::espidf::sysenv::output();
}
+13
View File
@@ -0,0 +1,13 @@
# This crate builds for the host, not the ESP32.
#
# It sits inside the firmware's directory tree, so cargo walks up and inherits
# ../.cargo/config.toml, which pins `target = "xtensa-esp32s3-espidf"` and an
# ldproxy linker that only work inside the ESP-IDF cross build. Without this file
# the desktop client is cross-compiled for Xtensa, which fails deep inside
# dependencies (libc not finding MAP_FAILED and friends, because it was being
# compiled for an embedded target that has no mmap).
#
# Overriding the target back to the host triple is the whole fix. Nothing else in
# the parent config applies to a non-ESP target.
[build]
target = "aarch64-apple-darwin"
+6101
View File
File diff suppressed because it is too large Load Diff
+29
View File
@@ -0,0 +1,29 @@
[package]
name = "cam-client"
version = "0.1.0"
edition = "2021"
[build-dependencies]
# Compiles ui/camera.slint into Rust before the crate itself builds. Kept separate
# from [dependencies] because it only runs on the build host, never on the target.
slint-build = "1"
[dependencies]
# Shared wire format with the firmware. Kept as a path dependency rather than
# copied, so the two sides cannot drift apart silently.
protocol = { path = "../protocol" }
# Slint for the UI. The default renderer on macOS is femtovg, which needs no
# extra system libraries; `backend-winit` provides the window integration.
slint = "1"
# JPEG decoding for display. Slint's Image can take raw RGBA, so the JPEG has to
# be decoded somewhere - `image` does that, and nothing on this side re-encodes.
image = { version = "0.25", default-features = false, features = ["jpeg"] }
anyhow = "1"
log = "0.4"
env_logger = "0.11"
[profile.release]
opt-level = 2
+158
View File
@@ -0,0 +1,158 @@
# cam-client
[ESP32-S3 摄像头固件](../README.md) 的桌面查看器。接收 UDP 数据报形式的 JPEG 帧,
解码后显示实时画面,并给出帧率与丢帧统计。
Rust + [Slint](https://slint.dev),不需要额外的系统库。
## 环境要求
- Rust(普通工具链——**不要**用固件那个 `esp` 工具链)
- 已在运行本固件的摄像头,且能在 2.4GHz WiFi 下访问到
## 目标架构
**编译前先设好。** 本目录位于固件源码树内部,Cargo 否则会继承
`../.cargo/config.toml` 去交叉编译 `xtensa-esp32s3-espidf`——报错会出现在依赖
深处,而不是一眼可见的地方。
`client/.cargo/config.toml` 因此把目标钉成宿主架构:
```toml
[build]
target = "aarch64-apple-darwin" # Intel Mac:x86_64-apple-darwin
# Linux:rustc -vV | grep host 的输出
```
查看你的:
```sh
rustc -vV | grep host
```
## 编译
```sh
cargo build --release
```
因为上面的 target 覆盖,二进制落在带架构名的目录里:
```sh
./target/aarch64-apple-darwin/release/cam-client
```
## 运行
```sh
./target/aarch64-apple-darwin/release/cam-client # 用下面的默认值
./target/aarch64-apple-darwin/release/cam-client 192.168.1.139
./target/aarch64-apple-darwin/release/cam-client 192.168.1.139 5600
```
```
用法: cam-client [CAMERA_IP] [PORT]
CAMERA_IP ESP32 摄像头地址(默认 192.168.1.139)
PORT UDP 视频端口 (默认 5600)
```
改 UI 时:
```sh
cargo run --release -- 192.168.1.139
```
## 界面读数
```
streaming 20.8 fps 10.1 kB per frame 35 ms between frames
640x480 shown 146 dropped 0 (0.0%) 1270 datagrams
```
丢帧率的存在是有理由的。这条链路**整帧丢弃而不重试**,所以单看帧率是有歧义的:
| 帧率 | 丢帧 | 判断 |
|---|---|---|
| 达到传感器上限 | 0 | 链路正常,瓶颈是传感器 |
| 远低于上限 | 有 | 瓶颈是网络 |
| 低 | 0,且显示 "no frames - camera?" | 板子没在推流 |
状态文字的颜色是同一信息的粗略版本:推流中绿色,否则琥珀色。
### 帧率是"计数"算出来的,不是"平均间隔"
不是对帧间隔取平均。一次 UI 轮询之间可能已完成好几帧,所以平均间隔量的是
**显示**的刷新快慢,而不是**相机**的出帧快慢——那曾把上限 22fps 的传感器报成
36fps。一个笃定的错误数字比没有数字更糟。
## 诊断
```sh
RUST_LOG=info ./target/aarch64-apple-darwin/release/cam-client 192.168.1.139
```
```
frames shown 146 | dropped 0 (0.0%) | datagrams 1270 | undecodable 0 | short 0
```
| 计数器 | 含义 |
|---|---|
| `frames shown` | 完整重组并交给界面的帧 |
| `dropped` | 因分片缺失而被放弃的帧 |
| `datagrams` | 能解析为分片的数据报 |
| `undecodable` | 收到了但解析不了的数据报 |
`undecodable` 非零意味着板子的数据报**到了但没看懂**——是协议或端口对不上,不是网络
问题。这两者很容易混淆,而这个计数器正是用来区分它们的。
## 排查
| 现象 | 原因 |
|---|---|
| 一直显示 `connecting to …` | 板子不在网上,或防火墙拦了入站 UDP 5600 |
| `no frames - camera?` | 板子停止推流:重启了,或掉线了 |
| 完全没有输出,也没有日志 | host triple 不对,见[目标架构](#目标架构) |
| 只有第一帧然后卡住 | 另一个客户端连在别的端口上;板子只推给最后宣告的那个 |
| 颜色错乱 | 通道顺序。当前构建用 `Rgba8Pixel`,另一个选项是 `Bgra8Pixel` |
| 编译在 `libc` / `memmap2` 报错 | 用的是 `esp` 工具链,或缺少 target triple 覆盖 |
## 目录结构
```
client/
├── Cargo.toml
├── build.rs 编译 ui/camera.slint
├── ui/camera.slint 窗口定义
└── src/
├── main.rs 命令行、线程编排、统计
└── receiver.rs socket、重组、JPEG 解码
```
线路格式在 [`../protocol`](../protocol),与固件共用,两端不会漂移。
## 线程模型
三个线程,且**没有任何 worker 线程碰 Slint 组件**:
| 线程 | 职责 |
|---|---|
| `udp-reader` | `recv_from`、解析、重组 |
| `jpeg-decode` | JPEG → RGBA |
| 主线程(事件循环) | RGBA → `slint::Image`、更新属性,每 30ms 一次 |
这是 Slint 的硬性约束而非偏好:组件只能在拥有它的线程上使用,`Weak<App>` 是
`!Send` 就是为了强制这一点。worker 线程去 upgrade 一个 weak handle 会拿到 `None`
然后什么都不做——现象和"界面卡死"完全一样。
解码单独一个线程,是因为一张 VGA 的 JPEG 要花几毫秒,放在收包线程里会挤进数据报
之间,造成**链路上本不存在**的丢帧。
## 可调项
| 项目 | 位置 | 说明 |
|---|---|---|
| 轮询间隔 | `src/main.rs` 的 `UI_POLL` | 30ms。更快只是白费 CPU,不会多出帧。 |
| 帧率统计窗口 | `FPS_WINDOW` | 2 秒。在 ~22fps 下更短的窗口会量化失真。 |
| 帧超时 | `receiver.rs` 的 `FRAME_TIMEOUT` | 500ms。一帧约 40ms,更慢的就是丢了。 |
| 宣告间隔 | `ANNOUNCE_INTERVAL` | 500ms。同时兼作保活。 |
| 主题 | `build.rs` | 默认 `fluent`;可试 `fluent-dark`、`material`、`native`。 |
+10
View File
@@ -0,0 +1,10 @@
//! Compiles the Slint markup and writes the generated Rust.
//!
//! Without this, `slint::include_modules!` has nothing to expand to: it reads the
//! path to the generated file from an environment variable that only this build
//! script sets.
fn main() {
let config = slint_build::CompilerConfiguration::new().with_style("fluent".to_owned());
slint_build::compile_with_config("ui/camera.slint", config).expect("compiling ui/camera.slint");
}
+310
View File
@@ -0,0 +1,310 @@
//! 绑定配置:这个客户端是什么身份,以及看哪块板子。
//!
//! 队伍的"选定后不可更改"落在 [`Config::camera_id`] 上:首次运行时从发现列表里选,
//! 结果写进配置文件,之后直接读,不再询问。
//!
//! 用 TOML 而不是 JSON,因为这个文件是**给人看的**——现场排查时要能一眼看懂改了什么。
//! 而它只有十来个字段,不值得为它引入整个序列化框架。
use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
/// 客户端身份。
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Role {
/// 队伍终端:一块画面 + 自己那一边的血量。
Team,
/// 裁判终端:两块画面 + 双方血量。
Referee,
}
impl Role {
pub fn as_str(self) -> &'static str {
match self {
Role::Team => "team",
Role::Referee => "referee",
}
}
pub fn parse(s: &str) -> Option<Self> {
match s {
"team" => Some(Role::Team),
"referee" => Some(Role::Referee),
_ => None,
}
}
pub fn to_protocol(self) -> protocol::SubscribeRole {
match self {
Role::Team => protocol::SubscribeRole::Team,
Role::Referee => protocol::SubscribeRole::Referee,
}
}
}
/// 磁盘上的配置。
#[derive(Debug, Clone)]
pub struct Config {
/// 客户端身份。
pub role: Role,
/// 队伍端:自己属于哪一边,0 = 队伍一,1 = 队伍二。
///
/// 决定血量栏显示哪一边。裁判端两个都要,所以忽略这个值。
pub team: u8,
/// 绑定的板子 id(MAC 派生,重启不变)。
///
/// `None` 表示还没选过,首次运行会在界面上询问。
pub camera_id: Option<u32>,
/// 板子的 IP。DHCP 可能变,所以这只是发现阶段的一个提示。
pub camera_addr: Option<std::net::Ipv4Addr>,
}
impl Default for Config {
/// 默认当裁判机:它不需要任何配置就能用,是现场最省事的起点。
fn default() -> Self {
Self {
role: Role::Referee,
team: 0,
camera_id: None,
camera_addr: None,
}
}
}
impl Config {
/// 从磁盘读取。
///
/// 文件不存在时返回默认配置,这是正常路径——首次运行本来就没有配置。
pub fn load(path: &Path) -> Result<Self> {
if !path.exists() {
log::info!("no config at {}; starting with defaults", path.display());
return Ok(Self::default());
}
let text = std::fs::read_to_string(path)
.with_context(|| format!("reading {}", path.display()))?;
Self::parse(&text).with_context(|| format!("parsing {}", path.display()))
}
/// 写入磁盘。
///
/// 手写序列化而不是引入 toml crate:这个文件只有十来个字段,且格式必须与
/// [`Config::parse`] 严格对称——两份代码比一个依赖更容易在 review 时看出不匹配。
pub fn save(&self, path: &Path) -> Result<()> {
if let Some(dir) = path.parent() {
std::fs::create_dir_all(dir)
.with_context(|| format!("creating {}", dir.display()))?;
}
let mut out = String::new();
out.push_str("# cam-client 配置。\n");
out.push_str("#\n");
out.push_str("# role : team | referee\n");
out.push_str("# team = 一路画面 + 自己那一边的血量,需要绑定一块板子\n");
out.push_str("# referee = 两路画面 + 双方血量,不需要绑定\n");
out.push_str("# team : 0 = 队伍一, 1 = 队伍二(仅 team 端用)\n");
out.push_str("#\n");
out.push_str("# 绑定之后不要再改 camera_id:队伍端按它选画面,改了就换了视角。\n");
out.push_str("# camera_id 由板子的 MAC 派生,重启后不变,所以这个绑定是稳的。\n\n");
out.push_str(&format!("role = \"{}\"\n", self.role.as_str()));
out.push_str(&format!("team = {}\n", self.team));
if let Some(id) = self.camera_id {
out.push_str(&format!("camera_id = \"0x{id:08X}\"\n"));
}
if let Some(addr) = self.camera_addr {
out.push_str(&format!("camera_addr = \"{addr}\"\n"));
}
std::fs::write(path, out).with_context(|| format!("writing {}", path.display()))?;
log::info!("saved config to {}", path.display());
Ok(())
}
/// 解析 TOML 的一个子集。
///
/// 只支持 `key = value`、字符串、`整数`、十六进制字符串和注释——恰好是这个文件
/// 需要的全部,遇到不认识的形式会明确报错而不是静默忽略,因为静默忽略会让一个
/// 打错的配置看起来像是生效了。
fn parse(text: &str) -> Result<Self> {
let mut config = Config::default();
let mut role_seen = false;
for (lineno, raw) in text.lines().enumerate() {
let line = strip_comment(raw).trim();
if line.is_empty() {
continue;
}
let Some((key, value)) = line.split_once('=') else {
anyhow::bail!("line {}: expected `key = value`, got {line:?}", lineno + 1);
};
let key = key.trim();
let value = value.trim().trim_matches('"');
match key {
"role" => {
config.role = Role::parse(value).ok_or_else(|| {
anyhow::anyhow!(
"line {}: role must be `team` or `referee`, got {value:?}",
lineno + 1
)
})?;
role_seen = true;
}
"team" => {
config.team = value
.parse::<u8>()
.with_context(|| format!("line {}: team must be 0 or 1", lineno + 1))?;
}
"camera_id" => {
config.camera_id = Some(parse_id(value).with_context(|| {
format!("line {}: camera_id must look like 0x1A2B3C4D", lineno + 1)
})?);
}
"camera_addr" => {
config.camera_addr = Some(value.parse().with_context(|| {
format!("line {}: camera_addr must be an IPv4 address", lineno + 1)
})?);
}
_ => {
anyhow::bail!(
"line {}: unknown key {key:?}; refusing to guess",
lineno + 1
);
}
}
}
if !role_seen {
// 缺 role 时按裁判端处理:它是唯一无需配置就能工作的模式。
config.role = Role::Referee;
}
Ok(config)
}
}
/// 去掉 `#` 之后的内容。
fn strip_comment(line: &str) -> &str {
match line.find('#') {
Some(at) => &line[..at],
None => line,
}
}
/// 解析 `0x1A2B3C4D` 或十进制形式的板子 id。
fn parse_id(text: &str) -> Result<u32> {
let text = text.trim();
if let Some(hex) = text.strip_prefix("0x").or_else(|| text.strip_prefix("0X")) {
u32::from_str_radix(hex, 16).with_context(|| format!("{text:?} is not hex"))
} else {
text.parse::<u32>()
.with_context(|| format!("{text:?} is not a number"))
}
}
/// 默认配置文件位置。
///
/// 优先 `$XDG_CONFIG_HOME` 或 `~/.config`,与 Linux 上其它工具一致;macOS 上用同一个
/// 路径而不是 `~/Library/Application Support`,是为了让两台机器(裁判机和队伍机可能
/// 是不同系统)的配置结构一致。
pub fn default_config_path() -> PathBuf {
let base = std::env::var_os("XDG_CONFIG_HOME")
.map(PathBuf::from)
.filter(|p| !p.as_os_str().is_empty())
.unwrap_or_else(|| {
let home = std::env::var_os("HOME").map(PathBuf::from).unwrap_or_else(|| {
PathBuf::from(".")
});
home.join(".config")
});
base.join("cam-client").join("config.toml")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_a_team_config() {
let config = Config::parse(
r#"
# 队伍一的机器
role = "team"
team = 1
camera_id = "0x3D4D8A9A"
"#,
)
.expect("parses");
assert_eq!(config.role, Role::Team);
assert_eq!(config.team, 1);
assert_eq!(config.camera_id, Some(0x3D4D_8A9A));
assert!(config.is_bound());
}
#[test]
fn referee_needs_no_binding() {
let config = Config::parse("role = \"referee\"").expect("parses");
assert_eq!(config.role, Role::Referee);
assert!(config.is_bound(), "the referee watches everything");
}
#[test]
fn a_team_without_a_camera_is_unbound() {
let config = Config::parse("role = \"team\"\nteam = 0").expect("parses");
assert_eq!(config.role, Role::Team);
assert!(!config.is_bound(), "must ask which camera to watch");
}
#[test]
fn roundtrips_through_save_and_load() {
let mut dir = std::env::temp_dir();
dir.push(format!("cam-client-test-{}", std::process::id()));
let path = dir.join("nested").join("config.toml");
let config = Config {
role: Role::Team,
team: 1,
camera_id: Some(0x1234_5678),
camera_addr: Some("192.168.1.50".parse().unwrap()),
};
config.save(&path).expect("saves");
let loaded = Config::load(&path).expect("loads");
assert_eq!(loaded.role, config.role);
assert_eq!(loaded.team, config.team);
assert_eq!(loaded.camera_id, config.camera_id);
assert_eq!(loaded.camera_addr, config.camera_addr);
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn a_missing_file_is_the_default_not_an_error() {
let config = Config::load(Path::new("/nonexistent/cam-client/config.toml"))
.expect("a missing config is a normal first run");
assert_eq!(config.role, Role::Referee);
}
#[test]
fn refuses_garbage_instead_of_ignoring_it() {
// 静默忽略一个打错的 key,会让配置看起来生效了。
assert!(Config::parse("rol = \"team\"").is_err(), "unknown key");
assert!(Config::parse("role = \"camera\"").is_err(), "bad role value");
assert!(Config::parse("role = \"team\"\nteam = nine").is_err(), "bad team");
assert!(Config::parse("just some text").is_err(), "not key = value");
}
}
+628
View File
@@ -0,0 +1,628 @@
//! ESP32 图传查看器。
//!
//! 两种身份,由 [`Role`] 决定:
//!
//! - **队伍端** —— 一块画面 + 自己那一边的血量。需要绑定一块板子(选定后不可更改)。
//! - **裁判端** —— 两块画面左右并排 + 双方血量。不需要绑定。
//!
//! 用法:
//!
//! ```text
//! cam-client 默认裁判端
//! cam-client --role team --team 1 队伍一
//! cam-client --camera-id 0x3D4D8A9A 绑定到某块板子
//! ```
//!
//! 绑定与身份存在配置文件里(默认 `~/.config/cam-client/config.toml`)。首次运行
//! 队伍端会列出发现的摄像头,选定后写入,之后不再询问。
//!
//! # 为什么血量是占位符
//!
//! 装甲板接口已经预留([`receiver::ArmorSource`] 与 `protocol::ArmorHealth`),但
//! 现在没有实现也没有数据源。界面显示占位符而不是 0——0 看起来像真值,现场看到会
//! 以为对方已经没血了。
mod config;
mod receiver;
use std::error::Error;
use std::net::Ipv4Addr;
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use config::{Config, Role};
use receiver::{
decode_frame, ArmorSource, CameraInfo, DecodedFrame, NoArmorYet, Receiver,
SourceStats, ARMOR_BOARDS,
};
slint::include_modules!();
const USAGE: &str = "\
用法: cam-client [选项]
--role <team|referee> 身份。默认 referee(无需配置即可看两路)
--team <0|1> 自己属于哪一边,仅 team 端用
--camera-id <0xID> 绑定到某块板子(MAC 派生,重启不变)
--camera <IP> 向哪块板子发订阅(默认 255.255.255.255 广播)
--config <路径> 配置文件位置
--list 列出发现的摄像头后退出
-h, --help 显示这段帮助
";
/// 事件循环轮询新数据的间隔。
///
/// 不是每帧一次:显示不需要每秒重绘 22 次,更快只是让事件循环和接收路径抢 CPU。
const UI_POLL: Duration = Duration::from_millis(33);
/// 帧率统计窗口。
///
/// 按计数除以墙钟时间,而不是对帧间隔取平均。一次轮询之间可能完成多帧,所以平均间隔
/// 量的是**显示**的刷新快慢而不是**相机**的出帧快慢——那会把上限 22fps 的传感器
/// 报成 36fps。一个笃定的错误数字比没有数字更糟。
const FPS_WINDOW: Duration = Duration::from_secs(2);
/// 某一路的显示状态。
struct View {
/// 显示哪块板子(`None` = 还没有画面)。
sender_id: Option<u32>,
fps: f32,
window_started: Instant,
window_frames: u64,
reported: bool,
}
impl View {
fn new() -> Self {
Self {
sender_id: None,
fps: 0.0,
window_started: Instant::now(),
window_frames: 0,
reported: false,
}
}
/// 累计窗口内的帧数,算出帧率。
fn update_fps(&mut self, stats: &SourceStats) {
let window = self.window_started.elapsed();
if window < FPS_WINDOW {
return;
}
self.fps = (stats.frames_shown - self.window_frames) as f32 / window.as_secs_f32();
self.window_frames = stats.frames_shown;
self.window_started = Instant::now();
}
}
/// 解码后的画面缓存,供事件循环取用。
///
/// 解码线程只处理纯数据(`Vec<u8>` + 尺寸),`slint::Image` 的构造留在事件循环,
/// 因为 `slint::Image` 不是 `Send`。
type DecodedSlots = Arc<Mutex<Vec<(u32, DecodedFrame)>>>;
fn main() -> Result<(), Box<dyn Error>> {
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
let args = Args::parse(std::env::args().skip(1))?;
if args.help {
print!("{USAGE}");
return Ok(());
}
let config_path = args.config.clone().unwrap_or_else(config::default_config_path);
// 命令行覆盖配置文件,这样现场可以临时当另一台机器用而不用改文件。
let mut config = Config::load(&config_path)?;
if let Some(role) = args.role {
config.role = role;
}
if let Some(team) = args.team {
config.team = team;
}
if let Some(id) = args.camera_id {
config.camera_id = Some(id);
}
log::info!(
"role={} team={} camera_id={:?} config={}",
config.role.as_str(),
config.team,
config.camera_id,
config_path.display()
);
let rx = Arc::new(Receiver::start(
args.camera,
config.role.to_protocol(),
)?);
let discovery = rx.discovery();
let frames = rx.frames();
if args.list {
// 等几个广播周期,让列表有内容。
let socket = bind_socket()?;
let for_list = Arc::clone(&rx);
let handle = std::thread::spawn(move || for_list.run(socket));
std::thread::sleep(Duration::from_secs(5));
let cameras = discovery.online_cameras();
if cameras.is_empty() {
println!("没有发现摄像头。板子每 2 秒广播一次,再等等或检查网络。");
} else {
println!("发现 {} 块板子:", cameras.len());
for c in cameras {
println!(" 0x{:08X} {} (id 与 MAC 派生,重启不变)", c.id, c.addr);
}
}
rx.shutdown();
let _ = handle.join();
return Ok(());
}
// 血量目前没有数据源。接口已经就位,装甲板接上后换掉这一行即可。
let armor: Arc<dyn ArmorSource> = Arc::new(NoArmorYet);
let decoded: DecodedSlots = Arc::new(Mutex::new(Vec::new()));
// 接收线程。
{
let rx = Arc::clone(&rx);
std::thread::Builder::new()
.name("udp-reader".into())
.spawn(move || {
let Ok(socket) = bind_socket() else {
log::error!("could not bind the socket for the receive loop");
return;
};
rx.run(socket);
})?;
}
// 解码线程:一张 VGA 的 JPEG 要几毫秒,放在收包线程里会挤进数据报之间,造成
// 链路上本不存在的丢帧。
{
let frames = Arc::clone(&frames);
let decoded = Arc::clone(&decoded);
std::thread::Builder::new()
.name("jpeg-decode".into())
.spawn(move || {
loop {
if frames.pending_senders().is_empty() {
std::thread::sleep(Duration::from_millis(5));
continue;
}
for sender_id in frames.pending_senders() {
let Some(frame) = frames.take(sender_id) else {
continue;
};
match decode_frame(&frame.data) {
Some(pixels) => {
let mut slot = decoded.lock().expect("decoded mutex poisoned");
// 同一路只保留最新一张,避免解码已经过时的画面。
slot.retain(|(id, _)| *id != sender_id);
slot.push((sender_id, pixels));
}
None => log::warn!(
"frame {} from 0x{:08X} is not a decodable JPEG ({} bytes)",
frame.frame_id,
frame.sender_id,
frame.data.len()
),
}
}
}
})?;
}
let timer = slint::Timer::default();
// 每个身份一个闭包,而不是一个闭包里再 match 身份。
//
// 两种身份是两个不同的组件类型(TeamApp / RefereeApp),没有共同的超类可以
// 在一个闭包里统一调用 set_frame/set_frame_a。写成两个分支后每边都拿到具体
// 类型,属性赋值直接调用,不再需要 match 身份——那种 match 会让编译器在每个
// 赋值点都检查一遍身份,而它本来就是编译期就定死的。
match config.role {
Role::Team => {
let ui = TeamApp::new()?;
ui.show()?;
let weak = ui.as_weak();
let discovery = Arc::clone(&discovery);
let decoded = Arc::clone(&decoded);
let armor = Arc::clone(&armor);
let config_path = config_path.clone();
let team = config.team;
let bound = config.camera_id;
let mut view = View::new();
// 绑定回调与刷新定时器都要读这份列表,所以用锁共享,而不是让其中一个
// 把 `choices` 移走。
let choices: Arc<Mutex<Vec<CameraChoice>>> = Arc::new(Mutex::new(Vec::new()));
// 绑定完成时写配置。"选定不可更改"就落在这里:此后配置里已有
// camera_id,界面不再出现选择页。
let choices_for_pick = Arc::clone(&choices);
let pick_handle = ui.on_pick(move |index| {
let Ok(index) = usize::try_from(index) else {
return;
};
let picked = choices_for_pick
.lock()
.expect("choices mutex poisoned")
.get(index)
.map(|c| c.id_text.to_string());
let Some(id_text) = picked else {
return;
};
let Some(id) = parse_id(&id_text).ok() else {
log::error!("could not read back the selected id {id_text:?}");
return;
};
let saved = Config {
role: Role::Team,
team,
camera_id: Some(id),
camera_addr: None,
};
if let Err(err) = saved.save(&config_path) {
log::error!("could not save the binding: {err}");
log::error!("画面仍会显示,但下次启动要重新选择");
} else {
log::info!("bound to camera 0x{id:08X}; saved in the config");
}
});
let choices_for_poll = Arc::clone(&choices);
timer.start(slint::TimerMode::Repeated, UI_POLL, move || {
let Some(ui) = weak.upgrade() else {
return;
};
let cameras = discovery.online_cameras();
// 未绑定:只显示选择界面。
let Some(bound) = bound else {
let updated: Vec<CameraChoice> = cameras
.iter()
.map(|c| CameraChoice {
id_text: format!("0x{:08X}", c.id).into(),
addr_text: format!("{} ({:?})", c.addr, c.silence()).into(),
online: c.is_online(),
})
.collect();
*choices_for_poll.lock().expect("choices mutex poisoned") = updated.clone();
ui.set_choices(std::rc::Rc::new(slint::VecModel::from(updated)).into());
ui.set_choosing(true);
return;
};
ui.set_choosing(false);
if view.sender_id != Some(bound) {
view = View::new();
view.sender_id = Some(bound);
}
if let Some(image) = take_decoded(&decoded, bound) {
ui.set_frame(image);
}
let stats = discovery.stats(bound);
view.update_fps(&stats);
if !view.reported && stats.frames_shown > 0 {
log_diagnostics(&stats, bound);
view.reported = true;
}
ui.set_stats(render_camera_stats(&cameras, bound, &stats, view.fps));
ui.set_health(render_health(team, armor.as_ref()));
});
// 绑定回调必须活到窗口关闭。CallbackHandle 是 Copy,但 Drop 会注销
// 回调,所以必须真的把它泄漏掉——之前用 `mem::forget` 编译器提示它对
// Copy 类型无效,那确实无效:第一次点击后回调就被注销了,第二次点击
// 什么也不会发生。
std::boxed::Box::leak(Box::new(pick_handle));
}
Role::Referee => {
let ui = RefereeApp::new()?;
ui.show()?;
let weak = ui.as_weak();
let discovery = Arc::clone(&discovery);
let decoded = Arc::clone(&decoded);
let armor = Arc::clone(&armor);
let mut views: Vec<View> = (0..2).map(|_| View::new()).collect();
timer.start(slint::TimerMode::Repeated, UI_POLL, move || {
let Some(ui) = weak.upgrade() else {
return;
};
let cameras = discovery.online_cameras();
// 两路按 id 顺序填槽。槽位保持稳定(不会因为某块板子临时离线就重新
// 洗牌),否则画面会左右跳动。
let ids: Vec<u32> = cameras.iter().map(|c| c.id).take(2).collect();
for (i, view) in views.iter_mut().enumerate() {
let wanted = ids.get(i).copied();
if view.sender_id != wanted {
*view = View::new();
view.sender_id = wanted;
}
}
for (i, view) in views.iter_mut().enumerate() {
let Some(sender_id) = view.sender_id else {
continue;
};
if let Some(image) = take_decoded(&decoded, sender_id) {
if i == 0 {
ui.set_frame_a(image);
} else {
ui.set_frame_b(image);
}
}
let stats = discovery.stats(sender_id);
view.update_fps(&stats);
if !view.reported && stats.frames_shown > 0 {
log_diagnostics(&stats, sender_id);
view.reported = true;
}
let view_stats = render_camera_stats(&cameras, sender_id, &stats, view.fps);
if i == 0 {
ui.set_stats_a(view_stats);
} else {
ui.set_stats_b(view_stats);
}
}
ui.set_health_a(render_health(0, armor.as_ref()));
ui.set_health_b(render_health(1, armor.as_ref()));
});
}
}
// Timer 一旦被 drop 就停止,所以要活到进程结束。
let _timer = Box::leak(Box::new(timer));
slint::run_event_loop()?;
rx.shutdown();
Ok(())
}
/// 从解码缓存里取出某一路的最新画面。
///
/// 解码线程只处理纯数据,`slint::Image` 的构造留在这里——`slint::Image` 不是
/// `Send`,跨不了线程。
fn take_decoded(
decoded: &DecodedSlots,
sender_id: u32,
) -> Option<slint::Image> {
let mut slot = decoded.lock().expect("decoded mutex poisoned");
let pos = slot.iter().position(|(id, _)| *id == sender_id)?;
let (_, pixels) = slot.remove(pos);
let buffer = slint::SharedPixelBuffer::<slint::Rgba8Pixel>::clone_from_slice(
&pixels.rgba,
pixels.width,
pixels.height,
);
Some(slint::Image::from_rgba8(buffer))
}
/// 命令行参数。
struct Args {
role: Option<Role>,
team: Option<u8>,
camera_id: Option<u32>,
camera: Ipv4Addr,
config: Option<PathBuf>,
list: bool,
help: bool,
}
impl Args {
fn parse(argv: impl Iterator<Item = String>) -> Result<Self, Box<dyn Error>> {
let mut args = Args {
role: None,
team: None,
camera_id: None,
camera: protocol::BROADCAST_ADDR,
config: None,
list: false,
help: false,
};
let mut it = argv.peekable();
while let Some(arg) = it.next() {
match arg.as_str() {
"-h" | "--help" => args.help = true,
"--list" => args.list = true,
"--role" => {
let v = it.next().ok_or("--role needs a value")?;
args.role =
Some(Role::parse(&v).ok_or("role must be `team` or `referee`")?);
}
"--team" => {
let v = it.next().ok_or("--team needs a value")?;
args.team = Some(v.parse().map_err(|_| "--team must be 0 or 1")?);
}
"--camera-id" => {
let v = it.next().ok_or("--camera-id needs a value")?;
args.camera_id = Some(parse_id(&v)?);
}
"--camera" => {
let v = it.next().ok_or("--camera needs a value")?;
args.camera = v
.parse()
.map_err(|_| "--camera must be an IPv4 address")?;
}
"--config" => {
args.config = Some(PathBuf::from(it.next().ok_or("--config needs a path")?));
}
other => {
return Err(format!("unknown argument {other:?}\n\n{USAGE}").into());
}
}
}
Ok(args)
}
}
/// 绑定 UDP 接收 socket。
///
/// 固定端口(而非临时端口)是必须的:板子从订阅的**源端口**学到推流目标,而这个
/// socket 同时承担收包。
fn bind_socket() -> std::io::Result<std::net::UdpSocket> {
let socket = std::net::UdpSocket::bind(std::net::SocketAddrV4::new(
Ipv4Addr::UNSPECIFIED,
protocol::DEFAULT_PORT,
))?;
socket.set_read_timeout(Some(Duration::from_millis(100)))?;
// 广播是发现的前提:板子靠它宣告存在,客户端靠它发起订阅。
let _ = socket.set_broadcast(true);
Ok(socket)
}
/// 解析 `0x1A2B3C4D` 或十进制形式的板子 id。
fn parse_id(text: &str) -> Result<u32, Box<dyn Error>> {
let text = text.trim();
if let Some(hex) = text.strip_prefix("0x").or_else(|| text.strip_prefix("0X")) {
Ok(u32::from_str_radix(hex, 16)?)
} else {
Ok(text.parse()?)
}
}
/// 一次性诊断,在某一路第一次出画面时记录。
///
/// 不再单列"无法解码"计数:那是链路正常但内容坏了的情形,会在解码线程里逐帧告警,
/// 汇总在这里反而会把它和普通的丢帧混在一起。
fn log_diagnostics(stats: &SourceStats, sender_id: u32) {
log::info!(
"camera 0x{sender_id:08X}: {} frames shown | {} dropped ({:.1}%) | \
{} datagrams | {}x{} {} bytes",
stats.frames_shown,
stats.frames_dropped,
stats.loss_percent(),
stats.datagrams,
stats.width,
stats.height,
stats.last_frame_len,
);
}
/// 把某一路的统计渲染成界面结构。
fn render_camera_stats(
cameras: &[CameraInfo],
sender_id: u32,
stats: &SourceStats,
fps: f32,
) -> CameraStats {
let online = cameras
.iter()
.find(|c| c.id == sender_id)
.map(CameraInfo::is_online)
.unwrap_or(false);
// 从没出过画面时区分"在线但还没收到"和"离线":前者只是刚连上,显示"等待画面"
// 比让人以为板子挂了诚实。
let status = if stats.frames_shown == 0 {
if online {
"已连接,等待画面…".to_string()
} else {
"离线".to_string()
}
} else {
format!("0x{:08X}", sender_id)
};
CameraStats {
online,
status_text: status.into(),
fps: format!("{fps:.1}").into(),
resolution: format!("{}x{}", stats.width, stats.height).into(),
frame_bytes: format!("{:.1} kB", stats.last_frame_len as f32 / 1024.0).into(),
frames_shown: stats.frames_shown.to_string().into(),
frames_dropped: stats.frames_dropped.to_string().into(),
loss_percent: format!("{:.1}%", stats.loss_percent()).into(),
datagrams: stats.datagrams.to_string().into(),
gap_ms: stats
.last_interval
.map(|d| format!("{:.0} ms", d.as_secs_f32() * 1000.0))
.unwrap_or_else(|| "-".to_string())
.into(),
}
}
/// 渲染一边的血量。
///
/// 现在永远走 `available == false` 的分支,因为还没有数据源。装甲板接上后
/// [`ArmorSource::health`] 返回 `Some`,界面自动显示真实数值,这里不用改。
///
/// 没有数据时用占位符而不是 0:0 看起来像真值,现场看到会以为对方已经没血了。
fn render_health(team: u8, armor: &dyn ArmorSource) -> TeamHealth {
let name = match team {
0 => "队伍一",
_ => "队伍二",
};
match armor.health(team) {
Some(hps) => TeamHealth {
team_name: format!("{name} · 装甲板").into(),
available: true,
placeholder: String::new().into(),
hps: std::rc::Rc::new(slint::VecModel::from(
hps.iter().map(|hp| *hp as i32).collect::<Vec<i32>>(),
))
.into(),
},
None => TeamHealth {
team_name: name.into(),
available: false,
placeholder:
"装甲板未接入:血量接口已预留(protocol::ArmorHealth),当前无数据源".into(),
// 依然给出 4 个格子:界面用 `available` 决定颜色,所以空数组会让占位
// 框整个消失,看起来像布局出错,而不是像"还没有数据"。
hps: std::rc::Rc::new(slint::VecModel::from(vec![0i32; ARMOR_BOARDS])).into(),
},
}
}
+458
View File
@@ -0,0 +1,458 @@
//! UDP 接收、发现、多路帧重组。
//!
//! # 多路
//!
//! 裁判终端同时收两块图传板,队伍终端也会收到两块(只是界面上只显示一块)。
//! 所以重组状态**必须按发送方分开**——把两块板的帧塞进同一个重组器,会让它们
//! 的 `frame_id` 互相顶掉,两路都出不来。
//!
//! # 发现与订阅
//!
//! 两个方向各司其职:
//!
//! - 板子每 2 秒广播一次 [`protocol::MsgType::Hello`],客户端被动收到就知道有谁在线。
//! - 客户端广播 [`protocol::MsgType::Subscribe`],板子收到后开始定向推流。
//!
//! 板子靠**源地址**记住客户端,所以客户端只要周期性续订就能维持推流,不存在
//! 任何超时淘汰的竞态。
//!
//! # 丢帧策略
//!
//! 缺了分片的帧整个丢弃、下一帧顶上,不重传也不尝试补洞。对实时画面这是对的:
//! 等重传补齐时,那一帧早就不新鲜了。这个取舍的代价是丢帧率必须摆在界面上,否则
//! "画面卡"没法区分是链路问题还是传感器问题。
use std::collections::HashMap;
use std::io::ErrorKind;
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4, UdpSocket};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::{Duration, Instant};
use protocol::{Fragment, FrameAssembler, Subscribe, SubscribeRole, DEFAULT_PORT, HEADER_LEN};
/// 接受的最大数据报。高于协议的理论上限(1427),留出余量,这样一个被截断的读只可能
/// 来自网络,而不是来自过小的缓冲。
const RECV_BUFFER: usize = 2048;
/// 重新广播订阅的间隔。
///
/// 比板子判定"客户端掉线"的超时(6 秒)短得多,所以偶发丢一两个包不会中断推流。
const SUBSCRIBE_INTERVAL: Duration = Duration::from_millis(800);
/// 一张帧没收齐就放弃的时限。
///
/// 一帧在 22fps 下约 45ms 到,所以几百毫秒还没齐的永远不会齐了。
const FRAME_TIMEOUT: Duration = Duration::from_millis(500);
/// 一块已知的图传板。
#[derive(Debug, Clone)]
pub struct CameraInfo {
pub id: u32,
pub addr: SocketAddrV4,
pub firmware: u16,
/// 最近一次收到它广播的时刻。
pub last_seen: Instant,
}
impl CameraInfo {
/// 多久没有广播了。
pub fn silence(&self) -> Duration {
self.last_seen.elapsed()
}
/// 板子在线吗?
///
/// 阈值是广播间隔的数倍,所以偶发丢包不会让界面上的在线状态闪��。
pub fn is_online(&self) -> bool {
self.silence() < Duration::from_secs(7)
}
}
/// 一块板的运行时统计。
#[derive(Debug, Clone, Copy, Default)]
pub struct SourceStats {
pub frames_shown: u64,
/// 因分片缺失被放弃的帧。
pub frames_dropped: u64,
pub datagrams: u64,
pub last_frame_len: usize,
pub width: u16,
pub height: u16,
/// 上一帧到这一帧之间的间隔。
pub last_interval: Option<Duration>,
last_shown_at: Option<Instant>,
}
impl SourceStats {
/// 丢弃的帧占已收到帧的比例。
pub fn loss_percent(&self) -> f32 {
let total = self.frames_shown + self.frames_dropped;
if total == 0 {
0.0
} else {
self.frames_dropped as f32 * 100.0 / total as f32
}
}
fn on_frame(&mut self, width: u16, height: u16, len: usize) {
self.last_interval = self.last_shown_at.map(|t| t.elapsed());
self.last_shown_at = Some(Instant::now());
self.frames_shown += 1;
self.width = width;
self.height = height;
self.last_frame_len = len;
}
}
/// 每一路的最新帧,等着被解码。
///
/// 每路只保留最新的一张:在 22fps 下把每一帧都排队意味着内存里压着几十张 JPEG,
/// 然后解码那些几毫秒后就被下一帧顶掉的画面——而这正是这条链路要消除的延迟。
#[derive(Default)]
pub struct LatestFrames {
inner: Mutex<HashMap<u32, protocol::Frame>>,
}
impl LatestFrames {
pub fn put(&self, frame: protocol::Frame) {
self.inner
.lock()
.expect("latest-frames mutex poisoned")
.insert(frame.sender_id, frame);
}
/// 取走某一路的待处理帧。
pub fn take(&self, sender_id: u32) -> Option<protocol::Frame> {
self.inner
.lock()
.expect("latest-frames mutex poisoned")
.remove(&sender_id)
}
/// 列出所有有待处理帧的板子 id。
pub fn pending_senders(&self) -> Vec<u32> {
self.inner
.lock()
.expect("latest-frames mutex poisoned")
.keys()
.copied()
.collect()
}
}
/// 所有板子的共享状态,跨线程只读。
pub struct Discovery {
cameras: Mutex<HashMap<u32, CameraInfo>>,
stats: Mutex<HashMap<u32, SourceStats>>,
}
impl Discovery {
fn new() -> Self {
Self {
cameras: Mutex::new(HashMap::new()),
stats: Mutex::new(HashMap::new()),
}
}
/// 当前已知的板子,按 id 排序,让界面上���列表顺序稳定。
pub fn cameras(&self) -> Vec<CameraInfo> {
let mut list: Vec<CameraInfo> = self
.cameras
.lock()
.expect("cameras mutex poisoned")
.values()
.cloned()
.collect();
list.sort_by_key(|c| c.id);
list
}
/// 在线且刚刚广播过的板子。
pub fn online_cameras(&self) -> Vec<CameraInfo> {
self.cameras().into_iter().filter(CameraInfo::is_online).collect()
}
/// 某一路的统计。
pub fn stats(&self, sender_id: u32) -> SourceStats {
self.stats
.lock()
.expect("stats mutex poisoned")
.get(&sender_id)
.copied()
.unwrap_or_default()
}
fn note_hello(&self, id: u32, addr: SocketAddrV4, firmware: u16) {
let mut cameras = self.cameras.lock().expect("cameras mutex poisoned");
cameras
.entry(id)
.and_modify(|c| {
c.addr = addr;
c.last_seen = Instant::now();
c.firmware = firmware;
})
.or_insert(CameraInfo {
id,
addr,
firmware,
last_seen: Instant::now(),
});
}
fn note_frame(&self, id: u32, width: u16, height: u16, len: usize) {
let mut stats = self.stats.lock().expect("stats mutex poisoned");
stats.entry(id).or_default().on_frame(width, height, len);
}
fn count(&self, id: u32, f: impl FnOnce(&mut SourceStats)) {
let mut stats = self.stats.lock().expect("stats mutex poisoned");
f(stats.entry(id).or_default());
}
/// 超过 `max_idle` 没有广播的板子从列表里移除。
///
/// 界面上不会留下一堆永远离线的板子。
pub fn forget_silent_cameras(&self, max_idle: Duration) {
self.cameras
.lock()
.expect("cameras mutex poisoned")
.retain(|_, c| c.silence() < max_idle);
}
}
/// 客户端身份。
///
/// 常量而非 MAC 派生:队伍客户端和裁判终端各自唯一,进程内也不需要区分自己,
/// 而一个确定的常量让日志更好读。
pub const CLIENT_ID: u32 = 0x0C11_0001;
/// 接收器。
///
/// socket 在这里创建而不是在接收线程里,这样"端口被占用"会立刻报错,而不是被线程
/// 吞掉、表现为一个永远不出画面的空窗口。
pub struct Receiver {
discovery: Arc<Discovery>,
frames: Arc<LatestFrames>,
role: SubscribeRole,
camera: Ipv4Addr,
stop: Arc<AtomicBool>,
}
impl Receiver {
/// 绑定视频端口并准备好发现。
///
/// 绑定固定端口(而非临时端口)是必须的:板子是从订阅的**源端口**学到推流目标的,
/// 临时端口也能工作,但固定端口让三台客户端行为一致,也方便抓包排查。
pub fn start(camera: Ipv4Addr, role: SubscribeRole) -> std::io::Result<Self> {
let socket =
UdpSocket::bind(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, DEFAULT_PORT))?;
socket.set_read_timeout(Some(Duration::from_millis(100)))?;
let broadcast = socket.set_broadcast(true).is_ok();
if !broadcast {
log::warn!("could not enable broadcast on the socket");
}
Ok(Self {
discovery: Arc::new(Discovery::new()),
frames: Arc::new(LatestFrames::default()),
role,
camera,
stop: Arc::new(AtomicBool::new(false)),
})
}
pub fn discovery(&self) -> Arc<Discovery> {
Arc::clone(&self.discovery)
}
pub fn frames(&self) -> Arc<LatestFrames> {
Arc::clone(&self.frames)
}
/// 运行接收循环,直到 [`Receiver::shutdown`]。
///
/// 阻塞,所以放在单独的线程上。
pub fn run(&self, socket: UdpSocket) {
let target = SocketAddrV4::new(self.camera, DEFAULT_PORT);
let subscribe = Subscribe {
role: self.role,
client_id: CLIENT_ID,
};
let subscribe_bytes = subscribe.encode();
// 每个发送方一个重组器:两块板的 frame_id 各自独立,混在一起会互相顶掉。
let mut assemblers: HashMap<u32, FrameAssembler> = HashMap::new();
let mut buf = [0u8; RECV_BUFFER];
let mut last_subscribe = Instant::now() - SUBSCRIBE_INTERVAL;
let mut last_sweep = Instant::now();
log::info!(
"listening on UDP {DEFAULT_PORT}, announcing {:?} to {target}",
self.role
);
while !self.stop.load(Ordering::Relaxed) {
// 周期性续订就是保活:板子在 6 秒收不到就认为我们走了。
if last_subscribe.elapsed() >= SUBSCRIBE_INTERVAL {
if let Err(err) = socket.send_to(&subscribe_bytes, target) {
log::warn!("could not subscribe at {target}: {err}");
}
last_subscribe = Instant::now();
}
match socket.recv_from(&mut buf) {
Ok((n, from)) => {
self.handle_datagram(&buf[..n], from, &mut assemblers);
}
Err(err)
if err.kind() == ErrorKind::WouldBlock
|| err.kind() == ErrorKind::TimedOut =>
{
// 帧之间的常态。读超时正是让退出能及时响应的原因。
}
Err(err) => {
log::warn!("recv failed: {err}");
thread::sleep(Duration::from_millis(100));
}
}
// 放弃等太久的半截帧。否则板子在帧中途死掉时,重组器会一直留着它。
for (id, asm) in assemblers.iter_mut() {
if asm.expire().is_some() {
self.discovery.count(*id, |s| s.frames_dropped += 1);
}
}
if last_sweep.elapsed() >= Duration::from_secs(5) {
self.discovery.forget_silent_cameras(Duration::from_secs(60));
last_sweep = Instant::now();
}
}
log::info!("receive loop stopped");
}
/// 处理一个数据报。
fn handle_datagram(
&self,
bytes: &[u8],
from: std::net::SocketAddr,
assemblers: &mut HashMap<u32, FrameAssembler>,
) {
if bytes.len() < HEADER_LEN {
return;
}
// 板子的在线通告。注意这里不看来源端口:广播来的源端口没有意义,身份由
// 头里的 sender_id 决定。
if let Some(hello) = protocol::Hello::decode(bytes) {
if let SocketAddr::V4(v4) = from {
let is_new = !self.discovery.cameras().iter().any(|c| c.id == hello.sender_id);
self.discovery.note_hello(hello.sender_id, v4, hello.firmware);
if is_new {
log::info!(
"found camera 0x{:08X} at {v4}",
hello.sender_id
);
}
}
return;
}
let Some(frag) = Fragment::decode(bytes) else {
// 既不是 Hello 也不是合法分片:将来的其它消息,或一次损坏。
return;
};
let id = frag.header.sender_id;
match assemblers
.entry(id)
.or_insert_with(|| FrameAssembler::new(FRAME_TIMEOUT))
.push(frag)
{
Ok(frame) => {
self.discovery.count(id, |s| s.datagrams += 1);
self.discovery
.note_frame(id, frame.width, frame.height, frame.data.len());
self.frames.put(frame);
}
Err(protocol::DropReason::Superseded) => {
// 被更新的帧追上了。这不是丢帧:半截的那张只是被超越,在健康链路上
// 这是帧被放弃的**正常**方式。
self.discovery.count(id, |s| s.datagrams += 1);
}
Err(protocol::DropReason::TimedOut) => {
self.discovery.count(id, |s| {
s.datagrams += 1;
s.frames_dropped += 1;
});
}
}
}
pub fn shutdown(&self) {
self.stop.store(true, Ordering::Relaxed);
}
}
/// 解码后的画面。
pub struct DecodedFrame {
pub rgba: Vec<u8>,
pub width: u32,
pub height: u32,
}
/// 解码一张 JPEG。
///
/// 返回 `None` 表示字节不是可解码的图像——协议层挡不住这种:它只检查分片结构,
/// 不检查 JPEG 本身是否有效。
pub fn decode_frame(jpeg: &[u8]) -> Option<DecodedFrame> {
let img = image::load_from_memory_with_format(jpeg, image::ImageFormat::Jpeg).ok()?;
let rgba = img.to_rgba8();
Some(DecodedFrame {
width: rgba.width(),
height: rgba.height(),
rgba: rgba.into_raw(),
})
}
/// 装甲板血量的来源。
///
/// **预留,当前没有实现也没有数据源。** 血量显示为占位符。
///
/// 接口和端口都已就位([`protocol::ARMOR_PORT`]、[`protocol::ArmorHealth`]):装甲板
/// 接上以后往那个端口发消息即可,裁判界面与其它代码都不用改。
pub trait ArmorSource: Send + Sync {
/// 某一边的血量。
///
/// 返回 4 个装甲板的当前血量,按 index 排列。没有数据时返回 `None`,界面据此
/// 显示占位符而不是 0——0 是一个看起来像真值的假值。
fn health(&self, team: u8) -> Option<[u16; 4]>;
}
/// 一边的装甲板数量。
///
/// RoboMaster 规则是每队 4 块。界面按这个数量画格子,所以数据还没接上时也能看到
/// 位置——空着比画 0 个格子更让人以为布局坏了。
pub const ARMOR_BOARDS: usize = 4;
/// 目前唯一的实现:什么都不提供。
///
/// 存在的意义是让界面代码从一开始就对着一个真实接口开发,而不是等装甲板就绪后再改
/// 一次结构。
pub struct NoArmorYet;
impl ArmorSource for NoArmorYet {
fn health(&self, _team: u8) -> Option<[u16; 4]> {
None
}
}
+416
View File
@@ -0,0 +1,416 @@
// Rectangle、VerticalLayout、HorizontalLayout 是语言内置的,无需 import。
/* 一块画面的读数。裁判端有两块,队伍端一块。 */
export struct CameraStats {
// 在线状态
online: bool,
status-text: string,
// 实时测量
fps: string,
resolution: string,
frame-bytes: string,
// 累计
frames-shown: string,
frames-dropped: string,
loss-percent: string,
datagrams: string,
// 帧间隔,用于把"卡"和"帧率低"区分开
gap-ms: string,
}
/* 一边的血量(4 块装甲板)。
*
* `available` 为 false 时界面显示占位符而不是 0:0 是一个看起来像真值的假值,
* 现场看到 0 会以为对方已经没血了。 */
export struct TeamHealth {
team-name: string,
available: bool,
// 占位符文案
placeholder: string,
// 每块装甲板的血量;available 时有效
hps: [int],
}
/* 首次运行时的摄像头选择。
*
* 队伍端需要绑定一块板子(选定后不可更改),裁判端不需要。 */
export struct CameraChoice {
id-text: string,
addr-text: string,
online: bool,
}
// ---------------------------------------------------------------- 队伍端
/*
* 队伍终端:一块画面 + 自己那一边的血量。
*
* 与裁判端是两套独立布局而不是一套加条件显示:两种场景的布局诉求不同(一边要省
* 画面空间给视频,一边要均分),共用一套再用 visible 切换会让每处改动都要考虑两种
* 情况。 */
export component TeamApp inherits Window {
title: "cam-client — 队伍";
preferred-width: 1000px;
preferred-height: 720px;
in property <image> frame;
in property <CameraStats> stats;
in property <TeamHealth> health;
// 选择绑定时显示,其余时候隐藏
in property <[CameraChoice]> choices;
in property <bool> choosing;
// 选中了列表里的第 N 项。
//
// 绑定在这里一次完成:Rust 侧收到回调就写配置文件,所以"选定"当场生效,而不是
// "下次启动才生效"。索引而不是 id,是为了让 Rust 侧回到自己那份列表里取完整
// 信息——.slint 里的模型只是给界面看的。
callback pick(int);
background: #101014;
// 绑定完成前只显示选择界面:没有画面可显示,显示一个空窗口更让人困惑。
if !root.choosing: VerticalLayout {
spacing: 0;
HorizontalLayout {
padding: 0;
spacing: 0;
Rectangle {
background: #0a0a0d;
vertical-stretch: 1;
frame := Image {
source: root.frame;
image-fit: contain;
width: parent.width;
height: parent.height;
horizontal-alignment: center;
vertical-alignment: center;
}
}
}
// 底部一条:左边自己这边的血量,右边这一路的统计。
Rectangle {
background: #1c1c22;
height: 72px;
HorizontalLayout {
padding: 14px;
spacing: 24px;
VerticalLayout {
padding: 0;
spacing: 2px;
horizontal-stretch: 1;
Text {
text: root.health.team-name;
color: #e8e8ec;
font-size: 14px;
font-weight: 600;
}
// 装甲板还没接上,所以是一排占位而不是四个数字。
HorizontalLayout {
padding: 0;
spacing: 6px;
for hp in root.health.hps: Rectangle {
width: 34px;
height: 12px;
border-width: 1px;
border-color: root.health.available ? #4a4a55 : #33333c;
background: root.health.available ? #7ddc7d : #1a1a20;
}
}
}
VerticalLayout {
padding: 0;
spacing: 2px;
preferred-width: 320px;
Text {
text: root.stats.status-text + " " + root.stats.fps + " fps "
+ root.stats.resolution + " " + root.stats.frame-bytes;
color: root.stats.online ? #e8e8ec : #e0a060;
font-size: 13px;
}
Text {
text: "shown " + root.stats.frames-shown
+ " dropped " + root.stats.frames-dropped
+ " (" + root.stats.loss-percent + ")"
+ " gap " + root.stats.gap-ms;
color: root.stats.frames-dropped == "0" ? #8a8a95 : #e0a060;
font-size: 12px;
}
}
}
}
// 血量还没有数据源:把这件事写出来,而不是让一排灰块让人猜。
Rectangle {
background: #14141a;
height: 26px;
Text {
text: root.health.placeholder;
color: #6a6a75;
font-size: 12px;
horizontal-alignment: center;
vertical-alignment: center;
}
}
}
// 选择界面
if root.choosing: VerticalLayout {
padding: 32px;
spacing: 16px;
Text {
text: "选择要观看的摄像头";
color: #e8e8ec;
font-size: 20px;
font-weight: 600;
}
Text {
text: "选定后不能更改。这里的选择会写入配置文件,重启后仍然生效。";
color: #8a8a95;
font-size: 13px;
wrap: word-wrap;
}
Rectangle { height: 8px; background: transparent; }
// TouchArea 而不是给 Rectangle 挂 callback:Rectangle 是基础元素,没有
// 点击回调;TouchArea 才是带交互的元素。
for choice[index] in root.choices: Rectangle {
background: choice.online ? #1c1c22 : #16161b;
border-width: 1px;
border-color: #2a2a33;
height: 56px;
TouchArea {
clicked => {
root.pick(index);
}
}
HorizontalLayout {
padding: 14px;
spacing: 16px;
Text {
text: choice.id-text;
color: #e8e8ec;
font-size: 14px;
font-weight: 600;
vertical-alignment: center;
horizontal-alignment: left;
}
Text {
text: choice.addr-text;
color: #8a8a95;
font-size: 12px;
vertical-alignment: center;
horizontal-alignment: left;
horizontal-stretch: 1;
}
Text {
text: choice.online ? "在线" : "离线";
color: choice.online ? #7ddc7d : #8a8a95;
font-size: 12px;
vertical-alignment: center;
}
}
}
// 一块都没发现时给出可操作的提示,而不是一个空列表。
if root.choices.length == 0: Rectangle {
background: #1a1418;
border-width: 1px;
border-color: #4a2a35;
height: 64px;
Text {
text: "还没有发现任何摄像头。请确认板子已上电、已连上这个网络,\n然后稍候——板子每 2 秒广播一次在线状态。";
color: #e0a060;
font-size: 13px;
wrap: word-wrap;
vertical-alignment: center;
}
}
}
}
// ---------------------------------------------------------------- 裁判端
/* 裁判终端:两块画面左右并排 + 双方血量。
*
* 与队伍端分开:这里的核心诉求是"一眼比出两边的状态",所以两块画面等宽、血量
* 并排对称。 */
export component RefereeApp inherits Window {
title: "cam-client — 裁判系统";
preferred-width: 1400px;
preferred-height: 800px;
in property <image> frame-a;
in property <image> frame-b;
in property <CameraStats> stats-a;
in property <CameraStats> stats-b;
in property <TeamHealth> health-a;
in property <TeamHealth> health-b;
background: #101014;
VerticalLayout {
spacing: 0;
// 两路画面等宽并排,各占一半。
HorizontalLayout {
padding: 0;
spacing: 2px;
Rectangle {
background: #0a0a0d;
vertical-stretch: 1;
horizontal-stretch: 1;
frame-a := Image {
source: root.frame-a;
image-fit: contain;
width: parent.width;
height: parent.height;
horizontal-alignment: center;
vertical-alignment: center;
}
}
Rectangle {
background: #0a0a0d;
vertical-stretch: 1;
horizontal-stretch: 1;
frame-b := Image {
source: root.frame-b;
image-fit: contain;
width: parent.width;
height: parent.height;
horizontal-alignment: center;
vertical-alignment: center;
}
}
}
// 每路画面下方的队伍条:名字、血量、统计。
HorizontalLayout {
padding: 0;
spacing: 2px;
Rectangle {
background: #1c1c22;
height: 78px;
horizontal-stretch: 1;
VerticalLayout {
padding: 10px;
spacing: 4px;
Text {
text: root.health-a.team-name + " " + root.stats-a.status-text
+ " " + root.stats-a.fps + " fps " + root.stats-a.resolution;
color: root.stats-a.online ? #e8e8ec : #e0a060;
font-size: 14px;
font-weight: 600;
}
HorizontalLayout {
padding: 0;
spacing: 6px;
for hp in root.health-a.hps: Rectangle {
width: 40px;
height: 14px;
border-width: 1px;
border-color: root.health-a.available ? #4a4a55 : #33333c;
background: root.health-a.available ? #7ddc7d : #1a1a20;
}
}
Text {
text: "shown " + root.stats-a.frames-shown
+ " dropped " + root.stats-a.frames-dropped
+ " (" + root.stats-a.loss-percent + ")"
+ " gap " + root.stats-a.gap-ms;
color: root.stats-a.frames-dropped == "0" ? #8a8a95 : #e0a060;
font-size: 12px;
}
}
}
Rectangle {
background: #1c1c22;
height: 78px;
horizontal-stretch: 1;
VerticalLayout {
padding: 10px;
spacing: 4px;
Text {
text: root.health-b.team-name + " " + root.stats-b.status-text
+ " " + root.stats-b.fps + " fps " + root.stats-b.resolution;
color: root.stats-b.online ? #e8e8ec : #e0a060;
font-size: 14px;
font-weight: 600;
}
HorizontalLayout {
padding: 0;
spacing: 6px;
for hp in root.health-b.hps: Rectangle {
width: 40px;
height: 14px;
border-width: 1px;
border-color: root.health-b.available ? #4a4a55 : #33333c;
background: root.health-b.available ? #7ddc7d : #1a1a20;
}
}
Text {
text: "shown " + root.stats-b.frames-shown
+ " dropped " + root.stats-b.frames-dropped
+ " (" + root.stats-b.loss-percent + ")"
+ " gap " + root.stats-b.gap-ms;
color: root.stats-b.frames-dropped == "0" ? #8a8a95 : #e0a060;
font-size: 12px;
}
}
}
}
// 装甲板还没接上:说清楚,而不是留一排看不懂的灰块。
Rectangle {
background: #14141a;
height: 26px;
Text {
text: root.health-a.placeholder;
color: #6a6a75;
font-size: 12px;
horizontal-alignment: center;
vertical-alignment: center;
}
}
}
}
+5
View File
@@ -0,0 +1,5 @@
idf_component_register(SRCS "cam_wrapper.c"
INCLUDE_DIRS "include"
# esp_timer for the frame-rate probe; the camera component
# pulls in the LCD_CAM driver and the FreeRTOS task API.
REQUIRES esp32-camera esp_timer)
+236
View File
@@ -0,0 +1,236 @@
/* Implementation of the cam_wrapper shim around esp32-camera. See cam_wrapper.h. */
#include "cam_wrapper.h"
#include <string.h>
#include "esp_log.h"
#include "esp_timer.h"
static void cam_wrapper_measure_fps(cam_wrapper_sensor_t sensor);
static const char *TAG = "cam_wrapper";
static sensor_t *s_sensor;
static char s_sensor_name[16] = "unknown";
static void fill_config(const cam_wrapper_config_t *in, camera_config_t *out)
{
memset(out, 0, sizeof(*out));
out->pin_pwdn = in->pin_pwdn;
out->pin_reset = in->pin_reset;
out->pin_xclk = in->pin_xclk;
out->pin_sccb_sda = in->pin_sccb_sda;
out->pin_sccb_scl = in->pin_sccb_scl;
out->pin_d0 = in->pin_d[0];
out->pin_d1 = in->pin_d[1];
out->pin_d2 = in->pin_d[2];
out->pin_d3 = in->pin_d[3];
out->pin_d4 = in->pin_d[4];
out->pin_d5 = in->pin_d[5];
out->pin_d6 = in->pin_d[6];
out->pin_d7 = in->pin_d[7];
out->pin_vsync = in->pin_vsync;
out->pin_href = in->pin_href;
out->pin_pclk = in->pin_pclk;
out->xclk_freq_hz = in->xclk_freq_hz;
out->ledc_timer = (ledc_timer_t)in->ledc_timer;
out->ledc_channel = (ledc_channel_t)in->ledc_channel;
out->pixel_format = (pixformat_t)in->pixel_format;
out->frame_size = (framesize_t)in->frame_size;
out->jpeg_quality = in->jpeg_quality;
out->fb_count = in->fb_count;
out->fb_location = (camera_fb_location_t)in->fb_location;
out->grab_mode = (camera_grab_mode_t)in->grab_mode;
out->sccb_i2c_port = in->sccb_i2c_port;
}
esp_err_t cam_wrapper_init(const cam_wrapper_config_t *config, cam_wrapper_sensor_t sensor)
{
camera_config_t cfg;
(void)sensor;
if (config == NULL) {
return ESP_ERR_INVALID_ARG;
}
fill_config(config, &cfg);
ESP_LOGI(TAG, "config: pwdn=%d reset=%d xclk=%d sda=%d scl=%d "
"d=[%d %d %d %d %d %d %d %d] vsync=%d href=%d pclk=%d "
"fmt=%d size=%d q=%d fb=%u loc=%d grab=%d i2c=%d vsync_inv=%d",
cfg.pin_pwdn, cfg.pin_reset, cfg.pin_xclk,
cfg.pin_sccb_sda, cfg.pin_sccb_scl,
cfg.pin_d0, cfg.pin_d1, cfg.pin_d2, cfg.pin_d3,
cfg.pin_d4, cfg.pin_d5, cfg.pin_d6, cfg.pin_d7,
cfg.pin_vsync, cfg.pin_href, cfg.pin_pclk,
(int)cfg.pixel_format, (int)cfg.frame_size, cfg.jpeg_quality,
(unsigned)cfg.fb_count, (int)cfg.fb_location, (int)cfg.grab_mode,
cfg.sccb_i2c_port, config->vsync_invert);
/* Must be set before init: cam_init copies it into the driver object, and
* ll_cam_set_pin applies it to the GPIO matrix for the VSYNC input. */
cam_set_vsync_invert(config->vsync_invert != 0);
esp_err_t err = esp_camera_init(&cfg);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_camera_init failed: 0x%x", err);
return err;
}
/* The driver probes the SCCB bus and binds a driver by the sensor's
* PID/ID, so name the sensor from the matched entry of its table rather
* than reporting something the driver does not actually know. */
s_sensor = esp_camera_sensor_get();
if (s_sensor != NULL) {
sensor_id_t id = s_sensor->id;
const camera_sensor_info_t *info = esp_camera_sensor_get_info(&id);
if (info != NULL && info->name != NULL) {
strncpy(s_sensor_name, info->name, sizeof(s_sensor_name) - 1);
s_sensor_name[sizeof(s_sensor_name) - 1] = '\0';
}
ESP_LOGI(TAG, "sensor detected: %s (pid=0x%04x, ver=0x%02x)",
s_sensor_name, id.PID, id.VER);
} else {
ESP_LOGW(TAG, "camera initialized but no sensor handle is available");
}
/* Measure what the sensor actually produces, here, with no WiFi and no HTTP
* in the picture. Everything downstream (socket writes, TCP, the browser) is
* downstream of this number, so when the stream is slow this is what says
* whether the problem is capture or transport. */
cam_wrapper_fb_t probe;
memset(&probe, 0, sizeof(probe));
if (cam_wrapper_fb_get(&probe, sensor) == ESP_OK) {
ESP_LOGI(TAG, "first frame: %ux%u, %u bytes, format %d",
(unsigned)probe.width, (unsigned)probe.height,
(unsigned)probe.len, probe.format);
cam_wrapper_fb_release(&probe);
cam_wrapper_measure_fps(sensor);
} else {
ESP_LOGW(TAG, "camera initialized but returned no frame");
}
return ESP_OK;
}
/* Grabs PROBE_FRAMES frames back to back and logs the achieved rate.
*
* This runs on the capture path with nothing else contending for the sensor, so
* it is the ceiling the stream can ever reach. Comparing it against the rate the
* stream actually achieves localises the bottleneck: equal means the transport is
* keeping up and the sensor is the limit, much lower means something between the
* two is dropping frames. */
#define PROBE_FRAMES 20
static void cam_wrapper_measure_fps(cam_wrapper_sensor_t sensor)
{
uint32_t total = 0;
int64_t start = esp_timer_get_time();
int taken = 0;
for (int i = 0; i < PROBE_FRAMES; i++) {
cam_wrapper_fb_t fb;
memset(&fb, 0, sizeof(fb));
if (cam_wrapper_fb_get(&fb, sensor) != ESP_OK) {
ESP_LOGW(TAG, "fps probe: no frame at index %d", i);
continue;
}
total += (uint32_t)fb.len;
taken++;
cam_wrapper_fb_release(&fb);
}
int64_t elapsed_us = esp_timer_get_time() - start;
if (taken == 0 || elapsed_us <= 0) {
ESP_LOGW(TAG, "fps probe: got no frames");
return;
}
double fps = (double)taken * 1000000.0 / (double)elapsed_us;
ESP_LOGI(TAG, "fps probe: %d frames in %lld ms -> %.1f fps, avg %u bytes "
"(sensor ceiling, no network involved)",
taken, (long long)(elapsed_us / 1000), fps,
(unsigned)(total / (uint32_t)taken));
}
esp_err_t cam_wrapper_fb_get(cam_wrapper_fb_t *out, cam_wrapper_sensor_t sensor)
{
(void)sensor;
if (out == NULL) {
return ESP_ERR_INVALID_ARG;
}
memset(out, 0, sizeof(*out));
camera_fb_t *fb = esp_camera_fb_get();
if (fb == NULL) {
return ESP_FAIL;
}
out->data = fb->buf;
out->len = fb->len;
out->width = fb->width;
out->height = fb->height;
out->format = (int)fb->format;
out->handle = fb;
return ESP_OK;
}
void cam_wrapper_fb_release(cam_wrapper_fb_t *fb)
{
if (fb == NULL || fb->handle == NULL) {
return;
}
esp_camera_fb_return((camera_fb_t *)fb->handle);
fb->data = NULL;
fb->len = 0;
fb->handle = NULL;
}
esp_err_t cam_wrapper_reconfigure(const cam_wrapper_config_t *config)
{
camera_config_t cfg;
if (config == NULL) {
return ESP_ERR_INVALID_ARG;
}
fill_config(config, &cfg);
return esp_camera_reconfigure(&cfg);
}
esp_err_t cam_wrapper_deinit(void)
{
esp_err_t err = esp_camera_deinit();
s_sensor = NULL;
strcpy(s_sensor_name, "unknown");
return err;
}
const char *cam_wrapper_sensor_name(void)
{
return s_sensor_name;
}
@@ -0,0 +1,134 @@
/* Union-free wrapper around the esp32-camera API.
*
* `camera_config_t` in esp_camera.h uses anonymous unions for the deprecated
* SCCB pin names, which bindgen renders as unsafe union fields that are
* awkward to build from Rust. This wrapper takes a plain struct and fills in
* the unions on the C side, so the generated bindings stay trivial to use.
*
* The enum-valued config fields are declared as local enums whose values equal
* the esp32-camera ones (`pixformat_t`, `framesize_t`, `camera_fb_location_t`,
* `camera_grab_mode_t`). That keeps the numbers defined in exactly one place -
* the esp32-camera headers - while still giving Rust named constants to use
* instead of bare magic numbers.
*/
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "esp_camera.h"
#include "esp_err.h"
/* Defined by esp32-camera's cam_hal.c but declared only in its private header,
* which components outside esp32-camera cannot include. Redeclared here so the
* wrapper can set the VSYNC polarity before esp_camera_init() and read back the
* clock the driver used. */
void cam_set_vsync_invert(bool invert);
bool cam_get_vsync_invert(void);
void cam_set_xclk_hz(int hz);
int cam_get_xclk_hz(void);
#ifdef __cplusplus
extern "C" {
#endif
typedef enum {
CAM_WRAPPER_PIXFORMAT_JPEG = PIXFORMAT_JPEG,
CAM_WRAPPER_PIXFORMAT_RGB565 = PIXFORMAT_RGB565,
} cam_wrapper_pixel_format_t;
typedef enum {
CAM_WRAPPER_FRAMESIZE_QQVGA = FRAMESIZE_QQVGA,
CAM_WRAPPER_FRAMESIZE_QVGA = FRAMESIZE_QVGA,
CAM_WRAPPER_FRAMESIZE_CIF = FRAMESIZE_CIF,
CAM_WRAPPER_FRAMESIZE_HVGA = FRAMESIZE_HVGA,
CAM_WRAPPER_FRAMESIZE_VGA = FRAMESIZE_VGA,
CAM_WRAPPER_FRAMESIZE_SVGA = FRAMESIZE_SVGA,
CAM_WRAPPER_FRAMESIZE_XGA = FRAMESIZE_XGA,
CAM_WRAPPER_FRAMESIZE_SXGA = FRAMESIZE_SXGA,
CAM_WRAPPER_FRAMESIZE_UXGA = FRAMESIZE_UXGA,
} cam_wrapper_framesize_t;
typedef enum {
CAM_WRAPPER_FB_IN_PSRAM = CAMERA_FB_IN_PSRAM,
CAM_WRAPPER_FB_IN_DRAM = CAMERA_FB_IN_DRAM,
} cam_wrapper_fb_location_t;
typedef enum {
CAM_WRAPPER_GRAB_WHEN_EMPTY = CAMERA_GRAB_WHEN_EMPTY,
CAM_WRAPPER_GRAB_LATEST = CAMERA_GRAB_LATEST,
} cam_wrapper_grab_mode_t;
/* Pin map of the camera connector. Use -1 for signals that are not wired. */
typedef struct {
int pin_pwdn;
int pin_reset;
int pin_xclk;
int pin_sccb_sda;
int pin_sccb_scl;
int pin_d[8]; /* index 0 -> pin_d0 .. index 7 -> pin_d7 */
int pin_vsync;
int pin_href;
int pin_pclk;
int xclk_freq_hz;
int ledc_timer;
int ledc_channel;
cam_wrapper_pixel_format_t pixel_format;
cam_wrapper_framesize_t frame_size;
int jpeg_quality;
size_t fb_count;
cam_wrapper_fb_location_t fb_location;
cam_wrapper_grab_mode_t grab_mode;
int sccb_i2c_port;
/* VSYNC polarity. 0 = active-high, 1 = active-low (esp32-camera's default,
* correct for most DVP sensors). Getting this wrong does not fail: the
* driver latches frames on the wrong edge and the result is a valid but
* very low frame rate, so it is worth being able to flip it. */
int vsync_invert;
} cam_wrapper_config_t;
/* Sensor hint. esp32-camera probes the sensor over SCCB and binds a driver by
* its PID/ID, so AUTO is normally all that is needed; the others exist only to
* disambiguate when the probe itself is unreliable. */
typedef enum {
CAM_WRAPPER_SENSOR_AUTO = 0,
CAM_WRAPPER_SENSOR_OV2640 = 1,
CAM_WRAPPER_SENSOR_OV3660 = 2,
CAM_WRAPPER_SENSOR_OV5640 = 3,
} cam_wrapper_sensor_t;
/* A single captured frame. `data` points into memory owned by the driver and
* stays valid only until cam_wrapper_fb_release() is called on this struct.
* The struct is caller-owned so it can live on the handler's stack. */
typedef struct {
uint8_t *data;
size_t len;
size_t width;
size_t height;
int format;
void *handle; /* internal driver frame; do not touch */
} cam_wrapper_fb_t;
esp_err_t cam_wrapper_init(const cam_wrapper_config_t *config, cam_wrapper_sensor_t sensor);
/* Acquire one frame into `out`. Returns ESP_OK on success, ESP_FAIL if the
* driver could not hand out a frame (e.g. timeout or buffer starvation). */
esp_err_t cam_wrapper_fb_get(cam_wrapper_fb_t *out, cam_wrapper_sensor_t sensor);
void cam_wrapper_fb_release(cam_wrapper_fb_t *fb);
esp_err_t cam_wrapper_reconfigure(const cam_wrapper_config_t *config);
esp_err_t cam_wrapper_deinit(void);
/* Name of the sensor the driver detected, or "unknown". Never NULL. */
const char *cam_wrapper_sensor_name(void);
#ifdef __cplusplus
}
#endif
+6
View File
@@ -0,0 +1,6 @@
[codespell]
skip = build,*.drawio,*.svg,*.pdf
ignore-words-list = laf,OT,totalY,ORE
write-changes = true
+7
View File
@@ -0,0 +1,7 @@
*.DS_Store
.vscode
**/build
**/sdkconfig
**/sdkconfig.old
**/dependencies.lock
**/managed_components/**
@@ -0,0 +1,8 @@
repos:
- repo: https://github.com/codespell-project/codespell
rev: v2.4.1
hooks:
- id: codespell
args: [--config=.codespellrc]
+110
View File
@@ -0,0 +1,110 @@
# get IDF version for comparison
set(idf_version "${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}")
set(priv_requires "")
# set conversion sources
set(srcs
conversions/yuv.c
conversions/to_jpg.cpp
conversions/to_bmp.c
conversions/jpge.cpp
)
set(priv_include_dirs
conversions/private_include
)
set(include_dirs
driver/include
conversions/include
)
# set driver sources only for supported platforms
if(IDF_TARGET STREQUAL "esp32" OR IDF_TARGET STREQUAL "esp32s2" OR IDF_TARGET STREQUAL "esp32s3")
list(APPEND srcs
driver/esp_camera.c
driver/esp_camera_af.c
driver/cam_hal.c
driver/sensor.c
sensors/ov2640.c
sensors/ov3660.c
sensors/ov5640.c
sensors/ov5640_af.c
sensors/ov7725.c
sensors/ov7670.c
sensors/nt99141.c
sensors/gc0308.c
sensors/gc2145.c
sensors/gc032a.c
sensors/bf3005.c
sensors/bf20a6.c
sensors/sc101iot.c
sensors/sc030iot.c
sensors/sc031gs.c
sensors/mega_ccm.c
sensors/hm1055.c
sensors/hm0360.c
)
list(APPEND priv_include_dirs
driver/private_include
sensors/private_include
target/private_include
)
if(IDF_TARGET STREQUAL "esp32")
list(APPEND srcs
target/xclk.c
target/esp32/ll_cam.c
)
endif()
if(IDF_TARGET STREQUAL "esp32s2")
list(APPEND srcs
target/xclk.c
target/esp32s2/ll_cam.c
)
list(APPEND priv_include_dirs
target/esp32s2/private_include
)
endif()
if(IDF_TARGET STREQUAL "esp32s3")
list(APPEND srcs
target/esp32s3/ll_cam.c
)
endif()
list(APPEND priv_requires freertos nvs_flash esp_mm)
set(min_version_for_esp_timer "4.2")
if (idf_version VERSION_GREATER_EQUAL min_version_for_esp_timer)
list(APPEND priv_requires esp_timer)
endif()
# include the SCCB I2C driver
# this uses either the legacy I2C API or the newer version from IDF v5.4
# as this features a method to obtain the I2C driver from a port number
if (idf_version VERSION_GREATER_EQUAL "5.4" AND NOT CONFIG_SCCB_HARDWARE_I2C_DRIVER_LEGACY)
list(APPEND srcs driver/sccb-ng.c)
else()
list(APPEND srcs driver/sccb.c)
endif()
endif()
set(req driver)
if (idf_version VERSION_GREATER_EQUAL "6.0")
list(APPEND priv_requires esp_driver_gpio esp_driver_spi esp_driver_i2c)
list(APPEND req esp_driver_ledc esp_hal_clock)
endif()
idf_component_register(
SRCS ${srcs}
INCLUDE_DIRS ${include_dirs}
PRIV_INCLUDE_DIRS ${priv_include_dirs}
REQUIRES ${req}
PRIV_REQUIRES ${priv_requires}
)
+306
View File
@@ -0,0 +1,306 @@
menu "Camera configuration"
config OV7670_SUPPORT
bool "Support OV7670 VGA"
default y
help
Enable this option if you want to use the OV7670.
Disable this option to save memory.
config OV7725_SUPPORT
bool "Support OV7725 VGA"
default y
help
Enable this option if you want to use the OV7725.
Disable this option to save memory.
config NT99141_SUPPORT
bool "Support NT99141 HD"
default y
help
Enable this option if you want to use the NT99141.
Disable this option to save memory.
config OV2640_SUPPORT
bool "Support OV2640 2MP"
default y
help
Enable this option if you want to use the OV2640.
Disable this option to save memory.
config OV3660_SUPPORT
bool "Support OV3660 3MP"
default y
help
Enable this option if you want to use the OV3360.
Disable this option to save memory.
config OV5640_SUPPORT
bool "Support OV5640 5MP"
default y
help
Enable this option if you want to use the OV5640.
Disable this option to save memory.
config CAMERA_AF_SUPPORT
bool "Enable autofocus (OV5640)"
depends on OV5640_SUPPORT
default n
help
Enable OV5640 autofocus support via the esp_camera_af API.
If disabled, the AF API will return ESP_ERR_NOT_SUPPORTED.
config CAMERA_AF_DEFAULT_TIMEOUT_MS
int "Autofocus default timeout (ms)"
depends on CAMERA_AF_SUPPORT
range 100 20000
default 2000
help
Default timeout used for autofocus operations (firmware load, trigger/wait).
config GC2145_SUPPORT
bool "Support GC2145 2MP"
default y
help
Enable this option if you want to use the GC2145.
Disable this option to save memory.
config GC032A_SUPPORT
bool "Support GC032A VGA"
default y
help
Enable this option if you want to use the GC032A.
Disable this option to save memory.
config GC0308_SUPPORT
bool "Support GC0308 VGA"
default y
help
Enable this option if you want to use the GC0308.
Disable this option to save memory.
config BF3005_SUPPORT
bool "Support BF3005(BYD3005) VGA"
default y
help
Enable this option if you want to use the BF3005.
Disable this option to save memory.
config BF20A6_SUPPORT
bool "Support BF20A6(BYD20A6) VGA"
default y
help
Enable this option if you want to use the BF20A6.
Disable this option to save memory.
config SC101IOT_SUPPORT
bool "Support SC101IOT HD"
default n
help
Enable this option if you want to use the SC101IOT.
Disable this option to save memory.
choice SC101_REGS_SELECT
prompt "SC101iot default regs"
default SC101IOT_720P_15FPS_ENABLED
depends on SC101IOT_SUPPORT
help
Currently SC010iot has several register sets available.
Select the one that matches your needs.
config SC101IOT_720P_15FPS_ENABLED
bool "xclk20M_720p_15fps"
help
Select this option means that when xclk is 20M, the frame rate is 15fps at 720p resolution.
config SC101IOT_VGA_25FPS_ENABLED
bool "xclk20M_VGA_25fps"
help
Select this option means that when xclk is 20M, the frame rate is 25fps at VGA resolution.
endchoice
config SC030IOT_SUPPORT
bool "Support SC030IOT VGA"
default y
help
Enable this option if you want to use the SC030IOT.
Disable this option to save memory.
config SC031GS_SUPPORT
bool "Support SC031GS VGA"
default n
help
SC031GS is a global shutter CMOS sensor with high frame rate and single-frame HDR.
Enable this option if you want to use the SC031GS.
Disable this option to save memory.
config HM1055_SUPPORT
bool "Support HM1055 VGA"
default y
help
Enable this option if you want to use the HM1055.
Disable this option to save memory.
config HM0360_SUPPORT
bool "Support HM0360 VGA"
default y
help
Enable this option if you want to use the HM0360.
Disable this option to save memory.
config MEGA_CCM_SUPPORT
bool "Support MEGA CCM 5MP"
default y
help
Enable this option if you want to use the MEGA CCM.
Disable this option to save memory.
choice SCCB_HARDWARE_I2C_DRIVER_SELECTION
prompt "I2C driver selection for SCCB"
default SCCB_HARDWARE_I2C_DRIVER_NEW
help
Select the I2C driver to use for SCCB communication.
NOTE: new driver is only supported for ESP-IDF >= 5.4.
config SCCB_HARDWARE_I2C_DRIVER_LEGACY
bool "Legacy I2C driver"
config SCCB_HARDWARE_I2C_DRIVER_NEW
bool "New I2C driver"
endchoice
choice SCCB_HARDWARE_I2C_PORT
bool "I2C peripheral to use for SCCB"
default SCCB_HARDWARE_I2C_PORT1
config SCCB_HARDWARE_I2C_PORT0
bool "I2C0"
config SCCB_HARDWARE_I2C_PORT1
bool "I2C1"
endchoice
config SCCB_CLK_FREQ
int "SCCB clk frequency"
default 100000
range 100000 400000
help
Increasing this value can reduce the initialization time of the sensor.
Please refer to the relevant instructions of the sensor to adjust the value.
choice GC_SENSOR_WINDOW_MODE
bool "GalaxyCore Sensor Window Mode"
depends on (GC2145_SUPPORT || GC032A_SUPPORT || GC0308_SUPPORT)
default GC_SENSOR_SUBSAMPLE_MODE
help
This option determines how to reduce the output size when the resolution you set is less than the maximum resolution.
SUBSAMPLE_MODE has a bigger perspective and WINDOWING_MODE has a higher frame rate.
config GC_SENSOR_WINDOWING_MODE
bool "Windowing Mode"
config GC_SENSOR_SUBSAMPLE_MODE
bool "Subsample Mode"
endchoice
config CAMERA_TASK_STACK_SIZE
int "CAM task stack size"
default 4096
help
Camera task stack size
choice CAMERA_TASK_PINNED_TO_CORE
bool "Camera task pinned to core"
default CAMERA_CORE0
help
Pin the camera handle task to a certain core(0/1). It can also be done automatically choosing NO_AFFINITY.
config CAMERA_CORE0
bool "CORE0"
config CAMERA_CORE1
bool "CORE1"
config CAMERA_NO_AFFINITY
bool "NO_AFFINITY"
endchoice
config CAMERA_DMA_BUFFER_SIZE_MAX
int "DMA buffer size"
range 8192 32768
default 32768
help
Maximum value of DMA buffer
Larger values may fail to allocate due to insufficient contiguous memory blocks, and smaller value may cause DMA interrupt to be too frequent.
config CAMERA_PSRAM_DMA
bool "Enable PSRAM DMA mode by default"
depends on IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3
default n
help
Enable DMA transfers directly from PSRAM on supported targets
(ESP32-S2 and ESP32-S3) by default.
choice CAMERA_JPEG_MODE_FRAME_SIZE_OPTION
prompt "JPEG mode frame size option"
default CAMERA_JPEG_MODE_FRAME_SIZE_AUTO
help
Select whether to use automatic calculation for JPEG mode frame size or specify a custom value.
config CAMERA_JPEG_MODE_FRAME_SIZE_AUTO
bool "Use automatic calculation (width * height / 5)"
help
Use the default calculation for JPEG mode frame size.
Note: In very low resolutions like QQVGA, the default calculation tends to result in insufficient buffer size.
config CAMERA_JPEG_MODE_FRAME_SIZE_CUSTOM
bool "Specify custom frame size"
help
Specify a custom frame size in bytes for JPEG mode.
endchoice
config CAMERA_JPEG_MODE_FRAME_SIZE
int "Custom JPEG mode frame size (bytes)"
default 8192
depends on CAMERA_JPEG_MODE_FRAME_SIZE_CUSTOM
help
This option sets the custom frame size in JPEG mode.
Specify the desired buffer size in bytes.
config CAMERA_CONVERTER_ENABLED
bool "Enable camera RGB/YUV converter"
depends on IDF_TARGET_ESP32S3
default n
help
Enable this option if you want to use RGB565/YUV422/YUV420/YUV411 format conversion.
choice CAMERA_CONV_PROTOCOL
bool "Camera converter protocol"
depends on CAMERA_CONVERTER_ENABLED
default LCD_CAM_CONV_BT601_ENABLED
help
Supports format conversion under both BT601 and BT709 standards.
config LCD_CAM_CONV_BT601_ENABLED
bool "BT601"
config LCD_CAM_CONV_BT709_ENABLED
bool "BT709"
endchoice
config LCD_CAM_CONV_FULL_RANGE_ENABLED
bool "Camera converter full range mode"
depends on CAMERA_CONVERTER_ENABLED
default y
help
Supports format conversion under both full color range mode and limited color range mode.
If full color range mode is selected, the color range of RGB or YUV is 0~255.
If limited color range mode is selected, the color range of RGB is 16~240, and the color range of YUV is Y[16~240], UV[16~235].
Full color range mode has a wider color range, so details in the image show more clearly.
Please confirm the color range mode of the current camera sensor, incorrect color range mode may cause color difference in the final converted image.
Full range mode is used by default. If this option is not selected, the format conversion function will be done using the limited range mode.
config LCD_CAM_ISR_IRAM_SAFE
bool "Execute camera ISR from IRAM"
depends on (IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3)
default n
help
If this option is enabled, camera ISR will execute from IRAM.
endmenu
+202
View File
@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+402
View File
@@ -0,0 +1,402 @@
# ESP32 Camera Driver
[![Build examples](https://github.com/espressif/esp32-camera/actions/workflows/build.yml/badge.svg)](https://github.com/espressif/esp32-camera/actions/workflows/build.yml) [![Component Registry](https://components.espressif.com/components/espressif/esp32-camera/badge.svg)](https://components.espressif.com/components/espressif/esp32-camera)
## General Information
This repository hosts ESP32 series Soc compatible driver for image sensors. Additionally it provides a few tools, which allow converting the captured frame data to the more common BMP and JPEG formats.
### Supported Soc
- ESP32
- ESP32-S2
- ESP32-S3
### Supported Sensor
| model | max resolution | color type | output format | Len Size |
| ------- | -------------- | ---------- | ------------------------------------------------------------ | -------- |
| OV2640 | 1600 x 1200 | color | YUV(422/420)/YCbCr422<br>RGB565/555<br>8-bit compressed data<br>8/10-bit Raw RGB data | 1/4" |
| OV3660 | 2048 x 1536 | color | raw RGB data<br/>RGB565/555/444<br/>CCIR656<br/>YCbCr422<br/>compression | 1/5" |
| OV5640 | 2592 x 1944 | color | RAW RGB<br/>RGB565/555/444<br/>CCIR656<br/>YUV422/420<br/>YCbCr422<br/>compression | 1/4" |
| OV7670 | 640 x 480 | color | Raw Bayer RGB<br/>Processed Bayer RGB<br>YUV/YCbCr422<br>GRB422<br>RGB565/555 | 1/6" |
| OV7725 | 640 x 480 | color | Raw RGB<br/>GRB 422<br/>RGB565/555/444<br/>YCbCr 422 | 1/4" |
| NT99141 | 1280 x 720 | color | YCbCr 422<br/>RGB565/555/444<br/>Raw<br/>CCIR656<br/>JPEG compression | 1/4" |
| GC032A | 640 x 480 | color | YUV/YCbCr422<br/>RAW Bayer<br/>RGB565 | 1/10" |
| GC0308 | 640 x 480 | color | YUV/YCbCr422<br/>RAW Bayer<br/>RGB565<br/>Grayscale | 1/6.5" |
| GC2145 | 1600 x 1200 | color | YUV/YCbCr422<br/>RAW Bayer<br/>RGB565 | 1/5" |
| BF3005 | 640 x 480 | color | YUV/YCbCr422<br/>RAW Bayer<br/>RGB565 | 1/4" |
| BF20A6 | 640 x 480 | color | YUV/YCbCr422<br/>RAW Bayer<br/>Only Y | 1/10" |
| SC101IOT| 1280 x 720 | color | YUV/YCbCr422<br/>Raw RGB | 1/4.2" |
| SC030IOT| 640 x 480 | color | YUV/YCbCr422<br/>RAW Bayer | 1/6.5" |
| SC031GS | 640 x 480 | monochrome | RAW MONO<br/>Grayscale | 1/6" |
| HM0360 | 656 x 496 | monochrome | RAW MONO<br/>Grayscale | 1/6" |
| HM1055 | 1280 x 720 | color | 8/10-bit Raw<br/>YUV/YCbCr422<br/>RGB565/555/444 | 1/6" |
## Important to Remember
- Except when using CIF or lower resolution with JPEG, the driver requires PSRAM to be installed and activated.
- Using YUV or RGB puts a lot of strain on the chip because writing to PSRAM is not particularly fast. The result is that image data might be missing. This is particularly true if WiFi is enabled. If you need RGB data, it is recommended that JPEG is captured and then turned into RGB using `fmt2rgb888` or `fmt2bmp`/`frame2bmp`.
- When 1 frame buffer is used, the driver will wait for the current frame to finish (VSYNC) and start I2S DMA. After the frame is acquired, I2S will be stopped and the frame buffer returned to the application. This approach gives more control over the system, but results in longer time to get the frame.
- When 2 or more frame buffers are used, I2S is running in continuous mode and each frame is pushed to a queue that the application can access. This approach puts more strain on the CPU/Memory, but allows for double the frame rate. Please use only with JPEG.
- The Kconfig option `CONFIG_CAMERA_PSRAM_DMA` enables PSRAM DMA mode on ESP32-S2 and ESP32-S3 devices. This flag defaults to false.
- You can switch PSRAM DMA mode at runtime using `esp_camera_set_psram_mode()`.
## Installation Instructions
### Using with ESP-IDF
- Add a dependency on `espressif/esp32-camera` component:
```bash
idf.py add-dependency "espressif/esp32-camera"
```
(or add it manually in idf_component.yml of your project)
- Enable PSRAM in `menuconfig` (also set Flash and PSRAM frequiencies to 80MHz)
- Include `esp_camera.h` in your code
These instructions also work for PlatformIO, if you are using `framework=espidf`.
### Using with Arduino
#### Arduino IDE
If you are using the arduino-esp32 core in Arduino IDE, no installation is needed! You can use esp32-camera right away.
#### PlatformIO
The easy way -- on the `env` section of `platformio.ini`, add the following:
```ini
[env]
lib_deps =
esp32-camera
```
Now the `esp_camera.h` is available to be included:
```c
#include "esp_camera.h"
```
Enable PSRAM on `menuconfig` or type it directly on `sdkconfig`. Check the [official doc](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/kconfig.html#config-esp32-spiram-support) for more info.
```
CONFIG_ESP32_SPIRAM_SUPPORT=y
```
## Examples
This component comes with a basic example illustrating how to get frames from the camera. You can try out the example using the following command:
```
idf.py create-project-from-example "espressif/esp32-camera:camera_example"
```
This command will download the example into `camera_example` directory. It comes already pre-configured with the correct settings in menuconfig.
### Initialization
```c
#include "esp_camera.h"
//WROVER-KIT PIN Map
#define CAM_PIN_PWDN -1 //power down is not used
#define CAM_PIN_RESET -1 //software reset will be performed
#define CAM_PIN_XCLK 21
#define CAM_PIN_SIOD 26
#define CAM_PIN_SIOC 27
#define CAM_PIN_D7 35
#define CAM_PIN_D6 34
#define CAM_PIN_D5 39
#define CAM_PIN_D4 36
#define CAM_PIN_D3 19
#define CAM_PIN_D2 18
#define CAM_PIN_D1 5
#define CAM_PIN_D0 4
#define CAM_PIN_VSYNC 25
#define CAM_PIN_HREF 23
#define CAM_PIN_PCLK 22
static camera_config_t camera_config = {
.pin_pwdn = CAM_PIN_PWDN,
.pin_reset = CAM_PIN_RESET,
.pin_xclk = CAM_PIN_XCLK,
.pin_sccb_sda = CAM_PIN_SIOD,
.pin_sccb_scl = CAM_PIN_SIOC,
.pin_d7 = CAM_PIN_D7,
.pin_d6 = CAM_PIN_D6,
.pin_d5 = CAM_PIN_D5,
.pin_d4 = CAM_PIN_D4,
.pin_d3 = CAM_PIN_D3,
.pin_d2 = CAM_PIN_D2,
.pin_d1 = CAM_PIN_D1,
.pin_d0 = CAM_PIN_D0,
.pin_vsync = CAM_PIN_VSYNC,
.pin_href = CAM_PIN_HREF,
.pin_pclk = CAM_PIN_PCLK,
.xclk_freq_hz = 20000000,
.ledc_timer = LEDC_TIMER_0,
.ledc_channel = LEDC_CHANNEL_0,
.pixel_format = PIXFORMAT_JPEG,//YUV422,GRAYSCALE,RGB565,JPEG
.frame_size = FRAMESIZE_UXGA,//QQVGA-UXGA, For ESP32, do not use sizes above QVGA when not JPEG. The performance of the ESP32-S series has improved a lot, but JPEG mode always gives better frame rates.
.jpeg_quality = 12, //0-63, for OV series camera sensors, lower number means higher quality
.fb_count = 1, //When jpeg mode is used, if fb_count more than one, the driver will work in continuous mode.
.grab_mode = CAMERA_GRAB_WHEN_EMPTY//CAMERA_GRAB_LATEST. Sets when buffers should be filled
};
esp_err_t camera_init(){
//power up the camera if PWDN pin is defined
if(CAM_PIN_PWDN != -1){
pinMode(CAM_PIN_PWDN, OUTPUT);
digitalWrite(CAM_PIN_PWDN, LOW);
}
//initialize the camera
esp_err_t err = esp_camera_init(&camera_config);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Camera Init Failed");
return err;
}
return ESP_OK;
}
esp_err_t camera_capture(){
//acquire a frame
camera_fb_t * fb = esp_camera_fb_get();
if (!fb) {
ESP_LOGE(TAG, "Camera Capture Failed");
return ESP_FAIL;
}
//replace this with your own function
process_image(fb->width, fb->height, fb->format, fb->buf, fb->len);
//return the frame buffer back to the driver for reuse
esp_camera_fb_return(fb);
return ESP_OK;
}
```
### JPEG HTTP Capture
```c
#include "esp_camera.h"
#include "esp_http_server.h"
#include "esp_timer.h"
typedef struct {
httpd_req_t *req;
size_t len;
} jpg_chunking_t;
static size_t jpg_encode_stream(void * arg, size_t index, const void* data, size_t len){
jpg_chunking_t *j = (jpg_chunking_t *)arg;
if(!index){
j->len = 0;
}
if(httpd_resp_send_chunk(j->req, (const char *)data, len) != ESP_OK){
return 0;
}
j->len += len;
return len;
}
esp_err_t jpg_httpd_handler(httpd_req_t *req){
camera_fb_t * fb = NULL;
esp_err_t res = ESP_OK;
size_t fb_len = 0;
int64_t fr_start = esp_timer_get_time();
fb = esp_camera_fb_get();
if (!fb) {
ESP_LOGE(TAG, "Camera capture failed");
httpd_resp_send_500(req);
return ESP_FAIL;
}
res = httpd_resp_set_type(req, "image/jpeg");
if(res == ESP_OK){
res = httpd_resp_set_hdr(req, "Content-Disposition", "inline; filename=capture.jpg");
}
if(res == ESP_OK){
if(fb->format == PIXFORMAT_JPEG){
fb_len = fb->len;
res = httpd_resp_send(req, (const char *)fb->buf, fb->len);
} else {
jpg_chunking_t jchunk = {req, 0};
res = frame2jpg_cb(fb, 80, jpg_encode_stream, &jchunk)?ESP_OK:ESP_FAIL;
httpd_resp_send_chunk(req, NULL, 0);
fb_len = jchunk.len;
}
}
esp_camera_fb_return(fb);
int64_t fr_end = esp_timer_get_time();
ESP_LOGI(TAG, "JPG: %uKB %ums", (uint32_t)(fb_len/1024), (uint32_t)((fr_end - fr_start)/1000));
return res;
}
```
### JPEG HTTP Stream
```c
#include "esp_camera.h"
#include "esp_http_server.h"
#include "esp_timer.h"
#define PART_BOUNDARY "123456789000000000000987654321"
static const char* _STREAM_CONTENT_TYPE = "multipart/x-mixed-replace;boundary=" PART_BOUNDARY;
static const char* _STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n";
static const char* _STREAM_PART = "Content-Type: image/jpeg\r\nContent-Length: %zu\r\n\r\n";
esp_err_t jpg_stream_httpd_handler(httpd_req_t *req){
camera_fb_t * fb = NULL;
esp_err_t res = ESP_OK;
size_t jpg_buf_len = 0;
uint8_t * jpg_buf = NULL;
char part_buf[64];
static int64_t last_frame = 0;
if(!last_frame) {
last_frame = esp_timer_get_time();
}
res = httpd_resp_set_type(req, _STREAM_CONTENT_TYPE);
if(res != ESP_OK){
return res;
}
while(true){
fb = esp_camera_fb_get();
if (!fb) {
ESP_LOGE(TAG, "Camera capture failed");
res = ESP_FAIL;
break;
}
if(fb->format != PIXFORMAT_JPEG){
bool jpeg_converted = frame2jpg(fb, 80, &jpg_buf, &jpg_buf_len);
if(!jpeg_converted){
ESP_LOGE(TAG, "JPEG compression failed");
esp_camera_fb_return(fb);
res = ESP_FAIL;
break;
}
} else {
jpg_buf_len = fb->len;
jpg_buf = fb->buf;
}
if(res == ESP_OK){
res = httpd_resp_send_chunk(req, _STREAM_BOUNDARY, strlen(_STREAM_BOUNDARY));
}
if(res == ESP_OK){
int hlen = snprintf(part_buf, sizeof(part_buf), _STREAM_PART, jpg_buf_len);
if(hlen < 0 || hlen >= sizeof(part_buf)){
ESP_LOGE(TAG, "Header truncated (%d bytes needed >= %zu buffer)",
hlen, sizeof(part_buf));
res = ESP_FAIL;
} else {
res = httpd_resp_send_chunk(req, part_buf, (size_t)hlen);
}
}
if(res == ESP_OK){
res = httpd_resp_send_chunk(req, (const char *)jpg_buf, jpg_buf_len);
}
if(fb->format != PIXFORMAT_JPEG){
free(jpg_buf);
}
esp_camera_fb_return(fb);
if(res != ESP_OK){
break;
}
int64_t fr_end = esp_timer_get_time();
int64_t frame_time = fr_end - last_frame;
last_frame = fr_end;
frame_time /= 1000;
float fps = frame_time > 0 ? 1000.0f / (float)frame_time : 0.0f;
ESP_LOGI(TAG, "MJPG: %uKB %ums (%.1ffps)",
(uint32_t)(jpg_buf_len/1024),
(uint32_t)frame_time, fps);
}
last_frame = 0;
return res;
}
```
### BMP HTTP Capture
```c
#include "esp_camera.h"
#include "esp_http_server.h"
#include "esp_timer.h"
esp_err_t bmp_httpd_handler(httpd_req_t *req){
camera_fb_t * fb = NULL;
esp_err_t res = ESP_OK;
int64_t fr_start = esp_timer_get_time();
fb = esp_camera_fb_get();
if (!fb) {
ESP_LOGE(TAG, "Camera capture failed");
httpd_resp_send_500(req);
return ESP_FAIL;
}
uint8_t * buf = NULL;
size_t buf_len = 0;
bool converted = frame2bmp(fb, &buf, &buf_len);
esp_camera_fb_return(fb);
if(!converted){
ESP_LOGE(TAG, "BMP conversion failed");
httpd_resp_send_500(req);
return ESP_FAIL;
}
res = httpd_resp_set_type(req, "image/x-windows-bmp")
|| httpd_resp_set_hdr(req, "Content-Disposition", "inline; filename=capture.bmp")
|| httpd_resp_send(req, (const char *)buf, buf_len);
free(buf);
int64_t fr_end = esp_timer_get_time();
ESP_LOGI(TAG, "BMP: %uKB %ums", (uint32_t)(buf_len/1024), (uint32_t)((fr_end - fr_start)/1000));
return res;
}
```
### Autofocus (OV5640)
This component includes an optional autofocus helper for OV5640 modules that have an AF-capable lens.
- Enable it in `menuconfig`: `Component config` → `Camera configuration` → `Enable autofocus (OV5640)`.
- Include the header: `#include "esp_camera_af.h"`.
Basic usage:
```c
#include "esp_camera.h"
#include "esp_camera_af.h"
// After esp_camera_init(...)
sensor_t *s = esp_camera_sensor_get();
esp_camera_af_config_t af_cfg = {
.mode = ESP_CAMERA_AF_MODE_AUTO,
.timeout_ms = 2000,
};
ESP_ERROR_CHECK(esp_camera_af_init(s, &af_cfg));
// Optional: trigger a single AF cycle and wait for completion
ESP_ERROR_CHECK(esp_camera_af_trigger(s));
esp_camera_af_status_t st;
ESP_ERROR_CHECK(esp_camera_af_wait(s, 0, &st));
```
Notes:
- If autofocus is disabled (or the sensor is not OV5640), the AF APIs return `ESP_ERR_NOT_SUPPORTED`.
- OV5640 autofocus relies on loading an internal firmware blob over SCCB during `esp_camera_af_init()`.
@@ -0,0 +1,167 @@
// Copyright 2015-2025 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef _IMG_CONVERTERS_H_
#define _IMG_CONVERTERS_H_
#ifdef __cplusplus
extern "C" {
#endif
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#include "esp_camera.h"
#include "jpeg_decoder.h"
typedef size_t (* jpg_out_cb)(void * arg, size_t index, const void* data, size_t len);
/**
* @brief Convert image buffer to JPEG
*
* @param src Source buffer in RGB565, RGB888, YUYV or GRAYSCALE format
* @param src_len Length in bytes of the source buffer
* @param width Width in pixels of the source image
* @param height Height in pixels of the source image
* @param format Format of the source image
* @param quality JPEG quality of the resulting image
* @param cp Callback to be called to write the bytes of the output JPEG
* @param arg Pointer to be passed to the callback
*
* @return true on success
*/
bool fmt2jpg_cb(uint8_t *src, size_t src_len, uint16_t width, uint16_t height, pixformat_t format, uint8_t quality, jpg_out_cb cb, void * arg);
/**
* @brief Convert camera frame buffer to JPEG
*
* @param fb Source camera frame buffer
* @param quality JPEG quality of the resulting image
* @param cp Callback to be called to write the bytes of the output JPEG
* @param arg Pointer to be passed to the callback
*
* @return true on success
*/
bool frame2jpg_cb(camera_fb_t * fb, uint8_t quality, jpg_out_cb cb, void * arg);
/**
* @brief Convert image buffer to JPEG buffer
*
* @param src Source buffer in RGB565, RGB888, YUYV or GRAYSCALE format
* @param src_len Length in bytes of the source buffer
* @param width Width in pixels of the source image
* @param height Height in pixels of the source image
* @param format Format of the source image
* @param quality JPEG quality of the resulting image
* @param out Pointer to be populated with the address of the resulting buffer.
* You MUST free the pointer once you are done with it.
* @param out_len Pointer to be populated with the length of the output buffer
*
* @return true on success
*/
bool fmt2jpg(uint8_t *src, size_t src_len, uint16_t width, uint16_t height, pixformat_t format, uint8_t quality, uint8_t ** out, size_t * out_len);
/**
* @brief Convert camera frame buffer to JPEG buffer
*
* @param fb Source camera frame buffer
* @param quality JPEG quality of the resulting image
* @param out Pointer to be populated with the address of the resulting buffer
* @param out_len Pointer to be populated with the length of the output buffer
*
* @return true on success
*/
bool frame2jpg(camera_fb_t * fb, uint8_t quality, uint8_t ** out, size_t * out_len);
/**
* @brief Convert image buffer to BMP buffer
*
* @param src Source buffer in JPEG, RGB565, RGB888, YUYV or GRAYSCALE format
* @param src_len Length in bytes of the source buffer
* @param width Width in pixels of the source image
* @param height Height in pixels of the source image
* @param format Format of the source image
* @param out Pointer to be populated with the address of the resulting buffer
* @param out_len Pointer to be populated with the length of the output buffer
*
* @return true on success
*/
bool fmt2bmp(uint8_t *src, size_t src_len, uint16_t width, uint16_t height, pixformat_t format, uint8_t ** out, size_t * out_len);
/**
* @brief Convert camera frame buffer to BMP buffer
*
* @param fb Source camera frame buffer
* @param out Pointer to be populated with the address of the resulting buffer
* @param out_len Pointer to be populated with the length of the output buffer
*
* @return true on success
*/
bool frame2bmp(camera_fb_t * fb, uint8_t ** out, size_t * out_len);
/**
* @brief Convert image buffer to RGB888 buffer (used for face detection)
*
* @param src Source buffer in JPEG, RGB565, RGB888, YUYV or GRAYSCALE format
* @param src_len Length in bytes of the source buffer
* @param format Format of the source image
* @param rgb_buf Pointer to the output buffer (width * height * 3)
*
* @return true on success
*/
bool fmt2rgb888(const uint8_t *src_buf, size_t src_len, pixformat_t format, uint8_t * rgb_buf);
// Macros for backwards compatibility
#define JPG_SCALE_NONE JPEG_IMAGE_SCALE_0
#define JPG_SCALE_2X JPEG_IMAGE_SCALE_1_2
#define JPG_SCALE_4X JPEG_IMAGE_SCALE_1_4
#define JPG_SCALE_8X JPEG_IMAGE_SCALE_1_8
#define JPG_SCALE_MAX JPEG_IMAGE_SCALE_1_8
bool jpg2rgb565(const uint8_t *src, size_t src_len, uint8_t * out, esp_jpeg_image_scale_t scale);
/**
* @brief Chroma subsampling modes for JPEG encoding.
*
* CHROMA_444: full chroma resolution (4:4:4)
* CHROMA_422: horizontal chroma subsampling (4:2:2)
* CHROMA_420: horizontal and vertical subsampling (4:2:0)
*/
typedef enum {
CHROMA_444 = 1,
CHROMA_422 = 2,
CHROMA_420 = 3
} chroma_t;
/**
* @brief Set default chroma subsampling mode for JPEG encoding.
*
* @param chroma Chroma subsampling mode (CHROMA_444, CHROMA_422, CHROMA_420)
*/
void jpgSetChroma(chroma_t chroma);
/**
* @brief Configure RGB565 input byte order for JPEG encoding.
*
* Controls how RGB565 source pixel data is interpreted before JPEG conversion.
* By default, the encoder assumes big-endian byte order (MSB first), which
* matches most ESP32 camera and frame buffer outputs.
*
* @param enable True to use big-endian RGB565 (default), false for little-endian.
*/
void jpgSetRgb565BE(bool enable);
#ifdef __cplusplus
}
#endif
#endif /* _IMG_CONVERTERS_H_ */
@@ -0,0 +1,728 @@
// jpge.cpp - C++ class for JPEG compression.
// Public domain, Rich Geldreich <richgel99@gmail.com>
// v1.01, Dec. 18, 2010 - Initial release
// v1.02, Apr. 6, 2011 - Removed 2x2 ordered dither in H2V1 chroma subsampling method load_block_16_8_8(). (The rounding factor was 2, when it should have been 1. Either way, it wasn't helping.)
// v1.03, Apr. 16, 2011 - Added support for optimized Huffman code tables, optimized dynamic memory allocation down to only 1 alloc.
// Also from Alex Evans: Added RGBA support, linear memory allocator (no longer needed in v1.03).
// v1.04, May. 19, 2012: Forgot to set m_pFile ptr to NULL in cfile_stream::close(). Thanks to Owen Kaluza for reporting this bug.
// Code tweaks to fix VS2008 static code analysis warnings (all looked harmless).
// Code review revealed method load_block_16_8_8() (used for the non-default H2V1 sampling mode to downsample chroma) somehow didn't get the rounding factor fix from v1.02.
#include "jpge.h"
#include <stdint.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <malloc.h>
#include "esp_heap_caps.h"
#define JPGE_MAX(a,b) (((a)>(b))?(a):(b))
#define JPGE_MIN(a,b) (((a)<(b))?(a):(b))
namespace jpge {
static inline void *jpge_malloc(size_t nSize) {
void * b = malloc(nSize);
if(b){
return b;
}
// check if SPIRAM is enabled and allocate on SPIRAM if allocatable
#if ((CONFIG_SPIRAM || CONFIG_SPIRAM_SUPPORT) && (CONFIG_SPIRAM_USE_CAPS_ALLOC || CONFIG_SPIRAM_USE_MALLOC))
return heap_caps_malloc(nSize, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
#else
return NULL;
#endif
}
static inline void jpge_free(void *p) { free(p); }
// Various JPEG enums and tables.
enum { M_SOF0 = 0xC0, M_DHT = 0xC4, M_SOI = 0xD8, M_EOI = 0xD9, M_SOS = 0xDA, M_DQT = 0xDB, M_APP0 = 0xE0 };
enum { DC_LUM_CODES = 12, AC_LUM_CODES = 256, DC_CHROMA_CODES = 12, AC_CHROMA_CODES = 256, MAX_HUFF_SYMBOLS = 257, MAX_HUFF_CODESIZE = 32 };
static const uint8 s_zag[64] = { 0,1,8,16,9,2,3,10,17,24,32,25,18,11,4,5,12,19,26,33,40,48,41,34,27,20,13,6,7,14,21,28,35,42,49,56,57,50,43,36,29,22,15,23,30,37,44,51,58,59,52,45,38,31,39,46,53,60,61,54,47,55,62,63 };
static const int16 s_std_lum_quant[64] = { 16,11,12,14,12,10,16,14,13,14,18,17,16,19,24,40,26,24,22,22,24,49,35,37,29,40,58,51,61,60,57,51,56,55,64,72,92,78,64,68,87,69,55,56,80,109,81,87,95,98,103,104,103,62,77,113,121,112,100,120,92,101,103,99 };
static const int16 s_std_croma_quant[64] = { 17,18,18,24,21,24,47,26,26,47,99,66,56,66,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99 };
static const uint8 s_dc_lum_bits[17] = { 0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0 };
static const uint8 s_dc_lum_val[DC_LUM_CODES] = { 0,1,2,3,4,5,6,7,8,9,10,11 };
static const uint8 s_ac_lum_bits[17] = { 0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d };
static const uint8 s_ac_lum_val[AC_LUM_CODES] = {
0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07,0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08,0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0,
0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16,0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28,0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,
0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89,
0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,
0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,
0xf9,0xfa
};
static const uint8 s_dc_chroma_bits[17] = { 0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0 };
static const uint8 s_dc_chroma_val[DC_CHROMA_CODES] = { 0,1,2,3,4,5,6,7,8,9,10,11 };
static const uint8 s_ac_chroma_bits[17] = { 0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77 };
static const uint8 s_ac_chroma_val[AC_CHROMA_CODES] = {
0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91,0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0,
0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34,0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26,0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,
0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87,
0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3,
0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,
0xf9,0xfa
};
const int YR = 19595, YG = 38470, YB = 7471, CB_R = -11059, CB_G = -21709, CB_B = 32768, CR_R = 32768, CR_G = -27439, CR_B = -5329;
static int32 m_last_quality = 0;
static int32 m_quantization_tables[2][64];
static bool m_huff_initialized = false;
static uint m_huff_codes[4][256];
static uint8 m_huff_code_sizes[4][256];
static uint8 m_huff_bits[4][17];
static uint8 m_huff_val[4][256];
static inline uint8 clamp(int i) {
if (i < 0) {
i = 0;
} else if (i > 255){
i = 255;
}
return static_cast<uint8>(i);
}
static void RGB_to_YCC(uint8* pDst, const uint8 *pSrc, int num_pixels) {
for ( ; num_pixels; pDst += 3, pSrc += 3, num_pixels--) {
const int r = pSrc[0], g = pSrc[1], b = pSrc[2];
pDst[0] = static_cast<uint8>((r * YR + g * YG + b * YB + 32768) >> 16);
pDst[1] = clamp(128 + ((r * CB_R + g * CB_G + b * CB_B + 32768) >> 16));
pDst[2] = clamp(128 + ((r * CR_R + g * CR_G + b * CR_B + 32768) >> 16));
}
}
static void RGB_to_Y(uint8* pDst, const uint8 *pSrc, int num_pixels) {
for ( ; num_pixels; pDst++, pSrc += 3, num_pixels--) {
pDst[0] = static_cast<uint8>((pSrc[0] * YR + pSrc[1] * YG + pSrc[2] * YB + 32768) >> 16);
}
}
static void Y_to_YCC(uint8* pDst, const uint8* pSrc, int num_pixels) {
for( ; num_pixels; pDst += 3, pSrc++, num_pixels--) {
pDst[0] = pSrc[0];
pDst[1] = 128;
pDst[2] = 128;
}
}
// Forward DCT - DCT derived from jfdctint.
enum { CONST_BITS = 13, ROW_BITS = 2 };
#define DCT_DESCALE(x, n) (((x) + (((int32)1) << ((n) - 1))) >> (n))
#define DCT_MUL(var, c) (static_cast<int16>(var) * static_cast<int32>(c))
#define DCT1D(s0, s1, s2, s3, s4, s5, s6, s7) \
int32 t0 = s0 + s7, t7 = s0 - s7, t1 = s1 + s6, t6 = s1 - s6, t2 = s2 + s5, t5 = s2 - s5, t3 = s3 + s4, t4 = s3 - s4; \
int32 t10 = t0 + t3, t13 = t0 - t3, t11 = t1 + t2, t12 = t1 - t2; \
int32 u1 = DCT_MUL(t12 + t13, 4433); \
s2 = u1 + DCT_MUL(t13, 6270); \
s6 = u1 + DCT_MUL(t12, -15137); \
u1 = t4 + t7; \
int32 u2 = t5 + t6, u3 = t4 + t6, u4 = t5 + t7; \
int32 z5 = DCT_MUL(u3 + u4, 9633); \
t4 = DCT_MUL(t4, 2446); t5 = DCT_MUL(t5, 16819); \
t6 = DCT_MUL(t6, 25172); t7 = DCT_MUL(t7, 12299); \
u1 = DCT_MUL(u1, -7373); u2 = DCT_MUL(u2, -20995); \
u3 = DCT_MUL(u3, -16069); u4 = DCT_MUL(u4, -3196); \
u3 += z5; u4 += z5; \
s0 = t10 + t11; s1 = t7 + u1 + u4; s3 = t6 + u2 + u3; s4 = t10 - t11; s5 = t5 + u2 + u4; s7 = t4 + u1 + u3;
static void DCT2D(int32 *p) {
int32 c, *q = p;
for (c = 7; c >= 0; c--, q += 8) {
int32 s0 = q[0], s1 = q[1], s2 = q[2], s3 = q[3], s4 = q[4], s5 = q[5], s6 = q[6], s7 = q[7];
DCT1D(s0, s1, s2, s3, s4, s5, s6, s7);
q[0] = s0 << ROW_BITS; q[1] = DCT_DESCALE(s1, CONST_BITS-ROW_BITS); q[2] = DCT_DESCALE(s2, CONST_BITS-ROW_BITS); q[3] = DCT_DESCALE(s3, CONST_BITS-ROW_BITS);
q[4] = s4 << ROW_BITS; q[5] = DCT_DESCALE(s5, CONST_BITS-ROW_BITS); q[6] = DCT_DESCALE(s6, CONST_BITS-ROW_BITS); q[7] = DCT_DESCALE(s7, CONST_BITS-ROW_BITS);
}
for (q = p, c = 7; c >= 0; c--, q++) {
int32 s0 = q[0*8], s1 = q[1*8], s2 = q[2*8], s3 = q[3*8], s4 = q[4*8], s5 = q[5*8], s6 = q[6*8], s7 = q[7*8];
DCT1D(s0, s1, s2, s3, s4, s5, s6, s7);
q[0*8] = DCT_DESCALE(s0, ROW_BITS+3); q[1*8] = DCT_DESCALE(s1, CONST_BITS+ROW_BITS+3); q[2*8] = DCT_DESCALE(s2, CONST_BITS+ROW_BITS+3); q[3*8] = DCT_DESCALE(s3, CONST_BITS+ROW_BITS+3);
q[4*8] = DCT_DESCALE(s4, ROW_BITS+3); q[5*8] = DCT_DESCALE(s5, CONST_BITS+ROW_BITS+3); q[6*8] = DCT_DESCALE(s6, CONST_BITS+ROW_BITS+3); q[7*8] = DCT_DESCALE(s7, CONST_BITS+ROW_BITS+3);
}
}
// Compute the actual canonical Huffman codes/code sizes given the JPEG huff bits and val arrays.
static void compute_huffman_table(uint *codes, uint8 *code_sizes, uint8 *bits, uint8 *val)
{
int i, l, last_p, si;
static uint8 huff_size[257];
static uint huff_code[257];
uint code;
int p = 0;
for (l = 1; l <= 16; l++) {
for (i = 1; i <= bits[l]; i++) {
huff_size[p++] = (char)l;
}
}
huff_size[p] = 0;
last_p = p; // write sentinel
code = 0; si = huff_size[0]; p = 0;
while (huff_size[p]) {
while (huff_size[p] == si) {
huff_code[p++] = code++;
}
code <<= 1;
si++;
}
memset(codes, 0, sizeof(codes[0])*256);
memset(code_sizes, 0, sizeof(code_sizes[0])*256);
for (p = 0; p < last_p; p++) {
codes[val[p]] = huff_code[p];
code_sizes[val[p]] = huff_size[p];
}
}
void jpeg_encoder::flush_output_buffer()
{
if (m_out_buf_left != JPGE_OUT_BUF_SIZE) {
m_all_stream_writes_succeeded = m_all_stream_writes_succeeded && m_pStream->put_buf(m_out_buf, JPGE_OUT_BUF_SIZE - m_out_buf_left);
}
m_pOut_buf = m_out_buf;
m_out_buf_left = JPGE_OUT_BUF_SIZE;
}
void jpeg_encoder::emit_byte(uint8 i)
{
*m_pOut_buf++ = i;
if (--m_out_buf_left == 0) {
flush_output_buffer();
}
}
void jpeg_encoder::put_bits(uint bits, uint len)
{
uint8 c = 0;
m_bit_buffer |= ((uint32)bits << (24 - (m_bits_in += len)));
while (m_bits_in >= 8) {
c = (uint8)((m_bit_buffer >> 16) & 0xFF);
emit_byte(c);
if (c == 0xFF) {
emit_byte(0);
}
m_bit_buffer <<= 8;
m_bits_in -= 8;
}
}
void jpeg_encoder::emit_word(uint i)
{
emit_byte(uint8(i >> 8)); emit_byte(uint8(i & 0xFF));
}
// JPEG marker generation.
void jpeg_encoder::emit_marker(int marker)
{
emit_byte(uint8(0xFF)); emit_byte(uint8(marker));
}
// Emit JFIF marker
void jpeg_encoder::emit_jfif_app0()
{
emit_marker(M_APP0);
emit_word(2 + 4 + 1 + 2 + 1 + 2 + 2 + 1 + 1);
emit_byte(0x4A); emit_byte(0x46); emit_byte(0x49); emit_byte(0x46); /* Identifier: ASCII "JFIF" */
emit_byte(0);
emit_byte(1); /* Major version */
emit_byte(1); /* Minor version */
emit_byte(0); /* Density unit */
emit_word(1);
emit_word(1);
emit_byte(0); /* No thumbnail image */
emit_byte(0);
}
// Emit quantization tables
void jpeg_encoder::emit_dqt()
{
for (int i = 0; i < ((m_num_components == 3) ? 2 : 1); i++)
{
emit_marker(M_DQT);
emit_word(64 + 1 + 2);
emit_byte(static_cast<uint8>(i));
for (int j = 0; j < 64; j++)
emit_byte(static_cast<uint8>(m_quantization_tables[i][j]));
}
}
// Emit start of frame marker
void jpeg_encoder::emit_sof()
{
emit_marker(M_SOF0); /* baseline */
emit_word(3 * m_num_components + 2 + 5 + 1);
emit_byte(8); /* precision */
emit_word(m_image_y);
emit_word(m_image_x);
emit_byte(m_num_components);
for (int i = 0; i < m_num_components; i++)
{
emit_byte(static_cast<uint8>(i + 1)); /* component ID */
emit_byte((m_comp_h_samp[i] << 4) + m_comp_v_samp[i]); /* h and v sampling */
emit_byte(i > 0); /* quant. table num */
}
}
// Emit Huffman table.
void jpeg_encoder::emit_dht(uint8 *bits, uint8 *val, int index, bool ac_flag)
{
emit_marker(M_DHT);
int length = 0;
for (int i = 1; i <= 16; i++)
length += bits[i];
emit_word(length + 2 + 1 + 16);
emit_byte(static_cast<uint8>(index + (ac_flag << 4)));
for (int i = 1; i <= 16; i++)
emit_byte(bits[i]);
for (int i = 0; i < length; i++)
emit_byte(val[i]);
}
// Emit all Huffman tables.
void jpeg_encoder::emit_dhts()
{
emit_dht(m_huff_bits[0+0], m_huff_val[0+0], 0, false);
emit_dht(m_huff_bits[2+0], m_huff_val[2+0], 0, true);
if (m_num_components == 3) {
emit_dht(m_huff_bits[0+1], m_huff_val[0+1], 1, false);
emit_dht(m_huff_bits[2+1], m_huff_val[2+1], 1, true);
}
}
// emit start of scan
void jpeg_encoder::emit_sos()
{
emit_marker(M_SOS);
emit_word(2 * m_num_components + 2 + 1 + 3);
emit_byte(m_num_components);
for (int i = 0; i < m_num_components; i++)
{
emit_byte(static_cast<uint8>(i + 1));
if (i == 0)
emit_byte((0 << 4) + 0);
else
emit_byte((1 << 4) + 1);
}
emit_byte(0); /* spectral selection */
emit_byte(63);
emit_byte(0);
}
void jpeg_encoder::load_block_8_8_grey(int x)
{
uint8 *pSrc;
sample_array_t *pDst = m_sample_array;
x <<= 3;
for (int i = 0; i < 8; i++, pDst += 8)
{
pSrc = m_mcu_lines[i] + x;
pDst[0] = pSrc[0] - 128; pDst[1] = pSrc[1] - 128; pDst[2] = pSrc[2] - 128; pDst[3] = pSrc[3] - 128;
pDst[4] = pSrc[4] - 128; pDst[5] = pSrc[5] - 128; pDst[6] = pSrc[6] - 128; pDst[7] = pSrc[7] - 128;
}
}
void jpeg_encoder::load_block_8_8(int x, int y, int c)
{
uint8 *pSrc;
sample_array_t *pDst = m_sample_array;
x = (x * (8 * 3)) + c;
y <<= 3;
for (int i = 0; i < 8; i++, pDst += 8)
{
pSrc = m_mcu_lines[y + i] + x;
pDst[0] = pSrc[0 * 3] - 128; pDst[1] = pSrc[1 * 3] - 128; pDst[2] = pSrc[2 * 3] - 128; pDst[3] = pSrc[3 * 3] - 128;
pDst[4] = pSrc[4 * 3] - 128; pDst[5] = pSrc[5 * 3] - 128; pDst[6] = pSrc[6 * 3] - 128; pDst[7] = pSrc[7 * 3] - 128;
}
}
void jpeg_encoder::load_block_16_8(int x, int c)
{
uint8 *pSrc1, *pSrc2;
sample_array_t *pDst = m_sample_array;
x = (x * (16 * 3)) + c;
int a = 0, b = 2;
for (int i = 0; i < 16; i += 2, pDst += 8)
{
pSrc1 = m_mcu_lines[i + 0] + x;
pSrc2 = m_mcu_lines[i + 1] + x;
pDst[0] = ((pSrc1[ 0 * 3] + pSrc1[ 1 * 3] + pSrc2[ 0 * 3] + pSrc2[ 1 * 3] + a) >> 2) - 128; pDst[1] = ((pSrc1[ 2 * 3] + pSrc1[ 3 * 3] + pSrc2[ 2 * 3] + pSrc2[ 3 * 3] + b) >> 2) - 128;
pDst[2] = ((pSrc1[ 4 * 3] + pSrc1[ 5 * 3] + pSrc2[ 4 * 3] + pSrc2[ 5 * 3] + a) >> 2) - 128; pDst[3] = ((pSrc1[ 6 * 3] + pSrc1[ 7 * 3] + pSrc2[ 6 * 3] + pSrc2[ 7 * 3] + b) >> 2) - 128;
pDst[4] = ((pSrc1[ 8 * 3] + pSrc1[ 9 * 3] + pSrc2[ 8 * 3] + pSrc2[ 9 * 3] + a) >> 2) - 128; pDst[5] = ((pSrc1[10 * 3] + pSrc1[11 * 3] + pSrc2[10 * 3] + pSrc2[11 * 3] + b) >> 2) - 128;
pDst[6] = ((pSrc1[12 * 3] + pSrc1[13 * 3] + pSrc2[12 * 3] + pSrc2[13 * 3] + a) >> 2) - 128; pDst[7] = ((pSrc1[14 * 3] + pSrc1[15 * 3] + pSrc2[14 * 3] + pSrc2[15 * 3] + b) >> 2) - 128;
int temp = a; a = b; b = temp;
}
}
void jpeg_encoder::load_block_16_8_8(int x, int c)
{
uint8 *pSrc1;
sample_array_t *pDst = m_sample_array;
x = (x * (16 * 3)) + c;
for (int i = 0; i < 8; i++, pDst += 8)
{
pSrc1 = m_mcu_lines[i + 0] + x;
pDst[0] = ((pSrc1[ 0 * 3] + pSrc1[ 1 * 3]) >> 1) - 128; pDst[1] = ((pSrc1[ 2 * 3] + pSrc1[ 3 * 3]) >> 1) - 128;
pDst[2] = ((pSrc1[ 4 * 3] + pSrc1[ 5 * 3]) >> 1) - 128; pDst[3] = ((pSrc1[ 6 * 3] + pSrc1[ 7 * 3]) >> 1) - 128;
pDst[4] = ((pSrc1[ 8 * 3] + pSrc1[ 9 * 3]) >> 1) - 128; pDst[5] = ((pSrc1[10 * 3] + pSrc1[11 * 3]) >> 1) - 128;
pDst[6] = ((pSrc1[12 * 3] + pSrc1[13 * 3]) >> 1) - 128; pDst[7] = ((pSrc1[14 * 3] + pSrc1[15 * 3]) >> 1) - 128;
}
}
void jpeg_encoder::load_quantized_coefficients(int component_num)
{
int32 *q = m_quantization_tables[component_num > 0];
int16 *pDst = m_coefficient_array;
for (int i = 0; i < 64; i++)
{
sample_array_t j = m_sample_array[s_zag[i]];
if (j < 0)
{
if ((j = -j + (*q >> 1)) < *q)
*pDst++ = 0;
else
*pDst++ = static_cast<int16>(-(j / *q));
}
else
{
if ((j = j + (*q >> 1)) < *q)
*pDst++ = 0;
else
*pDst++ = static_cast<int16>((j / *q));
}
q++;
}
}
void jpeg_encoder::code_coefficients_pass_two(int component_num)
{
int i, j, run_len, nbits, temp1, temp2;
int16 *pSrc = m_coefficient_array;
uint *codes[2];
uint8 *code_sizes[2];
if (component_num == 0)
{
codes[0] = m_huff_codes[0 + 0]; codes[1] = m_huff_codes[2 + 0];
code_sizes[0] = m_huff_code_sizes[0 + 0]; code_sizes[1] = m_huff_code_sizes[2 + 0];
}
else
{
codes[0] = m_huff_codes[0 + 1]; codes[1] = m_huff_codes[2 + 1];
code_sizes[0] = m_huff_code_sizes[0 + 1]; code_sizes[1] = m_huff_code_sizes[2 + 1];
}
temp1 = temp2 = pSrc[0] - m_last_dc_val[component_num];
m_last_dc_val[component_num] = pSrc[0];
if (temp1 < 0)
{
temp1 = -temp1; temp2--;
}
nbits = 0;
while (temp1)
{
nbits++; temp1 >>= 1;
}
put_bits(codes[0][nbits], code_sizes[0][nbits]);
if (nbits) put_bits(temp2 & ((1 << nbits) - 1), nbits);
for (run_len = 0, i = 1; i < 64; i++)
{
if ((temp1 = m_coefficient_array[i]) == 0)
run_len++;
else
{
while (run_len >= 16)
{
put_bits(codes[1][0xF0], code_sizes[1][0xF0]);
run_len -= 16;
}
if ((temp2 = temp1) < 0)
{
temp1 = -temp1;
temp2--;
}
nbits = 1;
while (temp1 >>= 1)
nbits++;
j = (run_len << 4) + nbits;
put_bits(codes[1][j], code_sizes[1][j]);
put_bits(temp2 & ((1 << nbits) - 1), nbits);
run_len = 0;
}
}
if (run_len)
put_bits(codes[1][0], code_sizes[1][0]);
}
void jpeg_encoder::code_block(int component_num)
{
DCT2D(m_sample_array);
load_quantized_coefficients(component_num);
code_coefficients_pass_two(component_num);
}
void jpeg_encoder::process_mcu_row()
{
if (m_num_components == 1)
{
for (int i = 0; i < m_mcus_per_row; i++)
{
load_block_8_8_grey(i); code_block(0);
}
}
else if ((m_comp_h_samp[0] == 1) && (m_comp_v_samp[0] == 1))
{
for (int i = 0; i < m_mcus_per_row; i++)
{
load_block_8_8(i, 0, 0); code_block(0); load_block_8_8(i, 0, 1); code_block(1); load_block_8_8(i, 0, 2); code_block(2);
}
}
else if ((m_comp_h_samp[0] == 2) && (m_comp_v_samp[0] == 1))
{
for (int i = 0; i < m_mcus_per_row; i++)
{
load_block_8_8(i * 2 + 0, 0, 0); code_block(0); load_block_8_8(i * 2 + 1, 0, 0); code_block(0);
load_block_16_8_8(i, 1); code_block(1); load_block_16_8_8(i, 2); code_block(2);
}
}
else if ((m_comp_h_samp[0] == 2) && (m_comp_v_samp[0] == 2))
{
for (int i = 0; i < m_mcus_per_row; i++)
{
load_block_8_8(i * 2 + 0, 0, 0); code_block(0); load_block_8_8(i * 2 + 1, 0, 0); code_block(0);
load_block_8_8(i * 2 + 0, 1, 0); code_block(0); load_block_8_8(i * 2 + 1, 1, 0); code_block(0);
load_block_16_8(i, 1); code_block(1); load_block_16_8(i, 2); code_block(2);
}
}
}
void jpeg_encoder::load_mcu(const void *pSrc)
{
const uint8* Psrc = reinterpret_cast<const uint8*>(pSrc);
uint8* pDst = m_mcu_lines[m_mcu_y_ofs]; // OK to write up to m_image_bpl_xlt bytes to pDst
if (m_num_components == 1) {
if (m_image_bpp == 3)
RGB_to_Y(pDst, Psrc, m_image_x);
else
memcpy(pDst, Psrc, m_image_x);
} else {
if (m_image_bpp == 3)
RGB_to_YCC(pDst, Psrc, m_image_x);
else
Y_to_YCC(pDst, Psrc, m_image_x);
}
// Possibly duplicate pixels at end of scanline if not a multiple of 8 or 16
if (m_num_components == 1)
memset(m_mcu_lines[m_mcu_y_ofs] + m_image_bpl_xlt, pDst[m_image_bpl_xlt - 1], m_image_x_mcu - m_image_x);
else
{
const uint8 y = pDst[m_image_bpl_xlt - 3 + 0], cb = pDst[m_image_bpl_xlt - 3 + 1], cr = pDst[m_image_bpl_xlt - 3 + 2];
uint8 *q = m_mcu_lines[m_mcu_y_ofs] + m_image_bpl_xlt;
for (int i = m_image_x; i < m_image_x_mcu; i++)
{
*q++ = y; *q++ = cb; *q++ = cr;
}
}
if (++m_mcu_y_ofs == m_mcu_y)
{
process_mcu_row();
m_mcu_y_ofs = 0;
}
}
// Quantization table generation.
void jpeg_encoder::compute_quant_table(int32 *pDst, const int16 *pSrc)
{
int32 q;
if (m_params.m_quality < 50)
q = 5000 / m_params.m_quality;
else
q = 200 - m_params.m_quality * 2;
for (int i = 0; i < 64; i++)
{
int32 j = *pSrc++; j = (j * q + 50L) / 100L;
*pDst++ = JPGE_MIN(JPGE_MAX(j, 1), 255);
}
}
// Higher-level methods.
bool jpeg_encoder::jpg_open(int p_x_res, int p_y_res, int src_channels)
{
m_num_components = 3;
switch (m_params.m_subsampling)
{
case Y_ONLY:
{
m_num_components = 1;
m_comp_h_samp[0] = 1; m_comp_v_samp[0] = 1;
m_mcu_x = 8; m_mcu_y = 8;
break;
}
case H1V1:
{
m_comp_h_samp[0] = 1; m_comp_v_samp[0] = 1;
m_comp_h_samp[1] = 1; m_comp_v_samp[1] = 1;
m_comp_h_samp[2] = 1; m_comp_v_samp[2] = 1;
m_mcu_x = 8; m_mcu_y = 8;
break;
}
case H2V1:
{
m_comp_h_samp[0] = 2; m_comp_v_samp[0] = 1;
m_comp_h_samp[1] = 1; m_comp_v_samp[1] = 1;
m_comp_h_samp[2] = 1; m_comp_v_samp[2] = 1;
m_mcu_x = 16; m_mcu_y = 8;
break;
}
case H2V2:
{
m_comp_h_samp[0] = 2; m_comp_v_samp[0] = 2;
m_comp_h_samp[1] = 1; m_comp_v_samp[1] = 1;
m_comp_h_samp[2] = 1; m_comp_v_samp[2] = 1;
m_mcu_x = 16; m_mcu_y = 16;
}
}
m_image_x = p_x_res; m_image_y = p_y_res;
m_image_bpp = src_channels;
m_image_bpl = m_image_x * src_channels;
m_image_x_mcu = (m_image_x + m_mcu_x - 1) & (~(m_mcu_x - 1));
m_image_y_mcu = (m_image_y + m_mcu_y - 1) & (~(m_mcu_y - 1));
m_image_bpl_xlt = m_image_x * m_num_components;
m_image_bpl_mcu = m_image_x_mcu * m_num_components;
m_mcus_per_row = m_image_x_mcu / m_mcu_x;
if ((m_mcu_lines[0] = static_cast<uint8*>(jpge_malloc(m_image_bpl_mcu * m_mcu_y))) == NULL) {
return false;
}
for (int i = 1; i < m_mcu_y; i++)
m_mcu_lines[i] = m_mcu_lines[i-1] + m_image_bpl_mcu;
if(m_last_quality != m_params.m_quality){
m_last_quality = m_params.m_quality;
compute_quant_table(m_quantization_tables[0], s_std_lum_quant);
compute_quant_table(m_quantization_tables[1], s_std_croma_quant);
}
if(!m_huff_initialized){
m_huff_initialized = true;
memcpy(m_huff_bits[0+0], s_dc_lum_bits, 17); memcpy(m_huff_val[0+0], s_dc_lum_val, DC_LUM_CODES);
memcpy(m_huff_bits[2+0], s_ac_lum_bits, 17); memcpy(m_huff_val[2+0], s_ac_lum_val, AC_LUM_CODES);
memcpy(m_huff_bits[0+1], s_dc_chroma_bits, 17); memcpy(m_huff_val[0+1], s_dc_chroma_val, DC_CHROMA_CODES);
memcpy(m_huff_bits[2+1], s_ac_chroma_bits, 17); memcpy(m_huff_val[2+1], s_ac_chroma_val, AC_CHROMA_CODES);
compute_huffman_table(&m_huff_codes[0+0][0], &m_huff_code_sizes[0+0][0], m_huff_bits[0+0], m_huff_val[0+0]);
compute_huffman_table(&m_huff_codes[2+0][0], &m_huff_code_sizes[2+0][0], m_huff_bits[2+0], m_huff_val[2+0]);
compute_huffman_table(&m_huff_codes[0+1][0], &m_huff_code_sizes[0+1][0], m_huff_bits[0+1], m_huff_val[0+1]);
compute_huffman_table(&m_huff_codes[2+1][0], &m_huff_code_sizes[2+1][0], m_huff_bits[2+1], m_huff_val[2+1]);
}
m_out_buf_left = JPGE_OUT_BUF_SIZE;
m_pOut_buf = m_out_buf;
m_bit_buffer = 0;
m_bits_in = 0;
m_mcu_y_ofs = 0;
m_pass_num = 2;
memset(m_last_dc_val, 0, 3 * sizeof(m_last_dc_val[0]));
// Emit all markers at beginning of image file.
emit_marker(M_SOI);
emit_jfif_app0();
emit_dqt();
emit_sof();
emit_dhts();
emit_sos();
return m_all_stream_writes_succeeded;
}
bool jpeg_encoder::process_end_of_image()
{
if (m_mcu_y_ofs) {
if (m_mcu_y_ofs < 16) { // check here just to shut up static analysis
for (int i = m_mcu_y_ofs; i < m_mcu_y; i++) {
memcpy(m_mcu_lines[i], m_mcu_lines[m_mcu_y_ofs - 1], m_image_bpl_mcu);
}
}
process_mcu_row();
}
put_bits(0x7F, 7);
emit_marker(M_EOI);
flush_output_buffer();
m_all_stream_writes_succeeded = m_all_stream_writes_succeeded && m_pStream->put_buf(NULL, 0);
m_pass_num++; // purposely bump up m_pass_num, for debugging
return true;
}
void jpeg_encoder::clear()
{
m_mcu_lines[0] = NULL;
m_pass_num = 0;
m_all_stream_writes_succeeded = true;
}
jpeg_encoder::jpeg_encoder()
{
clear();
}
jpeg_encoder::~jpeg_encoder()
{
deinit();
}
bool jpeg_encoder::init(output_stream *pStream, int width, int height, int src_channels, const params &comp_params)
{
deinit();
if (((!pStream) || (width < 1) || (height < 1)) || ((src_channels != 1) && (src_channels != 3) && (src_channels != 4)) || (!comp_params.check())) return false;
m_pStream = pStream;
m_params = comp_params;
return jpg_open(width, height, src_channels);
}
void jpeg_encoder::deinit()
{
jpge_free(m_mcu_lines[0]);
clear();
}
bool jpeg_encoder::process_scanline(const void* pScanline)
{
if ((m_pass_num < 1) || (m_pass_num > 2)) {
return false;
}
if (m_all_stream_writes_succeeded) {
if (!pScanline) {
if (!process_end_of_image()) {
return false;
}
} else {
load_mcu(pScanline);
}
}
return m_all_stream_writes_succeeded;
}
} // namespace jpge
@@ -0,0 +1,142 @@
// jpge.h - C++ class for JPEG compression.
// Public domain, Rich Geldreich <richgel99@gmail.com>
// Alex Evans: Added RGBA support, linear memory allocator.
#ifndef JPEG_ENCODER_H
#define JPEG_ENCODER_H
namespace jpge
{
typedef unsigned char uint8;
typedef signed short int16;
typedef signed int int32;
typedef unsigned short uint16;
typedef unsigned int uint32;
typedef unsigned int uint;
// JPEG chroma subsampling factors. Y_ONLY (grayscale images) and H2V2 (color images) are the most common.
enum subsampling_t { Y_ONLY = 0, H1V1 = 1, H2V1 = 2, H2V2 = 3 };
// JPEG compression parameters structure.
struct params {
inline params() : m_quality(85), m_subsampling(H2V2) { }
inline bool check() const {
if ((m_quality < 1) || (m_quality > 100)) {
return false;
}
if ((uint)m_subsampling > (uint)H2V2) {
return false;
}
return true;
}
// Quality: 1-100, higher is better. Typical values are around 50-95.
int m_quality;
// m_subsampling:
// 0 = Y (grayscale) only
// 1 = H1V1 subsampling (YCbCr 1x1x1, 3 blocks per MCU)
// 2 = H2V1 subsampling (YCbCr 2x1x1, 4 blocks per MCU)
// 3 = H2V2 subsampling (YCbCr 4x1x1, 6 blocks per MCU-- very common)
subsampling_t m_subsampling;
};
// Output stream abstract class - used by the jpeg_encoder class to write to the output stream.
// put_buf() is generally called with len==JPGE_OUT_BUF_SIZE bytes, but for headers it'll be called with smaller amounts.
class output_stream {
public:
virtual ~output_stream() { };
virtual bool put_buf(const void* Pbuf, int len) = 0;
virtual uint get_size() const = 0;
};
// Lower level jpeg_encoder class - useful if more control is needed than the above helper functions.
class jpeg_encoder {
public:
jpeg_encoder();
~jpeg_encoder();
// Initializes the compressor.
// pStream: The stream object to use for writing compressed data.
// params - Compression parameters structure, defined above.
// width, height - Image dimensions.
// channels - May be 1, or 3. 1 indicates grayscale, 3 indicates RGB source data.
// Returns false on out of memory or if a stream write fails.
bool init(output_stream *pStream, int width, int height, int src_channels, const params &comp_params = params());
// Call this method with each source scanline.
// width * src_channels bytes per scanline is expected (RGB or Y format).
// You must call with NULL after all scanlines are processed to finish compression.
// Returns false on out of memory or if a stream write fails.
bool process_scanline(const void* pScanline);
// Deinitializes the compressor, freeing any allocated memory. May be called at any time.
void deinit();
private:
jpeg_encoder(const jpeg_encoder &);
jpeg_encoder &operator =(const jpeg_encoder &);
typedef int32 sample_array_t;
enum { JPGE_OUT_BUF_SIZE = 512 };
output_stream *m_pStream;
params m_params;
uint8 m_num_components;
uint8 m_comp_h_samp[3], m_comp_v_samp[3];
int m_image_x, m_image_y, m_image_bpp, m_image_bpl;
int m_image_x_mcu, m_image_y_mcu;
int m_image_bpl_xlt, m_image_bpl_mcu;
int m_mcus_per_row;
int m_mcu_x, m_mcu_y;
uint8 *m_mcu_lines[16];
uint8 m_mcu_y_ofs;
sample_array_t m_sample_array[64];
int16 m_coefficient_array[64];
int m_last_dc_val[3];
uint8 m_out_buf[JPGE_OUT_BUF_SIZE];
uint8 *m_pOut_buf;
uint m_out_buf_left;
uint32 m_bit_buffer;
uint m_bits_in;
uint8 m_pass_num;
bool m_all_stream_writes_succeeded;
bool jpg_open(int p_x_res, int p_y_res, int src_channels);
void flush_output_buffer();
void put_bits(uint bits, uint len);
void emit_byte(uint8 i);
void emit_word(uint i);
void emit_marker(int marker);
void emit_jfif_app0();
void emit_dqt();
void emit_sof();
void emit_dht(uint8 *bits, uint8 *val, int index, bool ac_flag);
void emit_dhts();
void emit_sos();
void compute_quant_table(int32 *dst, const int16 *src);
void load_quantized_coefficients(int component_num);
void load_block_8_8_grey(int x);
void load_block_8_8(int x, int y, int c);
void load_block_16_8(int x, int c);
void load_block_16_8_8(int x, int c);
void code_coefficients_pass_two(int component_num);
void code_block(int component_num);
void process_mcu_row();
bool process_end_of_image();
void load_mcu(const void* src);
void clear();
void init();
};
} // namespace jpge
#endif // JPEG_ENCODER
@@ -0,0 +1,29 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef _CONVERSIONS_YUV_H_
#define _CONVERSIONS_YUV_H_
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
void yuv2rgb(uint8_t y, uint8_t u, uint8_t v, uint8_t *r, uint8_t *g, uint8_t *b);
#ifdef __cplusplus
}
#endif
#endif /* _CONVERSIONS_YUV_H_ */
@@ -0,0 +1,329 @@
// Copyright 2015-2025 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stddef.h>
#include <string.h>
#include <stdlib.h>
#include "img_converters.h"
#include "soc/efuse_reg.h"
#include "esp_heap_caps.h"
#include "yuv.h"
#include "sdkconfig.h"
#include "jpeg_decoder.h"
#include "esp_system.h"
#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG)
#include "esp32-hal-log.h"
#define TAG ""
#else
#include "esp_log.h"
static const char* TAG = "to_bmp";
#endif
static const int BMP_HEADER_LEN = 54;
static uint8_t work[3100]; // 3.1kB for JPEG decoder, static for legacy reasons
typedef struct {
uint32_t filesize;
uint32_t reserved;
uint32_t fileoffset_to_pixelarray;
uint32_t dibheadersize;
int32_t width;
int32_t height;
uint16_t planes;
uint16_t bitsperpixel;
uint32_t compression;
uint32_t imagesize;
uint32_t ypixelpermeter;
uint32_t xpixelpermeter;
uint32_t numcolorspallette;
uint32_t mostimpcolor;
} bmp_header_t;
static void *_malloc(size_t size)
{
// check if SPIRAM is enabled and allocate on SPIRAM if allocatable
#if ((CONFIG_SPIRAM || CONFIG_SPIRAM_SUPPORT) && (CONFIG_SPIRAM_USE_CAPS_ALLOC || CONFIG_SPIRAM_USE_MALLOC))
return heap_caps_malloc(size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
#endif
// try allocating in internal memory
return malloc(size);
}
static bool jpg2rgb888(const uint8_t *src, size_t src_len, uint8_t * out, esp_jpeg_image_scale_t scale)
{
esp_jpeg_image_cfg_t jpeg_cfg = {
.indata = (uint8_t *)src,
.indata_size = src_len,
.outbuf = out,
.outbuf_size = UINT32_MAX, // @todo: this is very bold assumption, keeping this like this for now, not to break existing code
.out_format = JPEG_IMAGE_FORMAT_RGB888,
.out_scale = scale,
.flags.swap_color_bytes = 0,
.advanced.working_buffer = work,
.advanced.working_buffer_size = sizeof(work),
};
esp_jpeg_image_output_t output_img = {};
if(esp_jpeg_decode(&jpeg_cfg, &output_img) != ESP_OK){
return false;
}
return true;
}
bool jpg2rgb565(const uint8_t *src, size_t src_len, uint8_t * out, esp_jpeg_image_scale_t scale)
{
esp_jpeg_image_cfg_t jpeg_cfg = {
.indata = (uint8_t *)src,
.indata_size = src_len,
.outbuf = out,
.outbuf_size = UINT32_MAX, // @todo: this is very bold assumption, keeping this like this for now, not to break existing code
.out_format = JPEG_IMAGE_FORMAT_RGB565,
.out_scale = scale,
.flags.swap_color_bytes = 0,
.advanced.working_buffer = work,
.advanced.working_buffer_size = sizeof(work),
};
esp_jpeg_image_output_t output_img = {};
if(esp_jpeg_decode(&jpeg_cfg, &output_img) != ESP_OK){
return false;
}
return true;
}
bool jpg2bmp(const uint8_t *src, size_t src_len, uint8_t ** out, size_t * out_len)
{
esp_jpeg_image_cfg_t jpeg_cfg = {
.indata = (uint8_t *)src,
.indata_size = src_len,
.out_format = JPEG_IMAGE_FORMAT_RGB888,
.out_scale = JPEG_IMAGE_SCALE_0,
.flags.swap_color_bytes = 0,
.advanced.working_buffer = work,
.advanced.working_buffer_size = sizeof(work),
};
bool ret = false;
uint8_t *output = NULL;
esp_jpeg_image_output_t output_img = {};
if (esp_jpeg_get_image_info(&jpeg_cfg, &output_img) != ESP_OK) {
ESP_LOGE(TAG, "Failed to get image info");
goto fail;
}
// @todo here we allocate memory and we assume that the user will free it
// this is not the best way to do it, but we need to keep the API
// compatible with the previous version
const size_t output_size = output_img.output_len + BMP_HEADER_LEN;
output = _malloc(output_size);
if (!output) {
ESP_LOGE(TAG, "Failed to allocate output buffer");
goto fail;
}
// Start writing decoded data after the BMP header
jpeg_cfg.outbuf = output + BMP_HEADER_LEN;
jpeg_cfg.outbuf_size = output_img.output_len;
if(esp_jpeg_decode(&jpeg_cfg, &output_img) != ESP_OK){
ESP_LOGE(TAG, "JPEG decode failed");
goto fail;
}
output[0] = 'B';
output[1] = 'M';
bmp_header_t * bitmap = (bmp_header_t*)&output[2];
bitmap->reserved = 0;
bitmap->filesize = output_size;
bitmap->fileoffset_to_pixelarray = BMP_HEADER_LEN;
bitmap->dibheadersize = 40;
bitmap->width = output_img.width;
bitmap->height = -output_img.height; //set negative for top to bottom
bitmap->planes = 1;
bitmap->bitsperpixel = 24;
bitmap->compression = 0;
bitmap->imagesize = output_img.output_len;
bitmap->ypixelpermeter = 0x0B13 ; //2835 , 72 DPI
bitmap->xpixelpermeter = 0x0B13 ; //2835 , 72 DPI
bitmap->numcolorspallette = 0;
bitmap->mostimpcolor = 0;
*out = output;
*out_len = output_size;
ret = true;
fail:
if (!ret && output) {
free(output);
}
return ret;
}
bool fmt2rgb888(const uint8_t *src_buf, size_t src_len, pixformat_t format, uint8_t * rgb_buf)
{
int pix_count = 0;
if(format == PIXFORMAT_JPEG) {
return jpg2rgb888(src_buf, src_len, rgb_buf, JPEG_IMAGE_SCALE_0);
} else if(format == PIXFORMAT_RGB888) {
memcpy(rgb_buf, src_buf, src_len);
} else if(format == PIXFORMAT_RGB565) {
int i;
uint8_t hb, lb;
pix_count = src_len / 2;
for(i=0; i<pix_count; i++) {
hb = *src_buf++;
lb = *src_buf++;
*rgb_buf++ = (lb & 0x1F) << 3;
*rgb_buf++ = (hb & 0x07) << 5 | (lb & 0xE0) >> 3;
*rgb_buf++ = hb & 0xF8;
}
} else if(format == PIXFORMAT_GRAYSCALE) {
int i;
uint8_t b;
pix_count = src_len;
for(i=0; i<pix_count; i++) {
b = *src_buf++;
*rgb_buf++ = b;
*rgb_buf++ = b;
*rgb_buf++ = b;
}
} else if(format == PIXFORMAT_YUV422) {
pix_count = src_len / 2;
int i, maxi = pix_count / 2;
uint8_t y0, y1, u, v;
uint8_t r, g, b;
for(i=0; i<maxi; i++) {
y0 = *src_buf++;
u = *src_buf++;
y1 = *src_buf++;
v = *src_buf++;
yuv2rgb(y0, u, v, &r, &g, &b);
*rgb_buf++ = b;
*rgb_buf++ = g;
*rgb_buf++ = r;
yuv2rgb(y1, u, v, &r, &g, &b);
*rgb_buf++ = b;
*rgb_buf++ = g;
*rgb_buf++ = r;
}
}
return true;
}
bool fmt2bmp(uint8_t *src, size_t src_len, uint16_t width, uint16_t height, pixformat_t format, uint8_t ** out, size_t * out_len)
{
if(format == PIXFORMAT_JPEG) {
return jpg2bmp(src, src_len, out, out_len);
}
*out = NULL;
*out_len = 0;
int pix_count = width*height;
// With BMP, 8-bit greyscale requires a palette.
// For a 640x480 image though, that's a savings
// over going RGB-24.
int bpp = (format == PIXFORMAT_GRAYSCALE) ? 1 : 3;
int palette_size = (format == PIXFORMAT_GRAYSCALE) ? 4 * 256 : 0;
size_t out_size = (pix_count * bpp) + BMP_HEADER_LEN + palette_size;
uint8_t * out_buf = (uint8_t *)_malloc(out_size);
if(!out_buf) {
ESP_LOGE(TAG, "_malloc failed! %u", out_size);
return false;
}
out_buf[0] = 'B';
out_buf[1] = 'M';
bmp_header_t * bitmap = (bmp_header_t*)&out_buf[2];
bitmap->reserved = 0;
bitmap->filesize = out_size;
bitmap->fileoffset_to_pixelarray = BMP_HEADER_LEN + palette_size;
bitmap->dibheadersize = 40;
bitmap->width = width;
bitmap->height = -height;//set negative for top to bottom
bitmap->planes = 1;
bitmap->bitsperpixel = bpp * 8;
bitmap->compression = 0;
bitmap->imagesize = pix_count * bpp;
bitmap->ypixelpermeter = 0x0B13 ; //2835 , 72 DPI
bitmap->xpixelpermeter = 0x0B13 ; //2835 , 72 DPI
bitmap->numcolorspallette = 0;
bitmap->mostimpcolor = 0;
uint8_t * palette_buf = out_buf + BMP_HEADER_LEN;
uint8_t * pix_buf = palette_buf + palette_size;
uint8_t * src_buf = src;
if (palette_size > 0) {
// Grayscale palette
for (int i = 0; i < 256; ++i) {
for (int j = 0; j < 3; ++j) {
*palette_buf = i;
palette_buf++;
}
// Reserved / alpha channel.
*palette_buf = 0;
palette_buf++;
}
}
//convert data to RGB888
if(format == PIXFORMAT_RGB888) {
memcpy(pix_buf, src_buf, pix_count*3);
} else if(format == PIXFORMAT_RGB565) {
int i;
uint8_t hb, lb;
for(i=0; i<pix_count; i++) {
hb = *src_buf++;
lb = *src_buf++;
*pix_buf++ = (lb & 0x1F) << 3;
*pix_buf++ = (hb & 0x07) << 5 | (lb & 0xE0) >> 3;
*pix_buf++ = hb & 0xF8;
}
} else if(format == PIXFORMAT_GRAYSCALE) {
memcpy(pix_buf, src_buf, pix_count);
} else if(format == PIXFORMAT_YUV422) {
int i, maxi = pix_count / 2;
uint8_t y0, y1, u, v;
uint8_t r, g, b;
for(i=0; i<maxi; i++) {
y0 = *src_buf++;
u = *src_buf++;
y1 = *src_buf++;
v = *src_buf++;
yuv2rgb(y0, u, v, &r, &g, &b);
*pix_buf++ = b;
*pix_buf++ = g;
*pix_buf++ = r;
yuv2rgb(y1, u, v, &r, &g, &b);
*pix_buf++ = b;
*pix_buf++ = g;
*pix_buf++ = r;
}
}
*out = out_buf;
*out_len = out_size;
return true;
}
bool frame2bmp(camera_fb_t * fb, uint8_t ** out, size_t * out_len)
{
return fmt2bmp(fb->buf, fb->len, fb->width, fb->height, fb->format, out, out_len);
}
@@ -0,0 +1,255 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stddef.h>
#include <string.h>
#include "esp_attr.h"
#include "soc/efuse_reg.h"
#include "esp_heap_caps.h"
#include "esp_camera.h"
#include "img_converters.h"
#include "jpge.h"
#include "yuv.h"
#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG)
#include "esp32-hal-log.h"
#define TAG ""
#else
#include "esp_log.h"
static const char* TAG = "to_jpg";
#endif
static jpge::subsampling_t default_subsampling = jpge::H2V2;
static bool rgb565_big_endian = true;
static void *_malloc(size_t size)
{
void * res = malloc(size);
if(res) {
return res;
}
// check if SPIRAM is enabled and is allocatable
#if ((CONFIG_SPIRAM || CONFIG_SPIRAM_SUPPORT) && (CONFIG_SPIRAM_USE_CAPS_ALLOC || CONFIG_SPIRAM_USE_MALLOC))
return heap_caps_malloc(size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
#endif
return NULL;
}
static IRAM_ATTR void convert_line_format(uint8_t * src, pixformat_t format, uint8_t * dst, size_t width, size_t in_channels, size_t line)
{
int i=0, o=0, l=0;
if(format == PIXFORMAT_GRAYSCALE) {
memcpy(dst, src + line * width, width);
} else if(format == PIXFORMAT_RGB888) {
l = width * 3;
src += l * line;
for(i=0; i<l; i+=3) {
dst[o++] = src[i+2];
dst[o++] = src[i+1];
dst[o++] = src[i];
}
} else if(format == PIXFORMAT_RGB565) {
l = width * 2;
src += l * line;
for(i=0; i<l; i+=2) {
if (rgb565_big_endian) {
dst[o++] = src[i] & 0xF8;
dst[o++] = (src[i] & 0x07) << 5 | (src[i+1] & 0xE0) >> 3;
dst[o++] = (src[i+1] & 0x1F) << 3;
} else {
dst[o++] = src[i+1] & 0xF8;
dst[o++] = (src[i+1] & 0x07) << 5 | (src[i] & 0xE0) >> 3;
dst[o++] = (src[i] & 0x1F) << 3;
}
}
} else if(format == PIXFORMAT_YUV422) {
uint8_t y0, y1, u, v;
uint8_t r, g, b;
l = width * 2;
src += l * line;
for(i=0; i<l; i+=4) {
y0 = src[i];
u = src[i+1];
y1 = src[i+2];
v = src[i+3];
yuv2rgb(y0, u, v, &r, &g, &b);
dst[o++] = r;
dst[o++] = g;
dst[o++] = b;
yuv2rgb(y1, u, v, &r, &g, &b);
dst[o++] = r;
dst[o++] = g;
dst[o++] = b;
}
}
}
bool convert_image(uint8_t *src, uint16_t width, uint16_t height, pixformat_t format, uint8_t quality, jpge::output_stream *dst_stream)
{
int num_channels = 3;
jpge::subsampling_t subsampling = default_subsampling;
if(format == PIXFORMAT_GRAYSCALE) {
num_channels = 1;
subsampling = jpge::Y_ONLY;
}
if(!quality) {
quality = 1;
} else if(quality > 100) {
quality = 100;
}
jpge::params comp_params = jpge::params();
comp_params.m_subsampling = subsampling;
comp_params.m_quality = quality;
jpge::jpeg_encoder dst_image;
if (!dst_image.init(dst_stream, width, height, num_channels, comp_params)) {
ESP_LOGE(TAG, "JPG encoder init failed");
return false;
}
uint8_t* line = (uint8_t*)_malloc(width * num_channels);
if(!line) {
ESP_LOGE(TAG, "Scan line malloc failed");
return false;
}
for (int i = 0; i < height; i++) {
convert_line_format(src, format, line, width, num_channels, i);
if (!dst_image.process_scanline(line)) {
ESP_LOGE(TAG, "JPG process line %u failed", i);
free(line);
return false;
}
}
free(line);
if (!dst_image.process_scanline(NULL)) {
ESP_LOGE(TAG, "JPG image finish failed");
return false;
}
dst_image.deinit();
return true;
}
class callback_stream : public jpge::output_stream {
protected:
jpg_out_cb ocb;
void * oarg;
size_t index;
public:
callback_stream(jpg_out_cb cb, void * arg) : ocb(cb), oarg(arg), index(0) { }
virtual ~callback_stream() { }
virtual bool put_buf(const void* data, int len)
{
index += ocb(oarg, index, data, len);
return true;
}
virtual size_t get_size() const
{
return index;
}
};
bool fmt2jpg_cb(uint8_t *src, size_t src_len, uint16_t width, uint16_t height, pixformat_t format, uint8_t quality, jpg_out_cb cb, void * arg)
{
callback_stream dst_stream(cb, arg);
return convert_image(src, width, height, format, quality, &dst_stream);
}
bool frame2jpg_cb(camera_fb_t * fb, uint8_t quality, jpg_out_cb cb, void * arg)
{
return fmt2jpg_cb(fb->buf, fb->len, fb->width, fb->height, fb->format, quality, cb, arg);
}
class memory_stream : public jpge::output_stream {
protected:
uint8_t *out_buf;
size_t max_len, index;
public:
memory_stream(void *pBuf, size_t buf_size) : out_buf(static_cast<uint8_t*>(pBuf)), max_len(buf_size), index(0) { }
virtual ~memory_stream() { }
virtual bool put_buf(const void* pBuf, int len)
{
if (!pBuf) {
//end of image
return true;
}
if ((size_t)len > (max_len - index)) {
//ESP_LOGW(TAG, "JPG output overflow: %d bytes (%d,%d,%d)", len - (max_len - index), len, index, max_len);
len = max_len - index;
}
if (len) {
memcpy(out_buf + index, pBuf, len);
index += len;
}
return true;
}
virtual size_t get_size() const
{
return index;
}
};
bool fmt2jpg(uint8_t *src, size_t src_len, uint16_t width, uint16_t height, pixformat_t format, uint8_t quality, uint8_t ** out, size_t * out_len)
{
//todo: allocate proper buffer for holding JPEG data
//this should be enough for CIF frame size
int jpg_buf_len = 128*1024;
uint8_t * jpg_buf = (uint8_t *)_malloc(jpg_buf_len);
if(jpg_buf == NULL) {
ESP_LOGE(TAG, "JPG buffer malloc failed");
return false;
}
memory_stream dst_stream(jpg_buf, jpg_buf_len);
if(!convert_image(src, width, height, format, quality, &dst_stream)) {
free(jpg_buf);
return false;
}
*out = jpg_buf;
*out_len = dst_stream.get_size();
return true;
}
bool frame2jpg(camera_fb_t * fb, uint8_t quality, uint8_t ** out, size_t * out_len)
{
return fmt2jpg(fb->buf, fb->len, fb->width, fb->height, fb->format, quality, out, out_len);
}
void jpgSetChroma(chroma_t chroma)
{
default_subsampling = static_cast<jpge::subsampling_t>(chroma);
}
void jpgSetRgb565BE(bool enable)
{
rgb565_big_endian = enable;
}
+298
View File
@@ -0,0 +1,298 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "yuv.h"
#include "esp_attr.h"
typedef struct {
int16_t vY;
int16_t vVr;
int16_t vVg;
int16_t vUg;
int16_t vUb;
} yuv_table_row;
static const yuv_table_row yuv_table[256] = {
// Y Vr Vg Ug Ub // #
{ -18, -204, 50, 104, -258 }, // 0
{ -17, -202, 49, 103, -256 }, // 1
{ -16, -201, 49, 102, -254 }, // 2
{ -15, -199, 48, 101, -252 }, // 3
{ -13, -197, 48, 100, -250 }, // 4
{ -12, -196, 48, 99, -248 }, // 5
{ -11, -194, 47, 99, -246 }, // 6
{ -10, -193, 47, 98, -244 }, // 7
{ -9, -191, 46, 97, -242 }, // 8
{ -8, -189, 46, 96, -240 }, // 9
{ -6, -188, 46, 95, -238 }, // 10
{ -5, -186, 45, 95, -236 }, // 11
{ -4, -185, 45, 94, -234 }, // 12
{ -3, -183, 44, 93, -232 }, // 13
{ -2, -181, 44, 92, -230 }, // 14
{ -1, -180, 44, 91, -228 }, // 15
{ 0, -178, 43, 91, -226 }, // 16
{ 1, -177, 43, 90, -223 }, // 17
{ 2, -175, 43, 89, -221 }, // 18
{ 3, -173, 42, 88, -219 }, // 19
{ 4, -172, 42, 87, -217 }, // 20
{ 5, -170, 41, 86, -215 }, // 21
{ 6, -169, 41, 86, -213 }, // 22
{ 8, -167, 41, 85, -211 }, // 23
{ 9, -165, 40, 84, -209 }, // 24
{ 10, -164, 40, 83, -207 }, // 25
{ 11, -162, 39, 82, -205 }, // 26
{ 12, -161, 39, 82, -203 }, // 27
{ 13, -159, 39, 81, -201 }, // 28
{ 15, -158, 38, 80, -199 }, // 29
{ 16, -156, 38, 79, -197 }, // 30
{ 17, -154, 37, 78, -195 }, // 31
{ 18, -153, 37, 78, -193 }, // 32
{ 19, -151, 37, 77, -191 }, // 33
{ 20, -150, 36, 76, -189 }, // 34
{ 22, -148, 36, 75, -187 }, // 35
{ 23, -146, 35, 74, -185 }, // 36
{ 24, -145, 35, 73, -183 }, // 37
{ 25, -143, 35, 73, -181 }, // 38
{ 26, -142, 34, 72, -179 }, // 39
{ 27, -140, 34, 71, -177 }, // 40
{ 29, -138, 34, 70, -175 }, // 41
{ 30, -137, 33, 69, -173 }, // 42
{ 31, -135, 33, 69, -171 }, // 43
{ 32, -134, 32, 68, -169 }, // 44
{ 33, -132, 32, 67, -167 }, // 45
{ 34, -130, 32, 66, -165 }, // 46
{ 36, -129, 31, 65, -163 }, // 47
{ 37, -127, 31, 65, -161 }, // 48
{ 38, -126, 30, 64, -159 }, // 49
{ 39, -124, 30, 63, -157 }, // 50
{ 40, -122, 30, 62, -155 }, // 51
{ 41, -121, 29, 61, -153 }, // 52
{ 43, -119, 29, 60, -151 }, // 53
{ 44, -118, 28, 60, -149 }, // 54
{ 45, -116, 28, 59, -147 }, // 55
{ 46, -114, 28, 58, -145 }, // 56
{ 47, -113, 27, 57, -143 }, // 57
{ 48, -111, 27, 56, -141 }, // 58
{ 50, -110, 26, 56, -139 }, // 59
{ 51, -108, 26, 55, -137 }, // 60
{ 52, -106, 26, 54, -135 }, // 61
{ 53, -105, 25, 53, -133 }, // 62
{ 54, -103, 25, 52, -131 }, // 63
{ 55, -102, 25, 52, -129 }, // 64
{ 57, -100, 24, 51, -127 }, // 65
{ 58, -98, 24, 50, -125 }, // 66
{ 59, -97, 23, 49, -123 }, // 67
{ 60, -95, 23, 48, -121 }, // 68
{ 61, -94, 23, 47, -119 }, // 69
{ 62, -92, 22, 47, -117 }, // 70
{ 64, -90, 22, 46, -115 }, // 71
{ 65, -89, 21, 45, -113 }, // 72
{ 66, -87, 21, 44, -110 }, // 73
{ 67, -86, 21, 43, -108 }, // 74
{ 68, -84, 20, 43, -106 }, // 75
{ 69, -82, 20, 42, -104 }, // 76
{ 71, -81, 19, 41, -102 }, // 77
{ 72, -79, 19, 40, -100 }, // 78
{ 73, -78, 19, 39, -98 }, // 79
{ 74, -76, 18, 39, -96 }, // 80
{ 75, -75, 18, 38, -94 }, // 81
{ 76, -73, 17, 37, -92 }, // 82
{ 77, -71, 17, 36, -90 }, // 83
{ 79, -70, 17, 35, -88 }, // 84
{ 80, -68, 16, 34, -86 }, // 85
{ 81, -67, 16, 34, -84 }, // 86
{ 82, -65, 16, 33, -82 }, // 87
{ 83, -63, 15, 32, -80 }, // 88
{ 84, -62, 15, 31, -78 }, // 89
{ 86, -60, 14, 30, -76 }, // 90
{ 87, -59, 14, 30, -74 }, // 91
{ 88, -57, 14, 29, -72 }, // 92
{ 89, -55, 13, 28, -70 }, // 93
{ 90, -54, 13, 27, -68 }, // 94
{ 91, -52, 12, 26, -66 }, // 95
{ 93, -51, 12, 26, -64 }, // 96
{ 94, -49, 12, 25, -62 }, // 97
{ 95, -47, 11, 24, -60 }, // 98
{ 96, -46, 11, 23, -58 }, // 99
{ 97, -44, 10, 22, -56 }, // 100
{ 98, -43, 10, 21, -54 }, // 101
{ 100, -41, 10, 21, -52 }, // 102
{ 101, -39, 9, 20, -50 }, // 103
{ 102, -38, 9, 19, -48 }, // 104
{ 103, -36, 8, 18, -46 }, // 105
{ 104, -35, 8, 17, -44 }, // 106
{ 105, -33, 8, 17, -42 }, // 107
{ 107, -31, 7, 16, -40 }, // 108
{ 108, -30, 7, 15, -38 }, // 109
{ 109, -28, 7, 14, -36 }, // 110
{ 110, -27, 6, 13, -34 }, // 111
{ 111, -25, 6, 13, -32 }, // 112
{ 112, -23, 5, 12, -30 }, // 113
{ 114, -22, 5, 11, -28 }, // 114
{ 115, -20, 5, 10, -26 }, // 115
{ 116, -19, 4, 9, -24 }, // 116
{ 117, -17, 4, 8, -22 }, // 117
{ 118, -15, 3, 8, -20 }, // 118
{ 119, -14, 3, 7, -18 }, // 119
{ 121, -12, 3, 6, -16 }, // 120
{ 122, -11, 2, 5, -14 }, // 121
{ 123, -9, 2, 4, -12 }, // 122
{ 124, -7, 1, 4, -10 }, // 123
{ 125, -6, 1, 3, -8 }, // 124
{ 126, -4, 1, 2, -6 }, // 125
{ 128, -3, 0, 1, -4 }, // 126
{ 129, -1, 0, 0, -2 }, // 127
{ 130, 0, 0, 0, 0 }, // 128
{ 131, 1, 0, 0, 2 }, // 129
{ 132, 3, 0, -1, 4 }, // 130
{ 133, 4, -1, -2, 6 }, // 131
{ 135, 6, -1, -3, 8 }, // 132
{ 136, 7, -1, -4, 10 }, // 133
{ 137, 9, -2, -4, 12 }, // 134
{ 138, 11, -2, -5, 14 }, // 135
{ 139, 12, -3, -6, 16 }, // 136
{ 140, 14, -3, -7, 18 }, // 137
{ 142, 15, -3, -8, 20 }, // 138
{ 143, 17, -4, -8, 22 }, // 139
{ 144, 19, -4, -9, 24 }, // 140
{ 145, 20, -5, -10, 26 }, // 141
{ 146, 22, -5, -11, 28 }, // 142
{ 147, 23, -5, -12, 30 }, // 143
{ 148, 25, -6, -13, 32 }, // 144
{ 150, 27, -6, -13, 34 }, // 145
{ 151, 28, -7, -14, 36 }, // 146
{ 152, 30, -7, -15, 38 }, // 147
{ 153, 31, -7, -16, 40 }, // 148
{ 154, 33, -8, -17, 42 }, // 149
{ 155, 35, -8, -17, 44 }, // 150
{ 157, 36, -8, -18, 46 }, // 151
{ 158, 38, -9, -19, 48 }, // 152
{ 159, 39, -9, -20, 50 }, // 153
{ 160, 41, -10, -21, 52 }, // 154
{ 161, 43, -10, -21, 54 }, // 155
{ 162, 44, -10, -22, 56 }, // 156
{ 164, 46, -11, -23, 58 }, // 157
{ 165, 47, -11, -24, 60 }, // 158
{ 166, 49, -12, -25, 62 }, // 159
{ 167, 51, -12, -26, 64 }, // 160
{ 168, 52, -12, -26, 66 }, // 161
{ 169, 54, -13, -27, 68 }, // 162
{ 171, 55, -13, -28, 70 }, // 163
{ 172, 57, -14, -29, 72 }, // 164
{ 173, 59, -14, -30, 74 }, // 165
{ 174, 60, -14, -30, 76 }, // 166
{ 175, 62, -15, -31, 78 }, // 167
{ 176, 63, -15, -32, 80 }, // 168
{ 178, 65, -16, -33, 82 }, // 169
{ 179, 67, -16, -34, 84 }, // 170
{ 180, 68, -16, -34, 86 }, // 171
{ 181, 70, -17, -35, 88 }, // 172
{ 182, 71, -17, -36, 90 }, // 173
{ 183, 73, -17, -37, 92 }, // 174
{ 185, 75, -18, -38, 94 }, // 175
{ 186, 76, -18, -39, 96 }, // 176
{ 187, 78, -19, -39, 98 }, // 177
{ 188, 79, -19, -40, 100 }, // 178
{ 189, 81, -19, -41, 102 }, // 179
{ 190, 82, -20, -42, 104 }, // 180
{ 192, 84, -20, -43, 106 }, // 181
{ 193, 86, -21, -43, 108 }, // 182
{ 194, 87, -21, -44, 110 }, // 183
{ 195, 89, -21, -45, 113 }, // 184
{ 196, 90, -22, -46, 115 }, // 185
{ 197, 92, -22, -47, 117 }, // 186
{ 199, 94, -23, -47, 119 }, // 187
{ 200, 95, -23, -48, 121 }, // 188
{ 201, 97, -23, -49, 123 }, // 189
{ 202, 98, -24, -50, 125 }, // 190
{ 203, 100, -24, -51, 127 }, // 191
{ 204, 102, -25, -52, 129 }, // 192
{ 206, 103, -25, -52, 131 }, // 193
{ 207, 105, -25, -53, 133 }, // 194
{ 208, 106, -26, -54, 135 }, // 195
{ 209, 108, -26, -55, 137 }, // 196
{ 210, 110, -26, -56, 139 }, // 197
{ 211, 111, -27, -56, 141 }, // 198
{ 213, 113, -27, -57, 143 }, // 199
{ 214, 114, -28, -58, 145 }, // 200
{ 215, 116, -28, -59, 147 }, // 201
{ 216, 118, -28, -60, 149 }, // 202
{ 217, 119, -29, -60, 151 }, // 203
{ 218, 121, -29, -61, 153 }, // 204
{ 219, 122, -30, -62, 155 }, // 205
{ 221, 124, -30, -63, 157 }, // 206
{ 222, 126, -30, -64, 159 }, // 207
{ 223, 127, -31, -65, 161 }, // 208
{ 224, 129, -31, -65, 163 }, // 209
{ 225, 130, -32, -66, 165 }, // 210
{ 226, 132, -32, -67, 167 }, // 211
{ 228, 134, -32, -68, 169 }, // 212
{ 229, 135, -33, -69, 171 }, // 213
{ 230, 137, -33, -69, 173 }, // 214
{ 231, 138, -34, -70, 175 }, // 215
{ 232, 140, -34, -71, 177 }, // 216
{ 233, 142, -34, -72, 179 }, // 217
{ 235, 143, -35, -73, 181 }, // 218
{ 236, 145, -35, -73, 183 }, // 219
{ 237, 146, -35, -74, 185 }, // 220
{ 238, 148, -36, -75, 187 }, // 221
{ 239, 150, -36, -76, 189 }, // 222
{ 240, 151, -37, -77, 191 }, // 223
{ 242, 153, -37, -78, 193 }, // 224
{ 243, 154, -37, -78, 195 }, // 225
{ 244, 156, -38, -79, 197 }, // 226
{ 245, 158, -38, -80, 199 }, // 227
{ 246, 159, -39, -81, 201 }, // 228
{ 247, 161, -39, -82, 203 }, // 229
{ 249, 162, -39, -82, 205 }, // 230
{ 250, 164, -40, -83, 207 }, // 231
{ 251, 165, -40, -84, 209 }, // 232
{ 252, 167, -41, -85, 211 }, // 233
{ 253, 169, -41, -86, 213 }, // 234
{ 254, 170, -41, -86, 215 }, // 235
{ 256, 172, -42, -87, 217 }, // 236
{ 257, 173, -42, -88, 219 }, // 237
{ 258, 175, -43, -89, 221 }, // 238
{ 259, 177, -43, -90, 223 }, // 239
{ 260, 178, -43, -91, 226 }, // 240
{ 261, 180, -44, -91, 228 }, // 241
{ 263, 181, -44, -92, 230 }, // 242
{ 264, 183, -44, -93, 232 }, // 243
{ 265, 185, -45, -94, 234 }, // 244
{ 266, 186, -45, -95, 236 }, // 245
{ 267, 188, -46, -95, 238 }, // 246
{ 268, 189, -46, -96, 240 }, // 247
{ 270, 191, -46, -97, 242 }, // 248
{ 271, 193, -47, -98, 244 }, // 249
{ 272, 194, -47, -99, 246 }, // 250
{ 273, 196, -48, -99, 248 }, // 251
{ 274, 197, -48, -100, 250 }, // 252
{ 275, 199, -48, -101, 252 }, // 253
{ 277, 201, -49, -102, 254 }, // 254
{ 278, 202, -49, -103, 256 } // 255
};
#define YUYV_CONSTRAIN(v) ((v)<0)?0:(((v)>255)?255:(v))
void IRAM_ATTR yuv2rgb(uint8_t y, uint8_t u, uint8_t v, uint8_t *r, uint8_t *g, uint8_t *b)
{
int16_t ri, gi, bi;
ri = yuv_table[y].vY + yuv_table[v].vVr;
gi = yuv_table[y].vY + yuv_table[u].vUg + yuv_table[v].vVg;
bi = yuv_table[y].vY + yuv_table[u].vUb;
*r = YUYV_CONSTRAIN(ri);
*g = YUYV_CONSTRAIN(gi);
*b = YUYV_CONSTRAIN(bi);
}
+852
View File
@@ -0,0 +1,852 @@
// Copyright 2010-2020 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdio.h>
#include <string.h>
#include <stdalign.h>
#include "esp_heap_caps.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "ll_cam.h"
#include "cam_hal.h"
#if (ESP_IDF_VERSION_MAJOR == 3) && (ESP_IDF_VERSION_MINOR == 3)
#include "rom/ets_sys.h"
#else
#include "esp_timer.h"
#include "esp_cache.h"
#include "hal/cache_hal.h"
#include "hal/cache_ll.h"
#include "esp_idf_version.h"
#ifndef ESP_CACHE_MSYNC_FLAG_DIR_M2C
#define ESP_CACHE_MSYNC_FLAG_DIR_M2C 0
#endif
#if CONFIG_IDF_TARGET_ESP32
#include "esp32/rom/ets_sys.h" // will be removed in idf v5.0
#elif CONFIG_IDF_TARGET_ESP32S2
#include "esp32s2/rom/ets_sys.h"
#elif CONFIG_IDF_TARGET_ESP32S3
#include "esp32s3/rom/ets_sys.h"
#endif
#endif // ESP_IDF_VERSION_MAJOR
#if CONFIG_LOG_DEFAULT_LEVEL_NONE
#define ESP_CAMERA_ETS_PRINTF(f, ...)
#else
#define ESP_CAMERA_ETS_PRINTF(f, ...) ets_printf(f, ##__VA_ARGS__)
#endif
#if CONFIG_CAMERA_TASK_STACK_SIZE
#define CAM_TASK_STACK CONFIG_CAMERA_TASK_STACK_SIZE
#else
#define CAM_TASK_STACK (4*1024)
#endif
static const char *TAG = "cam_hal";
static cam_obj_t *cam_obj = NULL;
#if defined(CONFIG_CAMERA_PSRAM_DMA)
#define CAMERA_PSRAM_DMA_ENABLED CONFIG_CAMERA_PSRAM_DMA
#else
#define CAMERA_PSRAM_DMA_ENABLED 0
#endif
static volatile bool g_psram_dma_mode = CAMERA_PSRAM_DMA_ENABLED;
static portMUX_TYPE g_psram_dma_lock = portMUX_INITIALIZER_UNLOCKED;
/* At top of cam_hal.c – one switch for noisy ISR prints */
#ifndef CAM_LOG_SPAM_EVERY_FRAME
#define CAM_LOG_SPAM_EVERY_FRAME 0 /* set to 1 to restore old behaviour */
#endif
/* Number of bytes copied to SRAM for SOI validation when capturing
* directly to PSRAM. Tunable to probe more of the frame start if needed. */
#ifndef CAM_SOI_PROBE_BYTES
#define CAM_SOI_PROBE_BYTES 32
#endif
/*
* PSRAM DMA may bypass the CPU cache. Always call esp_cache_msync() on
* PSRAM regions that the CPU will read so cached reads see the data written
* by DMA.
*/
static inline size_t dcache_line_size(void)
{
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0)
/* cache_hal_get_cache_line_size() added extra argument from IDF 5.2 */
return cache_hal_get_cache_line_size(CACHE_LL_LEVEL_EXT_MEM, CACHE_TYPE_DATA);
#else
/* Older releases only expose the ROM helper, all current targets
* have a 32‑byte DCache line */
return 32;
#endif
}
/*
* Invalidate CPU data cache lines that cover a region in PSRAM which
* has just been written by DMA. This guarantees subsequent CPU reads
* fetch the fresh data from PSRAM rather than stale cache contents.
* Both address and length are aligned to the data cache line size.
*/
static inline void cam_drop_psram_cache(void *addr, size_t len)
{
size_t line = dcache_line_size();
if (line == 0) {
line = 32; /* sane fallback */
}
uintptr_t start = (uintptr_t)addr & ~(line - 1);
size_t sync_len = (len + ((uintptr_t)addr - start) + line - 1) & ~(line - 1);
esp_cache_msync((void *)start, sync_len,
ESP_CACHE_MSYNC_FLAG_DIR_M2C | ESP_CACHE_MSYNC_FLAG_INVALIDATE);
}
/* Throttle repeated warnings printed from tight loops / ISRs.
*
* counter – static DRAM/IRAM uint16_t you pass in
* first – literal C string shown on first hit and as prefix of summaries
*/
#if CONFIG_LOG_DEFAULT_LEVEL >= 2
#define CAM_WARN_THROTTLE(counter, first) \
do { \
if (++(counter) == 1) { \
ESP_CAMERA_ETS_PRINTF(DRAM_STR("cam_hal: %s\r\n"), first); \
} else if ((counter) % 100 == 0) { \
ESP_CAMERA_ETS_PRINTF(DRAM_STR("cam_hal: %s - 100 additional misses\r\n"), first); \
} \
if ((counter) == 10000) (counter) = 1; \
} while (0)
#else
#define CAM_WARN_THROTTLE(counter, first) do { (void)(counter); } while (0)
#endif
/* JPEG markers (byte-order independent). */
static const uint8_t JPEG_SOI_MARKER[] = {0xFF, 0xD8, 0xFF}; /* SOI = FF D8 FF */
#define JPEG_SOI_MARKER_LEN (3)
static const uint8_t JPEG_EOI_BYTES[] = {0xFF, 0xD9}; /* EOI = FF D9 */
#define JPEG_EOI_MARKER_LEN (2)
/* Compute the scan window for JPEG EOI detection in PSRAM. */
static inline size_t eoi_probe_window(size_t half, size_t frame_len)
{
size_t w = half + (JPEG_EOI_MARKER_LEN - 1);
return w > frame_len ? frame_len : w;
}
static int cam_verify_jpeg_soi(const uint8_t *inbuf, uint32_t length)
{
static uint16_t warn_soi_miss_cnt = 0;
if (length < JPEG_SOI_MARKER_LEN) {
CAM_WARN_THROTTLE(warn_soi_miss_cnt,
"NO-SOI - JPEG start marker missing (len < 3b)");
return -1;
}
for (uint32_t i = 0; i <= length - JPEG_SOI_MARKER_LEN; i++) {
if (memcmp(&inbuf[i], JPEG_SOI_MARKER, JPEG_SOI_MARKER_LEN) == 0) {
//ESP_LOGW(TAG, "SOI: %d", (int) i);
return i;
}
}
CAM_WARN_THROTTLE(warn_soi_miss_cnt,
"NO-SOI - JPEG start marker missing");
return -1;
}
static int cam_verify_jpeg_eoi(const uint8_t *inbuf, uint32_t length, bool search_forward)
{
if (length < JPEG_EOI_MARKER_LEN) {
return -1;
}
if (search_forward) {
/* Scan forward to honor the earliest marker in the buffer. This avoids
* returning an EOI that belongs to a larger previous frame when the tail
* of that frame still resides in PSRAM. JPEG data is pseudo random, so
* the first marker byte appears rarely; test four positions per load to
* reduce memory traffic. */
const uint8_t *pat = JPEG_EOI_BYTES;
const uint32_t A = pat[0] * 0x01010101u;
const uint32_t ONE = 0x01010101u;
const uint32_t HIGH = 0x80808080u;
uint32_t i = 0;
while (i + 4 <= length) {
uint32_t w;
memcpy(&w, inbuf + i, 4); /* unaligned load is allowed */
uint32_t x = w ^ A; /* identify bytes equal to first marker byte */
uint32_t m = (~x & (x - ONE)) & HIGH; /* mask has high bit set for candidate bytes */
while (m) { /* handle only candidates to avoid unnecessary memcmp calls */
unsigned off = __builtin_ctz(m) >> 3;
uint32_t pos = i + off;
if (pos + JPEG_EOI_MARKER_LEN <= length &&
memcmp(inbuf + pos, pat, JPEG_EOI_MARKER_LEN) == 0) {
return pos;
}
m &= m - 1; /* clear processed candidate */
}
i += 4;
}
for (; i + JPEG_EOI_MARKER_LEN <= length; i++) {
if (memcmp(inbuf + i, pat, JPEG_EOI_MARKER_LEN) == 0) {
return i;
}
}
return -1;
}
const uint8_t *dptr = inbuf + length - JPEG_EOI_MARKER_LEN;
while (dptr >= inbuf) {
if (memcmp(dptr, JPEG_EOI_BYTES, JPEG_EOI_MARKER_LEN) == 0) {
return dptr - inbuf;
}
if (dptr == inbuf) {
break;
}
dptr--;
}
return -1;
}
static bool cam_get_next_frame(int * frame_pos)
{
if(!cam_obj->frames[*frame_pos].en){
for (int x = 0; x < cam_obj->frame_cnt; x++) {
if (cam_obj->frames[x].en) {
*frame_pos = x;
return true;
}
}
} else {
return true;
}
return false;
}
static bool cam_start_frame(int * frame_pos)
{
if (cam_get_next_frame(frame_pos)) {
if(ll_cam_start(cam_obj, *frame_pos)){
// Vsync the frame manually
ll_cam_do_vsync(cam_obj);
uint64_t us = (uint64_t)esp_timer_get_time();
cam_obj->frames[*frame_pos].fb.timestamp.tv_sec = us / 1000000UL;
cam_obj->frames[*frame_pos].fb.timestamp.tv_usec = us % 1000000UL;
return true;
}
}
return false;
}
void IRAM_ATTR ll_cam_send_event(cam_obj_t *cam, cam_event_t cam_event, BaseType_t * HPTaskAwoken)
{
if (xQueueSendFromISR(cam->event_queue, (void *)&cam_event, HPTaskAwoken) != pdTRUE) {
ll_cam_stop(cam);
cam->state = CAM_STATE_IDLE;
#if CAM_LOG_SPAM_EVERY_FRAME
ESP_DRAM_LOGD(TAG, "EV-%s-OVF", cam_event==CAM_IN_SUC_EOF_EVENT ? "EOF" : "VSYNC");
#else
static uint16_t ovf_cnt = 0;
CAM_WARN_THROTTLE(ovf_cnt,
cam_event==CAM_IN_SUC_EOF_EVENT ? "EV-EOF-OVF" : "EV-VSYNC-OVF");
#endif
}
}
//Copy frame from DMA dma_buffer to frame dma_buffer
static void cam_task(void *arg)
{
int cnt = 0;
int frame_pos = 0;
cam_obj->state = CAM_STATE_IDLE;
cam_event_t cam_event = 0;
xQueueReset(cam_obj->event_queue);
while (1) {
xQueueReceive(cam_obj->event_queue, (void *)&cam_event, portMAX_DELAY);
DBG_PIN_SET(1);
switch (cam_obj->state) {
case CAM_STATE_IDLE: {
if (cam_event == CAM_VSYNC_EVENT) {
//DBG_PIN_SET(1);
if(cam_start_frame(&frame_pos)){
cam_obj->frames[frame_pos].fb.len = 0;
cam_obj->state = CAM_STATE_READ_BUF;
}
cnt = 0;
}
}
break;
case CAM_STATE_READ_BUF: {
camera_fb_t * frame_buffer_event = &cam_obj->frames[frame_pos].fb;
size_t pixels_per_dma = (cam_obj->dma_half_buffer_size * cam_obj->fb_bytes_per_pixel) / (cam_obj->dma_bytes_per_item * cam_obj->in_bytes_per_pixel);
if (cam_event == CAM_IN_SUC_EOF_EVENT) {
if(!cam_obj->psram_mode){
if (cam_obj->fb_size < (frame_buffer_event->len + pixels_per_dma)) {
ESP_CAMERA_ETS_PRINTF(DRAM_STR("cam_hal: FB-OVF\r\n"));
ll_cam_stop(cam_obj);
continue;
}
frame_buffer_event->len += ll_cam_memcpy(cam_obj,
&frame_buffer_event->buf[frame_buffer_event->len],
&cam_obj->dma_buffer[(cnt % cam_obj->dma_half_buffer_cnt) * cam_obj->dma_half_buffer_size],
cam_obj->dma_half_buffer_size);
} else {
// stop if the next DMA copy would exceed the framebuffer slot
// size, since we're called only after the copy occurs
// This effectively reduces maximum usable frame buffer size
// by one DMA operation, as we can't predict here, if the next
// cam event will be a VSYNC
if (cnt + 1 >= cam_obj->frame_copy_cnt) {
ESP_CAMERA_ETS_PRINTF(DRAM_STR("cam_hal: DMA overflow\r\n"));
ll_cam_stop(cam_obj);
cam_obj->state = CAM_STATE_IDLE;
continue;
}
}
//Check for JPEG SOI in the first buffer. stop if not found
if (cam_obj->jpeg_mode && cnt == 0) {
if (cam_obj->psram_mode) {
/* dma_half_buffer_size already in BYTES (see ll_cam_memcpy()) */
size_t probe_len = cam_obj->dma_half_buffer_size;
/* clamp to avoid copying past the end of soi_probe */
if (probe_len > CAM_SOI_PROBE_BYTES) {
probe_len = CAM_SOI_PROBE_BYTES;
}
/* Invalidate cache lines for the DMA buffer before probing */
cam_drop_psram_cache(frame_buffer_event->buf, probe_len);
uint8_t soi_probe[CAM_SOI_PROBE_BYTES];
memcpy(soi_probe, frame_buffer_event->buf, probe_len);
int soi_off = cam_verify_jpeg_soi(soi_probe, probe_len);
if (soi_off != 0) {
static uint16_t warn_psram_soi_cnt = 0;
if (soi_off > 0) {
CAM_WARN_THROTTLE(warn_psram_soi_cnt,
"NO-SOI - JPEG start marker not at pos 0 (PSRAM)");
} else {
CAM_WARN_THROTTLE(warn_psram_soi_cnt,
"NO-SOI - JPEG start marker missing (PSRAM)");
}
ll_cam_stop(cam_obj);
cam_obj->state = CAM_STATE_IDLE;
continue;
}
} else {
int soi_off = cam_verify_jpeg_soi(frame_buffer_event->buf, frame_buffer_event->len);
if (soi_off != 0) {
static uint16_t warn_soi_bad_cnt = 0;
if (soi_off > 0) {
CAM_WARN_THROTTLE(warn_soi_bad_cnt,
"NO-SOI - JPEG start marker not at pos 0");
} else {
CAM_WARN_THROTTLE(warn_soi_bad_cnt,
"NO-SOI - JPEG start marker missing");
}
ll_cam_stop(cam_obj);
cam_obj->state = CAM_STATE_IDLE;
continue;
}
}
}
cnt++;
} else if (cam_event == CAM_VSYNC_EVENT) {
//DBG_PIN_SET(1);
ll_cam_stop(cam_obj);
if (cnt || !cam_obj->jpeg_mode || cam_obj->psram_mode) {
if (cam_obj->jpeg_mode) {
if (!cam_obj->psram_mode) {
if (cam_obj->fb_size < (frame_buffer_event->len + pixels_per_dma)) {
ESP_CAMERA_ETS_PRINTF(DRAM_STR("cam_hal: FB-OVF\r\n"));
cnt--;
} else {
frame_buffer_event->len += ll_cam_memcpy(cam_obj,
&frame_buffer_event->buf[frame_buffer_event->len],
&cam_obj->dma_buffer[(cnt % cam_obj->dma_half_buffer_cnt) * cam_obj->dma_half_buffer_size],
cam_obj->dma_half_buffer_size);
}
}
cnt++;
}
cam_obj->frames[frame_pos].en = 0;
if (cam_obj->psram_mode) {
if (cam_obj->jpeg_mode) {
frame_buffer_event->len = cnt * cam_obj->dma_half_buffer_size;
} else {
frame_buffer_event->len = cam_obj->recv_size;
}
} else if (!cam_obj->jpeg_mode) {
if (frame_buffer_event->len != cam_obj->fb_size) {
cam_obj->frames[frame_pos].en = 1;
ESP_CAMERA_ETS_PRINTF(DRAM_STR("cam_hal: FB-SIZE: %u != %u\r\n"), frame_buffer_event->len, (unsigned) cam_obj->fb_size);
}
}
//send frame
if(!cam_obj->frames[frame_pos].en && xQueueSend(cam_obj->frame_buffer_queue, (void *)&frame_buffer_event, 0) != pdTRUE) {
//pop frame buffer from the queue
camera_fb_t * fb2 = NULL;
if(xQueueReceive(cam_obj->frame_buffer_queue, &fb2, 0) == pdTRUE) {
//push the new frame to the end of the queue
if (xQueueSend(cam_obj->frame_buffer_queue, (void *)&frame_buffer_event, 0) != pdTRUE) {
cam_obj->frames[frame_pos].en = 1;
ESP_CAMERA_ETS_PRINTF(DRAM_STR("cam_hal: FBQ-SND\r\n"));
}
//free the popped buffer
cam_give(fb2);
} else {
//queue is full and we could not pop a frame from it
cam_obj->frames[frame_pos].en = 1;
ESP_CAMERA_ETS_PRINTF(DRAM_STR("cam_hal: FBQ-RCV\r\n"));
}
}
}
if(!cam_start_frame(&frame_pos)){
cam_obj->state = CAM_STATE_IDLE;
} else {
cam_obj->frames[frame_pos].fb.len = 0;
}
cnt = 0;
}
}
break;
}
DBG_PIN_SET(0);
}
}
static lldesc_t * allocate_dma_descriptors(uint32_t count, uint16_t size, uint8_t * buffer)
{
lldesc_t *dma = (lldesc_t *)heap_caps_malloc(count * sizeof(lldesc_t), MALLOC_CAP_DMA);
if (dma == NULL) {
return dma;
}
for (int x = 0; x < count; x++) {
dma[x].size = size;
dma[x].length = 0;
dma[x].sosf = 0;
dma[x].eof = 0;
dma[x].owner = 1;
dma[x].buf = (buffer + size * x);
dma[x].empty = (uint32_t)&dma[(x + 1) % count];
}
return dma;
}
static esp_err_t cam_dma_config(const camera_config_t *config)
{
bool ret = ll_cam_dma_sizes(cam_obj);
if (0 == ret) {
return ESP_FAIL;
}
cam_obj->dma_node_cnt = (cam_obj->dma_buffer_size) / cam_obj->dma_node_buffer_size; // Number of DMA nodes
cam_obj->frame_copy_cnt = cam_obj->recv_size / cam_obj->dma_half_buffer_size; // Number of interrupted copies, ping-pong copy
if (cam_obj->psram_mode) {
cam_obj->frame_copy_cnt++;
}
ESP_LOGI(TAG, "buffer_size: %d, half_buffer_size: %d, node_buffer_size: %d, node_cnt: %d, total_cnt: %d",
(int) cam_obj->dma_buffer_size, (int) cam_obj->dma_half_buffer_size, (int) cam_obj->dma_node_buffer_size,
(int) cam_obj->dma_node_cnt, (int) cam_obj->frame_copy_cnt);
cam_obj->dma_buffer = NULL;
cam_obj->dma = NULL;
cam_obj->frames = (cam_frame_t *)heap_caps_aligned_calloc(alignof(cam_frame_t), 1, cam_obj->frame_cnt * sizeof(cam_frame_t), MALLOC_CAP_DEFAULT);
CAM_CHECK(cam_obj->frames != NULL, "frames malloc failed", ESP_FAIL);
uint8_t dma_align = 0;
size_t fb_size = cam_obj->fb_size;
if (cam_obj->psram_mode) {
dma_align = ll_cam_get_dma_align(cam_obj);
if (cam_obj->fb_size < cam_obj->recv_size) {
fb_size = cam_obj->recv_size;
}
fb_size += cam_obj->dma_half_buffer_size;
}
/* Allocate memory for frame buffer */
size_t alloc_size = fb_size * sizeof(uint8_t) + dma_align;
uint32_t _caps = MALLOC_CAP_8BIT;
if (CAMERA_FB_IN_DRAM == config->fb_location) {
_caps |= MALLOC_CAP_INTERNAL;
} else {
_caps |= MALLOC_CAP_SPIRAM;
}
for (int x = 0; x < cam_obj->frame_cnt; x++) {
cam_obj->frames[x].dma = NULL;
cam_obj->frames[x].fb_offset = 0;
cam_obj->frames[x].en = 0;
ESP_LOGI(TAG, "Allocating %d Byte frame buffer in %s", alloc_size, _caps & MALLOC_CAP_SPIRAM ? "PSRAM" : "OnBoard RAM");
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 3, 0)
// In IDF v4.2 and earlier, memory returned by heap_caps_aligned_alloc must be freed using heap_caps_aligned_free.
// And heap_caps_aligned_free is deprecated on v4.3.
cam_obj->frames[x].fb.buf = (uint8_t *)heap_caps_aligned_alloc(16, alloc_size, _caps);
#else
cam_obj->frames[x].fb.buf = (uint8_t *)heap_caps_malloc(alloc_size, _caps);
#endif
CAM_CHECK(cam_obj->frames[x].fb.buf != NULL, "frame buffer malloc failed", ESP_FAIL);
if (cam_obj->psram_mode) {
//align PSRAM buffer. TODO: save the offset so proper address can be freed later
cam_obj->frames[x].fb_offset = dma_align - ((uint32_t)cam_obj->frames[x].fb.buf & (dma_align - 1));
cam_obj->frames[x].fb.buf += cam_obj->frames[x].fb_offset;
ESP_LOGI(TAG, "Frame[%d]: Offset: %u, Addr: 0x%08X", x, cam_obj->frames[x].fb_offset, (unsigned) cam_obj->frames[x].fb.buf);
cam_obj->frames[x].dma = allocate_dma_descriptors(cam_obj->dma_node_cnt, cam_obj->dma_node_buffer_size, cam_obj->frames[x].fb.buf);
CAM_CHECK(cam_obj->frames[x].dma != NULL, "frame dma malloc failed", ESP_FAIL);
}
cam_obj->frames[x].en = 1;
}
if (!cam_obj->psram_mode) {
cam_obj->dma_buffer = (uint8_t *)heap_caps_malloc(cam_obj->dma_buffer_size * sizeof(uint8_t), MALLOC_CAP_DMA);
if(NULL == cam_obj->dma_buffer) {
ESP_LOGE(TAG,"%s(%d): DMA buffer %d Byte malloc failed, the current largest free block:%d Byte", __FUNCTION__, __LINE__,
(int) cam_obj->dma_buffer_size, (int) heap_caps_get_largest_free_block(MALLOC_CAP_DMA));
return ESP_FAIL;
}
cam_obj->dma = allocate_dma_descriptors(cam_obj->dma_node_cnt, cam_obj->dma_node_buffer_size, cam_obj->dma_buffer);
CAM_CHECK(cam_obj->dma != NULL, "dma malloc failed", ESP_FAIL);
}
return ESP_OK;
}
/* VSYNC polarity used by the next `cam_init()`, and the XCLK frequency the driver
* was last asked for.
*
* These are set through cam_set_vsync_invert() before esp_camera_init() and read
* back afterwards. The polarity in particular is not a free choice: a mismatch
* between the sensor's VSYNC level and what the LCD_CAM expects does not fail
* loudly, it just makes the driver latch frames on the wrong edge, which shows up
* as a valid but very low frame rate. esp32-camera defaults to inverting because
* most DVP sensors are active-low; a board that inverts VSYNC needs the other. */
static bool s_vsync_invert = true;
static int s_xclk_hz = 0;
void cam_set_vsync_invert(bool invert)
{
s_vsync_invert = invert;
}
bool cam_get_vsync_invert(void)
{
return s_vsync_invert;
}
void cam_set_xclk_hz(int hz)
{
s_xclk_hz = hz;
}
int cam_get_xclk_hz(void)
{
return s_xclk_hz;
}
esp_err_t cam_init(const camera_config_t *config)
{
CAM_CHECK(NULL != config, "config pointer is invalid", ESP_ERR_INVALID_ARG);
esp_err_t ret = ESP_OK;
cam_obj = (cam_obj_t *)heap_caps_calloc(1, sizeof(cam_obj_t), MALLOC_CAP_DMA);
CAM_CHECK(NULL != cam_obj, "lcd_cam object malloc error", ESP_ERR_NO_MEM);
cam_obj->swap_data = 0;
cam_obj->vsync_pin = config->pin_vsync;
cam_obj->vsync_invert = s_vsync_invert;
s_xclk_hz = (int)config->xclk_freq_hz;
ll_cam_set_pin(cam_obj, config);
ret = ll_cam_config(cam_obj, config);
CAM_CHECK_GOTO(ret == ESP_OK, "ll_cam initialize failed", err);
#if CAMERA_DBG_PIN_ENABLE
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[DBG_PIN_NUM], PIN_FUNC_GPIO);
gpio_set_direction(DBG_PIN_NUM, GPIO_MODE_OUTPUT);
gpio_set_pull_mode(DBG_PIN_NUM, GPIO_FLOATING);
#endif
ESP_LOGI(TAG, "cam init ok");
return ESP_OK;
err:
free(cam_obj);
cam_obj = NULL;
return ESP_FAIL;
}
esp_err_t cam_config(const camera_config_t *config, framesize_t frame_size, uint16_t sensor_pid)
{
CAM_CHECK(NULL != config, "config pointer is invalid", ESP_ERR_INVALID_ARG);
esp_err_t ret = ESP_OK;
ret = ll_cam_set_sample_mode(cam_obj, (pixformat_t)config->pixel_format, config->xclk_freq_hz, sensor_pid);
CAM_CHECK_GOTO(ret == ESP_OK, "ll_cam_set_sample_mode failed", err);
cam_obj->jpeg_mode = config->pixel_format == PIXFORMAT_JPEG;
#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
cam_obj->psram_mode = g_psram_dma_mode;
#else
cam_obj->psram_mode = false;
#endif
ESP_LOGI(TAG, "PSRAM DMA mode %s", cam_obj->psram_mode ? "enabled" : "disabled");
cam_obj->frame_cnt = config->fb_count;
cam_obj->width = resolution[frame_size].width;
cam_obj->height = resolution[frame_size].height;
if(cam_obj->jpeg_mode){
#ifdef CONFIG_CAMERA_JPEG_MODE_FRAME_SIZE_AUTO
cam_obj->recv_size = cam_obj->width * cam_obj->height / 5;
#else
cam_obj->recv_size = CONFIG_CAMERA_JPEG_MODE_FRAME_SIZE;
#endif
cam_obj->fb_size = cam_obj->recv_size;
} else {
cam_obj->recv_size = cam_obj->width * cam_obj->height * cam_obj->in_bytes_per_pixel;
cam_obj->fb_size = cam_obj->width * cam_obj->height * cam_obj->fb_bytes_per_pixel;
}
ret = cam_dma_config(config);
CAM_CHECK_GOTO(ret == ESP_OK, "cam_dma_config failed", err);
size_t queue_size = cam_obj->dma_half_buffer_cnt - 1;
if (queue_size == 0) {
queue_size = 1;
}
cam_obj->event_queue = xQueueCreate(queue_size, sizeof(cam_event_t));
CAM_CHECK_GOTO(cam_obj->event_queue != NULL, "event_queue create failed", err);
size_t frame_buffer_queue_len = cam_obj->frame_cnt;
if (config->grab_mode == CAMERA_GRAB_LATEST && cam_obj->frame_cnt > 1) {
frame_buffer_queue_len = cam_obj->frame_cnt - 1;
}
cam_obj->frame_buffer_queue = xQueueCreate(frame_buffer_queue_len, sizeof(camera_fb_t*));
CAM_CHECK_GOTO(cam_obj->frame_buffer_queue != NULL, "frame_buffer_queue create failed", err);
ret = ll_cam_init_isr(cam_obj);
CAM_CHECK_GOTO(ret == ESP_OK, "cam intr alloc failed", err);
#if CONFIG_CAMERA_CORE0
xTaskCreatePinnedToCore(cam_task, "cam_task", CAM_TASK_STACK, NULL, configMAX_PRIORITIES - 2, &cam_obj->task_handle, 0);
#elif CONFIG_CAMERA_CORE1
xTaskCreatePinnedToCore(cam_task, "cam_task", CAM_TASK_STACK, NULL, configMAX_PRIORITIES - 2, &cam_obj->task_handle, 1);
#else
xTaskCreate(cam_task, "cam_task", CAM_TASK_STACK, NULL, configMAX_PRIORITIES - 2, &cam_obj->task_handle);
#endif
ESP_LOGI(TAG, "cam config ok");
return ESP_OK;
err:
cam_deinit();
return ESP_FAIL;
}
esp_err_t cam_deinit(void)
{
if (!cam_obj) {
return ESP_FAIL;
}
cam_stop();
if (cam_obj->task_handle) {
vTaskDelete(cam_obj->task_handle);
}
if (cam_obj->event_queue) {
vQueueDelete(cam_obj->event_queue);
}
if (cam_obj->frame_buffer_queue) {
vQueueDelete(cam_obj->frame_buffer_queue);
}
ll_cam_deinit(cam_obj);
if (cam_obj->dma) {
free(cam_obj->dma);
}
if (cam_obj->dma_buffer) {
free(cam_obj->dma_buffer);
}
if (cam_obj->frames) {
for (int x = 0; x < cam_obj->frame_cnt; x++) {
free(cam_obj->frames[x].fb.buf - cam_obj->frames[x].fb_offset);
if (cam_obj->frames[x].dma) {
free(cam_obj->frames[x].dma);
}
}
free(cam_obj->frames);
}
free(cam_obj);
cam_obj = NULL;
return ESP_OK;
}
void cam_stop(void)
{
ll_cam_vsync_intr_enable(cam_obj, false);
ll_cam_stop(cam_obj);
}
void cam_start(void)
{
ll_cam_vsync_intr_enable(cam_obj, true);
}
camera_fb_t *cam_take(TickType_t timeout)
{
camera_fb_t *dma_buffer = NULL;
const TickType_t start = xTaskGetTickCount();
#if CONFIG_IDF_TARGET_ESP32S3
uint16_t dma_reset_counter = 0;
static const uint8_t MAX_GDMA_RESETS = 3;
#else
/* throttle repeated NULL frame warnings */
static uint16_t warn_null_cnt = 0;
#endif
/* throttle repeated NO-EOI warnings */
static uint16_t warn_eoi_miss_cnt = 0;
for (;;)
{
TickType_t elapsed = xTaskGetTickCount() - start; /* TickType_t is unsigned so rollover is safe */
if (elapsed >= timeout) {
ESP_LOGW(TAG, "Failed to get frame: timeout");
return NULL;
}
TickType_t remaining = timeout - elapsed;
if (xQueueReceive(cam_obj->frame_buffer_queue, (void *)&dma_buffer, remaining) == pdFALSE) {
continue;
}
if (!dma_buffer) {
/* Work-around for ESP32-S3 GDMA freeze when Wi-Fi STA starts.
* See esp32-camera commit 984999f (issue #620). */
#if CONFIG_IDF_TARGET_ESP32S3
if (dma_reset_counter < MAX_GDMA_RESETS) {
ll_cam_dma_reset(cam_obj);
dma_reset_counter++;
continue; /* retry with queue timeout */
}
if (dma_reset_counter == MAX_GDMA_RESETS) {
ESP_CAMERA_ETS_PRINTF(DRAM_STR("cam_hal: Giving up GDMA reset after %u tries\r\n"),
(unsigned) dma_reset_counter);
dma_reset_counter++; /* suppress further logs */
}
#else
/* Early warning for misbehaving sensors on other chips */
CAM_WARN_THROTTLE(warn_null_cnt,
"Unexpected NULL frame on " CONFIG_IDF_TARGET);
#endif
vTaskDelay(1); /* immediate yield once resets are done */
continue; /* go to top of loop */
}
if (cam_obj->jpeg_mode) {
/* find the end marker for JPEG. Data after that can be discarded */
int offset_e = -1;
if (cam_obj->psram_mode) {
/* Search forward from (JPEG_EOI_MARKER_LEN - 1) bytes before the final
* DMA block. We prefer forward search to pick the earliest EOI in the
* last DMA node, avoiding stale markers from a larger prior frame. */
size_t probe_len = eoi_probe_window(cam_obj->dma_node_buffer_size,
dma_buffer->len);
if (probe_len < JPEG_EOI_MARKER_LEN) {
goto skip_eoi_check;
}
uint8_t *probe_start = dma_buffer->buf + dma_buffer->len - probe_len;
cam_drop_psram_cache(probe_start, probe_len);
int off = cam_verify_jpeg_eoi(probe_start, probe_len, true);
if (off >= 0) {
offset_e = dma_buffer->len - probe_len + off;
}
} else {
offset_e = cam_verify_jpeg_eoi(dma_buffer->buf, dma_buffer->len, false);
}
if (offset_e >= 0) {
dma_buffer->len = offset_e + JPEG_EOI_MARKER_LEN;
if (cam_obj->psram_mode) {
/* DMA may bypass cache, ensure full frame is visible */
cam_drop_psram_cache(dma_buffer->buf, dma_buffer->len);
}
return dma_buffer;
}
skip_eoi_check:
CAM_WARN_THROTTLE(warn_eoi_miss_cnt,
"NO-EOI - JPEG end marker missing");
cam_give(dma_buffer);
continue; /* wait for another frame */
} else if (cam_obj->psram_mode &&
cam_obj->in_bytes_per_pixel != cam_obj->fb_bytes_per_pixel) {
/* currently used only for YUV to GRAYSCALE */
dma_buffer->len = ll_cam_memcpy(cam_obj, dma_buffer->buf, dma_buffer->buf, dma_buffer->len);
}
if (cam_obj->psram_mode) {
/* DMA may bypass cache, ensure full frame is visible to the app */
cam_drop_psram_cache(dma_buffer->buf, dma_buffer->len);
}
return dma_buffer;
}
}
void cam_give(camera_fb_t *dma_buffer)
{
for (int x = 0; x < cam_obj->frame_cnt; x++) {
if (&cam_obj->frames[x].fb == dma_buffer) {
cam_obj->frames[x].en = 1;
break;
}
}
}
void cam_give_all(void) {
for (int x = 0; x < cam_obj->frame_cnt; x++) {
cam_obj->frames[x].en = 1;
}
}
bool cam_get_available_frames(void)
{
return 0 < uxQueueMessagesWaiting(cam_obj->frame_buffer_queue);
}
void cam_set_psram_mode(bool enable)
{
portENTER_CRITICAL(&g_psram_dma_lock);
g_psram_dma_mode = enable;
portEXIT_CRITICAL(&g_psram_dma_lock);
}
bool cam_get_psram_mode(void)
{
return g_psram_dma_mode;
}
+550
View File
@@ -0,0 +1,550 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "time.h"
#include "sys/time.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "esp_system.h"
#include "nvs_flash.h"
#include "nvs.h"
#include "sensor.h"
#include "sccb.h"
#include "cam_hal.h"
#include "esp_camera.h"
#include "xclk.h"
#if CONFIG_OV2640_SUPPORT
#include "ov2640.h"
#endif
#if CONFIG_OV7725_SUPPORT
#include "ov7725.h"
#endif
#if CONFIG_OV3660_SUPPORT
#include "ov3660.h"
#endif
#if CONFIG_OV5640_SUPPORT
#include "ov5640.h"
#endif
#if CONFIG_NT99141_SUPPORT
#include "nt99141.h"
#endif
#if CONFIG_OV7670_SUPPORT
#include "ov7670.h"
#endif
#if CONFIG_GC2145_SUPPORT
#include "gc2145.h"
#endif
#if CONFIG_GC032A_SUPPORT
#include "gc032a.h"
#endif
#if CONFIG_GC0308_SUPPORT
#include "gc0308.h"
#endif
#if CONFIG_BF3005_SUPPORT
#include "bf3005.h"
#endif
#if CONFIG_BF20A6_SUPPORT
#include "bf20a6.h"
#endif
#if CONFIG_SC101IOT_SUPPORT
#include "sc101iot.h"
#endif
#if CONFIG_SC030IOT_SUPPORT
#include "sc030iot.h"
#endif
#if CONFIG_SC031GS_SUPPORT
#include "sc031gs.h"
#endif
#if CONFIG_MEGA_CCM_SUPPORT
#include "mega_ccm.h"
#endif
#if CONFIG_HM1055_SUPPORT
#include "hm1055.h"
#endif
#if CONFIG_HM0360_SUPPORT
#include "hm0360.h"
#endif
#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG)
#include "esp32-hal-log.h"
#define TAG ""
#else
#include "esp_log.h"
static const char *TAG = "camera";
#endif
typedef struct {
sensor_t sensor;
camera_fb_t fb;
} camera_state_t;
static const char *CAMERA_SENSOR_NVS_KEY = "sensor";
static const char *CAMERA_PIXFORMAT_NVS_KEY = "pixformat";
static camera_state_t *s_state = NULL;
static camera_config_t s_saved_config;
#if CONFIG_IDF_TARGET_ESP32S3 // LCD_CAM module of ESP32-S3 will generate xclk
#define CAMERA_ENABLE_OUT_CLOCK(v)
#define CAMERA_DISABLE_OUT_CLOCK()
#else
#define CAMERA_ENABLE_OUT_CLOCK(v) camera_enable_out_clock((v))
#define CAMERA_DISABLE_OUT_CLOCK() camera_disable_out_clock()
#endif
typedef struct {
int (*detect)(int slv_addr, sensor_id_t *id);
int (*init)(sensor_t *sensor);
} sensor_func_t;
static const sensor_func_t g_sensors[] = {
#if CONFIG_OV7725_SUPPORT
{esp32_camera_ov7725_detect, esp32_camera_ov7725_init},
#endif
#if CONFIG_OV7670_SUPPORT
{esp32_camera_ov7670_detect, esp32_camera_ov7670_init},
#endif
#if CONFIG_OV2640_SUPPORT
{esp32_camera_ov2640_detect, esp32_camera_ov2640_init},
#endif
#if CONFIG_OV3660_SUPPORT
{esp32_camera_ov3660_detect, esp32_camera_ov3660_init},
#endif
#if CONFIG_OV5640_SUPPORT
{esp32_camera_ov5640_detect, esp32_camera_ov5640_init},
#endif
#if CONFIG_NT99141_SUPPORT
{esp32_camera_nt99141_detect, esp32_camera_nt99141_init},
#endif
#if CONFIG_GC2145_SUPPORT
{esp32_camera_gc2145_detect, esp32_camera_gc2145_init},
#endif
#if CONFIG_GC032A_SUPPORT
{esp32_camera_gc032a_detect, esp32_camera_gc032a_init},
#endif
#if CONFIG_GC0308_SUPPORT
{esp32_camera_gc0308_detect, esp32_camera_gc0308_init},
#endif
#if CONFIG_BF3005_SUPPORT
{esp32_camera_bf3005_detect, esp32_camera_bf3005_init},
#endif
#if CONFIG_BF20A6_SUPPORT
{esp32_camera_bf20a6_detect, esp32_camera_bf20a6_init},
#endif
#if CONFIG_SC101IOT_SUPPORT
{esp32_camera_sc101iot_detect, esp32_camera_sc101iot_init},
#endif
#if CONFIG_SC030IOT_SUPPORT
{esp32_camera_sc030iot_detect, esp32_camera_sc030iot_init},
#endif
#if CONFIG_SC031GS_SUPPORT
{esp32_camera_sc031gs_detect, esp32_camera_sc031gs_init},
#endif
#if CONFIG_MEGA_CCM_SUPPORT
{esp32_camera_mega_ccm_detect, esp32_camera_mega_ccm_init},
#endif
#if CONFIG_HM1055_SUPPORT
{esp32_camera_hm1055_detect, esp32_camera_hm1055_init},
#endif
#if CONFIG_HM0360_SUPPORT
{esp32_camera_hm0360_detect, esp32_camera_hm0360_init},
#endif
};
static esp_err_t camera_probe(const camera_config_t *config, camera_model_t *out_camera_model)
{
esp_err_t ret = ESP_OK;
*out_camera_model = CAMERA_NONE;
if (s_state != NULL) {
return ESP_ERR_INVALID_STATE;
}
s_state = (camera_state_t *) calloc(1, sizeof(camera_state_t));
if (!s_state) {
return ESP_ERR_NO_MEM;
}
if (config->pin_xclk >= 0) {
ESP_LOGD(TAG, "Enabling XCLK output");
CAMERA_ENABLE_OUT_CLOCK(config);
}
if (config->pin_sccb_sda != -1) {
ESP_LOGD(TAG, "Initializing SCCB");
ret = SCCB_Init(config->pin_sccb_sda, config->pin_sccb_scl);
} else {
ESP_LOGD(TAG, "Using existing I2C port");
ret = SCCB_Use_Port(config->sccb_i2c_port);
}
if(ret != ESP_OK) {
ESP_LOGE(TAG, "sccb init err");
goto err;
}
if (config->pin_pwdn >= 0) {
ESP_LOGD(TAG, "Resetting camera by power down line");
gpio_config_t conf = { 0 };
conf.pin_bit_mask = 1LL << config->pin_pwdn;
conf.mode = GPIO_MODE_OUTPUT;
gpio_config(&conf);
// careful, logic is inverted compared to reset pin
gpio_set_level(config->pin_pwdn, 1);
vTaskDelay(10 / portTICK_PERIOD_MS);
gpio_set_level(config->pin_pwdn, 0);
vTaskDelay(10 / portTICK_PERIOD_MS);
}
if (config->pin_reset >= 0) {
ESP_LOGD(TAG, "Resetting camera");
gpio_config_t conf = { 0 };
conf.pin_bit_mask = 1LL << config->pin_reset;
conf.mode = GPIO_MODE_OUTPUT;
gpio_config(&conf);
gpio_set_level(config->pin_reset, 0);
vTaskDelay(10 / portTICK_PERIOD_MS);
gpio_set_level(config->pin_reset, 1);
vTaskDelay(10 / portTICK_PERIOD_MS);
}
ESP_LOGD(TAG, "Searching for camera address");
vTaskDelay(10 / portTICK_PERIOD_MS);
int camera_model_id;
uint8_t slv_addr = 0x0;
/**
* This loop probes each known sensor until a supported camera is detected
*/
for(camera_model_id = 0; *out_camera_model == CAMERA_NONE && camera_model_id < CAMERA_MODEL_MAX ; camera_model_id++) {
slv_addr = camera_sensor[camera_model_id].sccb_addr;
if (ESP_OK != SCCB_Probe(slv_addr)) {
continue;
}
s_state->sensor.slv_addr = slv_addr;
s_state->sensor.xclk_freq_hz = config->xclk_freq_hz;
/**
* Read sensor ID and then initialize sensor
* Attention: Some sensors have the same SCCB address. Therefore, several attempts may be made in the detection process
*/
sensor_id_t *id = &s_state->sensor.id;
for (size_t i = 0; i < sizeof(g_sensors) / sizeof(sensor_func_t); i++) {
if (g_sensors[i].detect(slv_addr, id)) {
ESP_LOGI(TAG, "Camera PID=0x%02x VER=0x%02x MIDL=0x%02x MIDH=0x%02x",
id->PID, id->VER, id->MIDH, id->MIDL);
camera_sensor_info_t *info = esp_camera_sensor_get_info(id);
if (NULL != info) {
*out_camera_model = info->model;
ESP_LOGI(TAG, "Detected %s camera", info->name);
g_sensors[i].init(&s_state->sensor);
break;
}
}
}
}
if (CAMERA_NONE == *out_camera_model) { //If no supported sensors are detected
ESP_LOGE(TAG, "Detected camera not supported.");
ret = ESP_ERR_NOT_SUPPORTED;
goto err;
}
ESP_LOGI(TAG, "Detected camera at address=0x%02x", slv_addr);
ESP_LOGD(TAG, "Doing SW reset of sensor");
vTaskDelay(10 / portTICK_PERIOD_MS);
return s_state->sensor.reset(&s_state->sensor);
err :
CAMERA_DISABLE_OUT_CLOCK();
return ret;
}
#if CONFIG_CAMERA_CONVERTER_ENABLED
static pixformat_t get_output_data_format(camera_conv_mode_t conv_mode)
{
pixformat_t format = PIXFORMAT_RGB565;
switch (conv_mode) {
case YUV422_TO_YUV420:
format = PIXFORMAT_YUV420;
break;
case YUV422_TO_RGB565: // default format is RGB565
default:
break;
}
ESP_LOGD(TAG, "Convert to %d format enabled", format);
return format;
}
#endif
esp_err_t esp_camera_init(const camera_config_t *config)
{
esp_err_t err;
s_saved_config = *config;
err = cam_init(config);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Camera init failed with error 0x%x", err);
return err;
}
camera_model_t camera_model = CAMERA_NONE;
err = camera_probe(config, &camera_model);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Camera probe failed with error 0x%x(%s)", err, esp_err_to_name(err));
goto fail;
}
framesize_t frame_size = (framesize_t) config->frame_size;
pixformat_t pix_format = (pixformat_t) config->pixel_format;
if (PIXFORMAT_JPEG == pix_format && (!camera_sensor[camera_model].support_jpeg)) {
ESP_LOGE(TAG, "JPEG format is not supported on this sensor");
err = ESP_ERR_NOT_SUPPORTED;
goto fail;
}
if (frame_size > camera_sensor[camera_model].max_size) {
ESP_LOGW(TAG, "The frame size exceeds the maximum for this sensor, it will be forced to the maximum possible value");
frame_size = camera_sensor[camera_model].max_size;
}
err = cam_config(config, frame_size, s_state->sensor.id.PID);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Camera config failed with error 0x%x", err);
goto fail;
}
s_state->sensor.status.framesize = frame_size;
s_state->sensor.pixformat = pix_format;
ESP_LOGD(TAG, "Setting frame size to %dx%d", resolution[frame_size].width, resolution[frame_size].height);
if (s_state->sensor.set_framesize(&s_state->sensor, frame_size) != 0) {
ESP_LOGE(TAG, "Failed to set frame size");
err = ESP_ERR_CAMERA_FAILED_TO_SET_FRAME_SIZE;
goto fail;
}
s_state->sensor.set_pixformat(&s_state->sensor, pix_format);
#if CONFIG_CAMERA_CONVERTER_ENABLED
if(config->conv_mode) {
s_state->sensor.pixformat = get_output_data_format(config->conv_mode); // If conversion enabled, change the out data format by conversion mode
}
#endif
if (s_state->sensor.id.PID == OV2640_PID) {
s_state->sensor.set_gainceiling(&s_state->sensor, GAINCEILING_2X);
s_state->sensor.set_bpc(&s_state->sensor, false);
s_state->sensor.set_wpc(&s_state->sensor, true);
s_state->sensor.set_lenc(&s_state->sensor, true);
}
if (pix_format == PIXFORMAT_JPEG) {
s_state->sensor.set_quality(&s_state->sensor, config->jpeg_quality);
}
s_state->sensor.init_status(&s_state->sensor);
cam_start();
return ESP_OK;
fail:
esp_camera_deinit();
return err;
}
esp_err_t esp_camera_deinit()
{
esp_err_t ret = cam_deinit();
CAMERA_DISABLE_OUT_CLOCK();
if (s_state) {
SCCB_Deinit();
free(s_state);
s_state = NULL;
}
return ret;
}
#define FB_GET_TIMEOUT (4000 / portTICK_PERIOD_MS)
camera_fb_t *esp_camera_fb_get()
{
if (s_state == NULL) {
return NULL;
}
camera_fb_t *fb = cam_take(FB_GET_TIMEOUT);
//set the frame properties
if (fb) {
fb->width = resolution[s_state->sensor.status.framesize].width;
fb->height = resolution[s_state->sensor.status.framesize].height;
fb->format = s_state->sensor.pixformat;
}
return fb;
}
void esp_camera_fb_return(camera_fb_t *fb)
{
if (s_state == NULL) {
return;
}
cam_give(fb);
}
sensor_t *esp_camera_sensor_get()
{
if (s_state == NULL) {
return NULL;
}
return &s_state->sensor;
}
esp_err_t esp_camera_save_to_nvs(const char *key)
{
#if ESP_IDF_VERSION_MAJOR > 3
nvs_handle_t handle;
#else
nvs_handle handle;
#endif
esp_err_t ret = nvs_open(key, NVS_READWRITE, &handle);
if (ret == ESP_OK) {
sensor_t *s = esp_camera_sensor_get();
if (s != NULL) {
ret = nvs_set_blob(handle, CAMERA_SENSOR_NVS_KEY, &s->status, sizeof(camera_status_t));
if (ret == ESP_OK) {
uint8_t pf = s->pixformat;
ret = nvs_set_u8(handle, CAMERA_PIXFORMAT_NVS_KEY, pf);
}
return ret;
} else {
return ESP_ERR_CAMERA_NOT_DETECTED;
}
nvs_close(handle);
return ret;
} else {
return ret;
}
}
esp_err_t esp_camera_load_from_nvs(const char *key)
{
#if ESP_IDF_VERSION_MAJOR > 3
nvs_handle_t handle;
#else
nvs_handle handle;
#endif
uint8_t pf;
esp_err_t ret = nvs_open(key, NVS_READWRITE, &handle);
if (ret == ESP_OK) {
sensor_t *s = esp_camera_sensor_get();
camera_status_t st;
if (s != NULL) {
size_t size = sizeof(camera_status_t);
ret = nvs_get_blob(handle, CAMERA_SENSOR_NVS_KEY, &st, &size);
if (ret == ESP_OK) {
s->set_ae_level(s, st.ae_level);
s->set_aec2(s, st.aec2);
s->set_aec_value(s, st.aec_value);
s->set_agc_gain(s, st.agc_gain);
s->set_awb_gain(s, st.awb_gain);
s->set_bpc(s, st.bpc);
s->set_brightness(s, st.brightness);
s->set_colorbar(s, st.colorbar);
s->set_contrast(s, st.contrast);
s->set_dcw(s, st.dcw);
s->set_denoise(s, st.denoise);
s->set_exposure_ctrl(s, st.aec);
s->set_framesize(s, st.framesize);
s->set_gain_ctrl(s, st.agc);
s->set_gainceiling(s, st.gainceiling);
s->set_hmirror(s, st.hmirror);
s->set_lenc(s, st.lenc);
s->set_quality(s, st.quality);
s->set_raw_gma(s, st.raw_gma);
s->set_saturation(s, st.saturation);
s->set_sharpness(s, st.sharpness);
s->set_special_effect(s, st.special_effect);
s->set_vflip(s, st.vflip);
s->set_wb_mode(s, st.wb_mode);
s->set_whitebal(s, st.awb);
s->set_wpc(s, st.wpc);
}
ret = nvs_get_u8(handle, CAMERA_PIXFORMAT_NVS_KEY, &pf);
if (ret == ESP_OK) {
s->set_pixformat(s, pf);
}
} else {
return ESP_ERR_CAMERA_NOT_DETECTED;
}
nvs_close(handle);
return ret;
} else {
ESP_LOGW(TAG, "Error (%d) opening nvs key \"%s\"", ret, key);
return ret;
}
}
void esp_camera_return_all(void) {
if (s_state == NULL) {
return;
}
cam_give_all();
}
bool esp_camera_available_frames(void)
{
if (s_state == NULL) {
return false;
}
return cam_get_available_frames();
}
esp_err_t esp_camera_reconfigure(const camera_config_t *config)
{
if (!config) {
return ESP_ERR_INVALID_ARG;
}
if (s_state) {
esp_err_t err = esp_camera_deinit();
if (err != ESP_OK) {
return err;
}
}
s_saved_config = *config;
return esp_camera_init(&s_saved_config);
}
esp_err_t esp_camera_set_psram_mode(bool enable)
{
cam_set_psram_mode(enable);
if (!s_state) {
return ESP_ERR_INVALID_STATE;
}
return esp_camera_reconfigure(&s_saved_config);
}
bool esp_camera_get_psram_mode(void)
{
return cam_get_psram_mode();
}
@@ -0,0 +1,215 @@
#include "sdkconfig.h"
#include "esp_camera_af.h"
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_timer.h"
#ifndef CONFIG_CAMERA_AF_DEFAULT_TIMEOUT_MS
#define CONFIG_CAMERA_AF_DEFAULT_TIMEOUT_MS 2000
#endif
#if defined(CONFIG_CAMERA_AF_SUPPORT) && CONFIG_CAMERA_AF_SUPPORT
#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG)
#include "esp32-hal-log.h"
#define TAG ""
#else
#include "esp_log.h"
#if CONFIG_LOG_DEFAULT_LEVEL > 0
static const char *TAG = "camera_af";
#endif
#endif
static uint32_t s_default_timeout_ms = CONFIG_CAMERA_AF_DEFAULT_TIMEOUT_MS;
bool esp_camera_af_is_supported(const sensor_t *sensor)
{
if (!sensor || !sensor->af_is_supported) {
return false;
}
return sensor->af_is_supported((sensor_t *)sensor) != 0;
}
esp_err_t esp_camera_af_init(sensor_t *sensor, const esp_camera_af_config_t *config)
{
if (!sensor || !config) {
return ESP_ERR_INVALID_ARG;
}
if (config->timeout_ms) {
s_default_timeout_ms = config->timeout_ms;
}
if (!esp_camera_af_is_supported(sensor)) {
return ESP_ERR_NOT_SUPPORTED;
}
if (!sensor->af_init) {
return ESP_ERR_NOT_SUPPORTED;
}
int ret = sensor->af_init(sensor, s_default_timeout_ms);
if (ret < 0) {
ESP_LOGE(TAG, "AF init failed");
return ESP_FAIL;
}
return esp_camera_af_set_mode(sensor, config->mode);
}
esp_err_t esp_camera_af_set_mode(sensor_t *sensor, esp_camera_af_mode_t mode)
{
if (!sensor) {
return ESP_ERR_INVALID_ARG;
}
if (!esp_camera_af_is_supported(sensor)) {
return ESP_ERR_NOT_SUPPORTED;
}
if (!sensor->af_set_mode) {
return ESP_ERR_NOT_SUPPORTED;
}
int ret = sensor->af_set_mode(sensor, (int)mode);
return (ret == 0) ? ESP_OK : ESP_FAIL;
}
esp_err_t esp_camera_af_trigger(sensor_t *sensor)
{
if (!sensor) {
return ESP_ERR_INVALID_ARG;
}
if (!esp_camera_af_is_supported(sensor)) {
return ESP_ERR_NOT_SUPPORTED;
}
if (!sensor->af_trigger) {
return ESP_ERR_NOT_SUPPORTED;
}
int ret = sensor->af_trigger(sensor);
return (ret == 0) ? ESP_OK : ESP_FAIL;
}
esp_err_t esp_camera_af_get_status(sensor_t *sensor, esp_camera_af_status_t *out_status)
{
if (!sensor || !out_status) {
return ESP_ERR_INVALID_ARG;
}
if (!esp_camera_af_is_supported(sensor)) {
return ESP_ERR_NOT_SUPPORTED;
}
if (!sensor->af_get_status) {
return ESP_ERR_NOT_SUPPORTED;
}
memset(out_status, 0, sizeof(*out_status));
int ret = sensor->af_get_status(sensor, &out_status->raw, &out_status->focused, &out_status->busy);
return (ret == 0) ? ESP_OK : ESP_FAIL;
}
esp_err_t esp_camera_af_wait(sensor_t *sensor, uint32_t timeout_ms, esp_camera_af_status_t *out_status)
{
if (!sensor || !out_status) {
return ESP_ERR_INVALID_ARG;
}
if (!esp_camera_af_is_supported(sensor)) {
return ESP_ERR_NOT_SUPPORTED;
}
if (!timeout_ms) {
timeout_ms = s_default_timeout_ms;
}
const uint64_t start = esp_timer_get_time();
while (true) {
esp_err_t ret = esp_camera_af_get_status(sensor, out_status);
if (ret != ESP_OK) {
return ret;
}
if (!out_status->busy) {
return ESP_OK;
}
if (timeout_ms && ((esp_timer_get_time() - start) / 1000ULL) > timeout_ms) {
return ESP_ERR_TIMEOUT;
}
vTaskDelay(pdMS_TO_TICKS(5));
}
}
esp_err_t esp_camera_af_set_manual_position(sensor_t *sensor, uint16_t position)
{
if (!sensor) {
return ESP_ERR_INVALID_ARG;
}
if (!esp_camera_af_is_supported(sensor)) {
return ESP_ERR_NOT_SUPPORTED;
}
if (!sensor->af_set_manual_position) {
return ESP_ERR_NOT_SUPPORTED;
}
int ret = sensor->af_set_manual_position(sensor, position);
return (ret == 0) ? ESP_OK : ESP_FAIL;
}
#else // CONFIG_CAMERA_AF_SUPPORT
bool esp_camera_af_is_supported(const sensor_t *sensor)
{
(void)sensor;
return false;
}
esp_err_t esp_camera_af_init(sensor_t *sensor, const esp_camera_af_config_t *config)
{
(void)sensor;
(void)config;
return ESP_ERR_NOT_SUPPORTED;
}
esp_err_t esp_camera_af_set_mode(sensor_t *sensor, esp_camera_af_mode_t mode)
{
(void)sensor;
(void)mode;
return ESP_ERR_NOT_SUPPORTED;
}
esp_err_t esp_camera_af_trigger(sensor_t *sensor)
{
(void)sensor;
return ESP_ERR_NOT_SUPPORTED;
}
esp_err_t esp_camera_af_get_status(sensor_t *sensor, esp_camera_af_status_t *out_status)
{
(void)sensor;
(void)out_status;
return ESP_ERR_NOT_SUPPORTED;
}
esp_err_t esp_camera_af_wait(sensor_t *sensor, uint32_t timeout_ms, esp_camera_af_status_t *out_status)
{
(void)sensor;
(void)timeout_ms;
(void)out_status;
return ESP_ERR_NOT_SUPPORTED;
}
esp_err_t esp_camera_af_set_manual_position(sensor_t *sensor, uint16_t position)
{
(void)sensor;
(void)position;
return ESP_ERR_NOT_SUPPORTED;
}
#endif // CONFIG_CAMERA_AF_SUPPORT
@@ -0,0 +1,283 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/*
* Example Use
*
static camera_config_t camera_example_config = {
.pin_pwdn = PIN_PWDN,
.pin_reset = PIN_RESET,
.pin_xclk = PIN_XCLK,
.pin_sccb_sda = PIN_SIOD,
.pin_sccb_scl = PIN_SIOC,
.pin_d7 = PIN_D7,
.pin_d6 = PIN_D6,
.pin_d5 = PIN_D5,
.pin_d4 = PIN_D4,
.pin_d3 = PIN_D3,
.pin_d2 = PIN_D2,
.pin_d1 = PIN_D1,
.pin_d0 = PIN_D0,
.pin_vsync = PIN_VSYNC,
.pin_href = PIN_HREF,
.pin_pclk = PIN_PCLK,
.xclk_freq_hz = 20000000,
.ledc_timer = LEDC_TIMER_0,
.ledc_channel = LEDC_CHANNEL_0,
.pixel_format = PIXFORMAT_JPEG,
.frame_size = FRAMESIZE_SVGA,
.jpeg_quality = 10,
.fb_count = 2,
.grab_mode = CAMERA_GRAB_WHEN_EMPTY
};
esp_err_t camera_example_init(){
return esp_camera_init(&camera_example_config);
}
esp_err_t camera_example_capture(){
//capture a frame
camera_fb_t * fb = esp_camera_fb_get();
if (!fb) {
ESP_LOGE(TAG, "Frame buffer could not be acquired");
return ESP_FAIL;
}
//replace this with your own function
display_image(fb->width, fb->height, fb->pixformat, fb->buf, fb->len);
//return the frame buffer back to be reused
esp_camera_fb_return(fb);
return ESP_OK;
}
*/
#pragma once
#include "esp_err.h"
#include "driver/ledc.h"
#include "sensor.h"
#include "sys/time.h"
#include "sdkconfig.h"
/**
* @brief define for if chip supports camera
*/
#define ESP_CAMERA_SUPPORTED (CONFIG_IDF_TARGET_ESP32 | CONFIG_IDF_TARGET_ESP32S3 | \
CONFIG_IDF_TARGET_ESP32S2)
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Configuration structure for camera initialization
*/
typedef enum {
CAMERA_GRAB_WHEN_EMPTY, /*!< Fills buffers when they are empty. Less resources but first 'fb_count' frames might be old */
CAMERA_GRAB_LATEST /*!< Except when 1 frame buffer is used, queue will always contain the last 'fb_count' frames */
} camera_grab_mode_t;
/**
* @brief Camera frame buffer location
*/
typedef enum {
CAMERA_FB_IN_PSRAM, /*!< Frame buffer is placed in external PSRAM */
CAMERA_FB_IN_DRAM /*!< Frame buffer is placed in internal DRAM */
} camera_fb_location_t;
#if CONFIG_CAMERA_CONVERTER_ENABLED
/**
* @brief Camera RGB\YUV conversion mode
*/
typedef enum {
CONV_DISABLE,
RGB565_TO_YUV422,
YUV422_TO_RGB565,
YUV422_TO_YUV420
} camera_conv_mode_t;
#endif
/**
* @brief Configuration structure for camera initialization
*/
typedef struct {
int pin_pwdn; /*!< GPIO pin for camera power down line */
int pin_reset; /*!< GPIO pin for camera reset line */
int pin_xclk; /*!< GPIO pin for camera XCLK line */
union {
int pin_sccb_sda; /*!< GPIO pin for camera SDA line */
int pin_sscb_sda __attribute__((deprecated("please use pin_sccb_sda instead"))); /*!< GPIO pin for camera SDA line (legacy name) */
};
union {
int pin_sccb_scl; /*!< GPIO pin for camera SCL line */
int pin_sscb_scl __attribute__((deprecated("please use pin_sccb_scl instead"))); /*!< GPIO pin for camera SCL line (legacy name) */
};
int pin_d7; /*!< GPIO pin for camera D7 line */
int pin_d6; /*!< GPIO pin for camera D6 line */
int pin_d5; /*!< GPIO pin for camera D5 line */
int pin_d4; /*!< GPIO pin for camera D4 line */
int pin_d3; /*!< GPIO pin for camera D3 line */
int pin_d2; /*!< GPIO pin for camera D2 line */
int pin_d1; /*!< GPIO pin for camera D1 line */
int pin_d0; /*!< GPIO pin for camera D0 line */
int pin_vsync; /*!< GPIO pin for camera VSYNC line */
int pin_href; /*!< GPIO pin for camera HREF line */
int pin_pclk; /*!< GPIO pin for camera PCLK line */
int xclk_freq_hz; /*!< Frequency of XCLK signal, in Hz. */
ledc_timer_t ledc_timer; /*!< LEDC timer to be used for generating XCLK */
ledc_channel_t ledc_channel; /*!< LEDC channel to be used for generating XCLK */
pixformat_t pixel_format; /*!< Format of the pixel data: PIXFORMAT_ + YUV422|GRAYSCALE|RGB565|JPEG */
framesize_t frame_size; /*!< Size of the output image: FRAMESIZE_ + QVGA|CIF|VGA|SVGA|XGA|SXGA|UXGA */
int jpeg_quality; /*!< Quality of JPEG output. 0-63 lower means higher quality */
size_t fb_count; /*!< Number of frame buffers to be allocated. If more than one, then each frame will be acquired (double speed) */
camera_fb_location_t fb_location; /*!< The location where the frame buffer will be allocated */
camera_grab_mode_t grab_mode; /*!< When buffers should be filled */
#if CONFIG_CAMERA_CONVERTER_ENABLED
camera_conv_mode_t conv_mode; /*!< RGB<->YUV Conversion mode */
#endif
int sccb_i2c_port; /*!< If pin_sccb_sda is -1, use the already configured I2C bus by number */
} camera_config_t;
/**
* @brief Data structure of camera frame buffer
*/
typedef struct {
uint8_t * buf; /*!< Pointer to the pixel data */
size_t len; /*!< Length of the buffer in bytes */
size_t width; /*!< Width of the buffer in pixels */
size_t height; /*!< Height of the buffer in pixels */
pixformat_t format; /*!< Format of the pixel data */
struct timeval timestamp; /*!< Timestamp since boot of the first DMA buffer of the frame */
} camera_fb_t;
#define ESP_ERR_CAMERA_BASE 0x20000
#define ESP_ERR_CAMERA_NOT_DETECTED (ESP_ERR_CAMERA_BASE + 1)
#define ESP_ERR_CAMERA_FAILED_TO_SET_FRAME_SIZE (ESP_ERR_CAMERA_BASE + 2)
#define ESP_ERR_CAMERA_FAILED_TO_SET_OUT_FORMAT (ESP_ERR_CAMERA_BASE + 3)
#define ESP_ERR_CAMERA_NOT_SUPPORTED (ESP_ERR_CAMERA_BASE + 4)
/**
* @brief Initialize the camera driver
*
* This function detects and configures camera over I2C interface,
* allocates framebuffer and DMA buffers,
* initializes parallel I2S input, and sets up DMA descriptors.
*
* Currently this function can only be called once and there is
* no way to de-initialize this module.
*
* @param config Camera configuration parameters
*
* @return ESP_OK on success
*/
esp_err_t esp_camera_init(const camera_config_t* config);
/**
* @brief Deinitialize the camera driver
*
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_STATE if the driver hasn't been initialized yet
*/
esp_err_t esp_camera_deinit(void);
/**
* @brief Obtain pointer to a frame buffer.
*
* @return pointer to the frame buffer
*/
camera_fb_t* esp_camera_fb_get(void);
/**
* @brief Return the frame buffer to be reused again.
*
* @param fb Pointer to the frame buffer
*/
void esp_camera_fb_return(camera_fb_t * fb);
/**
* @brief Get a pointer to the image sensor control structure
*
* @return pointer to the sensor
*/
sensor_t * esp_camera_sensor_get(void);
/**
* @brief Save camera settings to non-volatile-storage (NVS)
*
* @param key A unique nvs key name for the camera settings
*/
esp_err_t esp_camera_save_to_nvs(const char *key);
/**
* @brief Load camera settings from non-volatile-storage (NVS)
*
* @param key A unique nvs key name for the camera settings
*/
esp_err_t esp_camera_load_from_nvs(const char *key);
/**
* @brief Return all frame buffers to be reused again.
*/
void esp_camera_return_all(void);
/**
* @brief Check if there are available frames to be immediately acquired
*/
bool esp_camera_available_frames(void);
/**
* @brief Enable or disable PSRAM DMA mode at runtime.
*
* @param enable True to enable PSRAM DMA mode, false to disable it.
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_STATE if the camera is not initialized
* - Propagated error from reinitialization on failure
*/
esp_err_t esp_camera_set_psram_mode(bool enable);
/**
* @brief Reinitialize the camera with a new configuration.
*
* @param config Updated camera configuration structure
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_ARG if config is NULL
* - Propagated error from deinit or init if they fail
*/
esp_err_t esp_camera_reconfigure(const camera_config_t *config);
/**
* @brief Get current PSRAM DMA mode state.
*
* @return True if PSRAM DMA is enabled, false otherwise.
*/
bool esp_camera_get_psram_mode(void);
#ifdef __cplusplus
}
#endif
#include "img_converters.h"
@@ -0,0 +1,104 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include "esp_err.h"
#include "sensor.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Auto-focus modes.
*
* - AUTO: sensor runs its internal AF routine (continuous, if supported).
* - MANUAL: AF is not running automatically; user triggers focus explicitly.
*/
typedef enum {
ESP_CAMERA_AF_MODE_AUTO = 0,
ESP_CAMERA_AF_MODE_MANUAL = 1,
} esp_camera_af_mode_t;
/**
* @brief Auto-focus configuration.
*
* Notes:
* - Some fields are sensor-dependent (e.g., manual position control).
* - For sensors without AF hardware/firmware support, APIs return ESP_ERR_NOT_SUPPORTED.
*/
typedef struct {
esp_camera_af_mode_t mode;
/**
* @brief Step size for manual stepping algorithms.
*
* Used by software search helpers and sensors that expose manual lens control.
*/
uint16_t step_size;
/**
* @brief Inclusive focus range limits for manual stepping algorithms.
*/
uint16_t range_min;
uint16_t range_max;
/**
* @brief Default timeout used by AF operations, in milliseconds.
*/
uint32_t timeout_ms;
} esp_camera_af_config_t;
/**
* @brief AF status values (sensor-specific raw values).
*/
typedef struct {
uint8_t raw;
bool focused;
bool busy;
} esp_camera_af_status_t;
/**
* @brief Return whether the attached sensor supports AF through this module.
*/
bool esp_camera_af_is_supported(const sensor_t *sensor);
/**
* @brief Initialize AF support for the attached sensor.
*/
esp_err_t esp_camera_af_init(sensor_t *sensor, const esp_camera_af_config_t *config);
/**
* @brief Change AF mode.
*/
esp_err_t esp_camera_af_set_mode(sensor_t *sensor, esp_camera_af_mode_t mode);
/**
* @brief Trigger a single autofocus cycle (if supported).
*/
esp_err_t esp_camera_af_trigger(sensor_t *sensor);
/**
* @brief Read current AF status.
*/
esp_err_t esp_camera_af_get_status(sensor_t *sensor, esp_camera_af_status_t *out_status);
/**
* @brief Wait for AF to finish or report focused.
*
* @param timeout_ms If 0, uses config timeout passed to esp_camera_af_init().
*/
esp_err_t esp_camera_af_wait(sensor_t *sensor, uint32_t timeout_ms, esp_camera_af_status_t *out_status);
/**
* @brief Set manual lens position.
*
* Some sensors (including OV5640 when using internal AF firmware only) may not expose a safe
* manual lens position register via the public driver; in those cases this returns ESP_ERR_NOT_SUPPORTED.
*/
esp_err_t esp_camera_af_set_manual_position(sensor_t *sensor, uint16_t position);
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,282 @@
/*
* This file is part of the OpenMV project.
* Copyright (c) 2013/2014 Ibrahim Abdelkader <i.abdalkader@gmail.com>
* This work is licensed under the MIT license, see the file LICENSE for details.
*
* Sensor abstraction layer.
*
*/
#ifndef __SENSOR_H__
#define __SENSOR_H__
#include <stdint.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef enum {
OV9650_PID = 0x96,
OV7725_PID = 0x77,
OV2640_PID = 0x26,
OV3660_PID = 0x3660,
OV5640_PID = 0x5640,
OV7670_PID = 0x76,
NT99141_PID = 0x1410,
GC2145_PID = 0x2145,
GC032A_PID = 0x232a,
GC0308_PID = 0x9b,
BF3005_PID = 0x30,
BF20A6_PID = 0x20a6,
SC101IOT_PID = 0xda4a,
SC030IOT_PID = 0x9a46,
SC031GS_PID = 0x0031,
MEGA_CCM_PID =0x039E,
HM1055_PID = 0x0955,
HM0360_PID = 0x0360
} camera_pid_t;
typedef enum {
CAMERA_OV7725,
CAMERA_OV2640,
CAMERA_OV3660,
CAMERA_OV5640,
CAMERA_OV7670,
CAMERA_NT99141,
CAMERA_GC2145,
CAMERA_GC032A,
CAMERA_GC0308,
CAMERA_BF3005,
CAMERA_BF20A6,
CAMERA_SC101IOT,
CAMERA_SC030IOT,
CAMERA_SC031GS,
CAMERA_MEGA_CCM,
CAMERA_HM1055,
CAMERA_HM0360,
CAMERA_MODEL_MAX,
CAMERA_NONE,
} camera_model_t;
typedef enum {
OV2640_SCCB_ADDR = 0x30,// 0x60 >> 1
OV5640_SCCB_ADDR = 0x3C,// 0x78 >> 1
OV3660_SCCB_ADDR = 0x3C,// 0x78 >> 1
OV7725_SCCB_ADDR = 0x21,// 0x42 >> 1
OV7670_SCCB_ADDR = 0x21,// 0x42 >> 1
NT99141_SCCB_ADDR = 0x2A,// 0x54 >> 1
GC2145_SCCB_ADDR = 0x3C,// 0x78 >> 1
GC032A_SCCB_ADDR = 0x21,// 0x42 >> 1
GC0308_SCCB_ADDR = 0x21,// 0x42 >> 1
BF3005_SCCB_ADDR = 0x6E,
BF20A6_SCCB_ADDR = 0x6E,
SC101IOT_SCCB_ADDR = 0x68,// 0xd0 >> 1
SC030IOT_SCCB_ADDR = 0x68,// 0xd0 >> 1
SC031GS_SCCB_ADDR = 0x30,
MEGA_CCM_SCCB_ADDR = 0x1F, // 0x3E >> 1
HM1055_SCCB_ADDR = 0x24,
HM0360_SCCB_ADDR = 0x12,
} camera_sccb_addr_t;
typedef enum {
PIXFORMAT_RGB565, // 2BPP/RGB565
PIXFORMAT_YUV422, // 2BPP/YUV422
PIXFORMAT_YUV420, // 1.5BPP/YUV420
PIXFORMAT_GRAYSCALE, // 1BPP/GRAYSCALE
PIXFORMAT_JPEG, // JPEG/COMPRESSED
PIXFORMAT_RGB888, // 3BPP/RGB888
PIXFORMAT_RAW, // RAW
PIXFORMAT_RGB444, // 3BP2P/RGB444
PIXFORMAT_RGB555, // 3BP2P/RGB555
PIXFORMAT_RAW8, // RAW 8-bit
} pixformat_t;
typedef enum {
FRAMESIZE_96X96, // 96x96
FRAMESIZE_QQVGA, // 160x120
FRAMESIZE_128X128, // 128x128
FRAMESIZE_QCIF, // 176x144
FRAMESIZE_HQVGA, // 240x176
FRAMESIZE_240X240, // 240x240
FRAMESIZE_QVGA, // 320x240
FRAMESIZE_320X320, // 320x320
FRAMESIZE_CIF, // 400x296
FRAMESIZE_HVGA, // 480x320
FRAMESIZE_VGA, // 640x480
FRAMESIZE_SVGA, // 800x600
FRAMESIZE_XGA, // 1024x768
FRAMESIZE_HD, // 1280x720
FRAMESIZE_SXGA, // 1280x1024
FRAMESIZE_UXGA, // 1600x1200
// 3MP Sensors
FRAMESIZE_FHD, // 1920x1080
FRAMESIZE_P_HD, // 720x1280
FRAMESIZE_P_3MP, // 864x1536
FRAMESIZE_QXGA, // 2048x1536
// 5MP Sensors
FRAMESIZE_QHD, // 2560x1440
FRAMESIZE_WQXGA, // 2560x1600
FRAMESIZE_P_FHD, // 1080x1920
FRAMESIZE_QSXGA, // 2560x1920
FRAMESIZE_5MP, // 2592x1944
FRAMESIZE_INVALID
} framesize_t;
typedef struct {
const camera_model_t model;
const char *name;
const camera_sccb_addr_t sccb_addr;
const camera_pid_t pid;
const framesize_t max_size;
const bool support_jpeg;
} camera_sensor_info_t;
typedef enum {
ASPECT_RATIO_4X3,
ASPECT_RATIO_3X2,
ASPECT_RATIO_16X10,
ASPECT_RATIO_5X3,
ASPECT_RATIO_16X9,
ASPECT_RATIO_21X9,
ASPECT_RATIO_5X4,
ASPECT_RATIO_1X1,
ASPECT_RATIO_9X16
} aspect_ratio_t;
typedef enum {
GAINCEILING_2X,
GAINCEILING_4X,
GAINCEILING_8X,
GAINCEILING_16X,
GAINCEILING_32X,
GAINCEILING_64X,
GAINCEILING_128X,
} gainceiling_t;
typedef struct {
uint16_t max_width;
uint16_t max_height;
uint16_t start_x;
uint16_t start_y;
uint16_t end_x;
uint16_t end_y;
uint16_t offset_x;
uint16_t offset_y;
uint16_t total_x;
uint16_t total_y;
} ratio_settings_t;
typedef struct {
const uint16_t width;
const uint16_t height;
const aspect_ratio_t aspect_ratio;
} resolution_info_t;
// Resolution table (in sensor.c)
extern const resolution_info_t resolution[];
// camera sensor table (in sensor.c)
extern const camera_sensor_info_t camera_sensor[];
typedef struct {
uint8_t MIDH;
uint8_t MIDL;
uint16_t PID;
uint8_t VER;
} sensor_id_t;
typedef struct {
framesize_t framesize;//0 - 10
bool scale;
bool binning;
uint8_t quality;//0 - 63
int8_t brightness;//-2 - 2
int8_t contrast;//-2 - 2
int8_t saturation;//-2 - 2
int8_t sharpness;//-2 - 2
uint8_t denoise;
uint8_t special_effect;//0 - 6
uint8_t wb_mode;//0 - 4
uint8_t awb;
uint8_t awb_gain;
uint8_t aec;
uint8_t aec2;
int8_t ae_level;//-2 - 2
uint16_t aec_value;//0 - 1200
uint8_t agc;
uint8_t agc_gain;//0 - 30
uint8_t gainceiling;//0 - 6
uint8_t bpc;
uint8_t wpc;
uint8_t raw_gma;
uint8_t lenc;
uint8_t hmirror;
uint8_t vflip;
uint8_t dcw;
uint8_t colorbar;
} camera_status_t;
typedef struct _sensor sensor_t;
typedef struct _sensor {
sensor_id_t id; // Sensor ID.
uint8_t slv_addr; // Sensor I2C slave address.
pixformat_t pixformat;
camera_status_t status;
int xclk_freq_hz;
// Sensor function pointers
int (*init_status) (sensor_t *sensor);
int (*reset) (sensor_t *sensor); // Reset the configuration of the sensor, and return ESP_OK if reset is successful
int (*set_pixformat) (sensor_t *sensor, pixformat_t pixformat);
int (*set_framesize) (sensor_t *sensor, framesize_t framesize);
int (*set_contrast) (sensor_t *sensor, int level);
int (*set_brightness) (sensor_t *sensor, int level);
int (*set_saturation) (sensor_t *sensor, int level);
int (*set_sharpness) (sensor_t *sensor, int level);
int (*set_denoise) (sensor_t *sensor, int level);
int (*set_gainceiling) (sensor_t *sensor, gainceiling_t gainceiling);
int (*set_quality) (sensor_t *sensor, int quality);
int (*set_colorbar) (sensor_t *sensor, int enable);
int (*set_whitebal) (sensor_t *sensor, int enable);
int (*set_gain_ctrl) (sensor_t *sensor, int enable);
int (*set_exposure_ctrl) (sensor_t *sensor, int enable);
int (*set_hmirror) (sensor_t *sensor, int enable);
int (*set_vflip) (sensor_t *sensor, int enable);
int (*set_aec2) (sensor_t *sensor, int enable);
int (*set_awb_gain) (sensor_t *sensor, int enable);
int (*set_agc_gain) (sensor_t *sensor, int gain);
int (*set_aec_value) (sensor_t *sensor, int gain);
int (*set_special_effect) (sensor_t *sensor, int effect);
int (*set_wb_mode) (sensor_t *sensor, int mode);
int (*set_ae_level) (sensor_t *sensor, int level);
int (*set_dcw) (sensor_t *sensor, int enable);
int (*set_bpc) (sensor_t *sensor, int enable);
int (*set_wpc) (sensor_t *sensor, int enable);
int (*set_raw_gma) (sensor_t *sensor, int enable);
int (*set_lenc) (sensor_t *sensor, int enable);
int (*get_reg) (sensor_t *sensor, int reg, int mask);
int (*set_reg) (sensor_t *sensor, int reg, int mask, int value);
int (*set_res_raw) (sensor_t *sensor, int startX, int startY, int endX, int endY, int offsetX, int offsetY, int totalX, int totalY, int outputX, int outputY, bool scale, bool binning);
int (*set_pll) (sensor_t *sensor, int bypass, int mul, int sys, int root, int pre, int seld5, int pclken, int pclk);
int (*set_xclk) (sensor_t *sensor, int timer, int xclk);
// Autofocus function pointers (sensor-specific implementations)
int (*af_is_supported) (sensor_t *sensor);
int (*af_init) (sensor_t *sensor, uint32_t timeout_ms);
int (*af_set_mode) (sensor_t *sensor, int mode); // 0=auto, 1=manual
int (*af_trigger) (sensor_t *sensor);
int (*af_get_status) (sensor_t *sensor, uint8_t *out_raw, bool *out_focused, bool *out_busy);
int (*af_set_manual_position) (sensor_t *sensor, uint16_t position);
} sensor_t;
camera_sensor_info_t *esp_camera_sensor_get_info(sensor_id_t *id);
#ifdef __cplusplus
}
#endif
#endif /* __SENSOR_H__ */
@@ -0,0 +1,75 @@
// Copyright 2010-2020 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include "esp_camera.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Uninitialize the lcd_cam module
*
* @param handle Provide handle pointer to release resources
*
* @return
* - ESP_OK Success
* - ESP_FAIL Uninitialize fail
*/
esp_err_t cam_deinit(void);
/**
* @brief Initialize the lcd_cam module
*
* @param config Configurations - see lcd_cam_config_t struct
*
* @return
* - ESP_OK Success
* - ESP_ERR_INVALID_ARG Parameter error
* - ESP_ERR_NO_MEM No memory to initialize lcd_cam
* - ESP_FAIL Initialize fail
*/
esp_err_t cam_init(const camera_config_t *config);
esp_err_t cam_config(const camera_config_t *config, framesize_t frame_size, uint16_t sensor_pid);
void cam_stop(void);
void cam_start(void);
camera_fb_t *cam_take(TickType_t timeout);
void cam_give(camera_fb_t *dma_buffer);
void cam_give_all(void);
bool cam_get_available_frames(void);
void cam_set_psram_mode(bool enable);
bool cam_get_psram_mode(void);
/* VSYNC polarity applied by the next `cam_init()`, and the XCLK frequency the
* driver was last given. Set the polarity before esp_camera_init() and read the
* clock back afterwards; see cam_hal.c for why the polarity is not fixed. */
void cam_set_vsync_invert(bool invert);
bool cam_get_vsync_invert(void);
void cam_set_xclk_hz(int hz);
int cam_get_xclk_hz(void);
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,22 @@
/*
* This file is part of the OpenMV project.
* Copyright (c) 2013/2014 Ibrahim Abdelkader <i.abdalkader@gmail.com>
* This work is licensed under the MIT license, see the file LICENSE for details.
*
* SCCB (I2C like) driver.
*
*/
#ifndef __SCCB_H__
#define __SCCB_H__
#include <stdint.h>
int SCCB_Init(int pin_sda, int pin_scl);
int SCCB_Use_Port(int sccb_i2c_port);
int SCCB_Deinit(void);
int SCCB_Probe(uint8_t slv_addr);
uint8_t SCCB_Read(uint8_t slv_addr, uint8_t reg);
int SCCB_Write(uint8_t slv_addr, uint8_t reg, uint8_t data);
uint8_t SCCB_Read16(uint8_t slv_addr, uint16_t reg);
int SCCB_Write16(uint8_t slv_addr, uint16_t reg, uint8_t data);
uint16_t SCCB_Read_Addr16_Val16(uint8_t slv_addr, uint16_t reg);
int SCCB_Write_Addr16_Val16(uint8_t slv_addr, uint16_t reg, uint16_t data);
#endif // __SCCB_H__
@@ -0,0 +1,10 @@
#pragma once
#include "esp_system.h"
#include "esp_camera.h"
esp_err_t xclk_timer_conf(int ledc_timer, int xclk_freq_hz);
esp_err_t camera_enable_out_clock(const camera_config_t *config);
void camera_disable_out_clock(void);
+360
View File
@@ -0,0 +1,360 @@
/*
* This file is part of the OpenMV project.
* Copyright (c) 2013/2014 Ibrahim Abdelkader <i.abdalkader@gmail.com>
* This work is licensed under the MIT license, see the file LICENSE for details.
*
* SCCB (I2C like) driver with the new esp-idf I2C API.
*
*/
#include <stdbool.h>
#include <string.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include "sccb.h"
#include "sensor.h"
#include <stdio.h>
#include "sdkconfig.h"
#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG)
#include "esp32-hal-log.h"
#else
#include "esp_log.h"
static const char *TAG = "sccb-ng";
#endif
#define LITTLETOBIG(x) ((x << 8) | (x >> 8))
#if (ESP_IDF_VERSION_MAJOR <= 5)
#include "esp_private/i2c_platform.h"
#endif
#include "driver/i2c_master.h"
#include "driver/i2c_types.h"
// support IDF 5.x
#ifndef portTICK_RATE_MS
#define portTICK_RATE_MS portTICK_PERIOD_MS
#endif
#define TIMEOUT_MS 1000 /*!< I2C timeout duration */
#define SCCB_FREQ CONFIG_SCCB_CLK_FREQ /*!< I2C master frequency */
#if CONFIG_SCCB_HARDWARE_I2C_PORT1
const int SCCB_I2C_PORT_DEFAULT = 1;
#else
const int SCCB_I2C_PORT_DEFAULT = 0;
#endif
#define MAX_DEVICES UINT8_MAX-1
/*
The legacy I2C driver used addresses to differentiate between devices, whereas the new driver uses
i2c_master_dev_handle_t structs which are registered to the bus.
To avoid re-writing all camera dependent code, we simply translate the devices address to the corresponding
device_handle. This keeps all interfaces to the drivers identical.
To perform this conversion the following local struct is used.
*/
typedef struct
{
i2c_master_dev_handle_t dev_handle;
uint16_t address;
} device_t;
static device_t devices[MAX_DEVICES];
static uint8_t device_count = 0;
static int sccb_i2c_port;
static bool sccb_owns_i2c_port;
i2c_master_dev_handle_t *get_handle_from_address(uint8_t slv_addr)
{
for (uint8_t i = 0; i < device_count; i++)
{
if (slv_addr == devices[i].address)
{
return &(devices[i].dev_handle);
}
}
ESP_LOGE(TAG, "Device with address %02x not found", slv_addr);
return NULL;
}
int SCCB_Install_Device(uint8_t slv_addr)
{
esp_err_t ret;
i2c_master_bus_handle_t bus_handle;
if (device_count >= MAX_DEVICES)
{
ESP_LOGE(TAG, "cannot add more than %d devices", MAX_DEVICES);
return ESP_FAIL;
}
ret = i2c_master_get_bus_handle(sccb_i2c_port, &bus_handle);
if (ret != ESP_OK)
{
ESP_LOGE(TAG, "failed to get SCCB I2C Bus handle for port %d", sccb_i2c_port);
return ret;
}
i2c_device_config_t dev_cfg = {
.dev_addr_length = I2C_ADDR_BIT_LEN_7,
.device_address = slv_addr, // not yet set
.scl_speed_hz = SCCB_FREQ,
};
ret = i2c_master_bus_add_device(bus_handle, &dev_cfg, &(devices[device_count].dev_handle));
if (ret != ESP_OK)
{
ESP_LOGE(TAG, "failed to install SCCB I2C device: %s", esp_err_to_name(ret));
return -1;
}
devices[device_count].address = slv_addr;
device_count++;
return 0;
}
int SCCB_Init(int pin_sda, int pin_scl)
{
ESP_LOGI(TAG, "pin_sda %d pin_scl %d", pin_sda, pin_scl);
// i2c_config_t conf;
esp_err_t ret;
sccb_i2c_port = SCCB_I2C_PORT_DEFAULT;
sccb_owns_i2c_port = true;
ESP_LOGI(TAG, "sccb_i2c_port=%d", sccb_i2c_port);
i2c_master_bus_config_t i2c_mst_config = {
.clk_source = I2C_CLK_SRC_DEFAULT,
.i2c_port = SCCB_I2C_PORT_DEFAULT,
.scl_io_num = pin_scl,
.sda_io_num = pin_sda,
.glitch_ignore_cnt = 7,
.flags.enable_internal_pullup = 1};
i2c_master_bus_handle_t bus_handle;
ret = i2c_new_master_bus(&i2c_mst_config, &bus_handle);
if (ret != ESP_OK)
{
ESP_LOGE(TAG, "failed to install SCCB I2C master bus on port %d: %s", sccb_i2c_port, esp_err_to_name(ret));
return ret;
}
return ESP_OK;
}
int SCCB_Use_Port(int i2c_num)
{ // sccb use an already initialized I2C port
if (sccb_owns_i2c_port)
{
SCCB_Deinit();
}
if (i2c_num < 0 || i2c_num > I2C_NUM_MAX)
{
return ESP_ERR_INVALID_ARG;
}
sccb_i2c_port = i2c_num;
sccb_owns_i2c_port = false; // in this case, camera doesn't own the i2c port
return ESP_OK;
}
int SCCB_Deinit(void)
{
esp_err_t ret;
for (uint8_t i = 0; i < device_count; i++)
{
ret = i2c_master_bus_rm_device(devices[i].dev_handle);
if (ret != ESP_OK)
{
ESP_LOGE(TAG, "failed to remove SCCB I2C Device");
return ret;
}
devices[i].dev_handle = NULL;
devices[i].address = 0;
}
device_count = 0;
if (!sccb_owns_i2c_port)
{
return ESP_OK;
}
sccb_owns_i2c_port = false;
i2c_master_bus_handle_t bus_handle;
ret = i2c_master_get_bus_handle(sccb_i2c_port, &bus_handle);
if (ret != ESP_OK)
{
ESP_LOGE(TAG, "failed to get SCCB I2C Bus handle for port %d", sccb_i2c_port);
return ret;
}
ret = i2c_del_master_bus(bus_handle);
if (ret != ESP_OK)
{
ESP_LOGE(TAG, "failed to get delete SCCB I2C Master Bus at port %d", sccb_i2c_port);
return ret;
}
return ESP_OK;
}
int SCCB_Probe(uint8_t slv_addr)
{
esp_err_t ret;
i2c_master_bus_handle_t bus_handle;
ret = i2c_master_get_bus_handle(sccb_i2c_port, &bus_handle);
if (ret != ESP_OK)
{
ESP_LOGE(TAG, "failed to get SCCB I2C Bus handle for port %d", sccb_i2c_port);
return ret;
}
ret = i2c_master_probe(bus_handle, slv_addr, TIMEOUT_MS);
if (ret == ESP_OK)
{
return SCCB_Install_Device(slv_addr);
}
return ret;
}
uint8_t SCCB_Read(uint8_t slv_addr, uint8_t reg)
{
i2c_master_dev_handle_t dev_handle = *(get_handle_from_address(slv_addr));
uint8_t tx_buffer[1] = { reg };
uint8_t rx_buffer[1] = { 0 };
esp_err_t ret = i2c_master_transmit_receive(dev_handle, tx_buffer, sizeof(tx_buffer),
rx_buffer, sizeof(rx_buffer), TIMEOUT_MS);
if (ret == ESP_OK)
{
return rx_buffer[0];
}
/*
* Most SCCB sensors expect an 8-bit register read to use a repeated-start
* write/read transaction. Some devices reject that sequence with the new
* IDF I2C master driver, so only fall back to a stop/start read after the
* repeated-start transaction fails.
*/
ESP_LOGD(TAG, "SCCB_Read repeated-start failed addr:0x%02x, reg:0x%02x, ret:%d; retrying",
slv_addr, reg, ret);
ret = i2c_master_transmit(dev_handle, tx_buffer, sizeof(tx_buffer), TIMEOUT_MS);
if (ret != ESP_OK)
{
ESP_LOGE(TAG, "SCCB_Read addr phase failed addr:0x%02x, reg:0x%02x, ret:%d",
slv_addr, reg, ret);
return 0;
}
ret = i2c_master_receive(dev_handle, rx_buffer, sizeof(rx_buffer), TIMEOUT_MS);
if (ret != ESP_OK)
{
ESP_LOGE(TAG, "SCCB_Read data phase failed addr:0x%02x, reg:0x%02x, ret:%d",
slv_addr, reg, ret);
return 0;
}
return rx_buffer[0];
}
int SCCB_Write(uint8_t slv_addr, uint8_t reg, uint8_t data)
{
i2c_master_dev_handle_t dev_handle = *(get_handle_from_address(slv_addr));
uint8_t tx_buffer[2];
tx_buffer[0] = reg;
tx_buffer[1] = data;
esp_err_t ret = i2c_master_transmit(dev_handle, tx_buffer, 2, TIMEOUT_MS);
if (ret != ESP_OK)
{
ESP_LOGE(TAG, "SCCB_Write Failed addr:0x%02x, reg:0x%02x, data:0x%02x, ret:%d", slv_addr, reg, data, ret);
}
return ret == ESP_OK ? 0 : -1;
}
uint8_t SCCB_Read16(uint8_t slv_addr, uint16_t reg)
{
i2c_master_dev_handle_t dev_handle = *(get_handle_from_address(slv_addr));
uint8_t rx_buffer[1];
uint16_t reg_htons = LITTLETOBIG(reg);
uint8_t *reg_u8 = (uint8_t *)&reg_htons;
esp_err_t ret = i2c_master_transmit_receive(dev_handle, reg_u8, 2, rx_buffer, 1, TIMEOUT_MS);
if (ret != ESP_OK)
{
ESP_LOGE(TAG, "W [%04x]=%02x fail\n", reg, rx_buffer[0]);
}
return rx_buffer[0];
}
int SCCB_Write16(uint8_t slv_addr, uint16_t reg, uint8_t data)
{
i2c_master_dev_handle_t dev_handle = *(get_handle_from_address(slv_addr));
uint8_t tx_buffer[3];
tx_buffer[0] = reg >> 8;
tx_buffer[1] = reg & 0x00ff;
tx_buffer[2] = data;
esp_err_t ret = i2c_master_transmit(dev_handle, tx_buffer, 3, TIMEOUT_MS);
if (ret != ESP_OK)
{
ESP_LOGE(TAG, "W [%04x]=%02x fail\n", reg, data);
}
return ret == ESP_OK ? 0 : -1;
}
uint16_t SCCB_Read_Addr16_Val16(uint8_t slv_addr, uint16_t reg)
{
i2c_master_dev_handle_t dev_handle = *(get_handle_from_address(slv_addr));
uint8_t rx_buffer[2];
uint16_t reg_htons = LITTLETOBIG(reg);
uint8_t *reg_u8 = (uint8_t *)&reg_htons;
esp_err_t ret = i2c_master_transmit_receive(dev_handle, reg_u8, 2, rx_buffer, 2, TIMEOUT_MS);
uint16_t data = ((uint16_t)rx_buffer[0] << 8) | (uint16_t)rx_buffer[1];
if (ret != ESP_OK)
{
ESP_LOGE(TAG, "W [%04x]=%02x fail\n", reg, data);
}
return data;
}
int SCCB_Write_Addr16_Val16(uint8_t slv_addr, uint16_t reg, uint16_t data)
{
i2c_master_dev_handle_t dev_handle = *(get_handle_from_address(slv_addr));
uint8_t tx_buffer[4];
tx_buffer[0] = reg >> 8;
tx_buffer[1] = reg & 0x00ff;
tx_buffer[2] = data >> 8;
tx_buffer[3] = data & 0x00ff;
esp_err_t ret = i2c_master_transmit(dev_handle, tx_buffer, 4, TIMEOUT_MS);
if (ret != ESP_OK)
{
ESP_LOGE(TAG, "W [%04x]=%02x fail\n", reg, data);
}
return ret == ESP_OK ? 0 : -1;
}
+251
View File
@@ -0,0 +1,251 @@
/*
* This file is part of the OpenMV project.
* Copyright (c) 2013/2014 Ibrahim Abdelkader <i.abdalkader@gmail.com>
* This work is licensed under the MIT license, see the file LICENSE for details.
*
* SCCB (I2C like) driver.
*
*/
#include <stdbool.h>
#include <string.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include "sccb.h"
#include "sensor.h"
#include <stdio.h>
#include "sdkconfig.h"
#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG)
#include "esp32-hal-log.h"
#else
#include "esp_log.h"
static const char* TAG = "sccb";
#endif
#define LITTLETOBIG(x) ((x<<8)|(x>>8))
#include "driver/i2c.h"
// support IDF 5.x
#ifndef portTICK_RATE_MS
#define portTICK_RATE_MS portTICK_PERIOD_MS
#endif
#define SCCB_FREQ CONFIG_SCCB_CLK_FREQ /*!< I2C master frequency*/
#define WRITE_BIT I2C_MASTER_WRITE /*!< I2C master write */
#define READ_BIT I2C_MASTER_READ /*!< I2C master read */
#define ACK_CHECK_EN 0x1 /*!< I2C master will check ack from slave*/
#define ACK_CHECK_DIS 0x0 /*!< I2C master will not check ack from slave */
#define ACK_VAL 0x0 /*!< I2C ack value */
#define NACK_VAL 0x1 /*!< I2C nack value */
#if CONFIG_SCCB_HARDWARE_I2C_PORT1
const int SCCB_I2C_PORT_DEFAULT = 1;
#else
const int SCCB_I2C_PORT_DEFAULT = 0;
#endif
static int sccb_i2c_port;
static bool sccb_owns_i2c_port;
int SCCB_Init(int pin_sda, int pin_scl)
{
ESP_LOGI(TAG, "pin_sda %d pin_scl %d", pin_sda, pin_scl);
i2c_config_t conf;
esp_err_t ret;
memset(&conf, 0, sizeof(i2c_config_t));
sccb_i2c_port = SCCB_I2C_PORT_DEFAULT;
sccb_owns_i2c_port = true;
ESP_LOGI(TAG, "sccb_i2c_port=%d", sccb_i2c_port);
conf.mode = I2C_MODE_MASTER;
conf.sda_io_num = pin_sda;
conf.sda_pullup_en = GPIO_PULLUP_ENABLE;
conf.scl_io_num = pin_scl;
conf.scl_pullup_en = GPIO_PULLUP_ENABLE;
conf.master.clk_speed = SCCB_FREQ;
if ((ret = i2c_param_config(sccb_i2c_port, &conf)) != ESP_OK) {
return ret;
}
return i2c_driver_install(sccb_i2c_port, conf.mode, 0, 0, 0);
}
int SCCB_Use_Port(int i2c_num) { // sccb use an already initialized I2C port
if (sccb_owns_i2c_port) {
SCCB_Deinit();
}
if (i2c_num < 0 || i2c_num > I2C_NUM_MAX) {
return ESP_ERR_INVALID_ARG;
}
sccb_i2c_port = i2c_num;
sccb_owns_i2c_port = false; // in this case, camera doesn't own the i2c port
return ESP_OK;
}
int SCCB_Deinit(void)
{
if (!sccb_owns_i2c_port) {
return ESP_OK;
}
sccb_owns_i2c_port = false;
return i2c_driver_delete(sccb_i2c_port);
}
int SCCB_Probe(uint8_t slv_addr)
{
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, ( slv_addr << 1 ) | WRITE_BIT, ACK_CHECK_EN);
i2c_master_stop(cmd);
esp_err_t ret = i2c_master_cmd_begin(sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS);
i2c_cmd_link_delete(cmd);
return ret;
}
uint8_t SCCB_Read(uint8_t slv_addr, uint8_t reg)
{
uint8_t data=0;
esp_err_t ret = ESP_FAIL;
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, ( slv_addr << 1 ) | WRITE_BIT, ACK_CHECK_EN);
i2c_master_write_byte(cmd, reg, ACK_CHECK_EN);
i2c_master_stop(cmd);
ret = i2c_master_cmd_begin(sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS);
i2c_cmd_link_delete(cmd);
if(ret != ESP_OK) return -1;
cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, ( slv_addr << 1 ) | READ_BIT, ACK_CHECK_EN);
i2c_master_read_byte(cmd, &data, NACK_VAL);
i2c_master_stop(cmd);
ret = i2c_master_cmd_begin(sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS);
i2c_cmd_link_delete(cmd);
if(ret != ESP_OK) {
ESP_LOGE(TAG, "SCCB_Read Failed addr:0x%02x, reg:0x%02x, data:0x%02x, ret:%d", slv_addr, reg, data, ret);
}
return data;
}
int SCCB_Write(uint8_t slv_addr, uint8_t reg, uint8_t data)
{
esp_err_t ret = ESP_FAIL;
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, ( slv_addr << 1 ) | WRITE_BIT, ACK_CHECK_EN);
i2c_master_write_byte(cmd, reg, ACK_CHECK_EN);
i2c_master_write_byte(cmd, data, ACK_CHECK_EN);
i2c_master_stop(cmd);
ret = i2c_master_cmd_begin(sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS);
i2c_cmd_link_delete(cmd);
if(ret != ESP_OK) {
ESP_LOGE(TAG, "SCCB_Write Failed addr:0x%02x, reg:0x%02x, data:0x%02x, ret:%d", slv_addr, reg, data, ret);
}
return ret == ESP_OK ? 0 : -1;
}
uint8_t SCCB_Read16(uint8_t slv_addr, uint16_t reg)
{
uint8_t data=0;
esp_err_t ret = ESP_FAIL;
uint16_t reg_htons = LITTLETOBIG(reg);
uint8_t *reg_u8 = (uint8_t *)&reg_htons;
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, ( slv_addr << 1 ) | WRITE_BIT, ACK_CHECK_EN);
i2c_master_write_byte(cmd, reg_u8[0], ACK_CHECK_EN);
i2c_master_write_byte(cmd, reg_u8[1], ACK_CHECK_EN);
i2c_master_stop(cmd);
ret = i2c_master_cmd_begin(sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS);
i2c_cmd_link_delete(cmd);
if(ret != ESP_OK) return -1;
cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, ( slv_addr << 1 ) | READ_BIT, ACK_CHECK_EN);
i2c_master_read_byte(cmd, &data, NACK_VAL);
i2c_master_stop(cmd);
ret = i2c_master_cmd_begin(sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS);
i2c_cmd_link_delete(cmd);
if(ret != ESP_OK) {
ESP_LOGE(TAG, "W [%04x]=%02x fail\n", reg, data);
}
return data;
}
int SCCB_Write16(uint8_t slv_addr, uint16_t reg, uint8_t data)
{
static uint16_t i = 0;
esp_err_t ret = ESP_FAIL;
uint16_t reg_htons = LITTLETOBIG(reg);
uint8_t *reg_u8 = (uint8_t *)&reg_htons;
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, ( slv_addr << 1 ) | WRITE_BIT, ACK_CHECK_EN);
i2c_master_write_byte(cmd, reg_u8[0], ACK_CHECK_EN);
i2c_master_write_byte(cmd, reg_u8[1], ACK_CHECK_EN);
i2c_master_write_byte(cmd, data, ACK_CHECK_EN);
i2c_master_stop(cmd);
ret = i2c_master_cmd_begin(sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS);
i2c_cmd_link_delete(cmd);
if(ret != ESP_OK) {
ESP_LOGE(TAG, "W [%04x]=%02x %d fail\n", reg, data, i++);
}
return ret == ESP_OK ? 0 : -1;
}
uint16_t SCCB_Read_Addr16_Val16(uint8_t slv_addr, uint16_t reg)
{
uint16_t data = 0;
uint8_t *data_u8 = (uint8_t *)&data;
esp_err_t ret = ESP_FAIL;
uint16_t reg_htons = LITTLETOBIG(reg);
uint8_t *reg_u8 = (uint8_t *)&reg_htons;
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, ( slv_addr << 1 ) | WRITE_BIT, ACK_CHECK_EN);
i2c_master_write_byte(cmd, reg_u8[0], ACK_CHECK_EN);
i2c_master_write_byte(cmd, reg_u8[1], ACK_CHECK_EN);
i2c_master_stop(cmd);
ret = i2c_master_cmd_begin(sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS);
i2c_cmd_link_delete(cmd);
if(ret != ESP_OK) return -1;
cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, ( slv_addr << 1 ) | READ_BIT, ACK_CHECK_EN);
i2c_master_read_byte(cmd, &data_u8[1], ACK_VAL);
i2c_master_read_byte(cmd, &data_u8[0], NACK_VAL);
i2c_master_stop(cmd);
ret = i2c_master_cmd_begin(sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS);
i2c_cmd_link_delete(cmd);
if(ret != ESP_OK) {
ESP_LOGE(TAG, "W [%04x]=%04x fail\n", reg, data);
}
return data;
}
int SCCB_Write_Addr16_Val16(uint8_t slv_addr, uint16_t reg, uint16_t data)
{
esp_err_t ret = ESP_FAIL;
uint16_t reg_htons = LITTLETOBIG(reg);
uint8_t *reg_u8 = (uint8_t *)&reg_htons;
uint16_t data_htons = LITTLETOBIG(data);
uint8_t *data_u8 = (uint8_t *)&data_htons;
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, ( slv_addr << 1 ) | WRITE_BIT, ACK_CHECK_EN);
i2c_master_write_byte(cmd, reg_u8[0], ACK_CHECK_EN);
i2c_master_write_byte(cmd, reg_u8[1], ACK_CHECK_EN);
i2c_master_write_byte(cmd, data_u8[0], ACK_CHECK_EN);
i2c_master_write_byte(cmd, data_u8[1], ACK_CHECK_EN);
i2c_master_stop(cmd);
ret = i2c_master_cmd_begin(sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS);
i2c_cmd_link_delete(cmd);
if(ret != ESP_OK) {
ESP_LOGE(TAG, "W [%04x]=%04x fail\n", reg, data);
}
return ret == ESP_OK ? 0 : -1;
}
+63
View File
@@ -0,0 +1,63 @@
#include <stdio.h>
#include "sensor.h"
const camera_sensor_info_t camera_sensor[CAMERA_MODEL_MAX] = {
// The sequence must be consistent with camera_model_t
{CAMERA_OV7725, "OV7725", OV7725_SCCB_ADDR, OV7725_PID, FRAMESIZE_VGA, false},
{CAMERA_OV2640, "OV2640", OV2640_SCCB_ADDR, OV2640_PID, FRAMESIZE_UXGA, true},
{CAMERA_OV3660, "OV3660", OV3660_SCCB_ADDR, OV3660_PID, FRAMESIZE_QXGA, true},
{CAMERA_OV5640, "OV5640", OV5640_SCCB_ADDR, OV5640_PID, FRAMESIZE_QSXGA, true},
{CAMERA_OV7670, "OV7670", OV7670_SCCB_ADDR, OV7670_PID, FRAMESIZE_VGA, false},
{CAMERA_NT99141, "NT99141", NT99141_SCCB_ADDR, NT99141_PID, FRAMESIZE_HD, true},
{CAMERA_GC2145, "GC2145", GC2145_SCCB_ADDR, GC2145_PID, FRAMESIZE_UXGA, false},
{CAMERA_GC032A, "GC032A", GC032A_SCCB_ADDR, GC032A_PID, FRAMESIZE_VGA, false},
{CAMERA_GC0308, "GC0308", GC0308_SCCB_ADDR, GC0308_PID, FRAMESIZE_VGA, false},
{CAMERA_BF3005, "BF3005", BF3005_SCCB_ADDR, BF3005_PID, FRAMESIZE_VGA, false},
{CAMERA_BF20A6, "BF20A6", BF20A6_SCCB_ADDR, BF20A6_PID, FRAMESIZE_VGA, false},
{CAMERA_SC101IOT, "SC101IOT", SC101IOT_SCCB_ADDR, SC101IOT_PID, FRAMESIZE_HD, false},
{CAMERA_SC030IOT, "SC030IOT", SC030IOT_SCCB_ADDR, SC030IOT_PID, FRAMESIZE_VGA, false},
{CAMERA_SC031GS, "SC031GS", SC031GS_SCCB_ADDR, SC031GS_PID, FRAMESIZE_VGA, false},
{CAMERA_MEGA_CCM, "MEGA_CCM", MEGA_CCM_SCCB_ADDR, MEGA_CCM_PID, FRAMESIZE_5MP, true},
{CAMERA_HM1055, "HM1055", HM1055_SCCB_ADDR, HM1055_PID, FRAMESIZE_HD, false},
{CAMERA_HM0360, "HM0360", HM0360_SCCB_ADDR, HM0360_PID, FRAMESIZE_VGA, false},
};
const resolution_info_t resolution[FRAMESIZE_INVALID] = {
{ 96, 96, ASPECT_RATIO_1X1 }, /* 96x96 */
{ 160, 120, ASPECT_RATIO_4X3 }, /* QQVGA */
{ 128, 128, ASPECT_RATIO_1X1 }, /* 128x128 */
{ 176, 144, ASPECT_RATIO_5X4 }, /* QCIF */
{ 240, 176, ASPECT_RATIO_4X3 }, /* HQVGA */
{ 240, 240, ASPECT_RATIO_1X1 }, /* 240x240 */
{ 320, 240, ASPECT_RATIO_4X3 }, /* QVGA */
{ 320, 320, ASPECT_RATIO_1X1 }, /* 320x320 */
{ 400, 296, ASPECT_RATIO_4X3 }, /* CIF */
{ 480, 320, ASPECT_RATIO_3X2 }, /* HVGA */
{ 640, 480, ASPECT_RATIO_4X3 }, /* VGA */
{ 800, 600, ASPECT_RATIO_4X3 }, /* SVGA */
{ 1024, 768, ASPECT_RATIO_4X3 }, /* XGA */
{ 1280, 720, ASPECT_RATIO_16X9 }, /* HD */
{ 1280, 1024, ASPECT_RATIO_5X4 }, /* SXGA */
{ 1600, 1200, ASPECT_RATIO_4X3 }, /* UXGA */
// 3MP Sensors
{ 1920, 1080, ASPECT_RATIO_16X9 }, /* FHD */
{ 720, 1280, ASPECT_RATIO_9X16 }, /* Portrait HD */
{ 864, 1536, ASPECT_RATIO_9X16 }, /* Portrait 3MP */
{ 2048, 1536, ASPECT_RATIO_4X3 }, /* QXGA */
// 5MP Sensors
{ 2560, 1440, ASPECT_RATIO_16X9 }, /* QHD */
{ 2560, 1600, ASPECT_RATIO_16X10 }, /* WQXGA */
{ 1088, 1920, ASPECT_RATIO_9X16 }, /* Portrait FHD */
{ 2560, 1920, ASPECT_RATIO_4X3 }, /* QSXGA */
{ 2592, 1944, ASPECT_RATIO_4X3 }, /* 5MP */
};
camera_sensor_info_t *esp_camera_sensor_get_info(sensor_id_t *id)
{
for (int i = 0; i < CAMERA_MODEL_MAX; i++) {
if (id->PID == camera_sensor[i].pid) {
return (camera_sensor_info_t *)&camera_sensor[i];
}
}
return NULL;
}
+15
View File
@@ -0,0 +1,15 @@
dependencies:
esp_jpeg:
public: true
version: ^1.3.1
idf: '>=5.1'
description: ESP32 compatible driver for OV2640, OV3660, OV5640, OV7670 and OV7725
image sensors.
documentation: https://github.com/espressif/esp32-camera/tree/main/README.md
issues: https://github.com/espressif/esp32-camera/issues
repository: git://github.com/espressif/esp32-camera.git
repository_info:
commit_sha: 202df95d7b1dc72e9303ad78f47b8dc9f339e6a1
path: .
url: https://github.com/espressif/esp32-camera
version: 2.1.7
+26
View File
@@ -0,0 +1,26 @@
{
"name": "esp32-camera",
"version": "2.0.0",
"keywords": "esp32, camera, espressif, esp32-cam",
"description": "ESP32 compatible driver for OV2640, OV3660, OV5640, OV7670 and OV7725 image sensors.",
"repository": {
"type": "git",
"url": "https://github.com/espressif/esp32-camera"
},
"frameworks": "espidf",
"platforms": "*",
"build": {
"flags": [
"-Idriver/include",
"-Iconversions/include",
"-Idriver/private_include",
"-Iconversions/private_include",
"-Isensors/private_include",
"-Itarget/private_include",
"-fno-rtti"
],
"includeDir": ".",
"srcDir": ".",
"srcFilter": ["-<*>", "+<driver>", "+<conversions>", "+<sensors>"]
}
}
+418
View File
@@ -0,0 +1,418 @@
// Copyright 2015-2021 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "sccb.h"
#include "bf20a6.h"
#include "bf20a6_regs.h"
#include "bf20a6_settings.h"
#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG)
#include "esp32-hal-log.h"
#else
#include "esp_log.h"
static const char *TAG = "bf20a6";
#endif
#define H8(v) ((v)>>8)
#define L8(v) ((v)&0xff)
//#define REG_DEBUG_ON
static int read_reg(uint8_t slv_addr, const uint16_t reg)
{
int ret = SCCB_Read(slv_addr, reg);
// ESP_LOGI(TAG, "READ Register 0x%02x VALUE: 0x%02x", reg, ret);
#ifdef REG_DEBUG_ON
if (ret < 0) {
ESP_LOGE(TAG, "READ REG 0x%04x FAILED: %d", reg, ret);
}
#endif
return ret;
}
static int write_reg(uint8_t slv_addr, const uint16_t reg, uint8_t value)
{
int ret = SCCB_Write(slv_addr, reg, value);
#ifdef REG_DEBUG_ON
if (ret < 0) {
ESP_LOGE(TAG, "WRITE REG 0x%04x FAILED: %d", reg, ret);
}
#endif
return ret;
}
#ifdef DEBUG_PRINT_REG
static int check_reg_mask(uint8_t slv_addr, uint16_t reg, uint8_t mask)
{
return (read_reg(slv_addr, reg) & mask) == mask;
}
static void print_regs(uint8_t slv_addr)
{
vTaskDelay(pdMS_TO_TICKS(100));
ESP_LOGI(TAG, "REG list look ======================");
for (size_t i = 0xf0; i <= 0xfe; i++) {
ESP_LOGI(TAG, "reg[0x%02x] = 0x%02x", i, read_reg(slv_addr, i));
}
ESP_LOGI(TAG, "\npage 0 ===");
write_reg(slv_addr, 0xfe, 0x00); // page 0
for (size_t i = 0x03; i <= 0x24; i++) {
ESP_LOGI(TAG, "p0 reg[0x%02x] = 0x%02x", i, read_reg(slv_addr, i));
}
for (size_t i = 0x40; i <= 0x95; i++) {
ESP_LOGI(TAG, "p0 reg[0x%02x] = 0x%02x", i, read_reg(slv_addr, i));
}
ESP_LOGI(TAG, "\npage 3 ===");
write_reg(slv_addr, 0xfe, 0x03); // page 3
for (size_t i = 0x01; i <= 0x43; i++) {
ESP_LOGI(TAG, "p3 reg[0x%02x] = 0x%02x", i, read_reg(slv_addr, i));
}
}
static int read_regs(uint8_t slv_addr, const uint16_t(*regs)[2])
{
int i = 0, ret = 0;
while (regs[i][0] != REGLIST_TAIL) {
if (regs[i][0] == REG_DLY) {
vTaskDelay(regs[i][1] / portTICK_PERIOD_MS);
} else {
ret = read_reg(slv_addr, regs[i][0]);
}
i++;
}
return ret;
}
#endif
static int set_reg_bits(sensor_t *sensor, uint8_t reg, uint8_t offset, uint8_t length, uint8_t value)
{
int ret = 0;
ret = SCCB_Read(sensor->slv_addr, reg);
if (ret < 0) {
return ret;
}
uint8_t mask = ((1 << length) - 1) << offset;
value = (ret & ~mask) | ((value << offset) & mask);
ret = SCCB_Write(sensor->slv_addr, reg & 0xFF, value);
return ret;
}
static int write_regs(uint8_t slv_addr, const uint16_t(*regs)[2])
{
int i = 0, ret = 0;
while (!ret && regs[i][0] != REGLIST_TAIL) {
if (regs[i][0] == REG_DLY) {
vTaskDelay(regs[i][1] / portTICK_PERIOD_MS);
} else {
ret = write_reg(slv_addr, regs[i][0], regs[i][1]);
}
i++;
}
return ret;
}
static int reset(sensor_t *sensor)
{
int ret;
// Software Reset: clear all registers and reset them to their default values
ret = write_reg(sensor->slv_addr, RESET_RELATED, 0x01);
if (ret) {
ESP_LOGE(TAG, "Software Reset FAILED!");
return ret;
}
vTaskDelay(100 / portTICK_PERIOD_MS);
ret = write_regs(sensor->slv_addr, bf20a6_default_init_regs);
if (ret == 0) {
ESP_LOGD(TAG, "Camera defaults loaded");
vTaskDelay(100 / portTICK_PERIOD_MS);
}
// int test_value = read_regs(sensor->slv_addr, bf20a6_default_init_regs);
return ret;
}
static int set_pixformat(sensor_t *sensor, pixformat_t pixformat)
{
int ret = 0;
switch (pixformat) {
case PIXFORMAT_YUV422:
set_reg_bits(sensor, 0x12, 0, 1, 0);
break;
case PIXFORMAT_RAW:
set_reg_bits(sensor, 0x12, 0, 1, 0x1);
break;
case PIXFORMAT_GRAYSCALE:
write_reg(sensor->slv_addr, 0x12, 0x23);
write_reg(sensor->slv_addr, 0x3a, 0x00);
write_reg(sensor->slv_addr, 0xe1, 0x92);
write_reg(sensor->slv_addr, 0xe3, 0x02);
break;
default:
ESP_LOGW(TAG, "set_pix unsupported format");
ret = -1;
break;
}
if (ret == 0) {
sensor->pixformat = pixformat;
ESP_LOGD(TAG, "Set pixformat to: %u", pixformat);
}
return ret;
}
static int set_framesize(sensor_t *sensor, framesize_t framesize)
{
int ret = 0;
if (framesize > FRAMESIZE_VGA) {
return -1;
}
uint16_t w = resolution[framesize].width;
uint16_t h = resolution[framesize].height;
sensor->status.framesize = framesize;
// Write MSBs
ret |= SCCB_Write(sensor->slv_addr, 0x17, 0);
ret |= SCCB_Write(sensor->slv_addr, 0x18, w >> 2);
ret |= SCCB_Write(sensor->slv_addr, 0x19, 0);
ret |= SCCB_Write(sensor->slv_addr, 0x1a, h >> 2);
// Write LSBs
ret |= SCCB_Write(sensor->slv_addr, 0x1b, 0);
if ((w <= 320) && (h <= 240)) {
ret |= SCCB_Write(sensor->slv_addr, 0x17, (80 - w / 4));
ret |= SCCB_Write(sensor->slv_addr, 0x18, (80 + w / 4));
ret |= SCCB_Write(sensor->slv_addr, 0x19, (60 - h / 4));
ret |= SCCB_Write(sensor->slv_addr, 0x1a, (60 + h / 4));
} else if ((w <= 640) && (h <= 480)) {
ret |= SCCB_Write(sensor->slv_addr, 0x17, (80 - w / 8));
ret |= SCCB_Write(sensor->slv_addr, 0x18, (80 + w / 8));
ret |= SCCB_Write(sensor->slv_addr, 0x19, (60 - h / 8));
ret |= SCCB_Write(sensor->slv_addr, 0x1a, (60 + h / 8));
}
// Delay
vTaskDelay(30 / portTICK_PERIOD_MS);
return ret;
}
static int set_hmirror(sensor_t *sensor, int enable)
{
int ret = 0;
sensor->status.hmirror = enable;
//ret = write_reg(sensor->slv_addr, 0xfe, 0x00);
ret |= set_reg_bits(sensor, 0x4a, 3, 0x01, enable);
if (ret == 0) {
ESP_LOGD(TAG, "Set h-mirror to: %d", enable);
}
return ret;
}
static int set_vflip(sensor_t *sensor, int enable)
{
int ret = 0;
sensor->status.vflip = enable;
//ret = write_reg(sensor->slv_addr, 0xfe, 0x00);
ret |= set_reg_bits(sensor, 0x4a, 2, 0x01, enable);
if (ret == 0) {
ESP_LOGD(TAG, "Set v-flip to: %d", enable);
}
return ret;
}
static int set_colorbar(sensor_t *sensor, int value)
{
int ret = 0;
ret = write_reg(sensor->slv_addr, 0xb6, value);
if (ret == 0) {
sensor->status.colorbar = value;
ESP_LOGD(TAG, "Set colorbar to: %d", value);
}
return ret;
}
static int set_sharpness(sensor_t *sensor, int level)
{
int ret = 0;
ret = SCCB_Write(sensor->slv_addr, 0x70, level);
if (ret == 0) {
ESP_LOGD(TAG, "Set sharpness to: %d", level);
sensor->status.sharpness = level;
}
return ret;
}
static int get_reg(sensor_t *sensor, int reg, int mask)
{
int ret = 0;
if (mask > 0xFF) {
ESP_LOGE(TAG, "mask should not more than 0xff");
} else {
ret = read_reg(sensor->slv_addr, reg);
}
if (ret > 0) {
ret &= mask;
}
return ret;
}
static int set_reg(sensor_t *sensor, int reg, int mask, int value)
{
int ret = 0;
if (mask > 0xFF) {
ESP_LOGE(TAG, "mask should not more than 0xff");
} else {
ret = read_reg(sensor->slv_addr, reg);
}
if (ret < 0) {
return ret;
}
value = (ret & ~mask) | (value & mask);
if (mask > 0xFF) {
} else {
ret = write_reg(sensor->slv_addr, reg, value);
}
return ret;
}
static int init_status(sensor_t *sensor)
{
// write_reg(sensor->slv_addr, 0xfe, 0x00);
sensor->status.brightness = SCCB_Read(sensor->slv_addr, 0x6f);
sensor->status.contrast = SCCB_Read(sensor->slv_addr, 0xd6);
sensor->status.saturation = 0;
sensor->status.sharpness = SCCB_Read(sensor->slv_addr, 0x70);
sensor->status.denoise = 0;
sensor->status.ae_level = 0;
sensor->status.gainceiling = SCCB_Read(sensor->slv_addr, 0x13);
sensor->status.awb = 0;
sensor->status.dcw = 0;
sensor->status.agc = 0;
sensor->status.aec = 0;
sensor->status.hmirror = 0;// check_reg_mask(sensor->slv_addr, P0_CISCTL_MODE1, 0x01);
sensor->status.vflip = 0;// check_reg_mask(sensor->slv_addr, P0_CISCTL_MODE1, 0x02);
sensor->status.colorbar = 0;
sensor->status.bpc = 0;
sensor->status.wpc = 0;
sensor->status.raw_gma = 0;
sensor->status.lenc = 0;
sensor->status.quality = 0;
sensor->status.special_effect = 0;
sensor->status.wb_mode = 0;
sensor->status.awb_gain = 0;
sensor->status.agc_gain = 0;
sensor->status.aec_value = 0;
sensor->status.aec2 = 0;
return 0;
}
static int set_dummy(sensor_t *sensor, int val)
{
ESP_LOGW(TAG, "dummy Unsupported");
return -1;
}
static int set_gainceiling_dummy(sensor_t *sensor, gainceiling_t val)
{
ESP_LOGW(TAG, "gainceiling Unsupported");
return -1;
}
int esp32_camera_bf20a6_detect(int slv_addr, sensor_id_t *id)
{
if (BF20A6_SCCB_ADDR == slv_addr) {
uint8_t MIDL = SCCB_Read(slv_addr, SENSOR_ID_LOW);
uint8_t MIDH = SCCB_Read(slv_addr, SENSOR_ID_HIGH);
uint16_t PID = MIDH << 8 | MIDL;
if (BF20A6_PID == PID) {
id->PID = PID;
return PID;
} else {
ESP_LOGI(TAG, "Mismatch PID=0x%x", PID);
}
}
return 0;
}
int esp32_camera_bf20a6_init(sensor_t *sensor)
{
sensor->init_status = init_status;
sensor->reset = reset;
sensor->set_pixformat = set_pixformat;
sensor->set_framesize = set_framesize;
sensor->set_contrast = set_dummy;
sensor->set_brightness = set_dummy;
sensor->set_saturation = set_dummy;
sensor->set_sharpness = set_sharpness;
sensor->set_denoise = set_dummy;
sensor->set_gainceiling = set_gainceiling_dummy;
sensor->set_quality = set_dummy;
sensor->set_colorbar = set_colorbar;
sensor->set_whitebal = set_dummy;
sensor->set_gain_ctrl = set_dummy;
sensor->set_exposure_ctrl = set_dummy;
sensor->set_hmirror = set_hmirror; // set_hmirror;
sensor->set_vflip = set_vflip; // set_vflip;
sensor->set_aec2 = set_dummy;
sensor->set_awb_gain = set_dummy;
sensor->set_agc_gain = set_dummy;
sensor->set_aec_value = set_dummy;
sensor->set_special_effect = set_dummy;
sensor->set_wb_mode = set_dummy;
sensor->set_ae_level = set_dummy;
sensor->set_dcw = set_dummy;
sensor->set_bpc = set_dummy;
sensor->set_wpc = set_dummy;
sensor->set_raw_gma = set_dummy;
sensor->set_lenc = set_dummy;
sensor->get_reg = get_reg;
sensor->set_reg = set_reg;
sensor->set_res_raw = NULL;
sensor->set_pll = NULL;
sensor->set_xclk = NULL;
// No autofocus support
sensor->af_is_supported = NULL;
sensor->af_init = NULL;
sensor->af_set_mode = NULL;
sensor->af_trigger = NULL;
sensor->af_get_status = NULL;
sensor->af_set_manual_position = NULL;
ESP_LOGD(TAG, "BF20A6 Attached");
return 0;
}
+549
View File
@@ -0,0 +1,549 @@
/*
* This file is part of the OpenMV project.
* Copyright (c) 2013/2014 Ibrahim Abdelkader <i.abdalkader@gmail.com>
* This work is licensed under the MIT license, see the file LICENSE for details.
*
* BF3005 driver.
*
* Copyright 2015-2021 Espressif Systems (Shanghai) PTE LTD
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "sccb.h"
#include "xclk.h"
#include "bf3005.h"
#include "bf3005_regs.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG)
#include "esp32-hal-log.h"
#else
#include "esp_log.h"
static const char* TAG = "bf3005";
#endif
static const uint8_t default_regs[][2] = {
{0x12, 0x40}, //soft reset
{0xff, 0xff}, //delay
{0xff, 0xff}, //delay
{0xff, 0xff}, //delay
{0xff, 0xff}, //delay
{0x13, 0x10},
{0x8c, 0x00},
{0x8d, 0x64},
{0x87, 0x10},
{0x13, 0x17},
{0x00, 0x20},
{0x01, 0x1a},
{0x02, 0x22},
{0x09, 0x03},
{0x0c, 0x80},
{0x0d, 0x24},
{0x0e, 0x21},
{0x0f, 0x28},
{0x11, 0x08},
{0x15, 0x10}, // 0X10
{0x16, 0x03},
{0x1e, 0x30},
{0x20, 0x8a},
{0x21, 0x03},
{0x23, 0x55},
{0x24, 0x68},
{0x25, 0x78},
{0x2a, 0x00},
{0x2b, 0x00},
{0x2d, 0x4f},
{0x2e, 0x98},
{0x2f, 0x04},
{0x30, 0xad},
{0x31, 0x17},
{0x32, 0x6e},
{0x33, 0x20},
{0x35, 0xa6},
{0x3b, 0x00},
{0x3e, 0x00},
{0x3f, 0xA8},
{0x40, 0x38},
{0x41, 0x32},
{0x42, 0x2b},
{0x43, 0x26},
{0x44, 0x1a},
{0x45, 0x16},
{0x46, 0x10},
{0x47, 0x0f},
{0x48, 0x0c},
{0x49, 0x0a},
{0x4b, 0x09},
{0x4c, 0x08},
{0x4d, 0x3c},
{0x4e, 0x06},
{0x4f, 0x05},
{0x50, 0x03},
{0x51, 0x25},
{0x52, 0x88},
{0x53, 0x03},
{0x63, 0x20},
{0x64, 0x02},
{0x65, 0xa6},
{0x66, 0xb6},
{0x69, 0x00},
{0x70, 0xFF},
{0x71, 0xa6},
{0x72, 0x2f},
{0x73, 0x2f},
{0x74, 0x2F},
{0x75, 0x0e},
{0x76, 0x1e},
{0x77, 0x00},
{0x78, 0x1e},
{0x79, 0x8a},
{0x7d, 0xe2},
{0x80, 0x44},
{0x81, 0x00},
{0x82, 0x18},
{0x83, 0x1b},
{0x84, 0x24},
{0x85, 0x2a},
{0x86, 0x4f},
{0x89, 0x82}, //0x82
{0x8b, 0x02},
{0x8e, 0x03},
{0x8f, 0xFC},
{0x9d, 0x4d},
{0x9e, 0x41},
{0xa1, 0x21},
{0xa2, 0x12},
{0xa3, 0x32},
{0xa4, 0x05},
{0xa5, 0x32},
{0xa6, 0x04},
{0xa7, 0x7f},
{0xa8, 0x7f},
{0xa9, 0x21},
{0xaa, 0x21},
{0xab, 0x21},
{0xac, 0x0a},
{0xad, 0xf0},
{0xae, 0xff},
{0xaf, 0x1d},
{0xb0, 0x94},
{0xb1, 0xc0},
{0xb2, 0xc0},
{0xd2, 0x30},
{0xe0, 0x0d},
{0xe1, 0x44},
{0xe7, 0x7c},
{0xe8, 0x89},
{0xe9, 0x01},
{0xea, 0x01},
{0xf0, 0x01},
{0xf3, 0x49},
{0xf4, 0xff},
{0xf5, 0x01},
{0xf6, 0xf2},
{0xf7, 0x6f},
{0x1b, 0x80},
{0x00, 0x00},
};
static int get_reg(sensor_t *sensor, int reg, int mask)
{
int ret = SCCB_Read(sensor->slv_addr, reg & 0xFF);
if(ret > 0){
ret &= mask;
}
return ret;
}
static int set_reg(sensor_t *sensor, int reg, int mask, int value)
{
int ret = 0;
ret = SCCB_Read(sensor->slv_addr, reg & 0xFF);
if(ret < 0){
return ret;
}
value = (ret & ~mask) | (value & mask);
ret = SCCB_Write(sensor->slv_addr, reg & 0xFF, value);
return ret;
}
static int set_reg_bits(sensor_t *sensor, uint8_t reg, uint8_t offset, uint8_t length, uint8_t value)
{
int ret = 0;
ret = SCCB_Read(sensor->slv_addr, reg);
if(ret < 0){
return ret;
}
uint8_t mask = ((1 << length) - 1) << offset;
value = (ret & ~mask) | ((value << offset) & mask);
ret = SCCB_Write(sensor->slv_addr, reg & 0xFF, value);
return ret;
}
static int get_reg_bits(sensor_t *sensor, uint8_t reg, uint8_t offset, uint8_t length)
{
int ret = 0;
ret = SCCB_Read(sensor->slv_addr, reg);
if(ret < 0){
return ret;
}
uint8_t mask = ((1 << length) - 1) << offset;
return (ret & mask) >> offset;
}
static int reset(sensor_t *sensor)
{
int i=0;
const uint8_t (*regs)[2];
// Write default registers
for (i=0, regs = default_regs; regs[i][0]; i++) {
SCCB_Write(sensor->slv_addr, regs[i][0], regs[i][1]);
}
// Delay
vTaskDelay(50 / portTICK_PERIOD_MS);
return 0;
}
static int set_pixformat(sensor_t *sensor, pixformat_t pixformat)
{
int ret=0;
sensor->pixformat = pixformat;
switch (pixformat) {
case PIXFORMAT_RGB565:
set_reg_bits(sensor, 0x12, 2, 1, 1);
break;
case PIXFORMAT_RAW:
set_reg_bits(sensor, 0x12, 0, 3, 0x4);
break;
case PIXFORMAT_YUV422:
case PIXFORMAT_GRAYSCALE:
set_reg_bits(sensor, 0x12, 2, 1, 0);
break;
default:
return -1;
}
// Delay
vTaskDelay(30 / portTICK_PERIOD_MS);
return ret;
}
static int set_framesize(sensor_t *sensor, framesize_t framesize)
{
int ret=0;
if (framesize > FRAMESIZE_VGA) {
return -1;
}
uint16_t w = resolution[framesize].width;
uint16_t h = resolution[framesize].height;
// uint8_t reg = SCCB_Read(sensor->slv_addr, COM7);
sensor->status.framesize = framesize;
// Write MSBs
ret |= SCCB_Write(sensor->slv_addr, 0x17, 0);
ret |= SCCB_Write(sensor->slv_addr, 0x18, w>>2);
ret |= SCCB_Write(sensor->slv_addr, 0x19, 0);
ret |= SCCB_Write(sensor->slv_addr, 0x1a, h>>2);
// Write LSBs
ret |= SCCB_Write(sensor->slv_addr, 0x03, 0);
printf("%s %d\r\n", __func__, __LINE__);
if((w<=320)&&(h<=240))
{
printf("%s %d\r\n", __func__, __LINE__);
// Enable auto-scaling/zooming factors
//ret |= SCCB_Write(sensor->slv_addr, 0x12, 0x50);
set_reg_bits(sensor, 0x12, 4, 1, 1);
ret |= SCCB_Write(sensor->slv_addr, 0x17, (80-w/4));
ret |= SCCB_Write(sensor->slv_addr, 0x18, (80+w/4));
ret |= SCCB_Write(sensor->slv_addr, 0x19, (60-h/4));
ret |= SCCB_Write(sensor->slv_addr, 0x1a, (60+h/4));
ret |= SCCB_Write(sensor->slv_addr, 0x03, 0);
} else if((w<=640)&&(h<=480))
{
// Enable auto-scaling/zooming factors
//ret |= SCCB_Write(sensor->slv_addr, 0x12, 0x40);
set_reg_bits(sensor, 0x12, 4, 1, 0);
ret |= SCCB_Write(sensor->slv_addr, 0x17, (80-w/8));
ret |= SCCB_Write(sensor->slv_addr, 0x18, (80+w/8));
ret |= SCCB_Write(sensor->slv_addr, 0x19, (60-h/8));
ret |= SCCB_Write(sensor->slv_addr, 0x1a, (60+h/8));
ret |= SCCB_Write(sensor->slv_addr, 0x03, 0);
}
// Delay
vTaskDelay(30 / portTICK_PERIOD_MS);
return ret;
}
static int set_colorbar(sensor_t *sensor, int value)
{
int ret=0;
sensor->status.colorbar = value;
ret |= SCCB_Write(sensor->slv_addr, 0xb9, value);
return ret;
}
static int set_whitebal(sensor_t *sensor, int enable)
{
if(set_reg_bits(sensor, 0x13, 1, 1, enable) >= 0){
sensor->status.awb = !!enable;
}
return sensor->status.awb;
}
static int set_gain_ctrl(sensor_t *sensor, int enable)
{
if(set_reg_bits(sensor, 0x13, 2, 1, enable) >= 0){
sensor->status.agc = !!enable;
}
return sensor->status.agc;
}
static int set_exposure_ctrl(sensor_t *sensor, int enable)
{
if(set_reg_bits(sensor, 0x13, 0, 1, enable) >= 0){
sensor->status.aec = !!enable;
}
return sensor->status.aec;
}
static int set_hmirror(sensor_t *sensor, int enable)
{
if(set_reg_bits(sensor, 0x1e, 5, 1, enable) >= 0){
sensor->status.hmirror = !!enable;
}
return sensor->status.hmirror;
}
static int set_vflip(sensor_t *sensor, int enable)
{
if(set_reg_bits(sensor, 0x1e, 4, 1, enable) >= 0){
sensor->status.vflip = !!enable;
}
return sensor->status.vflip;
}
static int set_raw_gma_dsp(sensor_t *sensor, int enable)
{
int ret = 0;
ret = set_reg_bits(sensor, 0xf1, 1, 1, !enable);
if (ret == 0) {
ESP_LOGD(TAG, "Set raw_gma to: %d", !enable);
sensor->status.raw_gma = !enable;
}
return ret;
}
static int set_lenc_dsp(sensor_t *sensor, int enable)
{
int ret = 0;
ret = set_reg_bits(sensor, 0xf1, 0, 1, !enable);
if (ret == 0) {
ESP_LOGD(TAG, "Set lenc to: %d", !enable);
sensor->status.lenc = !enable;
}
return ret;
}
static int set_agc_gain(sensor_t *sensor, int option)
{
int ret = 0;
ret = set_reg_bits(sensor, 0x13, 4, 1, !!option);
if (ret == 0) {
ESP_LOGD(TAG, "Set gain to: %d", !!option);
sensor->status.agc_gain = !!option;
}
return ret;
}
static int set_awb_gain_dsp(sensor_t *sensor, int value)
{
int ret = 0;
ret = SCCB_Write(sensor->slv_addr, 0xa6, value);
if (ret == 0) {
ESP_LOGD(TAG, "Set awb gain threthold to: %d", value);
sensor->status.awb_gain = value;
}
return ret;
}
static int set_brightness(sensor_t *sensor, int level)
{
int ret = 0;
ret = SCCB_Write(sensor->slv_addr, 0x55, level);
if (ret == 0) {
ESP_LOGD(TAG, "Set brightness to: %d", level);
sensor->status.brightness = level;
}
return ret;
}
static int set_contrast(sensor_t *sensor, int level)
{
int ret = 0;
ret = SCCB_Write(sensor->slv_addr, 0x56, level);
if (ret == 0) {
ESP_LOGD(TAG, "Set contrast to: %d", level);
sensor->status.contrast = level;
}
return ret;
}
static int set_sharpness(sensor_t *sensor, int level)
{
int ret = 0;
ret = SCCB_Write(sensor->slv_addr, 0x70, level);
if (ret == 0) {
ESP_LOGD(TAG, "Set sharpness to: %d", level);
sensor->status.sharpness = level;
}
return ret;
}
static int init_status(sensor_t *sensor)
{
sensor->status.brightness = SCCB_Read(sensor->slv_addr, 0x55);
sensor->status.contrast = SCCB_Read(sensor->slv_addr, 0x56);
sensor->status.saturation = 0;
sensor->status.ae_level = 0;
sensor->status.gainceiling = SCCB_Read(sensor->slv_addr, 0x87);
sensor->status.awb = get_reg_bits(sensor, 0x13, 1, 1);
sensor->status.awb_gain = SCCB_Read(sensor->slv_addr, 0xa6);
sensor->status.aec = get_reg_bits(sensor, 0x13, 0, 1);
sensor->status.agc = get_reg_bits(sensor, 0x13, 2, 1);
sensor->status.raw_gma = get_reg_bits(sensor, 0xf1, 1, 1);
sensor->status.lenc = get_reg_bits(sensor, 0xf1, 0, 1);
sensor->status.hmirror = get_reg_bits(sensor, 0x1e, 5, 1);
sensor->status.vflip = get_reg_bits(sensor, 0x1e, 4, 1);
sensor->status.colorbar = SCCB_Read(sensor->slv_addr, 0xb9);
sensor->status.sharpness = SCCB_Read(sensor->slv_addr, 0x70);
return 0;
}
static int set_dummy(sensor_t *sensor, int val){ return -1; }
static int set_gainceiling_dummy(sensor_t *sensor, gainceiling_t val){ return -1; }
static int set_res_raw(sensor_t *sensor, int startX, int startY, int endX, int endY, int offsetX, int offsetY, int totalX, int totalY, int outputX, int outputY, bool scale, bool binning){return -1;}
static int _set_pll(sensor_t *sensor, int bypass, int multiplier, int sys_div, int root_2x, int pre_div, int seld5, int pclk_manual, int pclk_div){return -1;}
static int set_xclk(sensor_t *sensor, int timer, int xclk)
{
int ret = 0;
sensor->xclk_freq_hz = xclk * 1000000U;
ret = xclk_timer_conf(timer, sensor->xclk_freq_hz);
return ret;
}
int esp32_camera_bf3005_detect(int slv_addr, sensor_id_t *id)
{
if (BF3005_SCCB_ADDR == slv_addr) {
uint16_t PID = SCCB_Read(slv_addr, 0xFC);
if (BF3005_PID == PID) {
id->PID = PID;
id->VER = SCCB_Read(slv_addr, 0xFD);
id->MIDL = SCCB_Read(slv_addr, 0xFC);
id->MIDH = SCCB_Read(slv_addr, 0xFD);
return PID;
} else {
ESP_LOGI(TAG, "Mismatch PID=0x%x", PID);
}
}
return 0;
}
int esp32_camera_bf3005_init(sensor_t *sensor)
{
// Set function pointers
sensor->reset = reset;
sensor->init_status = init_status;
sensor->set_pixformat = set_pixformat;
sensor->set_framesize = set_framesize;
sensor->set_brightness = set_brightness;
sensor->set_contrast = set_contrast;
sensor->set_colorbar = set_colorbar;
sensor->set_gain_ctrl = set_gain_ctrl;
sensor->set_exposure_ctrl = set_exposure_ctrl;
sensor->set_hmirror = set_hmirror;
sensor->set_vflip = set_vflip;
sensor->set_whitebal = set_whitebal;
sensor->set_awb_gain = set_awb_gain_dsp;
sensor->set_agc_gain = set_agc_gain;
sensor->set_raw_gma = set_raw_gma_dsp;
sensor->set_lenc = set_lenc_dsp;
sensor->set_sharpness = set_sharpness;
//not supported
sensor->set_saturation= set_dummy;
sensor->set_denoise = set_dummy;
sensor->set_quality = set_dummy;
sensor->set_special_effect = set_dummy;
sensor->set_wb_mode = set_dummy;
sensor->set_ae_level = set_dummy;
sensor->set_gainceiling = set_gainceiling_dummy;
sensor->get_reg = get_reg;
sensor->set_reg = set_reg;
sensor->set_res_raw = set_res_raw;
sensor->set_pll = _set_pll;
sensor->set_xclk = set_xclk;
// No autofocus support
sensor->af_is_supported = NULL;
sensor->af_init = NULL;
sensor->af_set_mode = NULL;
sensor->af_trigger = NULL;
sensor->af_get_status = NULL;
sensor->af_set_manual_position = NULL;
ESP_LOGD(TAG, "BF3005 Attached");
return 0;
}
+847
View File
@@ -0,0 +1,847 @@
// Copyright 2015-2021 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "sccb.h"
#include "gc0308.h"
#include "gc0308_regs.h"
#include "gc0308_settings.h"
#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG)
#include "esp32-hal-log.h"
#else
#include "esp_log.h"
static const char *TAG = "gc0308";
#endif
#define H8(v) ((v)>>8)
#define L8(v) ((v)&0xff)
//#define REG_DEBUG_ON
static int read_reg(uint8_t slv_addr, const uint16_t reg)
{
int ret = SCCB_Read(slv_addr, reg);
#ifdef REG_DEBUG_ON
if (ret < 0) {
ESP_LOGE(TAG, "READ REG 0x%04x FAILED: %d", reg, ret);
}
#endif
return ret;
}
static int write_reg(uint8_t slv_addr, const uint16_t reg, uint8_t value)
{
int ret = 0;
#ifndef REG_DEBUG_ON
ret = SCCB_Write(slv_addr, reg, value);
#else
int old_value = read_reg(slv_addr, reg);
if (old_value < 0) {
return old_value;
}
if ((uint8_t)old_value != value) {
ESP_LOGI(TAG, "NEW REG 0x%04x: 0x%02x to 0x%02x", reg, (uint8_t)old_value, value);
ret = SCCB_Write(slv_addr, reg, value);
} else {
ESP_LOGD(TAG, "OLD REG 0x%04x: 0x%02x", reg, (uint8_t)old_value);
ret = SCCB_Write(slv_addr, reg, value);//maybe not?
}
if (ret < 0) {
ESP_LOGE(TAG, "WRITE REG 0x%04x FAILED: %d", reg, ret);
}
#endif
return ret;
}
static int check_reg_mask(uint8_t slv_addr, uint16_t reg, uint8_t mask)
{
return (read_reg(slv_addr, reg) & mask) == mask;
}
static int set_reg_bits(uint8_t slv_addr, uint16_t reg, uint8_t offset, uint8_t mask, uint8_t value)
{
int ret = 0;
uint8_t c_value, new_value;
ret = read_reg(slv_addr, reg);
if (ret < 0) {
return ret;
}
c_value = ret;
new_value = (c_value & ~(mask << offset)) | ((value & mask) << offset);
ret = write_reg(slv_addr, reg, new_value);
return ret;
}
static int write_regs(uint8_t slv_addr, const uint8_t (*regs)[2], size_t regs_size)
{
int i = 0, ret = 0;
while (!ret && (i < regs_size)) {
if (regs[i][0] == REG_DLY) {
vTaskDelay(regs[i][1] / portTICK_PERIOD_MS);
} else {
ret = write_reg(slv_addr, regs[i][0], regs[i][1]);
}
i++;
}
return ret;
}
static void print_regs(uint8_t slv_addr)
{
#ifdef DEBUG_PRINT_REG
ESP_LOGI(TAG, "REG list look ======================");
for (size_t i = 0xf0; i <= 0xfe; i++) {
ESP_LOGI(TAG, "reg[0x%02x] = 0x%02x", i, read_reg(slv_addr, i));
}
ESP_LOGI(TAG, "\npage 0 ===");
write_reg(slv_addr, 0xfe, 0x00); // page 0
for (size_t i = 0x03; i <= 0xa2; i++) {
ESP_LOGI(TAG, "p0 reg[0x%02x] = 0x%02x", i, read_reg(slv_addr, i));
}
ESP_LOGI(TAG, "\npage 3 ===");
write_reg(slv_addr, 0xfe, 0x03); // page 3
for (size_t i = 0x01; i <= 0x43; i++) {
ESP_LOGI(TAG, "p3 reg[0x%02x] = 0x%02x", i, read_reg(slv_addr, i));
}
#endif
}
static int reset(sensor_t *sensor)
{
int ret = 0;
// Software Reset: clear all registers and reset them to their default values
ret = write_reg(sensor->slv_addr, RESET_RELATED, 0xf0);
if (ret) {
ESP_LOGE(TAG, "Software Reset FAILED!");
return ret;
}
vTaskDelay(80 / portTICK_PERIOD_MS);
ret = write_regs(sensor->slv_addr, gc0308_sensor_default_regs, sizeof(gc0308_sensor_default_regs)/(sizeof(uint8_t) * 2));
if (ret == 0) {
ESP_LOGD(TAG, "Camera defaults loaded");
vTaskDelay(80 / portTICK_PERIOD_MS);
ret = write_reg(sensor->slv_addr, 0xfe, 0x00);
#ifdef CONFIG_IDF_TARGET_ESP32
ret |= set_reg_bits(sensor->slv_addr, PCLK_DIV, 4, 0x07, 1); //frequency division for esp32, ensure pclk <= 15MHz
#endif
}
return ret;
}
static int set_pixformat(sensor_t *sensor, pixformat_t pixformat)
{
int ret = 0;
switch (pixformat) {
case PIXFORMAT_RGB565:
ret = write_reg(sensor->slv_addr, 0xfe, 0x00);
ret |= set_reg_bits(sensor->slv_addr, OUTPUT_FMT, 0, 0x0f, 6); //RGB565
break;
case PIXFORMAT_YUV422:
ret = write_reg(sensor->slv_addr, 0xfe, 0x00);
ret |= set_reg_bits(sensor->slv_addr, OUTPUT_FMT, 0, 0x0f, 2); //yuv422 Y Cb Y Cr
break;
case PIXFORMAT_GRAYSCALE:
ret = write_reg(sensor->slv_addr, 0xfe, 0x00);
ret |= write_reg(sensor->slv_addr, OUTPUT_FMT, 0xb1);
break;
default:
ESP_LOGW(TAG, "Unsupported format %u", pixformat);
ret = -1;
break;
}
if (ret == 0) {
sensor->pixformat = pixformat;
ESP_LOGD(TAG, "Set pixformat to: %u", pixformat);
}
return ret;
}
static int set_framesize(sensor_t *sensor, framesize_t framesize)
{
int ret = 0;
if (framesize > FRAMESIZE_VGA) {
ESP_LOGW(TAG, "Invalid framesize: %u", framesize);
framesize = FRAMESIZE_VGA;
}
sensor->status.framesize = framesize;
uint16_t w = resolution[framesize].width;
uint16_t h = resolution[framesize].height;
uint16_t row_s = (resolution[FRAMESIZE_VGA].height - h) / 2;
uint16_t col_s = (resolution[FRAMESIZE_VGA].width - w) / 2;
(void)row_s;
(void)col_s;
#if CONFIG_GC_SENSOR_SUBSAMPLE_MODE
struct subsample_cfg {
uint16_t ratio_numerator;
uint16_t ratio_denominator;
uint8_t reg0x54;
uint8_t reg0x56;
uint8_t reg0x57;
uint8_t reg0x58;
uint8_t reg0x59;
};
const struct subsample_cfg subsample_cfgs[] = { // define some subsample ratio
{84, 420, 0x55, 0x00, 0x00, 0x00, 0x00}, //1/5
{105, 420, 0x44, 0x00, 0x00, 0x00, 0x00},//1/4
{140, 420, 0x33, 0x00, 0x00, 0x00, 0x00},//1/3
{210, 420, 0x22, 0x00, 0x00, 0x00, 0x00},//1/2
{240, 420, 0x77, 0x02, 0x46, 0x02, 0x46},//4/7
{252, 420, 0x55, 0x02, 0x04, 0x02, 0x04},//3/5
{280, 420, 0x33, 0x02, 0x00, 0x02, 0x00},//2/3
{420, 420, 0x11, 0x00, 0x00, 0x00, 0x00},//1/1
};
uint16_t win_w = 640;
uint16_t win_h = 480;
const struct subsample_cfg *cfg = NULL;
/**
* Strategy: try to keep the maximum perspective
*/
for (size_t i = 0; i < sizeof(subsample_cfgs) / sizeof(struct subsample_cfg); i++) {
cfg = &subsample_cfgs[i];
if ((win_w * cfg->ratio_numerator / cfg->ratio_denominator >= w) && (win_h * cfg->ratio_numerator / cfg->ratio_denominator >= h)) {
win_w = w * cfg->ratio_denominator / cfg->ratio_numerator;
win_h = h * cfg->ratio_denominator / cfg->ratio_numerator;
row_s = (resolution[FRAMESIZE_VGA].height - win_h) / 2;
col_s = (resolution[FRAMESIZE_VGA].width - win_w) / 2;
ESP_LOGI(TAG, "subsample win:%dx%d, ratio:%f", win_w, win_h, (float)cfg->ratio_numerator / (float)cfg->ratio_denominator);
break;
}
}
ret = write_reg(sensor->slv_addr, 0xfe, 0x00);
ret |= write_reg(sensor->slv_addr, ROW_START_H, H8(row_s));
ret |= write_reg(sensor->slv_addr, ROW_START_L, L8(row_s));
ret |= write_reg(sensor->slv_addr, COL_START_H, H8(col_s));
ret |= write_reg(sensor->slv_addr, COL_START_L, L8(col_s));
ret |= write_reg(sensor->slv_addr, WIN_HEIGHT_H, H8(win_h + 8));
ret |= write_reg(sensor->slv_addr, WIN_HEIGHT_L, L8(win_h + 8));
ret |= write_reg(sensor->slv_addr, WIN_WIDTH_H, H8(win_w + 8));
ret |= write_reg(sensor->slv_addr, WIN_WIDTH_L, L8(win_w + 8));
ret |= write_reg(sensor->slv_addr, 0xfe, 0x01);
ret |= set_reg_bits(sensor->slv_addr, SUBSAMPLE_EN, 7, 0x01, 1);
ret |= set_reg_bits(sensor->slv_addr, SUBSAMPLE_EN2, 0, 0x01, 1);
ret |= write_reg(sensor->slv_addr, SUBSAMPLE_MODE, cfg->reg0x54);
ret |= write_reg(sensor->slv_addr, SUBSAMPLE_Y0, cfg->reg0x56);
ret |= write_reg(sensor->slv_addr, SUBSAMPLE_Y1, cfg->reg0x57);
ret |= write_reg(sensor->slv_addr, SUBSAMPLE_UV0, cfg->reg0x58);
ret |= write_reg(sensor->slv_addr, SUBSAMPLE_UV1, cfg->reg0x59);
ret |= write_reg(sensor->slv_addr, 0xfe, 0x00);
#elif CONFIG_GC_SENSOR_WINDOWING_MODE
ret = write_reg(sensor->slv_addr, 0xfe, 0x00);
ret |= write_reg(sensor->slv_addr, SUB_COL_N, col_s / 4);
ret |= write_reg(sensor->slv_addr, SUB_ROW_N, row_s / 4);
ret |= write_reg(sensor->slv_addr, SUB_COL_N1, (col_s + w) / 4);
ret |= write_reg(sensor->slv_addr, SUB_ROW_N1, (row_s + h) / 4);
ret |= write_reg(sensor->slv_addr, ROW_START_H, H8(row_s));
ret |= write_reg(sensor->slv_addr, ROW_START_L, L8(row_s));
ret |= write_reg(sensor->slv_addr, COL_START_H, H8(col_s));
ret |= write_reg(sensor->slv_addr, COL_START_L, L8(col_s));
ret |= write_reg(sensor->slv_addr, WIN_HEIGHT_H, H8(h + 8));
ret |= write_reg(sensor->slv_addr, WIN_HEIGHT_L, L8(h + 8));
ret |= write_reg(sensor->slv_addr, WIN_WIDTH_H, H8(w + 8));
ret |= write_reg(sensor->slv_addr, WIN_WIDTH_L, L8(w + 8));
#endif
if (ret == 0) {
ESP_LOGD(TAG, "Set framesize to: %ux%u", w, h);
}
return 0;
}
static int set_contrast(sensor_t *sensor, int level)
{
int ret = 0;
// GC0308 contrast range: -2 to +2 (mapped to register values)
if (level < -2 || level > 2) {
return -1;
}
ret = write_reg(sensor->slv_addr, 0xfe, 0x00);
// Adjust contrast (CONTRAST register)
// Default value is 0x40, adjust based on level
uint8_t contrast_val = 0x40 + (level * 0x10);
ret |= write_reg(sensor->slv_addr, CONTRAST, contrast_val);
if (ret == 0) {
sensor->status.contrast = level;
ESP_LOGD(TAG, "Set contrast to: %d", level);
}
return ret;
}
static int set_saturation(sensor_t *sensor, int level)
{
int ret = 0;
// GC0308 saturation range: -2 to +2
if (level < -2 || level > 2) {
return -1;
}
ret = write_reg(sensor->slv_addr, 0xfe, 0x00);
// Adjust saturation via Cb and Cr saturation gains (0xb1, 0xb2)
// Default values from settings: both 0x40
// Saturation adjustment: increase/decrease both Cb and Cr gains
uint8_t sat_val = 0x40 + (level * 0x10);
ret |= write_reg(sensor->slv_addr, SATURATION_Cb, sat_val);
ret |= write_reg(sensor->slv_addr, SATURATION_Cr, sat_val);
if (ret == 0) {
sensor->status.saturation = level;
ESP_LOGD(TAG, "Set saturation to: %d", level);
}
return ret;
}
static int set_sharpness(sensor_t *sensor, int level)
{
int ret = 0;
// GC0308 sharpness control via Edge Enhancement registers (0x77-0x79)
// Per Linux kernel driver: EDGE12_EFFECT, EDGE_POS_RATIO, EDGE1_MINMAX
// Range: -2 to +2
if (level < -2 || level > 2) {
return -1;
}
ret = write_reg(sensor->slv_addr, 0xfe, 0x00);
// Adjust edge enhancement effect
// Default values from Linux kernel driver: 0x38, 0x88 (Page 0)
// Increase for more sharpness, decrease for softer image
uint8_t edge_effect = 0x38 + (level * 0x08);
uint8_t edge_ratio = 0x88 + (level * 0x08);
ret |= write_reg(sensor->slv_addr, EDGE12_EFFECT, edge_effect);
ret |= write_reg(sensor->slv_addr, EDGE_POS_RATIO, edge_ratio);
// EDGE1_MINMAX typically kept at default unless fine-tuning needed
if (ret == 0) {
sensor->status.sharpness = level;
ESP_LOGD(TAG, "Set sharpness to: %d (edge enhancement)", level);
}
return ret;
}
static int set_colorbar(sensor_t *sensor, int enable)
{
int ret = 0;
ret = write_reg(sensor->slv_addr, 0xfe, 0x00);
ret |= set_reg_bits(sensor->slv_addr, OUT_CTRL, 0, 0x01, enable);
if (ret == 0) {
sensor->status.colorbar = enable;
ESP_LOGD(TAG, "Set colorbar to: %d", enable);
}
return ret;
}
static int set_whitebal(sensor_t *sensor, int enable)
{
int ret = 0;
ret = write_reg(sensor->slv_addr, 0xfe, 0x00);
// AWB enable/disable via AAAA_EN register, AWB_ENABLE bit
ret |= set_reg_bits(sensor->slv_addr, AAAA_EN, 1, 0x01, enable != 0);
if (ret == 0) {
sensor->status.awb = enable;
ESP_LOGD(TAG, "Set AWB to: %d", enable);
}
return ret;
}
static int set_gain_ctrl(sensor_t *sensor, int enable)
{
int ret = 0;
ret = write_reg(sensor->slv_addr, 0xfe, 0x00);
// AGC enable/disable via AAAA_EN register, AGC_ENABLE bit
ret |= set_reg_bits(sensor->slv_addr, AAAA_EN, 2, 0x01, enable != 0);
if (ret == 0) {
sensor->status.agc = enable;
ESP_LOGD(TAG, "Set AGC to: %d", enable);
}
return ret;
}
static int set_exposure_ctrl(sensor_t *sensor, int enable)
{
int ret = 0;
ret = write_reg(sensor->slv_addr, 0xfe, 0x00);
// AEC enable/disable via AAAA_EN register, AEC_ENABLE bit
ret |= set_reg_bits(sensor->slv_addr, AAAA_EN, 0, 0x01, enable != 0);
if (ret == 0) {
sensor->status.aec = enable;
ESP_LOGD(TAG, "Set AEC to: %d", enable);
}
return ret;
}
static int set_hmirror(sensor_t *sensor, int enable)
{
int ret = 0;
ret = write_reg(sensor->slv_addr, 0xfe, 0x00);
ret |= set_reg_bits(sensor->slv_addr, CISCTL_MODE1, 0, 0x01, enable != 0);
if (ret == 0) {
sensor->status.hmirror = enable;
ESP_LOGD(TAG, "Set h-mirror to: %d", enable);
}
return ret;
}
static int set_vflip(sensor_t *sensor, int enable)
{
int ret = 0;
ret = write_reg(sensor->slv_addr, 0xfe, 0x00);
ret |= set_reg_bits(sensor->slv_addr, CISCTL_MODE1, 1, 0x01, enable != 0);
if (ret == 0) {
sensor->status.vflip = enable;
ESP_LOGD(TAG, "Set v-flip to: %d", enable);
}
return ret;
}
static int set_agc_gain(sensor_t *sensor, int gain)
{
int ret = 0;
// GC0308 AGC gain range: 0-30 (standard sensor API range)
// Maps to hardware register values 0x00-0x3F (6-bit effective range)
// Hardware default is 0x14 (20)
if (gain < 0) {
gain = 0;
} else if (gain > 30) {
gain = 30;
}
// Map API range 0-30 to hardware range 0-63 (approximately 2x multiplier)
uint8_t gain_value = (gain * 63) / 30;
ret = write_reg(sensor->slv_addr, 0xfe, 0x00);
ret |= write_reg(sensor->slv_addr, GLOBAL_GAIN, gain_value);
if (ret == 0) {
sensor->status.agc_gain = gain;
ESP_LOGD(TAG, "Set AGC gain to: %d (hw: 0x%02x)", gain, gain_value);
}
return ret;
}
static int set_aec_value(sensor_t *sensor, int value)
{
int ret = 0;
// GC0308 exposure value range: 0-1200 (compatible with API)
// Hardware range: 0-4095 (12-bit), but practical max based on frame timing
// Default is approximately 0x01e0 (480)
if (value < 0) {
value = 0;
} else if (value > 1200) {
value = 1200;
}
// Exposure is 12-bit: high byte (0x03) holds bits[11:4], low byte (0x04) bits[3:0] in upper nibble
uint8_t exp_high = (value >> 4) & 0xFF;
uint8_t exp_low = (value << 4) & 0xF0;
ret = write_reg(sensor->slv_addr, 0xfe, 0x00);
ret |= write_reg(sensor->slv_addr, EXPOSURE_HIGH, exp_high);
ret |= write_reg(sensor->slv_addr, EXPOSURE_LOW, exp_low);
if (ret == 0) {
sensor->status.aec_value = value;
ESP_LOGD(TAG, "Set AEC value to: %d (0x%03x)", value, value);
}
return ret;
}
static int set_special_effect(sensor_t *sensor, int effect)
{
int ret = 0;
// Effect values: 0=Normal, 1=Negative, 2=Grayscale, 3=Red Tint, 4=Green Tint, 5=Blue Tint, 6=Sepia
if (effect < 0 || effect > 6) {
return -1;
}
ret = write_reg(sensor->slv_addr, 0xfe, 0x00);
switch (effect) {
case 0: // Normal
ret |= set_reg_bits(sensor->slv_addr, SPECIAL_EFFECT, 0, 0x03, EFFECT_NORMAL);
break;
case 1: // Negative
ret |= set_reg_bits(sensor->slv_addr, SPECIAL_EFFECT, 0, 0x03, EFFECT_NEGATIVE);
break;
case 2: // Grayscale (B&W)
ret |= set_reg_bits(sensor->slv_addr, SPECIAL_EFFECT, 0, 0x03, EFFECT_GRAYSCALE);
break;
case 3: // Red Tint
case 4: // Green Tint
case 5: // Blue Tint
case 6: // Sepia
// Color tint effects: these require grayscale mode + fixed Cb/Cr values
// However, the exact register behavior is undocumented
// Setting to grayscale mode as a safe fallback
ret |= set_reg_bits(sensor->slv_addr, SPECIAL_EFFECT, 0, 0x03, EFFECT_GRAYSCALE);
ESP_LOGW(TAG, "Color tint effects (3-6) may not be fully supported - using grayscale");
break;
default:
ret = -1;
break;
}
if (ret == 0) {
sensor->status.special_effect = effect;
ESP_LOGD(TAG, "Set special effect to: %d", effect);
}
return ret;
}
static int set_wb_mode(sensor_t *sensor, int mode)
{
int ret = 0;
if (mode < 0 || mode > 4) {
return -1;
}
ret = write_reg(sensor->slv_addr, 0xfe, 0x00);
if (mode == 0) {
// Auto WB - enable AWB and set auto gains
ret |= set_reg_bits(sensor->slv_addr, AAAA_EN, 1, 0x01, 1);
ret |= write_reg(sensor->slv_addr, AWB_R_GAIN, 0x56);
ret |= write_reg(sensor->slv_addr, AWB_G_GAIN, 0x40);
ret |= write_reg(sensor->slv_addr, AWB_B_GAIN, 0x4a);
} else {
// Manual WB - disable AWB and set specific gains
ret |= set_reg_bits(sensor->slv_addr, AAAA_EN, 1, 0x01, 0);
switch (mode) {
case 1: // Sunny (Daylight)
ret |= write_reg(sensor->slv_addr, AWB_R_GAIN, 0x74);
ret |= write_reg(sensor->slv_addr, AWB_G_GAIN, 0x52);
ret |= write_reg(sensor->slv_addr, AWB_B_GAIN, 0x40);
break;
case 2: // Cloudy
ret |= write_reg(sensor->slv_addr, AWB_R_GAIN, 0x8c);
ret |= write_reg(sensor->slv_addr, AWB_G_GAIN, 0x50);
ret |= write_reg(sensor->slv_addr, AWB_B_GAIN, 0x40);
break;
case 3: // Office (Incandescent)
ret |= write_reg(sensor->slv_addr, AWB_R_GAIN, 0x48);
ret |= write_reg(sensor->slv_addr, AWB_G_GAIN, 0x40);
ret |= write_reg(sensor->slv_addr, AWB_B_GAIN, 0x5c);
break;
case 4: // Home (Fluorescent)
ret |= write_reg(sensor->slv_addr, AWB_R_GAIN, 0x40);
ret |= write_reg(sensor->slv_addr, AWB_G_GAIN, 0x42);
ret |= write_reg(sensor->slv_addr, AWB_B_GAIN, 0x50);
break;
default:
ret = -1;
break;
}
}
if (ret == 0) {
sensor->status.wb_mode = mode;
ESP_LOGD(TAG, "Set WB mode to: %d", mode);
}
return ret;
}
static int set_ae_level(sensor_t *sensor, int level)
{
int ret = 0;
// AE level range: -2 to +2
if (level < -2 || level > 2) {
return -1;
}
ret = write_reg(sensor->slv_addr, 0xfe, 0x00);
// Adjust AE target via AEC_TARGET_Y register (Page 0, 0xd3)
// This is the expected luminance value for AEC
// Default is 0x48, adjust based on level
uint8_t ae_target = 0x48 + (level * 0x08);
ret |= write_reg(sensor->slv_addr, AEC_TARGET_Y, ae_target);
if (ret == 0) {
sensor->status.ae_level = level;
ESP_LOGD(TAG, "Set AE level to: %d", level);
}
return ret;
}
static int get_reg(sensor_t *sensor, int reg, int mask)
{
int ret = 0;
if (mask > 0xFF) {
ESP_LOGE(TAG, "mask should not more than 0xff");
} else {
ret = read_reg(sensor->slv_addr, reg);
}
if (ret > 0) {
ret &= mask;
}
return ret;
}
static int set_reg(sensor_t *sensor, int reg, int mask, int value)
{
int ret = 0;
if (mask > 0xFF) {
ESP_LOGE(TAG, "mask should not more than 0xff");
} else {
ret = read_reg(sensor->slv_addr, reg);
}
if (ret < 0) {
return ret;
}
value = (ret & ~mask) | (value & mask);
if (mask > 0xFF) {
} else {
ret = write_reg(sensor->slv_addr, reg, value);
}
return ret;
}
static int init_status(sensor_t *sensor)
{
int ret;
ret = write_reg(sensor->slv_addr, 0xfe, 0x00);
// Read contrast from CONTRAST register (0xb3)
// Default is 0x40, map back to level: (reg_val - 0x40) / 0x10
ret = read_reg(sensor->slv_addr, CONTRAST);
if (ret >= 0) {
int contrast_level = (ret - 0x40) / 0x10;
sensor->status.contrast = (contrast_level < -2) ? -2 : (contrast_level > 2) ? 2 : contrast_level;
} else {
sensor->status.contrast = 0;
}
// Read saturation from SATURATION_Cb register (0xb1)
// Default is 0x40, map back to level: (reg_val - 0x40) / 0x10
ret = read_reg(sensor->slv_addr, SATURATION_Cb);
if (ret >= 0) {
int saturation_level = (ret - 0x40) / 0x10;
sensor->status.saturation = (saturation_level < -2) ? -2 : (saturation_level > 2) ? 2 : saturation_level;
} else {
sensor->status.saturation = 0;
}
// Read sharpness from EDGE12_EFFECT register (0x77)
// GC0308 uses Edge Enhancement registers for sharpness control
// Linux kernel driver uses 0x38 as default
ret = read_reg(sensor->slv_addr, EDGE12_EFFECT);
if (ret >= 0) {
int sharpness_level = (ret - 0x38) / 0x08;
sensor->status.sharpness = (sharpness_level < -2) ? -2 : (sharpness_level > 2) ? 2 : sharpness_level;
} else {
sensor->status.sharpness = 0;
}
// Denoise not supported by GC0308
sensor->status.denoise = 0;
// Read AE level from AEC_TARGET_Y register (P0:0xd3)
// Same register as brightness, but different step size for level calculation
ret = read_reg(sensor->slv_addr, AEC_TARGET_Y);
if (ret >= 0) {
int ae_level = (ret - 0x48) / 0x08;
sensor->status.ae_level = (ae_level < -2) ? -2 : (ae_level > 2) ? 2 : ae_level;
} else {
sensor->status.ae_level = 0;
}
// Gainceiling not supported by GC0308
sensor->status.gainceiling = 0;
// Read AWB, AGC, AEC status from AAAA_EN register (0x22)
sensor->status.awb = check_reg_mask(sensor->slv_addr, AAAA_EN, AWB_ENABLE);
sensor->status.agc = check_reg_mask(sensor->slv_addr, AAAA_EN, AGC_ENABLE);
sensor->status.aec = check_reg_mask(sensor->slv_addr, AAAA_EN, AEC_ENABLE);
// DCW not supported by GC0308
sensor->status.dcw = 0;
// Read hmirror and vflip from CISCTL_MODE1 register (0x14)
sensor->status.hmirror = check_reg_mask(sensor->slv_addr, CISCTL_MODE1, HMIRROR_MASK);
sensor->status.vflip = check_reg_mask(sensor->slv_addr, CISCTL_MODE1, VFLIP_MASK);
// Read colorbar status from OUT_CTRL register (0x2e)
sensor->status.colorbar = check_reg_mask(sensor->slv_addr, OUT_CTRL, 0x01);
// BPC, WPC not specifically tracked by GC0308 driver
sensor->status.bpc = 0;
sensor->status.wpc = 0;
// Raw GMA, LENC not supported by GC0308
sensor->status.raw_gma = 0;
sensor->status.lenc = 0;
// Quality not supported by GC0308
sensor->status.quality = 0;
// Read special effect from SPECIAL_EFFECT register (0x23)
ret = read_reg(sensor->slv_addr, SPECIAL_EFFECT);
if (ret >= 0) {
// Map hardware register value to effect enum
// Bit[1:0] of SPECIAL_EFFECT register holds the effect mode
uint8_t effect_bits = ret & 0x03;
if (effect_bits == EFFECT_NORMAL) {
sensor->status.special_effect = 0; // Normal
} else if (effect_bits == EFFECT_NEGATIVE) {
sensor->status.special_effect = 1; // Negative
} else if (effect_bits == EFFECT_GRAYSCALE) {
// Could be Grayscale (2) or tint effects (3-6) - requires checking FIXED_CB/CR
// For simplicity, default to Grayscale when in EFFECT_GRAYSCALE mode
sensor->status.special_effect = 2; // Grayscale/B&W
} else {
sensor->status.special_effect = 0; // Default to Normal
}
} else {
sensor->status.special_effect = 0;
}
// Read white balance mode from AWB registers (0x5a-0x5c)
if (sensor->status.awb) {
sensor->status.wb_mode = 0; // Auto WB
} else {
// Manual WB - attempt to detect which preset is active by reading gain values
// Per Linux kernel driver: AWB registers are at 0x5a-0x5c
int r_gain = read_reg(sensor->slv_addr, AWB_R_GAIN);
int g_gain = read_reg(sensor->slv_addr, AWB_G_GAIN);
int b_gain = read_reg(sensor->slv_addr, AWB_B_GAIN);
if (r_gain >= 0 && g_gain >= 0 && b_gain >= 0) {
// Match against known presets from Linux kernel driver
// with tolerance for register variation (+/-4 values)
if (r_gain >= 0x70 && r_gain <= 0x78 && g_gain >= 0x4e && g_gain <= 0x56 && b_gain >= 0x3c && b_gain <= 0x44) {
sensor->status.wb_mode = 1; // Sunny/Daylight (0x74, 0x52, 0x40)
} else if (r_gain >= 0x88 && r_gain <= 0x90 && g_gain >= 0x4c && g_gain <= 0x54 && b_gain >= 0x3c && b_gain <= 0x44) {
sensor->status.wb_mode = 2; // Cloudy (0x8c, 0x50, 0x40)
} else if (r_gain >= 0x44 && r_gain <= 0x4c && g_gain >= 0x3c && g_gain <= 0x44 && b_gain >= 0x58 && b_gain <= 0x60) {
sensor->status.wb_mode = 3; // Office/Incandescent (0x48, 0x40, 0x5c)
} else if (r_gain >= 0x3c && r_gain <= 0x44 && g_gain >= 0x3e && g_gain <= 0x46 && b_gain >= 0x4c && b_gain <= 0x54) {
sensor->status.wb_mode = 4; // Home/Fluorescent (0x40, 0x42, 0x50)
} else {
sensor->status.wb_mode = 0; // Unknown/custom - default to Auto
}
} else {
sensor->status.wb_mode = 0; // Read error - default to Auto
}
}
// AWB gain not specifically tracked
sensor->status.awb_gain = 0;
// Read AGC gain from GLOBAL_GAIN register (0x50)
// Hardware range is 0-63 (6-bit), map to 0-30 for API compatibility
ret = read_reg(sensor->slv_addr, GLOBAL_GAIN);
if (ret >= 0) {
// Reverse map from hardware (0-63) to API (0-30)
sensor->status.agc_gain = (ret * 30) / 63;
if (sensor->status.agc_gain > 30) sensor->status.agc_gain = 30;
} else {
sensor->status.agc_gain = 0;
}
// Read AEC value from EXPOSURE registers (0x03, 0x04)
// 12-bit value: high byte (0x03) bits[11:4], low byte (0x04) bits[3:0] in upper nibble
int exp_high = read_reg(sensor->slv_addr, EXPOSURE_HIGH);
int exp_low = read_reg(sensor->slv_addr, EXPOSURE_LOW);
if (exp_high >= 0 && exp_low >= 0) {
sensor->status.aec_value = (exp_high << 4) | ((exp_low >> 4) & 0x0F);
if (sensor->status.aec_value > 1200) sensor->status.aec_value = 1200;
} else {
sensor->status.aec_value = 0;
}
// AEC2 not supported by GC0308
sensor->status.aec2 = 0;
print_regs(sensor->slv_addr);
return 0;
}
static int set_dummy(sensor_t *sensor, int val)
{
ESP_LOGW(TAG, "Unsupported");
return -1;
}
static int set_gainceiling_dummy(sensor_t *sensor, gainceiling_t val)
{
ESP_LOGW(TAG, "Unsupported");
return -1;
}
int esp32_camera_gc0308_detect(int slv_addr, sensor_id_t *id)
{
if (GC0308_SCCB_ADDR == slv_addr) {
write_reg(slv_addr, 0xfe, 0x00);
uint8_t PID = SCCB_Read(slv_addr, 0x00);
if (GC0308_PID == PID) {
id->PID = PID;
return PID;
} else {
ESP_LOGI(TAG, "Mismatch PID=0x%x", PID);
}
}
return 0;
}
int esp32_camera_gc0308_init(sensor_t *sensor)
{
sensor->init_status = init_status;
sensor->reset = reset;
sensor->set_pixformat = set_pixformat;
sensor->set_framesize = set_framesize;
sensor->set_contrast = set_contrast;
sensor->set_brightness = set_dummy;
sensor->set_saturation = set_saturation;
sensor->set_sharpness = set_sharpness;
sensor->set_denoise = set_dummy;
sensor->set_gainceiling = set_gainceiling_dummy;
sensor->set_quality = set_dummy;
sensor->set_colorbar = set_colorbar;
sensor->set_whitebal = set_whitebal;
sensor->set_gain_ctrl = set_gain_ctrl;
sensor->set_exposure_ctrl = set_exposure_ctrl;
sensor->set_hmirror = set_hmirror;
sensor->set_vflip = set_vflip;
sensor->set_aec2 = set_dummy;
sensor->set_awb_gain = set_dummy;
sensor->set_agc_gain = set_agc_gain;
sensor->set_aec_value = set_aec_value;
sensor->set_special_effect = set_special_effect;
sensor->set_wb_mode = set_wb_mode;
sensor->set_ae_level = set_ae_level;
sensor->set_dcw = set_dummy;
sensor->set_bpc = set_dummy;
sensor->set_wpc = set_dummy;
sensor->set_raw_gma = set_dummy;
sensor->set_lenc = set_dummy;
sensor->get_reg = get_reg;
sensor->set_reg = set_reg;
sensor->set_res_raw = NULL;
sensor->set_pll = NULL;
sensor->set_xclk = NULL;
// No autofocus support
sensor->af_is_supported = NULL;
sensor->af_init = NULL;
sensor->af_set_mode = NULL;
sensor->af_trigger = NULL;
sensor->af_get_status = NULL;
sensor->af_set_manual_position = NULL;
ESP_LOGD(TAG, "GC0308 Attached");
return 0;
}
+399
View File
@@ -0,0 +1,399 @@
// Copyright 2015-2021 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "sccb.h"
#include "gc032a.h"
#include "gc032a_regs.h"
#include "gc032a_settings.h"
#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG)
#include "esp32-hal-log.h"
#else
#include "esp_log.h"
static const char *TAG = "gc032a";
#endif
#define H8(v) ((v)>>8)
#define L8(v) ((v)&0xff)
//#define REG_DEBUG_ON
static int read_reg(uint8_t slv_addr, const uint16_t reg)
{
int ret = SCCB_Read(slv_addr, reg);
#ifdef REG_DEBUG_ON
if (ret < 0) {
ESP_LOGE(TAG, "READ REG 0x%04x FAILED: %d", reg, ret);
}
#endif
return ret;
}
static int write_reg(uint8_t slv_addr, const uint16_t reg, uint8_t value)
{
int ret = 0;
#ifndef REG_DEBUG_ON
ret = SCCB_Write(slv_addr, reg, value);
#else
int old_value = read_reg(slv_addr, reg);
if (old_value < 0) {
return old_value;
}
if ((uint8_t)old_value != value) {
ESP_LOGI(TAG, "NEW REG 0x%04x: 0x%02x to 0x%02x", reg, (uint8_t)old_value, value);
ret = SCCB_Write(slv_addr, reg, value);
} else {
ESP_LOGD(TAG, "OLD REG 0x%04x: 0x%02x", reg, (uint8_t)old_value);
ret = SCCB_Write(slv_addr, reg, value);//maybe not?
}
if (ret < 0) {
ESP_LOGE(TAG, "WRITE REG 0x%04x FAILED: %d", reg, ret);
}
#endif
return ret;
}
static int check_reg_mask(uint8_t slv_addr, uint16_t reg, uint8_t mask)
{
return (read_reg(slv_addr, reg) & mask) == mask;
}
static void print_regs(uint8_t slv_addr)
{
#ifdef DEBUG_PRINT_REG
vTaskDelay(pdMS_TO_TICKS(100));
ESP_LOGI(TAG, "REG list look ======================");
for (size_t i = 0xf0; i <= 0xfe; i++) {
ESP_LOGI(TAG, "reg[0x%02x] = 0x%02x", i, read_reg(slv_addr, i));
}
ESP_LOGI(TAG, "\npage 0 ===");
write_reg(slv_addr, 0xfe, 0x00); // page 0
for (size_t i = 0x03; i <= 0x24; i++) {
ESP_LOGI(TAG, "p0 reg[0x%02x] = 0x%02x", i, read_reg(slv_addr, i));
}
for (size_t i = 0x40; i <= 0x95; i++) {
ESP_LOGI(TAG, "p0 reg[0x%02x] = 0x%02x", i, read_reg(slv_addr, i));
}
ESP_LOGI(TAG, "\npage 3 ===");
write_reg(slv_addr, 0xfe, 0x03); // page 3
for (size_t i = 0x01; i <= 0x43; i++) {
ESP_LOGI(TAG, "p3 reg[0x%02x] = 0x%02x", i, read_reg(slv_addr, i));
}
#endif
}
static int set_reg_bits(uint8_t slv_addr, uint16_t reg, uint8_t offset, uint8_t mask, uint8_t value)
{
int ret = 0;
uint8_t c_value, new_value;
ret = read_reg(slv_addr, reg);
if (ret < 0) {
return ret;
}
c_value = ret;
new_value = (c_value & ~(mask << offset)) | ((value & mask) << offset);
ret = write_reg(slv_addr, reg, new_value);
return ret;
}
static int write_regs(uint8_t slv_addr, const uint16_t (*regs)[2])
{
int i = 0, ret = 0;
while (!ret && regs[i][0] != REGLIST_TAIL) {
if (regs[i][0] == REG_DLY) {
vTaskDelay(regs[i][1] / portTICK_PERIOD_MS);
} else {
ret = write_reg(slv_addr, regs[i][0], regs[i][1]);
}
i++;
}
return ret;
}
static int reset(sensor_t *sensor)
{
int ret;
// Software Reset: clear all registers and reset them to their default values
ret = write_reg(sensor->slv_addr, RESET_RELATED, 0xf0);
if (ret) {
ESP_LOGE(TAG, "Software Reset FAILED!");
return ret;
}
vTaskDelay(100 / portTICK_PERIOD_MS);
ret = write_regs(sensor->slv_addr, gc032a_default_regs);
if (ret == 0) {
ESP_LOGD(TAG, "Camera defaults loaded");
vTaskDelay(100 / portTICK_PERIOD_MS);
write_reg(sensor->slv_addr, 0xfe, 0x00);
set_reg_bits(sensor->slv_addr, 0xf7, 1, 0x01, 1); // PLL_mode1:div2en
set_reg_bits(sensor->slv_addr, 0xf7, 7, 0x01, 1); // PLL_mode1:dvp mode
set_reg_bits(sensor->slv_addr, 0xf8, 0, 0x3f, 8); //PLL_mode2 :divx4
set_reg_bits(sensor->slv_addr, 0xfa, 4, 0x0f, 2); //vlk div mode :divide_by
}
return ret;
}
static int set_pixformat(sensor_t *sensor, pixformat_t pixformat)
{
int ret = 0;
switch (pixformat) {
case PIXFORMAT_RGB565:
write_reg(sensor->slv_addr, 0xfe, 0x00);
ret = set_reg_bits(sensor->slv_addr, 0x44, 0, 0x1f, 6); //RGB565
break;
case PIXFORMAT_YUV422:
write_reg(sensor->slv_addr, 0xfe, 0x00);
ret = set_reg_bits(sensor->slv_addr, 0x44, 0, 0x1f, 3);
break;
default:
ESP_LOGW(TAG, "unsupported format");
ret = -1;
break;
}
if (ret == 0) {
sensor->pixformat = pixformat;
ESP_LOGD(TAG, "Set pixformat to: %u", pixformat);
}
return ret;
}
static int set_framesize(sensor_t *sensor, framesize_t framesize)
{
ESP_LOGI(TAG, "set_framesize");
int ret = 0;
if (framesize > FRAMESIZE_VGA) {
ESP_LOGW(TAG, "Invalid framesize: %u", framesize);
framesize = FRAMESIZE_VGA;
}
sensor->status.framesize = framesize;
uint16_t w = resolution[framesize].width;
uint16_t h = resolution[framesize].height;
uint16_t row_s = (resolution[FRAMESIZE_VGA].height - h) / 2;
uint16_t col_s = (resolution[FRAMESIZE_VGA].width - w) / 2;
write_reg(sensor->slv_addr, 0xfe, 0x00);
write_reg(sensor->slv_addr, P0_ROW_START_HIGH, H8(row_s)); // Row_start[8]
write_reg(sensor->slv_addr, P0_ROW_START_LOW, L8(row_s)); // Row_start[7:0]
write_reg(sensor->slv_addr, P0_COLUMN_START_HIGH, H8(col_s)); // Column_start[9:8]
write_reg(sensor->slv_addr, P0_COLUMN_START_LOW, L8(col_s)); // Column_start[7:0]
write_reg(sensor->slv_addr, P0_WINDOW_HEIGHT_HIGH, H8(h + 8)); //window_height [8]
write_reg(sensor->slv_addr, P0_WINDOW_HEIGHT_LOW, L8(h + 8)); //window_height [7:0]
write_reg(sensor->slv_addr, P0_WINDOW_WIDTH_HIGH, H8(w + 8)); //window_width [9:8]
write_reg(sensor->slv_addr, P0_WINDOW_WIDTH_LOW, L8(w + 8)); //window_width [7:0]
write_reg(sensor->slv_addr, P0_WIN_MODE, 0x01);
write_reg(sensor->slv_addr, P0_OUT_WIN_HEIGHT_HIGH, H8(h));
write_reg(sensor->slv_addr, P0_OUT_WIN_HEIGHT_LOW, L8(h));
write_reg(sensor->slv_addr, P0_OUT_WIN_WIDTH_HIGH, H8(w));
write_reg(sensor->slv_addr, P0_OUT_WIN_WIDTH_LOW, L8(w));
if (ret == 0) {
ESP_LOGD(TAG, "Set framesize to: %ux%u", w, h);
}
print_regs(sensor->slv_addr);
return ret;
}
static int set_hmirror(sensor_t *sensor, int enable)
{
int ret = 0;
sensor->status.hmirror = enable;
ret = write_reg(sensor->slv_addr, 0xfe, 0x00);
ret |= set_reg_bits(sensor->slv_addr, P0_CISCTL_MODE1, 0, 0x01, enable);
if (ret == 0) {
ESP_LOGD(TAG, "Set h-mirror to: %d", enable);
}
return ret;
}
static int set_vflip(sensor_t *sensor, int enable)
{
int ret = 0;
sensor->status.vflip = enable;
ret = write_reg(sensor->slv_addr, 0xfe, 0x00);
ret |= set_reg_bits(sensor->slv_addr, P0_CISCTL_MODE1, 1, 0x01, enable);
if (ret == 0) {
ESP_LOGD(TAG, "Set v-flip to: %d", enable);
}
return ret;
}
static int set_colorbar(sensor_t *sensor, int enable)
{
int ret = 0;
ret = write_reg(sensor->slv_addr, 0xfe, 0x00);
ret |= set_reg_bits(sensor->slv_addr, P0_DEBUG_MODE2, 3, 0x01, enable);
if (ret == 0) {
sensor->status.colorbar = enable;
ESP_LOGD(TAG, "Set colorbar to: %d", enable);
}
return ret;
}
static int get_reg(sensor_t *sensor, int reg, int mask)
{
int ret = 0;
if (mask > 0xFF) {
ESP_LOGE(TAG, "mask should not more than 0xff");
} else {
ret = read_reg(sensor->slv_addr, reg);
}
if (ret > 0) {
ret &= mask;
}
return ret;
}
static int set_reg(sensor_t *sensor, int reg, int mask, int value)
{
int ret = 0;
if (mask > 0xFF) {
ESP_LOGE(TAG, "mask should not more than 0xff");
} else {
ret = read_reg(sensor->slv_addr, reg);
}
if (ret < 0) {
return ret;
}
value = (ret & ~mask) | (value & mask);
if (mask > 0xFF) {
} else {
ret = write_reg(sensor->slv_addr, reg, value);
}
return ret;
}
static int init_status(sensor_t *sensor)
{
write_reg(sensor->slv_addr, 0xfe, 0x00);
sensor->status.brightness = 0;
sensor->status.contrast = 0;
sensor->status.saturation = 0;
sensor->status.sharpness = 0;
sensor->status.denoise = 0;
sensor->status.ae_level = 0;
sensor->status.gainceiling = 0;
sensor->status.awb = 0;
sensor->status.dcw = 0;
sensor->status.agc = 0;
sensor->status.aec = 0;
sensor->status.hmirror = check_reg_mask(sensor->slv_addr, P0_CISCTL_MODE1, 0x01);
sensor->status.vflip = check_reg_mask(sensor->slv_addr, P0_CISCTL_MODE1, 0x02);
sensor->status.colorbar = 0;
sensor->status.bpc = 0;
sensor->status.wpc = 0;
sensor->status.raw_gma = 0;
sensor->status.lenc = 0;
sensor->status.quality = 0;
sensor->status.special_effect = 0;
sensor->status.wb_mode = 0;
sensor->status.awb_gain = 0;
sensor->status.agc_gain = 0;
sensor->status.aec_value = 0;
sensor->status.aec2 = 0;
return 0;
}
static int set_dummy(sensor_t *sensor, int val)
{
ESP_LOGW(TAG, "Unsupported");
return -1;
}
static int set_gainceiling_dummy(sensor_t *sensor, gainceiling_t val)
{
ESP_LOGW(TAG, "Unsupported");
return -1;
}
int esp32_camera_gc032a_detect(int slv_addr, sensor_id_t *id)
{
if (GC032A_SCCB_ADDR == slv_addr) {
uint8_t MIDL = SCCB_Read(slv_addr, SENSOR_ID_LOW);
uint8_t MIDH = SCCB_Read(slv_addr, SENSOR_ID_HIGH);
uint16_t PID = MIDH << 8 | MIDL;
if (GC032A_PID == PID) {
id->PID = PID;
return PID;
} else {
ESP_LOGI(TAG, "Mismatch PID=0x%x", PID);
}
}
return 0;
}
int esp32_camera_gc032a_init(sensor_t *sensor)
{
sensor->init_status = init_status;
sensor->reset = reset;
sensor->set_pixformat = set_pixformat;
sensor->set_framesize = set_framesize;
sensor->set_contrast = set_dummy;
sensor->set_brightness = set_dummy;
sensor->set_saturation = set_dummy;
sensor->set_sharpness = set_dummy;
sensor->set_denoise = set_dummy;
sensor->set_gainceiling = set_gainceiling_dummy;
sensor->set_quality = set_dummy;
sensor->set_colorbar = set_colorbar;
sensor->set_whitebal = set_dummy;
sensor->set_gain_ctrl = set_dummy;
sensor->set_exposure_ctrl = set_dummy;
sensor->set_hmirror = set_hmirror;
sensor->set_vflip = set_vflip;
sensor->set_aec2 = set_dummy;
sensor->set_awb_gain = set_dummy;
sensor->set_agc_gain = set_dummy;
sensor->set_aec_value = set_dummy;
sensor->set_special_effect = set_dummy;
sensor->set_wb_mode = set_dummy;
sensor->set_ae_level = set_dummy;
sensor->set_dcw = set_dummy;
sensor->set_bpc = set_dummy;
sensor->set_wpc = set_dummy;
sensor->set_raw_gma = set_dummy;
sensor->set_lenc = set_dummy;
sensor->get_reg = get_reg;
sensor->set_reg = set_reg;
sensor->set_res_raw = NULL;
sensor->set_pll = NULL;
sensor->set_xclk = NULL;
// No autofocus support
sensor->af_is_supported = NULL;
sensor->af_init = NULL;
sensor->af_set_mode = NULL;
sensor->af_trigger = NULL;
sensor->af_get_status = NULL;
sensor->af_set_manual_position = NULL;
ESP_LOGD(TAG, "GC032A Attached");
return 0;
}
+485
View File
@@ -0,0 +1,485 @@
// Copyright 2015-2021 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "sccb.h"
#include "gc2145.h"
#include "gc2145_regs.h"
#include "gc2145_settings.h"
#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG)
#include "esp32-hal-log.h"
#else
#include "esp_log.h"
static const char *TAG = "gc2145";
#endif
#define H8(v) ((v)>>8)
#define L8(v) ((v)&0xff)
//#define REG_DEBUG_ON
static int read_reg(uint8_t slv_addr, const uint16_t reg)
{
int ret = SCCB_Read(slv_addr, reg);
#ifdef REG_DEBUG_ON
if (ret < 0) {
ESP_LOGE(TAG, "READ REG 0x%04x FAILED: %d", reg, ret);
}
#endif
return ret;
}
static int write_reg(uint8_t slv_addr, const uint16_t reg, uint8_t value)
{
int ret = 0;
#ifndef REG_DEBUG_ON
ret = SCCB_Write(slv_addr, reg, value);
#else
int old_value = read_reg(slv_addr, reg);
if (old_value < 0) {
return old_value;
}
if ((uint8_t)old_value != value) {
ESP_LOGI(TAG, "NEW REG 0x%04x: 0x%02x to 0x%02x", reg, (uint8_t)old_value, value);
ret = SCCB_Write(slv_addr, reg, value);
} else {
ESP_LOGD(TAG, "OLD REG 0x%04x: 0x%02x", reg, (uint8_t)old_value);
ret = SCCB_Write(slv_addr, reg, value);//maybe not?
}
if (ret < 0) {
ESP_LOGE(TAG, "WRITE REG 0x%04x FAILED: %d", reg, ret);
}
#endif
return ret;
}
static int check_reg_mask(uint8_t slv_addr, uint16_t reg, uint8_t mask)
{
return (read_reg(slv_addr, reg) & mask) == mask;
}
static int set_reg_bits(uint8_t slv_addr, uint16_t reg, uint8_t offset, uint8_t mask, uint8_t value)
{
int ret = 0;
uint8_t c_value, new_value;
ret = read_reg(slv_addr, reg);
if (ret < 0) {
return ret;
}
c_value = ret;
new_value = (c_value & ~(mask << offset)) | ((value & mask) << offset);
ret = write_reg(slv_addr, reg, new_value);
return ret;
}
static int write_regs(uint8_t slv_addr, const uint16_t (*regs)[2])
{
int i = 0, ret = 0;
while (!ret && regs[i][0] != REGLIST_TAIL) {
if (regs[i][0] == REG_DLY) {
vTaskDelay(regs[i][1] / portTICK_PERIOD_MS);
} else {
ret = write_reg(slv_addr, regs[i][0], regs[i][1]);
}
i++;
}
return ret;
}
static void print_regs(uint8_t slv_addr)
{
#ifdef DEBUG_PRINT_REG
vTaskDelay(pdMS_TO_TICKS(100));
ESP_LOGI(TAG, "REG list look ======================");
for (size_t i = 0xf0; i <= 0xfe; i++) {
ESP_LOGI(TAG, "reg[0x%02x] = 0x%02x", i, read_reg(slv_addr, i));
}
ESP_LOGI(TAG, "\npage 0 ===");
write_reg(slv_addr, 0xfe, 0x00); // page 0
for (size_t i = 0x03; i <= 0x24; i++) {
ESP_LOGI(TAG, "p0 reg[0x%02x] = 0x%02x", i, read_reg(slv_addr, i));
}
for (size_t i = 0x80; i <= 0xa2; i++) {
ESP_LOGI(TAG, "p0 reg[0x%02x] = 0x%02x", i, read_reg(slv_addr, i));
}
ESP_LOGI(TAG, "\npage 3 ===");
write_reg(slv_addr, 0xfe, 0x03); // page 3
for (size_t i = 0x01; i <= 0x43; i++) {
ESP_LOGI(TAG, "p3 reg[0x%02x] = 0x%02x", i, read_reg(slv_addr, i));
}
#endif
}
static int reset(sensor_t *sensor)
{
int ret = 0;
// Software Reset: clear all registers and reset them to their default values
ret = write_reg(sensor->slv_addr, RESET_RELATED, 0xe0);
if (ret) {
ESP_LOGE(TAG, "Software Reset FAILED!");
return ret;
}
vTaskDelay(100 / portTICK_PERIOD_MS);
ret = write_regs(sensor->slv_addr, gc2145_default_init_regs);
if (ret == 0) {
ESP_LOGD(TAG, "Camera defaults loaded");
vTaskDelay(100 / portTICK_PERIOD_MS);
#ifdef CONFIG_IDF_TARGET_ESP32
write_reg(sensor->slv_addr, 0xfe, 0x00);
//ensure pclk <= 15MHz for esp32
set_reg_bits(sensor->slv_addr, 0xf8, 0, 0x3f, 2); // divx4
set_reg_bits(sensor->slv_addr, 0xfa, 4, 0x0f, 2); // divide_by
#endif
}
return ret;
}
static int set_pixformat(sensor_t *sensor, pixformat_t pixformat)
{
int ret = 0;
switch (pixformat) {
case PIXFORMAT_RGB565:
write_reg(sensor->slv_addr, 0xfe, 0x00);
ret = set_reg_bits(sensor->slv_addr, P0_OUTPUT_FORMAT, 0, 0x1f, 6); //RGB565
break;
case PIXFORMAT_YUV422:
write_reg(sensor->slv_addr, 0xfe, 0x00);
ret = set_reg_bits(sensor->slv_addr, P0_OUTPUT_FORMAT, 0, 0x1f, 2); //yuv422
break;
default:
ESP_LOGW(TAG, "unsupported format");
ret = -1;
break;
}
if (ret == 0) {
sensor->pixformat = pixformat;
ESP_LOGD(TAG, "Set pixformat to: %u", pixformat);
}
return ret;
}
static int set_framesize(sensor_t *sensor, framesize_t framesize)
{
int ret = 0;
if (framesize > FRAMESIZE_UXGA) {
ESP_LOGW(TAG, "Invalid framesize: %u", framesize);
framesize = FRAMESIZE_UXGA;
}
sensor->status.framesize = framesize;
uint16_t w = resolution[framesize].width;
uint16_t h = resolution[framesize].height;
uint16_t row_s = (resolution[FRAMESIZE_UXGA].height - h) / 2;
uint16_t col_s = (resolution[FRAMESIZE_UXGA].width - w) / 2;
(void)row_s;
(void)col_s;
#if CONFIG_GC_SENSOR_SUBSAMPLE_MODE
struct subsample_cfg {
uint16_t ratio_numerator;
uint16_t ratio_denominator;
uint8_t reg0x99;
uint8_t reg0x9b;
uint8_t reg0x9c;
uint8_t reg0x9d;
uint8_t reg0x9e;
uint8_t reg0x9f;
uint8_t reg0xa0;
uint8_t reg0xa1;
uint8_t reg0xa2;
};
const struct subsample_cfg subsample_cfgs[] = { // define some subsample ratio
// {60, 420, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, //1/7 // A smaller ratio brings a larger view, but it reduces the frame rate
// {84, 420, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, //1/5
// {105, 420, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},//1/4
{140, 420, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},//1/3
{210, 420, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},//1/2
{240, 420, 0x77, 0x02, 0x46, 0x02, 0x46, 0x02, 0x46, 0x02, 0x46},//4/7
{252, 420, 0x55, 0x02, 0x04, 0x02, 0x04, 0x02, 0x04, 0x02, 0x04},//3/5
{280, 420, 0x33, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00},//2/3
{420, 420, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},//1/1
};
uint16_t win_w = resolution[FRAMESIZE_UXGA].width;
uint16_t win_h = resolution[FRAMESIZE_UXGA].height;
const struct subsample_cfg *cfg = NULL;
/**
* Strategy: try to keep the maximum perspective
*/
uint8_t i = 0;
if (framesize >= FRAMESIZE_QVGA) {
i = 1;
}
for (; i < sizeof(subsample_cfgs) / sizeof(struct subsample_cfg); i++) {
cfg = &subsample_cfgs[i];
if ((win_w * cfg->ratio_numerator / cfg->ratio_denominator >= w) && (win_h * cfg->ratio_numerator / cfg->ratio_denominator >= h)) {
win_w = w * cfg->ratio_denominator / cfg->ratio_numerator;
win_h = h * cfg->ratio_denominator / cfg->ratio_numerator;
row_s = (resolution[FRAMESIZE_UXGA].height - win_h) / 2;
col_s = (resolution[FRAMESIZE_UXGA].width - win_w) / 2;
ESP_LOGI(TAG, "subsample win:%dx%d, ratio:%f", win_w, win_h, (float)cfg->ratio_numerator / (float)cfg->ratio_denominator);
break;
}
}
write_reg(sensor->slv_addr, 0xfe, 0x00);
write_reg(sensor->slv_addr, P0_CROP_ENABLE, 0x01);
write_reg(sensor->slv_addr, 0x09, H8(row_s));
write_reg(sensor->slv_addr, 0x0a, L8(row_s));
write_reg(sensor->slv_addr, 0x0b, H8(col_s));
write_reg(sensor->slv_addr, 0x0c, L8(col_s));
write_reg(sensor->slv_addr, 0x0d, H8(win_h + 8));
write_reg(sensor->slv_addr, 0x0e, L8(win_h + 8));
write_reg(sensor->slv_addr, 0x0f, H8(win_w + 16));
write_reg(sensor->slv_addr, 0x10, L8(win_w + 16));
write_reg(sensor->slv_addr, 0x99, cfg->reg0x99);
write_reg(sensor->slv_addr, 0x9b, cfg->reg0x9b);
write_reg(sensor->slv_addr, 0x9c, cfg->reg0x9c);
write_reg(sensor->slv_addr, 0x9d, cfg->reg0x9d);
write_reg(sensor->slv_addr, 0x9e, cfg->reg0x9e);
write_reg(sensor->slv_addr, 0x9f, cfg->reg0x9f);
write_reg(sensor->slv_addr, 0xa0, cfg->reg0xa0);
write_reg(sensor->slv_addr, 0xa1, cfg->reg0xa1);
write_reg(sensor->slv_addr, 0xa2, cfg->reg0xa2);
write_reg(sensor->slv_addr, 0x95, H8(h));
write_reg(sensor->slv_addr, 0x96, L8(h));
write_reg(sensor->slv_addr, 0x97, H8(w));
write_reg(sensor->slv_addr, 0x98, L8(w));
#elif CONFIG_GC_SENSOR_WINDOWING_MODE
write_reg(sensor->slv_addr, 0xfe, 0x00);
write_reg(sensor->slv_addr, P0_CROP_ENABLE, 0x01);
// write_reg(sensor->slv_addr, 0xec, col_s / 8); //measure window
// write_reg(sensor->slv_addr, 0xed, row_s / 8);
// write_reg(sensor->slv_addr, 0xee, (col_s + h) / 8);
// write_reg(sensor->slv_addr, 0xef, (row_s + w) / 8);
write_reg(sensor->slv_addr, 0x09, H8(row_s));
write_reg(sensor->slv_addr, 0x0a, L8(row_s));
write_reg(sensor->slv_addr, 0x0b, H8(col_s));
write_reg(sensor->slv_addr, 0x0c, L8(col_s));
write_reg(sensor->slv_addr, 0x0d, H8(h + 8));
write_reg(sensor->slv_addr, 0x0e, L8(h + 8));
write_reg(sensor->slv_addr, 0x0f, H8(w + 8));
write_reg(sensor->slv_addr, 0x10, L8(w + 8));
write_reg(sensor->slv_addr, 0x95, H8(h));
write_reg(sensor->slv_addr, 0x96, L8(h));
write_reg(sensor->slv_addr, 0x97, H8(w));
write_reg(sensor->slv_addr, 0x98, L8(w));
#endif
if (ret == 0) {
ESP_LOGD(TAG, "Set framesize to: %ux%u", w, h);
}
return ret;
}
static int set_hmirror(sensor_t *sensor, int enable)
{
int ret = 0;
sensor->status.hmirror = enable;
ret = write_reg(sensor->slv_addr, 0xfe, 0x00);
ret |= set_reg_bits(sensor->slv_addr, P0_ANALOG_MODE1, 0, 0x01, enable != 0);
if (ret == 0) {
ESP_LOGD(TAG, "Set h-mirror to: %d", enable);
}
return ret;
}
static int set_vflip(sensor_t *sensor, int enable)
{
int ret = 0;
sensor->status.vflip = enable;
ret = write_reg(sensor->slv_addr, 0xfe, 0x00);
ret |= set_reg_bits(sensor->slv_addr, P0_ANALOG_MODE1, 1, 0x01, enable != 0);
if (ret == 0) {
ESP_LOGD(TAG, "Set v-flip to: %d", enable);
}
return ret;
}
static int set_colorbar(sensor_t *sensor, int enable)
{
int ret = 0;
// ret = write_reg(sensor->slv_addr, 0xfe, 0x00);
// ret |= set_reg_bits(sensor->slv_addr, P0_DEBUG_MODE3, 3, 0x01, enable);
if (ret == 0) {
sensor->status.colorbar = enable;
ESP_LOGD(TAG, "Set colorbar to: %d", enable);
}
return ret;
}
static int get_reg(sensor_t *sensor, int reg, int mask)
{
int ret = 0;
if (mask > 0xFF) {
ESP_LOGE(TAG, "mask should not more than 0xff");
} else {
ret = read_reg(sensor->slv_addr, reg);
}
if (ret > 0) {
ret &= mask;
}
return ret;
}
static int set_reg(sensor_t *sensor, int reg, int mask, int value)
{
int ret = 0;
if (mask > 0xFF) {
ESP_LOGE(TAG, "mask should not more than 0xff");
} else {
ret = read_reg(sensor->slv_addr, reg);
}
if (ret < 0) {
return ret;
}
value = (ret & ~mask) | (value & mask);
if (mask > 0xFF) {
} else {
ret = write_reg(sensor->slv_addr, reg, value);
}
return ret;
}
static int init_status(sensor_t *sensor)
{
write_reg(sensor->slv_addr, 0xfe, 0x00);
sensor->status.brightness = 0;
sensor->status.contrast = 0;
sensor->status.saturation = 0;
sensor->status.sharpness = 0;
sensor->status.denoise = 0;
sensor->status.ae_level = 0;
sensor->status.gainceiling = 0;
sensor->status.awb = 0;
sensor->status.dcw = 0;
sensor->status.agc = 0;
sensor->status.aec = 0;
sensor->status.hmirror = check_reg_mask(sensor->slv_addr, P0_ANALOG_MODE1, 0x01);
sensor->status.vflip = check_reg_mask(sensor->slv_addr, P0_ANALOG_MODE1, 0x02);
sensor->status.colorbar = 0;
sensor->status.bpc = 0;
sensor->status.wpc = 0;
sensor->status.raw_gma = 0;
sensor->status.lenc = 0;
sensor->status.quality = 0;
sensor->status.special_effect = 0;
sensor->status.wb_mode = 0;
sensor->status.awb_gain = 0;
sensor->status.agc_gain = 0;
sensor->status.aec_value = 0;
sensor->status.aec2 = 0;
print_regs(sensor->slv_addr);
return 0;
}
static int set_dummy(sensor_t *sensor, int val)
{
ESP_LOGW(TAG, "Unsupported");
return -1;
}
static int set_gainceiling_dummy(sensor_t *sensor, gainceiling_t val)
{
ESP_LOGW(TAG, "Unsupported");
return -1;
}
int esp32_camera_gc2145_detect(int slv_addr, sensor_id_t *id)
{
if (GC2145_SCCB_ADDR == slv_addr) {
uint8_t MIDL = SCCB_Read(slv_addr, CHIP_ID_LOW);
uint8_t MIDH = SCCB_Read(slv_addr, CHIP_ID_HIGH);
uint16_t PID = MIDH << 8 | MIDL;
if (GC2145_PID == PID) {
id->PID = PID;
return PID;
} else {
ESP_LOGI(TAG, "Mismatch PID=0x%x", PID);
}
}
return 0;
}
int esp32_camera_gc2145_init(sensor_t *sensor)
{
sensor->init_status = init_status;
sensor->reset = reset;
sensor->set_pixformat = set_pixformat;
sensor->set_framesize = set_framesize;
sensor->set_contrast = set_dummy;
sensor->set_brightness = set_dummy;
sensor->set_saturation = set_dummy;
sensor->set_sharpness = set_dummy;
sensor->set_denoise = set_dummy;
sensor->set_gainceiling = set_gainceiling_dummy;
sensor->set_quality = set_dummy;
sensor->set_colorbar = set_colorbar;
sensor->set_whitebal = set_dummy;
sensor->set_gain_ctrl = set_dummy;
sensor->set_exposure_ctrl = set_dummy;
sensor->set_hmirror = set_hmirror;
sensor->set_vflip = set_vflip;
sensor->set_aec2 = set_dummy;
sensor->set_awb_gain = set_dummy;
sensor->set_agc_gain = set_dummy;
sensor->set_aec_value = set_dummy;
sensor->set_special_effect = set_dummy;
sensor->set_wb_mode = set_dummy;
sensor->set_ae_level = set_dummy;
sensor->set_dcw = set_dummy;
sensor->set_bpc = set_dummy;
sensor->set_wpc = set_dummy;
sensor->set_raw_gma = set_dummy;
sensor->set_lenc = set_dummy;
sensor->get_reg = get_reg;
sensor->set_reg = set_reg;
sensor->set_res_raw = NULL;
sensor->set_pll = NULL;
sensor->set_xclk = NULL;
// No autofocus support
sensor->af_is_supported = NULL;
sensor->af_init = NULL;
sensor->af_set_mode = NULL;
sensor->af_trigger = NULL;
sensor->af_get_status = NULL;
sensor->af_set_manual_position = NULL;
ESP_LOGD(TAG, "GC2145 Attached");
return 0;
}
+484
View File
@@ -0,0 +1,484 @@
/*
*
* HM0360 driver.
*
*/
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "sccb.h"
#include "xclk.h"
#include "hm0360.h"
#include "hm0360_regs.h"
#include "hm0360_settings.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG)
#include "esp32-hal-log.h"
#else
#include "esp_log.h"
static const char *TAG = "HM0360";
#endif
// #define REG_DEBUG_ON
static int _set_pll(sensor_t *sensor, int bypass, int multiplier, int sys_div, int root_2x, int pre_div, int seld5, int pclk_manual, int pclk_div);
static int read_reg(uint8_t slv_addr, const uint16_t reg)
{
int ret = SCCB_Read16(slv_addr, reg);
#ifdef REG_DEBUG_ON
if (ret < 0) {
ESP_LOGE(TAG, "READ REG 0x%04x FAILED: %d", reg, ret);
}
#endif
return ret;
}
static int check_reg_mask(uint8_t slv_addr, uint16_t reg, uint8_t mask)
{
return (read_reg(slv_addr, reg) & mask) == mask;
}
static int read_reg16(uint8_t slv_addr, const uint16_t reg)
{
int ret = 0, ret2 = 0;
ret = read_reg(slv_addr, reg);
if (ret >= 0) {
ret = (ret & 0xFF) << 8;
ret2 = read_reg(slv_addr, reg + 1);
if (ret2 < 0) {
ret = ret2;
} else {
ret |= ret2 & 0xFF;
}
}
return ret;
}
static int write_reg(uint8_t slv_addr, const uint16_t reg, uint8_t value)
{
int ret = 0;
#ifndef REG_DEBUG_ON
ret = SCCB_Write16(slv_addr, reg, value);
#else
int old_value = read_reg(slv_addr, reg);
if (old_value < 0) {
return old_value;
}
if ((uint8_t)old_value != value) {
ESP_LOGD(TAG, "NEW REG 0x%04x: 0x%02x to 0x%02x", reg, (uint8_t)old_value, value);
ret = SCCB_Write16(slv_addr, reg, value);
} else {
ESP_LOGD(TAG, "OLD REG 0x%04x: 0x%02x", reg, (uint8_t)old_value);
ret = SCCB_Write16(slv_addr, reg, value); // maybe not?
}
if (ret < 0) {
ESP_LOGE(TAG, "WRITE REG 0x%04x FAILED: %d", reg, ret);
}
#endif
return ret;
}
static int set_reg_bits(uint8_t slv_addr, uint16_t reg, uint8_t offset, uint8_t mask, uint8_t value)
{
int ret = 0;
uint8_t c_value, new_value;
ret = read_reg(slv_addr, reg);
if (ret < 0) {
return ret;
}
c_value = ret;
new_value = (c_value & ~(mask << offset)) | ((value & mask) << offset);
ret = write_reg(slv_addr, reg, new_value);
return ret;
}
static int write_regs(uint8_t slv_addr, const uint16_t (*regs)[2])
{
int i = 0, ret = 0;
while (!ret && regs[i][0] != REGLIST_TAIL) {
if (regs[i][0] == REG_DLY) {
vTaskDelay(regs[i][1] / portTICK_PERIOD_MS);
} else {
ret = write_reg(slv_addr, regs[i][0], regs[i][1]);
}
i++;
}
return ret;
}
static int write_reg16(uint8_t slv_addr, const uint16_t reg, uint16_t value)
{
if (write_reg(slv_addr, reg, value >> 8) || write_reg(slv_addr, reg + 1, value)) {
return -1;
}
return 0;
}
static int write_addr_reg(uint8_t slv_addr, const uint16_t reg, uint16_t x_value, uint16_t y_value)
{
if (write_reg16(slv_addr, reg, x_value) || write_reg16(slv_addr, reg + 2, y_value)) {
return -1;
}
return 0;
}
#define write_reg_bits(slv_addr, reg, mask, enable) set_reg_bits(slv_addr, reg, 0, mask, (enable) ? (mask) : 0)
static int reset(sensor_t *sensor)
{
vTaskDelay(100 / portTICK_PERIOD_MS);
int ret = 0;
// Software Reset: clear all registers and reset them to their default values
ret = write_reg(sensor->slv_addr, SW_RESET, 0x00);
if (ret) {
ESP_LOGE(TAG, "Software Reset FAILED!");
return ret;
}
vTaskDelay(100 / portTICK_PERIOD_MS);
ret = write_regs(sensor->slv_addr, sensor_default_regs);
if (ret == 0) {
ESP_LOGD(TAG, "Camera defaults loaded");
vTaskDelay(100 / portTICK_PERIOD_MS);
}
return ret;
}
static int set_pixformat(sensor_t *sensor, pixformat_t pixformat)
{
int ret = 0;
sensor->pixformat = pixformat;
switch (pixformat) {
case PIXFORMAT_GRAYSCALE:
break;
default:
ESP_LOGE(TAG, "Only support GRAYSCALE");
return -1;
}
return ret;
}
static int set_framesize(sensor_t *sensor, framesize_t framesize)
{
int ret = 0;
sensor->status.framesize = framesize;
ret = write_regs(sensor->slv_addr, sensor_default_regs);
if (framesize == FRAMESIZE_QQVGA) {
ESP_LOGI(TAG, "Set FRAMESIZE_QQVGA");
ret |= write_regs(sensor->slv_addr, sensor_framesize_QQVGA);
ret |= set_reg_bits(sensor->slv_addr, 0x3024, 0, 0x01, 1);
} else if (framesize == FRAMESIZE_QVGA) {
ESP_LOGI(TAG, "Set FRAMESIZE_QVGA");
ret |= write_regs(sensor->slv_addr, sensor_framesize_QVGA);
ret |= set_reg_bits(sensor->slv_addr, 0x3024, 0, 0x01, 1);
} else if (framesize == FRAMESIZE_VGA) {
ESP_LOGI(TAG, "Set FRAMESIZE_VGA");
ret |= set_reg_bits(sensor->slv_addr, 0x3024, 0, 0x01, 0);
} else {
ESP_LOGI(TAG, "Dont support this size, Set FRAMESIZE_VGA");
ret |= set_reg_bits(sensor->slv_addr, 0x3024, 0, 0x01, 0);
}
if (ret == 0) {
_set_pll(sensor, 0, 0, 0, 0, 0, 0, 0, 0);
ret |= write_reg(sensor->slv_addr, 0x0104, 0x01);
}
return ret;
}
static int set_hmirror(sensor_t *sensor, int enable)
{
if (set_reg_bits(sensor->slv_addr, 0x0101, 0, 0x01, enable)) {
return -1;
}
ESP_LOGD(TAG, "Set h-mirror to: %d", enable);
return 0;
}
static int set_vflip(sensor_t *sensor, int enable)
{
if (set_reg_bits(sensor->slv_addr, 0x0101, 1, 0x01, enable)) {
return -1;
}
ESP_LOGD(TAG, "Set v-flip to: %d", enable);
return 0;
}
static int set_colorbar(sensor_t *sensor, int enable)
{
if (set_reg_bits(sensor->slv_addr, 0x0601, 0, 0x01, enable)) {
return -1;
}
ESP_LOGD(TAG, "Set color-bar to: %d", enable);
return 0;
}
static int set_exposure_ctrl(sensor_t *sensor, int enable)
{
if (set_reg_bits(sensor->slv_addr, 0x2000, 0, 0x01, enable)) {
return -1;
}
ESP_LOGD(TAG, "Set exposure to: %d", enable);
return 0;
}
static int set_brightness(sensor_t *sensor, int level)
{
uint8_t ae_mean;
switch (level) {
case 0:
ae_mean = 60;
break;
case 1:
ae_mean = 80;
break;
case 2:
ae_mean = 100;
break;
case 3:
ae_mean = 127;
break;
default:
ae_mean = 80;
}
return write_reg(sensor->slv_addr, AE_TARGET_MEAN, ae_mean);
}
static int get_reg(sensor_t *sensor, int reg, int mask)
{
int ret = 0, ret2 = 0;
if (mask > 0xFF) {
ret = read_reg16(sensor->slv_addr, reg);
if (ret >= 0 && mask > 0xFFFF) {
ret2 = read_reg(sensor->slv_addr, reg + 2);
if (ret2 >= 0) {
ret = (ret << 8) | ret2;
} else {
ret = ret2;
}
}
} else {
ret = read_reg(sensor->slv_addr, reg);
}
if (ret > 0) {
ret &= mask;
}
return ret;
}
static int set_reg(sensor_t *sensor, int reg, int mask, int value)
{
int ret = 0, ret2 = 0;
if (mask > 0xFF) {
ret = read_reg16(sensor->slv_addr, reg);
if (ret >= 0 && mask > 0xFFFF) {
ret2 = read_reg(sensor->slv_addr, reg + 2);
if (ret2 >= 0) {
ret = (ret << 8) | ret2;
} else {
ret = ret2;
}
}
} else {
ret = read_reg(sensor->slv_addr, reg);
}
if (ret < 0) {
return ret;
}
value = (ret & ~mask) | (value & mask);
if (mask > 0xFFFF) {
ret = write_reg16(sensor->slv_addr, reg, value >> 8);
if (ret >= 0) {
ret = write_reg(sensor->slv_addr, reg + 2, value & 0xFF);
}
} else if (mask > 0xFF) {
ret = write_reg16(sensor->slv_addr, reg, value);
} else {
ret = write_reg(sensor->slv_addr, reg, value);
}
return ret;
}
static int set_xclk(sensor_t *sensor, int timer, int xclk)
{
int ret = 0;
sensor->xclk_freq_hz = xclk * 1000000U;
ret = xclk_timer_conf(timer, sensor->xclk_freq_hz);
if (ret == 0) {
ESP_LOGD(TAG, "Set xclk to %d", xclk);
}
return ret;
}
static int _set_pll(sensor_t *sensor, int bypass, int multiplier, int sys_div, int root_2x, int pre_div, int seld5, int pclk_manual, int pclk_div)
{
(void)bypass;
(void)multiplier;
(void)sys_div;
(void)root_2x;
(void)pre_div;
(void)seld5;
(void)pclk_manual;
(void)pclk_div;
uint8_t value = 0;
uint8_t pll_cfg = 0;
if (sensor->xclk_freq_hz <= 6000000) {
value = 0x03;
} else if (sensor->xclk_freq_hz <= 12000000) {
value = 0x02;
} else if (sensor->xclk_freq_hz <= 18000000) {
value = 0x01;
} else { // max is 48000000
value = 0x00;
}
int ret = read_reg(sensor->slv_addr, PLL1CFG);
if (ret < 0) {
return ret;
}
if (ret > 0xFF) {
/*
* Guard against unexpected wide register values. If read_reg
* ever returns a 16-bit result, reject values that don't fit
* in a single byte to avoid truncation.
*/
return -ERANGE;
}
pll_cfg = (uint8_t)ret;
return write_reg(sensor->slv_addr, PLL1CFG, (pll_cfg & 0xFC) | value);
}
static int set_dummy(sensor_t *sensor, int val)
{
ESP_LOGW(TAG, "Unsupported");
return -1;
}
static int set_gainceiling_dummy(sensor_t *sensor, gainceiling_t val)
{
ESP_LOGW(TAG, "Unsupported");
return -1;
}
static int init_status(sensor_t *sensor)
{
(void) write_addr_reg;
sensor->status.brightness = 0;
sensor->status.contrast = 0;
sensor->status.saturation = 0;
sensor->status.sharpness = 0;
sensor->status.denoise = 0;
sensor->status.ae_level = 0;
sensor->status.awb = 0;
sensor->status.aec = 0;
sensor->status.hmirror = check_reg_mask(sensor->slv_addr, 0x101, 0x01);
sensor->status.vflip = check_reg_mask(sensor->slv_addr, 0x101, 0x02);
sensor->status.lenc = 0;
sensor->status.awb_gain = 0;
sensor->status.agc_gain = 0;
sensor->status.aec_value = 0;
return 0;
}
int esp32_camera_hm0360_detect(int slv_addr, sensor_id_t *id)
{
if (HM1055_SCCB_ADDR == slv_addr) {
uint8_t h = SCCB_Read16(slv_addr, MODEL_ID_H);
uint8_t l = SCCB_Read16(slv_addr, MODEL_ID_L);
uint16_t PID = (h << 8) | l;
if (HM0360_PID == PID) {
id->PID = PID;
id->VER = SCCB_Read16(slv_addr, SILICON_REV);
return PID;
} else {
ESP_LOGD(TAG, "Mismatch PID=0x%x", PID);
}
}
return 0;
}
int esp32_camera_hm0360_init(sensor_t *sensor)
{
sensor->reset = reset;
sensor->set_pixformat = set_pixformat;
sensor->set_framesize = set_framesize;
sensor->set_contrast = set_dummy;
sensor->set_brightness = set_brightness;
sensor->set_saturation = set_dummy;
sensor->set_sharpness = set_dummy;
sensor->set_gainceiling = set_gainceiling_dummy;
sensor->set_quality = set_dummy;
sensor->set_colorbar = set_colorbar;
sensor->set_gain_ctrl = set_dummy;
sensor->set_exposure_ctrl = set_exposure_ctrl;
sensor->set_whitebal = set_dummy;
sensor->set_hmirror = set_hmirror;
sensor->set_vflip = set_vflip;
sensor->init_status = init_status;
sensor->set_aec2 = set_dummy;
sensor->set_aec_value = set_dummy;
sensor->set_special_effect = set_dummy;
sensor->set_wb_mode = set_dummy;
sensor->set_ae_level = set_dummy;
sensor->set_dcw = set_dummy;
sensor->set_bpc = set_dummy;
sensor->set_wpc = set_dummy;
sensor->set_agc_gain = set_dummy;
sensor->set_raw_gma = set_dummy;
sensor->set_lenc = set_dummy;
sensor->set_denoise = set_dummy;
sensor->get_reg = get_reg;
sensor->set_reg = set_reg;
sensor->set_res_raw = NULL;
sensor->set_pll = _set_pll;
sensor->set_xclk = set_xclk;
// No autofocus support
sensor->af_is_supported = NULL;
sensor->af_init = NULL;
sensor->af_set_mode = NULL;
sensor->af_trigger = NULL;
sensor->af_get_status = NULL;
sensor->af_set_manual_position = NULL;
return 0;
}
+841
View File
@@ -0,0 +1,841 @@
/*
*
* HM1055 driver.
*
*/
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "sccb.h"
#include "xclk.h"
#include "hm1055.h"
#include "hm1055_regs.h"
#include "hm1055_settings.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG)
#include "esp32-hal-log.h"
#else
#include "esp_log.h"
static const char *TAG = "HM1055";
#endif
// #define REG_DEBUG_ON
static int _set_pll(sensor_t *sensor, int bypass, int multiplier, int sys_div, int root_2x, int pre_div, int seld5, int pclk_manual, int pclk_div);
static int read_reg(uint8_t slv_addr, const uint16_t reg)
{
int ret = SCCB_Read16(slv_addr, reg);
#ifdef REG_DEBUG_ON
if (ret < 0)
{
ESP_LOGE(TAG, "READ REG 0x%04x FAILED: %d", reg, ret);
}
#endif
return ret;
}
static int check_reg_mask(uint8_t slv_addr, uint16_t reg, uint8_t mask)
{
return (read_reg(slv_addr, reg) & mask) == mask;
}
static int read_reg16(uint8_t slv_addr, const uint16_t reg)
{
int ret = 0, ret2 = 0;
ret = read_reg(slv_addr, reg);
if (ret >= 0)
{
ret = (ret & 0xFF) << 8;
ret2 = read_reg(slv_addr, reg + 1);
if (ret2 < 0)
{
ret = ret2;
}
else
{
ret |= ret2 & 0xFF;
}
}
return ret;
}
static int write_reg(uint8_t slv_addr, const uint16_t reg, uint8_t value)
{
int ret = 0;
#ifndef REG_DEBUG_ON
ret = SCCB_Write16(slv_addr, reg, value);
#else
int old_value = read_reg(slv_addr, reg);
if (old_value < 0)
{
return old_value;
}
if ((uint8_t)old_value != value)
{
ESP_LOGD(TAG, "NEW REG 0x%04x: 0x%02x to 0x%02x", reg, (uint8_t)old_value, value);
ret = SCCB_Write16(slv_addr, reg, value);
}
else
{
ESP_LOGD(TAG, "OLD REG 0x%04x: 0x%02x", reg, (uint8_t)old_value);
ret = SCCB_Write16(slv_addr, reg, value); // maybe not?
}
if (ret < 0)
{
ESP_LOGE(TAG, "WRITE REG 0x%04x FAILED: %d", reg, ret);
}
#endif
return ret;
}
static int set_reg_bits(uint8_t slv_addr, uint16_t reg, uint8_t offset, uint8_t mask, uint8_t value)
{
int ret = 0;
uint8_t c_value, new_value;
ret = read_reg(slv_addr, reg);
if (ret < 0)
{
return ret;
}
c_value = ret;
new_value = (c_value & ~(mask << offset)) | ((value & mask) << offset);
ret = write_reg(slv_addr, reg, new_value);
return ret;
}
static int write_regs(uint8_t slv_addr, const uint16_t (*regs)[2])
{
int i = 0, ret = 0;
while (!ret && regs[i][0] != REGLIST_TAIL)
{
if (regs[i][0] == REG_DLY)
{
vTaskDelay(regs[i][1] / portTICK_PERIOD_MS);
}
else
{
ret = write_reg(slv_addr, regs[i][0], regs[i][1]);
}
i++;
}
return ret;
}
static int write_reg16(uint8_t slv_addr, const uint16_t reg, uint16_t value)
{
if (write_reg(slv_addr, reg, value >> 8) || write_reg(slv_addr, reg + 1, value))
{
return -1;
}
return 0;
}
static int write_addr_reg(uint8_t slv_addr, const uint16_t reg, uint16_t x_value, uint16_t y_value)
{
if (write_reg16(slv_addr, reg, x_value) || write_reg16(slv_addr, reg + 2, y_value))
{
return -1;
}
return 0;
}
#define write_reg_bits(slv_addr, reg, mask, enable) set_reg_bits(slv_addr, reg, 0, mask, (enable) ? (mask) : 0)
static int set_ae_level(sensor_t *sensor, int level);
static int reset(sensor_t *sensor)
{
vTaskDelay(100 / portTICK_PERIOD_MS);
int ret = 0;
// Software Reset: clear all registers and reset them to their default values
ret = write_reg(sensor->slv_addr, SFTRST, 0x55);
if (ret)
{
ESP_LOGE(TAG, "Software Reset FAILED!");
return ret;
}
vTaskDelay(100 / portTICK_PERIOD_MS);
ret = write_regs(sensor->slv_addr, sensor_default_regs);
if (ret == 0)
{
ESP_LOGD(TAG, "Camera defaults loaded");
vTaskDelay(100 / portTICK_PERIOD_MS);
set_ae_level(sensor, 0);
}
return ret;
}
static int set_pixformat(sensor_t *sensor, pixformat_t pixformat)
{
int ret = 0;
switch (pixformat)
{
case PIXFORMAT_RAW:
ret = write_reg(sensor->slv_addr, PORTCTRL, 0x20);
break;
case PIXFORMAT_YUV422:
ret = write_reg(sensor->slv_addr, PORTCTRL, 0x30);
break;
case PIXFORMAT_RGB565:
case PIXFORMAT_RGB888:
ret = write_reg(sensor->slv_addr, PORTCTRL, 0x40);
break;
case PIXFORMAT_RGB555:
ret = write_reg(sensor->slv_addr, PORTCTRL, 0x50);
break;
case PIXFORMAT_RGB444:
ret = write_reg(sensor->slv_addr, PORTCTRL, 0x60);
break;
default:
break;
}
if (ret == 0)
{
sensor->pixformat = pixformat;
ESP_LOGD(TAG, "Set pixformat: %d", pixformat);
}
return ret;
}
static int set_framesize(sensor_t *sensor, framesize_t framesize)
{
int ret = 0;
sensor->status.framesize = framesize;
ESP_LOGD(TAG, "Set framesize: %d", framesize);
ret = write_regs(sensor->slv_addr, sensor_default_regs);
if (framesize == FRAMESIZE_QQVGA)
{
ESP_LOGD(TAG, "Set FRAMESIZE_QQVGA");
ret = write_regs(sensor->slv_addr, sensor_framesize_QQVGA);
}
else if (framesize == FRAMESIZE_QCIF)
{
ESP_LOGD(TAG, "Set FRAMESIZE_QCIF");
ret = write_regs(sensor->slv_addr, sensor_framesize_QCIF);
}
else if (framesize == FRAMESIZE_240X240)
{
ESP_LOGD(TAG, "Set FRAMESIZE_240X240");
ret = write_regs(sensor->slv_addr, sensor_framesize_240X240);
}
else if (framesize == FRAMESIZE_QVGA)
{
ESP_LOGD(TAG, "Set FRAMESIZE_QVGA");
ret = write_regs(sensor->slv_addr, sensor_framesize_QVGA);
}
else if (framesize == FRAMESIZE_CIF)
{
ESP_LOGD(TAG, "Set FRAMESIZE_CIF");
ret = write_regs(sensor->slv_addr, sensor_framesize_CIF);
}
else if (framesize == FRAMESIZE_VGA)
{
ESP_LOGD(TAG, "Set FRAMESIZE_VGA");
ret = write_regs(sensor->slv_addr, sensor_framesize_VGA);
}
else if (framesize == FRAMESIZE_SVGA)
{
ESP_LOGD(TAG, "Set FRAMESIZE_SVGA");
ret = write_regs(sensor->slv_addr, sensor_framesize_SVGA);
}
else if (framesize == FRAMESIZE_HD)
{
ESP_LOGD(TAG, "Set FRAMESIZE_HD");
ret = write_regs(sensor->slv_addr, sensor_framesize_HD);
ret = _set_pll(sensor, 0, 288, 1, 0, 0, 0, 1, 16);
}
else
{
ESP_LOGD(TAG, "Dont support this size, Set FRAMESIZE_VGA");
ret = write_regs(sensor->slv_addr, sensor_framesize_VGA);
}
if (ret == 0)
{
ret = write_reg(sensor->slv_addr, CMU, 0x01) || write_reg(sensor->slv_addr, TGRDCFG, 0x01);
}
return ret;
}
static int set_hmirror(sensor_t *sensor, int enable)
{
int ret = 0;
ret = write_reg_bits(sensor->slv_addr, RDCFG, 0x02, enable);
if (ret == 0)
{
ESP_LOGD(TAG, "Set hmirror to: %d", enable);
sensor->status.hmirror = enable;
}
return ret;
}
static int set_vflip(sensor_t *sensor, int enable)
{
int ret = 0;
ret = write_reg_bits(sensor->slv_addr, RDCFG, 0x01, enable);
if (ret == 0)
{
ESP_LOGD(TAG, "Set vflip to: %d", enable);
sensor->status.vflip = enable;
}
return ret;
}
static int set_quality(sensor_t *sensor, int qs)
{
return 0;
}
static int set_colorbar(sensor_t *sensor, int enable)
{
return 0;
}
static int set_gain_ctrl(sensor_t *sensor, int enable)
{
return 0;
}
static int set_exposure_ctrl(sensor_t *sensor, int enable)
{
int ret = 0;
ret = write_reg_bits(sensor->slv_addr, AEWBCFG, 0x01, enable);
if (ret == 0)
{
ESP_LOGD(TAG, "Set aec to: %d", enable);
sensor->status.aec = enable;
}
return ret;
}
static int set_whitebal(sensor_t *sensor, int enable)
{
int ret = 0;
ret = write_reg_bits(sensor->slv_addr, AEWBCFG, 0x02, enable);
if (ret == 0)
{
ESP_LOGD(TAG, "Set awb to: %d", enable);
sensor->status.awb = enable;
}
return ret;
}
// Gamma enable
static int set_raw_gma_dsp(sensor_t *sensor, int enable)
{
int ret = 0;
ret = write_reg_bits(sensor->slv_addr, ISPCTRL1, 0x04, enable);
if (ret == 0)
{
ESP_LOGD(TAG, "Set raw_gma to: %d", enable);
sensor->status.raw_gma = enable;
}
return 0;
}
static int set_lenc_dsp(sensor_t *sensor, int enable)
{
int ret = 0;
ret = write_reg_bits(sensor->slv_addr, ISPCTRL3, 0x40, enable);
if (ret == 0)
{
ESP_LOGD(TAG, "Set lenc to: %d", enable);
sensor->status.lenc = enable;
}
return -1;
}
// real gain
static int set_agc_gain(sensor_t *sensor, int gain)
{
int ret = 0;
if (gain < 0 || gain > 7)
{
return -1;
}
ret = write_reg(sensor->slv_addr, AGAIN, gain);
if (ret == 0)
{
ESP_LOGD(TAG, "Set gain to: %d", gain);
sensor->status.agc_gain = gain;
}
return 0;
}
static int set_aec_value(sensor_t *sensor, int value)
{
int ret = 0;
ret = write_reg(sensor->slv_addr, AETARGM, value);
if (ret == 0)
{
ESP_LOGD(TAG, "Set aec_value to: %d", value);
sensor->status.aec_value = value;
}
return 0;
}
static int set_ae_level(sensor_t *sensor, int level)
{
int ret = 0;
if (level < -5 || level > 5)
{
return -1;
}
uint8_t target_level = ((level + 5) * 10) + 5;
uint8_t upper = target_level * 27 / 25;
uint8_t lower = target_level * 23 / 25;
ret = write_reg(sensor->slv_addr, AETARGU, upper) || write_reg(sensor->slv_addr, AETARGL, lower);
if (ret == 0)
{
ESP_LOGD(TAG, "Set ae_level to: %d", level);
sensor->status.ae_level = level;
}
return 0;
}
static int set_brightness(sensor_t *sensor, int level)
{
int ret = 0;
uint8_t ispctrl5 = read_reg(sensor->slv_addr, ISPCTRL5);
uint8_t brightness = 0;
switch (level)
{
case 3:
brightness = 0xFF;
break;
case 2:
brightness = 0xBA;
break;
case 1:
brightness = 0x96;
break;
case 0:
brightness = 0x72;
break;
case -1:
brightness = 0x48;
break;
case -2:
brightness = 0x24;
break;
case -3:
brightness = 0x00;
break;
default: // 0
break;
}
ispctrl5 |= 0x40; // enable brightness
ret = write_reg(sensor->slv_addr, ISPCTRL5, ispctrl5);
if (ret == 0)
{
ret = write_reg(sensor->slv_addr, BRIGHT, brightness);
}
if (ret == 0)
{
ESP_LOGD(TAG, "Set brightness to: %d", level);
sensor->status.brightness = level;
}
return ret;
}
static int set_contrast(sensor_t *sensor, int level)
{
int ret = 0;
uint8_t ispctrl5 = read_reg(sensor->slv_addr, ISPCTRL5);
ispctrl5 |= 0x80; // enable contrast
ret = write_reg(sensor->slv_addr, ISPCTRL5, ispctrl5);
ret = write_reg(sensor->slv_addr, ACONTQ, (level * 0x20) & 0xFF);
if (ret == 0)
{
ESP_LOGD(TAG, "Set contrast to: %d", level);
sensor->status.contrast = level;
}
return ret;
}
static int set_saturation(sensor_t *sensor, int level)
{
int ret = 0;
ret = write_reg(sensor->slv_addr, SAT, (level * 0x20) + 0x4A);
if (ret == 0)
{
ESP_LOGD(TAG, "Set saturation to: %d", level);
sensor->status.saturation = level;
}
return ret;
}
static int get_sharpness(sensor_t *sensor)
{
int ret = 0;
int level = 0;
ret = read_reg(sensor->slv_addr, EDGE);
level = (ret - 0x60) / 0x20;
ESP_LOGD(TAG, "Get sharpness: %d", level);
return level;
}
static int set_sharpness(sensor_t *sensor, int level)
{
int ret = 0;
ret = write_reg(sensor->slv_addr, EDGE, (level * 0x20) + 0x60);
if (ret == 0)
{
ESP_LOGD(TAG, "Set sharpness to: %d", level);
sensor->status.sharpness = level;
}
return ret;
}
static int get_denoise(sensor_t *sensor)
{
int ret = 0;
int level = 0;
ret = read_reg(sensor->slv_addr, YDN);
level = (ret - 0x07) / 2;
ESP_LOGD(TAG, "Get denoise: %d", level);
return level;
}
static int set_denoise(sensor_t *sensor, int level)
{
int ret = 0;
uint8_t ispctrl5 = read_reg(sensor->slv_addr, ISPCTRL5);
ispctrl5 |= 0x20; // enable denoise
ret = write_reg(sensor->slv_addr, ISPCTRL5, ispctrl5);
ret = write_reg(sensor->slv_addr, YDN, (level * 2) + 0x07);
if (ret == 0)
{
ESP_LOGD(TAG, "Set denoise to: %d", level);
sensor->status.denoise = level;
}
return ret;
}
static int get_reg(sensor_t *sensor, int reg, int mask)
{
int ret = 0, ret2 = 0;
if (mask > 0xFF)
{
ret = read_reg16(sensor->slv_addr, reg);
if (ret >= 0 && mask > 0xFFFF)
{
ret2 = read_reg(sensor->slv_addr, reg + 2);
if (ret2 >= 0)
{
ret = (ret << 8) | ret2;
}
else
{
ret = ret2;
}
}
}
else
{
ret = read_reg(sensor->slv_addr, reg);
}
if (ret > 0)
{
ret &= mask;
}
return ret;
}
static int set_reg(sensor_t *sensor, int reg, int mask, int value)
{
int ret = 0, ret2 = 0;
if (mask > 0xFF)
{
ret = read_reg16(sensor->slv_addr, reg);
if (ret >= 0 && mask > 0xFFFF)
{
ret2 = read_reg(sensor->slv_addr, reg + 2);
if (ret2 >= 0)
{
ret = (ret << 8) | ret2;
}
else
{
ret = ret2;
}
}
}
else
{
ret = read_reg(sensor->slv_addr, reg);
}
if (ret < 0)
{
return ret;
}
value = (ret & ~mask) | (value & mask);
if (mask > 0xFFFF)
{
ret = write_reg16(sensor->slv_addr, reg, value >> 8);
if (ret >= 0)
{
ret = write_reg(sensor->slv_addr, reg + 2, value & 0xFF);
}
}
else if (mask > 0xFF)
{
ret = write_reg16(sensor->slv_addr, reg, value);
}
else
{
ret = write_reg(sensor->slv_addr, reg, value);
}
return ret;
}
static int set_res_raw(sensor_t *sensor, int startX, int startY, int endX, int endY, int offsetX, int offsetY, int totalX, int totalY, int outputX, int outputY, bool scale, bool binning)
{
return 0;
}
static int _set_pll(sensor_t *sensor, int bypass, int multiplier, int sys_div, int root_2x, int pre_div, int seld5, int pclk_manual, int pclk_div)
{
int ret = 0;
uint8_t ckcfg1 = 0;
uint8_t ckcfg2 = 0;
uint8_t ckcfg3 = 0;
uint8_t pll2 = 0;
if (sensor->xclk_freq_hz <= 6000000)
{
ckcfg2 = 0x00;
}
else if (sensor->xclk_freq_hz <= 12000000)
{
ckcfg2 = 0x20;
}
else if (sensor->xclk_freq_hz <= 18000000)
{
ckcfg2 = 0x40;
}
else if (sensor->xclk_freq_hz <= 24000000)
{
ckcfg2 = 0x60;
}
else if (sensor->xclk_freq_hz <= 30000000)
{
ckcfg2 = 0x80;
}
else if (sensor->xclk_freq_hz <= 36000000)
{
ckcfg2 = 0xA0;
}
else if (sensor->xclk_freq_hz <= 42000000)
{
ckcfg2 = 0xC0;
}
else
{ // max is 48000000
ckcfg2 = 0xE0;
}
if (bypass == 0)
{
switch (multiplier)
{
case 204:
ckcfg2 |= 10;
break;
case 216:
ckcfg2 |= 11;
break;
case 228:
ckcfg2 |= 0x12;
break;
case 240:
ckcfg2 |= 0x13;
break;
case 288:
ckcfg2 |= 0x17;
break;
case 300:
ckcfg2 |= 0x18;
break;
case 312:
ckcfg2 |= 0x19;
break;
case 324:
ckcfg2 |= 0x1A;
break;
case 336:
ckcfg2 |= 0x1B;
break;
case 348:
ckcfg2 |= 0x1C;
break;
case 360:
ckcfg2 |= 0x1D;
break;
default:
ckcfg2 |= 0x17;
break;
}
}
if (pclk_manual > 0)
{
if (pclk_div > 128)
{
pclk_div = 128;
}
if (pclk_div < 1)
{
pclk_div = 1;
}
ckcfg1 |= (pclk_div - 1);
}
if (root_2x > 0)
{
ckcfg3 = 0x00;
}
else
{
ckcfg3 = 0x01;
}
ESP_LOGD(TAG, "ckcfg1 = 0x%02x, ckcfg2 = 0x%02x, ckcfg3 = 0x%02x, pll2 = 0x%02x", ckcfg1, ckcfg2, ckcfg3, pll2);
ret = write_reg(sensor->slv_addr, CKCFG1, ckcfg1);
ret = write_reg(sensor->slv_addr, CKCFG2, ckcfg2);
ret = write_reg(sensor->slv_addr, CKCFG3, ckcfg3);
ret = write_reg(sensor->slv_addr, PLL2, pll2);
return ret;
}
static int set_xclk(sensor_t *sensor, int timer, int xclk)
{
int ret = 0;
sensor->xclk_freq_hz = xclk * 1000000U;
ret = xclk_timer_conf(timer, sensor->xclk_freq_hz);
if (ret == 0)
{
ESP_LOGD(TAG, "Set xclk to %d", xclk);
}
return ret;
}
static int init_status(sensor_t *sensor)
{
(void) write_addr_reg;
sensor->status.brightness = 0;
sensor->status.contrast = 0;
sensor->status.saturation = 0;
sensor->status.sharpness = get_sharpness(sensor);
sensor->status.denoise = get_denoise(sensor);
sensor->status.ae_level = 0;
sensor->status.awb = check_reg_mask(sensor->slv_addr, AEWBCFG, 0x02);
sensor->status.agc = true;
sensor->status.aec = check_reg_mask(sensor->slv_addr, AEWBCFG, 0x04);
sensor->status.hmirror = check_reg_mask(sensor->slv_addr, RDCFG, 0x02);
sensor->status.vflip = check_reg_mask(sensor->slv_addr, RDCFG, 0x01);
sensor->status.lenc = check_reg_mask(sensor->slv_addr, ISPCTRL3, 0x40);
sensor->status.awb_gain = read_reg(sensor->slv_addr, DGAIN);
sensor->status.agc_gain = read_reg(sensor->slv_addr, AGAIN);
sensor->status.aec_value = read_reg(sensor->slv_addr, AETARGM);
return 0;
}
int esp32_camera_hm1055_detect(int slv_addr, sensor_id_t *id)
{
if (HM1055_SCCB_ADDR == slv_addr)
{
uint8_t h = SCCB_Read16(slv_addr, IDH);
uint8_t l = SCCB_Read16(slv_addr, IDL);
uint16_t PID = (h << 8) | l;
if (HM1055_PID == PID)
{
id->PID = PID;
return PID;
}
else
{
ESP_LOGD(TAG, "Mismatch PID=0x%x", PID);
}
}
return 0;
}
int esp32_camera_hm1055_init(sensor_t *sensor)
{
sensor->reset = reset;
sensor->set_pixformat = set_pixformat;
sensor->set_framesize = set_framesize;
sensor->set_contrast = set_contrast;
sensor->set_brightness = set_brightness;
sensor->set_saturation = set_saturation;
sensor->set_sharpness = set_sharpness;
sensor->set_gainceiling = NULL;
sensor->set_quality = set_quality;
sensor->set_colorbar = set_colorbar;
sensor->set_gain_ctrl = set_gain_ctrl;
sensor->set_exposure_ctrl = set_exposure_ctrl;
sensor->set_whitebal = set_whitebal;
sensor->set_hmirror = set_hmirror;
sensor->set_vflip = set_vflip;
sensor->init_status = init_status;
sensor->set_aec2 = NULL;
sensor->set_aec_value = set_aec_value;
sensor->set_special_effect = NULL;
sensor->set_wb_mode = NULL;
sensor->set_ae_level = set_ae_level;
sensor->set_dcw = NULL;
sensor->set_bpc = NULL;
sensor->set_wpc = NULL;
sensor->set_agc_gain = set_agc_gain;
sensor->set_raw_gma = set_raw_gma_dsp;
sensor->set_lenc = set_lenc_dsp;
sensor->set_denoise = set_denoise;
sensor->get_reg = get_reg;
sensor->set_reg = set_reg;
sensor->set_res_raw = set_res_raw;
sensor->set_pll = _set_pll;
sensor->set_xclk = set_xclk;
// No autofocus support
sensor->af_is_supported = NULL;
sensor->af_init = NULL;
sensor->af_set_mode = NULL;
sensor->af_trigger = NULL;
sensor->af_get_status = NULL;
sensor->af_set_manual_position = NULL;
return 0;
}
+415
View File
@@ -0,0 +1,415 @@
// Copyright 2015-2021 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "sccb.h"
#include "mega_ccm.h"
#include "mega_ccm_regs.h"
#include "mega_ccm_settings.h"
#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG)
#include "esp32-hal-log.h"
#else
#include "esp_log.h"
static const char *TAG = "mega_ccm";
#endif
#define H8(v) ((v)>>8)
#define L8(v) ((v)&0xff)
//#define REG_DEBUG_ON
static int read_reg(uint8_t slv_addr, const uint16_t reg){
int ret = SCCB_Read16(slv_addr, reg);
#ifdef REG_DEBUG_ON
if (ret < 0) {
ESP_LOGE(TAG, "READ REG 0x%04x FAILED: %d", reg, ret);
}
#endif
return ret;
}
static int write_reg(uint8_t slv_addr, const uint16_t reg, uint8_t value){
int ret = 0;
ret = SCCB_Write16(slv_addr, reg, value);
return ret;
}
static int reset(sensor_t *sensor)
{
int ret;
ret = write_reg(sensor->slv_addr, CAMERA_RST_REG, 0x00);
ret += write_reg(sensor->slv_addr, CAMERA_RST_REG, 0x01);
vTaskDelay(1000 / portTICK_PERIOD_MS);
return ret;
}
static int set_pixformat(sensor_t *sensor, pixformat_t pixformat)
{
int ret = 0;
switch (pixformat) {
case PIXFORMAT_JPEG:
ret = write_reg(sensor->slv_addr, PIXEL_FMT_REG, 0x01);
break;
case PIXFORMAT_RGB565:
ret = write_reg(sensor->slv_addr, PIXEL_FMT_REG, 0x02);
break;
case PIXFORMAT_YUV422:
ret = write_reg(sensor->slv_addr, PIXEL_FMT_REG, 0x03);
break;
default:
ESP_LOGW(TAG, "unsupported format");
ret = -1;
break;
}
if (ret == 0) {
sensor->pixformat = pixformat;
ESP_LOGD(TAG, "Set pixformat to: %u", pixformat);
}
return ret;
}
static int set_framesize(sensor_t *sensor, framesize_t framesize)
{
ESP_LOGI(TAG, "set_framesize");
int ret = 0;
if (framesize > FRAMESIZE_5MP) {
ESP_LOGW(TAG, "Invalid framesize: %u", framesize);
framesize = FRAMESIZE_5MP;
}
sensor->status.framesize = framesize;
uint16_t w = resolution[framesize].width;
uint16_t h = resolution[framesize].height;
switch (framesize){
case FRAMESIZE_QVGA:
ret = write_reg(sensor->slv_addr, RESOLUTION_REG, 0x01); //320x240
ret += write_reg(sensor->slv_addr, SYSTEM_CLK_DIV_REG, 0x02); // set system clk
ret += write_reg(sensor->slv_addr, SYSTEM_PLL_DIV_REG, 0x01); // set system pll
break;
case FRAMESIZE_VGA:
ret = write_reg(sensor->slv_addr, RESOLUTION_REG, 0x02); //640x480
ret += write_reg(sensor->slv_addr, SYSTEM_CLK_DIV_REG, 0x02); // set system clk
ret += write_reg(sensor->slv_addr, SYSTEM_PLL_DIV_REG, 0x01); // set system pll
break;
case FRAMESIZE_HD:
ret = write_reg(sensor->slv_addr, RESOLUTION_REG, 0x03); //1280x720
ret += write_reg(sensor->slv_addr, SYSTEM_CLK_DIV_REG, 0x02); // set system clk
ret += write_reg(sensor->slv_addr, SYSTEM_PLL_DIV_REG, 0x01); // set system pll
break;
case FRAMESIZE_UXGA:
ret = write_reg(sensor->slv_addr, RESOLUTION_REG, 0x04); //1600x1200
ret += write_reg(sensor->slv_addr, SYSTEM_CLK_DIV_REG, 0x02); // set system clk
ret += write_reg(sensor->slv_addr, SYSTEM_PLL_DIV_REG, 0x01); // set system pll
break;
case FRAMESIZE_FHD:
ret = write_reg(sensor->slv_addr, RESOLUTION_REG, 0x05); //1920x1080
ret += write_reg(sensor->slv_addr, SYSTEM_CLK_DIV_REG, 0x02); // set system clk
ret += write_reg(sensor->slv_addr, SYSTEM_PLL_DIV_REG, 0x01); // set system pll
break;
case FRAMESIZE_5MP:
ret = write_reg(sensor->slv_addr, RESOLUTION_REG, 0x06); //2592x1944
break;
case FRAMESIZE_96X96:
ret = write_reg(sensor->slv_addr, RESOLUTION_REG, 0x07); //96x96
break;
case FRAMESIZE_128X128:
ret = write_reg(sensor->slv_addr, RESOLUTION_REG, 0x08); //128x128
break;
case FRAMESIZE_320X320:
ret = write_reg(sensor->slv_addr, RESOLUTION_REG, 0x09); //320x320
break;
default:
ESP_LOGW(TAG, "unsupported framesize");
ret = -1;
break;
}
if (ret == 0) {
ESP_LOGD(TAG, "Set framesize to: %ux%u", w, h);
}
return ret;
}
static int set_hmirror(sensor_t *sensor, int enable)
{
int ret = 0;
sensor->status.hmirror = enable;
ret = write_reg(sensor->slv_addr, IMAGE_MIRROR_REG, enable);
if (ret == 0) {
ESP_LOGD(TAG, "Set h-mirror to: %d", enable);
}
return ret;
}
static int set_vflip(sensor_t *sensor, int enable)
{
int ret = 0;
sensor->status.vflip = enable;
ret = write_reg(sensor->slv_addr, IMAGE_FLIP_REG, enable);
if (ret == 0) {
ESP_LOGD(TAG, "Set v-flip to: %d", enable);
}
return ret;
}
static int set_quality(sensor_t *sensor, int qs)
{
int ret = 0;
ret = write_reg(sensor->slv_addr, IMAGE_QUALITY_REG, qs);
if (ret == 0) {
sensor->status.quality = qs;
ESP_LOGD(TAG, "Set quality to: %d", qs);
}
return ret;
}
static int set_brightness(sensor_t *sensor, int level)
{
int ret = 0;
if(level < 0) {
level = 0;
} else if(level > 8) {
level = 8;
}
ret = write_reg(sensor->slv_addr, BRIGHTNESS_REG, level);
if (ret == 0) {
ESP_LOGD(TAG, "Set brightness to: %d", level);
sensor->status.brightness = level;
}
return ret;
}
static int set_contrast (sensor_t *sensor, int level)
{
int ret = 0;
if(level < 0) {
level = 0;
} else if(level > 6) {
level = 6;
}
ret = write_reg(sensor->slv_addr, CONTRAST_REG, level);
if (ret == 0) {
ESP_LOGD(TAG, "Set contrast to: %d", level);
sensor->status.contrast = level;
}
return ret;
}
static int set_saturation (sensor_t *sensor, int level)
{
int ret = 0;
if(level < 0) {
level = 0;
} else if(level > 6) {
level = 6;
}
ret = write_reg(sensor->slv_addr, SATURATION_REG, level);
if (ret == 0) {
ESP_LOGD(TAG, "Set saturation to: %d", level);
sensor->status.saturation = level;
}
return ret;
}
static int set_agc_mode (sensor_t *sensor, int enable)
{
int ret = 0;
ret = write_reg(sensor->slv_addr, AGC_MODE_REG, enable);
if (ret == 0) {
ESP_LOGD(TAG, "Set agc mode to: %d", enable);
sensor->status.aec = enable;
}
return ret;
}
static int set_wb_mode (sensor_t *sensor, int mode)
{
int ret = 0;
if(mode < 0) {
mode = 0;
} else if(mode > 5) {
mode = 5;
}
ret = write_reg(sensor->slv_addr, AWB_MODE_REG, mode);
if (ret == 0) {
ESP_LOGD(TAG, "Set wb_mode to: %d", mode);
sensor->status.wb_mode = mode;
}
return ret;
}
static int set_special_effect (sensor_t *sensor, int effect)
{
int ret = 0;
if(effect < 0) {
effect = 0;
} else if(effect > 6) {
effect = 6;
}
ret = write_reg(sensor->slv_addr, SPECIAL_REG, effect);
if (ret == 0) {
ESP_LOGD(TAG, "Set special_effect to: %d", effect);
sensor->status.special_effect = effect;
}
return ret;
}
static int analog_gain (sensor_t *sensor, int val)
{
int ret = 0;
ret = write_reg(sensor->slv_addr, MANUAL_AGC_REG, val);
if (ret == 0) {
ESP_LOGD(TAG, "Set analog gain to: %d", val);
}
return ret;
}
static int exposure_line (sensor_t *sensor, int val)
{
int ret = 0;
ret = write_reg(sensor->slv_addr, MANUAL_EXP_H_REG, val>>8);
ret += write_reg(sensor->slv_addr, MANUAL_EXP_L_REG, val>>8);
if (ret == 0) {
ESP_LOGD(TAG, "Set exposure_line to: %d", val);
}
return ret;
}
static int init_status(sensor_t *sensor)
{
sensor->status.brightness = 0;
sensor->status.contrast = 0;
sensor->status.saturation = 0;
sensor->status.sharpness = 0;
sensor->status.denoise = 0;
sensor->status.ae_level = 0;
sensor->status.gainceiling = 0;
sensor->status.awb = 0;
sensor->status.dcw = 0;
sensor->status.agc = 0;
sensor->status.aec = 0;
sensor->status.hmirror = 0;
sensor->status.vflip = 0;
sensor->status.colorbar = 0;
sensor->status.bpc = 0;
sensor->status.wpc = 0;
sensor->status.raw_gma = 0;
sensor->status.lenc = 0;
sensor->status.quality = 0;
sensor->status.special_effect = 0;
sensor->status.wb_mode = 0;
sensor->status.awb_gain = 0;
sensor->status.agc_gain = 0;
sensor->status.aec_value = 0;
sensor->status.aec2 = 0;
return 0;
}
static int set_dummy(sensor_t *sensor, int val)
{
ESP_LOGW(TAG, "Unsupported");
return -1;
}
static int set_gainceiling_dummy(sensor_t *sensor, gainceiling_t val)
{
ESP_LOGW(TAG, "Unsupported");
return -1;
}
int esp32_camera_mega_ccm_detect(int slv_addr, sensor_id_t *id)
{
if (MEGA_CCM_SCCB_ADDR == slv_addr) {
uint8_t h = read_reg(slv_addr, SENSOR_ID_HIGH);
uint8_t l = read_reg(slv_addr, SENSOR_ID_LOW);
uint16_t PID = (h<<8) | l;
if (MEGA_CCM_PID == PID) {
id->PID = PID;
return PID;
} else {
ESP_LOGI(TAG, "Mismatch PID=0x%x", PID);
}
}
return 0;
}
int esp32_camera_mega_ccm_init(sensor_t *sensor)
{
sensor->init_status = init_status;
sensor->reset = reset;
sensor->set_pixformat = set_pixformat;
sensor->set_framesize = set_framesize;
sensor->set_contrast = set_contrast;
sensor->set_brightness = set_brightness;
sensor->set_saturation = set_saturation;
sensor->set_sharpness = set_dummy;
sensor->set_denoise = set_dummy;
sensor->set_gainceiling = set_gainceiling_dummy;
sensor->set_quality = set_quality;
sensor->set_colorbar = set_dummy;
sensor->set_whitebal = set_dummy;
sensor->set_gain_ctrl = set_dummy;
sensor->set_exposure_ctrl = set_dummy;
sensor->set_hmirror = set_hmirror;
sensor->set_vflip = set_vflip;
sensor->set_aec2 = set_agc_mode;
sensor->set_awb_gain = set_dummy;
sensor->set_agc_gain = analog_gain;
sensor->set_aec_value = exposure_line;
sensor->set_special_effect = set_special_effect;
sensor->set_wb_mode = set_wb_mode;
sensor->set_ae_level = set_dummy;
sensor->set_dcw = set_dummy;
sensor->set_bpc = set_dummy;
sensor->set_wpc = set_dummy;
sensor->set_raw_gma = set_dummy;
sensor->set_lenc = set_dummy;
sensor->get_reg = NULL;
sensor->set_reg = NULL;
sensor->set_res_raw = NULL;
sensor->set_pll = NULL;
sensor->set_xclk = NULL;
// No autofocus support
sensor->af_is_supported = NULL;
sensor->af_init = NULL;
sensor->af_set_mode = NULL;
sensor->af_trigger = NULL;
sensor->af_get_status = NULL;
sensor->af_set_manual_position = NULL;
ESP_LOGD(TAG, "MEGA_CCM Attached");
return 0;
}
File diff suppressed because it is too large Load Diff
+621
View File
@@ -0,0 +1,621 @@
/*
* This file is part of the OpenMV project.
* Copyright (c) 2013/2014 Ibrahim Abdelkader <i.abdalkader@gmail.com>
* This work is licensed under the MIT license, see the file LICENSE for details.
*
* OV2640 driver.
*
*/
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "sccb.h"
#include "xclk.h"
#include "ov2640.h"
#include "ov2640_regs.h"
#include "ov2640_settings.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG)
#include "esp32-hal-log.h"
#else
#include "esp_log.h"
static const char* TAG = "ov2640";
#endif
static volatile ov2640_bank_t reg_bank = BANK_MAX;
static int set_bank(sensor_t *sensor, ov2640_bank_t bank)
{
int res = 0;
if (bank != reg_bank) {
reg_bank = bank;
res = SCCB_Write(sensor->slv_addr, BANK_SEL, bank);
}
return res;
}
static int write_regs(sensor_t *sensor, const uint8_t (*regs)[2])
{
int i=0, res = 0;
while (regs[i][0]) {
if (regs[i][0] == BANK_SEL) {
res = set_bank(sensor, regs[i][1]);
} else {
res = SCCB_Write(sensor->slv_addr, regs[i][0], regs[i][1]);
}
if (res) {
return res;
}
i++;
}
return res;
}
static int write_reg(sensor_t *sensor, ov2640_bank_t bank, uint8_t reg, uint8_t value)
{
int ret = set_bank(sensor, bank);
if(!ret) {
ret = SCCB_Write(sensor->slv_addr, reg, value);
}
return ret;
}
static int set_reg_bits(sensor_t *sensor, uint8_t bank, uint8_t reg, uint8_t offset, uint8_t mask, uint8_t value)
{
int ret = 0;
uint8_t c_value, new_value;
ret = set_bank(sensor, bank);
if(ret) {
return ret;
}
c_value = SCCB_Read(sensor->slv_addr, reg);
new_value = (c_value & ~(mask << offset)) | ((value & mask) << offset);
ret = SCCB_Write(sensor->slv_addr, reg, new_value);
return ret;
}
static int read_reg(sensor_t *sensor, ov2640_bank_t bank, uint8_t reg)
{
if(set_bank(sensor, bank)){
return 0;
}
return SCCB_Read(sensor->slv_addr, reg);
}
static uint8_t get_reg_bits(sensor_t *sensor, uint8_t bank, uint8_t reg, uint8_t offset, uint8_t mask)
{
return (read_reg(sensor, bank, reg) >> offset) & mask;
}
static int write_reg_bits(sensor_t *sensor, uint8_t bank, uint8_t reg, uint8_t mask, int enable)
{
return set_reg_bits(sensor, bank, reg, 0, mask, enable?mask:0);
}
#define WRITE_REGS_OR_RETURN(regs) ret = write_regs(sensor, regs); if(ret){return ret;}
#define WRITE_REG_OR_RETURN(bank, reg, val) ret = write_reg(sensor, bank, reg, val); if(ret){return ret;}
#define SET_REG_BITS_OR_RETURN(bank, reg, offset, mask, val) ret = set_reg_bits(sensor, bank, reg, offset, mask, val); if(ret){return ret;}
static int reset(sensor_t *sensor)
{
int ret = 0;
WRITE_REG_OR_RETURN(BANK_SENSOR, COM7, COM7_SRST);
vTaskDelay(10 / portTICK_PERIOD_MS);
WRITE_REGS_OR_RETURN(ov2640_settings_cif);
return ret;
}
static int set_pixformat(sensor_t *sensor, pixformat_t pixformat)
{
int ret = 0;
sensor->pixformat = pixformat;
switch (pixformat) {
case PIXFORMAT_RGB565:
case PIXFORMAT_RGB888:
WRITE_REGS_OR_RETURN(ov2640_settings_rgb565);
break;
case PIXFORMAT_YUV422:
case PIXFORMAT_GRAYSCALE:
WRITE_REGS_OR_RETURN(ov2640_settings_yuv422);
break;
case PIXFORMAT_JPEG:
WRITE_REGS_OR_RETURN(ov2640_settings_jpeg3);
break;
default:
ret = -1;
break;
}
if(!ret) {
vTaskDelay(10 / portTICK_PERIOD_MS);
}
return ret;
}
static int set_window(sensor_t *sensor, ov2640_sensor_mode_t mode, int offset_x, int offset_y, int max_x, int max_y, int w, int h){
int ret = 0;
const uint8_t (*regs)[2];
ov2640_clk_t c;
c.reserved = 0;
max_x /= 4;
max_y /= 4;
w /= 4;
h /= 4;
uint8_t win_regs[][2] = {
{BANK_SEL, BANK_DSP},
{HSIZE, max_x & 0xFF},
{VSIZE, max_y & 0xFF},
{XOFFL, offset_x & 0xFF},
{YOFFL, offset_y & 0xFF},
{VHYX, ((max_y >> 1) & 0X80) | ((offset_y >> 4) & 0X70) | ((max_x >> 5) & 0X08) | ((offset_x >> 8) & 0X07)},
{TEST, (max_x >> 2) & 0X80},
{ZMOW, (w)&0xFF},
{ZMOH, (h)&0xFF},
{ZMHH, ((h>>6)&0x04)|((w>>8)&0x03)},
{0, 0}
};
if (sensor->pixformat == PIXFORMAT_JPEG) {
c.clk_2x = 0;
c.clk_div = 0;
c.pclk_auto = 0;
c.pclk_div = 8;
if(mode == OV2640_MODE_UXGA) {
c.pclk_div = 12;
}
// if (sensor->xclk_freq_hz == 16000000) {
// c.pclk_div = c.pclk_div / 2;
// }
} else {
#if CONFIG_IDF_TARGET_ESP32
c.clk_2x = 0;
#else
c.clk_2x = 1;
#endif
c.clk_div = 7;
c.pclk_auto = 1;
c.pclk_div = 8;
if (mode == OV2640_MODE_CIF) {
c.clk_div = 3;
} else if(mode == OV2640_MODE_UXGA) {
c.pclk_div = 12;
}
}
ESP_LOGI(TAG, "Set PLL: clk_2x: %u, clk_div: %u, pclk_auto: %u, pclk_div: %u", c.clk_2x, c.clk_div, c.pclk_auto, c.pclk_div);
if (mode == OV2640_MODE_CIF) {
regs = ov2640_settings_to_cif;
} else if (mode == OV2640_MODE_SVGA) {
regs = ov2640_settings_to_svga;
} else {
regs = ov2640_settings_to_uxga;
}
WRITE_REG_OR_RETURN(BANK_DSP, R_BYPASS, R_BYPASS_DSP_BYPAS);
WRITE_REGS_OR_RETURN(regs);
WRITE_REGS_OR_RETURN(win_regs);
WRITE_REG_OR_RETURN(BANK_SENSOR, CLKRC, c.clk);
WRITE_REG_OR_RETURN(BANK_DSP, R_DVP_SP, c.pclk);
WRITE_REG_OR_RETURN(BANK_DSP, R_BYPASS, R_BYPASS_DSP_EN);
vTaskDelay(10 / portTICK_PERIOD_MS);
//required when changing resolution
set_pixformat(sensor, sensor->pixformat);
return ret;
}
static int set_framesize(sensor_t *sensor, framesize_t framesize)
{
int ret = 0;
uint16_t w = resolution[framesize].width;
uint16_t h = resolution[framesize].height;
aspect_ratio_t ratio = resolution[framesize].aspect_ratio;
uint16_t max_x = ratio_table[ratio].max_x;
uint16_t max_y = ratio_table[ratio].max_y;
uint16_t offset_x = ratio_table[ratio].offset_x;
uint16_t offset_y = ratio_table[ratio].offset_y;
ov2640_sensor_mode_t mode = OV2640_MODE_UXGA;
sensor->status.framesize = framesize;
if (framesize <= FRAMESIZE_CIF) {
mode = OV2640_MODE_CIF;
max_x /= 4;
max_y /= 4;
offset_x /= 4;
offset_y /= 4;
if(max_y > 296){
max_y = 296;
}
} else if (framesize <= FRAMESIZE_SVGA) {
mode = OV2640_MODE_SVGA;
max_x /= 2;
max_y /= 2;
offset_x /= 2;
offset_y /= 2;
}
ret = set_window(sensor, mode, offset_x, offset_y, max_x, max_y, w, h);
return ret;
}
static int set_contrast(sensor_t *sensor, int level)
{
int ret=0;
level += 3;
if (level <= 0 || level > NUM_CONTRAST_LEVELS) {
return -1;
}
sensor->status.contrast = level-3;
for (int i=0; i<7; i++) {
WRITE_REG_OR_RETURN(BANK_DSP, contrast_regs[0][i], contrast_regs[level][i]);
}
return ret;
}
static int set_brightness(sensor_t *sensor, int level)
{
int ret=0;
level += 3;
if (level <= 0 || level > NUM_BRIGHTNESS_LEVELS) {
return -1;
}
sensor->status.brightness = level-3;
for (int i=0; i<5; i++) {
WRITE_REG_OR_RETURN(BANK_DSP, brightness_regs[0][i], brightness_regs[level][i]);
}
return ret;
}
static int set_saturation(sensor_t *sensor, int level)
{
int ret=0;
level += 3;
if (level <= 0 || level > NUM_SATURATION_LEVELS) {
return -1;
}
sensor->status.saturation = level-3;
for (int i=0; i<5; i++) {
WRITE_REG_OR_RETURN(BANK_DSP, saturation_regs[0][i], saturation_regs[level][i]);
}
return ret;
}
static int set_special_effect(sensor_t *sensor, int effect)
{
int ret=0;
effect++;
if (effect <= 0 || effect > NUM_SPECIAL_EFFECTS) {
return -1;
}
sensor->status.special_effect = effect-1;
for (int i=0; i<5; i++) {
WRITE_REG_OR_RETURN(BANK_DSP, special_effects_regs[0][i], special_effects_regs[effect][i]);
}
return ret;
}
static int set_wb_mode(sensor_t *sensor, int mode)
{
int ret=0;
if (mode < 0 || mode > NUM_WB_MODES) {
return -1;
}
sensor->status.wb_mode = mode;
SET_REG_BITS_OR_RETURN(BANK_DSP, 0XC7, 6, 1, mode?1:0);
if(mode) {
for (int i=0; i<3; i++) {
WRITE_REG_OR_RETURN(BANK_DSP, wb_modes_regs[0][i], wb_modes_regs[mode][i]);
}
}
return ret;
}
static int set_ae_level(sensor_t *sensor, int level)
{
int ret=0;
level += 3;
if (level <= 0 || level > NUM_AE_LEVELS) {
return -1;
}
sensor->status.ae_level = level-3;
for (int i=0; i<3; i++) {
WRITE_REG_OR_RETURN(BANK_SENSOR, ae_levels_regs[0][i], ae_levels_regs[level][i]);
}
return ret;
}
static int set_quality(sensor_t *sensor, int quality)
{
if(quality < 0) {
quality = 0;
} else if(quality > 63) {
quality = 63;
}
sensor->status.quality = quality;
return write_reg(sensor, BANK_DSP, QS, quality);
}
static int set_agc_gain(sensor_t *sensor, int gain)
{
if(gain < 0) {
gain = 0;
} else if(gain > 30) {
gain = 30;
}
sensor->status.agc_gain = gain;
return write_reg(sensor, BANK_SENSOR, GAIN, agc_gain_tbl[gain]);
}
static int set_gainceiling_sensor(sensor_t *sensor, gainceiling_t gainceiling)
{
sensor->status.gainceiling = gainceiling;
//return write_reg(sensor, BANK_SENSOR, COM9, COM9_AGC_SET(gainceiling));
return set_reg_bits(sensor, BANK_SENSOR, COM9, 5, 7, gainceiling);
}
static int set_aec_value(sensor_t *sensor, int value)
{
if(value < 0) {
value = 0;
} else if(value > 1200) {
value = 1200;
}
sensor->status.aec_value = value;
return set_reg_bits(sensor, BANK_SENSOR, REG04, 0, 3, value & 0x3)
|| write_reg(sensor, BANK_SENSOR, AEC, (value >> 2) & 0xFF)
|| set_reg_bits(sensor, BANK_SENSOR, REG45, 0, 0x3F, value >> 10);
}
static int set_aec2(sensor_t *sensor, int enable)
{
sensor->status.aec2 = enable;
return set_reg_bits(sensor, BANK_DSP, CTRL0, 6, 1, enable?0:1);
}
static int set_colorbar(sensor_t *sensor, int enable)
{
sensor->status.colorbar = enable;
return write_reg_bits(sensor, BANK_SENSOR, COM7, COM7_COLOR_BAR, enable?1:0);
}
static int set_agc_sensor(sensor_t *sensor, int enable)
{
sensor->status.agc = enable;
return write_reg_bits(sensor, BANK_SENSOR, COM8, COM8_AGC_EN, enable?1:0);
}
static int set_aec_sensor(sensor_t *sensor, int enable)
{
sensor->status.aec = enable;
return write_reg_bits(sensor, BANK_SENSOR, COM8, COM8_AEC_EN, enable?1:0);
}
static int set_hmirror_sensor(sensor_t *sensor, int enable)
{
sensor->status.hmirror = enable;
return write_reg_bits(sensor, BANK_SENSOR, REG04, REG04_HFLIP_IMG, enable?1:0);
}
static int set_vflip_sensor(sensor_t *sensor, int enable)
{
int ret = 0;
sensor->status.vflip = enable;
ret = write_reg_bits(sensor, BANK_SENSOR, REG04, REG04_VREF_EN, enable?1:0);
return ret & write_reg_bits(sensor, BANK_SENSOR, REG04, REG04_VFLIP_IMG, enable?1:0);
}
static int set_raw_gma_dsp(sensor_t *sensor, int enable)
{
sensor->status.raw_gma = enable;
return set_reg_bits(sensor, BANK_DSP, CTRL1, 5, 1, enable?1:0);
}
static int set_awb_dsp(sensor_t *sensor, int enable)
{
sensor->status.awb = enable;
return set_reg_bits(sensor, BANK_DSP, CTRL1, 3, 1, enable?1:0);
}
static int set_awb_gain_dsp(sensor_t *sensor, int enable)
{
sensor->status.awb_gain = enable;
return set_reg_bits(sensor, BANK_DSP, CTRL1, 2, 1, enable?1:0);
}
static int set_lenc_dsp(sensor_t *sensor, int enable)
{
sensor->status.lenc = enable;
return set_reg_bits(sensor, BANK_DSP, CTRL1, 1, 1, enable?1:0);
}
static int set_dcw_dsp(sensor_t *sensor, int enable)
{
sensor->status.dcw = enable;
return set_reg_bits(sensor, BANK_DSP, CTRL2, 5, 1, enable?1:0);
}
static int set_bpc_dsp(sensor_t *sensor, int enable)
{
sensor->status.bpc = enable;
return set_reg_bits(sensor, BANK_DSP, CTRL3, 7, 1, enable?1:0);
}
static int set_wpc_dsp(sensor_t *sensor, int enable)
{
sensor->status.wpc = enable;
return set_reg_bits(sensor, BANK_DSP, CTRL3, 6, 1, enable?1:0);
}
//unsupported
static int set_sharpness(sensor_t *sensor, int level)
{
return -1;
}
static int set_denoise(sensor_t *sensor, int level)
{
return -1;
}
static int get_reg(sensor_t *sensor, int reg, int mask)
{
int ret = read_reg(sensor, (reg >> 8) & 0x01, reg & 0xFF);
if(ret > 0){
ret &= mask;
}
return ret;
}
static int set_reg(sensor_t *sensor, int reg, int mask, int value)
{
int ret = 0;
ret = read_reg(sensor, (reg >> 8) & 0x01, reg & 0xFF);
if(ret < 0){
return ret;
}
value = (ret & ~mask) | (value & mask);
ret = write_reg(sensor, (reg >> 8) & 0x01, reg & 0xFF, value);
return ret;
}
static int set_res_raw(sensor_t *sensor, int startX, int startY, int endX, int endY, int offsetX, int offsetY, int totalX, int totalY, int outputX, int outputY, bool scale, bool binning)
{
return set_window(sensor, (ov2640_sensor_mode_t)startX, offsetX, offsetY, totalX, totalY, outputX, outputY);
}
static int _set_pll(sensor_t *sensor, int bypass, int multiplier, int sys_div, int root_2x, int pre_div, int seld5, int pclk_manual, int pclk_div)
{
return -1;
}
static int set_xclk(sensor_t *sensor, int timer, int xclk)
{
int ret = 0;
sensor->xclk_freq_hz = xclk * 1000000U;
ret = xclk_timer_conf(timer, sensor->xclk_freq_hz);
return ret;
}
static int init_status(sensor_t *sensor){
sensor->status.brightness = 0;
sensor->status.contrast = 0;
sensor->status.saturation = 0;
sensor->status.ae_level = 0;
sensor->status.special_effect = 0;
sensor->status.wb_mode = 0;
sensor->status.agc_gain = 30;
int agc_gain = read_reg(sensor, BANK_SENSOR, GAIN);
for (int i=0; i<30; i++){
if(agc_gain >= agc_gain_tbl[i] && agc_gain < agc_gain_tbl[i+1]){
sensor->status.agc_gain = i;
break;
}
}
sensor->status.aec_value = ((uint16_t)get_reg_bits(sensor, BANK_SENSOR, REG45, 0, 0x3F) << 10)
| ((uint16_t)read_reg(sensor, BANK_SENSOR, AEC) << 2)
| get_reg_bits(sensor, BANK_SENSOR, REG04, 0, 3);//0 - 1200
sensor->status.quality = read_reg(sensor, BANK_DSP, QS);
sensor->status.gainceiling = get_reg_bits(sensor, BANK_SENSOR, COM9, 5, 7);
sensor->status.awb = get_reg_bits(sensor, BANK_DSP, CTRL1, 3, 1);
sensor->status.awb_gain = get_reg_bits(sensor, BANK_DSP, CTRL1, 2, 1);
sensor->status.aec = get_reg_bits(sensor, BANK_SENSOR, COM8, 0, 1);
sensor->status.aec2 = get_reg_bits(sensor, BANK_DSP, CTRL0, 6, 1);
sensor->status.agc = get_reg_bits(sensor, BANK_SENSOR, COM8, 2, 1);
sensor->status.bpc = get_reg_bits(sensor, BANK_DSP, CTRL3, 7, 1);
sensor->status.wpc = get_reg_bits(sensor, BANK_DSP, CTRL3, 6, 1);
sensor->status.raw_gma = get_reg_bits(sensor, BANK_DSP, CTRL1, 5, 1);
sensor->status.lenc = get_reg_bits(sensor, BANK_DSP, CTRL1, 1, 1);
sensor->status.hmirror = get_reg_bits(sensor, BANK_SENSOR, REG04, 7, 1);
sensor->status.vflip = get_reg_bits(sensor, BANK_SENSOR, REG04, 6, 1);
sensor->status.dcw = get_reg_bits(sensor, BANK_DSP, CTRL2, 5, 1);
sensor->status.colorbar = get_reg_bits(sensor, BANK_SENSOR, COM7, 1, 1);
sensor->status.sharpness = 0;//not supported
sensor->status.denoise = 0;
return 0;
}
int esp32_camera_ov2640_detect(int slv_addr, sensor_id_t *id)
{
if (OV2640_SCCB_ADDR == slv_addr) {
SCCB_Write(slv_addr, 0xFF, 0x01);//bank sensor
uint16_t PID = SCCB_Read(slv_addr, 0x0A);
if (OV2640_PID == PID) {
id->PID = PID;
id->VER = SCCB_Read(slv_addr, REG_VER);
id->MIDL = SCCB_Read(slv_addr, REG_MIDL);
id->MIDH = SCCB_Read(slv_addr, REG_MIDH);
return PID;
} else {
ESP_LOGI(TAG, "Mismatch PID=0x%x", PID);
}
}
return 0;
}
int esp32_camera_ov2640_init(sensor_t *sensor)
{
sensor->reset = reset;
sensor->init_status = init_status;
sensor->set_pixformat = set_pixformat;
sensor->set_framesize = set_framesize;
sensor->set_contrast = set_contrast;
sensor->set_brightness= set_brightness;
sensor->set_saturation= set_saturation;
sensor->set_quality = set_quality;
sensor->set_colorbar = set_colorbar;
sensor->set_gainceiling = set_gainceiling_sensor;
sensor->set_gain_ctrl = set_agc_sensor;
sensor->set_exposure_ctrl = set_aec_sensor;
sensor->set_hmirror = set_hmirror_sensor;
sensor->set_vflip = set_vflip_sensor;
sensor->set_whitebal = set_awb_dsp;
sensor->set_aec2 = set_aec2;
sensor->set_aec_value = set_aec_value;
sensor->set_special_effect = set_special_effect;
sensor->set_wb_mode = set_wb_mode;
sensor->set_ae_level = set_ae_level;
sensor->set_dcw = set_dcw_dsp;
sensor->set_bpc = set_bpc_dsp;
sensor->set_wpc = set_wpc_dsp;
sensor->set_awb_gain = set_awb_gain_dsp;
sensor->set_agc_gain = set_agc_gain;
sensor->set_raw_gma = set_raw_gma_dsp;
sensor->set_lenc = set_lenc_dsp;
//not supported
sensor->set_sharpness = set_sharpness;
sensor->set_denoise = set_denoise;
sensor->get_reg = get_reg;
sensor->set_reg = set_reg;
sensor->set_res_raw = set_res_raw;
sensor->set_pll = _set_pll;
sensor->set_xclk = set_xclk;
// No autofocus support
sensor->af_is_supported = NULL;
sensor->af_init = NULL;
sensor->af_set_mode = NULL;
sensor->af_trigger = NULL;
sensor->af_get_status = NULL;
sensor->af_set_manual_position = NULL;
ESP_LOGD(TAG, "OV2640 Attached");
return 0;
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+241
View File
@@ -0,0 +1,241 @@
/*
* OV5640 Autofocus Implementation
*
* This file contains the sensor-specific autofocus implementation for OV5640.
*/
#include "sdkconfig.h"
#if defined(CONFIG_CAMERA_AF_SUPPORT) && CONFIG_CAMERA_AF_SUPPORT
#if defined(CONFIG_OV5640_SUPPORT) && CONFIG_OV5640_SUPPORT
#include "ov5640.h"
#include "ov5640_af_firmware.h"
#include "sensor.h"
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_timer.h"
#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG)
#include "esp32-hal-log.h"
#define TAG ""
#else
#include "esp_log.h"
static const char *TAG = "ov5640_af";
#endif
// OV5640 AF registers
#define OV5640_CMD_MAIN 0x3022
#define OV5640_CMD_ACK 0x3023
#define OV5640_CMD_PARA0 0x3024
#define OV5640_CMD_PARA1 0x3025
#define OV5640_CMD_PARA2 0x3026
#define OV5640_CMD_PARA3 0x3027
#define OV5640_CMD_PARA4 0x3028
#define OV5640_CMD_FW_STATUS 0x3029
#define OV5640_AF_TRIG_SINGLE 0x03
#define OV5640_AF_CONTINUOUS 0x04
#define OV5640_FW_STATUS_IDLE 0x70
#define OV5640_FW_STATUS_FOCUSED 0x10
static esp_err_t ov5640_reg_write(sensor_t *sensor, int reg, int value)
{
if (sensor->set_reg(sensor, reg, 0xff, value) < 0) {
return ESP_FAIL;
}
return ESP_OK;
}
static esp_err_t ov5640_reg_read(sensor_t *sensor, int reg, int *out_value)
{
int v = sensor->get_reg(sensor, reg, 0xff);
if (v < 0) {
return ESP_FAIL;
}
*out_value = v;
return ESP_OK;
}
static esp_err_t ov5640_af_wait_ack_clear(sensor_t *sensor, uint32_t timeout_ms)
{
const uint64_t start = esp_timer_get_time();
while (true) {
int ack = 0;
if (ov5640_reg_read(sensor, OV5640_CMD_ACK, &ack) != ESP_OK) {
return ESP_FAIL;
}
if (ack == 0x00) {
return ESP_OK;
}
if (timeout_ms && ((esp_timer_get_time() - start) / 1000ULL) > timeout_ms) {
return ESP_ERR_TIMEOUT;
}
vTaskDelay(pdMS_TO_TICKS(5));
}
}
static esp_err_t ov5640_af_wait_fw_idle(sensor_t *sensor, uint32_t timeout_ms)
{
const uint64_t start = esp_timer_get_time();
while (true) {
int st = 0;
if (ov5640_reg_read(sensor, OV5640_CMD_FW_STATUS, &st) != ESP_OK) {
return ESP_FAIL;
}
if ((uint8_t)st == OV5640_FW_STATUS_IDLE) {
return ESP_OK;
}
if (timeout_ms && ((esp_timer_get_time() - start) / 1000ULL) > timeout_ms) {
return ESP_ERR_TIMEOUT;
}
vTaskDelay(pdMS_TO_TICKS(5));
}
}
static esp_err_t ov5640_af_firmware_load(sensor_t *sensor, uint32_t timeout_ms)
{
esp_err_t ret = ov5640_reg_write(sensor, 0x3000, 0x20); // reset MCU
if (ret != ESP_OK) {
return ret;
}
uint16_t addr = 0x8000;
for (size_t i = 0; i < sizeof(ov5640_af_firmware); i++) {
ret = ov5640_reg_write(sensor, addr, ov5640_af_firmware[i]);
if (ret != ESP_OK) {
return ret;
}
addr++;
}
(void)ov5640_reg_write(sensor, OV5640_CMD_MAIN, 0x00);
(void)ov5640_reg_write(sensor, OV5640_CMD_ACK, 0x00);
(void)ov5640_reg_write(sensor, OV5640_CMD_PARA0, 0x00);
(void)ov5640_reg_write(sensor, OV5640_CMD_PARA1, 0x00);
(void)ov5640_reg_write(sensor, OV5640_CMD_PARA2, 0x00);
(void)ov5640_reg_write(sensor, OV5640_CMD_PARA3, 0x00);
(void)ov5640_reg_write(sensor, OV5640_CMD_PARA4, 0x00);
(void)ov5640_reg_write(sensor, OV5640_CMD_FW_STATUS, 0x7f);
(void)ov5640_reg_write(sensor, 0x3000, 0x00); // start MCU
return ov5640_af_wait_fw_idle(sensor, timeout_ms);
}
static esp_err_t ov5640_af_start(sensor_t *sensor, bool continuous, uint32_t timeout_ms)
{
esp_err_t ret = ov5640_reg_write(sensor, OV5640_CMD_MAIN, 0x01);
if (ret != ESP_OK) {
return ret;
}
ret = ov5640_reg_write(sensor, OV5640_CMD_MAIN, 0x08);
if (ret != ESP_OK) {
return ret;
}
ret = ov5640_af_wait_ack_clear(sensor, timeout_ms);
if (ret != ESP_OK) {
return ret;
}
ret = ov5640_reg_write(sensor, OV5640_CMD_ACK, 0x01);
if (ret != ESP_OK) {
return ret;
}
ret = ov5640_reg_write(sensor, OV5640_CMD_MAIN, continuous ? OV5640_AF_CONTINUOUS : OV5640_AF_TRIG_SINGLE);
if (ret != ESP_OK) {
return ret;
}
return ov5640_af_wait_ack_clear(sensor, timeout_ms);
}
// Sensor function pointer implementations
int ov5640_af_is_supported(sensor_t *sensor)
{
(void)sensor;
return 1; // OV5640 always supports AF
}
int ov5640_af_init(sensor_t *sensor, uint32_t timeout_ms)
{
if (!sensor) {
return -1;
}
esp_err_t ret = ov5640_af_firmware_load(sensor, timeout_ms);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "AF firmware init failed: %s", esp_err_to_name(ret));
return -1;
}
return 0;
}
int ov5640_af_set_mode(sensor_t *sensor, int mode)
{
if (!sensor) {
return -1;
}
esp_err_t ret;
switch (mode) {
case 0: // ESP_CAMERA_AF_MODE_AUTO
ret = ov5640_af_start(sensor, true, 2000);
break;
case 1: // ESP_CAMERA_AF_MODE_MANUAL
ret = ov5640_reg_write(sensor, OV5640_CMD_MAIN, 0x00);
break;
default:
return -1;
}
return (ret == ESP_OK) ? 0 : -1;
}
int ov5640_af_trigger(sensor_t *sensor)
{
if (!sensor) {
return -1;
}
esp_err_t ret = ov5640_af_start(sensor, false, 2000);
return (ret == ESP_OK) ? 0 : -1;
}
int ov5640_af_get_status(sensor_t *sensor, uint8_t *out_raw, bool *out_focused, bool *out_busy)
{
if (!sensor || !out_raw || !out_focused || !out_busy) {
return -1;
}
int st = 0;
esp_err_t ret = ov5640_reg_read(sensor, OV5640_CMD_FW_STATUS, &st);
if (ret != ESP_OK) {
return -1;
}
*out_raw = (uint8_t)st;
*out_focused = ((uint8_t)st == OV5640_FW_STATUS_FOCUSED);
*out_busy = !(((uint8_t)st == OV5640_FW_STATUS_IDLE) || *out_focused);
return 0;
}
int ov5640_af_set_manual_position(sensor_t *sensor, uint16_t position)
{
(void)sensor;
(void)position;
// Manual lens position is not exposed safely in this driver yet.
return -1;
}
#endif // CONFIG_OV5640_SUPPORT
#endif // CONFIG_CAMERA_AF_SUPPORT
+465
View File
@@ -0,0 +1,465 @@
/*
* This file is part of the OpenMV project.
* author: Juan Schiavoni <juanjoseschiavoni@hotmail.com>
* This work is licensed under the MIT license, see the file LICENSE for details.
*
* OV7725 driver.
*
*/
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "sccb.h"
#include "ov7670.h"
#include "ov7670_regs.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include <stdio.h>
#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG)
#include "esp32-hal-log.h"
#else
#include "esp_log.h"
static const char* TAG = "ov7760";
#endif
static int ov7670_clkrc = 0x01;
/*
* The default register settings, as obtained from OmniVision. There
* is really no making sense of most of these - lots of "reserved" values
* and such.
*
* These settings give VGA YUYV.
*/
struct regval_list {
uint8_t reg_num;
uint8_t value;
};
static struct regval_list ov7670_default_regs[] = {
/* Sensor automatically sets output window when resolution changes. */
{TSLB, 0x04},
/* Frame rate 30 fps at 12 Mhz clock */
{CLKRC, 0x00},
{DBLV, 0x4A},
{COM10, COM10_VSYNC_NEG | COM10_PCLK_FREE},
/* Improve white balance */
{COM4, 0x40},
/* Improve color */
{RSVD_B0, 0x84},
/* Enable 50/60 Hz auto detection */
{COM11, COM11_EXP|COM11_HZAUTO},
/* Disable some delays */
{HSYST, 0},
{HSYEN, 0},
{MVFP, MVFP_SUN},
/* More reserved magic, some of which tweaks white balance */
{AWBC1, 0x0a},
{AWBC2, 0xf0},
{AWBC3, 0x34},
{AWBC4, 0x58},
{AWBC5, 0x28},
{AWBC6, 0x3a},
{AWBCTR3, 0x0a},
{AWBCTR2, 0x55},
{AWBCTR1, 0x11},
{AWBCTR0, 0x9e},
{COM8, COM8_FAST_AUTO|COM8_STEP_UNLIMIT|COM8_AGC_EN|COM8_AEC_EN|COM8_AWB_EN},
/* End marker is FF because in ov7670 the address of GAIN 0 and default value too. */
{0xFF, 0xFF},
};
static struct regval_list ov7670_fmt_yuv422[] = {
{ COM7, 0x0 }, /* Selects YUV mode */
{ RGB444, 0 }, /* No RGB444 please */
{ COM1, 0 }, /* CCIR601 */
{ COM15, COM15_R00FF },
{ MVFP, MVFP_SUN },
{ COM9, 0x6A }, /* 128x gain ceiling; 0x8 is reserved bit */
{ MTX1, 0x80 }, /* "matrix coefficient 1" */
{ MTX2, 0x80 }, /* "matrix coefficient 2" */
{ MTX3, 0 }, /* vb */
{ MTX4, 0x22 }, /* "matrix coefficient 4" */
{ MTX5, 0x5e }, /* "matrix coefficient 5" */
{ MTX6, 0x80 }, /* "matrix coefficient 6" */
{ COM13, COM13_UVSAT },
{ 0xff, 0xff }, /* END MARKER */
};
static struct regval_list ov7670_fmt_rgb565[] = {
{ COM7, COM7_FMT_RGB565 }, /* Selects RGB mode */
{ RGB444, 0 }, /* No RGB444 please */
{ COM1, 0x0 }, /* CCIR601 */
{ COM15, COM15_RGB565 |COM15_R00FF },
{ MVFP, MVFP_SUN },
{ COM9, 0x6A }, /* 128x gain ceiling; 0x8 is reserved bit */
{ MTX1, 0xb3 }, /* "matrix coefficient 1" */
{ MTX2, 0xb3 }, /* "matrix coefficient 2" */
{ MTX3, 0 }, /* vb */
{ MTX4, 0x3d }, /* "matrix coefficient 4" */
{ MTX5, 0xa7 }, /* "matrix coefficient 5" */
{ MTX6, 0xe4 }, /* "matrix coefficient 6" */
{ COM13, COM13_UVSAT },
{ 0xff, 0xff }, /* END MARKER */
};
static struct regval_list ov7670_vga[] = {
{ COM3, 0x00 },
{ COM14, 0x00 },
{ SCALING_XSC, 0x3A },
{ SCALING_YSC, 0x35 },
{ SCALING_DCWCTR, 0x11 },
{ SCALING_PCLK_DIV, 0xF0 },
{ SCALING_PCLK_DELAY, 0x02 },
{ 0xff, 0xff },
};
static struct regval_list ov7670_qvga[] = {
{ COM3, 0x04 },
{ COM14, 0x19 },
{ SCALING_XSC, 0x3A },
{ SCALING_YSC, 0x35 },
{ SCALING_DCWCTR, 0x11 },
{ SCALING_PCLK_DIV, 0xF1 },
{ SCALING_PCLK_DELAY, 0x02 },
{ 0xff, 0xff },
};
static struct regval_list ov7670_qqvga[] = {
{ COM3, 0x04 }, //DCW enable
{ COM14, 0x1a }, //pixel clock divided by 4, manual scaling enable, DCW and PCLK controlled by register
{ SCALING_XSC, 0x3a },
{ SCALING_YSC, 0x35 },
{ SCALING_DCWCTR, 0x22 }, //downsample by 4
{ SCALING_PCLK_DIV, 0xf2 }, //pixel clock divided by 4
{ SCALING_PCLK_DELAY, 0x02 },
{ 0xff, 0xff },
};
/*
* Write a list of register settings; ff/ff stops the process.
*/
static int ov7670_write_array(sensor_t *sensor, struct regval_list *vals)
{
int ret = 0;
while ( (vals->reg_num != 0xff || vals->value != 0xff) && (ret == 0) ) {
ret = SCCB_Write(sensor->slv_addr, vals->reg_num, vals->value);
ESP_LOGD(TAG, "reset reg %02X, W(%02X) R(%02X)", vals->reg_num,
vals->value, SCCB_Read(sensor->slv_addr, vals->reg_num) );
vals++;
}
return ret;
}
/*
* Calculate the frame control registers.
*/
static int ov7670_frame_control(sensor_t *sensor, int hstart, int hstop, int vstart, int vstop)
{
struct regval_list frame[7];
frame[0].reg_num = HSTART;
frame[0].value = (hstart >> 3);
frame[1].reg_num = HSTOP;
frame[1].value = (hstop >> 3);
frame[2].reg_num = HREF;
frame[2].value = (((hstop & 0x07) << 3) | (hstart & 0x07));
frame[3].reg_num = VSTART;
frame[3].value = (vstart >> 2);
frame[4].reg_num = VSTOP;
frame[4].value = (vstop >> 2);
frame[5].reg_num = VREF;
frame[5].value = (((vstop & 0x02) << 2) | (vstart & 0x02));
/* End mark */
frame[6].reg_num = 0xFF;
frame[6].value = 0xFF;
return ov7670_write_array(sensor, frame);
}
static int reset(sensor_t *sensor)
{
int ret;
// Reset all registers
SCCB_Write(sensor->slv_addr, COM7, COM7_RESET);
// Delay 10 ms
vTaskDelay(10 / portTICK_PERIOD_MS);
ret = ov7670_write_array(sensor, ov7670_default_regs);
// Delay
vTaskDelay(30 / portTICK_PERIOD_MS);
return ret;
}
static int set_pixformat(sensor_t *sensor, pixformat_t pixformat)
{
int ret;
switch (pixformat) {
case PIXFORMAT_RGB565:
case PIXFORMAT_RGB888:
ret = ov7670_write_array(sensor, ov7670_fmt_rgb565);
break;
case PIXFORMAT_YUV422:
case PIXFORMAT_GRAYSCALE:
default:
ret = ov7670_write_array(sensor, ov7670_fmt_yuv422);
break;
}
vTaskDelay(30 / portTICK_PERIOD_MS);
/*
* If we're running RGB565, we must rewrite clkrc after setting
* the other parameters or the image looks poor. If we're *not*
* doing RGB565, we must not rewrite clkrc or the image looks
* *really* poor.
*
* (Update) Now that we retain clkrc state, we should be able
* to write it unconditionally, and that will make the frame
* rate persistent too.
*/
if (pixformat == PIXFORMAT_RGB565) {
ret = SCCB_Write(sensor->slv_addr, CLKRC, ov7670_clkrc);
}
return ret;
}
static int set_framesize(sensor_t *sensor, framesize_t framesize)
{
int ret;
// store clkrc before changing window settings...
ov7670_clkrc = SCCB_Read(sensor->slv_addr, CLKRC);
switch (framesize){
case FRAMESIZE_VGA:
if( (ret = ov7670_write_array(sensor, ov7670_vga)) == 0 ) {
/* These values from Omnivision */
ret = ov7670_frame_control(sensor, 158, 14, 10, 490);
}
break;
case FRAMESIZE_QVGA:
if( (ret = ov7670_write_array(sensor, ov7670_qvga)) == 0 ) {
/* These values from Omnivision */
ret = ov7670_frame_control(sensor, 158, 14, 10, 490);
}
break;
case FRAMESIZE_QQVGA:
if( (ret = ov7670_write_array(sensor, ov7670_qqvga)) == 0 ) {
/* These values from Omnivision */
ret = ov7670_frame_control(sensor, 158, 14, 12, 490);
}
break;
default:
ret = -1;
}
vTaskDelay(30 / portTICK_PERIOD_MS);
if (ret == 0) {
sensor->status.framesize = framesize;
}
return ret;
}
static int set_colorbar(sensor_t *sensor, int enable)
{
uint8_t ret = 0;
// Read register scaling_xsc
uint8_t reg = SCCB_Read(sensor->slv_addr, SCALING_XSC);
// Pattern to set color bar bit[0]=0 in every case
reg = SCALING_XSC_CBAR(reg);
// Write pattern to SCALING_XSC
ret = SCCB_Write(sensor->slv_addr, SCALING_XSC, reg);
// Read register scaling_ysc
reg = SCCB_Read(sensor->slv_addr, SCALING_YSC);
// Pattern to set color bar bit[0]=0 in every case
reg = SCALING_YSC_CBAR(reg, enable);
// Write pattern to SCALING_YSC
ret = ret | SCCB_Write(sensor->slv_addr, SCALING_YSC, reg);
// return 0 or 0xFF
return ret;
}
static int set_whitebal(sensor_t *sensor, int enable)
{
// Read register COM8
uint8_t reg = SCCB_Read(sensor->slv_addr, COM8);
// Set white bal on/off
reg = COM8_SET_AWB(reg, enable);
// Write back register COM8
return SCCB_Write(sensor->slv_addr, COM8, reg);
}
static int set_gain_ctrl(sensor_t *sensor, int enable)
{
// Read register COM8
uint8_t reg = SCCB_Read(sensor->slv_addr, COM8);
// Set white bal on/off
reg = COM8_SET_AGC(reg, enable);
// Write back register COM8
return SCCB_Write(sensor->slv_addr, COM8, reg);
}
static int set_exposure_ctrl(sensor_t *sensor, int enable)
{
// Read register COM8
uint8_t reg = SCCB_Read(sensor->slv_addr, COM8);
// Set white bal on/off
reg = COM8_SET_AEC(reg, enable);
// Write back register COM8
return SCCB_Write(sensor->slv_addr, COM8, reg);
}
static int set_hmirror(sensor_t *sensor, int enable)
{
// Read register MVFP
uint8_t reg = SCCB_Read(sensor->slv_addr, MVFP);
// Set mirror on/off
reg = MVFP_SET_MIRROR(reg, enable);
// Write back register MVFP
return SCCB_Write(sensor->slv_addr, MVFP, reg);
}
static int set_vflip(sensor_t *sensor, int enable)
{
// Read register MVFP
uint8_t reg = SCCB_Read(sensor->slv_addr, MVFP);
// Set mirror on/off
reg = MVFP_SET_FLIP(reg, enable);
// Write back register MVFP
return SCCB_Write(sensor->slv_addr, MVFP, reg);
}
static int init_status(sensor_t *sensor)
{
sensor->status.awb = 0;
sensor->status.aec = 0;
sensor->status.agc = 0;
sensor->status.hmirror = 0;
sensor->status.vflip = 0;
sensor->status.colorbar = 0;
return 0;
}
static int set_dummy(sensor_t *sensor, int val){ return -1; }
static int set_gainceiling_dummy(sensor_t *sensor, gainceiling_t val){ return -1; }
int esp32_camera_ov7670_detect(int slv_addr, sensor_id_t *id)
{
if (OV7670_SCCB_ADDR == slv_addr) {
SCCB_Write(slv_addr, 0xFF, 0x01);//bank sensor
uint16_t PID = SCCB_Read(slv_addr, 0x0A);
if (OV7670_PID == PID) {
id->PID = PID;
id->VER = SCCB_Read(slv_addr, REG_VER);
id->MIDL = SCCB_Read(slv_addr, REG_MIDL);
id->MIDH = SCCB_Read(slv_addr, REG_MIDH);
return PID;
} else {
ESP_LOGI(TAG, "Mismatch PID=0x%x", PID);
}
}
return 0;
}
int esp32_camera_ov7670_init(sensor_t *sensor)
{
// Set function pointers
sensor->reset = reset;
sensor->init_status = init_status;
sensor->set_pixformat = set_pixformat;
sensor->set_framesize = set_framesize;
sensor->set_colorbar = set_colorbar;
sensor->set_whitebal = set_whitebal;
sensor->set_gain_ctrl = set_gain_ctrl;
sensor->set_exposure_ctrl = set_exposure_ctrl;
sensor->set_hmirror = set_hmirror;
sensor->set_vflip = set_vflip;
//not supported
sensor->set_brightness= set_dummy;
sensor->set_saturation= set_dummy;
sensor->set_quality = set_dummy;
sensor->set_gainceiling = set_gainceiling_dummy;
sensor->set_aec2 = set_dummy;
sensor->set_aec_value = set_dummy;
sensor->set_special_effect = set_dummy;
sensor->set_wb_mode = set_dummy;
sensor->set_ae_level = set_dummy;
sensor->set_dcw = set_dummy;
sensor->set_bpc = set_dummy;
sensor->set_wpc = set_dummy;
sensor->set_awb_gain = set_dummy;
sensor->set_agc_gain = set_dummy;
sensor->set_raw_gma = set_dummy;
sensor->set_lenc = set_dummy;
sensor->set_sharpness = set_dummy;
sensor->set_denoise = set_dummy;
// Retrieve sensor's signature
sensor->id.MIDH = SCCB_Read(sensor->slv_addr, REG_MIDH);
sensor->id.MIDL = SCCB_Read(sensor->slv_addr, REG_MIDL);
sensor->id.PID = SCCB_Read(sensor->slv_addr, REG_PID);
sensor->id.VER = SCCB_Read(sensor->slv_addr, REG_VER);
// No autofocus support
sensor->af_is_supported = NULL;
sensor->af_init = NULL;
sensor->af_set_mode = NULL;
sensor->af_trigger = NULL;
sensor->af_get_status = NULL;
sensor->af_set_manual_position = NULL;
ESP_LOGD(TAG, "OV7670 Attached");
return 0;
}
+583
View File
@@ -0,0 +1,583 @@
/*
* This file is part of the OpenMV project.
* Copyright (c) 2013/2014 Ibrahim Abdelkader <i.abdalkader@gmail.com>
* This work is licensed under the MIT license, see the file LICENSE for details.
*
* OV7725 driver.
*
*/
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "sccb.h"
#include "xclk.h"
#include "ov7725.h"
#include "ov7725_regs.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG)
#include "esp32-hal-log.h"
#else
#include "esp_log.h"
static const char* TAG = "ov7725";
#endif
static const uint8_t default_regs[][2] = {
{COM3, COM3_SWAP_YUV},
{COM7, COM7_RES_QVGA | COM7_FMT_YUV},
{COM4, 0x01 | 0x00}, /* bypass PLL (0x00:off, 0x40:4x, 0x80:6x, 0xC0:8x) */
{CLKRC, 0x80 | 0x03}, /* Res/Bypass pre-scalar (0x40:bypass, 0x00-0x3F:prescaler PCLK=XCLK/(prescaler + 1)/2 ) */
// QVGA Window Size
{HSTART, 0x3F},
{HSIZE, 0x50},
{VSTART, 0x03},
{VSIZE, 0x78},
{HREF, 0x00},
// Scale down to QVGA Resolution
{HOUTSIZE, 0x50},
{VOUTSIZE, 0x78},
{EXHCH, 0x00},
{COM12, 0x03},
{TGT_B, 0x7F},
{FIXGAIN, 0x09},
{AWB_CTRL0, 0xE0},
{DSP_CTRL1, 0xFF},
{DSP_CTRL2, DSP_CTRL2_VDCW_EN | DSP_CTRL2_HDCW_EN | DSP_CTRL2_HZOOM_EN | DSP_CTRL2_VZOOM_EN},
{DSP_CTRL3, 0x00},
{DSP_CTRL4, 0x00},
{DSPAUTO, 0xFF},
{COM8, 0xF0},
{COM6, 0xC5},
{COM9, 0x11},
{COM10, COM10_VSYNC_NEG | COM10_PCLK_FREE}, //Invert VSYNC and MASK PCLK
{BDBASE, 0x7F},
{DBSTEP, 0x03},
{AEW, 0x75},
{AEB, 0x64},
{VPT, 0xA1},
{EXHCL, 0x00},
{AWB_CTRL3, 0xAA},
{COM8, 0xFF},
//Gamma
{GAM1, 0x0C},
{GAM2, 0x16},
{GAM3, 0x2A},
{GAM4, 0x4E},
{GAM5, 0x61},
{GAM6, 0x6F},
{GAM7, 0x7B},
{GAM8, 0x86},
{GAM9, 0x8E},
{GAM10, 0x97},
{GAM11, 0xA4},
{GAM12, 0xAF},
{GAM13, 0xC5},
{GAM14, 0xD7},
{GAM15, 0xE8},
{SLOP, 0x20},
{EDGE1, 0x05},
{EDGE2, 0x03},
{EDGE3, 0x00},
{DNSOFF, 0x01},
{MTX1, 0xB0},
{MTX2, 0x9D},
{MTX3, 0x13},
{MTX4, 0x16},
{MTX5, 0x7B},
{MTX6, 0x91},
{MTX_CTRL, 0x1E},
{BRIGHTNESS, 0x08},
{CONTRAST, 0x30},
{UVADJ0, 0x81},
{SDE, (SDE_CONT_BRIGHT_EN | SDE_SATURATION_EN)},
// For 30 fps/60Hz
{DM_LNL, 0x00},
{DM_LNH, 0x00},
{BDBASE, 0x7F},
{DBSTEP, 0x03},
// Lens Correction, should be tuned with real camera module
{LC_RADI, 0x10},
{LC_COEF, 0x10},
{LC_COEFB, 0x14},
{LC_COEFR, 0x17},
{LC_CTR, 0x05},
{COM5, 0xF5}, //0x65
{0x00, 0x00},
};
static int get_reg(sensor_t *sensor, int reg, int mask)
{
int ret = SCCB_Read(sensor->slv_addr, reg & 0xFF);
if(ret > 0){
ret &= mask;
}
return ret;
}
static int set_reg(sensor_t *sensor, int reg, int mask, int value)
{
int ret = 0;
ret = SCCB_Read(sensor->slv_addr, reg & 0xFF);
if(ret < 0){
return ret;
}
value = (ret & ~mask) | (value & mask);
ret = SCCB_Write(sensor->slv_addr, reg & 0xFF, value);
return ret;
}
static int set_reg_bits(sensor_t *sensor, uint8_t reg, uint8_t offset, uint8_t length, uint8_t value)
{
int ret = 0;
ret = SCCB_Read(sensor->slv_addr, reg);
if(ret < 0){
return ret;
}
uint8_t mask = ((1 << length) - 1) << offset;
value = (ret & ~mask) | ((value << offset) & mask);
ret = SCCB_Write(sensor->slv_addr, reg & 0xFF, value);
return ret;
}
static int get_reg_bits(sensor_t *sensor, uint8_t reg, uint8_t offset, uint8_t length)
{
int ret = 0;
ret = SCCB_Read(sensor->slv_addr, reg);
if(ret < 0){
return ret;
}
uint8_t mask = ((1 << length) - 1) << offset;
return (ret & mask) >> offset;
}
static int reset(sensor_t *sensor)
{
int i=0;
const uint8_t (*regs)[2];
// Reset all registers
SCCB_Write(sensor->slv_addr, COM7, COM7_RESET);
// Delay 10 ms
vTaskDelay(10 / portTICK_PERIOD_MS);
// Write default registers
for (i=0, regs = default_regs; regs[i][0]; i++) {
SCCB_Write(sensor->slv_addr, regs[i][0], regs[i][1]);
}
// Delay
vTaskDelay(30 / portTICK_PERIOD_MS);
return 0;
}
static int set_pixformat(sensor_t *sensor, pixformat_t pixformat)
{
int ret=0;
sensor->pixformat = pixformat;
// Read register COM7
uint8_t reg = SCCB_Read(sensor->slv_addr, COM7);
switch (pixformat) {
case PIXFORMAT_RGB565:
reg = COM7_SET_RGB(reg, COM7_FMT_RGB565);
break;
case PIXFORMAT_YUV422:
case PIXFORMAT_GRAYSCALE:
reg = COM7_SET_FMT(reg, COM7_FMT_YUV);
break;
default:
return -1;
}
// Write back register COM7
ret = SCCB_Write(sensor->slv_addr, COM7, reg);
// Delay
vTaskDelay(30 / portTICK_PERIOD_MS);
return ret;
}
static int set_framesize(sensor_t *sensor, framesize_t framesize)
{
int ret=0;
if (framesize > FRAMESIZE_VGA) {
return -1;
}
uint16_t w = resolution[framesize].width;
uint16_t h = resolution[framesize].height;
uint8_t reg = SCCB_Read(sensor->slv_addr, COM7);
sensor->status.framesize = framesize;
// Write MSBs
ret |= SCCB_Write(sensor->slv_addr, HOUTSIZE, w>>2);
ret |= SCCB_Write(sensor->slv_addr, VOUTSIZE, h>>1);
ret |= SCCB_Write(sensor->slv_addr, HSIZE, w>>2);
ret |= SCCB_Write(sensor->slv_addr, VSIZE, h>>1);
// Write LSBs
ret |= SCCB_Write(sensor->slv_addr, HREF, ((w&0x3) | ((h&0x1) << 2)));
if (framesize < FRAMESIZE_VGA) {
// Enable auto-scaling/zooming factors
ret |= SCCB_Write(sensor->slv_addr, DSPAUTO, 0xFF);
ret |= SCCB_Write(sensor->slv_addr, HSTART, 0x3F);
ret |= SCCB_Write(sensor->slv_addr, VSTART, 0x03);
ret |= SCCB_Write(sensor->slv_addr, COM7, reg | COM7_RES_QVGA);
ret |= SCCB_Write(sensor->slv_addr, CLKRC, 0x80 | 0x01);
} else {
// Disable auto-scaling/zooming factors
ret |= SCCB_Write(sensor->slv_addr, DSPAUTO, 0xF3);
// Clear auto-scaling/zooming factors
ret |= SCCB_Write(sensor->slv_addr, SCAL0, 0x00);
ret |= SCCB_Write(sensor->slv_addr, SCAL1, 0x00);
ret |= SCCB_Write(sensor->slv_addr, SCAL2, 0x00);
ret |= SCCB_Write(sensor->slv_addr, HSTART, 0x23);
ret |= SCCB_Write(sensor->slv_addr, VSTART, 0x07);
ret |= SCCB_Write(sensor->slv_addr, COM7, reg & ~COM7_RES_QVGA);
ret |= SCCB_Write(sensor->slv_addr, CLKRC, 0x80 | 0x03);
}
// Delay
vTaskDelay(30 / portTICK_PERIOD_MS);
return ret;
}
static int set_colorbar(sensor_t *sensor, int enable)
{
int ret=0;
uint8_t reg;
sensor->status.colorbar = enable;
// Read reg COM3
reg = SCCB_Read(sensor->slv_addr, COM3);
// Enable colorbar test pattern output
reg = COM3_SET_CBAR(reg, enable);
// Write back COM3
ret |= SCCB_Write(sensor->slv_addr, COM3, reg);
// Read reg DSP_CTRL3
reg = SCCB_Read(sensor->slv_addr, DSP_CTRL3);
// Enable DSP colorbar output
reg = DSP_CTRL3_SET_CBAR(reg, enable);
// Write back DSP_CTRL3
ret |= SCCB_Write(sensor->slv_addr, DSP_CTRL3, reg);
return ret;
}
static int set_whitebal(sensor_t *sensor, int enable)
{
if(set_reg_bits(sensor, COM8, 1, 1, enable) >= 0){
sensor->status.awb = !!enable;
}
return sensor->status.awb;
}
static int set_gain_ctrl(sensor_t *sensor, int enable)
{
if(set_reg_bits(sensor, COM8, 2, 1, enable) >= 0){
sensor->status.agc = !!enable;
}
return sensor->status.agc;
}
static int set_exposure_ctrl(sensor_t *sensor, int enable)
{
if(set_reg_bits(sensor, COM8, 0, 1, enable) >= 0){
sensor->status.aec = !!enable;
}
return sensor->status.aec;
}
static int set_hmirror(sensor_t *sensor, int enable)
{
if(set_reg_bits(sensor, COM3, 6, 1, enable) >= 0){
sensor->status.hmirror = !!enable;
}
return sensor->status.hmirror;
}
static int set_vflip(sensor_t *sensor, int enable)
{
if(set_reg_bits(sensor, COM3, 7, 1, enable) >= 0){
sensor->status.vflip = !!enable;
}
return sensor->status.vflip;
}
static int set_dcw_dsp(sensor_t *sensor, int enable)
{
int ret = 0;
ret = set_reg_bits(sensor, 0x65, 2, 1, !enable);
if (ret == 0) {
ESP_LOGD(TAG, "Set dcw to: %d", enable);
sensor->status.dcw = enable;
}
return ret;
}
static int set_aec2(sensor_t *sensor, int enable)
{
int ret = 0;
ret = set_reg_bits(sensor, COM8, 7, 1, enable);
if (ret == 0) {
ESP_LOGD(TAG, "Set aec2 to: %d", enable);
sensor->status.aec2 = enable;
}
return ret;
}
static int set_bpc_dsp(sensor_t *sensor, int enable)
{
int ret = 0;
ret = set_reg_bits(sensor, 0x64, 1, 1, enable);
if (ret == 0) {
ESP_LOGD(TAG, "Set bpc to: %d", enable);
sensor->status.bpc = enable;
}
return ret;
}
static int set_wpc_dsp(sensor_t *sensor, int enable)
{
int ret = 0;
ret = set_reg_bits(sensor, 0x64, 0, 1, enable);
if (ret == 0) {
ESP_LOGD(TAG, "Set wpc to: %d", enable);
sensor->status.wpc = enable;
}
return ret;
}
static int set_raw_gma_dsp(sensor_t *sensor, int enable)
{
int ret = 0;
ret = set_reg_bits(sensor, 0x64, 2, 1, enable);
if (ret == 0) {
ESP_LOGD(TAG, "Set raw_gma to: %d", enable);
sensor->status.raw_gma = enable;
}
return ret;
}
static int set_lenc_dsp(sensor_t *sensor, int enable)
{
int ret = 0;
ret = set_reg_bits(sensor, LC_CTR, 0, 1, enable);
if (ret == 0) {
ESP_LOGD(TAG, "Set lenc to: %d", enable);
sensor->status.lenc = enable;
}
return ret;
}
//real gain
static int set_agc_gain(sensor_t *sensor, int gain)
{
int ret = 0;
ret = set_reg_bits(sensor, COM9, 4, 3, gain % 5);
if (ret == 0) {
ESP_LOGD(TAG, "Set gain to: %d", gain);
sensor->status.agc_gain = gain;
}
return ret;
}
static int set_aec_value(sensor_t *sensor, int value)
{
int ret = 0;
ret = SCCB_Write(sensor->slv_addr, AEC, value & 0xff) | SCCB_Write(sensor->slv_addr, AECH, value >> 8);
if (ret == 0) {
ESP_LOGD(TAG, "Set aec_value to: %d", value);
sensor->status.aec_value = value;
}
return ret;
}
static int set_awb_gain_dsp(sensor_t *sensor, int enable)
{
int ret = 0;
ret = set_reg_bits(sensor, 0x63, 7, 1, enable);
if (ret == 0) {
ESP_LOGD(TAG, "Set awb_gain to: %d", enable);
sensor->status.awb_gain = enable;
}
return ret;
}
static int set_brightness(sensor_t *sensor, int level)
{
int ret = 0;
ret = SCCB_Write(sensor->slv_addr, 0x9B, level);
if (ret == 0) {
ESP_LOGD(TAG, "Set brightness to: %d", level);
sensor->status.brightness = level;
}
return ret;
}
static int set_contrast(sensor_t *sensor, int level)
{
int ret = 0;
ret = SCCB_Write(sensor->slv_addr, 0x9C, level);
if (ret == 0) {
ESP_LOGD(TAG, "Set contrast to: %d", level);
sensor->status.contrast = level;
}
return ret;
}
static int init_status(sensor_t *sensor)
{
sensor->status.brightness = SCCB_Read(sensor->slv_addr, 0x9B);
sensor->status.contrast = SCCB_Read(sensor->slv_addr, 0x9C);
sensor->status.saturation = 0;
sensor->status.ae_level = 0;
sensor->status.special_effect = get_reg_bits(sensor, 0x64, 5, 1);
sensor->status.wb_mode = get_reg_bits(sensor, 0x6B, 7, 1);
sensor->status.agc_gain = get_reg_bits(sensor, COM9, 4, 3);
sensor->status.aec_value = SCCB_Read(sensor->slv_addr, AEC) | (SCCB_Read(sensor->slv_addr, AECH) << 8);
sensor->status.gainceiling = SCCB_Read(sensor->slv_addr, 0x00);
sensor->status.awb = get_reg_bits(sensor, COM8, 1, 1);
sensor->status.awb_gain = get_reg_bits(sensor, 0x63, 7, 1);
sensor->status.aec = get_reg_bits(sensor, COM8, 0, 1);
sensor->status.aec2 = get_reg_bits(sensor, COM8, 7, 1);
sensor->status.agc = get_reg_bits(sensor, COM8, 2, 1);
sensor->status.bpc = get_reg_bits(sensor, 0x64, 1, 1);
sensor->status.wpc = get_reg_bits(sensor, 0x64, 0, 1);
sensor->status.raw_gma = get_reg_bits(sensor, 0x64, 2, 1);
sensor->status.lenc = get_reg_bits(sensor, LC_CTR, 0, 1);
sensor->status.hmirror = get_reg_bits(sensor, COM3, 6, 1);
sensor->status.vflip = get_reg_bits(sensor, COM3, 7, 1);
sensor->status.dcw = get_reg_bits(sensor, 0x65, 2, 1);
sensor->status.colorbar = get_reg_bits(sensor, COM3, 0, 1);
sensor->status.sharpness = get_reg_bits(sensor, EDGE0, 0, 5);
sensor->status.denoise = SCCB_Read(sensor->slv_addr, 0x8E);
return 0;
}
static int set_dummy(sensor_t *sensor, int val){ return -1; }
static int set_gainceiling_dummy(sensor_t *sensor, gainceiling_t val){ return -1; }
static int set_res_raw(sensor_t *sensor, int startX, int startY, int endX, int endY, int offsetX, int offsetY, int totalX, int totalY, int outputX, int outputY, bool scale, bool binning){return -1;}
static int _set_pll(sensor_t *sensor, int bypass, int multiplier, int sys_div, int root_2x, int pre_div, int seld5, int pclk_manual, int pclk_div){return -1;}
static int set_xclk(sensor_t *sensor, int timer, int xclk)
{
int ret = 0;
sensor->xclk_freq_hz = xclk * 1000000U;
ret = xclk_timer_conf(timer, sensor->xclk_freq_hz);
return ret;
}
int esp32_camera_ov7725_detect(int slv_addr, sensor_id_t *id)
{
if (OV7725_SCCB_ADDR == slv_addr) {
SCCB_Write(slv_addr, 0xFF, 0x01);//bank sensor
uint16_t PID = SCCB_Read(slv_addr, 0x0A);
if (OV7725_PID == PID) {
id->PID = PID;
id->VER = SCCB_Read(slv_addr, REG_VER);
id->MIDL = SCCB_Read(slv_addr, REG_MIDL);
id->MIDH = SCCB_Read(slv_addr, REG_MIDH);
return PID;
} else {
ESP_LOGI(TAG, "Mismatch PID=0x%x", PID);
}
}
return 0;
}
int esp32_camera_ov7725_init(sensor_t *sensor)
{
// Set function pointers
sensor->reset = reset;
sensor->init_status = init_status;
sensor->set_pixformat = set_pixformat;
sensor->set_framesize = set_framesize;
sensor->set_colorbar = set_colorbar;
sensor->set_whitebal = set_whitebal;
sensor->set_gain_ctrl = set_gain_ctrl;
sensor->set_exposure_ctrl = set_exposure_ctrl;
sensor->set_hmirror = set_hmirror;
sensor->set_vflip = set_vflip;
sensor->set_brightness = set_brightness;
sensor->set_contrast = set_contrast;
sensor->set_aec2 = set_aec2;
sensor->set_aec_value = set_aec_value;
sensor->set_awb_gain = set_awb_gain_dsp;
sensor->set_agc_gain = set_agc_gain;
sensor->set_dcw = set_dcw_dsp;
sensor->set_bpc = set_bpc_dsp;
sensor->set_wpc = set_wpc_dsp;
sensor->set_raw_gma = set_raw_gma_dsp;
sensor->set_lenc = set_lenc_dsp;
//not supported
sensor->set_saturation= set_dummy;
sensor->set_sharpness = set_dummy;
sensor->set_denoise = set_dummy;
sensor->set_quality = set_dummy;
sensor->set_special_effect = set_dummy;
sensor->set_wb_mode = set_dummy;
sensor->set_ae_level = set_dummy;
sensor->set_gainceiling = set_gainceiling_dummy;
sensor->get_reg = get_reg;
sensor->set_reg = set_reg;
sensor->set_res_raw = set_res_raw;
sensor->set_pll = _set_pll;
sensor->set_xclk = set_xclk;
// No autofocus support
sensor->af_is_supported = NULL;
sensor->af_init = NULL;
sensor->af_set_mode = NULL;
sensor->af_trigger = NULL;
sensor->af_get_status = NULL;
sensor->af_set_manual_position = NULL;
// Retrieve sensor's signature
sensor->id.MIDH = SCCB_Read(sensor->slv_addr, REG_MIDH);
sensor->id.MIDL = SCCB_Read(sensor->slv_addr, REG_MIDL);
sensor->id.PID = SCCB_Read(sensor->slv_addr, REG_PID);
sensor->id.VER = SCCB_Read(sensor->slv_addr, REG_VER);
ESP_LOGD(TAG, "OV7725 Attached");
return 0;
}
@@ -0,0 +1,27 @@
#ifndef __BF20A6_H__
#define __BF20A6_H__
#include "sensor.h"
/**
* @brief Detect sensor pid
*
* @param slv_addr SCCB address
* @param id Detection result
* @return
* 0: Can't detect this sensor
* Nonzero: This sensor has been detected
*/
int esp32_camera_bf20a6_detect(int slv_addr, sensor_id_t *id);
/**
* @brief initialize sensor function pointers
*
* @param sensor pointer of sensor
* @return
* Always 0
*/
int esp32_camera_bf20a6_init(sensor_t *sensor);
#endif // __BF20A6_H__
@@ -0,0 +1,12 @@
/*
* BF20A6 register definitions.
*/
#ifndef __BF20A6_REG_REGS_H__
#define __BF20A6_REG_REGS_H__
#define SENSOR_ID_HIGH 0XFC
#define SENSOR_ID_LOW 0XFD
#define RESET_RELATED 0XF2
#endif //__BF20A6_REG_REGS_H__
@@ -0,0 +1,158 @@
#include <stdint.h>
#define REG_DLY 0xffff
#define REGLIST_TAIL 0xffff /* Array end token */
static const uint16_t bf20a6_default_init_regs[][2] = {
{0xf2,0x01},
{0x12,0x20},
{0x3a,0x00},
{0xe1,0x92},
{0xe3,0x12},// PLL Control, important for framerate(choice: 0x02\0x12\0x22\0x32\0x82)
{0xe0,0x00},
{0x2a,0x98},
{0xcd,0x17},
{0xc0,0x10},
{0xc6,0x1d},
{0x10,0x35},
{0xe2,0x09},
{0xe4,0x72},
{0xe5,0x22},
{0xe6,0x24},
{0xe7,0x64},
{0xe8,0xa2}, // DVP:a2}, SPI:f2 VDDIO=1.8V,E8[2]=1},VDDIO=2.8V,E8[2]=0},
{0x4a,0x00},
{0x00,0x03},
{0x1f,0x02},
{0x22,0x02},
{0x0c,0x31},
{0x00,0x00},
{0x60,0x81},
{0x61,0x81},
{0xa0,0x08},
{0x01,0x1a},
// {0x01,0x1a},
// {0x01,0x1a},
// {0x02,0x15},
// {0x02,0x15},
{0x02,0x15},
{0x13,0x08},
{0x8a,0x96},
{0x8b,0x06},
{0x87,0x18},
{0x34,0x48}, // lens
{0x35,0x40},
{0x36,0x40},
{0x71,0x44},
{0x72,0x48},
{0x74,0xa2},
{0x75,0xa9},
{0x78,0x12},
{0x79,0xa0},
{0x7a,0x94},
{0x7c,0x97},
{0x40,0x30},
{0x41,0x30},
{0x42,0x28},
{0x43,0x1f},
{0x44,0x1c},
{0x45,0x16},
{0x46,0x13},
{0x47,0x10},
{0x48,0x0D},
{0x49,0x0C},
{0x4B,0x0A},
{0x4C,0x0B},
{0x4E,0x09},
{0x4F,0x08},
{0x50,0x08},
{0x5f,0x29},
{0x23,0x33},
{0xa1,0x10}, // AWB
{0xa2,0x0d},
{0xa3,0x30},
{0xa4,0x06},
{0xa5,0x22},
{0xa6,0x56},
{0xa7,0x18},
{0xa8,0x1a},
{0xa9,0x12},
{0xaa,0x12},
{0xab,0x16},
{0xac,0xb1},
{0xba,0x12},
{0xbb,0x12},
{0xad,0x12},
{0xae,0x56},
{0xaf,0x0a},
{0x3b,0x30},
{0x3c,0x12},
{0x3d,0x22},
{0x3e,0x3f},
{0x3f,0x28},
{0xb8,0xc3},
{0xb9,0xa3},
{0x39,0x47}, // pure color threshold
{0x26,0x13},
{0x27,0x16},
{0x28,0x14},
{0x29,0x18},
{0xee,0x0d},
{0x13,0x05},
{0x24,0x3C},
{0x81,0x20},
{0x82,0x40},
{0x83,0x30},
{0x84,0x58},
{0x85,0x30},
{0x92,0x08},
{0x86,0x80},
{0x8a,0x96},
{0x91,0xff},
{0x94,0x62},
{0x9a,0x18}, // outdoor threshold
{0xf0,0x45}, // integral time control, important for framerate(choice: 0x46\0x45\0x44..)
{0x51,0x17}, // color normal
{0x52,0x03},
{0x53,0x5F},
{0x54,0x47},
{0x55,0x66},
{0x56,0x0F},
{0x7e,0x14},
{0x57,0x36}, // color
{0x58,0x2A},
{0x59,0xAA},
{0x5a,0xA8},
{0x5b,0x43},
{0x5c,0x10},
{0x5d,0x00},
{0x7d,0x36},
{0x5e,0x10},
{0xd6,0x88}, // contrast
{0xd5,0x20}, // bright
{0xb0,0x84}, // low light ctrl in gray section
{0xb5,0x08}, // the threshold of GLB_GAIN
{0xb1,0xc8}, // saturation
{0xb2,0xc0},
{0xb3,0xd0},
{0xb4,0xB0},
{0x32,0x10},
// {0x8a,0x00},
// {0x8b,0x10},
{0xa0,0x09},
{0x00,0x03},
{0x0b,0x02},
{REGLIST_TAIL, 0x00},
};
@@ -0,0 +1,33 @@
/*
* This file is part of the OpenMV project.
* Copyright (c) 2013/2014 Ibrahim Abdelkader <i.abdalkader@gmail.com>
* This work is licensed under the MIT license, see the file LICENSE for details.
*
* BF3005 driver.
*
*/
#ifndef __BF3005_H__
#define __BF3005_H__
#include "sensor.h"
/**
* @brief Detect sensor pid
*
* @param slv_addr SCCB address
* @param id Detection result
* @return
* 0: Can't detect this sensor
* Nonzero: This sensor has been detected
*/
int esp32_camera_bf3005_detect(int slv_addr, sensor_id_t *id);
/**
* @brief initialize sensor function pointers
*
* @param sensor pointer of sensor
* @return
* Always 0
*/
int esp32_camera_bf3005_init(sensor_t *sensor);
#endif // __BF3005_H__
@@ -0,0 +1,337 @@
/*
* This file is part of the OpenMV project.
* Copyright (c) 2013/2014 Ibrahim Abdelkader <i.abdalkader@gmail.com>
* This work is licensed under the MIT license, see the file LICENSE for details.
*
* BF3005 register definitions.
*/
#ifndef __REG_REGS_H__
#define __REG_REGS_H__
#if 0
#define GAIN 0x00 /* AGC ¨C Gain control gain setting */
#define BLUE 0x01 /* AWB ¨C Blue channel gain setting */
#define RED 0x02 /* AWB ¨C Red channel gain setting */
#define GREEN 0x03 /* AWB ¨C Green channel gain setting */
#define BAVG 0x05 /* U/B Average Level */
#define GAVG 0x06 /* Y/Gb Average Level */
#define RAVG 0x07 /* V/R Average Level */
#define AECH 0x08 /* Exposure Value ¨C AEC MSBs */
#define COM2 0x09 /* Common Control 2 */
#define COM2_SOFT_SLEEP 0x10 /* Soft sleep mode */
#define COM2_OUT_DRIVE_1x 0x00 /* Output drive capability 1x */
#define COM2_OUT_DRIVE_2x 0x01 /* Output drive capability 2x */
#define COM2_OUT_DRIVE_3x 0x02 /* Output drive capability 3x */
#define COM2_OUT_DRIVE_4x 0x03 /* Output drive capability 4x */
#define REG_PID 0x0A /* Product ID Number MSB */
#define REG_VER 0x0B /* Product ID Number LSB */
#define COM3 0x0C /* Common Control 3 */
#define COM3_VFLIP 0x80 /* Vertical flip image ON/OFF selection */
#define COM3_MIRROR 0x40 /* Horizontal mirror image ON/OFF selection */
#define COM3_SWAP_BR 0x20 /* Swap B/R output sequence in RGB output mode */
#define COM3_SWAP_YUV 0x10 /* Swap Y/UV output sequence in YUV output mode */
#define COM3_SWAP_MSB 0x08 /* Swap output MSB/LSB */
#define COM3_TRI_CLOCK 0x04 /* Tri-state option for output clock at power-down period */
#define COM3_TRI_DATA 0x02 /* Tri-state option for output data at power-down period */
#define COM3_COLOR_BAR 0x01 /* Sensor color bar test pattern output enable */
#define COM3_SET_CBAR(r, x) ((r&0xFE)|((x&1)<<0))
#define COM3_SET_MIRROR(r, x) ((r&0xBF)|((x&1)<<6))
#define COM3_SET_FLIP(r, x) ((r&0x7F)|((x&1)<<7))
#define COM4 0x0D /* Common Control 4 */
#define COM4_PLL_BYPASS 0x00 /* Bypass PLL */
#define COM4_PLL_4x 0x40 /* PLL frequency 4x */
#define COM4_PLL_6x 0x80 /* PLL frequency 6x */
#define COM4_PLL_8x 0xc0 /* PLL frequency 8x */
#define COM4_AEC_FULL 0x00 /* AEC evaluate full window */
#define COM4_AEC_1_2 0x10 /* AEC evaluate 1/2 window */
#define COM4_AEC_1_4 0x20 /* AEC evaluate 1/4 window */
#define COM4_AEC_2_3 0x30 /* AEC evaluate 2/3 window */
#define COM5 0x0E /* Common Control 5 */
#define COM5_AFR 0x80 /* Auto frame rate control ON/OFF selection (night mode) */
#define COM5_AFR_SPEED 0x40 /* Auto frame rate control speed selection */
#define COM5_AFR_0 0x00 /* No reduction of frame rate */
#define COM5_AFR_1_2 0x10 /* Max reduction to 1/2 frame rate */
#define COM5_AFR_1_4 0x20 /* Max reduction to 1/4 frame rate */
#define COM5_AFR_1_8 0x30 /* Max reduction to 1/8 frame rate */
#define COM5_AFR_4x 0x04 /* Add frame when AGC reaches 4x gain */
#define COM5_AFR_8x 0x08 /* Add frame when AGC reaches 8x gain */
#define COM5_AFR_16x 0x0c /* Add frame when AGC reaches 16x gain */
#define COM5_AEC_NO_LIMIT 0x01 /* No limit to AEC increase step */
#define COM6 0x0F /* Common Control 6 */
#define COM6_AUTO_WINDOW 0x01 /* Auto window setting ON/OFF selection when format changes */
#define AEC 0x10 /* AEC[7:0] (see register AECH for AEC[15:8]) */
#define CLKRC 0x11 /* Internal Clock */
#define COM7 0x12 /* Common Control 7 */
#define COM7_RESET 0x80 /* SCCB Register Reset */
#define COM7_RES_VGA 0x00 /* Resolution VGA */
#define COM7_RES_QVGA 0x40 /* Resolution QVGA */
#define COM7_BT656 0x20 /* BT.656 protocol ON/OFF */
#define COM7_SENSOR_RAW 0x10 /* Sensor RAW */
#define COM7_FMT_GBR422 0x00 /* RGB output format GBR422 */
#define COM7_FMT_RGB565 0x04 /* RGB output format RGB565 */
#define COM7_FMT_RGB555 0x08 /* RGB output format RGB555 */
#define COM7_FMT_RGB444 0x0C /* RGB output format RGB444 */
#define COM7_FMT_YUV 0x00 /* Output format YUV */
#define COM7_FMT_P_BAYER 0x01 /* Output format Processed Bayer RAW */
#define COM7_FMT_RGB 0x02 /* Output format RGB */
#define COM7_FMT_R_BAYER 0x03 /* Output format Bayer RAW */
#define COM7_SET_FMT(r, x) ((r&0xFC)|((x&0x3)<<0))
#define COM7_SET_RGB(r, x) ((r&0xF0)|(x&0x0C)|COM7_FMT_RGB)
#define COM8 0x13 /* Common Control 8 */
#define COM8_FAST_AUTO 0x80 /* Enable fast AGC/AEC algorithm */
#define COM8_STEP_VSYNC 0x00 /* AEC - Step size limited to vertical blank */
#define COM8_STEP_UNLIMIT 0x40 /* AEC - Step size unlimited step size */
#define COM8_BANDF_EN 0x20 /* Banding filter ON/OFF */
#define COM8_AEC_BANDF 0x10 /* Enable AEC below banding value */
#define COM8_AEC_FINE_EN 0x08 /* Fine AEC ON/OFF control */
#define COM8_AGC_EN 0x04 /* AGC Enable */
#define COM8_AWB_EN 0x02 /* AWB Enable */
#define COM8_AEC_EN 0x01 /* AEC Enable */
#define COM8_SET_AGC(r, x) ((r&0xFB)|((x&0x1)<<2))
#define COM8_SET_AWB(r, x) ((r&0xFD)|((x&0x1)<<1))
#define COM8_SET_AEC(r, x) ((r&0xFE)|((x&0x1)<<0))
#define COM9 0x14 /* Common Control 9 */
#define COM9_HISTO_AVG 0x80 /* Histogram or average based AEC/AGC selection */
#define COM9_AGC_GAIN_2x 0x00 /* Automatic Gain Ceiling 2x */
#define COM9_AGC_GAIN_4x 0x10 /* Automatic Gain Ceiling 4x */
#define COM9_AGC_GAIN_8x 0x20 /* Automatic Gain Ceiling 8x */
#define COM9_AGC_GAIN_16x 0x30 /* Automatic Gain Ceiling 16x */
#define COM9_AGC_GAIN_32x 0x40 /* Automatic Gain Ceiling 32x */
#define COM9_DROP_VSYNC 0x04 /* Drop VSYNC output of corrupt frame */
#define COM9_DROP_HREF 0x02 /* Drop HREF output of corrupt frame */
#define COM9_SET_AGC(r, x) ((r&0x8F)|((x&0x07)<<4))
#define COM10 0x15 /* Common Control 10 */
#define COM10_NEGATIVE 0x80 /* Output negative data */
#define COM10_HSYNC_EN 0x40 /* HREF changes to HSYNC */
#define COM10_PCLK_FREE 0x00 /* PCLK output option: free running PCLK */
#define COM10_PCLK_MASK 0x20 /* PCLK output option: masked during horizontal blank */
#define COM10_PCLK_REV 0x10 /* PCLK reverse */
#define COM10_HREF_REV 0x08 /* HREF reverse */
#define COM10_VSYNC_FALLING 0x00 /* VSYNC changes on falling edge of PCLK */
#define COM10_VSYNC_RISING 0x04 /* VSYNC changes on rising edge of PCLK */
#define COM10_VSYNC_NEG 0x02 /* VSYNC negative */
#define COM10_OUT_RANGE_8 0x01 /* Output data range: Full range */
#define COM10_OUT_RANGE_10 0x00 /* Output data range: Data from [10] to [F0] (8 MSBs) */
#define REG16 0x16 /* Register 16 */
#define REG16_BIT_SHIFT 0x80 /* Bit shift test pattern options */
#define HSTART 0x17 /* Horizontal Frame (HREF column) Start 8 MSBs (2 LSBs are at HREF[5:4]) */
#define HSIZE 0x18 /* Horizontal Sensor Size (2 LSBs are at HREF[1:0]) */
#define VSTART 0x19 /* Vertical Frame (row) Start 8 MSBs (1 LSB is at HREF[6]) */
#define VSIZE 0x1A /* Vertical Sensor Size (1 LSB is at HREF[2]) */
#define PSHFT 0x1B /* Data Format - Pixel Delay Select */
#define REG_MIDH 0x1C /* Manufacturer ID Byte ¨C High */
#define REG_MIDL 0x1D /* Manufacturer ID Byte ¨C Low */
#define LAEC 0x1F /* Fine AEC Value - defines exposure value less than one row period */
#define COM11 0x20 /* Common Control 11 */
#define COM11_SNGL_FRAME_EN 0x02 /* Single frame ON/OFF selection */
#define COM11_SNGL_XFR_TRIG 0x01 /* Single frame transfer trigger */
#define BDBASE 0x22 /* Banding Filter Minimum AEC Value */
#define DBSTEP 0x23 /* Banding Filter Maximum Step */
#define AEW 0x24 /* AGC/AEC - Stable Operating Region (Upper Limit) */
#define AEB 0x25 /* AGC/AEC - Stable Operating Region (Lower Limit) */
#define VPT 0x26 /* AGC/AEC Fast Mode Operating Region */
#define REG28 0x28 /* Selection on the number of dummy rows, N */
#define HOUTSIZE 0x29 /* Horizontal Data Output Size MSBs (2 LSBs at register EXHCH[1:0]) */
#define EXHCH 0x2A /* Dummy Pixel Insert MSB */
#define EXHCL 0x2B /* Dummy Pixel Insert LSB */
#define VOUTSIZE 0x2C /* Vertical Data Output Size MSBs (LSB at register EXHCH[2]) */
#define ADVFL 0x2D /* LSB of Insert Dummy Rows in Vertical Sync (1 bit equals 1 row) */
#define ADVFH 0x2E /* MSB of Insert Dummy Rows in Vertical Sync */
#define YAVE 0x2F /* Y/G Channel Average Value */
#define LUMHTH 0x30 /* Histogram AEC/AGC Luminance High Level Threshold */
#define LUMLTH 0x31 /* Histogram AEC/AGC Luminance Low Level Threshold */
#define HREF 0x32 /* Image Start and Size Control */
#define DM_LNL 0x33 /* Dummy Row Low 8 Bits */
#define DM_LNH 0x34 /* Dummy Row High 8 Bits */
#define ADOFF_B 0x35 /* AD Offset Compensation Value for B Channel */
#define ADOFF_R 0x36 /* AD Offset Compensation Value for R Channel */
#define ADOFF_GB 0x37 /* AD Offset Compensation Value for GB Channel */
#define ADOFF_GR 0x38 /* AD Offset Compensation Value for GR Channel */
#define OFF_B 0x39 /* AD Offset Compensation Value for B Channel */
#define OFF_R 0x3A /* AD Offset Compensation Value for R Channel */
#define OFF_GB 0x3B /* AD Offset Compensation Value for GB Channel */
#define OFF_GR 0x3C /* AD Offset Compensation Value for GR Channel */
#define COM12 0x3D /* DC offset compensation for analog process */
#define COM13 0x3E /* Common Control 13 */
#define COM13_BLC_EN 0x80 /* BLC enable */
#define COM13_ADC_EN 0x40 /* ADC channel BLC ON/OFF control */
#define COM13_ANALOG_BLC 0x20 /* Analog processing channel BLC ON/OFF control */
#define COM13_ABLC_GAIN_EN 0x04 /* ABLC gain trigger enable */
#define COM14 0x3F /* Common Control 14 */
#define COM15 0x40 /* Common Control 15 */
#define COM16 0x41 /* Common Control 16 */
#define TGT_B 0x42 /* BLC Blue Channel Target Value */
#define TGT_R 0x43 /* BLC Red Channel Target Value */
#define TGT_GB 0x44 /* BLC Gb Channel Target Value */
#define TGT_GR 0x45 /* BLC Gr Channel Target Value */
#define LC_CTR 0x46 /* Lens Correction Control */
#define LC_CTR_RGB_COMP_1 0x00 /* R, G, and B channel compensation coefficient is set by LC_COEF (0x49) */
#define LC_CTR_RGB_COMP_3 0x04 /* R, G, and B channel compensation coefficient is set by registers
LC_COEFB (0x4B), LC_COEF (0x49), and LC_COEFR (0x4C), respectively */
#define LC_CTR_EN 0x01 /* Lens correction enable */
#define LC_XC 0x47 /* X Coordinate of Lens Correction Center Relative to Array Center */
#define LC_YC 0x48 /* Y Coordinate of Lens Correction Center Relative to Array Center */
#define LC_COEF 0x49 /* Lens Correction Coefficient */
#define LC_RADI 0x4A /* Lens Correction Radius */
#define LC_COEFB 0x4B /* Lens Correction B Channel Compensation Coefficient */
#define LC_COEFR 0x4C /* Lens Correction R Channel Compensation Coefficient */
#define FIXGAIN 0x4D /* Analog Fix Gain Amplifier */
#define AREF0 0x4E /* Sensor Reference Control */
#define AREF1 0x4F /* Sensor Reference Current Control */
#define AREF2 0x50 /* Analog Reference Control */
#define AREF3 0x51 /* ADC Reference Control */
#define AREF4 0x52 /* ADC Reference Control */
#define AREF5 0x53 /* ADC Reference Control */
#define AREF6 0x54 /* Analog Reference Control */
#define AREF7 0x55 /* Analog Reference Control */
#define UFIX 0x60 /* U Channel Fixed Value Output */
#define VFIX 0x61 /* V Channel Fixed Value Output */
#define AWBB_BLK 0x62 /* AWB Option for Advanced AWB */
#define AWB_CTRL0 0x63 /* AWB Control Byte 0 */
#define AWB_CTRL0_GAIN_EN 0x80 /* AWB gain enable */
#define AWB_CTRL0_CALC_EN 0x40 /* AWB calculate enable */
#define AWB_CTRL0_WBC_MASK 0x0F /* WBC threshold 2 */
#define DSP_CTRL1 0x64 /* DSP Control Byte 1 */
#define DSP_CTRL1_FIFO_EN 0x80 /* FIFO enable/disable selection */
#define DSP_CTRL1_UV_EN 0x40 /* UV adjust function ON/OFF selection */
#define DSP_CTRL1_SDE_EN 0x20 /* SDE enable */
#define DSP_CTRL1_MTRX_EN 0x10 /* Color matrix ON/OFF selection */
#define DSP_CTRL1_INTRP_EN 0x08 /* Interpolation ON/OFF selection */
#define DSP_CTRL1_GAMMA_EN 0x04 /* Gamma function ON/OFF selection */
#define DSP_CTRL1_BLACK_EN 0x02 /* Black defect auto correction ON/OFF */
#define DSP_CTRL1_WHITE_EN 0x01 /* White defect auto correction ON/OFF */
#define DSP_CTRL2 0x65 /* DSP Control Byte 2 */
#define DSP_CTRL2_VDCW_EN 0x08 /* Vertical DCW enable */
#define DSP_CTRL2_HDCW_EN 0x04 /* Horizontal DCW enable */
#define DSP_CTRL2_VZOOM_EN 0x02 /* Vertical zoom out enable */
#define DSP_CTRL2_HZOOM_EN 0x01 /* Horizontal zoom out enable */
#define DSP_CTRL3 0x66 /* DSP Control Byte 3 */
#define DSP_CTRL3_UV_EN 0x80 /* UV output sequence option */
#define DSP_CTRL3_CBAR_EN 0x20 /* DSP color bar ON/OFF selection */
#define DSP_CTRL3_FIFO_EN 0x08 /* FIFO power down ON/OFF selection */
#define DSP_CTRL3_SCAL1_PWDN 0x04 /* Scaling module power down control 1 */
#define DSP_CTRL3_SCAL2_PWDN 0x02 /* Scaling module power down control 2 */
#define DSP_CTRL3_INTRP_PWDN 0x01 /* Interpolation module power down control */
#define DSP_CTRL3_SET_CBAR(r, x) ((r&0xDF)|((x&1)<<5))
#define DSP_CTRL4 0x67 /* DSP Control Byte 4 */
#define DSP_CTRL4_YUV_RGB 0x00 /* Output selection YUV or RGB */
#define DSP_CTRL4_RAW8 0x02 /* Output selection RAW8 */
#define DSP_CTRL4_RAW10 0x03 /* Output selection RAW10 */
#define AWB_BIAS 0x68 /* AWB BLC Level Clip */
#define AWB_CTRL1 0x69 /* AWB Control 1 */
#define AWB_CTRL2 0x6A /* AWB Control 2 */
#define AWB_CTRL3 0x6B /* AWB Control 3 */
#define AWB_CTRL3_ADVANCED 0x80 /* AWB mode select - Advanced AWB */
#define AWB_CTRL3_SIMPLE 0x00 /* AWB mode select - Simple AWB */
#define AWB_CTRL4 0x6C /* AWB Control 4 */
#define AWB_CTRL5 0x6D /* AWB Control 5 */
#define AWB_CTRL6 0x6E /* AWB Control 6 */
#define AWB_CTRL7 0x6F /* AWB Control 7 */
#define AWB_CTRL8 0x70 /* AWB Control 8 */
#define AWB_CTRL9 0x71 /* AWB Control 9 */
#define AWB_CTRL10 0x72 /* AWB Control 10 */
#define AWB_CTRL11 0x73 /* AWB Control 11 */
#define AWB_CTRL12 0x74 /* AWB Control 12 */
#define AWB_CTRL13 0x75 /* AWB Control 13 */
#define AWB_CTRL14 0x76 /* AWB Control 14 */
#define AWB_CTRL15 0x77 /* AWB Control 15 */
#define AWB_CTRL16 0x78 /* AWB Control 16 */
#define AWB_CTRL17 0x79 /* AWB Control 17 */
#define AWB_CTRL18 0x7A /* AWB Control 18 */
#define AWB_CTRL19 0x7B /* AWB Control 19 */
#define AWB_CTRL20 0x7C /* AWB Control 20 */
#define AWB_CTRL21 0x7D /* AWB Control 21 */
#define GAM1 0x7E /* Gamma Curve 1st Segment Input End Point 0x04 Output Value */
#define GAM2 0x7F /* Gamma Curve 2nd Segment Input End Point 0x08 Output Value */
#define GAM3 0x80 /* Gamma Curve 3rd Segment Input End Point 0x10 Output Value */
#define GAM4 0x81 /* Gamma Curve 4th Segment Input End Point 0x20 Output Value */
#define GAM5 0x82 /* Gamma Curve 5th Segment Input End Point 0x28 Output Value */
#define GAM6 0x83 /* Gamma Curve 6th Segment Input End Point 0x30 Output Value */
#define GAM7 0x84 /* Gamma Curve 7th Segment Input End Point 0x38 Output Value */
#define GAM8 0x85 /* Gamma Curve 8th Segment Input End Point 0x40 Output Value */
#define GAM9 0x86 /* Gamma Curve 9th Segment Input End Point 0x48 Output Value */
#define GAM10 0x87 /* Gamma Curve 10th Segment Input End Point 0x50 Output Value */
#define GAM11 0x88 /* Gamma Curve 11th Segment Input End Point 0x60 Output Value */
#define GAM12 0x89 /* Gamma Curve 12th Segment Input End Point 0x70 Output Value */
#define GAM13 0x8A /* Gamma Curve 13th Segment Input End Point 0x90 Output Value */
#define GAM14 0x8B /* Gamma Curve 14th Segment Input End Point 0xB0 Output Value */
#define GAM15 0x8C /* Gamma Curve 15th Segment Input End Point 0xD0 Output Value */
#define SLOP 0x8D /* Gamma Curve Highest Segment Slope */
#define DNSTH 0x8E /* De-noise Threshold */
#define EDGE0 0x8F /* Edge Enhancement Strength Control */
#define EDGE1 0x90 /* Edge Enhancement Threshold Control */
#define DNSOFF 0x91 /* Auto De-noise Threshold Control */
#define EDGE2 0x92 /* Edge Enhancement Strength Upper Limit */
#define EDGE3 0x93 /* Edge Enhancement Strength Upper Limit */
#define MTX1 0x94 /* Matrix Coefficient 1 */
#define MTX2 0x95 /* Matrix Coefficient 2 */
#define MTX3 0x96 /* Matrix Coefficient 3 */
#define MTX4 0x97 /* Matrix Coefficient 4 */
#define MTX5 0x98 /* Matrix Coefficient 5 */
#define MTX6 0x99 /* Matrix Coefficient 6 */
#define MTX_CTRL 0x9A /* Matrix Control */
#define MTX_CTRL_DBL_EN 0x80 /* Matrix double ON/OFF selection */
#define BRIGHTNESS 0x9B /* Brightness Control */
#define CONTRAST 0x9C /* Contrast Gain */
#define UVADJ0 0x9E /* Auto UV Adjust Control 0 */
#define UVADJ1 0x9F /* Auto UV Adjust Control 1 */
#define SCAL0 0xA0 /* DCW Ratio Control */
#define SCAL1 0xA1 /* Horizontal Zoom Out Control */
#define SCAL2 0xA2 /* Vertical Zoom Out Control */
#define FIFODLYM 0xA3 /* FIFO Manual Mode Delay Control */
#define FIFODLYA 0xA4 /* FIFO Auto Mode Delay Control */
#define SDE 0xA6 /* Special Digital Effect Control */
#define SDE_NEGATIVE_EN 0x40 /* Negative image enable */
#define SDE_GRAYSCALE_EN 0x20 /* Gray scale image enable */
#define SDE_V_FIXED_EN 0x10 /* V fixed value enable */
#define SDE_U_FIXED_EN 0x08 /* U fixed value enable */
#define SDE_CONT_BRIGHT_EN 0x04 /* Contrast/Brightness enable */
#define SDE_SATURATION_EN 0x02 /* Saturation enable */
#define SDE_HUE_EN 0x01 /* Hue enable */
#define USAT 0xA7 /* U Component Saturation Gain */
#define VSAT 0xA8 /* V Component Saturation Gain */
#define HUECOS 0xA9 /* Cosine value ¡Á 0x80 */
#define HUESIN 0xAA /* Sine value ¡Á 0x80 */
#define SIGN_BIT 0xAB /* Sign Bit for Hue and Brightness */
#define DSPAUTO 0xAC /* DSP Auto Function ON/OFF Control */
#define DSPAUTO_AWB_EN 0x80 /* AWB auto threshold control */
#define DSPAUTO_DENOISE_EN 0x40 /* De-noise auto threshold control */
#define DSPAUTO_EDGE_EN 0x20 /* Sharpness (edge enhancement) auto strength control */
#define DSPAUTO_UV_EN 0x10 /* UV adjust auto slope control */
#define DSPAUTO_SCAL0_EN 0x08 /* Auto scaling factor control (register SCAL0 (0xA0)) */
#define DSPAUTO_SCAL1_EN 0x04 /* Auto scaling factor control (registers SCAL1 (0xA1 and SCAL2 (0xA2))*/
#define SET_REG(reg, x) (##reg_DEFAULT|x)
#endif //__REG_REGS_H__
#endif
@@ -0,0 +1,31 @@
#pragma once
#include "sensor.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Detect sensor pid
*
* @param slv_addr SCCB address
* @param id Detection result
* @return
* 0: Can't detect this sensor
* Nonzero: This sensor has been detected
*/
int esp32_camera_gc0308_detect(int slv_addr, sensor_id_t *id);
/**
* @brief initialize sensor function pointers
*
* @param sensor pointer of sensor
* @return
* Always 0
*/
int esp32_camera_gc0308_init(sensor_t *sensor);
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,137 @@
/*
* GC0308 register definitions.
* Based on GC0308 datasheet specifications
*/
#ifndef __GC0308_REG_REGS_H__
#define __GC0308_REG_REGS_H__
// System Control Registers
#define RESET_RELATED 0xfe // Bit[7]: Software reset
// Bit[6:5]: NA
// Bit[4]: CISCTL_restart_n
// Bit[3:1]: NA
// Bit[0]: page select
// 0:page0
// 1:page1
// Page 0 Registers
// Exposure Control (Manual, when AEC disabled)
#define EXPOSURE_HIGH 0x03 // Exposure time high byte (bits 11:4)
#define EXPOSURE_LOW 0x04 // Exposure time low byte (bits 3:0 in upper nibble)
// Total exposure = (EXPOSURE_HIGH << 4) | (EXPOSURE_LOW >> 4)
// Range: 0-4095 (12-bit value)
// Windowing Registers
#define ROW_START_H 0x05 // Row start address high byte
#define ROW_START_L 0x06 // Row start address low byte
#define COL_START_H 0x07 // Column start address high byte
#define COL_START_L 0x08 // Column start address low byte
#define WIN_HEIGHT_H 0x09 // Window height high byte
#define WIN_HEIGHT_L 0x0a // Window height low byte
#define WIN_WIDTH_H 0x0b // Window width high byte
#define WIN_WIDTH_L 0x0c // Window width low byte
// Sensor Control
#define CISCTL_MODE1 0x14 // Bit[1]: V_flip, Bit[0]: H_mirror
// AAAA_EN - Auto Algorithm Enable (AEC/AGC/AWB/ABB)
#define AAAA_EN 0x22 // Auto control enable register
// Per Linux kernel driver:
// Bit[0]: AEC enable (1=on, 0=off)
// Bit[1]: AWB enable (1=on, 0=off)
// Bit[2]: AGC enable (1=on, 0=off)
// Note: This was previously incorrectly named AEC_MODE1
// Special Effects and Output Control
#define SPECIAL_EFFECT 0x23 // Special effect control
// Bit[1:0]: Effect mode
// 00: Normal
// 01: Negative
// 10: Grayscale/Color tint mode
// Output Format
#define OUTPUT_FMT 0x24 // Output format control
// Bit[3:0]: Output format selection
// 0010: YCbYCr (YUV422)
// 0110: RGB565
// others: see datasheet
// Analog Circuit Control
#define PCLK_DIV 0x28 // PCLK divider control (frequency division)
// Output Control
#define OUT_CTRL 0x2e // Output control
// Bit[0]: Color bar enable
// Global Gain
#define GLOBAL_GAIN 0x50 // Global gain control (hardware: 0-63, 6-bit)
// API uses 0-30 range, mapped to hardware values
// Manual White Balance Gain (when AWB disabled)
// Per Linux kernel driver: registers 0x05a-0x05c control AWB RGB gains
#define AWB_R_GAIN 0x5a // Red channel gain for manual WB
#define AWB_G_GAIN 0x5b // Green channel gain for manual WB
#define AWB_B_GAIN 0x5c // Blue channel gain for manual WB
// Edge Enhancement (INTPEE - Interpolation and Edge-Enhancement)
// Per Linux kernel driver: registers 0x77-0x79 control edge enhancement
#define EDGE12_EFFECT 0x77 // Edge enhancement effect control
#define EDGE_POS_RATIO 0x78 // Edge position ratio
#define EDGE1_MINMAX 0x79 // Edge1 min/max control
// Saturation Control (Page 0)
// According to datasheet, saturation is controlled via 0xb1 and 0xb2
#define SATURATION_Cb 0xb1 // Cb saturation gain (default: 0x40)
#define SATURATION_Cr 0xb2 // Cr saturation gain (default: 0x40)
// Contrast
#define CONTRAST 0xb3 // Contrast control (default: 0x40)
// AEC Registers - Automatic Exposure Control (Page 0)
// Per Linux kernel driver: AEC control registers are at 0xd0-0xd3
#define AEC_MODE1 0xd0 // AEC mode control register 1
#define AEC_MODE2 0xd1 // AEC mode control register 2
#define AEC_MODE3 0xd2 // AEC mode control register 3
#define AEC_TARGET_Y 0xd3 // AEC target Y value / Expected luminance (default: 0x48)
// This controls both brightness and AE level
// Subsample Window (Page 0 for coordinate reference)
#define SUB_COL_N 0xf7 // Subsample column start (units of 4 pixels)
#define SUB_ROW_N 0xf8 // Subsample row start (units of 4 pixels)
#define SUB_COL_N1 0xf9 // Subsample column end (units of 4 pixels)
#define SUB_ROW_N1 0xfa // Subsample row end (units of 4 pixels)
// Page 1 Registers (access after setting 0xfe = 0x01)
// Subsample Control (Page 1)
#define SUBSAMPLE_EN 0x53 // Bit[7]: Subsample enable
#define SUBSAMPLE_MODE 0x54 // Subsample mode control
#define SUBSAMPLE_EN2 0x55 // Bit[0]: Subsample enable 2
#define SUBSAMPLE_Y0 0x56 // Y subsample config 0
#define SUBSAMPLE_Y1 0x57 // Y subsample config 1
#define SUBSAMPLE_UV0 0x58 // UV subsample config 0
#define SUBSAMPLE_UV1 0x59 // UV subsample config 1
/**
* @brief Register bit masks
*/
// AAAA_EN (0x22) bit masks - Auto Algorithm Enable
#define AEC_ENABLE 0x01 // Bit 0: AEC enable
#define AWB_ENABLE 0x02 // Bit 1: AWB enable
#define AGC_ENABLE 0x04 // Bit 2: AGC enable
// CISCTL_MODE1 (0x14) bit masks
#define HMIRROR_MASK 0x01 // Bit 0: Horizontal mirror
#define VFLIP_MASK 0x02 // Bit 1: Vertical flip
// SPECIAL_EFFECT (0x23) bit masks
#define EFFECT_NORMAL 0x00 // Normal mode
#define EFFECT_NEGATIVE 0x01 // Negative/inverse
#define EFFECT_GRAYSCALE 0x02 // B&W or color tint mode
// OUT_CTRL (0x2e) bit masks
#define COLORBAR_ENABLE 0x01 // Bit 0: Color bar test pattern
#endif // __GC0308_REG_REGS_H__
@@ -0,0 +1,262 @@
#ifndef _GC0308_SETTINGS_H_
#define _GC0308_SETTINGS_H_
#include <stdint.h>
#define REG_DLY 0xff
static const uint8_t gc0308_sensor_default_regs[][2] = {
{0xfe, 0x00},
{0xec, 0x20},
{0x05, 0x00},
{0x06, 0x00},
{0x07, 0x00},
{0x08, 0x00},
{0x09, 0x01},
{0x0a, 0xe8},
{0x0b, 0x02},
{0x0c, 0x88},
{0x0d, 0x02},
{0x0e, 0x02},
{0x10, 0x26},
{0x11, 0x0d},
{0x12, 0x2a},
{0x13, 0x00},
{0x14, 0x10}, // Linux kernel: CISCTL_MODE1 = 0x10 (no mirror/flip by default)
{0x15, 0x0a},
{0x16, 0x05},
{0x17, 0x01},
{0x18, 0x44},
{0x19, 0x44},
{0x1a, 0x2a},
{0x1b, 0x00},
{0x1c, 0x49},
{0x1d, 0x9a},
{0x1e, 0x61},
{0x1f, 0x00}, //pad drv <=24MHz, use 0x00 is ok
{0x20, 0x7f},
{0x21, 0xfa},
{0x22, 0x57},
{0x24, 0xa2}, //YCbYCr
{0x25, 0x0f},
{0x26, 0x03}, // 0x01
{0x28, 0x00},
{0x2d, 0x0a},
{0x2f, 0x01},
{0x30, 0xf7},
{0x31, 0x50},
{0x32, 0x00},
{0x33, 0x28},
{0x34, 0x2a},
{0x35, 0x28},
{0x39, 0x04},
{0x3a, 0x20},
{0x3b, 0x20},
{0x3c, 0x00},
{0x3d, 0x00},
{0x3e, 0x00},
{0x3f, 0x00},
{0x50, 0x14}, // GLOBAL_GAIN = 0x14 (default gain)
{0x52, 0x41},
{0x53, 0x80},
{0x54, 0x80},
{0x55, 0x80},
{0x56, 0x80},
// AWB Gain initialization (from Linux kernel driver)
{0x5a, 0x56}, // AWB_R_GAIN: Red gain for auto white balance
{0x5b, 0x40}, // AWB_G_GAIN: Green gain for auto white balance
{0x5c, 0x4a}, // AWB_B_GAIN: Blue gain for auto white balance
{0x8b, 0x20},
{0x8c, 0x20},
{0x8d, 0x20},
{0x8e, 0x14},
{0x8f, 0x10},
{0x90, 0x14},
{0x91, 0x3c},
{0x92, 0x50},
//{0x8b,0x10},
//{0x8c,0x10},
//{0x8d,0x10},
//{0x8e,0x10},
//{0x8f,0x10},
//{0x90,0x10},
//{0x91,0x3c},
//{0x92,0x50},
{0x5d, 0x12},
{0x5e, 0x1a},
{0x5f, 0x24},
{0x60, 0x07},
{0x61, 0x15},
{0x62, 0x08}, // 0x08
{0x64, 0x03}, // 0x03
{0x66, 0xe8},
{0x67, 0x86},
{0x68, 0x82},
{0x69, 0x18},
{0x6a, 0x0f},
{0x6b, 0x00},
{0x6c, 0x5f},
{0x6d, 0x8f},
{0x6e, 0x55},
{0x6f, 0x38},
{0x70, 0x15},
{0x71, 0x33},
{0x72, 0xdc},
{0x73, 0x00},
{0x74, 0x02},
{0x75, 0x3f},
{0x76, 0x02},
{0x77, 0x38}, // 0x47
{0x78, 0x88},
{0x79, 0x81},
{0x7a, 0x81},
{0x7b, 0x22},
{0x7c, 0xff},
{0x93, 0x48}, //color matrix default
{0x94, 0x02},
{0x95, 0x07},
{0x96, 0xe0},
{0x97, 0x40},
{0x98, 0xf0},
{0xb1, 0x40},
{0xb2, 0x40},
{0xb3, 0x40}, //0x40
{0xb6, 0xe0},
{0xbd, 0x38},
{0xbe, 0x36},
{0xd0, 0xCB},
{0xd1, 0x10},
{0xd2, 0x90},
{0xd3, 0x48},
{0xd5, 0xF2},
{0xd6, 0x16},
{0xdb, 0x92},
{0xdc, 0xA5},
{0xdf, 0x23},
{0xd9, 0x00},
{0xda, 0x00},
{0xe0, 0x09},
{0xed, 0x04},
{0xee, 0xa0},
{0xef, 0x40},
{0x80, 0x03},
{0x9F, 0x10},
{0xA0, 0x20},
{0xA1, 0x38},
{0xA2, 0x4e},
{0xA3, 0x63},
{0xA4, 0x76},
{0xA5, 0x87},
{0xA6, 0xa2},
{0xA7, 0xb8},
{0xA8, 0xca},
{0xA9, 0xd8},
{0xAA, 0xe3},
{0xAB, 0xeb},
{0xAC, 0xf0},
{0xAD, 0xF8},
{0xAE, 0xFd},
{0xAF, 0xFF},
{0xc0, 0x00},
{0xc1, 0x10},
{0xc2, 0x1c},
{0xc3, 0x30},
{0xc4, 0x43},
{0xc5, 0x54},
{0xc6, 0x65},
{0xc7, 0x75},
{0xc8, 0x93},
{0xc9, 0xB0},
{0xca, 0xCB},
{0xcb, 0xE6},
{0xcc, 0xFF},
{0xf0, 0x02},
{0xf1, 0x01},
{0xf2, 0x02},
{0xf3, 0x30},
{0xf7, 0x04},
{0xf8, 0x02},
{0xf9, 0x9f},
{0xfa, 0x78},
{0xfe, 0x01},
{0x00, 0xf5},
{0x02, 0x20},
{0x04, 0x10},
{0x05, 0x08},
{0x06, 0x20},
{0x08, 0x0a},
{0x0a, 0xa0},
{0x0b, 0x60},
{0x0c, 0x08},
{0x0e, 0x44},
{0x0f, 0x32},
{0x10, 0x41},
{0x11, 0x37},
{0x12, 0x22},
{0x13, 0x19},
{0x14, 0x44},
{0x15, 0x44},
{0x16, 0xc2},
{0x17, 0xA8},
{0x18, 0x18},
{0x19, 0x50},
{0x1a, 0xd8},
{0x1b, 0xf5},
{0x70, 0x40},
{0x71, 0x58},
{0x72, 0x30},
{0x73, 0x48},
{0x74, 0x20},
{0x75, 0x60},
{0x77, 0x20},
{0x78, 0x32},
{0x30, 0x03},
{0x31, 0x40},
{0x32, 0x10},
{0x33, 0xe0},
{0x34, 0xe0},
{0x35, 0x00},
{0x36, 0x80},
{0x37, 0x00},
{0x38, 0x04},
{0x39, 0x09},
{0x3a, 0x12},
{0x3b, 0x1C},
{0x3c, 0x28},
{0x3d, 0x31},
{0x3e, 0x44},
{0x3f, 0x57},
{0x40, 0x6C},
{0x41, 0x81},
{0x42, 0x94},
{0x43, 0xA7},
{0x44, 0xB8},
{0x45, 0xD6},
{0x46, 0xEE},
{0x47, 0x0d},
{0x62, 0xf7},
{0x63, 0x68},
{0x64, 0xd3},
{0x65, 0xd3},
{0x66, 0x60},
{0xfe, 0x00},
{0x01, 0x32}, //frame setting
{0x02, 0x0c},
{0x0f, 0x01},
{0xe2, 0x00},
{0xe3, 0x78},
{0xe4, 0x00},
{0xe5, 0xfe},
{0xe6, 0x01},
{0xe7, 0xe0},
{0xe8, 0x01},
{0xe9, 0xe0},
{0xea, 0x01},
{0xeb, 0xe0},
{0xfe, 0x00},
};
#endif
@@ -0,0 +1,31 @@
/*
*
* GC032A driver.
*
*/
#ifndef __GC032A_H__
#define __GC032A_H__
#include "sensor.h"
/**
* @brief Detect sensor pid
*
* @param slv_addr SCCB address
* @param id Detection result
* @return
* 0: Can't detect this sensor
* Nonzero: This sensor has been detected
*/
int esp32_camera_gc032a_detect(int slv_addr, sensor_id_t *id);
/**
* @brief initialize sensor function pointers
*
* @param sensor pointer of sensor
* @return
* Always 0
*/
int esp32_camera_gc032a_init(sensor_t *sensor);
#endif // __GC032A_H__
@@ -0,0 +1,82 @@
/*
* GC032A register definitions.
*/
#ifndef __GC032A_REG_REGS_H__
#define __GC032A_REG_REGS_H__
#define SENSOR_ID_HIGH 0XF0
#define SENSOR_ID_LOW 0XF1
#define PAD_VB_HIZ_MODE 0XF2
#define SYNC_OUTPUT 0XF3
#define I2C_CONFIG 0XF4
#define PLL_MODE1 0XF7
#define PLL_MODE2 0XF8
#define CM_MODE 0XF9
#define ISP_DIV_MODE 0XFA
#define I2C_DEVICE_ID 0XFB
#define ANALOG_PWC 0XFC
#define ISP_DIV_MODE2 0XFD
#define RESET_RELATED 0XFE // Bit[7]: Software reset
// Bit[6]: cm reset
// Bit[5]: spi reset
// Bit[4]: CISCTL_restart_n
// Bit[3]: PLL_rst
// Bit[2:0]: page select
// 000:page0
// 001:page1
// 010:page2
// 011:page3
//----page0-----------------------------
#define P0_EXPOSURE_HIGH 0X03
#define P0_EXPOSURE_LOW 0X04
#define P0_HB_HIGH 0X05
#define P0_HB_LOW 0X06
#define P0_VB_HIGH 0X07
#define P0_VB_LOW 0X08
#define P0_ROW_START_HIGH 0X09
#define P0_ROW_START_LOW 0X0A
#define P0_COLUMN_START_HIGH 0X0B
#define P0_COLUMN_START_LOW 0X0C
#define P0_WINDOW_HEIGHT_HIGH 0X0D
#define P0_WINDOW_HEIGHT_LOW 0X0E
#define P0_WINDOW_WIDTH_HIGH 0X0F
#define P0_WINDOW_WIDTH_LOW 0X10
#define P0_SH_DELAY 0X11
#define P0_VS_ST 0X12
#define P0_VS_ET 0X13
#define P0_CISCTL_MODE1 0X17
#define P0_BLOCK_ENABLE_1 0X40
#define P0_AAAA_ENABLE 0X42
#define P0_SPECIAL_EFFECT 0X43
#define P0_SYNC_MODE 0X46
#define P0_GAIN_CODE 0X48
#define P0_DEBUG_MODE2 0X4C
#define P0_WIN_MODE 0X50
#define P0_OUT_WIN_Y1_HIGH 0X51
#define P0_OUT_WIN_Y1_LOW 0X52
#define P0_OUT_WIN_X1_HIGH 0X53
#define P0_OUT_WIN_X1_LOW 0X54
#define P0_OUT_WIN_HEIGHT_HIGH 0X55
#define P0_OUT_WIN_HEIGHT_LOW 0X56
#define P0_OUT_WIN_WIDTH_HIGH 0X57
#define P0_OUT_WIN_WIDTH_LOW 0X58
#define P0_GLOBAL_SATURATION 0XD0
#define P0_SATURATION_CB 0XD1
#define P0_SATURATION_CR 0XD2
#define P0_LUMA_CONTRAST 0XD3
#define P0_CONTRAST_CENTER 0XD4
#define P0_LUMA_OFFSET 0XD5
#define P0_FIXED_CB 0XDA
#define P0_FIXED_CR 0XDB
//----page3-----------------------------
#define P3_IMAGE_WIDTH_LOW 0X5B
#define P3_IMAGE_WIDTH_HIGH 0X5C
#define P3_IMAGE_HEIGHT_LOW 0X5D
#define P3_IMAGE_HEIGHT_HIGH 0X5E
#endif //__GC032A_REG_REGS_H__
@@ -0,0 +1,401 @@
#ifndef _GC032A_SETTINGS_H_
#define _GC032A_SETTINGS_H_
#include <stdint.h>
#include <stdbool.h>
#include "esp_attr.h"
#include "gc032a_regs.h"
#define REG_DLY 0xffff
#define REGLIST_TAIL 0x0000
/*
* The default register settings, as obtained from OmniVision. There
* is really no making sense of most of these - lots of "reserved" values
* and such.
*
*/
static const uint16_t gc032a_default_regs[][2] = {
/*System*/
{0xf3, 0xff},
{0xf5, 0x06},
{0xf7, 0x01},
{0xf8, 0x03},
{0xf9, 0xce},
{0xfa, 0x00},
{0xfc, 0x02},
{0xfe, 0x02},
{0x81, 0x03},
{0xfe, 0x00},
{0x77, 0x64},
{0x78, 0x40},
{0x79, 0x60},
/*ANALOG & CISCTL*/
{0xfe, 0x00},
{0x03, 0x01},
{0x04, 0xce},
{0x05, 0x01},
{0x06, 0xad},
{0x07, 0x00},
{0x08, 0x10},
{0x0a, 0x00},
{0x0c, 0x00},
{0x0d, 0x01},
{0x0e, 0xe8}, // height 488
{0x0f, 0x02},
{0x10, 0x88}, // width 648
{0x17, 0x54},
{0x19, 0x08},
{0x1a, 0x0a},
{0x1f, 0x40},
{0x20, 0x30},
{0x2e, 0x80},
{0x2f, 0x2b},
{0x30, 0x1a},
{0xfe, 0x02},
{0x03, 0x02},
{0x05, 0xd7},
{0x06, 0x60},
{0x08, 0x80},
{0x12, 0x89},
/*blk*/
{0xfe, 0x00},
{0x18, 0x02},
{0xfe, 0x02},
{0x40, 0x22},
{0x45, 0x00},
{0x46, 0x00},
{0x49, 0x20},
{0x4b, 0x3c},
{0x50, 0x20},
{0x42, 0x10},
/*isp*/
{0xfe, 0x01},
{0x0a, 0xc5},
{0x45, 0x00},
{0xfe, 0x00},
{0x40, 0xff},
{0x41, 0x25},
{0x42, 0xcf},
{0x43, 0x10},
{0x44, 0x83},
{0x46, 0x23},
{0x49, 0x03},
{0x52, 0x02},
{0x54, 0x00},
{0xfe, 0x02},
{0x22, 0xf6},
/*Shading*/
{0xfe, 0x01},
{0xc1, 0x38},
{0xc2, 0x4c},
{0xc3, 0x00},
{0xc4, 0x32},
{0xc5, 0x24},
{0xc6, 0x16},
{0xc7, 0x08},
{0xc8, 0x08},
{0xc9, 0x00},
{0xca, 0x20},
{0xdc, 0x8a},
{0xdd, 0xa0},
{0xde, 0xa6},
{0xdf, 0x75},
/*AWB*/
{0xfe, 0x01},
{0x7c, 0x09},
{0x65, 0x06},
{0x7c, 0x08},
{0x56, 0xf4},
{0x66, 0x0f},
{0x67, 0x84},
{0x6b, 0x80},
{0x6d, 0x12},
{0x6e, 0xb0},
{0x86, 0x00},
{0x87, 0x00},
{0x88, 0x00},
{0x89, 0x00},
{0x8a, 0x00},
{0x8b, 0x00},
{0x8c, 0x00},
{0x8d, 0x00},
{0x8e, 0x00},
{0x8f, 0x00},
{0x90, 0x00},
{0x91, 0x00},
{0x92, 0xf4},
{0x93, 0xd5},
{0x94, 0x50},
{0x95, 0x0f},
{0x96, 0xf4},
{0x97, 0x2d},
{0x98, 0x0f},
{0x99, 0xa6},
{0x9a, 0x2d},
{0x9b, 0x0f},
{0x9c, 0x59},
{0x9d, 0x2d},
{0x9e, 0xaa},
{0x9f, 0x67},
{0xa0, 0x59},
{0xa1, 0x00},
{0xa2, 0x00},
{0xa3, 0x0a},
{0xa4, 0x00},
{0xa5, 0x00},
{0xa6, 0xd4},
{0xa7, 0x9f},
{0xa8, 0x55},
{0xa9, 0xd4},
{0xaa, 0x9f},
{0xab, 0xac},
{0xac, 0x9f},
{0xad, 0x55},
{0xae, 0xd4},
{0xaf, 0xac},
{0xb0, 0xd4},
{0xb1, 0xa3},
{0xb2, 0x55},
{0xb3, 0xd4},
{0xb4, 0xac},
{0xb5, 0x00},
{0xb6, 0x00},
{0xb7, 0x05},
{0xb8, 0xd6},
{0xb9, 0x8c},
/*CC*/
{0xfe, 0x01},
{0xd0, 0x40},
{0xd1, 0xf8},
{0xd2, 0x00},
{0xd3, 0xfa},
{0xd4, 0x45},
{0xd5, 0x02},
{0xd6, 0x30},
{0xd7, 0xfa},
{0xd8, 0x08},
{0xd9, 0x08},
{0xda, 0x58},
{0xdb, 0x02},
{0xfe, 0x00},
/*Gamma*/
{0xfe, 0x00},
{0xba, 0x00},
{0xbb, 0x04},
{0xbc, 0x0a},
{0xbd, 0x0e},
{0xbe, 0x22},
{0xbf, 0x30},
{0xc0, 0x3d},
{0xc1, 0x4a},
{0xc2, 0x5d},
{0xc3, 0x6b},
{0xc4, 0x7a},
{0xc5, 0x85},
{0xc6, 0x90},
{0xc7, 0xa5},
{0xc8, 0xb5},
{0xc9, 0xc2},
{0xca, 0xcc},
{0xcb, 0xd5},
{0xcc, 0xde},
{0xcd, 0xea},
{0xce, 0xf5},
{0xcf, 0xff},
/*Auto Gamma*/
{0xfe, 0x00},
{0x5a, 0x08},
{0x5b, 0x0f},
{0x5c, 0x15},
{0x5d, 0x1c},
{0x5e, 0x28},
{0x5f, 0x36},
{0x60, 0x45},
{0x61, 0x51},
{0x62, 0x6a},
{0x63, 0x7d},
{0x64, 0x8d},
{0x65, 0x98},
{0x66, 0xa2},
{0x67, 0xb5},
{0x68, 0xc3},
{0x69, 0xcd},
{0x6a, 0xd4},
{0x6b, 0xdc},
{0x6c, 0xe3},
{0x6d, 0xf0},
{0x6e, 0xf9},
{0x6f, 0xff},
/*Gain*/
{0xfe, 0x00},
{0x70, 0x50},
/*AEC*/
{0xfe, 0x00},
{0x4f, 0x01},
{0xfe, 0x01},
{0x0d, 0x00},
{0x12, 0xa0},
{0x13, 0x3a},
{0x44, 0x04},
{0x1f, 0x30},
{0x20, 0x40},
{0x26, 0x9a},
{0x3e, 0x20},
{0x3f, 0x2d},
{0x40, 0x40},
{0x41, 0x5b},
{0x42, 0x82},
{0x43, 0xb7},
{0x04, 0x0a},
{0x02, 0x79},
{0x03, 0xc0},
/*measure window*/
{0xfe, 0x01},
{0xcc, 0x08},
{0xcd, 0x08},
{0xce, 0xa4},
{0xcf, 0xec},
/*DNDD*/
{0xfe, 0x00},
{0x81, 0xb8},
{0x82, 0x12},
{0x83, 0x0a},
{0x84, 0x01},
{0x86, 0x50},
{0x87, 0x18},
{0x88, 0x10},
{0x89, 0x70},
{0x8a, 0x20},
{0x8b, 0x10},
{0x8c, 0x08},
{0x8d, 0x0a},
/*Intpee*/
{0xfe, 0x00},
{0x8f, 0xaa},
{0x90, 0x9c},
{0x91, 0x52},
{0x92, 0x03},
{0x93, 0x03},
{0x94, 0x08},
{0x95, 0x44},
{0x97, 0x00},
{0x98, 0x00},
/*ASDE*/
{0xfe, 0x00},
{0xa1, 0x30},
{0xa2, 0x41},
{0xa4, 0x30},
{0xa5, 0x20},
{0xaa, 0x30},
{0xac, 0x32},
/*YCP*/
{0xfe, 0x00},
{0xd1, 0x3c},
{0xd2, 0x3c},
{0xd3, 0x38},
{0xd6, 0xf4},
{0xd7, 0x1d},
{0xdd, 0x73},
{0xde, 0x84},
/*Banding*/
{0xfe, 0x00},
{0x05, 0x01},
{0x06, 0xad},
{0x07, 0x00},
{0x08, 0x10},
{0xfe, 0x01},
{0x25, 0x00},
{0x26, 0x9a},
{0x27, 0x01},
{0x28, 0xce},
{0x29, 0x02},
{0x2a, 0x68},
{0x2b, 0x02},
{0x2c, 0x68},
{0x2d, 0x07},
{0x2e, 0xd2},
{0x2f, 0x0b},
{0x30, 0x6e},
{0x31, 0x0e},
{0x32, 0x70},
{0x33, 0x12},
{0x34, 0x0c},
{0x3c, 0x30},
/*Analog&Cisctl*/
{0xfe, 0x00},
{0x05, 0x01},
{0x06, 0xa0},
{0x07, 0x00},
{0x08, 0x20},
{0x0a, 0x78},
{0x0c, 0xa0},
{0x0d, 0x00}, //window_height [8]
{0x0e, 0xf8}, //window_height [7:0] 248
{0x0f, 0x01}, //window_width [9:8]
{0x10, 0x48}, //window_width [7:0] 328
{0x55, 0x00},
{0x56, 0xf0}, // 240
{0x57, 0x01},
{0x58, 0x40}, // 320
/*SPI*/
{0xfe, 0x03},
{0x5b, 0x40},
{0x5c, 0x01},
{0x5d, 0xf0},
{0x5e, 0x00},
/*AEC*/
{0xfe, 0x01},
{0x25, 0x00}, //step
{0x26, 0x63},
{0x27, 0x01},
{0x28, 0x29},
{0x29, 0x01},
{0x2a, 0x29},
{0x2b, 0x01},
{0x2c, 0x29},
{0x2d, 0x01},
{0x2e, 0x29},
{0x2f, 0x01},
{0x30, 0x29},
{0x31, 0x01},
{0x32, 0x29},
{0x33, 0x01},
{0x34, 0x29},
{0x3c, 0x00},
/*measure window*/
{0xfe, 0x01},
{0xcc, 0x04},
{0xcd, 0x04},
{0xce, 0x72},
{0xcf, 0x52},
{REGLIST_TAIL, 0x00},
};
#endif
@@ -0,0 +1,27 @@
#ifndef __GC2145_H__
#define __GC2145_H__
#include "sensor.h"
/**
* @brief Detect sensor pid
*
* @param slv_addr SCCB address
* @param id Detection result
* @return
* 0: Can't detect this sensor
* Nonzero: This sensor has been detected
*/
int esp32_camera_gc2145_detect(int slv_addr, sensor_id_t *id);
/**
* @brief initialize sensor function pointers
*
* @param sensor pointer of sensor
* @return
* Always 0
*/
int esp32_camera_gc2145_init(sensor_t *sensor);
#endif // __GC2145_H__
@@ -0,0 +1,85 @@
/*
* GC2145 register definitions.
*/
#ifndef __GC2145_REG_REGS_H__
#define __GC2145_REG_REGS_H__
#define CHIP_ID_HIGH 0XF0
#define CHIP_ID_LOW 0XF1
#define PLL_MODE1 0XF7
#define PLL_MODE2 0XF8
#define CM_MODE 0XF9
#define CLK_DIV_MODE 0XFA
#define RESET_RELATED 0xfe // Bit[7]: Software reset
// Bit[6]: cm reset
// Bit[5]: mipi reset
// Bit[4]: CISCTL_restart_n
// Bit[3]: NA
// Bit[2:0]: page select
// 000:page0
// 001:page1
// 010:page2
// 011:page3
//-page0----------------
#define P0_EXPOSURE_HIGH 0X03
#define P0_EXPOSURE_LOW 0X04
#define P0_HB_HIGH 0X05
#define P0_HB_LOW 0X06
#define P0_VB_HIGH 0X07
#define P0_VB_LOW 0X08
#define P0_ROW_START_HIGH 0X09
#define P0_ROW_START_LOW 0X0A
#define P0_COL_START_HIGH 0X0B
#define P0_COL_START_LOW 0X0C
#define P0_WIN_HEIGHT_HIGH 0X0D
#define P0_WIN_HEIGHT_LOW 0X0E
#define P0_WIN_WIDTH_HIGH 0X0F
#define P0_WIN_WIDTH_LOW 0X10
#define P0_ANALOG_MODE1 0X17
#define P0_ANALOG_MODE2 0X18
#define P0_SPECIAL_EFFECT 0X83
#define P0_OUTPUT_FORMAT 0x84 // Format select
// Bit[7]:YUV420 row switch
// Bit[6]:YUV420 col switch
// Bit[7]:YUV420_legacy
// Bit[4:0]:output data mode
// 5’h00 Cb Y Cr Y
// 5’h01 Cr Y Cb Y
// 5’h02 Y Cb Y Cr
// 5’h03 Y Cr Y Cb
// 5’h04 LSC bypass, C/Y
// 5’h05 LSC bypass, Y/C
// 5’h06 RGB 565
// 5’h0f bypass 10bits
// 5’h17 switch odd/even column /row to controls output Bayer pattern
// 00 RGBG
// 01 RGGB
// 10 BGGR
// 11 GBRG
// 5'h18 DNDD out mode
// 5'h19 LSC out mode
// 5;h1b EEINTP out mode
#define P0_FRAME_START 0X85
#define P0_SYNC_MODE 0X86
#define P0_MODULE_GATING 0X88
#define P0_BYPASS_MODE 0X89
#define P0_DEBUG_MODE2 0X8C
#define P0_DEBUG_MODE3 0X8D
#define P0_CROP_ENABLE 0X90
#define P0_OUT_WIN_Y1_HIGH 0X91
#define P0_OUT_WIN_Y1_LOW 0X92
#define P0_OUT_WIN_X1_HIGH 0X93
#define P0_OUT_WIN_X1_LOW 0X94
#define P0_OUT_WIN_HEIGHT_HIGH 0X95
#define P0_OUT_WIN_HEIGHT_LOW 0X96
#define P0_OUT_WIN_WIDTH_HIGH 0X97
#define P0_OUT_WIN_WIDTH_LOW 0X98
#define P0_SUBSAMPLE 0X99
#define P0_SUBSAMPLE_MODE 0X9A
#endif // __GC2145_REG_REGS_H__
@@ -0,0 +1,719 @@
#include <stdint.h>
#define REG_DLY 0xffff
#define REGLIST_TAIL 0x0000 /* Array end token */
static const uint16_t gc2145_default_init_regs[][2] = {
{0xfe, 0xf0},
{0xfe, 0xf0},
{0xfe, 0xf0},
{0xfc, 0x06},
{0xf6, 0x00},
{0xf7, 0x1d}, //37 //17 //37 //1d//05
{0xf8, 0x83}, //87 //83 //82
{0xfa, 0x00},
{0xf9, 0xfe}, //ff
{0xfd, 0x00},
{0xc2, 0x00},
{0xf2, 0x0f},
//////////////////////////////////////////////////////
//////////////////// Analog & Cisctl ////////////////
//////////////////////////////////////////////////////
{0xfe, 0x00},
{0x03, 0x04}, //exp time
{0x04, 0x62}, //exp time
{0x05, 0x01}, //00 //hb[11:8]
{0x06, 0x3b}, //0b //hb
{0x09, 0x00}, //row start
{0x0a, 0x00}, //
{0x0b, 0x00}, //col start
{0x0c, 0x00},
{0x0d, 0x04}, //height
{0x0e, 0xc0},
{0x0f, 0x06}, //width
{0x10, 0x52},
{0x12, 0x2e}, //sh_delay 太短 YUV出图异常
{0x17, 0x14}, //CISCTL Mode1 [1:0]mirror flip
{0x18, 0x22}, //sdark mode
{0x19, 0x0f}, // AD pipe number
{0x1a, 0x01}, //AD manual switch mode
{0x1b, 0x4b}, //48 restg Width,SH width
{0x1c, 0x07}, //06 帧率快后,横条纹 //12 //TX Width,Space Width
{0x1d, 0x10}, //double reset
{0x1e, 0x88}, //90//98 //fix 竖线//Analog Mode1,TX high,Coln_r
{0x1f, 0x78}, //78 //38 //18 //Analog Mode2,txlow
{0x20, 0x03}, //07 //Analog Mode3,comv,ad_clk mode
{0x21, 0x40}, //10//20//40 //fix 灯管横条纹
{0x22, 0xa0}, //d0//f0 //a2 //Vref vpix FPN严重
{0x24, 0x1e},
{0x25, 0x01}, //col sel
{0x26, 0x10}, //Analog PGA gain1
{0x2d, 0x60}, //40//40 //txl drv mode
{0x30, 0x01}, //Analog Mode4
{0x31, 0x90}, //b0//70 // Analog Mode7 [7:5]rsgh_r灯管横条纹[4:3]isp_g
{0x33, 0x06}, //03//02//01 //EQ_hstart_width
{0x34, 0x01},
//
///////////////////////////////////////////////////
//////////////////// ISP reg //////////////////////
//////////////////////////////////////////////////////
{0x80, 0xff}, //outdoor gamma_en, GAMMA_en, CC_en, EE_en, INTP_en, DN_en, DD_en,LSC_en
{0x81, 0x24}, //26//24 //BLK dither mode, ll_y_en ,skin_en, edge SA, new_skin_mode, autogray_en,ll_gamma_en,BFF test image
{0x82, 0xfa}, //FA //auto_SA, auto_EE, auto_DN, auto_DD, auto_LSC, ABS_en, AWB_en, NA
{0x83, 0x00}, //special_effect
{0x84, 0x02}, //output format
{0x86, 0x03}, //c2 //46 //c2 //sync mode
{0x88, 0x03}, //[1]ctl_auto_gating [0]out_auto_gating
{0x89, 0x03}, //bypass disable
{0x85, 0x30}, //60//frame start cut
{0x8a, 0x00}, //ISP_quiet_mode,close aaa pclk,BLK gate mode,exception,close first pipe clock,close dndd clock,close intp clock,DIV_gatedclk_en
{0x8b, 0x00}, //[7:6]BFF_gate_mode,[5]BLK switch gain,[4]protect exp,[3:2]pipe gate mode,[1]not split sram,[0]dark current update
{0xb0, 0x55}, //60 //global gain
{0xc3, 0x00}, //[7:4]auto_exp_gamma_th1[11:8],[3:0]auto_exp_gamma_th2[11:8]
{0xc4, 0x80}, //auto_exp_gamma_th1[7:0] into
{0xc5, 0x90}, //auto_exp_gamma_th2[7:0] out //outdoor gamma
{0xc6, 0x38}, //auto_gamma_th1
{0xc7, 0x40}, //auto_gamma_th2
{0xec, 0x06}, //measure window
{0xed, 0x04},
{0xee, 0x60}, //16 col
{0xef, 0x90}, //8 row
{0xb6, 0x01}, //[0]aec en
{0x90, 0x01}, //crop
{0x91, 0x00},
{0x92, 0x00},
{0x93, 0x00},
{0x94, 0x00}, //08
{0x95, 0x04},
{0x96, 0xb0},
{0x97, 0x06},
{0x98, 0x40},
///////////////////////////////////////////////
/////////// BLK ////////////////////////
///////////////////////////////////////////////
{0x18, 0x02},
{0x40, 0x42}, //2b //27
{0x41, 0x00}, //80 //dark row sel
{0x43, 0x54}, //[7:4]BLK start not smooth [3:0]output start frame
{0x5e, 0x00}, //00//10 //18
{0x5f, 0x00}, //00//10 //18
{0x60, 0x00}, //00//10 //18
{0x61, 0x00}, //00///10 //18
{0x62, 0x00}, //00//10 //18
{0x63, 0x00}, //00//10 //18
{0x64, 0x00}, //00/10 //18
{0x65, 0x00}, //00//10 //18
{0x66, 0x20}, //1e
{0x67, 0x20}, //1e
{0x68, 0x20}, //1e
{0x69, 0x20}, //1e
{0x76, 0x00}, //0f
{0x6a, 0x00}, //06
{0x6b, 0x00}, //06
{0x6c, 0x3e}, //06
{0x6d, 0x3e}, //06
{0x6e, 0x3f}, //06
{0x6f, 0x3f}, //06
{0x70, 0x00}, //06
{0x71, 0x00}, //06 //manual offset
{0x76, 0x00}, //1f//add offset
{0x72, 0xf0}, //[7:4]BLK DD th [3:0]BLK various th
{0x7e, 0x3c}, //ndark
{0x7f, 0x00},
{0xfe, 0x02},
{0x48, 0x15},
{0x49, 0x00}, //04//04 //ASDE OFFSET SLOPE
{0x4b, 0x0b}, //ASDE y OFFSET SLOPE
{0xfe, 0x00},
///////////////////////////////////////////////
/////////// AEC ////////////////////////
///////////////////////////////////////////////
{0xfe, 0x01},
{0x01, 0x04}, //AEC X1
{0x02, 0xc0}, //AEC X2
{0x03, 0x04}, //AEC Y1
{0x04, 0x90}, //AEC Y2
{0x05, 0x30}, //20 //AEC center X1
{0x06, 0x90}, //40 //AEC center X2
{0x07, 0x20}, //30 //AEC center Y1
{0x08, 0x70}, //60 //AEC center Y2
{0x09, 0x00}, //AEC show mode
{0x0a, 0xc2}, //[7]col gain enable
{0x0b, 0x11}, //AEC every N
{0x0c, 0x10}, //AEC_mode3 center weight
{0x13, 0x40}, //2a //AEC Y target
{0x17, 0x00}, //AEC ignore mode
{0x1c, 0x11}, //
{0x1e, 0x61}, //
{0x1f, 0x30}, //40//50 //max pre gain
{0x20, 0x40}, //60//40 //max post gain
{0x22, 0x80}, //AEC outdoor THD
{0x23, 0x20}, //target_Y_low_limit
{0xfe, 0x02},
{0x0f, 0x04}, //05
{0xfe, 0x01},
{0x12, 0x35}, //35 //[5:4]group_size [3]slope_disable [2]outdoor_enable [0]histogram_enable
{0x15, 0x50}, //target_Y_high_limit
{0x10, 0x31}, //num_thd_high
{0x3e, 0x28}, //num_thd_low
{0x3f, 0xe0}, //luma_thd
{0x40, 0x20}, //luma_slope
{0x41, 0x0f}, //color_diff
{0xfe, 0x02},
{0x0f, 0x05}, //max_col_level
///////////////////////////
////// INTPEE /////////////
///////////////////////////
{0xfe, 0x02}, //page2
{0x90, 0x6c}, //ac //eeintp mode1
{0x91, 0x03}, //02 ////eeintp mode2
{0x92, 0xc8}, //44 //low criteria for direction
{0x94, 0x66},
{0x95, 0xb5},
{0x97, 0x64}, //78 ////edge effect
{0xa2, 0x11}, //fix direction
{0xfe, 0x00},
/////////////////////////////
//////// DNDD///////////////
/////////////////////////////
{0xfe, 0x02},
{0x80, 0xc1}, //c1 //[7]share mode [6]skin mode [5]is 5x5 mode [1:0]noise value select 0:2 1:2.5 2:3 3:4
{0x81, 0x08}, //
{0x82, 0x08}, //signal a 0.6
{0x83, 0x08}, //04 //signal b 2.5
{0x84, 0x0a}, //10 //05 dark_DD_TH
{0x86, 0xf0}, //a0 Y_value_dd_th2
{0x87, 0x50}, //90 Y_value_dd_th3
{0x88, 0x15}, //60 Y_value_dd_th4
{0x89, 0x50}, //80 // asde th2
{0x8a, 0x30}, //60 // asde th3
{0x8b, 0x10}, //30 // asde th4
/////////////////////////////////////////////////
///////////// ASDE ////////////////////////
/////////////////////////////////////////////////
{0xfe, 0x01}, //page 1
{0x21, 0x14}, //luma_value_div_sel(分频,与0xef呈2倍关系,增大1,0xef的值减小1倍)
//ff ef luma_value read_only
{0xfe, 0x02}, //page2
{0xa3, 0x40}, //ASDE_low_luma_value_LSC_th_H
{0xa4, 0x20}, //ASDE_low_luma_value_LSC_th_L
{0xa5, 0x40}, //80 //ASDE_LSC_gain_dec_slope_H
{0xa6, 0x80}, // 80 //ASDE_LSC_gain_dec_slope_L
//ff a7 ASDE_LSC_gain_dec //read only
{0xab, 0x40}, //50 //ASDE_low_luma_value_OT_th
{0xae, 0x0c}, //[3]EE1_effect_inc_or_dec_high,[2]EE2_effect_inc_or_dec_high,
//[1]EE1_effect_inc_or_dec_low,[0]EE2_effect_inc_or_dec_low, 1:inc 0:dec
{0xb3, 0x34}, //44 //ASDE_EE1_effect_slope_low,ASDE_EE2_effect_slope_low
{0xb4, 0x44}, //12 //ASDE_EE1_effect_slope_high,ASDE_EE2_effect_slope_high
{0xb6, 0x38}, //40//40 //ASDE_auto_saturation_dec_slope
{0xb7, 0x02}, //04 //ASDE_sub_saturation_slope
{0xb9, 0x30}, //[7:0]ASDE_auto_saturation_low_limit
{0x3c, 0x08}, //[3:0]auto gray_dec_slope
{0x3d, 0x30}, //[7:0]auto gray_dec_th
{0x4b, 0x0d}, //y offset slope
{0x4c, 0x20}, //y offset limit
{0xfe, 0x00},
//
///////////////////gamma1////////////////////
////Gamma
{0xfe, 0x02},
{0x10, 0x10},
{0x11, 0x15},
{0x12, 0x1a},
{0x13, 0x1f},
{0x14, 0x2c},
{0x15, 0x39},
{0x16, 0x45},
{0x17, 0x54},
{0x18, 0x69},
{0x19, 0x7d},
{0x1a, 0x8f},
{0x1b, 0x9d},
{0x1c, 0xa9},
{0x1d, 0xbd},
{0x1e, 0xcd},
{0x1f, 0xd9},
{0x20, 0xe3},
{0x21, 0xea},
{0x22, 0xef},
{0x23, 0xf5},
{0x24, 0xf9},
{0x25, 0xff},
/////auto gamma/////
{0xfe, 0x02},
{0x26, 0x0f},
{0x27, 0x14},
{0x28, 0x19},
{0x29, 0x1e},
{0x2a, 0x27},
{0x2b, 0x33},
{0x2c, 0x3b},
{0x2d, 0x45},
{0x2e, 0x59},
{0x2f, 0x69},
{0x30, 0x7c},
{0x31, 0x89},
{0x32, 0x98},
{0x33, 0xae},
{0x34, 0xc0},
{0x35, 0xcf},
{0x36, 0xda},
{0x37, 0xe2},
{0x38, 0xe9},
{0x39, 0xf3},
{0x3a, 0xf9},
{0x3b, 0xff},
///////////////////////////////////////////////
/////////// YCP ///////////////////////
///////////////////////////////////////////////
{0xfe, 0x02},
{0xd1, 0x30}, //32 //
{0xd2, 0x30}, //32 //
{0xd3, 0x45},
{0xdd, 0x14}, //edge sa
{0xde, 0x86}, //asde auto gray
{0xed, 0x01}, //
{0xee, 0x28},
{0xef, 0x30},
{0xd8, 0xd8}, //autogray protecy
////////////////////////////
//////// LSC 0.8///////////////
////////////////////////////
{0xfe, 0x01},
{0xa1, 0x80}, // center_row
{0xa2, 0x80}, // center_col
{0xa4, 0x00}, // sign of b1
{0xa5, 0x00}, // sign of b1
{0xa6, 0x70}, // sign of b4
{0xa7, 0x00}, // sign of b4
{0xa8, 0x77}, // sign of b22
{0xa9, 0x77}, // sign of b22
{0xaa, 0x1f}, // Q1_b1 of R
{0xab, 0x0d}, // Q1_b1 of G
{0xac, 0x19}, // Q1_b1 of B
{0xad, 0x24}, // Q2_b1 of R
{0xae, 0x0e}, // Q2_b1 of G
{0xaf, 0x1d}, // Q2_b1 of B
{0xb0, 0x12}, // Q3_b1 of R
{0xb1, 0x0c}, // Q3_b1 of G
{0xb2, 0x06}, // Q3_b1 of B
{0xb3, 0x13}, // Q4_b1 of R
{0xb4, 0x10}, // Q4_b1 of G
{0xb5, 0x0c}, // Q4_b1 of B
{0xb6, 0x6a}, // right_b2 of R
{0xb7, 0x46}, // right_b2 of G
{0xb8, 0x40}, // right_b2 of B
{0xb9, 0x0b}, // right_b4 of R
{0xba, 0x04}, // right_b4 of G
{0xbb, 0x00}, // right_b4 of B
{0xbc, 0x53}, // left_b2 of R
{0xbd, 0x37}, // left_b2 of G
{0xbe, 0x2d}, // left_b2 of B
{0xbf, 0x0a}, // left_b4 of R
{0xc0, 0x0a}, // left_b4 of G
{0xc1, 0x14}, // left_b4 of B
{0xc2, 0x34}, // up_b2 of R
{0xc3, 0x22}, // up_b2 of G
{0xc4, 0x18}, // up_b2 of B
{0xc5, 0x23}, // up_b4 of R
{0xc6, 0x0f}, // up_b4 of G
{0xc7, 0x3c}, // up_b4 of B
{0xc8, 0x20}, // down_b2 of R
{0xc9, 0x1f}, // down_b2 of G
{0xca, 0x17}, // down_b2 of B
{0xcb, 0x2d}, // down_b4 of R
{0xcc, 0x12}, // down_b4 of G
{0xcd, 0x20}, // down_b4 of B
{0xd0, 0x61}, // right_up_b22 of R
{0xd1, 0x2f}, // right_up_b22 of G
{0xd2, 0x39}, // right_up_b22 of B
{0xd3, 0x45}, // right_down_b22 of R
{0xd4, 0x2c}, // right_down_b22 of G
{0xd5, 0x21}, // right_down_b22 of B
{0xd6, 0x64}, // left_up_b22 of R
{0xd7, 0x2d}, // left_up_b22 of G
{0xd8, 0x30}, // left_up_b22 of B
{0xd9, 0x42}, // left_down_b22 of R
{0xda, 0x27}, // left_down_b22 of G
{0xdb, 0x13}, // left_down_b22 of B
{0xfe, 0x00},
/////////////////////////////////////////////////
///////////// AWB ////////////////////////
/////////////////////////////////////////////////
{0xfe, 0x01},
{0x4f, 0x00},
{0x4f, 0x00},
{0x4b, 0x01},
{0x4f, 0x00},
{0x4c, 0x01},
{0x4d, 0x6f},
{0x4e, 0x02},
{0x4c, 0x01},
{0x4d, 0x70},
{0x4e, 0x02},
{0x4c, 0x01},
{0x4d, 0x8f},
{0x4e, 0x02},
{0x4c, 0x01},
{0x4d, 0x90},
{0x4e, 0x02}, //light
{0x4c, 0x01},
{0x4d, 0xed},
{0x4e, 0x33}, //light
{0x4c, 0x01},
{0x4d, 0xcd},
{0x4e, 0x33}, //light
{0x4c, 0x01},
{0x4d, 0xec},
{0x4e, 0x03}, //light
{0x4c, 0x01},
{0x4d, 0x6c},
{0x4e, 0x03},
{0x4c, 0x01},
{0x4d, 0x6d},
{0x4e, 0x03},
{0x4c, 0x01},
{0x4d, 0x6e},
{0x4e, 0x03},
{0x4c, 0x01},
{0x4d, 0x8c},
{0x4e, 0x03},
{0x4c, 0x01},
{0x4d, 0x8d},
{0x4e, 0x03},
{0x4c, 0x01},
{0x4d, 0x8e},
{0x4e, 0x03},
{0x4c, 0x01},
{0x4d, 0xab},
{0x4e, 0x03},
{0x4c, 0x01},
{0x4d, 0xac},
{0x4e, 0x03},
{0x4c, 0x01},
{0x4d, 0xad},
{0x4e, 0x03},
{0x4c, 0x01},
{0x4d, 0xae},
{0x4e, 0x03},
{0x4c, 0x01},
{0x4d, 0xcb},
{0x4e, 0x03},
{0x4c, 0x01},
{0x4d, 0xcc},
{0x4e, 0x03},
{0x4c, 0x01},
{0x4d, 0xce},
{0x4e, 0x03},
{0x4c, 0x01},
{0x4d, 0xeb},
{0x4e, 0x03},
{0x4c, 0x01},
{0x4d, 0xec},
{0x4e, 0x03},
{0x4c, 0x01},
{0x4d, 0xee},
{0x4e, 0x03},
{0x4c, 0x02},
{0x4d, 0x0c},
{0x4e, 0x03},
{0x4c, 0x02},
{0x4d, 0x0d},
{0x4e, 0x03},
{0x4c, 0x01},
{0x4d, 0xea},
{0x4e, 0x03},
{0x4c, 0x01},
{0x4d, 0xaf},
{0x4e, 0x03}, //dark
{0x4c, 0x01},
{0x4d, 0xcf},
{0x4e, 0x03}, //dark
{0x4c, 0x01},
{0x4d, 0xca},
{0x4e, 0x04}, //light
{0x4c, 0x02},
{0x4d, 0x0b},
{0x4e, 0x05}, //light
{0x4c, 0x02},
{0x4d, 0xc8},
{0x4e, 0x06}, //light 100lux
{0x4c, 0x02},
{0x4d, 0xa8},
{0x4e, 0x06}, //light
{0x4c, 0x02},
{0x4d, 0xa9},
{0x4e, 0x06}, //light
{0x4c, 0x02},
{0x4d, 0x89},
{0x4e, 0x06}, //400lux
{0x4c, 0x02},
{0x4d, 0x69},
{0x4e, 0x06}, //f12
{0x4c, 0x02},
{0x4d, 0x6a},
{0x4e, 0x06}, //f12
{0x4c, 0x02},
{0x4d, 0xc7},
{0x4e, 0x07},
{0x4c, 0x02},
{0x4d, 0xe7},
{0x4e, 0x07}, //100lux
{0x4c, 0x03},
{0x4d, 0x07},
{0x4e, 0x07}, //light
{0x4c, 0x02},
{0x4d, 0xe8},
{0x4e, 0x07},
{0x4c, 0x02},
{0x4d, 0xe9},
{0x4e, 0x07},
{0x4c, 0x03},
{0x4d, 0x08},
{0x4e, 0x07},
{0x4c, 0x03},
{0x4d, 0x09},
{0x4e, 0x07},
{0x4c, 0x03},
{0x4d, 0x27},
{0x4e, 0x07},
{0x4c, 0x03},
{0x4d, 0x28},
{0x4e, 0x07},
{0x4c, 0x03},
{0x4d, 0x29},
{0x4e, 0x07},
{0x4c, 0x03},
{0x4d, 0x47},
{0x4e, 0x07},
{0x4c, 0x03},
{0x4d, 0x48},
{0x4e, 0x07},
{0x4c, 0x03},
{0x4d, 0x49},
{0x4e, 0x07},
{0x4c, 0x03},
{0x4d, 0x67},
{0x4e, 0x07},
{0x4c, 0x03},
{0x4d, 0x68},
{0x4e, 0x07},
{0x4c, 0x03},
{0x4d, 0x69},
{0x4e, 0x07},
{0x4f, 0x01},
{0xfe, 0x01},
{0x50, 0x80}, //AWB_PRE_mode
{0x51, 0xa8}, //AWB_pre_THD_min[7:0]
{0x52, 0x57}, //AWB_pre_THD_min[15:8] Dominiate luma 0.25=639c 0.22=57a8
{0x53, 0x38}, //AWB_pre_THD_min_MIX[7:0]
{0x54, 0xc7}, //AWB_pre_THD_min_MIX[15:8] Mix luma 0.5
{0x56, 0x0e}, //AWB_tone mode
{0x58, 0x08}, //AWB_C_num_sel,AWB_D_num_sel
{0x5b, 0x00}, //AWB_mix_mode
{0x5c, 0x74}, //green_num0[7:0]
{0x5d, 0x8b}, //green_num0[15:8] 0.35
{0x61, 0xd3}, //R2G_stand0
{0x62, 0xb5}, //B2G_stand0
{0x63, 0x00}, //88//a4 //AWB gray mode [7]enable
{0x65, 0x04}, //AWB margin
{0x67, 0xb2}, //R2G_stand3[7:0] FF/CWF
{0x68, 0xac}, //B2G_stand3[7:0]
{0x69, 0x00}, //R2G_stand4[9:8] B2G_stand4[9:8] R2G_stand3[9:8] B2G_stand3[9:8]
{0x6a, 0xb2}, //R2G_stand4[7:0] TL84/TL84&CWF
{0x6b, 0xac}, //B2G_stand4[7:0]
{0x6c, 0xb2}, //R2G_stand5[7:0] A
{0x6d, 0xac}, //B2G_stand5[7:0]
{0x6e, 0x40}, //AWB_skin_weight R2G_stand5[9:8] B2G_stand5[9:8]
{0x6f, 0x18}, //AWB_indoor_THD (0x21=17 calculate)
{0x73, 0x00}, //AWB_indoor_mode
{0x70, 0x10}, //AWB low luma TH
{0x71, 0xe8}, //AWB outdoor TH
{0x72, 0xc0}, //outdoor mode
{0x74, 0x01}, //[2:0]AWB skip mode 2x2,4x4,4x8,8x8
{0x75, 0x01}, //[1:0]AWB_every_N
{0x7f, 0x08}, //[3]gray world frame start
{0x76, 0x70}, //R limit
{0x77, 0x58}, //G limit
{0x78, 0xa0}, //d8 //B limit
{0xfe, 0x00},
//
//////////////////////////////////////////
/////////// CC ////////////////////////
//////////////////////////////////////////
{0xfe, 0x02},
{0xc0, 0x01}, //[5:4] CC mode [0]CCT enable
{0xC1, 0x50}, //D50/D65
{0xc2, 0xF9},
{0xc3, 0x00}, //0
{0xc4, 0xe8}, //e0
{0xc5, 0x48},
{0xc6, 0xf0},
{0xC7, 0x50},
{0xc8, 0xf2},
{0xc9, 0x00},
{0xcA, 0xE0},
{0xcB, 0x45},
{0xcC, 0xec},
{0xCd, 0x45},
{0xce, 0xf0},
{0xcf, 0x00},
{0xe3, 0xf0},
{0xe4, 0x45},
{0xe5, 0xe8},
{0xfe, 0x00},
{0xf2, 0x0f},
//////////////frame rate 50Hz
{0xfe, 0x00},
{0xf7, 0x1d},
{0xf8, 0x84},
{0xfa, 0x00},
{0x05, 0x01}, //hb
{0x06, 0x3b},
{0x07, 0x01}, //Vb
{0x08, 0x0b},
{0xfe, 0x01},
{0x25, 0x01},
{0x26, 0x32}, //step
{0x27, 0x03}, //8.15fps
{0x28, 0x96},
{0x29, 0x03}, //8.15fps
{0x2a, 0x96},
{0x2b, 0x03}, //8.15fps
{0x2c, 0x96},
{0x2d, 0x04}, //8.15fps
{0x2e, 0x62},
{0x3c, 0x00},
{0xfe, 0x00},
/////////dark sun//////
{0xfe, 0x00},
{0x18, 0x22},
{0xfe, 0x02},
{0x40, 0xbf},
{0x46, 0xcf},
{0xfe, 0x00},
{0xfe, 0x00},
{0xf7, 0x1d},
{0xf8, 0x84},
{0xfa, 0x10},
{0x05, 0x01}, //hb
{0x06, 0x18},
{0x07, 0x00}, //Vb
{0x08, 0x2e},
{0xfe, 0x01},
{0x25, 0x00},
{0x26, 0xa2}, //step
{0x27, 0x01},
{0x28, 0xe6},
{0x29, 0x01},
{0x2a, 0xe6},
{0x2b, 0x01},
{0x2c, 0xe6},
{0x2d, 0x04}, // AEC_exp_level4[12:8]
{0x2e, 0x62}, // AEC_exp_level4[7:0]
{0x3c, 0x00},
{0xfe, 0x00},
{0x09, 0x01}, //row start
{0x0a, 0xd0}, //
{0x0b, 0x02}, //col start
{0x0c, 0x70},
{0x0d, 0x01}, //height
{0x0e, 0x00},
{0x0f, 0x01}, //width
{0x10, 0x50},
{0x90, 0x01}, //crop
{0x91, 0x00},
{0x92, 0x00},
{0x93, 0x00},
{0x94, 0x00},
{0x95, 0x00},
{0x96, 0xf0},
{0x97, 0x01},
{0x98, 0x40},
{REGLIST_TAIL, 0x00},
};
@@ -0,0 +1,27 @@
/*
* HM0360 driver.
*/
#ifndef __HM0360_H__
#define __HM0360_H__
#include "sensor.h"
/**
* @brief Detect sensor pid
*
* @param slv_addr SCCB address
* @param id Detection result
* @return
* 0: Can't detect this sensor
* Nonzero: This sensor has been detected
*/
int esp32_camera_hm0360_detect(int slv_addr, sensor_id_t *id);
/**
* @brief initialize sensor function pointers
*
* @param sensor pointer of sensor
* @return
* Always 0
*/
int esp32_camera_hm0360_init(sensor_t *sensor);
#endif // __HM1055_H__
@@ -0,0 +1,150 @@
/*
* HM1055 register definitions.
*/
#ifndef __REG_REGS_H__
#define __REG_REGS_H__
// Sensor ID
#define MODEL_ID_H 0x0000
#define MODEL_ID_L 0x0001
#define SILICON_REV 0x0002
#define FRAME_COUNT_H 0x0005
#define FRAME_COUNT_L 0x0006
#define PIXEL_ODERDER 0x0007
// Snesor mode control
#define MODEL_SELECT 0x0100
#define IMAGE_ORIENTATION 0x0101
#define EMBEDDED_LINE_EN 0x0102
#define SW_RESET 0x0103
#define COMMAND_UPDATE 0x0104
// Sensor exposure gain control
#define INTEGRATION_H 0x0202
#define INTEGRATION_L 0x0203
#define ANALOG_GAIN 0x0205
#define DIGITAL_GAIN_H 0x020E
#define DIGITAL_GAIN_L 0x020F
// clock control
#define PLL1CFG 0x0300
#define PLL2CFG 0x0301
#define PLL3CFG 0x0302
// frame timing control
#define FRAME_LENGTH_LINES_H 0x0340
#define FRAME_LENGTH_LINES_L 0x0341
#define LINE_LENGTH_PCK_H 0x0342
#define LINE_LENGTH_PCK_L 0x0343
// monochrome programming
#define MONO_MODE 0x0370
#define MONO_MODE_ISP 0x0371
#define MONO_MODE_SEL 0x0372
// sub-sampling / binning control
#define H_SUB 0x0380
#define V_SUB 0x0381
#define BINNING_MODE 0x0382
// test pattern control
#define TEST_PATTERN_MODE 0x0601
#define TEST_DATA_BLUE_H 0x0602
#define TEST_DATA_BLUE_L 0x0603
#define TEST_DATA_GB_H 0x0604
#define TEST_DATA_GB_L 0x0605
#define TEST_DATA_GR_H 0x0605
#define TEST_DATA_GR_L 0x0606
#define TEST_DATA_RED_H 0x0608
#define TEST_DATA_RED_L 0x0609
// black level control
#define BLC_TGT 0x1004
#define BLC2_TGT 0x1009
// monochrome programming
#define MONO_CTRL 0x100A
// VSYNC / HSYNC / pixel shift
#define OPFM_CTRL 0x1014
// Tone mapping registers
#define CMPRS_CTRL 0x102F
#define CMPRS_01 0x1030
#define CMPRS_02 0x1031
#define CMPRS_03 0x1032
#define CMPRS_04 0x1033
#define CMPRS_05 0x1034
#define CMPRS_06 0x1035
#define CMPRS_07 0x1036
#define CMPRS_08 0x1037
#define CMPRS_09 0x1038
#define CMPRS_10 0x1039
#define CMPRS_11 0x103A
#define CMPRS_12 0x103B
#define CMPRS_13 0x103C
#define CMPRS_14 0x103D
#define CMPRS_15 0x103E
#define CMPRS_16 0x103F
// automatic exposure programming
#define AE_CTRL 0x2000
#define AE_CTRL1 0x2001
#define CNT_ORG_H_H 0x2002
#define CNT_ORG_H_L 0x2003
#define CNT_ORG_V_H 0x2004
#define CNT_ORG_V_L 0x2005
#define CNT_ST_H_H 0x2006
#define CNT_ST_H_L 0x2007
#define CNT_ST_V_H 0x2008
#define CNT_ST_V_L 0x2009
#define CTRL_PG_SKIPCNT 0x200A
#define BV_WIN_WEIGHT_EN 0x200D
#define MAX_INTG_H 0x2029
#define MAX_INTG_L 0x202A
#define MAX_AGAIN 0x202B
#define MAX_DGAIN_H 0x202C
#define MAX_DGAIN_L 0x202D
#define MIN_INTG 0x202E
#define MIN_AGAIN 0x202F
#define MIN_DGAIN 0x2030
#define T_DAMPING 0x2031
#define N_DAMPING 0x2032
#define ALC_TH 0x2033
#define AE_TARGET_MEAN 0x2034
#define AE_MIN_MEAN 0x2035
#define AE_TARGET_ZONE 0x2036
#define CONVERGE_IN_TH 0x2037
#define CONVERGE_OUT_TH 0x2038
// Interrupt control
#define PULSE_MODE 0x2061
#define PULSE_TH_H 0x2062
#define PULSE_TH_L 0x2063
#define INT_INDIC 0x2064
#define INT_CLEAR 0x2065
// Motion detection control
#define MD_CTRL 0x2080
#define ROI_START_END_V 0x2081
#define ROI_START_END_H 0x2082
#define MD_TH_MIN 0x2083
#define MD_TH_STR_L 0x2084
#define MD_TH_STR_H 0x2085
#define MD_LIGHT_COEF 0x2099
#define MD_BLOCK_NUM_TH 0x209B
#define MD_LATENCY 0x209C
#define MD_LATENCY_TH 0x209D
#define MD_CTRL1 0x209E
// Context switch control registers
#define PMU_CFG_3 0x3024
#define PMU_CFG_4 0x3025
// Operation mode control
#define WIN_MODE 0x3030
// IO and clock control
#define PAD_REGISTER_07 0x3112
#endif //__REG_REGS_H__
@@ -0,0 +1,549 @@
#include <stdint.h>
#define REG_DLY 0xffff
#define REGLIST_TAIL 0x0000
static const DRAM_ATTR uint16_t sensor_default_regs[][2] = {
{0x0103, 0x00},
{REG_DLY, 100},
{0x3035, 0x01},
{0x3126, 0x03},
{0x3128, 0x57},
{0x3142, 0x9F},
{0x311C, 0x10},
{0x3115, 0x42},
{0x3116, 0x10},
{0x3117, 0x0A},
{0x310B, 0x10},
{0x3031, 0x01},
{0x312E, 0x00},
{0x30D7, 0x00},
{0x30DC, 0x00},
{0x30E1, 0x00},
{0x30E6, 0x00},
{0x30EB, 0x00},
{0x30F0, 0x00},
{0x30F5, 0x00},
{0x30FA, 0x00},
{0x30FF, 0x00},
{0x3104, 0x00},
{0x30D8, 0xA7},
{0x30DD, 0x27},
{0x30E2, 0x27},
{0x30E7, 0x27},
{0x30EC, 0x27},
{0x30F1, 0xA7},
{0x30F6, 0x27},
{0x30FB, 0x27},
{0x3100, 0x27},
{0x3105, 0x27},
{0x30D9, 0x00}, // 220215
{0x30DE, 0x05},
{0x30E3, 0x05},
{0x30E8, 0x05},
{0x30ED, 0x05},
{0x30F2, 0x00}, // 220215
{0x30F7, 0x05},
{0x30FC, 0x05},
{0x3101, 0x05},
{0x3106, 0x05},
{0x30C4, 0x10},
{0x30C5, 0x01},
{0x30C6, 0x2F}, // 220215
{0x30CB, 0xFF},
{0x30CC, 0xFF},
{0x30CD, 0x7F},
{0x30CE, 0x7F},
{0x30D3, 0x01},
{0x30D4, 0xFF},
{0x311F, 0x0E},
{0x3120, 0x0D},
{0x3121, 0x0F},
{0x3122, 0x00},
{0x3147, 0x18},
{0x314B, 0x01},
{0x3149, 0x28},
{0x3150, 0x50},
{0x3152, 0x00},
{0x3156, 0x2C},
{0x3163, 0x1F},
{0x3165, 0x7F},
{0x312B, 0x41},
{0x3113, 0xA0},
{0x3114, 0x67},
{0x317D, 0x02},
{0x3160, 0x1F},
{0x318C, 0x00},
{0x315C, 0xE0},
{0x311E, 0x0F},
{0x30D5, 0x00},
{0x30DA, 0x01},
{0x30DF, 0x07},
{0x30E4, 0x47},
{0x30E9, 0x87},
{0x30FD, 0x47},
{0x3102, 0x87},
{0x311D, 0x06},
{0x3141, 0x2A},
{0x315A, 0x0A},
{0x30D6, 0x40},
{0x30DB, 0x40},
{0x30E0, 0x40},
{0x30E5, 0x30},
{0x30EA, 0x30},
{0x30EF, 0x40},
{0x30F4, 0x40},
{0x30F9, 0x40},
{0x30FE, 0x30},
{0x3103, 0x30},
{0x3164, 0x7F},
{0x282A, 0x0F},
{0x282E, 0x2F},
{0x282B, 0x08},
{0x312A, 0x11},
{0x1000, 0x43},
{0x1001, 0x80},
{0x0350, 0xE0},
{0x101D, 0xCF},
{0x1021, 0x5D},
// setA VGA
{0x3500, 0x74},
{0x3501, 0x0A},
{0x3502, 0x77},
{0x3503, 0x04},
{0x3504, 0x14},
{0x3505, 0x03},
{0x3506, 0x00},
{0x3507, 0x00},
{0x3508, 0x00},
{0x3509, 0x00},
{0x350A, 0xFF},
{0x350B, 0x00},
{0x350D, 0x01},
{0x350C, 0x00},
{0x350F, 0x00},
{0x3510, 0x01},
{0x3513, 0x00},
{0x351C, 0x00},
{0x3514, 0x00},
{0x3515, 0x01},
{0x3516, 0x00},
{0x3517, 0x02},
{0x3518, 0x00},
{0x3519, 0x7F},
{0x351A, 0x00},
{0x351B, 0x5F},
{0x351D, 0x04},
{0x351E, 0x10},
{0x352A, 0x01},
{0x352B, 0x04},
{0x352C, 0x01},
{0x352D, 0x38},
{0x354B, 0x21},
{0x354C, 0x01},
{0x354D, 0xE0},
{0x354E, 0xF0},
{0x354F, 0x10},
{0x3550, 0x10},
{0x3551, 0x10},
{0x3552, 0x20},
{0x3553, 0x10},
{0x3554, 0x01},
{0x3555, 0x06},
{0x3556, 0x0C},
{0x3557, 0x12},
{0x3558, 0x1C},
{0x3559, 0x30},
{0x3549, 0x04},
{0x354A, 0x35},
// setB QVGA
{0x355A, 0x74},
{0x355B, 0x0A},
{0x355C, 0x77},
{0x355D, 0x04},
{0x355E, 0x14},
{0x355F, 0x03},
{0x3560, 0x00},
{0x3561, 0x01},
{0x3562, 0x01},
{0x3563, 0x00},
{0x3564, 0xFF},
{0x3565, 0x00},
{0x3567, 0x01},
{0x3566, 0x00},
{0x3569, 0x00},
{0x356A, 0x01},
{0x356D, 0x00},
{0x3576, 0x00},
{0x356E, 0x00},
{0x356F, 0x01},
{0x3570, 0x00},
{0x3571, 0x02},
{0x3572, 0x00},
{0x3573, 0x3F},
{0x3574, 0x00},
{0x3575, 0x2F},
{0x3577, 0x04},
{0x3578, 0x10},
{0x3584, 0x01},
{0x3585, 0x04},
{0x3586, 0x01},
{0x3587, 0x38},
{0x3588, 0x02},
{0x3589, 0x12},
{0x358A, 0x04},
{0x358B, 0x24},
{0x358C, 0x06},
{0x358D, 0x36},
{0x35A5, 0x21},
{0x35A6, 0x01},
{0x35A7, 0xE0},
{0x35A8, 0xF0},
{0x35A9, 0x10},
{0x35AA, 0x10},
{0x35AB, 0x10},
{0x35AC, 0x20},
{0x35AD, 0x10},
{0x35AE, 0x01},
{0x35AF, 0x06},
{0x35B0, 0x0C},
{0x35B1, 0x12},
{0x35B2, 0x1C},
{0x35B3, 0x30},
{0x35A3, 0x02},
{0x35A4, 0x03},
// AE_tuning
{0x3512, 0x3F},
{0x351F, 0x04},
{0x3520, 0x03},
{0x3521, 0x00},
{0x3523, 0x60},
{0x3524, 0x08},
{0x3525, 0x19},
{0x3526, 0x08},
{0x3527, 0x10},
{0x356C, 0x3F},
{0x3579, 0x04},
{0x357A, 0x03},
{0x357B, 0x00},
{0x357D, 0x60},
{0x357E, 0x08},
{0x357F, 0x19},
{0x3580, 0x08},
{0x3581, 0x10},
{0x2048, 0x00},
{0x2049, 0x10},
{0x204A, 0x40},
{0x204E, 0x00},
{0x204F, 0x38},
{0x2050, 0xE0},
{0x204B, 0x00},
{0x204C, 0x08},
{0x204D, 0x20},
{0x2051, 0x00},
{0x2052, 0x1C},
{0x2053, 0x70},
{0x2054, 0x00},
{0x2055, 0x1A},
{0x2056, 0xC0},
{0x2057, 0x00},
{0x2058, 0x06},
{0x2059, 0xB0},
// MD tuning
{0x2080, 0x41},
{0x2083, 0x01},
{0x208D, 0x02},
{0x208E, 0x08},
{0x208F, 0x0D},
{0x2090, 0x14},
{0x2091, 0x1D},
{0x2092, 0x30},
{0x2093, 0x08},
{0x2094, 0x0A},
{0x2095, 0x0F},
{0x2096, 0x14},
{0x2097, 0x18},
{0x2098, 0x20},
{0x2099, 0x10},
{0x209A, 0x00},
{0x209B, 0x01},
{0x209C, 0x01},
{0x209D, 0x11},
{0x209E, 0x06},
// Tone mapping
{0x1030, 0x04},
{0x1031, 0x08},
{0x1032, 0x10},
{0x1033, 0x18},
{0x1034, 0x20},
{0x1035, 0x28},
{0x1036, 0x30},
{0x1037, 0x38},
{0x1038, 0x40},
{0x1039, 0x50},
{0x103A, 0x60},
{0x103B, 0x70},
{0x103C, 0x80},
{0x103D, 0xA0},
{0x103E, 0xC0},
{0x103F, 0xE0},
// RESERVED
{0x35B4, 0x74},
{0x35B5, 0x0A},
{0x35B6, 0x77},
{0x35B7, 0x00},
{0x35B8, 0x94},
{0x35B9, 0x03},
{0x35BA, 0x00},
{0x35BB, 0x03},
{0x35BD, 0x00},
{0x35BE, 0xFF},
{0x35BF, 0x00},
{0x35C1, 0x01},
{0x35C0, 0x01},
{0x35C3, 0x00},
{0x35C4, 0x00},
{0x35C6, 0x3F},
{0x35C7, 0x00},
{0x35D0, 0x00},
{0x35D3, 0x04},
{0x35D7, 0x18},
{0x35D8, 0x01},
{0x35D9, 0x20},
{0x35DA, 0x08},
{0x35DB, 0x14},
{0x35DC, 0x70},
{0x35C8, 0x00},
{0x35C9, 0x01},
{0x35CA, 0x00},
{0x35CB, 0x02},
{0x35CC, 0x00},
{0x35CD, 0x0F},
{0x35CE, 0x00},
{0x35CF, 0x0B},
{0x35DE, 0x00},
{0x35DF, 0x01},
{0x35FD, 0x00},
{0x35FE, 0x5E},
// RESERVED
{0x3024, 0x00},
{0x3025, 0x12},
{0x3026, 0x03},
{0x3027, 0x81},
{0x3028, 0x01},
{0x3029, 0x00},
{0x302A, 0x30},
// Interrupt
{0x2061, 0x00},
{0x2062, 0x00},
{0x2063, 0xC8},
// Parallel I/F
{0x1014, 0x00},
{0x102F, 0x08},
{0x309E, 0x05},
{0x309F, 0x02},
{0x30A0, 0x02},
{0x30A1, 0x00},
{0x30A2, 0x08},
{0x30A3, 0x00},
{0x30A4, 0x20},
{0x30A5, 0x04},
{0x30A6, 0x02},
{0x30A7, 0x02},
{0x30A8, 0x01},
{0x30B0, 0x03},
{0x3112, 0x04},
{0x311A, 0x30}, //31:bypass internal ldo 30:internal LDO
// MIPI
{0x2800, 0x00},
// Enable BPC}
{0x0370, 0x00},
{0x0371, 0x00},
{0x0372, 0x01},
{0x100A, 0x05},
{0x2590, 0x01},
{0x0104, 0x01},
{0x0100, 0x01},
{REGLIST_TAIL, 0x00}, // tail
};
static const DRAM_ATTR uint16_t sensor_framesize_VGA[][2] = {
{0x0006, 0x10},
{0x000D, 0x00},
{0x000E, 0x00},
{0x0122, 0xEB},
{0x0125, 0xFF},
{0x0126, 0x70},
{0x05E0, 0XC1},
{0x05E1, 0x00},
{0x05E2, 0xC1},
{0x05E3, 0x00},
{0x05E4, 0x03},
{0x05E5, 0x00},
{0x05E6, 0x82},
{0x05E7, 0x02},
{0x05E8, 0x04},
{0x05E9, 0x00},
{0x05EA, 0xE3},
{0x05EB, 0x01},
{REGLIST_TAIL, 0x00},
};
static const DRAM_ATTR uint16_t sensor_framesize_QVGA[][2] = {
{0x355A, 0x74}, // vt_sys_d
{0x355B, 0x0A}, // op_sys_d
{0x355C, 0x77}, // mclk_div_d
{0x355D, 0x01},
{0x355E, 0x1C},
{0x355F, 0x03},
{0x3560, 0x00},
{0x3561, 0x01},
{0x3562, 0x01},
{0x3563, 0x00},
{0x3564, 0xFF},
{0x3565, 0x00},
{0x3567, 0x01},
{0x3566, 0x00},
{0x3569, 0x00},
{0x356A, 0x01},
{0x356C, 0x7F},
{0x356D, 0x00},
{0x3576, 0x00},
{0x3579, 0x03}, // Max Again
{0x356E, 0x00}, // AE cnt org H HB
{0x356F, 0x01}, // AE cnt org H LB
{0x3570, 0x00}, // AE cnt org V HB
{0x3571, 0x02}, // AE cnt org V LB
{0x3572, 0x00}, // AE cnt st H HB
{0x3573, 0x3F}, // AE cnt st H LB
{0x3574, 0x00}, // AE cnt st V HB
{0x3575, 0x2F}, // AE cnt st V LB
{0x3577, 0x04},
{0x3578, 0x24},
{0x3584, 0x01},
{0x3585, 0x04},
{0x3586, 0x01},
{0x3587, 0x38},
{0x3588, 0x02}, // FR stage 1 H
{0x3589, 0x12}, // FR stage 1 L
{0x358A, 0x04}, // FR stage 2 H
{0x358B, 0x24}, // FR stage 2 L
{0x358C, 0x06}, // FR stage 3 H
{0x358D, 0x36}, // FR stage 3 L
{0x35A5, 0x21}, // [7:1]MD_light_coef [0] MD enable
{0x35A6, 0x01}, // MD_block_num_th
{0x35A7, 0xE0}, // [7:4]md_roi_end_v [3:0]md_roi_start_v
{0x35A8, 0xF0}, // [7:4]md_roi_end_h [3:0]md_roi_start_h
{0x35A9, 0x10}, // [6:0] md_th_str_HCG
{0x35AA, 0x10}, // [5:0] md_th_str_LCG
{0x35AB, 0x10}, // [5:0] md_th_str_HDR
{0x35AC, 0x20}, // md_flick_skip_th_adj_N
{0x35AD, 0x10}, // md_flick_skip_th_adj_P
{0x35AE, 0x01},
{0x35AF, 0x06},
{0x35B0, 0x0C},
{0x35B1, 0x12},
{0x35BC, 0x1C},
{0x35B3, 0x30},
{0x35A3, 0x02}, // full trigger H
{0x35A4, 0x03}, // full trigger L
{REGLIST_TAIL, 0x00},
};
static const DRAM_ATTR uint16_t sensor_framesize_240X240[][2] = {
{0x0006, 0x00},
{0x000D, 0x01},
{0x000E, 0x11},
{0x0122, 0xFB},
{0x0125, 0xFF},
{0x0126, 0x70},
{0x05E0, 0XBE},
{0x05E1, 0x00},
{0x05E2, 0xBE},
{0x05E3, 0x00},
{0x05E4, 0x62},
{0x05E5, 0x00},
{0x05E6, 0x51},
{0x05E7, 0x01},
{0x05E8, 0x04},
{0x05E9, 0x00},
{0x05EA, 0xF3},
{0x05EB, 0x00},
{REGLIST_TAIL, 0x00},
};
static const DRAM_ATTR uint16_t sensor_framesize_QQVGA[][2] = {
{0x355A, 0x74}, // vt_sys_d
{0x355B, 0x0A}, // op_sys_d
{0x355C, 0x77}, // mclk_div_d
{0x355D, 0x00},
{0x355E, 0x8E},
{0x355F, 0x03},
{0x3560, 0x00},
{0x3561, 0x02},
{0x3562, 0x02},
{0x3563, 0x00},
{0x3564, 0xFF},
{0x3565, 0x00},
{0x3567, 0x01},
{0x3566, 0x00},
{0x3569, 0x00},
{0x356A, 0x01},
{0x356C, 0x7F},
{0x356D, 0x00},
{0x3576, 0x00},
{0x3579, 0x03}, // Max Again
{0x356E, 0x00}, // AE cnt org H HB
{0x356F, 0x01}, // AE cnt org H LB
{0x3570, 0x00}, // AE cnt org V HB
{0x3571, 0x02}, // AE cnt org V LB
{0x3572, 0x00}, // AE cnt st H HB
{0x3573, 0x1F}, // AE cnt st H LB
{0x3574, 0x00}, // AE cnt st V HB
{0x3575, 0x17}, // AE cnt st V LB
{0x3577, 0x04},
{0x3578, 0x24},
{0x3584, 0x01},
{0x3585, 0x04},
{0x3586, 0x01},
{0x3587, 0x38},
{0x3588, 0x02}, // FR stage 1 H
{0x3589, 0x12}, // FR stage 1 L
{0x358A, 0x04}, // FR stage 2 H
{0x358B, 0x24}, // FR stage 2 L
{0x358C, 0x06}, // FR stage 3 H
{0x358D, 0x36}, // FR stage 3 L
{0x35A5, 0x21}, // [7:1]MD_light_coef [0] MD enable
{0x35A6, 0x01}, // MD_block_num_th
{0x35A7, 0xD0}, // [7:4]md_roi_end_v [3:0]md_roi_start_v
{0x35A8, 0xF0}, // [7:4]md_roi_end_h [3:0]md_roi_start_h
{0x35A9, 0x10}, // [6:0] md_th_str_HCG
{0x35AA, 0x10}, // [5:0] md_th_str_LCG
{0x35AB, 0x10}, // [5:0] md_th_str_HDR
{0x35AC, 0x20}, // md_flick_skip_th_adj_N
{0x35AD, 0x10}, // md_flick_skip_th_adj_P
{0x35AE, 0x01},
{0x35AF, 0x06},
{0x35B0, 0x0C},
{0x35B1, 0x12},
{0x35B2, 0x1C},
{0x35B3, 0x30},
{0x35A3, 0x00}, // full trigger H
{0x35A4, 0xEA}, // full trigger L
{REGLIST_TAIL, 0x00},
};
@@ -0,0 +1,27 @@
/*
* HM1055 driver.
*/
#ifndef __HM1055_H__
#define __HM1055_H__
#include "sensor.h"
/**
* @brief Detect sensor pid
*
* @param slv_addr SCCB address
* @param id Detection result
* @return
* 0: Can't detect this sensor
* Nonzero: This sensor has been detected
*/
int esp32_camera_hm1055_detect(int slv_addr, sensor_id_t *id);
/**
* @brief initialize sensor function pointers
*
* @param sensor pointer of sensor
* @return
* Always 0
*/
int esp32_camera_hm1055_init(sensor_t *sensor);
#endif // __HM1055_H__
@@ -0,0 +1,120 @@
/*
* HM1055 register definitions.
*/
#ifndef __REG_REGS_H__
#define __REG_REGS_H__
// Imager Configuration
#define CMU 0x0000
#define IDH 0x0001
#define IDL 0x0002
#define VID 0x0003
#define PWDCTRL 0x0004
#define TGRDCFG 0x0005
#define RDCFG 0x0006
#define VREAD 0x000D
#define HREAD 0x000E
#define IMG_CFG 0x000F
// Imager Operation
#define BLNKRH 0x0010
#define BLNKRL 0x0011
#define BLNKCCLK 0x0012
#define BLNKC 0x0013
#define INTGH 0x0015
#define INTGL 0x0016
#define AGAIN 0x0018
#define DGAIN 0x001D
// IO and Clock Configuration Setting
#define OPRTCFG 0x0020
#define SFTRST 0x0022 // any value to reset
#define IOCTRLH 0x0023
#define IOCTRLL 0x0024
#define CKCFG1 0x0025
#define CKCFG2 0x0026
#define PORTCTRL 0x0027
#define TESTIMG 0x0028
#define CCIR656 0x0029
#define PLL1 0x002A
#define CKCFG3 0x002B
#define PLL2 0x002C
// Black Level Target
#define BLCTGT 0x0040
#define BLCTGT2 0x0053
// Vertical Arbitrary Window Configuration
#define VAWINSTARTH 0x0078
#define VAWINSTARTL 0x0079
#define VAWINENDH 0x007A
#define VAWINENDL 0x007B
// Image Signal Processing Control
#define CMU_AE 0x0100
#define CMU_AWB 0x0101
#define ISPID 0x0105
#define ISPCTRL1 0x0120
#define ISPCTRL2 0x0121
#define ISPCTRL3 0x0122
#define ISPCTRL4 0x0124
#define ISPCTRL5 0x0125
#define ISPCTRL6 0x0126
// RAW Noise Filter Control
#define RAWNF 0x01E4
#define ARAWNF 0x01E5
#define ARAWNFODEL 0x01E6
// Automatic Exposure Control Registers
#define AEWBCFG 0x0380
#define AETARGU 0x0381
#define AETARGL 0x0382
#define AETARGM 0x0383
// Saturation and Hue Control
#define SAT 0x0480
#define ASAT 0x0481
#define ASATODEL 0x0482
#define HUESIN 0x0486
#define HUECOS 0x0487
#define SCENE 0x0488
// Contrast and Brightness Control
#define CONTM 0x04B0
#define ACONTM 0x04B1
#define CONTQ 0x04B3
#define ACONTQ 0x04B4
#define CONTN 0x04B6
#define CONTP 0x04B9
#define CONTGAIN 0x04BC
#define YMEAN 0x04BD
#define BRIGHT 0x04C0
// Y Denoise Control
#define YDN 0x0580
#define AYDN 0x0581
#define AYDNODEL 0x0582
// Sharpness Control
#define EDGE 0x05A1
// Fade to Black Control
#define F2BMEAN 0x05D0
#define F2BRANGE 0x05D1
// Digital Window Control
#define YUVSCXL 0x05E0
#define YUVSCXH 0x05E1
#define YUVSCYL 0x05E2
#define YUVSCYH 0x05E3
#define WINXSTL 0x05E4
#define WINXSTH 0x05E5
#define WINXEDL 0x05E6
#define WINXEDH 0x05E7
#define WINYSTL 0x05E8
#define WINYSTH 0x05E9
#define WINYEDL 0x05EA
#define WINYEDH 0x05EB
#endif //__REG_REGS_H__
@@ -0,0 +1,697 @@
#include <stdint.h>
#define REG_DLY 0xffff
#define REGLIST_TAIL 0xffff
static const DRAM_ATTR uint16_t sensor_default_regs[][2] = {
{0x0022, 0x00}, // RESET
{0x0026, 0x77}, // 24MHz MCLK, 72MHz PCLK
{0x002A, 0x44}, // Modified by Wilson
{0x002B, 0x02},
{0x002C, 0x00}, // Turn off DCC
{0x0025, 0x00}, // PLL on
{0x0020, 0x08},
{0x0027, 0x40}, // FPGA 24 data format
{0x0028, 0xC0}, //
{0x0004, 0x10}, //
{0x0006, 0x00}, // Modified by Brandon. No Flip
{0x0012, 0x0F},
{0x0044, 0x04},
{0x004A, 0x2A},
{0x004B, 0x72},
{0x004E, 0x30},
{0x0070, 0x2A},
{0x0071, 0x57},
{0x0072, 0x55},
{0x0073, 0x30},
{0x0077, 0x04},
{0x0080, 0xC2},
{0x0082, 0xA2},
{0x0083, 0xF0},
{0x0085, 0x11}, // Set ADC power to 100% and Increase Comparator power and reduce ADC swing range(4/19, K.Kim)
{0x0086, 0x22},
{0x0087, 0x08},
{0x0088, 0x6e}, // Temperature config 0x80[7:5]:76C, Minimize CTIA & PGA power and set VCMI to 200mV(4/19, K.Kim)
{0x0089, 0x2A},
{0x008A, 0x2F}, // Set BLC power to 50%(4/19, K.Kim)
{0x008D, 0x20},
{0x008f, 0x77},
{0x0090, 0x01},
{0x0091, 0x02},
{0x0092, 0x03},
{0x0093, 0x04},
{0x0094, 0x14},
{0x0095, 0x09},
{0x0096, 0x0A},
{0x0097, 0x0B},
{0x0098, 0x0C},
{0x0099, 0x04},
{0x009A, 0x14},
{0x009B, 0x34},
{0x00A0, 0x00},
{0x00A1, 0x00},
{0x0B3B, 0x0B},
{0x0040, 0x0A}, // BLC Phase1
{0x0053, 0x0A}, // BLC Phase2
{0x0120, 0x36}, // 36:50Hz, 37:60Hz
{0x0121, 0x80},
{0x0122, 0xEB}, // 122[3] is temperature_control_bit2
{0x0123, 0xCC}, // 123[3:0]maskTH
{0x0124, 0xDE},
{0x0125, 0xDF},
{0x0126, 0x70},
{0x0128, 0x1F},
{0x0129, 0x8F},
{0x0132, 0xF8},
{0x011F, 0x08}, // BPC COLD PIXEL ENABLE if 11F[0] = 1 then on else 0 off
{0x0144, 0x04}, // Hot pixel ratio
{0x0145, 0x00}, // Hot pixel ratio alpha
{0x0146, 0x20}, // Vertical Line TH
{0x0147, 0x20}, // Vertical Line TH alpha
{0x0148, 0x14}, // HD_2line_BPC_TH
{0x0149, 0x14}, // HD_2line_BPC_TH alpha
{0x0156, 0x0C}, // BPC RATIO FOR COLD PIXEL
{0x0157, 0x0C}, // BPC RATIO FOR COLD PIXEL
{0x0158, 0x0A}, // 2 LINE BPC RATIO
{0x0159, 0x0A}, // 2 LINE BPC RATIO
{0x015A, 0x03}, // BPC RATIO FOR COLD PIXEL
{0x015B, 0x40}, // Corner TH
{0x015C, 0x21}, // Corner TH alpha
{0x015E, 0x0F}, // Debug_mode
{0x0168, 0xC8}, // BPC T1
{0x0169, 0xC8}, // BPC T1 ALPHA
{0x016A, 0x96}, // BPC T2
{0x016B, 0x96}, // BPC T2 ALPHA
{0x016C, 0x64}, // BPC T3
{0x016D, 0x64}, // BPC T3 ALPHA
{0x016E, 0x32}, // BPC T4
{0x016F, 0x32}, // BPC T4 ALPHA
{0x01EF, 0xF1}, // BPC Control Byte
{0x0131, 0x44}, //[6] DS_EN, [5] VOFF, [4] EDGEON, [3] SBPI_MODE, [2] RAW_SAHRPNESS_EN, [1]G1G2B_EN
{0x014C, 0x60}, // VSUB
{0x014D, 0x24}, // VSUB ALPHA0
{0x015D, 0x90}, //[7] TG_en// [6:0]:min Tgain
{0x01D8, 0x40}, // NOISE TH
{0x01D9, 0x20}, // NL_V1
{0x01DA, 0x23}, // NL_V2
{0x0150, 0x05}, // NL_INC1
{0x0155, 0x07}, // NL_INC2
{0x0178, 0x10}, // EVDIV
{0x017A, 0x10}, // EVDIVD
{0x01BA, 0x10}, // EVSUB
{0x0176, 0x00}, // EDGE2W
{0x0179, 0x10}, // EVDIV ALPHA0
{0x017B, 0x10}, // EVDIVD ALPHA0
{0x01BB, 0x10}, // EVSUB ALPHA0
{0x0177, 0x00}, // EDGE2W ALPHA0
{0x01E7, 0x20}, // TLTH1
{0x01E8, 0x30}, // TLTH2
{0x01E9, 0x50}, // TLTH3
{0x01E4, 0x18}, // BD STRENGTH
{0x01E5, 0x20}, // BD STRENGTH ALPHA0
{0x01E6, 0x04}, // BD STRENGTH OUTDOOR
{0x0210, 0x21}, // STD
{0x0211, 0x0A}, // STD ALPHA0
{0x0212, 0x21}, // STD OUTDOOR
{0x01DB, 0x04}, // G1G2 STRENGTH
{0x01DC, 0x14}, // SBPI_OFFSET
{0x0151, 0x08},
{0x01F2, 0x18},
{0x01F8, 0x3C},
{0x01FE, 0x24},
{0x0213, 0x03}, // BPC_Control_BYTE_2 // BPC_line_edge_th 3rd bpc
{0x0214, 0x03}, // BPC_Control_BYTE_3 // 3rd bpc strength[4:0] , [7:5] is for 2line bpc temp_control_bit
{0x0215, 0x10}, // BPC_Control_BYTE_4 // 1st bpc strength[4:0]
{0x0216, 0x08}, // BPC_Control_BYTE_5 // 1st br pulse strength[4:0]
{0x0217, 0x05}, // BPC_Control_BYTE_6 // 1st gbgr pulse strength[4:0]
{0x0218, 0xB8},
{0x0219, 0x01},
{0x021A, 0xB8},
{0x021B, 0x01},
{0x021C, 0xB8},
{0x021D, 0x01},
{0x021E, 0xB8},
{0x021F, 0x01},
{0x0220, 0xF1},
{0x0221, 0x5D},
{0x0222, 0x0A},
{0x0223, 0x80},
{0x0224, 0x50},
{0x0225, 0x09},
{0x0226, 0x80},
{0x022A, 0x56},
{0x022B, 0x13},
{0x022C, 0x80},
{0x022D, 0x11},
{0x022E, 0x08},
{0x022F, 0x11},
{0x0230, 0x08},
{0x0233, 0x11},
{0x0234, 0x08},
{0x0235, 0x88},
{0x0236, 0x02},
{0x0237, 0x88},
{0x0238, 0x02},
{0x023B, 0x88},
{0x023C, 0x02},
{0x023D, 0x68},
{0x023E, 0x01},
{0x023F, 0x68},
{0x0240, 0x01},
{0x0243, 0x68},
{0x0244, 0x01},
{0x0251, 0x0F},
{0x0252, 0x00},
{0x0260, 0x00},
{0x0261, 0x4A},
{0x0262, 0x2C},
{0x0263, 0x68},
{0x0264, 0x40},
{0x0265, 0x2C},
{0x0266, 0x6A},
{0x026A, 0x40},
{0x026B, 0x30},
{0x026C, 0x66},
{0x0278, 0x98},
{0x0279, 0x20},
{0x027A, 0x80},
{0x027B, 0x73},
{0x027C, 0x08},
{0x027D, 0x80},
{0x0280, 0x0D},
{0x0282, 0x1A},
{0x0284, 0x30},
{0x0286, 0x53},
{0x0288, 0x62},
{0x028a, 0x6E},
{0x028c, 0x7A},
{0x028e, 0x83},
{0x0290, 0x8B},
{0x0292, 0x92},
{0x0294, 0x9D},
{0x0296, 0xA8},
{0x0298, 0xBC},
{0x029a, 0xCF},
{0x029c, 0xE2},
{0x029e, 0x2A},
{0x02A0, 0x02},
{0x02C0, 0x7D}, // CCM N
{0x02C1, 0x01},
{0x02C2, 0x7C},
{0x02C3, 0x04},
{0x02C4, 0x01},
{0x02C5, 0x04},
{0x02C6, 0x3E},
{0x02C7, 0x04},
{0x02C8, 0x90},
{0x02C9, 0x01},
{0x02CA, 0x52},
{0x02CB, 0x04},
{0x02CC, 0x04},
{0x02CD, 0x04},
{0x02CE, 0xA9},
{0x02CF, 0x04},
{0x02D0, 0xAD},
{0x02D1, 0x01},
{0x0302, 0x00},
{0x0303, 0x00},
{0x0304, 0x00},
{0x02e0, 0x04}, // CCM by Alpha
{0x02F0, 0x4E}, // CCM A
{0x02F1, 0x04},
{0x02F2, 0xB1},
{0x02F3, 0x00},
{0x02F4, 0x63},
{0x02F5, 0x04},
{0x02F6, 0x28},
{0x02F7, 0x04},
{0x02F8, 0x29},
{0x02F9, 0x04},
{0x02FA, 0x51},
{0x02FB, 0x00},
{0x02FC, 0x64},
{0x02FD, 0x04},
{0x02FE, 0x6B},
{0x02FF, 0x04},
{0x0300, 0xCF},
{0x0301, 0x00},
{0x0305, 0x08},
{0x0306, 0x40},
{0x0307, 0x00},
{0x032D, 0x70},
{0x032E, 0x01},
{0x032F, 0x00},
{0x0330, 0x01},
{0x0331, 0x70},
{0x0332, 0x01},
{0x0333, 0x82}, // AWB channel offset
{0x0334, 0x82},
{0x0335, 0x86},
{0x0340, 0x30}, // AWB
{0x0341, 0x44},
{0x0342, 0x4A},
{0x0343, 0x3C}, // CT1
{0x0344, 0x83}, //
{0x0345, 0x4D}, // CT2
{0x0346, 0x75}, //
{0x0347, 0x56}, // CT3
{0x0348, 0x68}, //
{0x0349, 0x5E}, // CT4
{0x034A, 0x5C}, //
{0x034B, 0x65}, // CT5
{0x034C, 0x52}, //
{0x0350, 0x88},
{0x0352, 0x18},
{0x0354, 0x80},
{0x0355, 0x50},
{0x0356, 0x88},
{0x0357, 0xE0},
{0x0358, 0x00},
{0x035A, 0x00},
{0x035B, 0xAC},
{0x0360, 0x02},
{0x0361, 0x18},
{0x0362, 0x50},
{0x0363, 0x6C},
{0x0364, 0x00},
{0x0365, 0xF0},
{0x0366, 0x08},
{0x036A, 0x10},
{0x036B, 0x18},
{0x036E, 0x10},
{0x0370, 0x10},
{0x0371, 0x18},
{0x0372, 0x0C},
{0x0373, 0x38},
{0x0374, 0x3A},
{0x0375, 0x12},
{0x0376, 0x20},
{0x0380, 0xFF},
{0x0381, 0x44},
{0x0382, 0x34},
{0x038A, 0x80},
{0x038B, 0x0A},
{0x038C, 0xC1},
{0x038E, 0x3C},
{0x038F, 0x09},
{0x0390, 0xE0},
{0x0391, 0x01},
{0x0392, 0x03},
{0x0393, 0x80},
{0x0395, 0x22},
{0x0398, 0x02}, // AE Frame Control
{0x0399, 0xF0},
{0x039A, 0x03},
{0x039B, 0xAC},
{0x039C, 0x04},
{0x039D, 0x68},
{0x039E, 0x05},
{0x039F, 0xE0},
{0x03A0, 0x07},
{0x03A1, 0x58},
{0x03A2, 0x08},
{0x03A3, 0xD0},
{0x03A4, 0x0B},
{0x03A5, 0xC0},
{0x03A6, 0x18},
{0x03A7, 0x1C},
{0x03A8, 0x20},
{0x03A9, 0x24},
{0x03AA, 0x28},
{0x03AB, 0x30},
{0x03AC, 0x24},
{0x03AD, 0x21},
{0x03AE, 0x1C},
{0x03AF, 0x18},
{0x03B0, 0x17},
{0x03B1, 0x13},
{0x03B7, 0x64}, // AEAWB Windowing Step for 7x7
{0x03B8, 0x00},
{0x03B9, 0xB4},
{0x03BA, 0x00},
{0x03bb, 0xff}, // WinWeight 7x7
{0x03bc, 0xff},
{0x03bd, 0xff},
{0x03be, 0xff},
{0x03bf, 0xff},
{0x03c0, 0xff},
{0x03c1, 0x01},
{0x03e0, 0x04},
{0x03e1, 0x11},
{0x03e2, 0x01},
{0x03e3, 0x04},
{0x03e4, 0x10},
{0x03e5, 0x21},
{0x03e6, 0x11},
{0x03e7, 0x00},
{0x03e8, 0x11},
{0x03e9, 0x32},
{0x03ea, 0x12},
{0x03eb, 0x01},
{0x03ec, 0x21},
{0x03ed, 0x33},
{0x03ee, 0x23},
{0x03ef, 0x01},
{0x03f0, 0x11},
{0x03f1, 0x32},
{0x03f2, 0x12},
{0x03f3, 0x01},
{0x03f4, 0x10},
{0x03f5, 0x21},
{0x03f6, 0x11},
{0x03f7, 0x00},
{0x03f8, 0x04},
{0x03f9, 0x11},
{0x03fa, 0x01},
{0x03fb, 0x04},
{0x03DC, 0x47}, // SubWinAE setting, min_dark_cnt=7, exit_dark_cnt=4
{0x03DD, 0x5A}, // AEbl_th=70%
{0x03DE, 0x41}, // enable SubWinAE, bottom 3x5, DM_tol=12.5%
{0x03DF, 0x53}, // limit_ratio=65%
{0x0420, 0x82}, // Digital Gain offset
{0x0421, 0x00},
{0x0422, 0x00},
{0x0423, 0x88},
{0x0430, 0x08}, // ABLC
{0x0431, 0x30},
{0x0432, 0x0c},
{0x0433, 0x04}, // 0A
{0x0435, 0x08}, // 10
{0x0450, 0xFF}, // Alpha
{0x0451, 0xD0},
{0x0452, 0xB8},
{0x0453, 0x88},
{0x0454, 0x00},
{0x0458, 0x80},
{0x0459, 0x03},
{0x045A, 0x00},
{0x045B, 0x50},
{0x045C, 0x00},
{0x045D, 0x90},
{0x0465, 0x02},
{0x0466, 0x14},
{0x047A, 0x00}, // ELOFFNRB
{0x047B, 0x00}, // ELOFFNRY
{0x047C, 0x04},
{0x047D, 0x50},
{0x047E, 0x04},
{0x047F, 0x90},
{0x0480, 0x58},
{0x0481, 0x06},
{0x0482, 0x08}, // Sat outdoor
{0x04B0, 0x50}, // Contrast
{0x04B6, 0x30},
{0x04B9, 0x10},
{0x04B3, 0x00},
{0x04B1, 0x85},
{0x04B4, 0x00},
{0x0540, 0x00}, //
{0x0541, 0xBC}, // 60Hz Flicker
{0x0542, 0x00}, //
{0x0543, 0xE1}, // 50Hz Flicker
{0x0580, 0x04}, // BLUR WEIGHT
{0x0581, 0x0F}, // BLUR WEIGHT ALPHA0
{0x0582, 0x04}, // BLUR WEIGHT OUTDOOR
{0x05A1, 0x0A}, // SHARPNESS STRENGTH
{0x05A2, 0x21}, //[6:4] SHARP_ORE_NEG, [1:0] FIL SELECTION
{0x05A3, 0x84}, //[7]EA_EN, [6] EE_CENTER, [3:0] EDGEDIV
{0x05A4, 0x24}, // SHARP_ORE
{0x05A5, 0xFF}, // ORE_MAX
{0x05A6, 0x00}, // ORE_LOW
{0x05A7, 0x24}, // OUGB_HOT
{0x05A8, 0x24}, // OUGB_COLD
{0x05A9, 0x02}, // EDGE RANGE
{0x05B1, 0x24}, // EDGE SUB
{0x05B2, 0x0C}, //[3] Y_ADJC, [2]CMODE
{0x05B4, 0x1F}, // FIX WEIGHT
{0x05AE, 0x75}, // OAR START
{0x05AF, 0x78}, //[7:5]OAR STEP, [4:0]OAR DECSTR
{0x05B6, 0x00}, // SHARPNESS STRENGTH ALPHA0
{0x05B7, 0x10}, // SHARP_ORE ALPHA0
{0x05BF, 0x20}, // EDGESUB ALPHA0
{0x05C1, 0x06}, // SHARPNESS STRENGTH OUTDOOR
{0x05C2, 0x18}, // SHARP ORE OUTDOOR
{0x05C7, 0x00}, // TFBPI
{0x05CC, 0x04}, // Raw sharpness strength
{0x05CD, 0x00}, // Raw sharpness strength ALPHA0
{0x05CE, 0x03}, // Raw sharpness strength outdoor
{0x05E4, 0x08},
{0x05E5, 0x00},
{0x05E6, 0x07},
{0x05E7, 0x05},
{0x05E8, 0x08},
{0x05E9, 0x00},
{0x05EA, 0xD7},
{0x05EB, 0x02},
{0x0660, 0x00},
{0x0661, 0x16},
{0x0662, 0x07},
{0x0663, 0xf1},
{0x0664, 0x07},
{0x0665, 0xde},
{0x0666, 0x07},
{0x0667, 0xe7},
{0x0668, 0x00},
{0x0669, 0x35},
{0x066a, 0x07},
{0x066b, 0xf9},
{0x066c, 0x07},
{0x066d, 0xb7},
{0x066e, 0x00},
{0x066f, 0x27},
{0x0670, 0x07},
{0x0671, 0xf3},
{0x0672, 0x07},
{0x0673, 0xc5},
{0x0674, 0x07},
{0x0675, 0xee},
{0x0676, 0x00},
{0x0677, 0x16},
{0x0678, 0x01},
{0x0679, 0x80},
{0x067a, 0x00},
{0x067b, 0x85},
{0x067c, 0x07},
{0x067d, 0xe1},
{0x067e, 0x07},
{0x067f, 0xf5},
{0x0680, 0x07},
{0x0681, 0xb9},
{0x0682, 0x00},
{0x0683, 0x31},
{0x0684, 0x07},
{0x0685, 0xe6},
{0x0686, 0x07},
{0x0687, 0xd3},
{0x0688, 0x00},
{0x0689, 0x18},
{0x068a, 0x07},
{0x068b, 0xfa},
{0x068c, 0x07},
{0x068d, 0xd2},
{0x068e, 0x00},
{0x068f, 0x08},
{0x0690, 0x00},
{0x0691, 0x02},
{0xAFD0, 0x03}, // Auto Flicker detection off
{0xAFD3, 0x18},
{0xAFD4, 0x04},
{0xAFD5, 0xB8}, // auto flicker samp time 3188
{0xAFD6, 0x02},
{0xAFD7, 0x44},
{0xAFD8, 0x02},
// QVGA
{0x0006, 0x00},
{0x000D, 0x01},
{0x000E, 0x11},
{0x0122, 0xFB},
{0x0125, 0xFF},
{0x0126, 0x70},
{0x05E0, 0XBE},
{0x05E1, 0x00},
{0x05E2, 0xBE},
{0x05E3, 0x00},
{0x05E4, 0x3A},
{0x05E5, 0x00},
{0x05E6, 0x79},
{0x05E7, 0x01},
{0x05E8, 0x04},
{0x05E9, 0x00},
{0x05EA, 0xF3},
{0x05EB, 0x00},
{0x0000, 0x01},
{0x0100, 0x01},
{0x0101, 0x01},
{REGLIST_TAIL, 0x00}, // tail
};
static const DRAM_ATTR uint16_t sensor_framesize_HD[][2] = {
{0x0006, 0x00},
{0x000D, 0x00},
{0x000E, 0x00},
{0x0122, 0xFB},
{0x0125, 0xDF},
{0x0126, 0x70},
{0x05E4, 0x08},
{0x05E5, 0x00},
{0x05E6, 0x07},
{0x05E7, 0x05},
{0x05E8, 0x08},
{0x05E9, 0x00},
{0x05EA, 0xD7},
{0x05EB, 0x02},
{REGLIST_TAIL, 0x00},
};
static const DRAM_ATTR uint16_t sensor_framesize_SVGA[][2] = {
{0x0006, 0x10},
{0x000D, 0x00},
{0x000E, 0x00},
{0x0122, 0xEB},
{0x0125, 0xDF},
{0x0126, 0x70},
{0x05E4, 0x58},
{0x05E5, 0x00},
{0x05E6, 0x77},
{0x05E7, 0x03},
{0x05E8, 0x42},
{0x05E9, 0x00},
{0x05EA, 0x99},
{0x05EB, 0x02},
{REGLIST_TAIL, 0x00},
};
static const DRAM_ATTR uint16_t sensor_framesize_VGA[][2] = {
{0x0006, 0x10},
{0x000D, 0x00},
{0x000E, 0x00},
{0x0122, 0xEB},
{0x0125, 0xFF},
{0x0126, 0x70},
{0x05E0, 0XC1},
{0x05E1, 0x00},
{0x05E2, 0xC1},
{0x05E3, 0x00},
{0x05E4, 0x03},
{0x05E5, 0x00},
{0x05E6, 0x82},
{0x05E7, 0x02},
{0x05E8, 0x04},
{0x05E9, 0x00},
{0x05EA, 0xE3},
{0x05EB, 0x01},
{REGLIST_TAIL, 0x00},
};
static const DRAM_ATTR uint16_t sensor_framesize_CIF[][2] = {
{0x0006, 0x00},
{0x000D, 0x01},
{0x000E, 0x11},
{0x0122, 0xFB},
{0x0125, 0xFF},
{0x0126, 0x72},
{0x05E0, 0XC0},
{0x05E1, 0x00},
{0x05E2, 0x8F},
{0x05E3, 0x00},
{0x05E4, 0x11},
{0x05E5, 0x00},
{0x05E6, 0xA0},
{0x05E7, 0x01},
{0x05E8, 0x08},
{0x05E9, 0x00},
{0x05EA, 0x2F},
{0x05EB, 0x01},
{REGLIST_TAIL, 0x00},
};
static const DRAM_ATTR uint16_t sensor_framesize_QVGA[][2] = {
{0x0006, 0x00},
{0x000D, 0x01},
{0x000E, 0x11},
{0x0122, 0xFB},
{0x0125, 0xFF},
{0x0126, 0x70},
{0x05E0, 0XBE},
{0x05E1, 0x00},
{0x05E2, 0xBE},
{0x05E3, 0x00},
{0x05E4, 0x3A},
{0x05E5, 0x00},
{0x05E6, 0x79},
{0x05E7, 0x01},
{0x05E8, 0x04},
{0x05E9, 0x00},
{0x05EA, 0xF3},
{0x05EB, 0x00},
{REGLIST_TAIL, 0x00},
};
static const DRAM_ATTR uint16_t sensor_framesize_QCIF[][2] = {
{0x0006, 0x00},
{0x000D, 0x01},
{0x000E, 0x11},
{0x0122, 0xFB},
{0x0125, 0xFF},
{0x0126, 0x72},
{0x05E0, 0X52},
{0x05E1, 0x01},
{0x05E2, 0x38},
{0x05E3, 0x01},
{0x05E4, 0x22},
{0x05E5, 0x00},
{0x05E6, 0xD1},
{0x05E7, 0x00},
{0x05E8, 0x04},
{0x05E9, 0x00},
{0x05EA, 0x93},
{0x05EB, 0x00},
{REGLIST_TAIL, 0x00},
};
static const DRAM_ATTR uint16_t sensor_framesize_240X240[][2] = {
{0x0006, 0x00},
{0x000D, 0x01},
{0x000E, 0x11},
{0x0122, 0xFB},
{0x0125, 0xFF},
{0x0126, 0x70},
{0x05E0, 0XBE},
{0x05E1, 0x00},
{0x05E2, 0xBE},
{0x05E3, 0x00},
{0x05E4, 0x62},
{0x05E5, 0x00},
{0x05E6, 0x51},
{0x05E7, 0x01},
{0x05E8, 0x04},
{0x05E9, 0x00},
{0x05EA, 0xF3},
{0x05EB, 0x00},
{REGLIST_TAIL, 0x00},
};
static const DRAM_ATTR uint16_t sensor_framesize_QQVGA[][2] = {
{0x0006, 0x00},
{0x000D, 0x01},
{0x000E, 0x11},
{0x0122, 0xFB},
{0x0125, 0xFF},
{0x0126, 0x70},
{0x05E0, 0X78},
{0x05E1, 0x01},
{0x05E2, 0x78},
{0x05E3, 0x01},
{0x05E4, 0x1E},
{0x05E5, 0x00},
{0x05E6, 0xBD},
{0x05E7, 0x00},
{0x05E8, 0x03},
{0x05E9, 0x00},
{0x05EA, 0x7A},
{0x05EB, 0x00},
{REGLIST_TAIL, 0x00},
};
@@ -0,0 +1,31 @@
/*
*
* MEGA_CCM driver.
*
*/
#ifndef __MEGA_CCM_H__
#define __MEGA_CCM_H__
#include "sensor.h"
/**
* @brief Detect sensor pid
*
* @param slv_addr SCCB address
* @param id Detection result
* @return
* 0: Can't detect this sensor
* Nonzero: This sensor has been detected
*/
int esp32_camera_mega_ccm_detect(int slv_addr, sensor_id_t *id);
/**
* @brief initialize sensor function pointers
*
* @param sensor pointer of sensor
* @return
* Always 0
*/
int esp32_camera_mega_ccm_init(sensor_t *sensor);
#endif // __MEGA_CCM_H__
@@ -0,0 +1,43 @@
/*
* MEGA_CCM register definitions.
*/
#ifndef __MEGA_CCM_REG_REGS_H__
#define __MEGA_CCM_REG_REGS_H__
#define ID_BASE 0x0000
#define SENSOR_BASE 0x0100
#define SYS_CLK_BASE 0x0200
#define BYPASS_BASE 0XFFF0
#define SENSOR_ID_HIGH ID_BASE | 0x00
#define SENSOR_ID_LOW ID_BASE | 0x01
#define FIRMWARE_VER ID_BASE | 0x02
#define CAMERA_RST_REG SENSOR_BASE|0x02
#define PIXEL_FMT_REG SENSOR_BASE|0x20
#define RESOLUTION_REG SENSOR_BASE|0x21
#define BRIGHTNESS_REG SENSOR_BASE|0x22
#define CONTRAST_REG SENSOR_BASE|0x23
#define SATURATION_REG SENSOR_BASE|0x24
#define EXP_COMPENSATE_REG SENSOR_BASE|0x25
#define AWB_MODE_REG SENSOR_BASE|0x26
#define SPECIAL_REG SENSOR_BASE|0x27
#define SHARPNESS_REG SENSOR_BASE|0x28
#define FOCUS_REG SENSOR_BASE|0x29
#define IMAGE_QUALITY_REG SENSOR_BASE|0x2A
#define IMAGE_FLIP_REG SENSOR_BASE|0x2B
#define IMAGE_MIRROR_REG SENSOR_BASE|0x2C
#define AGC_MODE_REG SENSOR_BASE|0x30
#define MANUAL_AGC_REG SENSOR_BASE|0x31
#define MANUAL_EXP_H_REG SENSOR_BASE|0x33
#define MANUAL_EXP_L_REG SENSOR_BASE|0x34
#define SYSTEM_CLK_DIV_REG SYS_CLK_BASE|0x00
#define SYSTEM_PLL_DIV_REG SYS_CLK_BASE|0x01
#endif //__MEGA_CCM_REG_REGS_H__
@@ -0,0 +1,19 @@
#ifndef _GC032A_SETTINGS_H_
#define _GC032A_SETTINGS_H_
#include <stdint.h>
#include <stdbool.h>
#include "esp_attr.h"
#include "mega_ccm_regs.h"
#define REG_DLY 0xffff
#define REGLIST_TAIL 0x0000
static const uint16_t mega_ccm_default_regs[][2] = {
{0x0120, 0x01 }, // JPEG
{0x0121, 0x01 }, // 320X240
{REGLIST_TAIL, 0x00},
};
#endif
@@ -0,0 +1,34 @@
/*
* This file is part of the OpenMV project.
* Copyright (c) 2013/2014 Ibrahim Abdelkader <i.abdalkader@gmail.com>
* This work is licensed under the MIT license, see the file LICENSE for details.
*
* NT99141 driver.
*
*/
#ifndef __NT99141_H__
#define __NT99141_H__
#include "sensor.h"
/**
* @brief Detect sensor pid
*
* @param slv_addr SCCB address
* @param id Detection result
* @return
* 0: Can't detect this sensor
* Nonzero: This sensor has been detected
*/
int esp32_camera_nt99141_detect(int slv_addr, sensor_id_t *id);
/**
* @brief initialize sensor function pointers
*
* @param sensor pointer of sensor
* @return
* Always 0
*/
int esp32_camera_nt99141_init(sensor_t *sensor);
#endif // __NT99141_H__
@@ -0,0 +1,211 @@
/*
* NT99141 register definitions.
*/
#ifndef __NT99141_REG_REGS_H__
#define __NT99141_REG_REGS_H__
/* system control registers */
#define SYSTEM_CTROL0 0x3021 // Bit[7]: Software reset
// Bit[6]: Software power down
// Bit[5]: Reserved
// Bit[4]: SRB clock SYNC enable
// Bit[3]: Isolation suspend select
// Bit[2:0]: Not used
/* output format control registers */
#define FORMAT_CTRL 0x501F // Format select
// Bit[2:0]:
// 000: YUV422
// 001: RGB
// 010: Dither
// 011: RAW after DPC
// 101: RAW after CIP
/* format control registers */
#define FORMAT_CTRL00 0x4300
/* frame control registers */
#define FRAME_CTRL01 0x4201 // Control Passed Frame Number When both ON and OFF number set to 0x00,frame control is in bypass mode
// Bit[7:4]: Not used
// Bit[3:0]: Frame ON number
#define FRAME_CTRL02 0x4202 // Control Masked Frame Number When both ON and OFF number set to 0x00,frame control is in bypass mode
// Bit[7:4]: Not used
// BIT[3:0]: Frame OFF number
/* ISP top control registers */
#define PRE_ISP_TEST_SETTING_1 0x3025 // Bit[7]: Test enable
// 0: Test disable
// 1: Color bar enable
// Bit[6]: Rolling
// Bit[5]: Transparent
// Bit[4]: Square black and white
// Bit[3:2]: Color bar style
// 00: Standard 8 color bar
// 01: Gradual change at vertical mode 1
// 10: Gradual change at horizontal
// 11: Gradual change at vertical mode 2
// Bit[1:0]: Test select
// 00: Color bar
// 01: Random data
// 10: Square data
// 11: Black image
//exposure = {0x3500[3:0], 0x3501[7:0], 0x3502[7:0]} / 16 × tROW
/* AEC/AGC control functions */
#define AEC_PK_MANUAL 0x3201 // AEC Manual Mode Control
// Bit[7:6]: Reserved
// Bit[5]: Gain delay option
// Valid when 0x3503[4]=1’b0
// 0: Delay one frame latch
// 1: One frame latch
// Bit[4:2]: Reserved
// Bit[1]: AGC manual
// 0: Auto enable
// 1: Manual enable
// Bit[0]: AEC manual
// 0: Auto enable
// 1: Manual enable
//gain = {0x350A[1:0], 0x350B[7:0]} / 16
/* mirror and flip registers */
#define TIMING_TC_REG20 0x3022 // Timing Control Register
// Bit[2:1]: Vertical flip enable
// 00: Normal
// 11: Vertical flip
// Bit[0]: Vertical binning enable
#define TIMING_TC_REG21 0x3022 // Timing Control Register
// Bit[5]: Compression Enable
// Bit[2:1]: Horizontal mirror enable
// 00: Normal
// 11: Horizontal mirror
// Bit[0]: Horizontal binning enable
#define CLOCK_POL_CONTROL 0x3024// Bit[5]: PCLK polarity 0: active low
// 1: active high
// Bit[3]: Gate PCLK under VSYNC
// Bit[2]: Gate PCLK under HREF
// Bit[1]: HREF polarity
// 0: active low
// 1: active high
// Bit[0] VSYNC polarity
// 0: active low
// 1: active high
#define DRIVE_CAPABILITY 0x306a // Bit[7:6]:
// 00: 1x
// 01: 2x
// 10: 3x
// 11: 4x
#define X_ADDR_ST_H 0x3800 //Bit[3:0]: X address start[11:8]
#define X_ADDR_ST_L 0x3801 //Bit[7:0]: X address start[7:0]
#define Y_ADDR_ST_H 0x3802 //Bit[2:0]: Y address start[10:8]
#define Y_ADDR_ST_L 0x3803 //Bit[7:0]: Y address start[7:0]
#define X_ADDR_END_H 0x3804 //Bit[3:0]: X address end[11:8]
#define X_ADDR_END_L 0x3805 //Bit[7:0]:
#define Y_ADDR_END_H 0x3806 //Bit[2:0]: Y address end[10:8]
#define Y_ADDR_END_L 0x3807 //Bit[7:0]:
// Size after scaling
#define X_OUTPUT_SIZE_H 0x3808 //Bit[3:0]: DVP output horizontal width[11:8]
#define X_OUTPUT_SIZE_L 0x3809 //Bit[7:0]:
#define Y_OUTPUT_SIZE_H 0x380a //Bit[2:0]: DVP output vertical height[10:8]
#define Y_OUTPUT_SIZE_L 0x380b //Bit[7:0]:
#define X_TOTAL_SIZE_H 0x380c //Bit[3:0]: Total horizontal size[11:8]
#define X_TOTAL_SIZE_L 0x380d //Bit[7:0]:
#define Y_TOTAL_SIZE_H 0x380e //Bit[7:0]: Total vertical size[15:8]
#define Y_TOTAL_SIZE_L 0x380f //Bit[7:0]:
#define X_OFFSET_H 0x3810 //Bit[3:0]: ISP horizontal offset[11:8]
#define X_OFFSET_L 0x3811 //Bit[7:0]:
#define Y_OFFSET_H 0x3812 //Bit[2:0]: ISP vertical offset[10:8]
#define Y_OFFSET_L 0x3813 //Bit[7:0]:
#define X_INCREMENT 0x3814 //Bit[7:4]: Horizontal odd subsample increment
//Bit[3:0]: Horizontal even subsample increment
#define Y_INCREMENT 0x3815 //Bit[7:4]: Vertical odd subsample increment
//Bit[3:0]: Vertical even subsample increment
// Size before scaling
//#define X_INPUT_SIZE (X_ADDR_END - X_ADDR_ST + 1 - (2 * X_OFFSET))
//#define Y_INPUT_SIZE (Y_ADDR_END - Y_ADDR_ST + 1 - (2 * Y_OFFSET))
#define ISP_CONTROL_01 0x3021 // Bit[5]: Scale enable
// 0: Disable
// 1: Enable
#define SCALE_CTRL_1 0x5601 // Bit[6:4]: HDIV RW
// DCW scale times
// 000: DCW 1 time
// 001: DCW 2 times
// 010: DCW 4 times
// 100: DCW 8 times
// 101: DCW 16 times
// Others: DCW 16 times
// Bit[2:0]: VDIV RW
// DCW scale times
// 000: DCW 1 time
// 001: DCW 2 times
// 010: DCW 4 times
// 100: DCW 8 times
// 101: DCW 16 times
// Others: DCW 16 times
#define SCALE_CTRL_2 0x5602 // X_SCALE High Bits
#define SCALE_CTRL_3 0x5603 // X_SCALE Low Bits
#define SCALE_CTRL_4 0x5604 // Y_SCALE High Bits
#define SCALE_CTRL_5 0x5605 // Y_SCALE Low Bits
#define SCALE_CTRL_6 0x5606 // Bit[3:0]: V Offset
#define PCLK_RATIO 0x3824 // Bit[4:0]: PCLK ratio manual
#define VFIFO_CTRL0C 0x460C // Bit[1]: PCLK manual enable
// 0: Auto
// 1: Manual by PCLK_RATIO
#define VFIFO_X_SIZE_H 0x4602
#define VFIFO_X_SIZE_L 0x4603
#define VFIFO_Y_SIZE_H 0x4604
#define VFIFO_Y_SIZE_L 0x4605
#define SC_PLLS_CTRL0 0x303a // Bit[7]: PLLS bypass
#define SC_PLLS_CTRL1 0x303b // Bit[4:0]: PLLS multiplier
#define SC_PLLS_CTRL2 0x303c // Bit[6:4]: PLLS charge pump control
// Bit[3:0]: PLLS system divider
#define SC_PLLS_CTRL3 0x303d // Bit[5:4]: PLLS pre-divider
// 00: 1
// 01: 1.5
// 10: 2
// 11: 3
// Bit[2]: PLLS root-divider - 1
// Bit[1:0]: PLLS seld5
// 00: 1
// 01: 1
// 10: 2
// 11: 2.5
#define COMPRESSION_CTRL00 0x4400 //
#define COMPRESSION_CTRL01 0x4401 //
#define COMPRESSION_CTRL02 0x4402 //
#define COMPRESSION_CTRL03 0x4403 //
#define COMPRESSION_CTRL04 0x4404 //
#define COMPRESSION_CTRL05 0x4405 //
#define COMPRESSION_CTRL06 0x4406 //
#define COMPRESSION_CTRL07 0x3401 // Bit[5:0]: QS
#define COMPRESSION_ISI_CTRL 0x4408 //
#define COMPRESSION_CTRL09 0x4409 //
#define COMPRESSION_CTRL0a 0x440a //
#define COMPRESSION_CTRL0b 0x440b //
#define COMPRESSION_CTRL0c 0x440c //
#define COMPRESSION_CTRL0d 0x440d //
#define COMPRESSION_CTRL0E 0x440e //
/**
* @brief register value
*/
#define TEST_COLOR_BAR 0x02 /* Enable Color Bar roling Test */
#define AEC_PK_MANUAL_AGC_MANUALEN 0x02 /* Enable AGC Manual enable */
#define AEC_PK_MANUAL_AEC_MANUALEN 0x01 /* Enable AEC Manual enable */
#define TIMING_TC_REG20_VFLIP 0x01 /* Vertical flip enable */
#define TIMING_TC_REG21_HMIRROR 0x02 /* Horizontal mirror enable */
#endif // __NT99141_REG_REGS_H__
@@ -0,0 +1,825 @@
#ifndef _NT99141_SETTINGS_H_
#define _NT99141_SETTINGS_H_
#include <stdint.h>
#include <stdbool.h>
#include "esp_attr.h"
#include "nt99141_regs.h"
static const ratio_settings_t ratio_table[] = {
// mw, mh, sx, sy, ex, ey, ox, oy, tx, ty
{ 1280, 720, 0, 4, 1283, 723, 0, 4, 1660, 963 },
};
#define REG_DLY 0xffff
#define REGLIST_TAIL 0x0000
static const DRAM_ATTR uint16_t sensor_default_regs[][2] = {
//initial
{0x3021, 0x00},
{REG_DLY, 100}, // delay 100ms
{0x3109, 0x04},
{0x3040, 0x04},
{0x3041, 0x02},
{0x3042, 0xFF},
{0x3043, 0x08},
{0x3052, 0xE0},
{0x305F, 0x33},
{0x3100, 0x07},
{0x3106, 0x03},
{0x3105, 0x01},
{0x3108, 0x05},
{0x3110, 0x22},
{0x3111, 0x57},
{0x3112, 0x22},
{0x3113, 0x55},
{0x3114, 0x05},
{0x3135, 0x00},
{0x32F0, 0x01},
{0x3290, 0x01},
{0x3291, 0x80},
{0x3296, 0x01},
{0x3297, 0x73},
{0x3250, 0x80},
{0x3251, 0x03},
{0x3252, 0xFF},
{0x3253, 0x00},
{0x3254, 0x03},
{0x3255, 0xFF},
{0x3256, 0x00},
{0x3257, 0x50},
{0x3270, 0x00},
{0x3271, 0x0C},
{0x3272, 0x18},
{0x3273, 0x32},
{0x3274, 0x44},
{0x3275, 0x54},
{0x3276, 0x70},
{0x3277, 0x88},
{0x3278, 0x9D},
{0x3279, 0xB0},
{0x327A, 0xCF},
{0x327B, 0xE2},
{0x327C, 0xEF},
{0x327D, 0xF7},
{0x327E, 0xFF},
{0x3302, 0x00},
{0x3303, 0x40},
{0x3304, 0x00},
{0x3305, 0x96},
{0x3306, 0x00},
{0x3307, 0x29},
{0x3308, 0x07},
{0x3309, 0xBA},
{0x330A, 0x06},
{0x330B, 0xF5},
{0x330C, 0x01},
{0x330D, 0x51},
{0x330E, 0x01},
{0x330F, 0x30},
{0x3310, 0x07},
{0x3311, 0x16},
{0x3312, 0x07},
{0x3313, 0xBA},
{0x3326, 0x02},
{0x32F6, 0x0F},
{0x32F9, 0x42},
{0x32FA, 0x24},
{0x3325, 0x4A},
{0x3330, 0x00},
{0x3331, 0x0A},
{0x3332, 0xFF},
{0x3338, 0x30},
{0x3339, 0x84},
{0x333A, 0x48},
{0x333F, 0x07},
{0x3360, 0x10},
{0x3361, 0x18},
{0x3362, 0x1f},
{0x3363, 0x37},
{0x3364, 0x80},
{0x3365, 0x80},
{0x3366, 0x68},
{0x3367, 0x60},
{0x3368, 0x30},
{0x3369, 0x28},
{0x336A, 0x20},
{0x336B, 0x10},
{0x336C, 0x00},
{0x336D, 0x20},
{0x336E, 0x1C},
{0x336F, 0x18},
{0x3370, 0x10},
{0x3371, 0x38},
{0x3372, 0x3C},
{0x3373, 0x3F},
{0x3374, 0x3F},
{0x338A, 0x34},
{0x338B, 0x7F},
{0x338C, 0x10},
{0x338D, 0x23},
{0x338E, 0x7F},
{0x338F, 0x14},
{0x3375, 0x08},
{0x3376, 0x0C},
{0x3377, 0x18},
{0x3378, 0x20},
{0x3012, 0x02},
{0x3013, 0xD0},
{0x3025, 0x02}, //colorbar
{REGLIST_TAIL, 0x00}, // tail
};
static const DRAM_ATTR uint16_t sensor_fmt_jpeg[][2] = {
{0x32F0, 0x70}, // YUV422
{REGLIST_TAIL, 0x00}, // tail
};
static const DRAM_ATTR uint16_t sensor_fmt_raw[][2] = {
{0x32F0, 0x50}, // RAW
{REGLIST_TAIL, 0x00}, // tail
};
static const DRAM_ATTR uint16_t sensor_fmt_grayscale[][2] = {
{0x32F1, 0x01},
{REGLIST_TAIL, 0x00}, // tail
};
static const DRAM_ATTR uint16_t sensor_fmt_yuv422[][2] = {
{0x32F0, 0x00}, // YUV422
{REGLIST_TAIL, 0x00}, // tail
};
static const DRAM_ATTR uint16_t sensor_fmt_rgb565[][2] = {
{0x32F0, 0x01}, // RGB
{REGLIST_TAIL, 0x00}, // tail
};
static const DRAM_ATTR uint8_t sensor_saturation_levels[9][1] = {
{0x60},//-4
{0x68},//-3
{0x70},//-2
{0x78},//-1
{0x80},//0
{0x88},//+1
{0x90},//+2
{0x98},//+3
{0xA0},//+4
};
static const DRAM_ATTR uint8_t sensor_special_effects[7][4] = {
{0x00, 0x80, 0x80, 0x01},//Normal
{0x03, 0x80, 0x80, 0x01},//Negative
{0x01, 0x80, 0x80, 0x01},//Grayscale
{0x05, 0x2A, 0xF0, 0x01},//Red Tint
{0x05, 0x60, 0x20, 0x01},//Green Tint
{0x05, 0xF0, 0x80, 0x01},//Blue Tint
{0x02, 0x80, 0x80, 0x01},//Sepia
};
// AE LEVEL
static const DRAM_ATTR uint16_t sensor_ae_level[][2] = {
// 1. [AE_Target : 0x24]
// Set_Device_Format = FORMAT_16_8
// SET_Device_Addr = 0x54
{0x32B8, 0x29 },
{0x32B9, 0x1F },
{0x32BC, 0x24 },
{0x32BD, 0x27 },
{0x32BE, 0x21 },
//------------------------------------------------------------------------
// 2. [AE_Target : 0x28]
// Set_Device_Format = FORMAT_16_8
// SET_Device_Addr = 0x54
{0x32B8, 0x2D },
{0x32B9, 0x23 },
{0x32BC, 0x28 },
{0x32BD, 0x2B },
{0x32BE, 0x25 },
//------------------------------------------------------------------------
// 3. [AE_Target : 0x2C]
// Set_Device_Format = FORMAT_16_8
// SET_Device_Addr = 0x54
{0x32B8, 0x32 },
{0x32B9, 0x26 },
{0x32BC, 0x2C },
{0x32BD, 0x2F },
{0x32BE, 0x29 },
//------------------------------------------------------------------------
// 4, [AE_Target : 0x30]
// Set_Device_Format = FORMAT_16_8
// SET_Device_Addr = 0x54
{0x32B8, 0x36 },
{0x32B9, 0x2A },
{0x32BC, 0x30 },
{0x32BD, 0x33 },
{0x32BE, 0x2D },
//------------------------------------------------------------------------
// 5. [AE_Target : 0x34]
// Set_Device_Format = FORMAT_16_8
// SET_Device_Addr = 0x54
{0x32B8, 0x3B },
{0x32B9, 0x2D },
{0x32BC, 0x34 },
{0x32BD, 0x38 },
{0x32BE, 0x30 },
//------------------------------------------------------------------------
// 6. [AE_Target : 0x38]
// Set_Device_Format = FORMAT_16_8
// SET_Device_Addr = 0x54
{0x32B8, 0x3F },
{0x32B9, 0x31 },
{0x32BC, 0x38 },
{0x32BD, 0x3C },
{0x32BE, 0x34 },
//------------------------------------------------------------------------
// 7. [AE_Target : 0x3D]
// Set_Device_Format = FORMAT_16_8
// SET_Device_Addr = 0x54
{0x32B8, 0x44 },
{0x32B9, 0x34 },
{0x32BC, 0x3C },
{0x32BD, 0x40 },
{0x32BE, 0x38 },
//------------------------------------------------------------------------
// 8. [AE_Target : 0x40]
// Set_Device_Format = FORMAT_16_8
// SET_Device_Addr = 0x54
{0x32B8, 0x48 },
{0x32B9, 0x38 },
{0x32BC, 0x40 },
{0x32BD, 0x44 },
{0x32BE, 0x3C },
//------------------------------------------------------------------------
// 9. [AE_Target : 0x44]
// Set_Device_Format = FORMAT_16_8
// SET_Device_Addr = 0x54
{0x32B8, 0x4D },
{0x32B9, 0x3B },
{0x32BC, 0x44 },
{0x32BD, 0x49 },
{0x32BE, 0x3F },
};
static const DRAM_ATTR uint16_t sensor_framesize_HD[][2] = {
//[JPEG_1280x720_8.18_8.18_Fps]
{0x3021, 0x00},
{REG_DLY, 100}, // delay 100ms
{0x32BF, 0x60},
{0x32C0, 0x5A},
{0x32C1, 0x5A},
{0x32C2, 0x5A},
{0x32C3, 0x00},
{0x32C4, 0x20},
{0x32C5, 0x20},
{0x32C6, 0x20},
{0x32C7, 0x00},
{0x32C8, 0x3C},
{0x32C9, 0x5A},
{0x32CA, 0x7A},
{0x32CB, 0x7A},
{0x32CC, 0x7A},
{0x32CD, 0x7A},
{0x32DB, 0x5E},
{0x32F0, 0x70},
{0x3400, 0x08},
{0x3400, 0x00},
{0x3401, 0x4E},
{0x3404, 0x00},
{0x3405, 0x00},
{0x3410, 0x00},
{0x3200, 0x3E},
{0x3201, 0x0F},
{0x3028, 0x0F},
{0x3029, 0x00},
{0x302A, 0x08},
{0x3022, 0x24},
{0x3023, 0x24},
{0x3002, 0x00},
{0x3003, 0x04},
{0x3004, 0x00},
{0x3005, 0x04},
{0x3006, 0x05},
{0x3007, 0x03},
{0x3008, 0x02},
{0x3009, 0xD3},
{0x300A, 0x06},
{0x300B, 0x7C},
{0x300C, 0x02},
{0x300D, 0xE0},
{0x300E, 0x05},
{0x300F, 0x00},
{0x3010, 0x02},
{0x3011, 0xD0},
{0x32B8, 0x3F},
{0x32B9, 0x31},
{0x32BB, 0x87},
{0x32BC, 0x38},
{0x32BD, 0x3C},
{0x32BE, 0x34},
{0x3201, 0x3F},
{0x3021, 0x06},
{0x3025, 0x00}, //normal
{0x3400, 0x01},
{0x3060, 0x01},
{REGLIST_TAIL, 0x00}, // tail
};
static const DRAM_ATTR uint16_t sensor_framesize_VGA[][2] = {
//[JPEG_640x480_10.14_10.14_Fps]
{0x3021, 0x00},
{REG_DLY, 100}, // delay 100ms
{0x32BF, 0x60},
{0x32C0, 0x5A},
{0x32C1, 0x5A},
{0x32C2, 0x5A},
{0x32C3, 0x00},
{0x32C4, 0x20},
{0x32C5, 0x20},
{0x32C6, 0x20},
{0x32C7, 0x00},
{0x32C8, 0x4B},
{0x32C9, 0x5A},
{0x32CA, 0x7A},
{0x32CB, 0x7A},
{0x32CC, 0x7A},
{0x32CD, 0x7A},
{0x32DB, 0x62},
{0x32F0, 0x70},
{0x3400, 0x08},
{0x3400, 0x00},
{0x3401, 0x4E},
{0x3404, 0x00},
{0x3405, 0x00},
{0x3410, 0x00},
{0x32E0, 0x02},
{0x32E1, 0x80},
{0x32E2, 0x01},
{0x32E3, 0xE0},
{0x32E4, 0x00},
{0x32E5, 0x80},
{0x32E6, 0x00},
{0x32E7, 0x80},
{0x3200, 0x3E},
{0x3201, 0x0F},
{0x3028, 0x0F},
{0x3029, 0x00},
{0x302A, 0x08},
{0x3022, 0x24},
{0x3023, 0x24},
{0x3002, 0x00},
{0x3003, 0xA4},
{0x3004, 0x00},
{0x3005, 0x04},
{0x3006, 0x04},
{0x3007, 0x63},
{0x3008, 0x02},
{0x3009, 0xD3},
{0x300A, 0x05},
{0x300B, 0x3C},
{0x300C, 0x02},
{0x300D, 0xE0},
{0x300E, 0x03},
{0x300F, 0xC0},
{0x3010, 0x02},
{0x3011, 0xD0},
{0x32B8, 0x3F},
{0x32B9, 0x31},
{0x32BB, 0x87},
{0x32BC, 0x38},
{0x32BD, 0x3C},
{0x32BE, 0x34},
{0x3201, 0x7F},
{0x3021, 0x06},
{0x3025, 0x00}, //normal
{0x3400, 0x01},
{0x3060, 0x01},
{REGLIST_TAIL, 0x00}, // tail
};
static const DRAM_ATTR uint16_t sensor_framesize_QVGA[][2] = {
//[JPEG_320x240_10.14_10.14_Fps]
{0x3021, 0x00},
{REG_DLY, 100}, // delay 100ms
{0x32BF, 0x60},
{0x32C0, 0x5A},
{0x32C1, 0x5A},
{0x32C2, 0x5A},
{0x32C3, 0x00},
{0x32C4, 0x20},
{0x32C5, 0x20},
{0x32C6, 0x20},
{0x32C7, 0x00},
{0x32C8, 0x4B},
{0x32C9, 0x5A},
{0x32CA, 0x7A},
{0x32CB, 0x7A},
{0x32CC, 0x7A},
{0x32CD, 0x7A},
{0x32DB, 0x62},
{0x32F0, 0x70},
{0x3400, 0x08},
{0x3400, 0x00},
{0x3401, 0x4E},
{0x3404, 0x00},
{0x3405, 0x00},
{0x3410, 0x00},
{0x32E0, 0x01},
{0x32E1, 0x40},
{0x32E2, 0x00},
{0x32E3, 0xF0},
{0x32E4, 0x02},
{0x32E5, 0x02},
{0x32E6, 0x02},
{0x32E7, 0x03},
{0x3200, 0x3E},
{0x3201, 0x0F},
{0x3028, 0x0F},
{0x3029, 0x00},
{0x302A, 0x08},
{0x3022, 0x24},
{0x3023, 0x24},
{0x3002, 0x00},
{0x3003, 0xA4},
{0x3004, 0x00},
{0x3005, 0x04},
{0x3006, 0x04},
{0x3007, 0x63},
{0x3008, 0x02},
{0x3009, 0xD3},
{0x300A, 0x05},
{0x300B, 0x3C},
{0x300C, 0x02},
{0x300D, 0xE0},
{0x300E, 0x03},
{0x300F, 0xC0},
{0x3010, 0x02},
{0x3011, 0xD0},
{0x32B8, 0x3F},
{0x32B9, 0x31},
{0x32BB, 0x87},
{0x32BC, 0x38},
{0x32BD, 0x3C},
{0x32BE, 0x34},
{0x3201, 0x7F},
{0x3021, 0x06},
{0x3025, 0x00}, //normal
{0x3400, 0x01},
{0x3060, 0x01},
{REGLIST_TAIL, 0x00}, // tail
};
static const DRAM_ATTR uint16_t sensor_framesize_VGA_xyskip[][2] = {
// [JPEG_640x360_20.00_25.01_Fps_XY_Skip]
// Set_Device_Format = FORMAT_16_8
// SET_Device_Addr = 0x54
{0x3021, 0x00},
{REG_DLY, 100}, // delay 100ms
{0x32BF, 0x60 },
{0x320A, 0xB2 },
{0x32C0, 0x64 },
{0x32C1, 0x64 },
{0x32C2, 0x64 },
{0x32C3, 0x00 },
{0x32C4, 0x20 },
{0x32C5, 0x20 },
{0x32C6, 0x20 },
{0x32C7, 0x00 },
{0x32C8, 0x62 },
{0x32C9, 0x64 },
{0x32CA, 0x84 },
{0x32CB, 0x84 },
{0x32CC, 0x84 },
{0x32CD, 0x84 },
{0x32DB, 0x68 },
{0x32F0, 0x70 },
{0x3400, 0x08 },
{0x3400, 0x00 },
{0x3401, 0x4E },
{0x3404, 0x00 },
{0x3405, 0x00 },
{0x3410, 0x00 },
{0x3200, 0x3E },
{0x3201, 0x0F },
{0x3028, 0x0F },
{0x3029, 0x00 },
{0x302A, 0x08 },
{0x3022, 0x24 },
{0x3023, 0x6C },
{0x3002, 0x00 },
{0x3003, 0x04 },
{0x3004, 0x00 },
{0x3005, 0x04 },
{0x3006, 0x05 },
{0x3007, 0x03 },
{0x3008, 0x02 },
{0x3009, 0xD3 },
{0x300A, 0x03 },
{0x300B, 0xFC },
{0x300C, 0x01 },
{0x300D, 0x88 },
{0x300E, 0x02 },
{0x300F, 0x80 },
{0x3010, 0x01 },
{0x3011, 0x68 },
{0x32B8, 0x3F },
{0x32B9, 0x31 },
{0x32BB, 0x87 },
{0x32BC, 0x38 },
{0x32BD, 0x3C },
{0x32BE, 0x34 },
{0x3201, 0x3F },
{0x3025, 0x00 }, //normal
{0x3021, 0x06 },
{0x3400, 0x01 },
{0x3060, 0x01 },
{REGLIST_TAIL, 0x00}, // tail
};
static const DRAM_ATTR uint16_t sensor_framesize_VGA_xskip[][2] = {
//[JPEG_640x480_Xskip_13.32_13.32_Fps]
{0x3021, 0x00},
{REG_DLY, 100}, // delay 100ms
{0x32BF, 0x60},
{0x32C0, 0x5A},
{0x32C1, 0x5A},
{0x32C2, 0x5A},
{0x32C3, 0x00},
{0x32C4, 0x20},
{0x32C5, 0x20},
{0x32C6, 0x20},
{0x32C7, 0x00},
{0x32C8, 0x62},
{0x32C9, 0x5A},
{0x32CA, 0x7A},
{0x32CB, 0x7A},
{0x32CC, 0x7A},
{0x32CD, 0x7A},
{0x32DB, 0x68},
{0x32F0, 0x70},
{0x3400, 0x08},
{0x3400, 0x00},
{0x3401, 0x4E},
{0x3404, 0x00},
{0x3405, 0x00},
{0x3410, 0x00},
{0x32E0, 0x02},
{0x32E1, 0x80},
{0x32E2, 0x01},
{0x32E3, 0xE0},
{0x32E4, 0x00},
{0x32E5, 0x00},
{0x32E6, 0x00},
{0x32E7, 0x80},
{0x3200, 0x3E},
{0x3201, 0x0F},
{0x3028, 0x0F},
{0x3029, 0x00},
{0x302A, 0x08},
{0x3022, 0x24},
{0x3023, 0x2C},
{0x3002, 0x00},
{0x3003, 0x04},
{0x3004, 0x00},
{0x3005, 0x04},
{0x3006, 0x05},
{0x3007, 0x03},
{0x3008, 0x02},
{0x3009, 0xD3},
{0x300A, 0x03},
{0x300B, 0xFC},
{0x300C, 0x02},
{0x300D, 0xE0},
{0x300E, 0x02},
{0x300F, 0x80},
{0x3010, 0x02},
{0x3011, 0xD0},
{0x32B8, 0x3F},
{0x32B9, 0x31},
{0x32BB, 0x87},
{0x32BC, 0x38},
{0x32BD, 0x3C},
{0x32BE, 0x34},
{0x3201, 0x7F},
{0x3021, 0x06},
{0x3025, 0x00}, //normal
{0x3400, 0x01},
{0x3060, 0x01},
{REGLIST_TAIL, 0x00}, // tail
};
static const DRAM_ATTR uint16_t sensor_framesize_QVGA_xskip[][2] = {
{0x3021, 0x00},
{REG_DLY, 100}, // delay 100ms
//[JPEG_320x240_Xskip_13.32_13.32_Fps]
{0x32BF, 0x60},
{0x32C0, 0x5A},
{0x32C1, 0x5A},
{0x32C2, 0x5A},
{0x32C3, 0x00},
{0x32C4, 0x20},
{0x32C5, 0x20},
{0x32C6, 0x20},
{0x32C7, 0x00},
{0x32C8, 0x62},
{0x32C9, 0x5A},
{0x32CA, 0x7A},
{0x32CB, 0x7A},
{0x32CC, 0x7A},
{0x32CD, 0x7A},
{0x32DB, 0x68},
{0x32F0, 0x70},
{0x3400, 0x08},
{0x3400, 0x00},
{0x3401, 0x4E},
{0x3404, 0x00},
{0x3405, 0x00},
{0x3410, 0x00},
{0x32E0, 0x01},
{0x32E1, 0x40},
{0x32E2, 0x00},
{0x32E3, 0xF0},
{0x32E4, 0x01},
{0x32E5, 0x01},
{0x32E6, 0x02},
{0x32E7, 0x03},
{0x3200, 0x3E},
{0x3201, 0x0F},
{0x3028, 0x0F},
{0x3029, 0x00},
{0x302A, 0x08},
{0x3022, 0x24},
{0x3023, 0x2C},
{0x3002, 0x00},
{0x3003, 0x04},
{0x3004, 0x00},
{0x3005, 0x04},
{0x3006, 0x05},
{0x3007, 0x03},
{0x3008, 0x02},
{0x3009, 0xD3},
{0x300A, 0x03},
{0x300B, 0xFC},
{0x300C, 0x02},
{0x300D, 0xE0},
{0x300E, 0x02},
{0x300F, 0x80},
{0x3010, 0x02},
{0x3011, 0xD0},
{0x32B8, 0x3F},
{0x32B9, 0x31},
{0x32BB, 0x87},
{0x32BC, 0x38},
{0x32BD, 0x3C},
{0x32BE, 0x34},
{0x3201, 0x7F},
{0x3021, 0x06},
{0x3025, 0x00}, //normal
{0x3400, 0x01},
{0x3060, 0x01},
{REGLIST_TAIL, 0x00}, // tail
};
static const DRAM_ATTR uint16_t sensor_framesize_VGA_crop[][2] = {
//[JPEG_640x480_Crop_19.77_19.77_Fps]
{0x3021, 0x00},
{REG_DLY, 100}, // delay 100ms
{0x32BF, 0x60},
{0x32C0, 0x5A},
{0x32C1, 0x5A},
{0x32C2, 0x5A},
{0x32C3, 0x00},
{0x32C4, 0x20},
{0x32C5, 0x20},
{0x32C6, 0x20},
{0x32C7, 0x00},
{0x32C8, 0x62},
{0x32C9, 0x5A},
{0x32CA, 0x7A},
{0x32CB, 0x7A},
{0x32CC, 0x7A},
{0x32CD, 0x7A},
{0x32DB, 0x68},
{0x32F0, 0x70},
{0x3400, 0x08},
{0x3400, 0x00},
{0x3401, 0x4E},
{0x3404, 0x00},
{0x3405, 0x00},
{0x3410, 0x00},
{0x3200, 0x3E},
{0x3201, 0x0F},
{0x3028, 0x0F},
{0x3029, 0x00},
{0x302A, 0x08},
{0x3022, 0x24},
{0x3023, 0x24},
{0x3002, 0x01},
{0x3003, 0x44},
{0x3004, 0x00},
{0x3005, 0x7C},
{0x3006, 0x03},
{0x3007, 0xC3},
{0x3008, 0x02},
{0x3009, 0x5B},
{0x300A, 0x03},
{0x300B, 0xFC},
{0x300C, 0x01},
{0x300D, 0xF0},
{0x300E, 0x02},
{0x300F, 0x80},
{0x3010, 0x01},
{0x3011, 0xE0},
{0x32B8, 0x3F},
{0x32B9, 0x31},
{0x32BB, 0x87},
{0x32BC, 0x38},
{0x32BD, 0x3C},
{0x32BE, 0x34},
{0x3201, 0x3F},
{0x3021, 0x06},
{0x3025, 0x00}, //normal
{0x3400, 0x01},
{0x3060, 0x01},
{REGLIST_TAIL, 0x00}, // tail
};
static const DRAM_ATTR uint16_t sensor_framesize_QVGA_crop[][2] = {
//[JPEG_320x240_Crop_19.77_19.77_Fps]
{0x3021, 0x00},
{REG_DLY, 100}, // delay 100ms
{0x32BF, 0x60},
{0x32C0, 0x5A},
{0x32C1, 0x5A},
{0x32C2, 0x5A},
{0x32C3, 0x00},
{0x32C4, 0x20},
{0x32C5, 0x20},
{0x32C6, 0x20},
{0x32C7, 0x00},
{0x32C8, 0x62},
{0x32C9, 0x5A},
{0x32CA, 0x7A},
{0x32CB, 0x7A},
{0x32CC, 0x7A},
{0x32CD, 0x7A},
{0x32DB, 0x68},
{0x32F0, 0x70},
{0x3400, 0x08},
{0x3400, 0x00},
{0x3401, 0x4E},
{0x3404, 0x00},
{0x3405, 0x00},
{0x3410, 0x00},
{0x32E0, 0x01},
{0x32E1, 0x40},
{0x32E2, 0x00},
{0x32E3, 0xF0},
{0x32E4, 0x01},
{0x32E5, 0x01},
{0x32E6, 0x01},
{0x32E7, 0x02},
{0x3200, 0x3E},
{0x3201, 0x0F},
{0x3028, 0x0F},
{0x3029, 0x00},
{0x302A, 0x08},
{0x3022, 0x24},
{0x3023, 0x24},
{0x3002, 0x01},
{0x3003, 0x44},
{0x3004, 0x00},
{0x3005, 0x7C},
{0x3006, 0x03},
{0x3007, 0xC3},
{0x3008, 0x02},
{0x3009, 0x5B},
{0x300A, 0x03},
{0x300B, 0xFC},
{0x300C, 0x01},
{0x300D, 0xF0},
{0x300E, 0x02},
{0x300F, 0x80},
{0x3010, 0x01},
{0x3011, 0xE0},
{0x32B8, 0x3F},
{0x32B9, 0x31},
{0x32BB, 0x87},
{0x32BC, 0x38},
{0x32BD, 0x3C},
{0x32BE, 0x34},
{0x3201, 0x7F},
{0x3021, 0x06},
{0x3025, 0x00}, //normal
{0x3400, 0x01},
{0x3060, 0x01},
{REGLIST_TAIL, 0x00}, // tail
};
#endif
@@ -0,0 +1,32 @@
/*
* This file is part of the OpenMV project.
* Copyright (c) 2013/2014 Ibrahim Abdelkader <i.abdalkader@gmail.com>
* This work is licensed under the MIT license, see the file LICENSE for details.
*
* OV2640 driver.
*
*/
#ifndef __OV2640_H__
#define __OV2640_H__
#include "sensor.h"
/**
* @brief Detect sensor pid
*
* @param slv_addr SCCB address
* @param id Detection result
* @return
* 0: Can't detect this sensor
* Nonzero: This sensor has been detected
*/
int esp32_camera_ov2640_detect(int slv_addr, sensor_id_t *id);
/**
* @brief initialize sensor function pointers
*
* @param sensor pointer of sensor
* @return
* Always 0
*/
int esp32_camera_ov2640_init(sensor_t *sensor);
#endif // __OV2640_H__
@@ -0,0 +1,216 @@
/*
* This file is part of the OpenMV project.
* Copyright (c) 2013/2014 Ibrahim Abdelkader <i.abdalkader@gmail.com>
* This work is licensed under the MIT license, see the file LICENSE for details.
*
* OV2640 register definitions.
*/
#ifndef __REG_REGS_H__
#define __REG_REGS_H__
/* DSP register bank FF=0x00*/
#define R_BYPASS 0x05
#define QS 0x44
#define CTRLI 0x50
#define HSIZE 0x51
#define VSIZE 0x52
#define XOFFL 0x53
#define YOFFL 0x54
#define VHYX 0x55
#define DPRP 0x56
#define TEST 0x57
#define ZMOW 0x5A
#define ZMOH 0x5B
#define ZMHH 0x5C
#define BPADDR 0x7C
#define BPDATA 0x7D
#define CTRL2 0x86
#define CTRL3 0x87
#define SIZEL 0x8C
#define HSIZE8 0xC0
#define VSIZE8 0xC1
#define CTRL0 0xC2
#define CTRL1 0xC3
#define R_DVP_SP 0xD3
#define IMAGE_MODE 0xDA
#define RESET 0xE0
#define MS_SP 0xF0
#define SS_ID 0xF7
#define SS_CTRL 0xF7
#define MC_BIST 0xF9
#define MC_AL 0xFA
#define MC_AH 0xFB
#define MC_D 0xFC
#define P_CMD 0xFD
#define P_STATUS 0xFE
#define BANK_SEL 0xFF
#define CTRLI_LP_DP 0x80
#define CTRLI_ROUND 0x40
#define CTRL0_AEC_EN 0x80
#define CTRL0_AEC_SEL 0x40
#define CTRL0_STAT_SEL 0x20
#define CTRL0_VFIRST 0x10
#define CTRL0_YUV422 0x08
#define CTRL0_YUV_EN 0x04
#define CTRL0_RGB_EN 0x02
#define CTRL0_RAW_EN 0x01
#define CTRL2_DCW_EN 0x20
#define CTRL2_SDE_EN 0x10
#define CTRL2_UV_ADJ_EN 0x08
#define CTRL2_UV_AVG_EN 0x04
#define CTRL2_CMX_EN 0x01
#define CTRL3_BPC_EN 0x80
#define CTRL3_WPC_EN 0x40
#define R_DVP_SP_AUTO_MODE 0x80
#define R_BYPASS_DSP_EN 0x00
#define R_BYPASS_DSP_BYPAS 0x01
#define IMAGE_MODE_Y8_DVP_EN 0x40
#define IMAGE_MODE_JPEG_EN 0x10
#define IMAGE_MODE_YUV422 0x00
#define IMAGE_MODE_RAW10 0x04
#define IMAGE_MODE_RGB565 0x08
#define IMAGE_MODE_HREF_VSYNC 0x02
#define IMAGE_MODE_LBYTE_FIRST 0x01
#define RESET_MICROC 0x40
#define RESET_SCCB 0x20
#define RESET_JPEG 0x10
#define RESET_DVP 0x04
#define RESET_IPU 0x02
#define RESET_CIF 0x01
#define MC_BIST_RESET 0x80
#define MC_BIST_BOOT_ROM_SEL 0x40
#define MC_BIST_12KB_SEL 0x20
#define MC_BIST_12KB_MASK 0x30
#define MC_BIST_512KB_SEL 0x08
#define MC_BIST_512KB_MASK 0x0C
#define MC_BIST_BUSY_BIT_R 0x02
#define MC_BIST_MC_RES_ONE_SH_W 0x02
#define MC_BIST_LAUNCH 0x01
typedef enum {
BANK_DSP, BANK_SENSOR, BANK_MAX
} ov2640_bank_t;
/* Sensor register bank FF=0x01*/
#define GAIN 0x00
#define COM1 0x03
#define REG04 0x04
#define REG08 0x08
#define COM2 0x09
#define REG_PID 0x0A
#define REG_VER 0x0B
#define COM3 0x0C
#define COM4 0x0D
#define AEC 0x10
#define CLKRC 0x11
#define COM7 0x12
#define COM8 0x13
#define COM9 0x14 /* AGC gain ceiling */
#define COM10 0x15
#define HSTART 0x17
#define HSTOP 0x18
#define VSTART 0x19
#define VSTOP 0x1A
#define REG_MIDH 0x1C
#define REG_MIDL 0x1D
#define AEW 0x24
#define AEB 0x25
#define VV 0x26
#define REG2A 0x2A
#define FRARL 0x2B
#define ADDVSL 0x2D
#define ADDVSH 0x2E
#define YAVG 0x2F
#define HSDY 0x30
#define HEDY 0x31
#define REG32 0x32
#define ARCOM2 0x34
#define REG45 0x45
#define FLL 0x46
#define FLH 0x47
#define COM19 0x48
#define ZOOMS 0x49
#define COM22 0x4B
#define COM25 0x4E
#define BD50 0x4F
#define BD60 0x50
#define REG5D 0x5D
#define REG5E 0x5E
#define REG5F 0x5F
#define REG60 0x60
#define HISTO_LOW 0x61
#define HISTO_HIGH 0x62
#define REG04_DEFAULT 0x28
#define REG04_HFLIP_IMG 0x80
#define REG04_VFLIP_IMG 0x40
#define REG04_VREF_EN 0x10
#define REG04_HREF_EN 0x08
#define REG04_SET(x) (REG04_DEFAULT|x)
#define COM2_STDBY 0x10
#define COM2_OUT_DRIVE_1x 0x00
#define COM2_OUT_DRIVE_2x 0x01
#define COM2_OUT_DRIVE_3x 0x02
#define COM2_OUT_DRIVE_4x 0x03
#define COM3_DEFAULT 0x38
#define COM3_BAND_50Hz 0x04
#define COM3_BAND_60Hz 0x00
#define COM3_BAND_AUTO 0x02
#define COM3_BAND_SET(x) (COM3_DEFAULT|x)
#define COM7_SRST 0x80
#define COM7_RES_UXGA 0x00 /* UXGA */
#define COM7_RES_SVGA 0x40 /* SVGA */
#define COM7_RES_CIF 0x20 /* CIF */
#define COM7_ZOOM_EN 0x04 /* Enable Zoom */
#define COM7_COLOR_BAR 0x02 /* Enable Color Bar Test */
#define COM8_DEFAULT 0xC0
#define COM8_BNDF_EN 0x20 /* Enable Banding filter */
#define COM8_AGC_EN 0x04 /* AGC Auto/Manual control selection */
#define COM8_AEC_EN 0x01 /* Auto/Manual Exposure control */
#define COM8_SET(x) (COM8_DEFAULT|x)
#define COM9_DEFAULT 0x08
#define COM9_AGC_GAIN_2x 0x00 /* AGC: 2x */
#define COM9_AGC_GAIN_4x 0x01 /* AGC: 4x */
#define COM9_AGC_GAIN_8x 0x02 /* AGC: 8x */
#define COM9_AGC_GAIN_16x 0x03 /* AGC: 16x */
#define COM9_AGC_GAIN_32x 0x04 /* AGC: 32x */
#define COM9_AGC_GAIN_64x 0x05 /* AGC: 64x */
#define COM9_AGC_GAIN_128x 0x06 /* AGC: 128x */
#define COM9_AGC_SET(x) (COM9_DEFAULT|(x<<5))
#define COM10_HREF_EN 0x80 /* HSYNC changes to HREF */
#define COM10_HSYNC_EN 0x40 /* HREF changes to HSYNC */
#define COM10_PCLK_FREE 0x20 /* PCLK output option: free running PCLK */
#define COM10_PCLK_EDGE 0x10 /* Data is updated at the rising edge of PCLK */
#define COM10_HREF_NEG 0x08 /* HREF negative */
#define COM10_VSYNC_NEG 0x02 /* VSYNC negative */
#define COM10_HSYNC_NEG 0x01 /* HSYNC negative */
#define CTRL1_AWB 0x08 /* Enable AWB */
#define VV_AGC_TH_SET(h,l) ((h<<4)|(l&0x0F))
#define REG32_UXGA 0x36
#define REG32_SVGA 0x09
#define REG32_CIF 0x89
#define CLKRC_2X 0x80
#define CLKRC_2X_UXGA (0x01 | CLKRC_2X)
#define CLKRC_2X_SVGA CLKRC_2X
#define CLKRC_2X_CIF CLKRC_2X
#endif //__REG_REGS_H__
@@ -0,0 +1,485 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef _OV2640_SETTINGS_H_
#define _OV2640_SETTINGS_H_
#include <stdint.h>
#include <stdbool.h>
#include "esp_attr.h"
#include "ov2640_regs.h"
typedef enum {
OV2640_MODE_UXGA, OV2640_MODE_SVGA, OV2640_MODE_CIF, OV2640_MODE_MAX
} ov2640_sensor_mode_t;
typedef struct {
union {
struct {
uint8_t pclk_div:7;
uint8_t pclk_auto:1;
};
uint8_t pclk;
};
union {
struct {
uint8_t clk_div:6;
uint8_t reserved:1;
uint8_t clk_2x:1;
};
uint8_t clk;
};
} ov2640_clk_t;
typedef struct {
uint16_t offset_x;
uint16_t offset_y;
uint16_t max_x;
uint16_t max_y;
} ov2640_ratio_settings_t;
static const DRAM_ATTR ov2640_ratio_settings_t ratio_table[] = {
// ox, oy, mx, my
{ 0, 0, 1600, 1200 }, //4x3
{ 8, 72, 1584, 1056 }, //3x2
{ 0, 100, 1600, 1000 }, //16x10
{ 0, 120, 1600, 960 }, //5x3
{ 0, 150, 1600, 900 }, //16x9
{ 2, 258, 1596, 684 }, //21x9
{ 50, 0, 1500, 1200 }, //5x4
{ 200, 0, 1200, 1200 }, //1x1
{ 462, 0, 676, 1200 } //9x16
};
// 30fps@24MHz
const DRAM_ATTR uint8_t ov2640_settings_cif[][2] = {
{BANK_SEL, BANK_DSP},
{0x2c, 0xff},
{0x2e, 0xdf},
{BANK_SEL, BANK_SENSOR},
{0x3c, 0x32},
{CLKRC, 0x01},
{COM2, COM2_OUT_DRIVE_3x},
{REG04, REG04_DEFAULT},
{COM8, COM8_DEFAULT | COM8_BNDF_EN | COM8_AGC_EN | COM8_AEC_EN},
{COM9, COM9_AGC_SET(COM9_AGC_GAIN_8x)},
{0x2c, 0x0c},
{0x33, 0x78},
{0x3a, 0x33},
{0x3b, 0xfB},
{0x3e, 0x00},
{0x43, 0x11},
{0x16, 0x10},
{0x39, 0x92},
{0x35, 0xda},
{0x22, 0x1a},
{0x37, 0xc3},
{0x23, 0x00},
{ARCOM2, 0xc0},
{0x06, 0x88},
{0x07, 0xc0},
{COM4, 0x87},
{0x0e, 0x41},
{0x4c, 0x00},
{0x4a, 0x81},
{0x21, 0x99},
{AEW, 0x40},
{AEB, 0x38},
{VV, VV_AGC_TH_SET(8,2)},
{0x5c, 0x00},
{0x63, 0x00},
{HISTO_LOW, 0x70},
{HISTO_HIGH, 0x80},
{0x7c, 0x05},
{0x20, 0x80},
{0x28, 0x30},
{0x6c, 0x00},
{0x6d, 0x80},
{0x6e, 0x00},
{0x70, 0x02},
{0x71, 0x94},
{0x73, 0xc1},
{0x3d, 0x34},
{0x5a, 0x57},
{BD50, 0xbb},
{BD60, 0x9c},
{COM7, COM7_RES_CIF},
{HSTART, 0x11},
{HSTOP, 0x43},
{VSTART, 0x00},
{VSTOP, 0x25},
{REG32, 0x89},
{0x37, 0xc0},
{BD50, 0xca},
{BD60, 0xa8},
{0x6d, 0x00},
{0x3d, 0x38},
{BANK_SEL, BANK_DSP},
{0xe5, 0x7f},
{MC_BIST, MC_BIST_RESET | MC_BIST_BOOT_ROM_SEL},
{0x41, 0x24},
{RESET, RESET_JPEG | RESET_DVP},
{0x76, 0xff},
{0x33, 0xa0},
{0x42, 0x20},
{0x43, 0x18},
{0x4c, 0x00},
{CTRL3, CTRL3_WPC_EN | 0x10 },
{0x88, 0x3f},
{0xd7, 0x03},
{0xd9, 0x10},
{R_DVP_SP, R_DVP_SP_AUTO_MODE | 0x02},
{0xc8, 0x08},
{0xc9, 0x80},
{BPADDR, 0x00},
{BPDATA, 0x00},
{BPADDR, 0x03},
{BPDATA, 0x48},
{BPDATA, 0x48},
{BPADDR, 0x08},
{BPDATA, 0x20},
{BPDATA, 0x10},
{BPDATA, 0x0e},
{0x90, 0x00},
{0x91, 0x0e},
{0x91, 0x1a},
{0x91, 0x31},
{0x91, 0x5a},
{0x91, 0x69},
{0x91, 0x75},
{0x91, 0x7e},
{0x91, 0x88},
{0x91, 0x8f},
{0x91, 0x96},
{0x91, 0xa3},
{0x91, 0xaf},
{0x91, 0xc4},
{0x91, 0xd7},
{0x91, 0xe8},
{0x91, 0x20},
{0x92, 0x00},
{0x93, 0x06},
{0x93, 0xe3},
{0x93, 0x05},
{0x93, 0x05},
{0x93, 0x00},
{0x93, 0x04},
{0x93, 0x00},
{0x93, 0x00},
{0x93, 0x00},
{0x93, 0x00},
{0x93, 0x00},
{0x93, 0x00},
{0x93, 0x00},
{0x96, 0x00},
{0x97, 0x08},
{0x97, 0x19},
{0x97, 0x02},
{0x97, 0x0c},
{0x97, 0x24},
{0x97, 0x30},
{0x97, 0x28},
{0x97, 0x26},
{0x97, 0x02},
{0x97, 0x98},
{0x97, 0x80},
{0x97, 0x00},
{0x97, 0x00},
{0xa4, 0x00},
{0xa8, 0x00},
{0xc5, 0x11},
{0xc6, 0x51},
{0xbf, 0x80},
{0xc7, 0x10},
{0xb6, 0x66},
{0xb8, 0xA5},
{0xb7, 0x64},
{0xb9, 0x7C},
{0xb3, 0xaf},
{0xb4, 0x97},
{0xb5, 0xFF},
{0xb0, 0xC5},
{0xb1, 0x94},
{0xb2, 0x0f},
{0xc4, 0x5c},
{CTRL1, 0xfd},
{0x7f, 0x00},
{0xe5, 0x1f},
{0xe1, 0x67},
{0xdd, 0x7f},
{IMAGE_MODE, 0x00},
{RESET, 0x00},
{R_BYPASS, R_BYPASS_DSP_EN},
{0, 0}
};
const DRAM_ATTR uint8_t ov2640_settings_to_cif[][2] = {
{BANK_SEL, BANK_SENSOR},
{COM7, COM7_RES_CIF},
//Set the sensor output window
{COM1, 0x0A},
{REG32, REG32_CIF},
{HSTART, 0x11},
{HSTOP, 0x43},
{VSTART, 0x00},
{VSTOP, 0x25},
//{CLKRC, 0x00},
{BD50, 0xca},
{BD60, 0xa8},
{0x5a, 0x23},
{0x6d, 0x00},
{0x3d, 0x38},
{0x39, 0x92},
{0x35, 0xda},
{0x22, 0x1a},
{0x37, 0xc3},
{0x23, 0x00},
{ARCOM2, 0xc0},
{0x06, 0x88},
{0x07, 0xc0},
{COM4, 0x87},
{0x0e, 0x41},
{0x4c, 0x00},
{BANK_SEL, BANK_DSP},
{RESET, RESET_DVP},
//Set the sensor resolution (UXGA, SVGA, CIF)
{HSIZE8, 0x32},
{VSIZE8, 0x25},
{SIZEL, 0x00},
//Set the image window size >= output size
{HSIZE, 0x64},
{VSIZE, 0x4a},
{XOFFL, 0x00},
{YOFFL, 0x00},
{VHYX, 0x00},
{TEST, 0x00},
{CTRL2, CTRL2_DCW_EN | 0x1D},
{CTRLI, CTRLI_LP_DP | 0x00},
//{R_DVP_SP, 0x08},
{0, 0}
};
const DRAM_ATTR uint8_t ov2640_settings_to_svga[][2] = {
{BANK_SEL, BANK_SENSOR},
{COM7, COM7_RES_SVGA},
//Set the sensor output window
{COM1, 0x0A},
{REG32, REG32_SVGA},
{HSTART, 0x11},
{HSTOP, 0x43},
{VSTART, 0x00},
{VSTOP, 0x4b},
//{CLKRC, 0x00},
{0x37, 0xc0},
{BD50, 0xca},
{BD60, 0xa8},
{0x5a, 0x23},
{0x6d, 0x00},
{0x3d, 0x38},
{0x39, 0x92},
{0x35, 0xda},
{0x22, 0x1a},
{0x37, 0xc3},
{0x23, 0x00},
{ARCOM2, 0xc0},
{0x06, 0x88},
{0x07, 0xc0},
{COM4, 0x87},
{0x0e, 0x41},
{0x42, 0x03},
{0x4c, 0x00},
{BANK_SEL, BANK_DSP},
{RESET, RESET_DVP},
//Set the sensor resolution (UXGA, SVGA, CIF)
{HSIZE8, 0x64},
{VSIZE8, 0x4B},
{SIZEL, 0x00},
//Set the image window size >= output size
{HSIZE, 0xC8},
{VSIZE, 0x96},
{XOFFL, 0x00},
{YOFFL, 0x00},
{VHYX, 0x00},
{TEST, 0x00},
{CTRL2, CTRL2_DCW_EN | 0x1D},
{CTRLI, CTRLI_LP_DP | 0x00},
//{R_DVP_SP, 0x08},
{0, 0}
};
const DRAM_ATTR uint8_t ov2640_settings_to_uxga[][2] = {
{BANK_SEL, BANK_SENSOR},
{COM7, COM7_RES_UXGA},
//Set the sensor output window
{COM1, 0x0F},
{REG32, REG32_UXGA},
{HSTART, 0x11},
{HSTOP, 0x75},
{VSTART, 0x01},
{VSTOP, 0x97},
//{CLKRC, 0x00},
{0x3d, 0x34},
{BD50, 0xbb},
{BD60, 0x9c},
{0x5a, 0x57},
{0x6d, 0x80},
{0x39, 0x82},
{0x23, 0x00},
{0x07, 0xc0},
{0x4c, 0x00},
{0x35, 0x88},
{0x22, 0x0a},
{0x37, 0x40},
{ARCOM2, 0xa0},
{0x06, 0x02},
{COM4, 0xb7},
{0x0e, 0x01},
{0x42, 0x83},
{BANK_SEL, BANK_DSP},
{RESET, RESET_DVP},
//Set the sensor resolution (UXGA, SVGA, CIF)
{HSIZE8, 0xc8},
{VSIZE8, 0x96},
{SIZEL, 0x00},
//Set the image window size >= output size
{HSIZE, 0x90},
{VSIZE, 0x2c},
{XOFFL, 0x00},
{YOFFL, 0x00},
{VHYX, 0x88},
{TEST, 0x00},
{CTRL2, CTRL2_DCW_EN | 0x1d},
{CTRLI, 0x00},
//{R_DVP_SP, 0x06},
{0, 0}
};
const DRAM_ATTR uint8_t ov2640_settings_jpeg3[][2] = {
{BANK_SEL, BANK_DSP},
{RESET, RESET_JPEG | RESET_DVP},
{IMAGE_MODE, IMAGE_MODE_JPEG_EN | IMAGE_MODE_HREF_VSYNC},
{0xD7, 0x03},
{0xE1, 0x77},
{0xE5, 0x1F},
{0xD9, 0x10},
{0xDF, 0x80},
{0x33, 0x80},
{0x3C, 0x10},
{0xEB, 0x30},
{0xDD, 0x7F},
{RESET, 0x00},
{0, 0}
};
static const uint8_t ov2640_settings_yuv422[][2] = {
{BANK_SEL, BANK_DSP},
{RESET, RESET_DVP},
{IMAGE_MODE, IMAGE_MODE_YUV422},
{0xD7, 0x01},
{0xE1, 0x67},
{RESET, 0x00},
{0, 0},
};
static const uint8_t ov2640_settings_rgb565[][2] = {
{BANK_SEL, BANK_DSP},
{RESET, RESET_DVP},
{IMAGE_MODE, IMAGE_MODE_RGB565},
{0xD7, 0x03},
{0xE1, 0x77},
{RESET, 0x00},
{0, 0},
};
#define NUM_BRIGHTNESS_LEVELS (5)
static const uint8_t brightness_regs[NUM_BRIGHTNESS_LEVELS + 1][5] = {
{BPADDR, BPDATA, BPADDR, BPDATA, BPDATA },
{0x00, 0x04, 0x09, 0x00, 0x00 }, /* -2 */
{0x00, 0x04, 0x09, 0x10, 0x00 }, /* -1 */
{0x00, 0x04, 0x09, 0x20, 0x00 }, /* 0 */
{0x00, 0x04, 0x09, 0x30, 0x00 }, /* +1 */
{0x00, 0x04, 0x09, 0x40, 0x00 }, /* +2 */
};
#define NUM_CONTRAST_LEVELS (5)
static const uint8_t contrast_regs[NUM_CONTRAST_LEVELS + 1][7] = {
{BPADDR, BPDATA, BPADDR, BPDATA, BPDATA, BPDATA, BPDATA },
{0x00, 0x04, 0x07, 0x20, 0x18, 0x34, 0x06 }, /* -2 */
{0x00, 0x04, 0x07, 0x20, 0x1c, 0x2a, 0x06 }, /* -1 */
{0x00, 0x04, 0x07, 0x20, 0x20, 0x20, 0x06 }, /* 0 */
{0x00, 0x04, 0x07, 0x20, 0x24, 0x16, 0x06 }, /* +1 */
{0x00, 0x04, 0x07, 0x20, 0x28, 0x0c, 0x06 }, /* +2 */
};
#define NUM_SATURATION_LEVELS (5)
static const uint8_t saturation_regs[NUM_SATURATION_LEVELS + 1][5] = {
{BPADDR, BPDATA, BPADDR, BPDATA, BPDATA },
{0x00, 0x02, 0x03, 0x28, 0x28 }, /* -2 */
{0x00, 0x02, 0x03, 0x38, 0x38 }, /* -1 */
{0x00, 0x02, 0x03, 0x48, 0x48 }, /* 0 */
{0x00, 0x02, 0x03, 0x58, 0x58 }, /* +1 */
{0x00, 0x02, 0x03, 0x68, 0x68 }, /* +2 */
};
#define NUM_SPECIAL_EFFECTS (7)
static const uint8_t special_effects_regs[NUM_SPECIAL_EFFECTS + 1][5] = {
{BPADDR, BPDATA, BPADDR, BPDATA, BPDATA },
{0x00, 0X00, 0x05, 0X80, 0X80 }, /* no effect */
{0x00, 0X40, 0x05, 0X80, 0X80 }, /* negative */
{0x00, 0X18, 0x05, 0X80, 0X80 }, /* black and white */
{0x00, 0X18, 0x05, 0X40, 0XC0 }, /* reddish */
{0x00, 0X18, 0x05, 0X40, 0X40 }, /* greenish */
{0x00, 0X18, 0x05, 0XA0, 0X40 }, /* blue */
{0x00, 0X18, 0x05, 0X40, 0XA6 }, /* retro */
};
#define NUM_WB_MODES (4)
static const uint8_t wb_modes_regs[NUM_WB_MODES + 1][3] = {
{0XCC, 0XCD, 0XCE },
{0x5E, 0X41, 0x54 }, /* sunny */
{0x65, 0X41, 0x4F }, /* cloudy */
{0x52, 0X41, 0x66 }, /* office */
{0x42, 0X3F, 0x71 }, /* home */
};
#define NUM_AE_LEVELS (5)
static const uint8_t ae_levels_regs[NUM_AE_LEVELS + 1][3] = {
{ AEW, AEB, VV },
{0x20, 0X18, 0x60 },
{0x34, 0X1C, 0x00 },
{0x3E, 0X38, 0x81 },
{0x48, 0X40, 0x81 },
{0x58, 0X50, 0x92 },
};
const uint8_t agc_gain_tbl[31] = {
0x00, 0x10, 0x18, 0x30, 0x34, 0x38, 0x3C, 0x70, 0x72, 0x74, 0x76, 0x78, 0x7A, 0x7C, 0x7E, 0xF0,
0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF
};
#endif /* _OV2640_SETTINGS_H_ */
@@ -0,0 +1,34 @@
/*
* This file is part of the OpenMV project.
* Copyright (c) 2013/2014 Ibrahim Abdelkader <i.abdalkader@gmail.com>
* This work is licensed under the MIT license, see the file LICENSE for details.
*
* OV3660 driver.
*
*/
#ifndef __OV3660_H__
#define __OV3660_H__
#include "sensor.h"
/**
* @brief Detect sensor pid
*
* @param slv_addr SCCB address
* @param id Detection result
* @return
* 0: Can't detect this sensor
* Nonzero: This sensor has been detected
*/
int esp32_camera_ov3660_detect(int slv_addr, sensor_id_t *id);
/**
* @brief initialize sensor function pointers
*
* @param sensor pointer of sensor
* @return
* Always 0
*/
int esp32_camera_ov3660_init(sensor_t *sensor);
#endif // __OV3660_H__
@@ -0,0 +1,211 @@
/*
* OV3660 register definitions.
*/
#ifndef __OV3660_REG_REGS_H__
#define __OV3660_REG_REGS_H__
/* system control registers */
#define SYSTEM_CTROL0 0x3008 // Bit[7]: Software reset
// Bit[6]: Software power down
// Bit[5]: Reserved
// Bit[4]: SRB clock SYNC enable
// Bit[3]: Isolation suspend select
// Bit[2:0]: Not used
/* output format control registers */
#define FORMAT_CTRL 0x501F // Format select
// Bit[2:0]:
// 000: YUV422
// 001: RGB
// 010: Dither
// 011: RAW after DPC
// 101: RAW after CIP
/* format control registers */
#define FORMAT_CTRL00 0x4300
/* frame control registers */
#define FRAME_CTRL01 0x4201 // Control Passed Frame Number When both ON and OFF number set to 0x00,frame control is in bypass mode
// Bit[7:4]: Not used
// Bit[3:0]: Frame ON number
#define FRAME_CTRL02 0x4202 // Control Masked Frame Number When both ON and OFF number set to 0x00,frame control is in bypass mode
// Bit[7:4]: Not used
// BIT[3:0]: Frame OFF number
/* ISP top control registers */
#define PRE_ISP_TEST_SETTING_1 0x503D // Bit[7]: Test enable
// 0: Test disable
// 1: Color bar enable
// Bit[6]: Rolling
// Bit[5]: Transparent
// Bit[4]: Square black and white
// Bit[3:2]: Color bar style
// 00: Standard 8 color bar
// 01: Gradual change at vertical mode 1
// 10: Gradual change at horizontal
// 11: Gradual change at vertical mode 2
// Bit[1:0]: Test select
// 00: Color bar
// 01: Random data
// 10: Square data
// 11: Black image
//exposure = {0x3500[3:0], 0x3501[7:0], 0x3502[7:0]} / 16 × tROW
/* AEC/AGC control functions */
#define AEC_PK_MANUAL 0x3503 // AEC Manual Mode Control
// Bit[7:6]: Reserved
// Bit[5]: Gain delay option
// Valid when 0x3503[4]=1’b0
// 0: Delay one frame latch
// 1: One frame latch
// Bit[4:2]: Reserved
// Bit[1]: AGC manual
// 0: Auto enable
// 1: Manual enable
// Bit[0]: AEC manual
// 0: Auto enable
// 1: Manual enable
//gain = {0x350A[1:0], 0x350B[7:0]} / 16
/* mirror and flip registers */
#define TIMING_TC_REG20 0x3820 // Timing Control Register
// Bit[2:1]: Vertical flip enable
// 00: Normal
// 11: Vertical flip
// Bit[0]: Vertical binning enable
#define TIMING_TC_REG21 0x3821 // Timing Control Register
// Bit[5]: Compression Enable
// Bit[2:1]: Horizontal mirror enable
// 00: Normal
// 11: Horizontal mirror
// Bit[0]: Horizontal binning enable
#define CLOCK_POL_CONTROL 0x4740// Bit[5]: PCLK polarity 0: active low
// 1: active high
// Bit[3]: Gate PCLK under VSYNC
// Bit[2]: Gate PCLK under HREF
// Bit[1]: HREF polarity
// 0: active low
// 1: active high
// Bit[0] VSYNC polarity
// 0: active low
// 1: active high
#define DRIVE_CAPABILITY 0x302c // Bit[7:6]:
// 00: 1x
// 01: 2x
// 10: 3x
// 11: 4x
#define X_ADDR_ST_H 0x3800 //Bit[3:0]: X address start[11:8]
#define X_ADDR_ST_L 0x3801 //Bit[7:0]: X address start[7:0]
#define Y_ADDR_ST_H 0x3802 //Bit[2:0]: Y address start[10:8]
#define Y_ADDR_ST_L 0x3803 //Bit[7:0]: Y address start[7:0]
#define X_ADDR_END_H 0x3804 //Bit[3:0]: X address end[11:8]
#define X_ADDR_END_L 0x3805 //Bit[7:0]:
#define Y_ADDR_END_H 0x3806 //Bit[2:0]: Y address end[10:8]
#define Y_ADDR_END_L 0x3807 //Bit[7:0]:
// Size after scaling
#define X_OUTPUT_SIZE_H 0x3808 //Bit[3:0]: DVP output horizontal width[11:8]
#define X_OUTPUT_SIZE_L 0x3809 //Bit[7:0]:
#define Y_OUTPUT_SIZE_H 0x380a //Bit[2:0]: DVP output vertical height[10:8]
#define Y_OUTPUT_SIZE_L 0x380b //Bit[7:0]:
#define X_TOTAL_SIZE_H 0x380c //Bit[3:0]: Total horizontal size[11:8]
#define X_TOTAL_SIZE_L 0x380d //Bit[7:0]:
#define Y_TOTAL_SIZE_H 0x380e //Bit[7:0]: Total vertical size[15:8]
#define Y_TOTAL_SIZE_L 0x380f //Bit[7:0]:
#define X_OFFSET_H 0x3810 //Bit[3:0]: ISP horizontal offset[11:8]
#define X_OFFSET_L 0x3811 //Bit[7:0]:
#define Y_OFFSET_H 0x3812 //Bit[2:0]: ISP vertical offset[10:8]
#define Y_OFFSET_L 0x3813 //Bit[7:0]:
#define X_INCREMENT 0x3814 //Bit[7:4]: Horizontal odd subsample increment
//Bit[3:0]: Horizontal even subsample increment
#define Y_INCREMENT 0x3815 //Bit[7:4]: Vertical odd subsample increment
//Bit[3:0]: Vertical even subsample increment
// Size before scaling
//#define X_INPUT_SIZE (X_ADDR_END - X_ADDR_ST + 1 - (2 * X_OFFSET))
//#define Y_INPUT_SIZE (Y_ADDR_END - Y_ADDR_ST + 1 - (2 * Y_OFFSET))
#define ISP_CONTROL_01 0x5001 // Bit[5]: Scale enable
// 0: Disable
// 1: Enable
#define SCALE_CTRL_1 0x5601 // Bit[6:4]: HDIV RW
// DCW scale times
// 000: DCW 1 time
// 001: DCW 2 times
// 010: DCW 4 times
// 100: DCW 8 times
// 101: DCW 16 times
// Others: DCW 16 times
// Bit[2:0]: VDIV RW
// DCW scale times
// 000: DCW 1 time
// 001: DCW 2 times
// 010: DCW 4 times
// 100: DCW 8 times
// 101: DCW 16 times
// Others: DCW 16 times
#define SCALE_CTRL_2 0x5602 // X_SCALE High Bits
#define SCALE_CTRL_3 0x5603 // X_SCALE Low Bits
#define SCALE_CTRL_4 0x5604 // Y_SCALE High Bits
#define SCALE_CTRL_5 0x5605 // Y_SCALE Low Bits
#define SCALE_CTRL_6 0x5606 // Bit[3:0]: V Offset
#define PCLK_RATIO 0x3824 // Bit[4:0]: PCLK ratio manual
#define VFIFO_CTRL0C 0x460C // Bit[1]: PCLK manual enable
// 0: Auto
// 1: Manual by PCLK_RATIO
#define VFIFO_X_SIZE_H 0x4602
#define VFIFO_X_SIZE_L 0x4603
#define VFIFO_Y_SIZE_H 0x4604
#define VFIFO_Y_SIZE_L 0x4605
#define SC_PLLS_CTRL0 0x303a // Bit[7]: PLLS bypass
#define SC_PLLS_CTRL1 0x303b // Bit[4:0]: PLLS multiplier
#define SC_PLLS_CTRL2 0x303c // Bit[6:4]: PLLS charge pump control
// Bit[3:0]: PLLS system divider
#define SC_PLLS_CTRL3 0x303d // Bit[5:4]: PLLS pre-divider
// 00: 1
// 01: 1.5
// 10: 2
// 11: 3
// Bit[2]: PLLS root-divider - 1
// Bit[1:0]: PLLS seld5
// 00: 1
// 01: 1
// 10: 2
// 11: 2.5
#define COMPRESSION_CTRL00 0x4400 //
#define COMPRESSION_CTRL01 0x4401 //
#define COMPRESSION_CTRL02 0x4402 //
#define COMPRESSION_CTRL03 0x4403 //
#define COMPRESSION_CTRL04 0x4404 //
#define COMPRESSION_CTRL05 0x4405 //
#define COMPRESSION_CTRL06 0x4406 //
#define COMPRESSION_CTRL07 0x4407 // Bit[5:0]: QS
#define COMPRESSION_ISI_CTRL 0x4408 //
#define COMPRESSION_CTRL09 0x4409 //
#define COMPRESSION_CTRL0a 0x440a //
#define COMPRESSION_CTRL0b 0x440b //
#define COMPRESSION_CTRL0c 0x440c //
#define COMPRESSION_CTRL0d 0x440d //
#define COMPRESSION_CTRL0E 0x440e //
/**
* @brief register value
*/
#define TEST_COLOR_BAR 0xC0 /* Enable Color Bar roling Test */
#define AEC_PK_MANUAL_AGC_MANUALEN 0x02 /* Enable AGC Manual enable */
#define AEC_PK_MANUAL_AEC_MANUALEN 0x01 /* Enable AEC Manual enable */
#define TIMING_TC_REG20_VFLIP 0x06 /* Vertical flip enable */
#define TIMING_TC_REG21_HMIRROR 0x06 /* Horizontal mirror enable */
#endif // __OV3660_REG_REGS_H__
@@ -0,0 +1,318 @@
#ifndef _OV3660_SETTINGS_H_
#define _OV3660_SETTINGS_H_
#include <stdint.h>
#include <stdbool.h>
#include "esp_attr.h"
#include "ov3660_regs.h"
static const ratio_settings_t ratio_table[] = {
// mw, mh, sx, sy, ex, ey, ox, oy, tx, ty
{ 2048, 1536, 0, 0, 2079, 1547, 16, 6, 2300, 1564 }, //4x3
{ 1920, 1280, 64, 128, 2015, 1419, 16, 6, 2172, 1436 }, //3x2
{ 2048, 1280, 0, 128, 2079, 1419, 16, 6, 2300, 1436 }, //16x10
{ 1920, 1152, 64, 192, 2015, 1355, 16, 6, 2172, 1372 }, //5x3
{ 1920, 1080, 64, 242, 2015, 1333, 16, 6, 2172, 1322 }, //16x9
{ 2048, 880, 0, 328, 2079, 1219, 16, 6, 2300, 1236 }, //21x9
{ 1920, 1536, 64, 0, 2015, 1547, 16, 6, 2172, 1564 }, //5x4
{ 1536, 1536, 256, 0, 1823, 1547, 16, 6, 2044, 1564 }, //1x1
{ 864, 1536, 592, 0, 1487, 1547, 16, 6, 2044, 1564 } //9x16
};
#define REG_DLY 0xffff
#define REGLIST_TAIL 0x0000
static const DRAM_ATTR uint16_t sensor_default_regs[][2] = {
{SYSTEM_CTROL0, 0x82}, // software reset
{REG_DLY, 10}, // delay 10ms
{0x3103, 0x13},
{SYSTEM_CTROL0, 0x42},
{0x3017, 0xff},
{0x3018, 0xff},
{DRIVE_CAPABILITY, 0xc3},
{CLOCK_POL_CONTROL, 0x21},
{0x3611, 0x01},
{0x3612, 0x2d},
{0x3032, 0x00},
{0x3614, 0x80},
{0x3618, 0x00},
{0x3619, 0x75},
{0x3622, 0x80},
{0x3623, 0x00},
{0x3624, 0x03},
{0x3630, 0x52},
{0x3632, 0x07},
{0x3633, 0xd2},
{0x3704, 0x80},
{0x3708, 0x66},
{0x3709, 0x12},
{0x370b, 0x12},
{0x3717, 0x00},
{0x371b, 0x60},
{0x371c, 0x00},
{0x3901, 0x13},
{0x3600, 0x08},
{0x3620, 0x43},
{0x3702, 0x20},
{0x3739, 0x48},
{0x3730, 0x20},
{0x370c, 0x0c},
{0x3a18, 0x00},
{0x3a19, 0xf8},
{0x3000, 0x10},
{0x3004, 0xef},
{0x6700, 0x05},
{0x6701, 0x19},
{0x6702, 0xfd},
{0x6703, 0xd1},
{0x6704, 0xff},
{0x6705, 0xff},
{0x3c01, 0x80},
{0x3c00, 0x04},
{0x3a08, 0x00}, {0x3a09, 0x62}, //50Hz Band Width Step (10bit)
{0x3a0e, 0x08}, //50Hz Max Bands in One Frame (6 bit)
{0x3a0a, 0x00}, {0x3a0b, 0x52}, //60Hz Band Width Step (10bit)
{0x3a0d, 0x09}, //60Hz Max Bands in One Frame (6 bit)
{0x3a00, 0x3a},//night mode off
{0x3a14, 0x09},
{0x3a15, 0x30},
{0x3a02, 0x09},
{0x3a03, 0x30},
{COMPRESSION_CTRL0E, 0x08},
{0x4520, 0x0b},
{0x460b, 0x37},
{0x4713, 0x02},
{0x471c, 0xd0},
{0x5086, 0x00},
{0x5002, 0x00},
{0x501f, 0x00},
{SYSTEM_CTROL0, 0x02},
{0x5180, 0xff},
{0x5181, 0xf2},
{0x5182, 0x00},
{0x5183, 0x14},
{0x5184, 0x25},
{0x5185, 0x24},
{0x5186, 0x16},
{0x5187, 0x16},
{0x5188, 0x16},
{0x5189, 0x68},
{0x518a, 0x60},
{0x518b, 0xe0},
{0x518c, 0xb2},
{0x518d, 0x42},
{0x518e, 0x35},
{0x518f, 0x56},
{0x5190, 0x56},
{0x5191, 0xf8},
{0x5192, 0x04},
{0x5193, 0x70},
{0x5194, 0xf0},
{0x5195, 0xf0},
{0x5196, 0x03},
{0x5197, 0x01},
{0x5198, 0x04},
{0x5199, 0x12},
{0x519a, 0x04},
{0x519b, 0x00},
{0x519c, 0x06},
{0x519d, 0x82},
{0x519e, 0x38},
{0x5381, 0x1d},
{0x5382, 0x60},
{0x5383, 0x03},
{0x5384, 0x0c},
{0x5385, 0x78},
{0x5386, 0x84},
{0x5387, 0x7d},
{0x5388, 0x6b},
{0x5389, 0x12},
{0x538a, 0x01},
{0x538b, 0x98},
{0x5480, 0x01},
// {0x5481, 0x05},
// {0x5482, 0x09},
// {0x5483, 0x10},
// {0x5484, 0x3a},
// {0x5485, 0x4c},
// {0x5486, 0x5a},
// {0x5487, 0x68},
// {0x5488, 0x74},
// {0x5489, 0x80},
// {0x548a, 0x8e},
// {0x548b, 0xa4},
// {0x548c, 0xb4},
// {0x548d, 0xc8},
// {0x548e, 0xde},
// {0x548f, 0xf0},
// {0x5490, 0x15},
{0x5000, 0xa7},
{0x5800, 0x0C},
{0x5801, 0x09},
{0x5802, 0x0C},
{0x5803, 0x0C},
{0x5804, 0x0D},
{0x5805, 0x17},
{0x5806, 0x06},
{0x5807, 0x05},
{0x5808, 0x04},
{0x5809, 0x06},
{0x580a, 0x09},
{0x580b, 0x0E},
{0x580c, 0x05},
{0x580d, 0x01},
{0x580e, 0x01},
{0x580f, 0x01},
{0x5810, 0x05},
{0x5811, 0x0D},
{0x5812, 0x05},
{0x5813, 0x01},
{0x5814, 0x01},
{0x5815, 0x01},
{0x5816, 0x05},
{0x5817, 0x0D},
{0x5818, 0x08},
{0x5819, 0x06},
{0x581a, 0x05},
{0x581b, 0x07},
{0x581c, 0x0B},
{0x581d, 0x0D},
{0x581e, 0x12},
{0x581f, 0x0D},
{0x5820, 0x0E},
{0x5821, 0x10},
{0x5822, 0x10},
{0x5823, 0x1E},
{0x5824, 0x53},
{0x5825, 0x15},
{0x5826, 0x05},
{0x5827, 0x14},
{0x5828, 0x54},
{0x5829, 0x25},
{0x582a, 0x33},
{0x582b, 0x33},
{0x582c, 0x34},
{0x582d, 0x16},
{0x582e, 0x24},
{0x582f, 0x41},
{0x5830, 0x50},
{0x5831, 0x42},
{0x5832, 0x15},
{0x5833, 0x25},
{0x5834, 0x34},
{0x5835, 0x33},
{0x5836, 0x24},
{0x5837, 0x26},
{0x5838, 0x54},
{0x5839, 0x25},
{0x583a, 0x15},
{0x583b, 0x25},
{0x583c, 0x53},
{0x583d, 0xCF},
{0x3a0f, 0x30},
{0x3a10, 0x28},
{0x3a1b, 0x30},
{0x3a1e, 0x28},
{0x3a11, 0x60},
{0x3a1f, 0x14},
{0x5302, 0x28},
{0x5303, 0x20},
{0x5306, 0x1c}, //de-noise offset 1
{0x5307, 0x28}, //de-noise offset 2
{0x4002, 0xc5},
{0x4003, 0x81},
{0x4005, 0x12},
{0x5688, 0x11},
{0x5689, 0x11},
{0x568a, 0x11},
{0x568b, 0x11},
{0x568c, 0x11},
{0x568d, 0x11},
{0x568e, 0x11},
{0x568f, 0x11},
{0x5580, 0x06},
{0x5588, 0x00},
{0x5583, 0x40},
{0x5584, 0x2c},
{ISP_CONTROL_01, 0x83}, // turn color matrix, awb and SDE
{REGLIST_TAIL, 0x00}, // tail
};
static const DRAM_ATTR uint16_t sensor_fmt_jpeg[][2] = {
{FORMAT_CTRL, 0x00}, // YUV422
{FORMAT_CTRL00, 0x30}, // YUYV
{0x3002, 0x00},//0x1c to 0x00 !!!
{0x3006, 0xff},//0xc3 to 0xff !!!
{0x471c, 0x50},//0xd0 to 0x50 !!!
{REGLIST_TAIL, 0x00}, // tail
};
static const DRAM_ATTR uint16_t sensor_fmt_raw[][2] = {
{FORMAT_CTRL00, 0x00}, // RAW
{REGLIST_TAIL, 0x00}
};
static const DRAM_ATTR uint16_t sensor_fmt_grayscale[][2] = {
{FORMAT_CTRL, 0x00}, // YUV422
{FORMAT_CTRL00, 0x10}, // Y8
{REGLIST_TAIL, 0x00}
};
static const DRAM_ATTR uint16_t sensor_fmt_yuv422[][2] = {
{FORMAT_CTRL, 0x00}, // YUV422
{FORMAT_CTRL00, 0x30}, // YUYV
{REGLIST_TAIL, 0x00}
};
static const DRAM_ATTR uint16_t sensor_fmt_rgb565[][2] = {
{FORMAT_CTRL, 0x01}, // RGB
{FORMAT_CTRL00, 0x61}, // RGB565 (BGR)
{REGLIST_TAIL, 0x00}
};
static const DRAM_ATTR uint8_t sensor_saturation_levels[9][11] = {
{0x1d, 0x60, 0x03, 0x07, 0x48, 0x4f, 0x4b, 0x40, 0x0b, 0x01, 0x98},//-4
{0x1d, 0x60, 0x03, 0x08, 0x54, 0x5c, 0x58, 0x4b, 0x0d, 0x01, 0x98},//-3
{0x1d, 0x60, 0x03, 0x0a, 0x60, 0x6a, 0x64, 0x56, 0x0e, 0x01, 0x98},//-2
{0x1d, 0x60, 0x03, 0x0b, 0x6c, 0x77, 0x70, 0x60, 0x10, 0x01, 0x98},//-1
{0x1d, 0x60, 0x03, 0x0c, 0x78, 0x84, 0x7d, 0x6b, 0x12, 0x01, 0x98},//0
{0x1d, 0x60, 0x03, 0x0d, 0x84, 0x91, 0x8a, 0x76, 0x14, 0x01, 0x98},//+1
{0x1d, 0x60, 0x03, 0x0e, 0x90, 0x9e, 0x96, 0x80, 0x16, 0x01, 0x98},//+2
{0x1d, 0x60, 0x03, 0x10, 0x9c, 0xac, 0xa2, 0x8b, 0x17, 0x01, 0x98},//+3
{0x1d, 0x60, 0x03, 0x11, 0xa8, 0xb9, 0xaf, 0x96, 0x19, 0x01, 0x98},//+4
};
static const DRAM_ATTR uint8_t sensor_special_effects[7][4] = {
{0x06, 0x40, 0x2c, 0x08},//Normal
{0x46, 0x40, 0x28, 0x08},//Negative
{0x1e, 0x80, 0x80, 0x08},//Grayscale
{0x1e, 0x80, 0xc0, 0x08},//Red Tint
{0x1e, 0x60, 0x60, 0x08},//Green Tint
{0x1e, 0xa0, 0x40, 0x08},//Blue Tint
{0x1e, 0x40, 0xa0, 0x08},//Sepia
};
#endif
@@ -0,0 +1,37 @@
#ifndef __OV5640_H__
#define __OV5640_H__
#include "sensor.h"
/**
* @brief Detect sensor pid
*
* @param slv_addr SCCB address
* @param id Detection result
* @return
* 0: Can't detect this sensor
* Nonzero: This sensor has been detected
*/
int esp32_camera_ov5640_detect(int slv_addr, sensor_id_t *id);
/**
* @brief initialize sensor function pointers
*
* @param sensor pointer of sensor
* @return
* Always 0
*/
int esp32_camera_ov5640_init(sensor_t *sensor);
// Autofocus function implementations (in ov5640_af.c)
#if defined(CONFIG_CAMERA_AF_SUPPORT) && CONFIG_CAMERA_AF_SUPPORT
int ov5640_af_is_supported(sensor_t *sensor);
int ov5640_af_init(sensor_t *sensor, uint32_t timeout_ms);
int ov5640_af_set_mode(sensor_t *sensor, int mode);
int ov5640_af_trigger(sensor_t *sensor);
int ov5640_af_get_status(sensor_t *sensor, uint8_t *out_raw, bool *out_focused, bool *out_busy);
int ov5640_af_set_manual_position(sensor_t *sensor, uint16_t position);
#endif
#endif // __OV5640_H__
@@ -0,0 +1,267 @@
#pragma once
#include <stdint.h>
/*
* OV5640 AF firmware blob.
*
* Firmware payload loaded into the OV5640 AF microcontroller memory region.
* Used by the OV5640 autofocus control sequence implemented in this component.
*/
static const uint8_t ov5640_af_firmware[] = {
0x02, 0x0f, 0xd6, 0x02, 0x0a, 0x39, 0xc2, 0x01, 0x22, 0x22, 0x00, 0x02, 0x0f, 0xb2, 0xe5, 0x1f,
0x70, 0x72, 0xf5, 0x1e, 0xd2, 0x35, 0xff, 0xef, 0x25, 0xe0, 0x24, 0x4e, 0xf8, 0xe4, 0xf6, 0x08,
0xf6, 0x0f, 0xbf, 0x34, 0xf2, 0x90, 0x0e, 0x93, 0xe4, 0x93, 0xff, 0xe5, 0x4b, 0xc3, 0x9f, 0x50,
0x04, 0x7f, 0x05, 0x80, 0x02, 0x7f, 0xfb, 0x78, 0xbd, 0xa6, 0x07, 0x12, 0x0f, 0x04, 0x40, 0x04,
0x7f, 0x03, 0x80, 0x02, 0x7f, 0x30, 0x78, 0xbc, 0xa6, 0x07, 0xe6, 0x18, 0xf6, 0x08, 0xe6, 0x78,
0xb9, 0xf6, 0x78, 0xbc, 0xe6, 0x78, 0xba, 0xf6, 0x78, 0xbf, 0x76, 0x33, 0xe4, 0x08, 0xf6, 0x78,
0xb8, 0x76, 0x01, 0x75, 0x4a, 0x02, 0x78, 0xb6, 0xf6, 0x08, 0xf6, 0x74, 0xff, 0x78, 0xc1, 0xf6,
0x08, 0xf6, 0x75, 0x1f, 0x01, 0x78, 0xbc, 0xe6, 0x75, 0xf0, 0x05, 0xa4, 0xf5, 0x4b, 0x12, 0x0a,
0xff, 0xc2, 0x37, 0x22, 0x78, 0xb8, 0xe6, 0xd3, 0x94, 0x00, 0x40, 0x02, 0x16, 0x22, 0xe5, 0x1f,
0xb4, 0x05, 0x23, 0xe4, 0xf5, 0x1f, 0xc2, 0x01, 0x78, 0xb6, 0xe6, 0xfe, 0x08, 0xe6, 0xff, 0x78,
0x4e, 0xa6, 0x06, 0x08, 0xa6, 0x07, 0xa2, 0x37, 0xe4, 0x33, 0xf5, 0x3c, 0x90, 0x30, 0x28, 0xf0,
0x75, 0x1e, 0x10, 0xd2, 0x35, 0x22, 0xe5, 0x4b, 0x75, 0xf0, 0x05, 0x84, 0x78, 0xbc, 0xf6, 0x90,
0x0e, 0x8c, 0xe4, 0x93, 0xff, 0x25, 0xe0, 0x24, 0x0a, 0xf8, 0xe6, 0xfc, 0x08, 0xe6, 0xfd, 0x78,
0xbc, 0xe6, 0x25, 0xe0, 0x24, 0x4e, 0xf8, 0xa6, 0x04, 0x08, 0xa6, 0x05, 0xef, 0x12, 0x0f, 0x0b,
0xd3, 0x78, 0xb7, 0x96, 0xee, 0x18, 0x96, 0x40, 0x0d, 0x78, 0xbc, 0xe6, 0x78, 0xb9, 0xf6, 0x78,
0xb6, 0xa6, 0x06, 0x08, 0xa6, 0x07, 0x90, 0x0e, 0x8c, 0xe4, 0x93, 0x12, 0x0f, 0x0b, 0xc3, 0x78,
0xc2, 0x96, 0xee, 0x18, 0x96, 0x50, 0x0d, 0x78, 0xbc, 0xe6, 0x78, 0xba, 0xf6, 0x78, 0xc1, 0xa6,
0x06, 0x08, 0xa6, 0x07, 0x78, 0xb6, 0xe6, 0xfe, 0x08, 0xe6, 0xc3, 0x78, 0xc2, 0x96, 0xff, 0xee,
0x18, 0x96, 0x78, 0xc3, 0xf6, 0x08, 0xa6, 0x07, 0x90, 0x0e, 0x95, 0xe4, 0x18, 0x12, 0x0e, 0xe9,
0x40, 0x02, 0xd2, 0x37, 0x78, 0xbc, 0xe6, 0x08, 0x26, 0x08, 0xf6, 0xe5, 0x1f, 0x64, 0x01, 0x70,
0x4a, 0xe6, 0xc3, 0x78, 0xc0, 0x12, 0x0e, 0xdf, 0x40, 0x05, 0x12, 0x0e, 0xda, 0x40, 0x39, 0x12,
0x0f, 0x02, 0x40, 0x04, 0x7f, 0xfe, 0x80, 0x02, 0x7f, 0x02, 0x78, 0xbd, 0xa6, 0x07, 0x78, 0xb9,
0xe6, 0x24, 0x03, 0x78, 0xbf, 0xf6, 0x78, 0xb9, 0xe6, 0x24, 0xfd, 0x78, 0xc0, 0xf6, 0x12, 0x0f,
0x02, 0x40, 0x06, 0x78, 0xc0, 0xe6, 0xff, 0x80, 0x04, 0x78, 0xbf, 0xe6, 0xff, 0x78, 0xbe, 0xa6,
0x07, 0x75, 0x1f, 0x02, 0x78, 0xb8, 0x76, 0x01, 0x02, 0x02, 0x4a, 0xe5, 0x1f, 0x64, 0x02, 0x60,
0x03, 0x02, 0x02, 0x2a, 0x78, 0xbe, 0xe6, 0xff, 0xc3, 0x78, 0xc0, 0x12, 0x0e, 0xe0, 0x40, 0x08,
0x12, 0x0e, 0xda, 0x50, 0x03, 0x02, 0x02, 0x28, 0x12, 0x0f, 0x02, 0x40, 0x04, 0x7f, 0xff, 0x80,
0x02, 0x7f, 0x01, 0x78, 0xbd, 0xa6, 0x07, 0x78, 0xb9, 0xe6, 0x04, 0x78, 0xbf, 0xf6, 0x78, 0xb9,
0xe6, 0x14, 0x78, 0xc0, 0xf6, 0x18, 0x12, 0x0f, 0x04, 0x40, 0x04, 0xe6, 0xff, 0x80, 0x02, 0x7f,
0x00, 0x78, 0xbf, 0xa6, 0x07, 0xd3, 0x08, 0xe6, 0x64, 0x80, 0x94, 0x80, 0x40, 0x04, 0xe6, 0xff,
0x80, 0x02, 0x7f, 0x00, 0x78, 0xc0, 0xa6, 0x07, 0xc3, 0x18, 0xe6, 0x64, 0x80, 0x94, 0xb3, 0x50,
0x04, 0xe6, 0xff, 0x80, 0x02, 0x7f, 0x33, 0x78, 0xbf, 0xa6, 0x07, 0xc3, 0x08, 0xe6, 0x64, 0x80,
0x94, 0xb3, 0x50, 0x04, 0xe6, 0xff, 0x80, 0x02, 0x7f, 0x33, 0x78, 0xc0, 0xa6, 0x07, 0x12, 0x0f,
0x02, 0x40, 0x06, 0x78, 0xc0, 0xe6, 0xff, 0x80, 0x04, 0x78, 0xbf, 0xe6, 0xff, 0x78, 0xbe, 0xa6,
0x07, 0x75, 0x1f, 0x03, 0x78, 0xb8, 0x76, 0x01, 0x80, 0x20, 0xe5, 0x1f, 0x64, 0x03, 0x70, 0x26,
0x78, 0xbe, 0xe6, 0xff, 0xc3, 0x78, 0xc0, 0x12, 0x0e, 0xe0, 0x40, 0x05, 0x12, 0x0e, 0xda, 0x40,
0x09, 0x78, 0xb9, 0xe6, 0x78, 0xbe, 0xf6, 0x75, 0x1f, 0x04, 0x78, 0xbe, 0xe6, 0x75, 0xf0, 0x05,
0xa4, 0xf5, 0x4b, 0x02, 0x0a, 0xff, 0xe5, 0x1f, 0xb4, 0x04, 0x10, 0x90, 0x0e, 0x94, 0xe4, 0x78,
0xc3, 0x12, 0x0e, 0xe9, 0x40, 0x02, 0xd2, 0x37, 0x75, 0x1f, 0x05, 0x22, 0x30, 0x01, 0x03, 0x02,
0x04, 0xc0, 0x30, 0x02, 0x03, 0x02, 0x04, 0xc0, 0x90, 0x51, 0xa5, 0xe0, 0x78, 0x93, 0xf6, 0xa3,
0xe0, 0x08, 0xf6, 0xa3, 0xe0, 0x08, 0xf6, 0xe5, 0x1f, 0x70, 0x3c, 0x75, 0x1e, 0x20, 0xd2, 0x35,
0x12, 0x0c, 0x7a, 0x78, 0x7e, 0xa6, 0x06, 0x08, 0xa6, 0x07, 0x78, 0x8b, 0xa6, 0x09, 0x18, 0x76,
0x01, 0x12, 0x0c, 0x5b, 0x78, 0x4e, 0xa6, 0x06, 0x08, 0xa6, 0x07, 0x78, 0x8b, 0xe6, 0x78, 0x6e,
0xf6, 0x75, 0x1f, 0x01, 0x78, 0x93, 0xe6, 0x78, 0x90, 0xf6, 0x78, 0x94, 0xe6, 0x78, 0x91, 0xf6,
0x78, 0x95, 0xe6, 0x78, 0x92, 0xf6, 0x22, 0x79, 0x90, 0xe7, 0xd3, 0x78, 0x93, 0x96, 0x40, 0x05,
0xe7, 0x96, 0xff, 0x80, 0x08, 0xc3, 0x79, 0x93, 0xe7, 0x78, 0x90, 0x96, 0xff, 0x78, 0x88, 0x76,
0x00, 0x08, 0xa6, 0x07, 0x79, 0x91, 0xe7, 0xd3, 0x78, 0x94, 0x96, 0x40, 0x05, 0xe7, 0x96, 0xff,
0x80, 0x08, 0xc3, 0x79, 0x94, 0xe7, 0x78, 0x91, 0x96, 0xff, 0x12, 0x0c, 0x8e, 0x79, 0x92, 0xe7,
0xd3, 0x78, 0x95, 0x96, 0x40, 0x05, 0xe7, 0x96, 0xff, 0x80, 0x08, 0xc3, 0x79, 0x95, 0xe7, 0x78,
0x92, 0x96, 0xff, 0x12, 0x0c, 0x8e, 0x12, 0x0c, 0x5b, 0x78, 0x8a, 0xe6, 0x25, 0xe0, 0x24, 0x4e,
0xf8, 0xa6, 0x06, 0x08, 0xa6, 0x07, 0x78, 0x8a, 0xe6, 0x24, 0x6e, 0xf8, 0xa6, 0x09, 0x78, 0x8a,
0xe6, 0x24, 0x01, 0xff, 0xe4, 0x33, 0xfe, 0xd3, 0xef, 0x94, 0x0f, 0xee, 0x64, 0x80, 0x94, 0x80,
0x40, 0x04, 0x7f, 0x00, 0x80, 0x05, 0x78, 0x8a, 0xe6, 0x04, 0xff, 0x78, 0x8a, 0xa6, 0x07, 0xe5,
0x1f, 0xb4, 0x01, 0x0a, 0xe6, 0x60, 0x03, 0x02, 0x04, 0xc0, 0x75, 0x1f, 0x02, 0x22, 0x12, 0x0c,
0x7a, 0x78, 0x80, 0xa6, 0x06, 0x08, 0xa6, 0x07, 0x12, 0x0c, 0x7a, 0x78, 0x82, 0xa6, 0x06, 0x08,
0xa6, 0x07, 0x78, 0x6e, 0xe6, 0x78, 0x8c, 0xf6, 0x78, 0x6e, 0xe6, 0x78, 0x8d, 0xf6, 0x7f, 0x01,
0xef, 0x25, 0xe0, 0x24, 0x4f, 0xf9, 0xc3, 0x78, 0x81, 0xe6, 0x97, 0x18, 0xe6, 0x19, 0x97, 0x50,
0x0a, 0x12, 0x0c, 0x82, 0x78, 0x80, 0xa6, 0x04, 0x08, 0xa6, 0x05, 0x74, 0x6e, 0x2f, 0xf9, 0x78,
0x8c, 0xe6, 0xc3, 0x97, 0x50, 0x08, 0x74, 0x6e, 0x2f, 0xf8, 0xe6, 0x78, 0x8c, 0xf6, 0xef, 0x25,
0xe0, 0x24, 0x4f, 0xf9, 0xd3, 0x78, 0x83, 0xe6, 0x97, 0x18, 0xe6, 0x19, 0x97, 0x40, 0x0a, 0x12,
0x0c, 0x82, 0x78, 0x82, 0xa6, 0x04, 0x08, 0xa6, 0x05, 0x74, 0x6e, 0x2f, 0xf9, 0x78, 0x8d, 0xe6,
0xd3, 0x97, 0x40, 0x08, 0x74, 0x6e, 0x2f, 0xf8, 0xe6, 0x78, 0x8d, 0xf6, 0x0f, 0xef, 0x64, 0x10,
0x70, 0x9e, 0xc3, 0x79, 0x81, 0xe7, 0x78, 0x83, 0x96, 0xff, 0x19, 0xe7, 0x18, 0x96, 0x78, 0x84,
0xf6, 0x08, 0xa6, 0x07, 0xc3, 0x79, 0x8c, 0xe7, 0x78, 0x8d, 0x96, 0x08, 0xf6, 0xd3, 0x79, 0x81,
0xe7, 0x78, 0x7f, 0x96, 0x19, 0xe7, 0x18, 0x96, 0x40, 0x05, 0x09, 0xe7, 0x08, 0x80, 0x06, 0xc3,
0x79, 0x7f, 0xe7, 0x78, 0x81, 0x96, 0xff, 0x19, 0xe7, 0x18, 0x96, 0xfe, 0x78, 0x86, 0xa6, 0x06,
0x08, 0xa6, 0x07, 0x79, 0x8c, 0xe7, 0xd3, 0x78, 0x8b, 0x96, 0x40, 0x05, 0xe7, 0x96, 0xff, 0x80,
0x08, 0xc3, 0x79, 0x8b, 0xe7, 0x78, 0x8c, 0x96, 0xff, 0x78, 0x8f, 0xa6, 0x07, 0xe5, 0x1f, 0x64,
0x02, 0x70, 0x69, 0x90, 0x0e, 0x91, 0x93, 0xff, 0x18, 0xe6, 0xc3, 0x9f, 0x50, 0x72, 0x12, 0x0c,
0x4a, 0x12, 0x0c, 0x2f, 0x90, 0x0e, 0x8e, 0x12, 0x0c, 0x38, 0x78, 0x80, 0x12, 0x0c, 0x6b, 0x7b,
0x04, 0x12, 0x0c, 0x1d, 0xc3, 0x12, 0x06, 0x45, 0x50, 0x56, 0x90, 0x0e, 0x92, 0xe4, 0x93, 0xff,
0x78, 0x8f, 0xe6, 0x9f, 0x40, 0x02, 0x80, 0x11, 0x90, 0x0e, 0x90, 0xe4, 0x93, 0xff, 0xd3, 0x78,
0x89, 0xe6, 0x9f, 0x18, 0xe6, 0x94, 0x00, 0x40, 0x03, 0x75, 0x1f, 0x05, 0x12, 0x0c, 0x4a, 0x12,
0x0c, 0x2f, 0x90, 0x0e, 0x8f, 0x12, 0x0c, 0x38, 0x78, 0x7e, 0x12, 0x0c, 0x6b, 0x7b, 0x40, 0x12,
0x0c, 0x1d, 0xd3, 0x12, 0x06, 0x45, 0x40, 0x18, 0x75, 0x1f, 0x05, 0x22, 0xe5, 0x1f, 0xb4, 0x05,
0x0f, 0xd2, 0x01, 0xc2, 0x02, 0xe4, 0xf5, 0x1f, 0xf5, 0x1e, 0xd2, 0x35, 0xd2, 0x33, 0xd2, 0x36,
0x22, 0xef, 0x8d, 0xf0, 0xa4, 0xa8, 0xf0, 0xcf, 0x8c, 0xf0, 0xa4, 0x28, 0xce, 0x8d, 0xf0, 0xa4,
0x2e, 0xfe, 0x22, 0xbc, 0x00, 0x0b, 0xbe, 0x00, 0x29, 0xef, 0x8d, 0xf0, 0x84, 0xff, 0xad, 0xf0,
0x22, 0xe4, 0xcc, 0xf8, 0x75, 0xf0, 0x08, 0xef, 0x2f, 0xff, 0xee, 0x33, 0xfe, 0xec, 0x33, 0xfc,
0xee, 0x9d, 0xec, 0x98, 0x40, 0x05, 0xfc, 0xee, 0x9d, 0xfe, 0x0f, 0xd5, 0xf0, 0xe9, 0xe4, 0xce,
0xfd, 0x22, 0xed, 0xf8, 0xf5, 0xf0, 0xee, 0x84, 0x20, 0xd2, 0x1c, 0xfe, 0xad, 0xf0, 0x75, 0xf0,
0x08, 0xef, 0x2f, 0xff, 0xed, 0x33, 0xfd, 0x40, 0x07, 0x98, 0x50, 0x06, 0xd5, 0xf0, 0xf2, 0x22,
0xc3, 0x98, 0xfd, 0x0f, 0xd5, 0xf0, 0xea, 0x22, 0xe8, 0x8f, 0xf0, 0xa4, 0xcc, 0x8b, 0xf0, 0xa4,
0x2c, 0xfc, 0xe9, 0x8e, 0xf0, 0xa4, 0x2c, 0xfc, 0x8a, 0xf0, 0xed, 0xa4, 0x2c, 0xfc, 0xea, 0x8e,
0xf0, 0xa4, 0xcd, 0xa8, 0xf0, 0x8b, 0xf0, 0xa4, 0x2d, 0xcc, 0x38, 0x25, 0xf0, 0xfd, 0xe9, 0x8f,
0xf0, 0xa4, 0x2c, 0xcd, 0x35, 0xf0, 0xfc, 0xeb, 0x8e, 0xf0, 0xa4, 0xfe, 0xa9, 0xf0, 0xeb, 0x8f,
0xf0, 0xa4, 0xcf, 0xc5, 0xf0, 0x2e, 0xcd, 0x39, 0xfe, 0xe4, 0x3c, 0xfc, 0xea, 0xa4, 0x2d, 0xce,
0x35, 0xf0, 0xfd, 0xe4, 0x3c, 0xfc, 0x22, 0x75, 0xf0, 0x08, 0x75, 0x82, 0x00, 0xef, 0x2f, 0xff,
0xee, 0x33, 0xfe, 0xcd, 0x33, 0xcd, 0xcc, 0x33, 0xcc, 0xc5, 0x82, 0x33, 0xc5, 0x82, 0x9b, 0xed,
0x9a, 0xec, 0x99, 0xe5, 0x82, 0x98, 0x40, 0x0c, 0xf5, 0x82, 0xee, 0x9b, 0xfe, 0xed, 0x9a, 0xfd,
0xec, 0x99, 0xfc, 0x0f, 0xd5, 0xf0, 0xd6, 0xe4, 0xce, 0xfb, 0xe4, 0xcd, 0xfa, 0xe4, 0xcc, 0xf9,
0xa8, 0x82, 0x22, 0xb8, 0x00, 0xc1, 0xb9, 0x00, 0x59, 0xba, 0x00, 0x2d, 0xec, 0x8b, 0xf0, 0x84,
0xcf, 0xce, 0xcd, 0xfc, 0xe5, 0xf0, 0xcb, 0xf9, 0x78, 0x18, 0xef, 0x2f, 0xff, 0xee, 0x33, 0xfe,
0xed, 0x33, 0xfd, 0xec, 0x33, 0xfc, 0xeb, 0x33, 0xfb, 0x10, 0xd7, 0x03, 0x99, 0x40, 0x04, 0xeb,
0x99, 0xfb, 0x0f, 0xd8, 0xe5, 0xe4, 0xf9, 0xfa, 0x22, 0x78, 0x18, 0xef, 0x2f, 0xff, 0xee, 0x33,
0xfe, 0xed, 0x33, 0xfd, 0xec, 0x33, 0xfc, 0xc9, 0x33, 0xc9, 0x10, 0xd7, 0x05, 0x9b, 0xe9, 0x9a,
0x40, 0x07, 0xec, 0x9b, 0xfc, 0xe9, 0x9a, 0xf9, 0x0f, 0xd8, 0xe0, 0xe4, 0xc9, 0xfa, 0xe4, 0xcc,
0xfb, 0x22, 0x75, 0xf0, 0x10, 0xef, 0x2f, 0xff, 0xee, 0x33, 0xfe, 0xed, 0x33, 0xfd, 0xcc, 0x33,
0xcc, 0xc8, 0x33, 0xc8, 0x10, 0xd7, 0x07, 0x9b, 0xec, 0x9a, 0xe8, 0x99, 0x40, 0x0a, 0xed, 0x9b,
0xfd, 0xec, 0x9a, 0xfc, 0xe8, 0x99, 0xf8, 0x0f, 0xd5, 0xf0, 0xda, 0xe4, 0xcd, 0xfb, 0xe4, 0xcc,
0xfa, 0xe4, 0xc8, 0xf9, 0x22, 0xeb, 0x9f, 0xf5, 0xf0, 0xea, 0x9e, 0x42, 0xf0, 0xe9, 0x9d, 0x42,
0xf0, 0xe8, 0x9c, 0x45, 0xf0, 0x22, 0xe8, 0x60, 0x0f, 0xec, 0xc3, 0x13, 0xfc, 0xed, 0x13, 0xfd,
0xee, 0x13, 0xfe, 0xef, 0x13, 0xff, 0xd8, 0xf1, 0x22, 0xe8, 0x60, 0x0f, 0xef, 0xc3, 0x33, 0xff,
0xee, 0x33, 0xfe, 0xed, 0x33, 0xfd, 0xec, 0x33, 0xfc, 0xd8, 0xf1, 0x22, 0xe4, 0x93, 0xfc, 0x74,
0x01, 0x93, 0xfd, 0x74, 0x02, 0x93, 0xfe, 0x74, 0x03, 0x93, 0xff, 0x22, 0xe6, 0xfb, 0x08, 0xe6,
0xf9, 0x08, 0xe6, 0xfa, 0x08, 0xe6, 0xcb, 0xf8, 0x22, 0xec, 0xf6, 0x08, 0xed, 0xf6, 0x08, 0xee,
0xf6, 0x08, 0xef, 0xf6, 0x22, 0xa4, 0x25, 0x82, 0xf5, 0x82, 0xe5, 0xf0, 0x35, 0x83, 0xf5, 0x83,
0x22, 0xd0, 0x83, 0xd0, 0x82, 0xf8, 0xe4, 0x93, 0x70, 0x12, 0x74, 0x01, 0x93, 0x70, 0x0d, 0xa3,
0xa3, 0x93, 0xf8, 0x74, 0x01, 0x93, 0xf5, 0x82, 0x88, 0x83, 0xe4, 0x73, 0x74, 0x02, 0x93, 0x68,
0x60, 0xef, 0xa3, 0xa3, 0xa3, 0x80, 0xdf, 0x90, 0x38, 0x04, 0x78, 0x52, 0x12, 0x0b, 0xfd, 0x90,
0x38, 0x00, 0xe0, 0xfe, 0xa3, 0xe0, 0xfd, 0xed, 0xff, 0xc3, 0x12, 0x0b, 0x9e, 0x90, 0x38, 0x10,
0x12, 0x0b, 0x92, 0x90, 0x38, 0x06, 0x78, 0x54, 0x12, 0x0b, 0xfd, 0x90, 0x38, 0x02, 0xe0, 0xfe,
0xa3, 0xe0, 0xfd, 0xed, 0xff, 0xc3, 0x12, 0x0b, 0x9e, 0x90, 0x38, 0x12, 0x12, 0x0b, 0x92, 0xa3,
0xe0, 0xb4, 0x31, 0x07, 0x78, 0x52, 0x79, 0x52, 0x12, 0x0c, 0x13, 0x90, 0x38, 0x14, 0xe0, 0xb4,
0x71, 0x15, 0x78, 0x52, 0xe6, 0xfe, 0x08, 0xe6, 0x78, 0x02, 0xce, 0xc3, 0x13, 0xce, 0x13, 0xd8,
0xf9, 0x79, 0x53, 0xf7, 0xee, 0x19, 0xf7, 0x90, 0x38, 0x15, 0xe0, 0xb4, 0x31, 0x07, 0x78, 0x54,
0x79, 0x54, 0x12, 0x0c, 0x13, 0x90, 0x38, 0x15, 0xe0, 0xb4, 0x71, 0x15, 0x78, 0x54, 0xe6, 0xfe,
0x08, 0xe6, 0x78, 0x02, 0xce, 0xc3, 0x13, 0xce, 0x13, 0xd8, 0xf9, 0x79, 0x55, 0xf7, 0xee, 0x19,
0xf7, 0x79, 0x52, 0x12, 0x0b, 0xd9, 0x09, 0x12, 0x0b, 0xd9, 0xaf, 0x47, 0x12, 0x0b, 0xb2, 0xe5,
0x44, 0xfb, 0x7a, 0x00, 0xfd, 0x7c, 0x00, 0x12, 0x04, 0xd3, 0x78, 0x5a, 0xa6, 0x06, 0x08, 0xa6,
0x07, 0xaf, 0x45, 0x12, 0x0b, 0xb2, 0xad, 0x03, 0x7c, 0x00, 0x12, 0x04, 0xd3, 0x78, 0x56, 0xa6,
0x06, 0x08, 0xa6, 0x07, 0xaf, 0x48, 0x78, 0x54, 0x12, 0x0b, 0xb4, 0xe5, 0x43, 0xfb, 0xfd, 0x7c,
0x00, 0x12, 0x04, 0xd3, 0x78, 0x5c, 0xa6, 0x06, 0x08, 0xa6, 0x07, 0xaf, 0x46, 0x7e, 0x00, 0x78,
0x54, 0x12, 0x0b, 0xb6, 0xad, 0x03, 0x7c, 0x00, 0x12, 0x04, 0xd3, 0x78, 0x58, 0xa6, 0x06, 0x08,
0xa6, 0x07, 0xc3, 0x78, 0x5b, 0xe6, 0x94, 0x08, 0x18, 0xe6, 0x94, 0x00, 0x50, 0x05, 0x76, 0x00,
0x08, 0x76, 0x08, 0xc3, 0x78, 0x5d, 0xe6, 0x94, 0x08, 0x18, 0xe6, 0x94, 0x00, 0x50, 0x05, 0x76,
0x00, 0x08, 0x76, 0x08, 0x78, 0x5a, 0x12, 0x0b, 0xc6, 0xff, 0xd3, 0x78, 0x57, 0xe6, 0x9f, 0x18,
0xe6, 0x9e, 0x40, 0x0e, 0x78, 0x5a, 0xe6, 0x13, 0xfe, 0x08, 0xe6, 0x78, 0x57, 0x12, 0x0c, 0x08,
0x80, 0x04, 0x7e, 0x00, 0x7f, 0x00, 0x78, 0x5e, 0x12, 0x0b, 0xbe, 0xff, 0xd3, 0x78, 0x59, 0xe6,
0x9f, 0x18, 0xe6, 0x9e, 0x40, 0x0e, 0x78, 0x5c, 0xe6, 0x13, 0xfe, 0x08, 0xe6, 0x78, 0x59, 0x12,
0x0c, 0x08, 0x80, 0x04, 0x7e, 0x00, 0x7f, 0x00, 0xe4, 0xfc, 0xfd, 0x78, 0x62, 0x12, 0x06, 0x99,
0x78, 0x5a, 0x12, 0x0b, 0xc6, 0x78, 0x57, 0x26, 0xff, 0xee, 0x18, 0x36, 0xfe, 0x78, 0x66, 0x12,
0x0b, 0xbe, 0x78, 0x59, 0x26, 0xff, 0xee, 0x18, 0x36, 0xfe, 0xe4, 0xfc, 0xfd, 0x78, 0x6a, 0x12,
0x06, 0x99, 0x12, 0x0b, 0xce, 0x78, 0x66, 0x12, 0x06, 0x8c, 0xd3, 0x12, 0x06, 0x45, 0x40, 0x08,
0x12, 0x0b, 0xce, 0x78, 0x66, 0x12, 0x06, 0x99, 0x78, 0x54, 0x12, 0x0b, 0xd0, 0x78, 0x6a, 0x12,
0x06, 0x8c, 0xd3, 0x12, 0x06, 0x45, 0x40, 0x0a, 0x78, 0x54, 0x12, 0x0b, 0xd0, 0x78, 0x6a, 0x12,
0x06, 0x99, 0x78, 0x61, 0xe6, 0x90, 0x60, 0x01, 0xf0, 0x78, 0x65, 0xe6, 0xa3, 0xf0, 0x78, 0x69,
0xe6, 0xa3, 0xf0, 0x78, 0x55, 0xe6, 0xa3, 0xf0, 0x7d, 0x01, 0x78, 0x61, 0x12, 0x0b, 0xe9, 0x24,
0x01, 0x12, 0x0b, 0xa6, 0x78, 0x65, 0x12, 0x0b, 0xe9, 0x24, 0x02, 0x12, 0x0b, 0xa6, 0x78, 0x69,
0x12, 0x0b, 0xe9, 0x24, 0x03, 0x12, 0x0b, 0xa6, 0x78, 0x6d, 0x12, 0x0b, 0xe9, 0x24, 0x04, 0x12,
0x0b, 0xa6, 0x0d, 0xbd, 0x05, 0xd4, 0xc2, 0x0e, 0xc2, 0x06, 0x22, 0x85, 0x08, 0x41, 0x90, 0x30,
0x24, 0xe0, 0xf5, 0x3d, 0xa3, 0xe0, 0xf5, 0x3e, 0xa3, 0xe0, 0xf5, 0x3f, 0xa3, 0xe0, 0xf5, 0x40,
0xa3, 0xe0, 0xf5, 0x3c, 0xd2, 0x34, 0xe5, 0x41, 0x12, 0x06, 0xb1, 0x09, 0x31, 0x03, 0x09, 0x35,
0x04, 0x09, 0x3b, 0x05, 0x09, 0x3e, 0x06, 0x09, 0x41, 0x07, 0x09, 0x4a, 0x08, 0x09, 0x5b, 0x12,
0x09, 0x73, 0x18, 0x09, 0x89, 0x19, 0x09, 0x5e, 0x1a, 0x09, 0x6a, 0x1b, 0x09, 0xad, 0x80, 0x09,
0xb2, 0x81, 0x0a, 0x1d, 0x8f, 0x0a, 0x09, 0x90, 0x0a, 0x1d, 0x91, 0x0a, 0x1d, 0x92, 0x0a, 0x1d,
0x93, 0x0a, 0x1d, 0x94, 0x0a, 0x1d, 0x98, 0x0a, 0x17, 0x9f, 0x0a, 0x1a, 0xec, 0x00, 0x00, 0x0a,
0x38, 0x12, 0x0f, 0x74, 0x22, 0x12, 0x0f, 0x74, 0xd2, 0x03, 0x22, 0xd2, 0x03, 0x22, 0xc2, 0x03,
0x22, 0xa2, 0x37, 0xe4, 0x33, 0xf5, 0x3c, 0x02, 0x0a, 0x1d, 0xc2, 0x01, 0xc2, 0x02, 0xc2, 0x03,
0x12, 0x0d, 0x0d, 0x75, 0x1e, 0x70, 0xd2, 0x35, 0x02, 0x0a, 0x1d, 0x02, 0x0a, 0x04, 0x85, 0x40,
0x4a, 0x85, 0x3c, 0x4b, 0x12, 0x0a, 0xff, 0x02, 0x0a, 0x1d, 0x85, 0x4a, 0x40, 0x85, 0x4b, 0x3c,
0x02, 0x0a, 0x1d, 0xe4, 0xf5, 0x22, 0xf5, 0x23, 0x85, 0x40, 0x31, 0x85, 0x3f, 0x30, 0x85, 0x3e,
0x2f, 0x85, 0x3d, 0x2e, 0x12, 0x0f, 0x46, 0x80, 0x1f, 0x75, 0x22, 0x00, 0x75, 0x23, 0x01, 0x74,
0xff, 0xf5, 0x2d, 0xf5, 0x2c, 0xf5, 0x2b, 0xf5, 0x2a, 0x12, 0x0f, 0x46, 0x85, 0x2d, 0x40, 0x85,
0x2c, 0x3f, 0x85, 0x2b, 0x3e, 0x85, 0x2a, 0x3d, 0xe4, 0xf5, 0x3c, 0x80, 0x70, 0x12, 0x0f, 0x16,
0x80, 0x6b, 0x85, 0x3d, 0x45, 0x85, 0x3e, 0x46, 0xe5, 0x47, 0xc3, 0x13, 0xff, 0xe5, 0x45, 0xc3,
0x9f, 0x50, 0x02, 0x8f, 0x45, 0xe5, 0x48, 0xc3, 0x13, 0xff, 0xe5, 0x46, 0xc3, 0x9f, 0x50, 0x02,
0x8f, 0x46, 0xe5, 0x47, 0xc3, 0x13, 0xff, 0xfd, 0xe5, 0x45, 0x2d, 0xfd, 0xe4, 0x33, 0xfc, 0xe5,
0x44, 0x12, 0x0f, 0x90, 0x40, 0x05, 0xe5, 0x44, 0x9f, 0xf5, 0x45, 0xe5, 0x48, 0xc3, 0x13, 0xff,
0xfd, 0xe5, 0x46, 0x2d, 0xfd, 0xe4, 0x33, 0xfc, 0xe5, 0x43, 0x12, 0x0f, 0x90, 0x40, 0x05, 0xe5,
0x43, 0x9f, 0xf5, 0x46, 0x12, 0x06, 0xd7, 0x80, 0x14, 0x85, 0x40, 0x48, 0x85, 0x3f, 0x47, 0x85,
0x3e, 0x46, 0x85, 0x3d, 0x45, 0x80, 0x06, 0x02, 0x06, 0xd7, 0x12, 0x0d, 0x7e, 0x90, 0x30, 0x24,
0xe5, 0x3d, 0xf0, 0xa3, 0xe5, 0x3e, 0xf0, 0xa3, 0xe5, 0x3f, 0xf0, 0xa3, 0xe5, 0x40, 0xf0, 0xa3,
0xe5, 0x3c, 0xf0, 0x90, 0x30, 0x23, 0xe4, 0xf0, 0x22, 0xc0, 0xe0, 0xc0, 0x83, 0xc0, 0x82, 0xc0,
0xd0, 0x90, 0x3f, 0x0c, 0xe0, 0xf5, 0x32, 0xe5, 0x32, 0x30, 0xe3, 0x74, 0x30, 0x36, 0x66, 0x90,
0x60, 0x19, 0xe0, 0xf5, 0x0a, 0xa3, 0xe0, 0xf5, 0x0b, 0x90, 0x60, 0x1d, 0xe0, 0xf5, 0x14, 0xa3,
0xe0, 0xf5, 0x15, 0x90, 0x60, 0x21, 0xe0, 0xf5, 0x0c, 0xa3, 0xe0, 0xf5, 0x0d, 0x90, 0x60, 0x29,
0xe0, 0xf5, 0x0e, 0xa3, 0xe0, 0xf5, 0x0f, 0x90, 0x60, 0x31, 0xe0, 0xf5, 0x10, 0xa3, 0xe0, 0xf5,
0x11, 0x90, 0x60, 0x39, 0xe0, 0xf5, 0x12, 0xa3, 0xe0, 0xf5, 0x13, 0x30, 0x01, 0x06, 0x30, 0x33,
0x03, 0xd3, 0x80, 0x01, 0xc3, 0x92, 0x09, 0x30, 0x02, 0x06, 0x30, 0x33, 0x03, 0xd3, 0x80, 0x01,
0xc3, 0x92, 0x0a, 0x30, 0x33, 0x0c, 0x30, 0x03, 0x09, 0x20, 0x02, 0x06, 0x20, 0x01, 0x03, 0xd3,
0x80, 0x01, 0xc3, 0x92, 0x0b, 0x90, 0x30, 0x01, 0xe0, 0x44, 0x40, 0xf0, 0xe0, 0x54, 0xbf, 0xf0,
0xe5, 0x32, 0x30, 0xe1, 0x14, 0x30, 0x34, 0x11, 0x90, 0x30, 0x22, 0xe0, 0xf5, 0x08, 0xe4, 0xf0,
0x30, 0x00, 0x03, 0xd3, 0x80, 0x01, 0xc3, 0x92, 0x08, 0xe5, 0x32, 0x30, 0xe5, 0x12, 0x90, 0x56,
0xa1, 0xe0, 0xf5, 0x09, 0x30, 0x31, 0x09, 0x30, 0x05, 0x03, 0xd3, 0x80, 0x01, 0xc3, 0x92, 0x0d,
0x90, 0x3f, 0x0c, 0xe5, 0x32, 0xf0, 0xd0, 0xd0, 0xd0, 0x82, 0xd0, 0x83, 0xd0, 0xe0, 0x32, 0x90,
0x0e, 0x7e, 0xe4, 0x93, 0xfe, 0x74, 0x01, 0x93, 0xff, 0xc3, 0x90, 0x0e, 0x7c, 0x74, 0x01, 0x93,
0x9f, 0xff, 0xe4, 0x93, 0x9e, 0xfe, 0xe4, 0x8f, 0x3b, 0x8e, 0x3a, 0xf5, 0x39, 0xf5, 0x38, 0xab,
0x3b, 0xaa, 0x3a, 0xa9, 0x39, 0xa8, 0x38, 0xaf, 0x4b, 0xfc, 0xfd, 0xfe, 0x12, 0x05, 0x28, 0x12,
0x0d, 0xe1, 0xe4, 0x7b, 0xff, 0xfa, 0xf9, 0xf8, 0x12, 0x05, 0xb3, 0x12, 0x0d, 0xe1, 0x90, 0x0e,
0x69, 0xe4, 0x12, 0x0d, 0xf6, 0x12, 0x0d, 0xe1, 0xe4, 0x85, 0x4a, 0x37, 0xf5, 0x36, 0xf5, 0x35,
0xf5, 0x34, 0xaf, 0x37, 0xae, 0x36, 0xad, 0x35, 0xac, 0x34, 0xa3, 0x12, 0x0d, 0xf6, 0x8f, 0x37,
0x8e, 0x36, 0x8d, 0x35, 0x8c, 0x34, 0xe5, 0x3b, 0x45, 0x37, 0xf5, 0x3b, 0xe5, 0x3a, 0x45, 0x36,
0xf5, 0x3a, 0xe5, 0x39, 0x45, 0x35, 0xf5, 0x39, 0xe5, 0x38, 0x45, 0x34, 0xf5, 0x38, 0xe4, 0xf5,
0x22, 0xf5, 0x23, 0x85, 0x3b, 0x31, 0x85, 0x3a, 0x30, 0x85, 0x39, 0x2f, 0x85, 0x38, 0x2e, 0x02,
0x0f, 0x46, 0xe0, 0xa3, 0xe0, 0x75, 0xf0, 0x02, 0xa4, 0xff, 0xae, 0xf0, 0xc3, 0x08, 0xe6, 0x9f,
0xf6, 0x18, 0xe6, 0x9e, 0xf6, 0x22, 0xff, 0xe5, 0xf0, 0x34, 0x60, 0x8f, 0x82, 0xf5, 0x83, 0xec,
0xf0, 0x22, 0x78, 0x52, 0x7e, 0x00, 0xe6, 0xfc, 0x08, 0xe6, 0xfd, 0x02, 0x04, 0xc1, 0xe4, 0xfc,
0xfd, 0x12, 0x06, 0x99, 0x78, 0x5c, 0xe6, 0xc3, 0x13, 0xfe, 0x08, 0xe6, 0x13, 0x22, 0x78, 0x52,
0xe6, 0xfe, 0x08, 0xe6, 0xff, 0xe4, 0xfc, 0xfd, 0x22, 0xe7, 0xc4, 0xf8, 0x54, 0xf0, 0xc8, 0x68,
0xf7, 0x09, 0xe7, 0xc4, 0x54, 0x0f, 0x48, 0xf7, 0x22, 0xe6, 0xfc, 0xed, 0x75, 0xf0, 0x04, 0xa4,
0x22, 0x12, 0x06, 0x7c, 0x8f, 0x48, 0x8e, 0x47, 0x8d, 0x46, 0x8c, 0x45, 0x22, 0xe0, 0xfe, 0xa3,
0xe0, 0xfd, 0xee, 0xf6, 0xed, 0x08, 0xf6, 0x22, 0x13, 0xff, 0xc3, 0xe6, 0x9f, 0xff, 0x18, 0xe6,
0x9e, 0xfe, 0x22, 0xe6, 0xc3, 0x13, 0xf7, 0x08, 0xe6, 0x13, 0x09, 0xf7, 0x22, 0xad, 0x39, 0xac,
0x38, 0xfa, 0xf9, 0xf8, 0x12, 0x05, 0x28, 0x8f, 0x3b, 0x8e, 0x3a, 0x8d, 0x39, 0x8c, 0x38, 0xab,
0x37, 0xaa, 0x36, 0xa9, 0x35, 0xa8, 0x34, 0x22, 0x93, 0xff, 0xe4, 0xfc, 0xfd, 0xfe, 0x12, 0x05,
0x28, 0x8f, 0x37, 0x8e, 0x36, 0x8d, 0x35, 0x8c, 0x34, 0x22, 0x78, 0x84, 0xe6, 0xfe, 0x08, 0xe6,
0xff, 0xe4, 0x8f, 0x37, 0x8e, 0x36, 0xf5, 0x35, 0xf5, 0x34, 0x22, 0x90, 0x0e, 0x8c, 0xe4, 0x93,
0x25, 0xe0, 0x24, 0x0a, 0xf8, 0xe6, 0xfe, 0x08, 0xe6, 0xff, 0x22, 0xe6, 0xfe, 0x08, 0xe6, 0xff,
0xe4, 0x8f, 0x3b, 0x8e, 0x3a, 0xf5, 0x39, 0xf5, 0x38, 0x22, 0x78, 0x4e, 0xe6, 0xfe, 0x08, 0xe6,
0xff, 0x22, 0xef, 0x25, 0xe0, 0x24, 0x4e, 0xf8, 0xe6, 0xfc, 0x08, 0xe6, 0xfd, 0x22, 0x78, 0x89,
0xef, 0x26, 0xf6, 0x18, 0xe4, 0x36, 0xf6, 0x22, 0x75, 0x89, 0x03, 0x75, 0xa8, 0x01, 0x75, 0xb8,
0x04, 0x75, 0x34, 0xff, 0x75, 0x35, 0x0e, 0x75, 0x36, 0x15, 0x75, 0x37, 0x0d, 0x12, 0x0e, 0x9a,
0x12, 0x00, 0x09, 0x12, 0x0f, 0x16, 0x12, 0x00, 0x06, 0xd2, 0x00, 0xd2, 0x34, 0xd2, 0xaf, 0x75,
0x34, 0xff, 0x75, 0x35, 0x0e, 0x75, 0x36, 0x49, 0x75, 0x37, 0x03, 0x12, 0x0e, 0x9a, 0x30, 0x08,
0x09, 0xc2, 0x34, 0x12, 0x08, 0xcb, 0xc2, 0x08, 0xd2, 0x34, 0x30, 0x0b, 0x09, 0xc2, 0x36, 0x12,
0x02, 0x6c, 0xc2, 0x0b, 0xd2, 0x36, 0x30, 0x09, 0x09, 0xc2, 0x36, 0x12, 0x00, 0x0e, 0xc2, 0x09,
0xd2, 0x36, 0x30, 0x0e, 0x03, 0x12, 0x06, 0xd7, 0x30, 0x35, 0xd3, 0x90, 0x30, 0x29, 0xe5, 0x1e,
0xf0, 0xb4, 0x10, 0x05, 0x90, 0x30, 0x23, 0xe4, 0xf0, 0xc2, 0x35, 0x80, 0xc1, 0xe4, 0xf5, 0x4b,
0x90, 0x0e, 0x7a, 0x93, 0xff, 0xe4, 0x8f, 0x37, 0xf5, 0x36, 0xf5, 0x35, 0xf5, 0x34, 0xaf, 0x37,
0xae, 0x36, 0xad, 0x35, 0xac, 0x34, 0x90, 0x0e, 0x6a, 0x12, 0x0d, 0xf6, 0x8f, 0x37, 0x8e, 0x36,
0x8d, 0x35, 0x8c, 0x34, 0x90, 0x0e, 0x72, 0x12, 0x06, 0x7c, 0xef, 0x45, 0x37, 0xf5, 0x37, 0xee,
0x45, 0x36, 0xf5, 0x36, 0xed, 0x45, 0x35, 0xf5, 0x35, 0xec, 0x45, 0x34, 0xf5, 0x34, 0xe4, 0xf5,
0x22, 0xf5, 0x23, 0x85, 0x37, 0x31, 0x85, 0x36, 0x30, 0x85, 0x35, 0x2f, 0x85, 0x34, 0x2e, 0x12,
0x0f, 0x46, 0xe4, 0xf5, 0x22, 0xf5, 0x23, 0x90, 0x0e, 0x72, 0x12, 0x0d, 0xea, 0x12, 0x0f, 0x46,
0xe4, 0xf5, 0x22, 0xf5, 0x23, 0x90, 0x0e, 0x6e, 0x12, 0x0d, 0xea, 0x02, 0x0f, 0x46, 0xe5, 0x40,
0x24, 0xf2, 0xf5, 0x37, 0xe5, 0x3f, 0x34, 0x43, 0xf5, 0x36, 0xe5, 0x3e, 0x34, 0xa2, 0xf5, 0x35,
0xe5, 0x3d, 0x34, 0x28, 0xf5, 0x34, 0xe5, 0x37, 0xff, 0xe4, 0xfe, 0xfd, 0xfc, 0x78, 0x18, 0x12,
0x06, 0x69, 0x8f, 0x40, 0x8e, 0x3f, 0x8d, 0x3e, 0x8c, 0x3d, 0xe5, 0x37, 0x54, 0xa0, 0xff, 0xe5,
0x36, 0xfe, 0xe4, 0xfd, 0xfc, 0x78, 0x07, 0x12, 0x06, 0x56, 0x78, 0x10, 0x12, 0x0f, 0x9a, 0xe4,
0xff, 0xfe, 0xe5, 0x35, 0xfd, 0xe4, 0xfc, 0x78, 0x0e, 0x12, 0x06, 0x56, 0x12, 0x0f, 0x9d, 0xe4,
0xff, 0xfe, 0xfd, 0xe5, 0x34, 0xfc, 0x78, 0x18, 0x12, 0x06, 0x56, 0x78, 0x08, 0x12, 0x0f, 0x9a,
0x22, 0x8f, 0x3b, 0x8e, 0x3a, 0x8d, 0x39, 0x8c, 0x38, 0x22, 0x12, 0x06, 0x7c, 0x8f, 0x31, 0x8e,
0x30, 0x8d, 0x2f, 0x8c, 0x2e, 0x22, 0x93, 0xf9, 0xf8, 0x02, 0x06, 0x69, 0x00, 0x00, 0x00, 0x00,
0x12, 0x01, 0x17, 0x08, 0x31, 0x15, 0x53, 0x54, 0x44, 0x20, 0x20, 0x20, 0x20, 0x20, 0x13, 0x01,
0x10, 0x01, 0x56, 0x40, 0x1a, 0x30, 0x29, 0x7e, 0x00, 0x30, 0x04, 0x20, 0xdf, 0x30, 0x05, 0x40,
0xbf, 0x50, 0x03, 0x00, 0xfd, 0x50, 0x27, 0x01, 0xfe, 0x60, 0x00, 0x11, 0x00, 0x3f, 0x05, 0x30,
0x00, 0x3f, 0x06, 0x22, 0x00, 0x3f, 0x01, 0x2a, 0x00, 0x3f, 0x02, 0x00, 0x00, 0x36, 0x06, 0x07,
0x00, 0x3f, 0x0b, 0x0f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x30, 0x01, 0x40, 0xbf, 0x30, 0x01, 0x00,
0xbf, 0x30, 0x29, 0x70, 0x00, 0x3a, 0x00, 0x00, 0xff, 0x3a, 0x00, 0x00, 0xff, 0x36, 0x03, 0x36,
0x02, 0x41, 0x44, 0x58, 0x20, 0x18, 0x10, 0x0a, 0x04, 0x04, 0x00, 0x03, 0xff, 0x64, 0x00, 0x00,
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x04, 0x06, 0x06, 0x00, 0x03, 0x51, 0x00, 0x7a,
0x50, 0x3c, 0x28, 0x1e, 0x10, 0x10, 0x50, 0x2d, 0x28, 0x16, 0x10, 0x10, 0x02, 0x00, 0x10, 0x0c,
0x10, 0x04, 0x0c, 0x6e, 0x06, 0x05, 0x00, 0xa5, 0x5a, 0x00, 0xae, 0x35, 0xaf, 0x36, 0xe4, 0xfd,
0xed, 0xc3, 0x95, 0x37, 0x50, 0x33, 0x12, 0x0f, 0xe2, 0xe4, 0x93, 0xf5, 0x38, 0x74, 0x01, 0x93,
0xf5, 0x39, 0x45, 0x38, 0x60, 0x23, 0x85, 0x39, 0x82, 0x85, 0x38, 0x83, 0xe0, 0xfc, 0x12, 0x0f,
0xe2, 0x74, 0x03, 0x93, 0x52, 0x04, 0x12, 0x0f, 0xe2, 0x74, 0x02, 0x93, 0x42, 0x04, 0x85, 0x39,
0x82, 0x85, 0x38, 0x83, 0xec, 0xf0, 0x0d, 0x80, 0xc7, 0x22, 0x78, 0xbe, 0xe6, 0xd3, 0x08, 0xff,
0xe6, 0x64, 0x80, 0xf8, 0xef, 0x64, 0x80, 0x98, 0x22, 0x93, 0xff, 0x7e, 0x00, 0xe6, 0xfc, 0x08,
0xe6, 0xfd, 0x12, 0x04, 0xc1, 0x78, 0xc1, 0xe6, 0xfc, 0x08, 0xe6, 0xfd, 0xd3, 0xef, 0x9d, 0xee,
0x9c, 0x22, 0x78, 0xbd, 0xd3, 0xe6, 0x64, 0x80, 0x94, 0x80, 0x22, 0x25, 0xe0, 0x24, 0x0a, 0xf8,
0xe6, 0xfe, 0x08, 0xe6, 0xff, 0x22, 0xe5, 0x3c, 0xd3, 0x94, 0x00, 0x40, 0x0b, 0x90, 0x0e, 0x88,
0x12, 0x0b, 0xf1, 0x90, 0x0e, 0x86, 0x80, 0x09, 0x90, 0x0e, 0x82, 0x12, 0x0b, 0xf1, 0x90, 0x0e,
0x80, 0xe4, 0x93, 0xf5, 0x44, 0xa3, 0xe4, 0x93, 0xf5, 0x43, 0xd2, 0x06, 0x30, 0x06, 0x03, 0xd3,
0x80, 0x01, 0xc3, 0x92, 0x0e, 0x22, 0xa2, 0xaf, 0x92, 0x32, 0xc2, 0xaf, 0xe5, 0x23, 0x45, 0x22,
0x90, 0x0e, 0x5d, 0x60, 0x0e, 0x12, 0x0f, 0xcb, 0xe0, 0xf5, 0x2c, 0x12, 0x0f, 0xc8, 0xe0, 0xf5,
0x2d, 0x80, 0x0c, 0x12, 0x0f, 0xcb, 0xe5, 0x30, 0xf0, 0x12, 0x0f, 0xc8, 0xe5, 0x31, 0xf0, 0xa2,
0x32, 0x92, 0xaf, 0x22, 0xd2, 0x01, 0xc2, 0x02, 0xe4, 0xf5, 0x1f, 0xf5, 0x1e, 0xd2, 0x35, 0xd2,
0x33, 0xd2, 0x36, 0xd2, 0x01, 0xc2, 0x02, 0xf5, 0x1f, 0xf5, 0x1e, 0xd2, 0x35, 0xd2, 0x33, 0x22,
0xfb, 0xd3, 0xed, 0x9b, 0x74, 0x80, 0xf8, 0x6c, 0x98, 0x22, 0x12, 0x06, 0x69, 0xe5, 0x40, 0x2f,
0xf5, 0x40, 0xe5, 0x3f, 0x3e, 0xf5, 0x3f, 0xe5, 0x3e, 0x3d, 0xf5, 0x3e, 0xe5, 0x3d, 0x3c, 0xf5,
0x3d, 0x22, 0xc0, 0xe0, 0xc0, 0x83, 0xc0, 0x82, 0x90, 0x3f, 0x0d, 0xe0, 0xf5, 0x33, 0xe5, 0x33,
0xf0, 0xd0, 0x82, 0xd0, 0x83, 0xd0, 0xe0, 0x32, 0x90, 0x0e, 0x5f, 0xe4, 0x93, 0xfe, 0x74, 0x01,
0x93, 0xf5, 0x82, 0x8e, 0x83, 0x22, 0x78, 0x7f, 0xe4, 0xf6, 0xd8, 0xfd, 0x75, 0x81, 0xcd, 0x02,
0x0c, 0x98, 0x8f, 0x82, 0x8e, 0x83, 0x75, 0xf0, 0x04, 0xed, 0x02, 0x06, 0xa5
};
@@ -0,0 +1,213 @@
/*
* OV5640 register definitions.
*/
#ifndef __OV5640_REG_REGS_H__
#define __OV5640_REG_REGS_H__
/* system control registers */
#define SYSTEM_CTROL0 0x3008 // Bit[7]: Software reset
// Bit[6]: Software power down
// Bit[5]: Reserved
// Bit[4]: SRB clock SYNC enable
// Bit[3]: Isolation suspend select
// Bit[2:0]: Not used
#define DRIVE_CAPABILITY 0x302c // Bit[7:6]:
// 00: 1x
// 01: 2x
// 10: 3x
// 11: 4x
#define SC_PLLS_CTRL0 0x303a // Bit[7]: PLLS bypass
#define SC_PLLS_CTRL1 0x303b // Bit[4:0]: PLLS multiplier
#define SC_PLLS_CTRL2 0x303c // Bit[6:4]: PLLS charge pump control
// Bit[3:0]: PLLS system divider
#define SC_PLLS_CTRL3 0x303d // Bit[5:4]: PLLS pre-divider
// 00: 1
// 01: 1.5
// 10: 2
// 11: 3
// Bit[2]: PLLS root-divider - 1
// Bit[1:0]: PLLS seld5
// 00: 1
// 01: 1
// 10: 2
// 11: 2.5
/* AEC/AGC control functions */
#define AEC_PK_MANUAL 0x3503 // AEC Manual Mode Control
// Bit[7:6]: Reserved
// Bit[5]: Gain delay option
// Valid when 0x3503[4]=1’b0
// 0: Delay one frame latch
// 1: One frame latch
// Bit[4:2]: Reserved
// Bit[1]: AGC manual
// 0: Auto enable
// 1: Manual enable
// Bit[0]: AEC manual
// 0: Auto enable
// 1: Manual enable
//gain = {0x350A[1:0], 0x350B[7:0]} / 16
#define X_ADDR_ST_H 0x3800 //Bit[3:0]: X address start[11:8]
#define X_ADDR_ST_L 0x3801 //Bit[7:0]: X address start[7:0]
#define Y_ADDR_ST_H 0x3802 //Bit[2:0]: Y address start[10:8]
#define Y_ADDR_ST_L 0x3803 //Bit[7:0]: Y address start[7:0]
#define X_ADDR_END_H 0x3804 //Bit[3:0]: X address end[11:8]
#define X_ADDR_END_L 0x3805 //Bit[7:0]:
#define Y_ADDR_END_H 0x3806 //Bit[2:0]: Y address end[10:8]
#define Y_ADDR_END_L 0x3807 //Bit[7:0]:
// Size after scaling
#define X_OUTPUT_SIZE_H 0x3808 //Bit[3:0]: DVP output horizontal width[11:8]
#define X_OUTPUT_SIZE_L 0x3809 //Bit[7:0]:
#define Y_OUTPUT_SIZE_H 0x380a //Bit[2:0]: DVP output vertical height[10:8]
#define Y_OUTPUT_SIZE_L 0x380b //Bit[7:0]:
#define X_TOTAL_SIZE_H 0x380c //Bit[3:0]: Total horizontal size[11:8]
#define X_TOTAL_SIZE_L 0x380d //Bit[7:0]:
#define Y_TOTAL_SIZE_H 0x380e //Bit[7:0]: Total vertical size[15:8]
#define Y_TOTAL_SIZE_L 0x380f //Bit[7:0]:
#define X_OFFSET_H 0x3810 //Bit[3:0]: ISP horizontal offset[11:8]
#define X_OFFSET_L 0x3811 //Bit[7:0]:
#define Y_OFFSET_H 0x3812 //Bit[2:0]: ISP vertical offset[10:8]
#define Y_OFFSET_L 0x3813 //Bit[7:0]:
#define X_INCREMENT 0x3814 //Bit[7:4]: Horizontal odd subsample increment
//Bit[3:0]: Horizontal even subsample increment
#define Y_INCREMENT 0x3815 //Bit[7:4]: Vertical odd subsample increment
//Bit[3:0]: Vertical even subsample increment
// Size before scaling
//#define X_INPUT_SIZE (X_ADDR_END - X_ADDR_ST + 1 - (2 * X_OFFSET))
//#define Y_INPUT_SIZE (Y_ADDR_END - Y_ADDR_ST + 1 - (2 * Y_OFFSET))
/* mirror and flip registers */
#define TIMING_TC_REG20 0x3820 // Timing Control Register
// Bit[2:1]: Vertical flip enable
// 00: Normal
// 11: Vertical flip
// Bit[0]: Vertical binning enable
#define TIMING_TC_REG21 0x3821 // Timing Control Register
// Bit[5]: Compression Enable
// Bit[2:1]: Horizontal mirror enable
// 00: Normal
// 11: Horizontal mirror
// Bit[0]: Horizontal binning enable
#define PCLK_RATIO 0x3824 // Bit[4:0]: PCLK ratio manual
/* frame control registers */
#define FRAME_CTRL01 0x4201 // Control Passed Frame Number When both ON and OFF number set to 0x00,frame control is in bypass mode
// Bit[7:4]: Not used
// Bit[3:0]: Frame ON number
#define FRAME_CTRL02 0x4202 // Control Masked Frame Number When both ON and OFF number set to 0x00,frame control is in bypass mode
// Bit[7:4]: Not used
// BIT[3:0]: Frame OFF number
/* format control registers */
#define FORMAT_CTRL00 0x4300
#define CLOCK_POL_CONTROL 0x4740// Bit[5]: PCLK polarity 0: active low
// 1: active high
// Bit[3]: Gate PCLK under VSYNC
// Bit[2]: Gate PCLK under HREF
// Bit[1]: HREF polarity
// 0: active low
// 1: active high
// Bit[0] VSYNC polarity
// 0: active low
// 1: active high
#define ISP_CONTROL_01 0x5001 // Bit[5]: Scale enable
// 0: Disable
// 1: Enable
/* output format control registers */
#define FORMAT_CTRL 0x501F // Format select
// Bit[2:0]:
// 000: YUV422
// 001: RGB
// 010: Dither
// 011: RAW after DPC
// 101: RAW after CIP
/* ISP top control registers */
#define PRE_ISP_TEST_SETTING_1 0x503D // Bit[7]: Test enable
// 0: Test disable
// 1: Color bar enable
// Bit[6]: Rolling
// Bit[5]: Transparent
// Bit[4]: Square black and white
// Bit[3:2]: Color bar style
// 00: Standard 8 color bar
// 01: Gradual change at vertical mode 1
// 10: Gradual change at horizontal
// 11: Gradual change at vertical mode 2
// Bit[1:0]: Test select
// 00: Color bar
// 01: Random data
// 10: Square data
// 11: Black image
//exposure = {0x3500[3:0], 0x3501[7:0], 0x3502[7:0]} / 16 × tROW
#define SCALE_CTRL_1 0x5601 // Bit[6:4]: HDIV RW
// DCW scale times
// 000: DCW 1 time
// 001: DCW 2 times
// 010: DCW 4 times
// 100: DCW 8 times
// 101: DCW 16 times
// Others: DCW 16 times
// Bit[2:0]: VDIV RW
// DCW scale times
// 000: DCW 1 time
// 001: DCW 2 times
// 010: DCW 4 times
// 100: DCW 8 times
// 101: DCW 16 times
// Others: DCW 16 times
#define SCALE_CTRL_2 0x5602 // X_SCALE High Bits
#define SCALE_CTRL_3 0x5603 // X_SCALE Low Bits
#define SCALE_CTRL_4 0x5604 // Y_SCALE High Bits
#define SCALE_CTRL_5 0x5605 // Y_SCALE Low Bits
#define SCALE_CTRL_6 0x5606 // Bit[3:0]: V Offset
#define VFIFO_CTRL0C 0x460C // Bit[1]: PCLK manual enable
// 0: Auto
// 1: Manual by PCLK_RATIO
#define VFIFO_X_SIZE_H 0x4602
#define VFIFO_X_SIZE_L 0x4603
#define VFIFO_Y_SIZE_H 0x4604
#define VFIFO_Y_SIZE_L 0x4605
#define COMPRESSION_CTRL00 0x4400 //
#define COMPRESSION_CTRL01 0x4401 //
#define COMPRESSION_CTRL02 0x4402 //
#define COMPRESSION_CTRL03 0x4403 //
#define COMPRESSION_CTRL04 0x4404 //
#define COMPRESSION_CTRL05 0x4405 //
#define COMPRESSION_CTRL06 0x4406 //
#define COMPRESSION_CTRL07 0x4407 // Bit[5:0]: QS
#define COMPRESSION_ISI_CTRL 0x4408 //
#define COMPRESSION_CTRL09 0x4409 //
#define COMPRESSION_CTRL0a 0x440a //
#define COMPRESSION_CTRL0b 0x440b //
#define COMPRESSION_CTRL0c 0x440c //
#define COMPRESSION_CTRL0d 0x440d //
#define COMPRESSION_CTRL0E 0x440e //
/**
* @brief register value
*/
#define TEST_COLOR_BAR 0xC0 /* Enable Color Bar roling Test */
#define AEC_PK_MANUAL_AGC_MANUALEN 0x02 /* Enable AGC Manual enable */
#define AEC_PK_MANUAL_AEC_MANUALEN 0x01 /* Enable AEC Manual enable */
#define TIMING_TC_REG20_VFLIP 0x06 /* Vertical flip enable */
#define TIMING_TC_REG21_HMIRROR 0x06 /* Horizontal mirror enable */
#endif // __OV3660_REG_REGS_H__
@@ -0,0 +1,335 @@
#ifndef _OV5640_SETTINGS_H_
#define _OV5640_SETTINGS_H_
#include <stdint.h>
#include <stdbool.h>
#include "esp_attr.h"
#include "ov5640_regs.h"
static const ratio_settings_t ratio_table[] = {
// mw, mh, sx, sy, ex, ey, ox, oy, tx, ty
{ 2560, 1920, 0, 0, 2623, 1951, 32, 16, 2844, 1968 }, //4x3
{ 2560, 1704, 0, 110, 2623, 1843, 32, 16, 2844, 1752 }, //3x2
{ 2560, 1600, 0, 160, 2623, 1791, 32, 16, 2844, 1648 }, //16x10
{ 2560, 1536, 0, 192, 2623, 1759, 32, 16, 2844, 1584 }, //5x3
{ 2560, 1440, 0, 240, 2623, 1711, 32, 16, 2844, 1488 }, //16x9
{ 2560, 1080, 0, 420, 2623, 1531, 32, 16, 2844, 1128 }, //21x9
{ 2400, 1920, 80, 0, 2543, 1951, 32, 16, 2684, 1968 }, //5x4
{ 1920, 1920, 320, 0, 2543, 1951, 32, 16, 2684, 1968 }, //1x1
{ 1088, 1920, 736, 0, 1887, 1951, 32, 16, 1884, 1968 } //9x16
};
#define REG_DLY 0xffff
#define REGLIST_TAIL 0x0000
static const DRAM_ATTR uint16_t sensor_default_regs[][2] = {
{SYSTEM_CTROL0, 0x82}, // software reset
{REG_DLY, 10}, // delay 10ms
{SYSTEM_CTROL0, 0x42}, // power down
//enable pll
{0x3103, 0x13},
//io direction
{0x3017, 0xff},
{0x3018, 0xff},
{DRIVE_CAPABILITY, 0xc3},
{CLOCK_POL_CONTROL, 0x21},
{0x4713, 0x02},//jpg mode select
{ISP_CONTROL_01, 0x83}, // turn color matrix, awb and SDE
//sys reset
{0x3000, 0x20}, // reset MCU
{REG_DLY, 10}, // delay 10ms
{0x3002, 0x1c},
//clock enable
{0x3004, 0xff},
{0x3006, 0xc3},
//isp control
{0x5000, 0xa7},
{ISP_CONTROL_01, 0xa3},//+scaling?
{0x5003, 0x08},//special_effect
//unknown
{0x370c, 0x02},//!!IMPORTANT
{0x3634, 0x40},//!!IMPORTANT
//AEC/AGC
{0x3a02, 0x03},
{0x3a03, 0xd8},
{0x3a08, 0x01},
{0x3a09, 0x27},
{0x3a0a, 0x00},
{0x3a0b, 0xf6},
{0x3a0d, 0x04},
{0x3a0e, 0x03},
{0x3a0f, 0x30},//ae_level
{0x3a10, 0x28},//ae_level
{0x3a11, 0x60},//ae_level
{0x3a13, 0x43},
{0x3a14, 0x03},
{0x3a15, 0xd8},
{0x3a18, 0x00},//gainceiling
{0x3a19, 0xf8},//gainceiling
{0x3a1b, 0x30},//ae_level
{0x3a1e, 0x26},//ae_level
{0x3a1f, 0x14},//ae_level
//vcm debug
{0x3600, 0x08},
{0x3601, 0x33},
//50/60Hz
{0x3c01, 0xa4},
{0x3c04, 0x28},
{0x3c05, 0x98},
{0x3c06, 0x00},
{0x3c07, 0x08},
{0x3c08, 0x00},
{0x3c09, 0x1c},
{0x3c0a, 0x9c},
{0x3c0b, 0x40},
{0x460c, 0x22},//disable jpeg footer
//BLC
{0x4001, 0x02},
{0x4004, 0x02},
//AWB
{0x5180, 0xff},
{0x5181, 0xf2},
{0x5182, 0x00},
{0x5183, 0x14},
{0x5184, 0x25},
{0x5185, 0x24},
{0x5186, 0x09},
{0x5187, 0x09},
{0x5188, 0x09},
{0x5189, 0x75},
{0x518a, 0x54},
{0x518b, 0xe0},
{0x518c, 0xb2},
{0x518d, 0x42},
{0x518e, 0x3d},
{0x518f, 0x56},
{0x5190, 0x46},
{0x5191, 0xf8},
{0x5192, 0x04},
{0x5193, 0x70},
{0x5194, 0xf0},
{0x5195, 0xf0},
{0x5196, 0x03},
{0x5197, 0x01},
{0x5198, 0x04},
{0x5199, 0x12},
{0x519a, 0x04},
{0x519b, 0x00},
{0x519c, 0x06},
{0x519d, 0x82},
{0x519e, 0x38},
//color matrix (Saturation)
{0x5381, 0x1e},
{0x5382, 0x5b},
{0x5383, 0x08},
{0x5384, 0x0a},
{0x5385, 0x7e},
{0x5386, 0x88},
{0x5387, 0x7c},
{0x5388, 0x6c},
{0x5389, 0x10},
{0x538a, 0x01},
{0x538b, 0x98},
//CIP control (Sharpness)
{0x5300, 0x10},//sharpness
{0x5301, 0x10},//sharpness
{0x5302, 0x18},//sharpness
{0x5303, 0x19},//sharpness
{0x5304, 0x10},
{0x5305, 0x10},
{0x5306, 0x08},//denoise
{0x5307, 0x16},
{0x5308, 0x40},
{0x5309, 0x10},//sharpness
{0x530a, 0x10},//sharpness
{0x530b, 0x04},//sharpness
{0x530c, 0x06},//sharpness
//GAMMA
{0x5480, 0x01},
{0x5481, 0x00},
{0x5482, 0x1e},
{0x5483, 0x3b},
{0x5484, 0x58},
{0x5485, 0x66},
{0x5486, 0x71},
{0x5487, 0x7d},
{0x5488, 0x83},
{0x5489, 0x8f},
{0x548a, 0x98},
{0x548b, 0xa6},
{0x548c, 0xb8},
{0x548d, 0xca},
{0x548e, 0xd7},
{0x548f, 0xe3},
{0x5490, 0x1d},
//Special Digital Effects (SDE) (UV adjust)
{0x5580, 0x06},//enable brightness and contrast
{0x5583, 0x40},//special_effect
{0x5584, 0x10},//special_effect
{0x5586, 0x20},//contrast
{0x5587, 0x00},//brightness
{0x5588, 0x00},//brightness
{0x5589, 0x10},
{0x558a, 0x00},
{0x558b, 0xf8},
{0x501d, 0x40},// enable manual offset of contrast
//power on
{0x3008, 0x02},
//50Hz
{0x3c00, 0x04},
{REG_DLY, 300},
{REGLIST_TAIL, 0x00}, // tail
};
static const DRAM_ATTR uint16_t sensor_fmt_jpeg[][2] = {
{FORMAT_CTRL, 0x00}, // YUV422
{FORMAT_CTRL00, 0x30}, // YUYV
{0x3002, 0x00},//0x1c to 0x00 !!!
{0x3006, 0xff},//0xc3 to 0xff !!!
{0x471c, 0x50},//0xd0 to 0x50 !!!
{REGLIST_TAIL, 0x00}, // tail
};
static const DRAM_ATTR uint16_t sensor_fmt_raw[][2] = {
{FORMAT_CTRL, 0x03}, // RAW (DPC)
{FORMAT_CTRL00, 0x00}, // RAW
{REGLIST_TAIL, 0x00}
};
static const DRAM_ATTR uint16_t sensor_fmt_grayscale[][2] = {
{FORMAT_CTRL, 0x00}, // YUV422
{FORMAT_CTRL00, 0x10}, // Y8
{REGLIST_TAIL, 0x00}
};
static const DRAM_ATTR uint16_t sensor_fmt_yuv422[][2] = {
{FORMAT_CTRL, 0x00}, // YUV422
{FORMAT_CTRL00, 0x30}, // YUYV
{REGLIST_TAIL, 0x00}
};
static const DRAM_ATTR uint16_t sensor_fmt_rgb565[][2] = {
{FORMAT_CTRL, 0x01}, // RGB
{FORMAT_CTRL00, 0x61}, // RGB565 (BGR)
{REGLIST_TAIL, 0x00}
};
static const DRAM_ATTR uint8_t sensor_saturation_levels[9][11] = {
{0x1d, 0x60, 0x03, 0x07, 0x48, 0x4f, 0x4b, 0x40, 0x0b, 0x01, 0x98},//-4
{0x1d, 0x60, 0x03, 0x08, 0x54, 0x5c, 0x58, 0x4b, 0x0d, 0x01, 0x98},//-3
{0x1d, 0x60, 0x03, 0x0a, 0x60, 0x6a, 0x64, 0x56, 0x0e, 0x01, 0x98},//-2
{0x1d, 0x60, 0x03, 0x0b, 0x6c, 0x77, 0x70, 0x60, 0x10, 0x01, 0x98},//-1
{0x1d, 0x60, 0x03, 0x0c, 0x78, 0x84, 0x7d, 0x6b, 0x12, 0x01, 0x98},//0
{0x1d, 0x60, 0x03, 0x0d, 0x84, 0x91, 0x8a, 0x76, 0x14, 0x01, 0x98},//+1
{0x1d, 0x60, 0x03, 0x0e, 0x90, 0x9e, 0x96, 0x80, 0x16, 0x01, 0x98},//+2
{0x1d, 0x60, 0x03, 0x10, 0x9c, 0xac, 0xa2, 0x8b, 0x17, 0x01, 0x98},//+3
{0x1d, 0x60, 0x03, 0x11, 0xa8, 0xb9, 0xaf, 0x96, 0x19, 0x01, 0x98},//+4
};
static const DRAM_ATTR uint8_t sensor_special_effects[7][4] = {
{0x06, 0x40, 0x2c, 0x08},//Normal
{0x46, 0x40, 0x28, 0x08},//Negative
{0x1e, 0x80, 0x80, 0x08},//Grayscale
{0x1e, 0x80, 0xc0, 0x08},//Red Tint
{0x1e, 0x60, 0x60, 0x08},//Green Tint
{0x1e, 0xa0, 0x40, 0x08},//Blue Tint
{0x1e, 0x40, 0xa0, 0x08},//Sepia
};
static const DRAM_ATTR uint16_t sensor_regs_gamma0[][2] = {
{0x5480, 0x01},
{0x5481, 0x08},
{0x5482, 0x14},
{0x5483, 0x28},
{0x5484, 0x51},
{0x5485, 0x65},
{0x5486, 0x71},
{0x5487, 0x7d},
{0x5488, 0x87},
{0x5489, 0x91},
{0x548a, 0x9a},
{0x548b, 0xaa},
{0x548c, 0xb8},
{0x548d, 0xcd},
{0x548e, 0xdd},
{0x548f, 0xea},
{0x5490, 0x1d}
};
static const DRAM_ATTR uint16_t sensor_regs_gamma1[][2] = {
{0x5480, 0x1},
{0x5481, 0x0},
{0x5482, 0x1e},
{0x5483, 0x3b},
{0x5484, 0x58},
{0x5485, 0x66},
{0x5486, 0x71},
{0x5487, 0x7d},
{0x5488, 0x83},
{0x5489, 0x8f},
{0x548a, 0x98},
{0x548b, 0xa6},
{0x548c, 0xb8},
{0x548d, 0xca},
{0x548e, 0xd7},
{0x548f, 0xe3},
{0x5490, 0x1d}
};
static const DRAM_ATTR uint16_t sensor_regs_awb0[][2] = {
{0x5180, 0xff},
{0x5181, 0xf2},
{0x5182, 0x00},
{0x5183, 0x14},
{0x5184, 0x25},
{0x5185, 0x24},
{0x5186, 0x09},
{0x5187, 0x09},
{0x5188, 0x09},
{0x5189, 0x75},
{0x518a, 0x54},
{0x518b, 0xe0},
{0x518c, 0xb2},
{0x518d, 0x42},
{0x518e, 0x3d},
{0x518f, 0x56},
{0x5190, 0x46},
{0x5191, 0xf8},
{0x5192, 0x04},
{0x5193, 0x70},
{0x5194, 0xf0},
{0x5195, 0xf0},
{0x5196, 0x03},
{0x5197, 0x01},
{0x5198, 0x04},
{0x5199, 0x12},
{0x519a, 0x04},
{0x519b, 0x00},
{0x519c, 0x06},
{0x519d, 0x82},
{0x519e, 0x38}
};
#endif
@@ -0,0 +1,33 @@
/*
* This file is part of the OpenMV project.
* author: Juan Schiavoni <juanjoseschiavoni@hotmail.com>
* This work is licensed under the MIT license, see the file LICENSE for details.
*
* OV7670 driver.
*
*/
#ifndef __OV7670_H__
#define __OV7670_H__
#include "sensor.h"
/**
* @brief Detect sensor pid
*
* @param slv_addr SCCB address
* @param id Detection result
* @return
* 0: Can't detect this sensor
* Nonzero: This sensor has been detected
*/
int esp32_camera_ov7670_detect(int slv_addr, sensor_id_t *id);
/**
* @brief initialize sensor function pointers
*
* @param sensor pointer of sensor
* @return
* Always 0
*/
int esp32_camera_ov7670_init(sensor_t *sensor);
#endif // __OV7670_H__

Some files were not shown because too many files have changed in this diff Show More