clade / PyDAQmx

Interface to National Instrument NIDAQmx driver
Other
133 stars 54 forks source link

Synchronous Input and Output #47

Open ChrissaK opened 7 years ago

ChrissaK commented 7 years ago

Hi,

Since I'm new to PyDAQmx, I'd really appreciate your help. I'm trying to do synchronous input and output using a NI USB-6212 (BNC) device, but I have several questions:

  1. Can a task support both input and output? As far as I understand, it cannot, so I have to create two separate tasks, right?

  2. I tried to write a script using the simple functions DAQmxWriteAnalogF64 and DAQmxReadAnalogF64. What I wanted to do is read back the sine wave I wrote to the output channel. After reading instructions in the NI site, I used the following functions and parameters to control the timing of the two tasks:

daq.DAQmxCfgSampClkTiming(taskHandle_output,"",fs,daq.DAQmx_Val_Falling,daq.DAQmx_Val_ContSamps,block)
daq.DAQmxCfgSampClkTiming(taskHandle_input,"ao/SampleClock",fs,daq.DAQmx_Val_Falling,daq.DAQmx_Val_ContSamps,block)

with block = 1024. However, there was a noticeable latency between the two tasks.

  1. I am wondering if I have to use callback functions (and maybe threading??) to achieve this simultaneity of input and output. But again, it's not straightforward to me how to do that. Would I have to write two different callback functions, one for the output and one for the input task?

Thanks in advance for any advice!

clade commented 7 years ago

1/ Yes, you need two task. I also advise you to use python object to create the task. 2/ You are using the same clock, but you also need to start at the same time. This can be done using a trigger (CfgDigEdgeRefTrig) 3/ You don't need to use tread. If you want simultaneity, you have to rely on the hardware for the timing. The input or output data will be store in the buffer of the board.

ChrissaK commented 7 years ago

Thanks for your immediate reply!

  1. Is there a particular reason why you advise me to use the object version? Or is it just more python-like?

  2. Thanks! But again, concerning the triggering, I don't think I understand it very well. Do I need a digital signal in one of the channels of the device to use as a trigger? And if so, how am I going to generate it? If not, can I use 'ao/StartTrigger' in the DAQmxCfgDigEdgeRefTrig function as a trigger source for the input? Do I need another physical cable or not?

  3. Great! But do I need callback functions or not? What is the main difference between callback functions and the simple read and write functions? Are the latter blocking (meaning that I can't have real simultaneity if I just use them?)

ChrissaK commented 7 years ago

I am also trying to write a continuous output script, using a callback function equivalent to that that you have in the example for the continuous input. However, it seems that it continually outputs the first 1024 samples of the target array, although I have a counter (global variable) which I increase each time in every call of the callback function. The counter is indeed increased, however it seems that something goes wrong with the data that it tries to output...

My code is the following:

import PyDAQmx as daq
#from PyDAQmx.DAQmxCallBack import *
import numpy as np
import matplotlib.pyplot as plt
import time

fs = 44100
stim_amp = 1 # Need to remain below 2V for neurostimulation
f_stim = 10 #frequenccy
duration = 3  #seconds
block = 1024

range_samples = np.arange((fs*duration//block)*block)
N = len(range_samples)
neurostim_samples = stim_amp*(np.sin(2*np.pi*range_samples*f_stim/fs)).astype(np.float64)

plt.figure
plt.plot(range_samples, neurostim_samples)

class MyList(list):
    pass

data = MyList()
id_data = daq.DAQmxCallBack.create_callbackdata_id(data)

def EveryNCallback_py(taskHandle, everyNsamplesEventType, nSamples, callbackData_ptr):
    global i
    global neurostim_samples
    global block
    print 'this is callback'
    i = i + 1
    print i
    write = daq.int32()
    daq.DAQmxWriteAnalogF64(taskHandle,block,0,0,daq.DAQmx_Val_GroupByChannel,neurostim_samples[(i-1)*block:i*block],daq.byref(write),None)
    print 'end of callback'

    return 0 # The function should return an integer

EveryNCallback = daq.DAQmxEveryNSamplesEventCallbackPtr(EveryNCallback_py)

taskHandle = daq.TaskHandle()
daq.DAQmxCreateTask("",daq.byref(taskHandle))
daq.DAQmxCreateAOVoltageChan(taskHandle,"Dev1/ao0","",-10.0,10.0,daq.DAQmx_Val_Volts,None)
daq.DAQmxCfgSampClkTiming(taskHandle,"",fs,daq.DAQmx_Val_Rising,daq.DAQmx_Val_ContSamps,block)

daq.DAQmxCfgOutputBuffer(taskHandle, block)
write = daq.int32()
daq.DAQmxRegisterEveryNSamplesEvent(taskHandle,daq.DAQmx_Val_Transferred_From_Buffer,block,0,EveryNCallback,id_data)

i = 1
daq.DAQmxWriteAnalogF64(taskHandle,block,0,0,daq.DAQmx_Val_GroupByChannel,neurostim_samples[(i-1)*block:i*block],daq.byref(write),None)

daq.DAQmxStartTask(taskHandle)
time.sleep(duration)
daq.DAQmxStopTask(taskHandle)
daq.DAQmxClearTask(taskHandle)
umeshmohan commented 7 years ago

Hi. I have been using PyDAQmx for my experiments for continuous acquisition for several years now. This is quite old code and I would probably write a better version now. But it definitely works. https://github.com/umeshmohan/ECA/blob/master/ExperimentControlandAnalysis/DAQmxAcquisition.py

ChrissaK commented 7 years ago

@umeshmohan thank you so much for your help!

ChrissaK commented 7 years ago

@umeshmohan thanks once again for your code, I have changed it a bit and it works for my case as well. I want to use it for 2 analog outputs and 2 analog inputs simultaneously. I have tried for the time being only for 1 output and 1 input.

However, I have noticed that even with this code, the acquisition process doesn't start immediately - meaning that although it writes at the first run 1024 samples (which is the block size I use), the corresponding number of samples read differs in each run, as you can see in the attached file. Is that normal?

I have also noticed that this number changes if I change the sampling rate, the block size or the waiting time inside the for loop in the main function, but I haven't found a combination that gives a steady (1024) number of samples read in each run.

DAQmxAcquisition output.txt

umeshmohan commented 7 years ago

Hi @ChrissaK. The acquisition will be happening in a synchronized manner as soon as you start it. But the transfer from device buffer to PC buffer is contingent on multiple factors and is not constant. So for instance if you start recording and send out data in 1 k sample chunks, the first time you send out a 1 k sample chunk, you wont get back any data (as seen in your log). The second time you send out 1 k sample chunk, the device would have acquired 1 k sample data to send back but the device has only managed to send 23 sample to PC. You will be able to read the rest only when you send the next command. So it is critical to keep track of the actual number of samples read (Line 133 in the code I mentioned previously). and at the end of recording, send a few more chunks of insignificant output to read in the data that has not yet been transferred. This delay between output and input is only because of buffers and not the actual delay in data it self. You can confirm this by looping analog/digital output to analog/digital input and checking that there is at max one sample difference between the two.

When I wrote this code several years ago, I could not figure out the "proper" way to deal with this and settled with "warm up" and "cool down" in recordings. i.e., I start recording with 5 s of insignificant output, continue with recording, and after the last output has been sent, keep sending another 5 s of insignificant output to ensure that the actual input is not missed. I haven't figured out a way to read the last few (~1000) samples in continuous recording.