Demo
A camera streaming demo running on RVComp. Source code is available in the feature/v1.1.0-demo branch of the RVComp repository.
Overview
This demo uses an OV7670 (OmniVision) camera module connected to a Nexys 4 DDR board. The camera captures 320×240 frames, converts them to 8-bit grayscale on the FPGA, and transmits them to a host PC over Ethernet. The demo runs on a local network (LAN).
OV7670 → FPGA (Gray8 capture) → Linux (cam_tx, UDP) → Host (relay.py, WebSocket) → Browser
Demo Video
The demo is filmed in a bright indoor environment. Because frames are encoded as 8-bit grayscale, good ambient lighting improves image quality.
Running the Demo
On RVComp (Linux)
After booting Linux, assign an IP address and bring up the Ethernet interface:
$ ip addr add <IP_ADDRESS>/<PREFIX_LEN> dev eth0
$ ip link set eth0 up
Then start the camera transmitter:
$ cam_tx --host <HOST_IP_ADDRESS>
Usage: cam_tx [--dev PATH] [--host IP] [--port N] [--payload N]
Defaults: --dev /dev/rvcomp_cam0 --host 192.168.0.2 --port 5000 --payload 1200
On the Host PC
Run relay.py from tools/camera/ using uv:
$ cd tools/camera
$ uv run relay.py --port 5000 --ws-port 8000
Then open http://localhost:8000 in a browser to view the live camera stream.
usage: relay.py [-h] [--udp-host UDP_HOST] [--udp-port UDP_PORT] [--ws-host WS_HOST] [--ws-port WS_PORT]
RVComp camera relay
options:
-h, --help show this help message and exit
--udp-host UDP_HOST UDP bind host
--udp-port UDP_PORT UDP bind port
--ws-host WS_HOST HTTP/WS bind host
--ws-port WS_PORT HTTP/WS bind port
System Configuration
External Pins
The OV7670 is connected to the PMOD JA and JB headers on the Nexys 4 DDR board.
Signal |
Direction |
Description |
|---|---|---|
|
Output |
Camera input clock (25 MHz) |
|
Input |
Pixel clock |
|
Input |
Horizontal sync |
|
Input |
Vertical sync |
|
Input |
8-bit pixel data |
|
Output |
I2C clock (SCCB) |
|
Inout |
I2C data (SCCB) |
Camera Driver
An FPGA block captures frames from the OV7670 via I2C (SCCB). Pixel data is converted to 8-bit grayscale and stored in two frame buffers in BRAM (ping-pong). 8-bit grayscale was chosen because fitting two full-resolution color frame buffers in BRAM would exceed the available capacity.
A Linux misc driver (rvcomp-camera) exposes the device as /dev/rvcomp_cam0. The driver accesses the camera hardware via MMIO polling (no IRQ). It supports read, poll, and ioctl (RVCOMP_CAM_IOC_G_INFO, RVCOMP_CAM_IOC_G_META).
MMIO Map
Region |
Start |
End |
Description |
|---|---|---|---|
CSR |
|
|
Control and status registers |
Frame aperture |
|
|
Ping-pong frame buffer window |
CSR Map
All CSRs are 32-bit wide.
Offset |
Name |
Access |
Description |
|---|---|---|---|
0x00 |
|
R |
Device ID |
0x04 |
|
R/W |
Control (bit 0: capture enable) |
0x08 |
|
R |
Status |
0x0C |
|
R |
Image width in pixels |
0x10 |
|
R |
Image height in pixels |
0x14 |
|
R |
Row stride in bytes |
0x18 |
|
R |
Total frame size in bytes |
0x1C |
|
R |
Frame sequence counter |
0x20 |
|
R |
Bank holding the latest complete frame |
0x24 |
|
R/W |
Bank selected for readout |
0x28 |
|
R |
Number of dropped frames |
0x2C |
|
R/W |
Gain control |
Transmission Program (cam_tx)
cam_tx is a userspace program that reads frames from /dev/rvcomp_cam0 and transmits them to the host as UDP datagrams. Each frame is split into chunks with a custom header (RVCP magic, sequence number, width, height, chunk index/count) to allow reassembly on the host.
Client Program (relay.py)
relay.py runs on the host and acts as a relay between cam_tx and the browser. It listens for UDP datagrams from cam_tx, reassembles the frame chunks, and broadcasts each completed frame to all connected WebSocket clients. The browser connects to the WebSocket endpoint (/ws) and renders each frame on an HTML5 Canvas element.
Going Further
The current implementation uses CPU-driven (PIO) reads from the BRAM frame buffer and UDP transmission, which limits the achievable frame rate.
Since this runs on an FPGA, the hardware is fully reconfigurable. For example:
DMA: offload frame readout from the CPU entirely
FPGA-side Ethernet framing: generate UDP/IP headers in hardware and push frames to the MAC without CPU involvement
Pipeline: overlap capture and transmission in the FPGA fabric
Changes that would require a full chip respin on an ASIC can be explored here simply by modifying the RTL — feel free to experiment.