Moku Pro
MokuOS: 4.2.2
Hardware version: 3.0
Firmware: 644.0
Python: 3.11.15
I have a Moku pro and I tried to write a python API to repetitively trigger the data logger for automated measurement. Within multi-instrument mode, I set up a trigger source using a signal generator. I then route that signal to the data logger, and use a loop to rearm the session after each trigger has been received.
But sometimes I run into errors where the data logger freezes and indefinitely stays in the waiting for trigger stage. I confirmed that the trigger signal is firing correctly when this happens. I was wondering if there is a problem with my python control code, or perhaps I’m running into a software bug.
A screenshot of the multi-instrument set up is shown below, and I have attached the python script that I was using. Any help would be appreciated, thanks in advance!
“”"
Setup, 3 MIM slots:
Slot 1 – Waveform Generator (WFG)
OutB: periodic square-wave trigger (Internal burst mode),
also routed out to physical Output 1
Slot 2 – Lock-in Amplifier (LIA)
InputA ← physical Input 1 ← [loop cable] ← physical Output 1
(kept in the chain in case its own internal
clock/processing contributes to the bug)
OutA → Slot3InA (demodulated output, recorded)
Slot 3 – Datalogger (DL)
InputA ← Slot2OutA (recorded data)
InputB ← Slot1OutB (hardware trigger source, internal)
Routing:
Slot1OutB → Slot3InB (trigger, internal)
Slot1OutB → Output1 (trigger, physical monitor/loopback)
Input1 → Slot2InA (physical Input 1 → LIA in)
Slot2OutA → Slot3InA (data)
Goal: WFG OutB fires a trigger pulse every BURST_PERIOD seconds via its
‘Internal’ burst mode. Each loop iteration arms the Datalogger and waits
for logging_progress()[‘complete’] to go True. l connection.
“”"
import time
from moku.instruments import (
WaveformGenerator,
LockInAmp,
Datalogger,
MultiInstrument,
)
# ─── Configuration ──────────────────────────────────────────────────────────
IP_ADDRESS = ‘…’
# Datalogger
SAMPLE_RATE = 100 # Sa/s
DURATION = 0.2 # seconds per triggered session
NUM_SESSIONS = None # None = run until Ctrl-C (recommended for bug hunting)
# Waveform Generator
WFG_TRIGGER_FREQUENCY = 1000 # Hz — shape of the pulse itself
WFG_TRIGGER_AMPLITUDE = 2.0 # Vpp (swings -1V to +1V)
WFG_TRIGGER_OFFSET = 0.0 # V
WFG_TRIGGER_DUTY = 50 # %
WFG_TRIGGER_BURST_CYCLES = 1
BURST_PERIOD = 3 # seconds between trigger events
# Lock-in Amplifier
LIA_FREQUENCY = 1e6 # Hz — internal demod reference (no live input signal)
LIA_CORNER_FREQ = 100e3
LIA_FILTER_SLOPE = ‘Slope6dB’
LIA_MAIN_OUTPUT = ‘R’
# Datalogger trigger — comfortably within the -1V..+1V swing of the WFG
# trigger signal, not a marginal/near-peak value.
TRIGGER_LEVEL = 0.5
# ─── Connect and configure ───────────────────────────────────────────────────
print(‘Connecting to Moku Pro …’)
moku = MultiInstrument(IP_ADDRESS, platform_id=4, force_connect=True)
wfg = moku.set_instrument(1, WaveformGenerator)
lia = moku.set_instrument(2, LockInAmp)
dl = moku.set_instrument(3, Datalogger)
moku.set_connections(connections=[
dict(source=‘Slot1OutB’, destination=‘Slot3InB’), # trigger → DL (internal)
dict(source=‘Slot1OutB’, destination=‘Output1’), # trigger → physical Output 1
dict(source=‘Input1’, destination=‘Slot2InA’), # physical Input 1 → LIA in
dict(source=‘Slot2OutA’, destination=‘Slot3InA’), # LIA out → DL data channel
])
print(‘Instruments deployed and connected.’)
# ── Front-end configuration ─────────────────────────────────────────────────
moku.set_frontend(channel=1, impedance=‘1MOhm’, coupling=‘DC’, bandwidth=‘300MHz’, attenuation=‘0dB’)
moku.set_output(channel=1, output_gain=‘0dB’) # 2 Vpp — trigger monitor out
print(‘Front-end (AFE) settings applied.’)
# OutA not used — off.
wfg.generate_waveform(channel=1, type=‘Off’)
wfg.generate_waveform(
channel=2,
type=‘Square’,
frequency=WFG_TRIGGER_FREQUENCY,
amplitude=WFG_TRIGGER_AMPLITUDE,
offset=WFG_TRIGGER_OFFSET,
duty=WFG_TRIGGER_DUTY,
)
wfg.set_burst_mode(
channel=2,
source=‘Internal’,
mode=‘NCycle’,
burst_cycles=WFG_TRIGGER_BURST_CYCLES,
burst_period=BURST_PERIOD,
)
print(f’WFG: OutA = off | OutB = burst trigger, period {BURST_PERIOD}s’)
lia.set_demodulation(mode=‘Internal’, frequency=LIA_FREQUENCY)
lia.set_filter(corner_frequency=LIA_CORNER_FREQ, slope=LIA_FILTER_SLOPE)
lia.set_outputs(main=LIA_MAIN_OUTPUT, aux=‘None’)
print(f’LIA: {LIA_FREQUENCY:.0f} Hz demod, corner {LIA_CORNER_FREQ:.0f} Hz ({LIA_FILTER_SLOPE})')
dl.set_acquisition_mode(mode=‘Precision’)
dl.enable_input(channel=1, enable=True) # InputA — recorded LIA data
dl.enable_input(channel=2, enable=True) # InputB — hardware trigger source
print(‘Datalogger configured.\n’)
# ─── Triggered logging loop ───────────────────────────────────────────────────
try:
dl.stop_logging()
except Exception:
pass
def arm():
return dl.start_logging(
duration=DURATION,
sample_rate=SAMPLE_RATE,
trigger_source=‘InputB’,
trigger_level=TRIGGER_LEVEL,
)
session = 0
try:
log_info = arm()
session = 1
print(f’[{session:04d}] Armed — {log_info[“file_name”]}')
while NUM_SESSIONS is None or session < NUM_SESSIONS:
start_wait = time.time()
while True:
progress = dl.logging_progress()
if progress.get(‘complete’):
break
elapsed = time.time() - start_wait
print(f’\r[{session:04d}] Waiting for trigger … {elapsed:5.1f}s’, end=‘’, flush=True)
time.sleep(0.05)
print(f’\r[{session:04d}] Done — {progress[“file_name”]} ’
f’({progress[“samples_logged”]} samples) ')
session += 1
log_info = arm()
print(f’[{session:04d}] Armed — {log_info[“file_name”]}')
except KeyboardInterrupt:
print(f’\nStopped by user after {session} attempted sessions.')
finally:
try:
moku.relinquish_ownership()
except Exception:
pass
print(‘Disconnected.’)
