USB Host Camera Basics
Support for USB host cameras is provided by the Adafruit CircuitPython USB Host Camera library. This page covers the basic setup and usage of the library to view and capture photos from a USB camera. See the following guide pages for complete projects that are ready to run.
Isochronous Buffer Size
USB devices with isochronous communication need a ring buffer to store incoming packets while CircuitPython is doing other things. The CIRCUITPY_USB_HOST_ISO_BUFFER_SIZE configuration in settings.toml is the way to declare the size allocated for that buffer. A size of 16384 has been found to work nicely for a USB web camera sending 320x240 pixel frames.
Here is an example settings.toml file with the buffer configured.
CIRCUITPY_USB_HOST_ISO_BUFFER_SIZE = 16384
Detect the Camera
The UVCCamera() initializer will automatically search for a supported camera connected to a USB host port on the Fruit Jam. It is good practice to make your project code retry until a camera is found in case you happen to power up the board without the camera connected. This loop from the simpletest script demonstrates how to check for the camera every 2 seconds until it one is found.
import time
import adafruit_usb_host_camera
camera = None
while camera is None:
try:
# Finds the camera among the attached devices (a keyboard, a mouse...)
camera = adafruit_usb_host_camera.UVCCamera()
except ValueError:
print("Plug a USB camera into the USB host port")
time.sleep(2)
Configure Mode
Most USB cameras support multiple different modes, each with different capture formats and sizes. The list of supported modes is communicated by the camera inside of its USB descriptor. To make things easy, the UVCCamera class has the find_mode(width, height) function. Pass in the width and height that you want to capture photos at, and the function will return a Mode object corresponding to the closest match that the camera supports.
On the Fruit Jam, 320x240 works best, if you want a live preview from the camera displayed over DVI. 640x480 works as well, but saving a photo takes longer, and the display cannot show the preview at high frames per second (FPS).
print("Camera modes:", camera.modes)
mode = camera.find_mode(320, 240)
print("Using", mode)
Start the Camera
To start the camera so it will begin sending back image data, use the start_camera() function. It accepts a Mode object representing the capture mode that you want to use.
camera.start(mode)
Save a Photo
To capture a photo and save it as a jpeg file, use the save_jpeg() function and pass a path for where to save the photo. The path must be mounted as writable to CircuitPython. The easiest options are inside of the /saves/ directory, or on an SD card.
camera.save_jpeg("/saves/photo.jpg")
Live Preview via DVI
To show a live preview from the camera on a display connected to the Fruit Jam's DVI output, the camera.bitmap property and update_bitmap() function are used. The picogame module is the fastest way to render the preview. You can use it instead of displayio for maximizing the preview FPS.
First initialize the display configured for 320x240, you can skip this the display is already set to 320x240 with 16 bit color depth from your settings.toml file. In that case just use framebuffer = supervisor.runtime.display.framebuffer.
Next set up the camera in the same way shown above. Then get the bitmap object from the camera and set up the Picogame Sprite and layers.
Inside the main loop, update the preview by calling update_bitmap() and picogame.render()
# --------- Display Setup ---------
WIDTH, HEIGHT = 320, 240
# Manual display init
displayio.release_displays()
framebuffer = picodvi.Framebuffer(
WIDTH,
HEIGHT,
clk_dp=board.CKP,
clk_dn=board.CKN,
red_dp=board.D0P,
red_dn=board.D0N,
green_dp=board.D1P,
green_dn=board.D1N,
blue_dp=board.D2P,
blue_dn=board.D2N,
color_depth=16,
)
# Or automatic display init
# Inside settings.toml:
# CIRCUITPY_DISPLAY_WIDTH=320
# CIRCUITPY_DISPLAY_COLOR_DEPTH=16
# Here in code.py:
# framebuffer = supervisor.runtime.display.framebuffer
# Either way, wrap the framebuffer with picogame
render_buffer = picogame.Framebuffer(framebuffer, WIDTH, HEIGHT, native_rgb565=True)
# --------- Camera Setup ---------
# ... same as shown above ...
# --------- Picogame Sprite and Layers setup ---------
# The first access captures a frame and creates the bitmap.
bitmap = None
while bitmap is None:
try:
bitmap = camera.bitmap
except RuntimeError:
pass # no complete frame in time; keep trying
# update_bitmap() decodes each frame into this same bitmap, and picogame draws
# that memory (no copy) straight into the DVI framebuffer.
sprite = picogame.Sprite(picogame.Bitmap(bitmap, bitmap.width, bitmap.height), x=0, y=0)
layers = [sprite]
# --------- Main Loop ---------
while True:
try:
camera.update_bitmap()
except RuntimeError:
continue # no complete frame in time; keep trying
picogame.render(render_buffer, layers, None, 0, 0, WIDTH, HEIGHT)
Page last edited September 28, 2026
Text editor powered by tinymce.