Jetson multi-camera sync: CSI hardware trigger setup
Running multiple CSI cameras on Jetson simultaneously is straightforward, the hardware supports it natively. Synchronizing them so frames align within a millisecond requires hardware trigger synchronization. Software timestamp comparison introduces too much jitter for stereo vision or surround-view applications. This post covers the full synchronization setup.
Key Insights
- Hardware trigger sync is the only reliable method for multi-camera synchronization on Jetson, software sync has too much jitter for stereo or surround-view
- The sync signal comes from a Jetson GPIO (or external source) to each sensor’s trigger input, not from Jetson to the NVCSI
- Both sensors must be in external trigger mode, not free-running, or trigger sync does not override the internal frame timer
- NVCSI captures multiple streams in parallel by hardware, the synchronization problem is at the sensor output, not at capture
- A consistent frame offset (not jitter) points to trigger edge polarity mismatch, not a timing problem
Why free-running sync fails
The most common multi-camera “synchronization” approach is running both cameras at the same frame rate setting and timestamping frames as they arrive. This fails for any application requiring real synchronization.
Here is why: camera sensors use internal oscillators to time their frame rate. These oscillators have frequency tolerance, typically ±100-300 ppm for a standard crystal. Two cameras at 30 fps nominal might run at 30.002 fps and 29.998 fps. That 4 ms/second drift means the cameras are a full frame apart within 4 seconds of running. For stereo depth estimation, this is completely unacceptable.
Software timestamp comparison is also unreliable. The timestamp is applied when the frame arrives at the V4L2 buffer, which depends on interrupt latency and task scheduling. Jitter of 5–30 ms is typical.
Hardware trigger sync solves both problems by forcing every camera to expose on a common hardware edge. The sensors do not free-run, they wait for the trigger pulse.
Hardware trigger sync setup
The physical connection: a Jetson GPIO drives a sync pulse to the trigger input of each sensor. For CSI cameras, this is usually a direct GPIO connection via the carrier board.
Jetson GPIO (e.g., TEGRA234_AON_GPIO(BB, 2))
├── Sensor 0 trigger input (e.g., XVS or TRIGGER pin)
└── Sensor 1 trigger input
The GPIO is configured as a PWM output at the target frame rate (33.33 ms period for 30 fps), or driven manually for triggered-on-demand capture.
In the sensor driver, switch the sensor from free-running to external trigger mode:
/* Put sensor in external trigger mode */
static int sensor_set_trigger_mode(struct camera_common_data *s_data)
{
/* Register address and value depend on your sensor */
/* For IMX477: write to trigger mode register */
return sensor_write_reg(s_data->client,
SENSOR_TRIGGER_MODE_REG,
SENSOR_TRIGGER_EXTERNAL);
}
In the DTS, declare the sync GPIO:
imx477@10 {
/* ... */
trigger-gpios = <&tegra_aon_gpio TEGRA234_AON_GPIO(BB, 2) GPIO_ACTIVE_HIGH>;
nvidia,trigger-mode = "external";
};
imx477@11 {
/* ... same GPIO reference ... */
trigger-gpios = <&tegra_aon_gpio TEGRA234_AON_GPIO(BB, 2) GPIO_ACTIVE_HIGH>;
nvidia,trigger-mode = "external";
};
Both sensors reference the same GPIO. When the GPIO fires, both sensors expose simultaneously.
Multi-camera pipeline on Jetson
With synchronized sensors, the Jetson pipeline handles multiple streams in hardware in parallel:
Sensor 0 → NVCSI port 0 → VI → /dev/video0
Sensor 1 → NVCSI port 1 → VI → /dev/video1
The VI captures both streams independently. In a GStreamer pipeline, both streams can run concurrently within a single process (avoid separate processes, see nvvidconv performance collapse with multiple GStreamer processes on Jetson):
gst-launch-1.0 \
nvarguscamerasrc sensor-id=0 ! \
'video/x-raw(memory:NVMM),width=1920,height=1080' ! \
queue ! comp.sink_0 \
nvarguscamerasrc sensor-id=1 ! \
'video/x-raw(memory:NVMM),width=1920,height=1080' ! \
queue ! comp.sink_1 \
nvmultistreamtiler name=comp rows=1 columns=2 \
width=3840 height=1080 ! \
nvvidconv ! nvoverlaysink
The queue elements decouple the two sensor pipelines so a slow frame from one sensor does not stall the other.
Verifying synchronization
After enabling hardware trigger sync, verify that frames from both sensors arrive within the expected window:
# Capture frames with timestamps from both sensors simultaneously
v4l2-ctl --stream-mmap -d /dev/video0 --count=10 -v 2>&1 | grep "ts:" > /tmp/cam0_ts.txt &
v4l2-ctl --stream-mmap -d /dev/video1 --count=10 -v 2>&1 | grep "ts:" > /tmp/cam1_ts.txt &
wait
# Compare timestamps
paste /tmp/cam0_ts.txt /tmp/cam1_ts.txt
# Hardware sync: timestamps should differ by < 1ms per frame
# Software sync / no sync: timestamps drift several ms per frame
Generating the trigger pulse from Jetson
The trigger GPIO needs to toggle at exactly your target frame rate. The simplest approach on Jetson is a userspace PWM loop using /sys/class/gpio:
# Export GPIO (find your GPIO chip and line number for TEGRA234_AON_GPIO(BB, 2))
echo 502 > /sys/class/gpio/export
echo out > /sys/class/gpio/gpio502/direction
# Toggle at 30Hz in a shell loop (for testing only — use a real-time thread for production)
while true; do
echo 1 > /sys/class/gpio/gpio502/value
sleep 0.016
echo 0 > /sys/class/gpio/gpio502/value
sleep 0.017
done
For production, use the Tegra PWM controller or a kernel thread with SCHED_FIFO priority. A userspace loop with standard sleep has ±2 ms jitter under load, which is acceptable for most applications but insufficient for precision strobe timing.
Alternatively, use gpioset from the libgpiod package for cleaner GPIO control:
# Pulse the GPIO once (for on-demand triggered capture)
gpioset --mode=time --usec=500 $(gpiodetect | grep tegra | head -1 | awk '{print $1}') 502=1
Diagnosing synchronization failures
Timestamps diverge after a few seconds
External trigger mode is not active on one or both sensors. The sensor is free-running and ignoring the GPIO. Check that the trigger mode register write succeeded during driver probe, look for errors in dmesg | grep -i "trigger\|imx". Also verify the GPIO is actually toggling: gpioget <chip> <line> while the pipeline is running.
Consistent 1–2 frame offset between cameras
Trigger edge polarity mismatch. One sensor triggers on rising edge, the other on falling. Check the trigger mode register in each sensor’s driver, both must use the same edge. On IMX sensors, the trigger edge is set in the XTRIG_TRIG_SEL register.
Both cameras capture but one shows a black frame every N frames
The trigger period is slightly mismatched to one sensor’s internal line time. The sensor is completing its readout from the previous frame when the next trigger arrives and drops it. Fix: increase the trigger period slightly (reduce frame rate by 0.5 fps) to give the sensor’s readout time more margin.
nvarguscamerasrc opens both sensors but only one produces frames
Both sensors are on the same NVCSI port or the DTS maps them to conflicting resources. Check the DTS sensor node port assignments, each sensor must map to a unique NVCSI port. Review dmesg | grep nvcsi for port conflict errors.
For custom carrier board multi-camera designs or complex synchronization requirements, the Jetson camera bring-up service covers fixed-bid engagements. For GMSL2 multi-camera synchronization which uses the FSYNC signal over the GMSL2 control channel instead of a direct GPIO, see GMSL2 multi-camera sync on Jetson Orin.
The Linux V4L2 timestamp documentation is at kernel.org. NVIDIA’s multi-camera pipeline guidance is in the Jetson Linux Accelerated GStreamer User Guide.
Relevant Services
NVIDIA Jetson Expert Support
Stuck on a Jetson bring-up?
We've debugged this failure mode before. BSP, device tree, camera pipelines, OTA, most blockers clear in the first session. No long retainers. No guessing.
Frequently Asked Questions
Can Jetson Orin capture from multiple CSI cameras simultaneously?
Yes. Jetson Orin has 6 NVCSI ports (AGX Orin) or 4 (Orin NX), each capable of independent capture. Multiple cameras can stream simultaneously to separate V4L2 video nodes. The capture itself is parallel in hardware, the bottleneck is usually the downstream pipeline (GStreamer, Argus, inference) not the NVCSI.
What is the best way to synchronize two CSI cameras on Jetson for stereo vision?
Hardware trigger synchronization using the sensor's external trigger input. A Jetson GPIO generates a sync pulse at the desired frame rate. The DTS configures both sensors to use external trigger mode via their trigger input pins. Both sensors capture on the same pulse edge. This gives sub-millisecond synchronization. Software sync (timestamping frames as they arrive) introduces 5–30 ms of jitter from interrupt latency and scheduling.
How do I set up hardware frame sync for multiple CSI cameras on Jetson?
Configure a Jetson GPIO as PWM output at your target frame rate (e.g., 30Hz = 33ms period). Route this GPIO to each sensor's trigger input pin. In the sensor driver, write the register that puts the sensor in external trigger mode. In the DTS, declare the trigger GPIO and trigger mode for each sensor node. The sensor should output one frame per trigger edge, eliminating free-running clock drift.
What is the FRSYNC signal on Jetson and how does it relate to camera sync?
FRSYNC (Frame Sync) is a Jetson signal routed through the NVCSI hardware that can synchronize frame capture across multiple CSI ports. It is exposed via GPIO and can be used as the hardware trigger for connected sensors. On AGX Orin, certain NVCSI port pairs share a FRSYNC signal. Check the Orin TRM for which ports can be synchronized via FRSYNC vs. requiring an external GPIO-based approach.
My two synchronized CSI cameras have a consistent 1-2 frame offset. What is wrong?
A consistent frame offset with hardware trigger sync usually means one sensor is configured for rising-edge trigger and the other for falling-edge, or the trigger GPIO has different propagation delay to each sensor due to PCB routing differences. Check the trigger edge polarity register setting on each sensor, both must be identical. Also check whether the NVCSI captures from both sensors in the same interrupt cycle.
How do I configure the trigger GPIO as a PWM output on Jetson?
Use the Linux PWM subsystem or sysfs GPIO. For a PWM output at 30fps: export the GPIO, set direction to output, then toggle it at 30Hz via a tight loop in a real-time thread, or use the tegra PWM controller if your carrier board routes a Jetson PWM pin to the trigger input. A userspace loop with nanosleep is usually accurate enough for camera sync, drift at 30Hz is sub-100us over a minute with a properly scheduled thread.
Can I use nvarguscamerasrc for synchronized multi-camera capture in GStreamer?
Yes. Use one nvarguscamerasrc per sensor-id and run them in the same GStreamer pipeline process. With hardware trigger sync active, both sources produce frames on the same physical trigger edge. The Argus API uses the NVCSI hardware timestamp, so corresponding frames from both sensors will have matching timestamps. Avoid running two separate gst-launch-1.0 processes, inter-process scheduling adds milliseconds of jitter.
What happens if one sensor is in external trigger mode and the other is free-running?
The free-running sensor ignores the trigger GPIO and captures at its internal frame rate. The triggered sensor waits for the GPIO pulse. The result is that corresponding frames from the two sensors are not synchronized, they will drift apart over time exactly like two free-running sensors. Both sensors must have external trigger mode enabled in their driver register configuration. Check dmesg for any sensor initialization errors that may have prevented the trigger mode write.
How many CSI cameras can I synchronize simultaneously on Jetson Orin AGX?
Up to 6 simultaneously, one per NVCSI port on AGX Orin (4 on Orin NX). All sensors share the same trigger GPIO signal, so the GPIO fanout is the only practical limit, most carrier boards support 4–6 trigger lines. All sensors must support external trigger mode and be configured to the same frame rate. For more than 4 cameras, use GMSL2 with a multi-port deserializer rather than direct CSI, GMSL2 routes the trigger over the coaxial cable, removing the GPIO fanout problem.
Written by
Andrés CamposCo-Founder & CTO · ProventusNova
8 years deep in embedded systems, from underwater ROVs to edge AI. Andrés leads every technical delivery personally.
Connect on LinkedInRelated Articles
GMSL2 multi-camera sync on Jetson Orin: FSYNC setup guide
GMSL2 multi-camera frame sync for Jetson Orin: FSYNC path, virtual channel assignment, MAX96724 topology, GStreamer pipeline, and verification.
GMSL YUV422 capture and FORCE_FE errors on Jetson Orin — debug guide
Debug GMSL YUV422 capture issues on Jetson Orin — FORCE_FE decoder config, partial frame faults, and MAX9295/MAX9296 YUV format setup.
Best Companies for Custom Camera Driver Development on NVIDIA Jetson
Comparing the top options for V4L2, MIPI CSI, and GMSL2 camera driver development on NVIDIA Jetson — specialists, hardware vendors, and freelancers.
CSI camera driver on Jetson: V4L2, Argus, and IMX sensor bring-up
How to bring up a CSI camera driver on NVIDIA Jetson. Covers V4L2 vs Argus, IMX sensor devicetree config, media-ctl debugging, and the 5 most common failures.