Quick Start Guide

This guide will help you get started with XClock quickly. We’ll cover basic usage patterns for both the Python library and command-line interface.

Prerequisites

Make sure you have:

  • Installed XClock (see Installation)

  • Installed your DAQ device drivers (e.g., LabJack LJM software)

  • Connected your DAQ device via USB

Your First Clock

Let’s create a simple clock that runs for 5 seconds at 100 Hz.

Using Python

from xclock.devices import LabJackT4

# Initialize device
t4 = LabJackT4()

# Get available output channels
channels = t4.get_available_output_clock_channels()
print(f"Available channels: {channels}")

# Add a 100 Hz clock for 5 seconds
t4.add_clock_channel(
    clock_tick_rate_hz=100,
    channel_name=channels[0],
    duration_s=5.0,
)

# Start the clock and wait for completion
t4.start_clocks(wait_for_pulsed_clocks_to_finish=True)

print("Clock finished!")
t4.close()

Using CLI

xclock --clock-tick-rates 100 --duration 5 start

Multiple Synchronized Clocks

One of XClock’s key features is running multiple clocks simultaneously, all synchronized to the same base clock.

Python Example

from xclock.devices import LabJackT4

t4 = LabJackT4()
channels = t4.get_available_output_clock_channels()

# Add multiple clocks with different frequencies
t4.add_clock_channel(
    clock_tick_rate_hz=60,    # Camera 1 at 60 Hz
    channel_name=channels[0],
    duration_s=10.0,
)

t4.add_clock_channel(
    clock_tick_rate_hz=100,   # Camera 2 at 100 Hz
    channel_name=channels[1],
    duration_s=10.0,
)

# Start all clocks simultaneously
t4.start_clocks(wait_for_pulsed_clocks_to_finish=True)
t4.close()

CLI Example

xclock --clock-tick-rates 60,100 --duration 10 start

Recording Timestamps

XClock can record precise timestamps of all clock edges, which is useful for synchronizing data post-acquisition.

Python Example

from xclock.devices import LabJackT4
import pathlib
import numpy as np

t4 = LabJackT4()
channels = t4.get_available_output_clock_channels()

# Add clocks
t4.add_clock_channel(
    clock_tick_rate_hz=100,
    channel_name=channels[0],
    duration_s=10.0,
)

# Start and record timestamps
output_file = pathlib.Path.home() / "Documents" / "XClock" / "timestamps.csv"
t4.start_clocks_and_record_edge_timestamps(
    wait_for_pulsed_clocks_to_finish=True,
    filename=output_file
)

print(f"Timestamps saved to: {output_file}")

# Load and inspect the data
data = np.loadtxt(output_file, dtype=np.int64, delimiter=",")
print(f"Recorded {len(data)} edges")
print(f"First few timestamps:\n{data[:5]}")

t4.close()

CLI Example

xclock --clock-tick-rates 100 --duration 10 --record-timestamps start

Timestamps are saved to ~/Documents/XClock/xclock_timestamps_<timestamp>.csv

Understanding the Timestamp Format

The CSV file contains three columns:

  1. Timestamp (nanoseconds): Time since device start (relative timing)

  2. Edge type: Indicates which clock and edge direction

    • Positive number (e.g., 1, 2): Rising edge on clock 1, 2, etc.

    • Negative number (e.g., -1, -2): Falling edge on clock 1, 2, etc.

  3. Unix timestamp (nanoseconds): Host system time (absolute timing)

Example:

1000000,1,1638360000000000000      # Rising edge on clock 1 at 1ms (device time)
1500000,-1,1638360000500000000     # Falling edge on clock 1 at 1.5ms
2000000,1,1638360001000000000      # Rising edge on clock 1 at 2ms
2000000,2,1638360001000000000      # Rising edge on clock 2 at 2ms (same device time)

The Unix timestamp (column 3) allows you to correlate events with other systems or absolute wall-clock time.

Continuous vs. Pulsed Clocks

XClock supports two modes:

Continuous Clocks

Runs indefinitely until manually stopped.

t4.add_clock_channel(
    clock_tick_rate_hz=100,
    channel_name=channels[0],
    number_of_pulses=None,  # None = continuous
)

t4.start_clocks(wait_for_pulsed_clocks_to_finish=False)

# Do your experiment...
import time
time.sleep(30)

# Stop when done
t4.stop_clocks()

Pulsed Clocks

Generates a specific number of pulses then stops automatically.

# Specify exact pulse count
t4.add_clock_channel(
    clock_tick_rate_hz=100,
    channel_name=channels[0],
    number_of_pulses=1000,  # Exactly 1000 pulses
)

# Or use duration (auto-calculates pulses)
t4.add_clock_channel(
    clock_tick_rate_hz=100,
    channel_name=channels[0],
    duration_s=10.0,  # 100 Hz * 10s = 1000 pulses
)

Trigger-Based Start

Start clocks when an external trigger signal is received.

Python Example

from xclock.devices import LabJackT4, EdgeType

t4 = LabJackT4()
channels = t4.get_available_output_clock_channels()
trigger_channels = t4.get_available_input_start_trigger_channels()

# Add clocks (but don't start yet)
t4.add_clock_channel(
    clock_tick_rate_hz=100,
    channel_name=channels[0],
    duration_s=10.0,
)

print(f"Waiting for trigger on {trigger_channels[0]}...")
print("Send a rising edge to start the clock.")

# Wait for trigger
triggered = t4.wait_for_trigger_edge(
    channel_name=trigger_channels[0],
    timeout_s=30.0,
    edge_type=EdgeType.RISING,
)

if triggered:
    print("Trigger received! Starting clocks...")
    t4.start_clocks(wait_for_pulsed_clocks_to_finish=True)
else:
    print("Timeout waiting for trigger")

t4.close()

CLI Example

xclock --clock-tick-rates 100 --duration 10 --when on_trigger --timeout 30 start

Common Patterns

Pattern 1: Synchronize Two Cameras

from xclock.devices import LabJackT4

t4 = LabJackT4()
channels = t4.get_available_output_clock_channels()

# Behavior camera at 60 Hz
t4.add_clock_channel(60, channels[0], duration_s=300)  # 5 minutes

# Imaging camera at 30 Hz
t4.add_clock_channel(30, channels[1], duration_s=300)

# Record all frame timestamps
t4.start_clocks_and_record_edge_timestamps(
    wait_for_pulsed_clocks_to_finish=True,
    filename="camera_sync.csv"
)

Pattern 2: Test Device Connectivity

from xclock.devices import LabJackT4

try:
    t4 = LabJackT4()
    print(f"✓ Connected to LabJack T4")
    print(f"✓ Base clock: {t4.base_clock_frequency_hz} Hz")
    print(f"✓ Available channels: {t4.get_available_output_clock_channels()}")
    t4.close()
except Exception as e:
    print(f"✗ Connection failed: {e}")

Pattern 3: Short Test Pulse

Useful for testing wiring and connections:

from xclock.devices import LabJackT4

t4 = LabJackT4()
channels = t4.get_available_output_clock_channels()

# Generate 10 pulses at 10 Hz (1 second total)
t4.add_clock_channel(
    clock_tick_rate_hz=10,
    channel_name=channels[0],
    number_of_pulses=10,
)

t4.start_clocks(wait_for_pulsed_clocks_to_finish=True)
print("Test pulses complete!")
t4.close()

Next Steps

Now that you understand the basics:

Common Issues

Clock frequency not exactly as requested

XClock calculates the closest achievable frequency based on the device’s base clock and divisor limitations. The actual frequency is returned in the ClockChannel.actual_sample_rate_hz field.

channel = t4.add_clock_channel(clock_tick_rate_hz=100, ...)
print(f"Requested: 100 Hz, Actual: {channel.actual_sample_rate_hz} Hz")

Device already in use

If you get an error about the device being in use:

  1. Make sure you called close() on previous device instances

  2. Check if another process is using the device

  3. Disconnect and reconnect the device

  4. Restart your Python kernel/terminal

No pulses generated

Common causes:

  • Clock not started with start_clocks()

  • number_of_pulses set to 0

  • Device not properly connected

  • Check the physical wiring and connections