Three synchronized CSI camera modules for Jetson Orin multi-camera hardware trigger synchronization setup
jetsonmulti-cameracsiframe synctriggersynchronizationv4l2

Jetson multi-camera sync: CSI hardware trigger setup

Andres Campos · · Updated

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 vs software sync vs GMSL2 FSYNC: which to use?

The right synchronization method depends on your camera interface and how many cameras you need to synchronize.

MethodSync accuracyMax camerasCable requirementWhen to use
Hardware GPIO trigger (CSI cameras)<1 ms4–6 (GPIO fanout limit)Dedicated trigger wire to each sensor2–4 CSI cameras on a single carrier board
Software timestamping5–30 ms jitterUnlimitedNoneNever — only acceptable for non-synchronized logging
GMSL2 FSYNC<1 ms8+ per deserializerExisting GMSL2 coax4+ cameras, especially over distance
NVCSI FRSYNC<1 ms2 per port pairBoard routing onlySpecific Orin port pairs that share FRSYNC

For most stereo vision and surround-view applications on Jetson with CSI cameras, hardware GPIO trigger is the right choice. It requires a trigger wire from one Jetson GPIO to each sensor’s trigger input pin, which is typically 4–6 connections on a well-designed carrier board.

For GMSL2 cameras, FSYNC over the coax is the standard approach. The MAX9296A deserializer generates the FSYNC pulse; no additional wiring is needed beyond the GMSL2 cable already present.

Software timestamping is not a synchronization method — it is a measurement of when frames arrived, which is different from when they were captured. For any application where inter-camera alignment matters (stereo depth, surround view, multi-sensor fusion), software timestamps are inadequate.

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

What synchronization accuracy does hardware trigger achieve?

With hardware trigger sync correctly configured, frame capture timestamps from two CSI cameras on the same Jetson should differ by less than 1 millisecond. In practice, on Orin with correctly configured sensors, we measure deltas of 100–400 microseconds between corresponding frames.

Example v4l2-ctl timestamp output from a synchronized stereo pair (Jetson AGX Orin, 30fps, IMX477 sensors):

cam0: ts: 1.234567890  cam1: ts: 1.234568102  delta: 212 µs
cam0: ts: 1.267901234  cam1: ts: 1.267901456  delta: 222 µs
cam0: ts: 1.301234567  cam1: ts: 1.301234789  delta: 222 µs
cam0: ts: 1.334567890  cam1: ts: 1.334568101  delta: 211 µs

The delta is stable across frames — this is what correctly working hardware trigger sync looks like. The residual 200-µs offset is from fixed PCB routing delay differences between the two trigger paths, not from timing jitter.

For comparison, the same cameras running free (software timestamps only):

cam0: ts: 1.234567890  cam1: ts: 1.239123456  delta: 4.555 ms
cam0: ts: 1.267901234  cam1: ts: 1.274123456  delta: 6.222 ms
cam0: ts: 1.301234567  cam1: ts: 1.309876543  delta: 8.641 ms  ← drifting
cam0: ts: 1.334567890  cam1: ts: 1.345012345  delta: 10.444 ms

The free-running cameras drift apart at roughly 4 ms/second due to crystal oscillator tolerance mismatch. At 10 seconds, the cameras are a full frame apart. Hardware trigger prevents this entirely.

For stereo depth estimation, a >1 ms delta between corresponding frames creates depth errors proportional to the object’s velocity. At 60 km/h (16.7 m/s), 1 ms of desync introduces ~17 mm of depth error per meter of distance — acceptable for many applications. At 10 ms desync (typical software sync), that error grows to 167 mm/m, which breaks most stereo depth algorithms.

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.

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.

What is the GMSL2 FSYNC signal and how does it differ from GPIO trigger sync?

FSYNC in GMSL2 context is a frame synchronization signal transmitted over the GMSL2 control channel (reverse channel) from the deserializer to the serializer and sensor. Unlike GPIO trigger sync where a dedicated wire runs to each sensor, GMSL2 FSYNC routes the trigger over the existing coaxial cable. This eliminates the GPIO fanout problem for multi-camera setups and is the reason GMSL2 cameras are preferred for 4+ camera synchronization. The deserializer (e.g. MAX9296A) generates the FSYNC pulse on the coax; the serializer (MAX9295A) passes it to the sensor trigger input.

How do I verify hardware trigger sync is working without an oscilloscope?

Capture timestamps from both cameras simultaneously using v4l2-ctl and compare them. Run: v4l2-ctl --stream-mmap -d /dev/video0 --count=10 -v 2>&1 | grep ts: > /tmp/cam0.txt & v4l2-ctl --stream-mmap -d /dev/video1 --count=10 -v 2>&1 | grep ts: > /tmp/cam1.txt & wait. Then paste /tmp/cam0.txt /tmp/cam1.txt and look at the timestamp columns side by side. Hardware sync: deltas under 1ms. Software sync or free-running: deltas of 5–30ms that drift over time.

Can I use a frame rate below 30fps with hardware trigger sync on Jetson?

Yes. The trigger GPIO PWM frequency sets the frame rate. For 15fps, use a 66ms period; for 10fps, 100ms. The sensor must support the target frame rate in its external trigger mode — most sensors support any rate from 1fps up to their maximum. Slower frame rates can actually improve synchronization accuracy because the sensor's readout time is a smaller fraction of the frame period, giving more margin before the next trigger arrives.

Andrés Campos, Co-Founder & CTO at ProventusNova

Written by

Andrés Campos

Co-Founder & CTO · ProventusNova

8 years deep in embedded systems, from underwater ROVs to edge AI. Andrés leads every technical delivery personally.

Connect on LinkedIn