from __future__ import print_function
import sys
import PyDAQmx as daq
from PyDAQmx import uInt32, int32, int16, byref
from contextlib import contextmanager
I am trying to use a NI PCI-6602 with the BNC-2121 accessory to make a multichannel pulse sequence. To make sure things are working, first try to get 4 synchronous square pulses out.
My first (naiive) approach is to make one task, add 4 counter out channels to it, and start the task.
def configure_counter(duration=.1):
pulses = daq.Task()
channels = (2,3,4,5)
# configure pulses
for ch in channels:
channel = "Dev1/ctr%d" % ch
pulses.CreateCOPulseChanTime(
channel, "", # physical channel, name to assign
daq.DAQmx_Val_Seconds, # units: seconds
daq.DAQmx_Val_Low, # idle state: low
0., duration, duration, # initial delay, low time, high time
)
return pulses
def start_pulse(pulse):
pulse.StartTask()
def finish_pulse(pulse):
pulse.WaitUntilTaskDone(10.)
pulse.StopTask()
@contextmanager
def pulsing(pulse):
"""This stops the pulsing task if I interrupt the python kernel"""
try:
yield
except KeyboardInterrupt:
# stop the counters
try:
pulse.StopTask()
print("stopped pulses", file=sys.stderr)
except DAQError:
print("no need to stop pulses", file=sys.stderr)
raise
The following should make 2us pulses on the 4 channels:
# make sure to press 'single shot' on the scope, then:
pp = configure_counter(duration=2e-6)
with pulsing(pp):
start_pulse(pp)
finish_pulse(pp)
Using this approach I see that the different pulses do not fire synchronously, but rather they fire in the order they were configured, staggered by almost 7us.
So, apparently adding a bunch of channels to a single task doesn't work. Try defining them in individual tasks.
def configure_counter(duration=.1):
pulses = []
channels = (2,3,4,5)
# configure a task for each pulse.
for ch in channels:
pulse = daq.Task()
channel = "Dev1/ctr%d" % ch
pulse.CreateCOPulseChanTime(
channel, "", # physical channel, name to assign
daq.DAQmx_Val_Seconds, # units: seconds
daq.DAQmx_Val_Low, # idle state: low
0., duration, duration, # initial delay, low time, high time
)
pulses.append(pulse)
# configure the first pulse to trigger off the second pulse, and so on
for pulse,ch in zip(pulses[:-1],channels[1:]):
trigch = "Ctr%dInternalOutput" % ch
pulse.CfgDigEdgeStartTrig(trigch, daq.DAQmx_Val_Rising)
return pulses
def start_pulses(*pulses):
for pulse in pulses:
pulse.StartTask()
def finish_pulses(*pulses):
# wait for pulse to be done
for pulse in pulses:
pulse.WaitUntilTaskDone(10.)
pulse.StopTask()
@contextmanager
def pulsing(*pulses):
try:
yield
except KeyboardInterrupt:
# stop the counters
for i,pulse in enumerate(pulses):
try:
pulse.StopTask()
print("stopped pulse %d" % i, file=sys.stderr)
except DAQError:
print("no need to stop pulse %d" % i, file=sys.stderr)
raise
# press 'single shot', then:
pp = configure_counter(duration=100e-9)
with pulsing(*pp):
start_pulses(*pp)
finish_pulses(*pp)
The pulses now fire in the reverse of the order configured, staggered by 40ns or so. Much better, but would be better still if we could trigger all from the same channel.
def configure_counter(duration=.1):
pulses = []
channels = (2,3,4,5)
# configure a task for each pulse.
for ch in channels:
pulse = daq.Task()
channel = "Dev1/ctr%d" % ch
pulse.CreateCOPulseChanTime(
channel, "", # physical channel, name to assign
daq.DAQmx_Val_Seconds, # units: seconds
daq.DAQmx_Val_Low, # idle state: low
0., duration, duration, # initial delay, low time, high time
)
pulses.append(pulse)
# configure the all but the last pulse to trigger from the last pulse
trigch = "Ctr%dInternalOutput" % channels[-1]
for pulse in pulses[:-1]:
pulse.CfgDigEdgeStartTrig(trigch, daq.DAQmx_Val_Rising)
return pulses
# press 'single shot', then:
pp = configure_counter(duration=100e-9)
with pulsing(*pp):
start_pulses(*pp)
finish_pulses(*pp)
On the scope, the pulses from channels 2 and 3 are synchronous, but a little behind channel 4, which is of course a little behind channel 5, which is the trigger source. I think the discrepcancy between channels 2 and 3 from 4 is that 4 is a paired counter with channel 5.
I can get 3 synchronous channels by making sure that none of them is paired with the one I'm using to trigger from. That is, by changing channels
to (2,3,1,5)
above.