Skip to content

Added board ESP32-S3 N16R8 Wroom with espcamera#10959

Open
themipper wants to merge 3 commits intoadafruit:mainfrom
themipper:esp32s3_n16r8_wroom_cam
Open

Added board ESP32-S3 N16R8 Wroom with espcamera#10959
themipper wants to merge 3 commits intoadafruit:mainfrom
themipper:esp32s3_n16r8_wroom_cam

Conversation

@themipper
Copy link
Copy Markdown

I followed the official guide and created a fork.
Built circuitpython and my board defintion.
Flashed the board with the new firmware.
Used the pre-commit automatic checks.

Added code.py and deepsleep.py scripts to test the following functionalilties.

  • connecting to wifi network
  • board.LED blinking
  • initialize OV3660 and OV5640 camera
  • capture image with both cameras
  • send image via TCP Socket connection to a testserver. Images were sent correctly.
  • tested deep- and light-sleep

Everything works as expected.

Note:

What I couldn't get to work was that the board.I2C() is initialized from the start. Even when following the examples from other ESP32-S3 boards in the repository.

Initializing the camera using busio.I2C(...) works.

i2c = busio.I2C(scl=board.CAM_SCL, sda=board.CAM_SDA)
  
print("camera init")
cam = espcamera.Camera(
    data_pins=board.CAM_DATA,         # tuple of 8 data pins
    external_clock_pin=board.CAM_XCLK,
    pixel_clock_pin=board.CAM_PCLK,
    vsync_pin=board.CAM_VSYNC,
    href_pin=board.CAM_HREF,
    i2c=i2c,
    pixel_format=espcamera.PixelFormat.JPEG,
    frame_size=espcamera.FrameSize.VGA,  
    jpeg_quality=10,                      # 0 (best) – 63 (worst)
    framebuffer_count=1,
    grab_mode=espcamera.GrabMode.WHEN_EMPTY,        
)

Code.py

import time
import espcamera
import board
import digitalio
import busio
import wifi
import socketpool
import os
import deepsleep

# ── Configuration ────────────────────────────────────────────
SSID     = "ssid"
PASSWORD = "pwd"
SERVER_HOST = "ip"
SERVER_PORT = 8889
# ─────────────────────────────────────────────────────────────

# LED on GPIO 2
led = digitalio.DigitalInOut(board.LED)
led.direction = digitalio.Direction.OUTPUT

# blink the LED
def blink(times=1, on_ms=200, off_ms=200):    
    for _ in range(times):
        led.value = True
        time.sleep(on_ms / 1000)
        led.value = False
        time.sleep(off_ms / 1000)

#prints all board defined PINS
def print_board():
    print("print: board")
    for item in dir(board):
        print("  ",item, "=", getattr(board, item))
        
#prints all members of an object
def print_object(o):
    print("print: ",o)
    for item in dir(o):
        print("  ",item, "=", getattr(o, item))

#create camera
def create_camera() -> espcamera.Camera:
    """
    Initialise and return a configured espcamera.Camera.
    Call once at startup — not per frame.
  
    """    

    i2c = busio.I2C(scl=board.CAM_SCL, sda=board.CAM_SDA)
    
    print("camera init")
    cam = espcamera.Camera(
        data_pins=board.CAM_DATA,         # tuple of 8 data pins
        external_clock_pin=board.CAM_XCLK,
        pixel_clock_pin=board.CAM_PCLK,
        vsync_pin=board.CAM_VSYNC,
        href_pin=board.CAM_HREF,
        i2c=i2c,
        pixel_format=espcamera.PixelFormat.JPEG,
        frame_size=espcamera.FrameSize.VGA,  
        jpeg_quality=10,                      # 0 (best) – 63 (worst)
        framebuffer_count=1,
        grab_mode=espcamera.GrabMode.WHEN_EMPTY,        
    )
    #cam.hmirror=True
    cam.vflip=True
    print("camera init - done")
    return cam

# capture image
def capture_image(cam: espcamera.Camera) -> bytearray:    
    """
    Capture a single JPEG frame.
    Returns a memoryview of the JPEG bytes, or None on timeout.
    """
    print("capture")
    frame = cam.take(1.0)
    print("capture done")
    return frame                         # copy into a concrete bytearray

# send data in chunks over tcp socket connection
def _send_all(sock, data):
    """Reliably send all bytes, handling partial sends and EAGAIN."""
    offset = 0
    view = memoryview(data) if not isinstance(data, memoryview) else data
    while offset < len(view):
        blink(1, on_ms=10, off_ms=10)
        print(".", end="")
        try:
            sent = sock.send(view[offset:offset + 1024])
            if sent > 0:
                offset += sent
        except OSError as e:
            if e.errno == 11:  # EAGAIN - would block, just retry
                time.sleep(0.01)
            else:
                raise

# send the image over a tcp socket connection            
def send_image(host: str, port: int, image) -> int:
    length = len(image)
    header = length.to_bytes(4, "big")
    pool = socketpool.SocketPool(wifi.radio)

    with pool.socket(pool.AF_INET, pool.SOCK_STREAM) as sock:
        sock.settimeout(10.0)
        sock.connect((host, port))
        
        _send_all(sock, header)        
        _send_all(sock, image)        

    return length

# ── Connect to WiFi ───────────────────────────────────────────
def connect():
    
    if not wifi.radio.connected:    
        print("Connecting to WiFi...")
        blink(1, on_ms=100)  # short blink = trying

        try:
            wifi.radio.connect(SSID, PASSWORD)
            ip = str(wifi.radio.ipv4_address)
            print(f"Connected! IP: {ip}")
            blink(5, on_ms=50, off_ms=10)  # 3 quick blinks = success
        except Exception as e:
            print(f"WiFi failed: {e}")
            blink(5, on_ms=500)              # 5 slow blinks = error
            raise
    else:
        print("Connected to WiFi")

#-- Main Program
start = time.monotonic()
connect()



#led.value = False
print("create Camera")
cam = create_camera()
print_object(cam)
print("Camera created")

span = time.monotonic() -start
start = time.monotonic()
print(f"Init done in {span:.3f}s")

# ── Main loop ─────────────────────────────────────────────────
keep_running = True
loop = 2
counter = 0
while keep_running:    
    counter += 1
    start = time.monotonic()
    deepsleep.print_wakeup()
    print(f"Starting loop {counter}/{loop}")
    connect()
    print("Capturing ...", end = "")
    frame = capture_image(cam)
    print("Captured")
    print("Sending", end="")
    sent = -1
    sent  = send_image(SERVER_HOST, SERVER_PORT, frame)
    print(f" {sent} bytes JPEG sent")        
    
    if counter >= loop:
        keep_running = False
    
    span = time.monotonic() -start    
    print(f"Loop done in {span:.3f}s")
    
    #time.sleep(2)
    deepsleep.goto_lightsleep(2)
    

Deepsleep.py

import alarm
import time

def print_wakeup():
    # --- print wakeup reason ---
    if alarm.wake_alarm is None:
        print("Wakeup reason: fresh boot / reset")
    elif isinstance(alarm.wake_alarm, alarm.time.TimeAlarm):
        print("Wakeup reason: deep sleep timer")
    elif isinstance(alarm.wake_alarm, alarm.pin.PinAlarm):
        print(f"Wakeup reason: pin alarm on {alarm.wake_alarm.pin}")
    else:
        print(f"Wakeup reason: {alarm.wake_alarm}")

def goto_deepsleep(seconds:int = 5):
    '''
        Enters the deep sleep that restarts the program from the start.
        Saves the most power but also means you have to init every module fresh.
    '''
    print(f"Going to deep sleep for {seconds} seconds...")
    time_alarm = alarm.time.TimeAlarm(monotonic_time=time.monotonic() + seconds)
    alarm.exit_and_deep_sleep_until_alarms(time_alarm)


def goto_lightsleep(seconds:int = 5):
    '''
        Enters the light sleep that resumes the program where it left.
        Saves less power than the deep sleep but also does not need to re-init everything
    '''
    print(f"Going to light sleep for {seconds} seconds...")
    time_alarm = alarm.time.TimeAlarm(monotonic_time=time.monotonic() + seconds)
    alarm.light_sleep_until_alarms(time_alarm)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant