Multifunction DAQ

cancel
Showing results for 
Search instead for 
Did you mean: 

How to generate an output waveform that follows raising and failing edge of input waveform?

Solved!
Go to solution

Dear all,

 

I managed to generate an analog waveform as the DAQ is triggered by a wave generator (square pulse connected to PF1 pin) using `CfgDigEdgeAdvTrig'. The output is produced as soon as the trigger waveform is sent to the NI board (USB-6341). 

 

However, I want to generate an output that continuously follows the digital input waveform. For example (1) the digital output that goes up and down following the rising and falling edge of the square pulse, instead of only using the trigger as a starting point, (2) the analog output that will move to the next value as it detects the change value in the digital input. I have been looking in the manual but cannot seem to find the correct function.

 

Any hint on how to accomplish this? I have been trying `CfgDigEdgeRefTrig` and `CfgDigEdgeAdvTrig` with an error saying "Specified property is not supported by the device".

 

Thank you so much, NI community!!

 

Franky

0 Kudos
Message 1 of 9
(2,339 Views)

I'm no help for the Python syntax to implement this, but there's a *mostly* straightforward way to do this very simply by using hardware features of your X-series device.

 

Here's an outline of the approach, including keywords that should help you to be able to track down the proper syntax in Python.

 

1. Configure a digital input task that uses "change detection" as its sample timing mode.  Have it sample on both rising and falling edges of the digital signal in question.

2. Configure an analog output task that uses the special internal signal known as the "change detection event" as its sample clock.  You'll probably want this configured for continuous sampling.

3. Define your buffer of AO values.  Under continuous sampling, by default the task will regenerate that buffer of values repeatedly, one sample per digital transition.

 

That's most of it.  The remaining catch is making sure that the polarity of the first digital transition corresponds to the first sample in the AO buffer (such as low->high transition and initial AO buffer value representing "high").

 

One possible approach is to first set up temporary throw-away tasks that check the initial DI state and/or set the initial AO state.  You can then fill your AO buffer appropriately for that initial state.  (Note: this works for the many situations where the DI won't change between the initial sneak peak and the start of the "real" app.  This is often under user control -- start the app, then start the equipment that'll make the DI start changing.)

 

 

-Kevin P

CAUTION! New LabVIEW adopters -- it's too late for me, but you *can* save yourself. The new subscription policy for LabVIEW puts NI's hand in your wallet for the rest of your working life. Are you sure you're *that* dedicated to LabVIEW? (Summary of my reasons in this post, part of a voluminous thread of mostly complaints starting here).
Message 2 of 9
(2,288 Views)

Awesome!! Thank you so much, Kevin! We managed to set the analog output to use the external wave generator as an external clock. But we have not been successful to use a "change detection" sample timing mode. Instead we only managed to have it set into rising or falling edge timing mode using the command `CfgSampClkTiming`. 

 

What Kevin suggested should have been able to be accomplished using `CfgChangeDetectionTiming` based on NI-documentation, but it throws me an error -200077. I am posting the error and code here in case somebody has experiences with the API. Attached is the output probed by the oscilloscope: yellow is the waveform, purple is the digital output (DO), and cyan is the analog output (AO).

 

Any hints on how to accomplish this change detection sample timing, aka detecting both rising and falling edges, will save us a lot of time!! Thank you.

 

Franky

 

 

DAQmx Error Requested value is not a supported value for this property. The property value may be invalid because it conflicts with another property.
Property: DAQmx_SampTimingType
Requested Value: DAQmx_Val_ChangeDetection
Possible Values: DAQmx_Val_SampClk, DAQmx_Val_OnDemand

Task Name: _unnamedTask<0>

Status Code: -200077
 in function DAQmxCfgChangeDetectionTiming

 

 

 

import PyDAQmx as daq
import numpy as np
import ctypes as ct

valueAO = 1.7
samples_per_ch = 10
dataDO = np.zeros((samples_per_ch,8),dtype=np.uint8)
dataDO[::2,:] = 1
print(dataDO)
DAQ_sample_rate_Hz = 20000
wf = np.linspace(1,5,samples_per_ch)
wf[-1] = 0

try:
    # ANALOG output
    taskAO = daq.Task()
    taskAO.CreateAOVoltageChan("/Dev1/ao0","",-10.0,10.0,daq.DAQmx_Val_Volts,None)
    
    ## Stop any task
    taskAO.StopTask()
    
    ## Configure timing, using external clock wave generator)
    #taskAO.CfgSampClkTiming("/Dev1/PFI0",DAQ_sample_rate_Hz,daq.DAQmx_Val_Rising,daq.DAQmx_Val_FiniteSamps,samples_per_ch)
    taskAO.CfgChangeDetectionTiming("/Dev1/PFI0","/Dev1/PFI0",daq.DAQmx_Val_FiniteSamps,samples_per_ch)
    
    ## Set where the starting trigger 
    taskAO.CfgDigEdgeStartTrig("/Dev1/PFI0", daq.DAQmx_Val_Rising)
    
    ## Write the output waveform
    samples_per_ch_ct = ct.c_int32()
    taskAO.WriteAnalogF64(samples_per_ch, False, 10.0, daq.DAQmx_Val_GroupByScanNumber,wf,ct.byref(samples_per_ch_ct),None)
    
    # DIGITAL output
    taskDO = daq.Task()
    taskDO.CreateDOChan("/Dev1/port0/line0:7","",daq.DAQmx_Val_ChanForAllLines)
    
    ## Configure timing, using external clock wave generator) 
    #taskDO.CfgSampClkTiming("/Dev1/PFI0",DAQ_sample_rate_Hz,daq.DAQmx_Val_Rising,daq.DAQmx_Val_FiniteSamps,samples_per_ch)
    taskDO.CfgChangeDetectionTiming("/Dev1/PFI0","/Dev1/PFI0",daq.DAQmx_Val_FiniteSamps,samples_per_ch)
    
    ## Set where the starting trigger 
    taskDO.CfgDigEdgeStartTrig("/Dev1/PFI0", daq.DAQmx_Val_Rising)
    
    ## Write the output waveform
    taskDO.WriteDigitalLines(samples_per_ch,False,10.0,daq.DAQmx_Val_GroupByChannel,dataDO,None,None)

    ## Start both tasks  
    taskDO.StartTask()
    taskAO.StartTask()
    taskAO.WaitUntilTaskDone(-1) # wait until signal is sent
    taskDO.WaitUntilTaskDone(-1) # wait until signal is sent
   
    ## Stop and clear both tasks
    taskAO.StopTask()
    taskDO.StopTask()
    taskAO.ClearTask()
    taskDO.ClearTask()
    
except daq.DAQError as err:
    print("DAQmx Error %s"%err)

 

 

0 Kudos
Message 3 of 9
(2,281 Views)

Again, can't get you all the way through the Python syntax, but I *can* see at least *some* of what's wrong at the moment.

 

An AO task can't be *directly* configured to use Change Detection timing with a function like you tried.  It has to be done *indirectly*.

 

Under LabVIEW, one would call the regular DAQmx Timing config function.  One of the inputs allows you to choose the clock source.  *This* is where I would specify something similar to "/Dev1/ChangeDetectEvent" as the clock source.

 

One other subtlety: be sure to start the AO task first.  Then it'll be sure to be ready and waiting when the DI task asserts the first change detection event.

 

 

-Kevin P

CAUTION! New LabVIEW adopters -- it's too late for me, but you *can* save yourself. The new subscription policy for LabVIEW puts NI's hand in your wallet for the rest of your working life. Are you sure you're *that* dedicated to LabVIEW? (Summary of my reasons in this post, part of a voluminous thread of mostly complaints starting here).
Message 4 of 9
(2,277 Views)

Kevin, thank you for the insight! Your intuition is very helpful to navigate this problem on our X-series board. Based on the query search of` `CfgChangeDetectionTiming` on NI's Example folders, it appears that this function is only intended for Digital Input (DI) aka reading not writing. In addition to that, as I switched the create task from DO to DI, the initial error disappears, which indicates the same notion.

 

Having said that, do you think it will be a good route to attempt reading the DI using CfgChangeDetectionTiming timing from PF1 (wave generator), then using this reading (in the buffer) to drive the digital output (DO) to see both rising and falling edges? We don't really need the AO to follow both rising and falling edges, only the DO. I have not been able to figure how to do this yet to test its viability.

 

Franky

0 Kudos
Message 5 of 9
(2,265 Views)

Sorry, oversight on my part.  You can do the same thing for the DO task that I suggested for the AO task -- configure it with the regular DAQmxTiming config function for setting up a sample clock, BUT identify the internal signal '/Dev1/ChangeDetectEvent' as the clock source.

 

Again, start both the AO and DO tasks before starting DI.

 

 

-Kevin P

CAUTION! New LabVIEW adopters -- it's too late for me, but you *can* save yourself. The new subscription policy for LabVIEW puts NI's hand in your wallet for the rest of your working life. Are you sure you're *that* dedicated to LabVIEW? (Summary of my reasons in this post, part of a voluminous thread of mostly complaints starting here).
Message 6 of 9
(2,217 Views)

Kevin, great news!! We found the way to implement this. The way to do it is to set up a DI task with change detection clock timing and export it to unused PFI pins, both for the timing and the StartTrigger. This exported signal is then used as a clock and starting trigger for both AO and DO tasks.

 

Below is the Python code in case somebody needs it! Attached is the photo of the output probed by oscilloscope (yellow is the input, cyan is the analog, and purple is the digital)

 

Thank you so much, Kevin! I am wondering if you know a trick on how to repeat this sequence. Right now, the sequence is limited by 'samples_per_ch' which I am not sure what the maximum value is. We want to repeat this indefinitely in the hardware.

 

Franky

 

import PyDAQmx as daq
import numpy as np
import ctypes as ct

samples_per_ch = 21 # must be odd number
DAQ_sample_rate_Hz = 10000

# Generate values for DO
dataDO = np.zeros((samples_per_ch,8),dtype=np.uint8)
dataDO[::2,:] = 1
dataDO[-1, :] = 0
print(dataDO)

# Generate values for AO
min_volt = 1
max_volt = 5
wf = np.zeros(samples_per_ch)
wf_temp = np.linspace(min_volt,max_volt,samples_per_ch//2)
wf[1::2] = wf_temp # start : end : step
wf[2::2] = wf_temp
wf[-1] = 0

try:    
    # ----- DIGITAL input -------
    taskDI = daq.Task()
    taskDI.CreateDIChan("/Dev1/PFI0","",daq.DAQmx_Val_ChanForAllLines)
    taskDI.CfgInputBuffer(0)
    
    #Configure change detectin timing (from wave generator)   
    taskDI.CfgChangeDetectionTiming("/Dev1/PFI0","/Dev1/PFI0",daq.DAQmx_Val_ContSamps,0)
    
    #Set where the starting trigger 
    taskDI.CfgDigEdgeStartTrig("/Dev1/PFI0",daq.DAQmx_Val_Rising)
    
    ## Export the digital input to PFI1 (will be used for start trigger) and PFI2 (will be used for clock)
    taskDI.ExportSignal(daq.DAQmx_Val_ChangeDetectionEvent, "/Dev1/PFI2")
    taskDI.ExportSignal(daq.DAQmx_Val_StartTrigger, "/Dev1/PFI1")
    
    # ----- DIGITAL output ------   
    taskDO = daq.Task()
    taskDO.CreateDOChan("/Dev1/port0/line0:7","",daq.DAQmx_Val_ChanForAllLines)

    ## Stop any task
    taskDO.StopTask()
    
    ## Configure timing (from DI task ported to /Dev/PFI2) 
    taskDO.CfgSampClkTiming("/Dev1/PFI2",DAQ_sample_rate_Hz,daq.DAQmx_Val_Rising,daq.DAQmx_Val_FiniteSamps,samples_per_ch)
    
    ## Set where the starting trigger (from DI task ported to /Dev/PFI1)
    taskDO.CfgDigEdgeStartTrig("/Dev1/PFI1",daq.DAQmx_Val_Rising)
    
    ## Write the output waveform 
    taskDO.WriteDigitalLines(samples_per_ch,False,10.0,daq.DAQmx_Val_GroupByChannel,dataDO,None,None)

    # ------- ANALOG output -----------
    taskAO = daq.Task()
    taskAO.CreateAOVoltageChan("/Dev1/ao0","",-10.0,10.0,daq.DAQmx_Val_Volts,None)
    
    ## Stop any task
    taskAO.StopTask()
    
    ## Configure timing (from DI task ported to /Dev/PFI2)
    taskAO.CfgSampClkTiming("/Dev1/PFI2",DAQ_sample_rate_Hz,daq.DAQmx_Val_Rising,daq.DAQmx_Val_FiniteSamps,samples_per_ch)
   
    ## Set where the starting trigger (from DI task ported to /Dev/PFI1)
    taskAO.CfgDigEdgeStartTrig("/Dev1/PFI1",daq.DAQmx_Val_Rising)
    
    ## Write the output waveform
    samples_per_ch_ct = ct.c_int32()
    taskAO.WriteAnalogF64(samples_per_ch, False, 10.0, daq.DAQmx_Val_GroupByScanNumber,wf,ct.byref(samples_per_ch_ct),None)

    ## ------ Start both tasks ----------
    taskDI.StartTask()
    taskAO.StartTask()    
    taskDO.StartTask()    
    taskAO.WaitUntilTaskDone(-1) # wait until signal is sent
    taskDO.WaitUntilTaskDone(-1) # wait until signal is sent
   
    ## Stop and clear both tasks
    taskDI.StopTask()
    taskAO.StopTask()
    taskDO.StopTask()
    taskDI.ClearTask()
    taskAO.ClearTask()
    taskDO.ClearTask()
    
except daq.DAQError as err:
    print("DAQmx Error %s"%err)

 

0 Kudos
Message 7 of 9
(2,201 Views)
Solution
Accepted by topic author fdjutant

To run continuously:

- set both DO and AO for continuous sampling (like the DI task) rather than finite

- you may need to read from the DI task periodically so its buffer doesn't fill and put the task into an error state

- you'll need to get rid of the code that waits for the tasks to be done and use a different method to decide when to end execution.

- by default, continuous output tasks will regenerate their buffer contents repeatedly, so you just need to write values to DO and AO one time before starting the task.

 

Note that if you start the DO and AO tasks *before* starting DI, you won't need to configure triggering for DO or AO.  The use of the change detection event as a sample clock will be sufficient on its own to establish sync.

 

 

-Kevin P

CAUTION! New LabVIEW adopters -- it's too late for me, but you *can* save yourself. The new subscription policy for LabVIEW puts NI's hand in your wallet for the rest of your working life. Are you sure you're *that* dedicated to LabVIEW? (Summary of my reasons in this post, part of a voluminous thread of mostly complaints starting here).
Message 8 of 9
(2,189 Views)

Dear Kevin P,

 

Sorry for the super late reply. Your suggestion does work well! Thank you so much. Changing the timing for DI, DO, and AO to continuous enables the long repeats. We haven't applied the periodic reading from the DI task, but this is sufficient for what we want to do. I attach the Python code below in case somebody needs it.

 

Thank you, Kevin. We really appreciate your help and insights!

 

Franky

 

import PyDAQmx as daq
import numpy as np
import ctypes as ct

# Setup DAQ
nvoltage_steps = scan_steps
# 2 time steps per frame, except for first frame plus one final frame to reset voltage
samples_per_ch = (nvoltage_steps * 2 - 1) + 1
DAQ_sample_rate_Hz = 10000
retriggerable = True
num_DI_channels = 8

# Generate values for DO
dataDO = np.zeros((samples_per_ch,num_DI_channels),dtype=np.uint8)
dataDO[::2,:] = 1
dataDO[-1, :] = 0
print(dataDO[:, 0])

# Generate voltage steps
max_volt = min_volt + scan_axis_range_volts  # 2
voltage_values = np.linspace(min_volt,max_volt,nvoltage_steps)

# Generate values for AO
wf = np.zeros(samples_per_ch)
# one voltage value for first frame
wf[0] = voltage_values[0]
# two voltage values for all other frames
wf[1:-1:2] = voltage_values[1:] # start : end : step
wf[2:-1:2] = voltage_values[1:]
# set back to right value at end
wf[-1] = voltage_values[0]
#wf[:] = 0 # for test: galvo is not moved
print(wf)

try:    
    # ----- DIGITAL input -------
    taskDI = daq.Task()
    taskDI.CreateDIChan("/Dev1/PFI0","",daq.DAQmx_Val_ChanForAllLines)
    
    ## Configure change detectin timing (from wave generator)
    taskDI.CfgInputBuffer(0)    # must be enforced for change-detection timing
    taskDI.CfgChangeDetectionTiming("/Dev1/PFI0","/Dev1/PFI0",daq.DAQmx_Val_ContSamps,0)

    ## Set where the starting trigger 
    taskDI.CfgDigEdgeStartTrig("/Dev1/PFI0",daq.DAQmx_Val_Rising)
    
    ## Export DI signal to unused PFI pins, for clock and start
    taskDI.ExportSignal(daq.DAQmx_Val_ChangeDetectionEvent, "/Dev1/PFI2")
    taskDI.ExportSignal(daq.DAQmx_Val_StartTrigger,"/Dev1/PFI1")
    
    # ----- DIGITAL output -----   
    taskDO = daq.Task()
    taskDO.CreateDOChan("/Dev1/port0/line0:7","",daq.DAQmx_Val_ChanForAllLines)

    ## Configure timing (from DI task) 
    taskDO.CfgSampClkTiming("/Dev1/PFI2",DAQ_sample_rate_Hz,daq.DAQmx_Val_Rising,daq.DAQmx_Val_ContSamps,samples_per_ch)
    
    ## Write the output waveform
    samples_per_ch_ct_digital = ct.c_int32()
    taskDO.WriteDigitalLines(samples_per_ch,False,10.0,daq.DAQmx_Val_GroupByChannel,dataDO,ct.byref(samples_per_ch_ct_digital),None)
    print("WriteDigitalLines sample per channel count = %d" % samples_per_ch_ct_digital.value)

    # ----- ANALOG output -----
    taskAO = daq.Task()
    taskAO.CreateAOVoltageChan("/Dev1/ao0","",-4.0,4.0,daq.DAQmx_Val_Volts,None)
    
    ## Configure timing (from DI task)
    taskAO.CfgSampClkTiming("/Dev1/PFI2",DAQ_sample_rate_Hz,daq.DAQmx_Val_Rising,daq.DAQmx_Val_ContSamps,samples_per_ch)
  
    ## Write the output waveform
    samples_per_ch_ct = ct.c_int32()
    taskAO.WriteAnalogF64(samples_per_ch,False,10.0,daq.DAQmx_Val_GroupByScanNumber,wf,ct.byref(samples_per_ch_ct),None)
    print("WriteAnalogF64 sample per channel count = %d" % samples_per_ch_ct.value)

    # ----- Start all tasks -----
    taskAO.StartTask()    
    taskDO.StartTask()    
    taskDI.StartTask()
    
except daq.DAQError as err:
    print("DAQmx Error %s"%err)

# stop DAQ
try:
    ## Stop and clear both tasks
    taskDI.StopTask()
    taskDO.StopTask()
    taskAO.StopTask()
    taskDI.ClearTask()
    taskAO.ClearTask()
    taskDO.ClearTask()
except daq.DAQError as err:
    print("DAQmx Error %s"%err)

 

 

0 Kudos
Message 9 of 9
(2,124 Views)