updates
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
# DFR1154 camera bring-up
|
||||
|
||||
The sketch uses the pin mapping from [DFRobot's DFR1154 CameraWebServer example](https://github.com/DFRobot/DFR1154_Examples/blob/master/5.3%20CameraWebServer/source%20code/5_3/5_3.ino), initializes the OV3660 at QVGA JPEG, and sends one frame over USB serial when the host sends `C`. It avoids Wi-Fi setup and stores no credentials. `capture_frame.py` validates and saves the JPEG inside `hardware/camera-captures/`.
|
||||
|
||||
The board under test is marked **V1.1**. Its ESP32-S3 MAC is `E8:F6:0A:87:0B:4C`, with 16 MB flash and 8 MB PSRAM. The USB port was `/dev/cu.usbmodem1101` during initial inspection; verify it before uploading because port names can change. Only upload after checking the board MAC. Flashing will replace its installed program.
|
||||
|
||||
Compile profile: `esp32:esp32:esp32s3:FlashSize=16M,PSRAM=opi,PartitionScheme=app3M_fat9M_16MB,USBMode=hwcdc,CDCOnBoot=cdc` using Arduino ESP32 core 3.3.5. The existing `vendor/ctags-tool/bin` arm64 helper is required on this Mac. The host capture script can run with `/Users/joshua/.local/pipx/venvs/esptool/bin/python`, which has PySerial.
|
||||
|
||||
The first successful result should include a readable frame with reasonable exposure and no severe corruption. Save a serial log and note its actual dimensions, lighting, and camera orientation in `hardware/bringup.md`. This test proves image capture, not person localization or tracking speed.
|
||||
@@ -0,0 +1,95 @@
|
||||
// DFRobot DFR1154 camera bring-up: capture one QVGA JPEG on each 'C' command.
|
||||
// GPIO assignments match DFRobot's DFR1154 CameraWebServer example.
|
||||
// The serial protocol contains no Wi-Fi credentials or network dependency.
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "esp_camera.h"
|
||||
|
||||
namespace {
|
||||
bool camera_ready = false;
|
||||
|
||||
void print_error(const char* stage, esp_err_t error) {
|
||||
Serial.printf("ERROR %s 0x%X\n", stage, static_cast<unsigned int>(error));
|
||||
}
|
||||
|
||||
void capture_one_frame() {
|
||||
if (!camera_ready) {
|
||||
Serial.println("ERROR camera_not_ready");
|
||||
return;
|
||||
}
|
||||
camera_fb_t* frame = esp_camera_fb_get();
|
||||
if (!frame) {
|
||||
Serial.println("ERROR capture_failed");
|
||||
return;
|
||||
}
|
||||
if (frame->format != PIXFORMAT_JPEG || frame->len == 0) {
|
||||
Serial.println("ERROR unexpected_frame_format");
|
||||
esp_camera_fb_return(frame);
|
||||
return;
|
||||
}
|
||||
|
||||
Serial.printf("JPEG %u %u %u\n", static_cast<unsigned int>(frame->len),
|
||||
static_cast<unsigned int>(frame->width),
|
||||
static_cast<unsigned int>(frame->height));
|
||||
Serial.write(frame->buf, frame->len);
|
||||
Serial.print("\nDONE\n");
|
||||
Serial.flush();
|
||||
esp_camera_fb_return(frame);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
delay(500);
|
||||
|
||||
camera_config_t config = {};
|
||||
config.ledc_channel = LEDC_CHANNEL_0;
|
||||
config.ledc_timer = LEDC_TIMER_0;
|
||||
config.pin_d0 = 16;
|
||||
config.pin_d1 = 18;
|
||||
config.pin_d2 = 21;
|
||||
config.pin_d3 = 17;
|
||||
config.pin_d4 = 14;
|
||||
config.pin_d5 = 7;
|
||||
config.pin_d6 = 6;
|
||||
config.pin_d7 = 4;
|
||||
config.pin_xclk = 5;
|
||||
config.pin_pclk = 15;
|
||||
config.pin_vsync = 1;
|
||||
config.pin_href = 2;
|
||||
config.pin_sccb_sda = 8;
|
||||
config.pin_sccb_scl = 9;
|
||||
config.pin_pwdn = -1;
|
||||
config.pin_reset = -1;
|
||||
config.xclk_freq_hz = 20000000;
|
||||
config.pixel_format = PIXFORMAT_JPEG;
|
||||
config.frame_size = FRAMESIZE_QVGA;
|
||||
config.jpeg_quality = 12;
|
||||
config.fb_count = 1;
|
||||
config.grab_mode = CAMERA_GRAB_WHEN_EMPTY;
|
||||
config.fb_location = CAMERA_FB_IN_PSRAM;
|
||||
|
||||
if (!psramFound()) {
|
||||
Serial.println("ERROR psram_missing");
|
||||
return;
|
||||
}
|
||||
const esp_err_t result = esp_camera_init(&config);
|
||||
if (result != ESP_OK) {
|
||||
print_error("camera_init", result);
|
||||
return;
|
||||
}
|
||||
sensor_t* sensor = esp_camera_sensor_get();
|
||||
if (sensor) {
|
||||
Serial.printf("SENSOR_PID 0x%04X\n", sensor->id.PID);
|
||||
if (sensor->id.PID == OV3660_PID) sensor->set_vflip(sensor, 1);
|
||||
}
|
||||
camera_ready = true;
|
||||
Serial.println("CAMERA_READY QVGA_JPEG");
|
||||
}
|
||||
|
||||
void loop() {
|
||||
while (Serial.available()) {
|
||||
if (Serial.read() == 'C') capture_one_frame();
|
||||
}
|
||||
delay(5);
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
"""Request one JPEG from camera_bringup.ino and keep evidence in this project."""
|
||||
|
||||
from pathlib import Path
|
||||
import argparse
|
||||
import hashlib
|
||||
import time
|
||||
|
||||
import serial
|
||||
|
||||
|
||||
PROJECT_ROOT = Path(__file__).resolve().parents[2]
|
||||
DEFAULT_OUTPUT = PROJECT_ROOT / "hardware" / "camera-captures" / "first-frame.jpg"
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--port", required=True)
|
||||
parser.add_argument("--output", type=Path, default=DEFAULT_OUTPUT)
|
||||
args = parser.parse_args()
|
||||
serial_lines = []
|
||||
|
||||
with serial.Serial(args.port, 115200, timeout=2, write_timeout=2) as camera:
|
||||
time.sleep(1)
|
||||
camera.reset_input_buffer()
|
||||
camera.write(b"C")
|
||||
deadline = time.monotonic() + 12
|
||||
header = None
|
||||
while time.monotonic() < deadline:
|
||||
line = camera.readline().decode("ascii", errors="replace").strip()
|
||||
if line:
|
||||
print(line)
|
||||
serial_lines.append(line)
|
||||
if line.startswith("ERROR "):
|
||||
raise RuntimeError(line)
|
||||
if line.startswith("JPEG "):
|
||||
header = line
|
||||
break
|
||||
if header is None:
|
||||
raise TimeoutError("Camera did not return a JPEG header")
|
||||
|
||||
_, size_text, width_text, height_text = header.split()
|
||||
size = int(size_text)
|
||||
if not 100 <= size <= 500_000:
|
||||
raise ValueError(f"Unexpected JPEG length: {size}")
|
||||
data = camera.read(size)
|
||||
if len(data) != size:
|
||||
raise IOError(f"Incomplete JPEG: {len(data)} of {size} bytes")
|
||||
if not (data.startswith(b"\xff\xd8") and data.endswith(b"\xff\xd9")):
|
||||
raise ValueError("JPEG markers missing")
|
||||
trailer = camera.readline().strip()
|
||||
if not trailer:
|
||||
trailer = camera.readline().strip()
|
||||
if trailer != b"DONE":
|
||||
raise ValueError(f"Unexpected frame trailer: {trailer!r}")
|
||||
serial_lines.append("DONE")
|
||||
|
||||
args.output.parent.mkdir(parents=True, exist_ok=True)
|
||||
args.output.write_bytes(data)
|
||||
log_path = args.output.with_suffix(".log")
|
||||
log_path.write_text(
|
||||
"\n".join(
|
||||
[
|
||||
f"port={args.port}",
|
||||
f"jpeg_sha256={hashlib.sha256(data).hexdigest()}",
|
||||
*serial_lines,
|
||||
]
|
||||
) + "\n"
|
||||
)
|
||||
print(f"Saved {width_text}x{height_text} JPEG ({size} bytes): {args.output}")
|
||||
print(f"Saved serial capture log: {log_path}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user