Jump to content
  • 0

Digital Inputs control in AD2/3 via python(pydwf).


Kirsan

Question

Hi @reddish and @attila.

I try to use digital inputs(logic analyzer[LA]) + pydwf + AD2/3.

I'm relatively new to using Python.

My main task is use patterns and LA at the same time to program and read some IC with unique protocol.

So at first I tried to use LA and python.

I send signals to two inputs (D0, D1):

.thumb.png.73b9226ad3c57ba66c55fe99c63772e6.png

The next step, I used standard  py example with little changes to see how it should works:

from ctypes import *
from dwfconstants import *
import math
import time
import matplotlib.pyplot as plt
import sys
import numpy

if sys.platform.startswith("win"):
    dwf = cdll.dwf
elif sys.platform.startswith("darwin"):
    dwf = cdll.LoadLibrary("/Library/Frameworks/dwf.framework/dwf")
else:
    dwf = cdll.LoadLibrary("libdwf.so")

hdwf = c_int()
sts = c_byte()

version = create_string_buffer(16)
dwf.FDwfGetVersion(version)
print("DWF Version: "+str(version.value))

#open device
print("Opening first device")
dwf.FDwfDeviceOpen(c_int(-1), byref(hdwf))

if hdwf.value == 0:
    print("failed to open device")
    szerr = create_string_buffer(512)
    dwf.FDwfGetLastErrorMsg(szerr)
    print(str(szerr.value))
    quit()

# sample rate = system frequency / divider
hzDI = c_double()
dwf.FDwfDigitalInInternalClockInfo(hdwf, byref(hzDI))
dwf.FDwfDigitalInDividerSet(hdwf, c_int(int(hzDI.value/100e6))) # 100MHz
# 16bit per sample format
dwf.FDwfDigitalInSampleFormatSet(hdwf, c_int(16))
# set number of sample to acquire
cSamples = 64
rgwSamples = (c_uint16*cSamples)()
dwf.FDwfDigitalInBufferSizeSet(hdwf, c_int(cSamples))

# begin acquisition
dwf.FDwfDigitalInConfigure(hdwf, c_int(0), c_int(1))

print("Waiting for acquisition...")
while True:
    dwf.FDwfDigitalInStatus(hdwf, c_int(1), byref(sts))
    if sts.value == stsDone.value :
        break
    time.sleep(1)
print("   done")

# get samples, byte size
dwf.FDwfDigitalInStatusData(hdwf, rgwSamples, 2*cSamples)
dwf.FDwfDeviceCloseAll()
print(numpy.fromiter(rgwSamples, dtype = numpy.uint16))
print(len(numpy.fromiter(rgwSamples, dtype = numpy.uint16)))

And result is:

DWF Version: b'3.20.34'
Opening first device
Waiting for acquisition...
   done
[3 3 3 3 3 3 3 3 3 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1
 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 3 2 2 2 2]
64

It looks normal and correct.

 

Next step, I tried to realized the same via pydwf with some little changes:

import time
import pydwf

dwf = pydwf.DwfLibrary()

device = pydwf.DeviceControl(dwf)
pydwf.DeviceEnumeration(dwf).enumerateDevices()
device.openEx("index:1,config:0")

AD3 = pydwf.DwfDevice(dwf,1)
AD3.paramSet(pydwf.DwfDeviceParameter.Frequency, 100_000_000)

digitalIn = AD3.digitalIn
digitalIn.dividerSet(1)
digitalIn.sampleFormatSet(16)
digitalIn.bufferSizeSet(64)
digitalIn.configure(0,1)
print("Waiting for acquisition...")
while True:
    status = digitalIn.status(True).name
    if status == "Done":
        break
    time.sleep(1)
print("   done")


data = digitalIn.statusData(64)
print(data)
print(len(data))

And result is:

Waiting for acquisition...
   done
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1
 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0]
64

It looks strange. Looks like signal missed some data).

 

In general, both scripts do the same, but results are different.

 

I also noted that if I change in the next line:

dwf.FDwfDigitalInStatusData(hdwf, rgwSamples, 2*cSamples)

coefficient from 2 to 1 or 4(for example):

dwf.FDwfDigitalInStatusData(hdwf, rgwSamples, 4*cSamples)

result will be the same, and array length will be the same(64).

If I understand correctly, then the data from the AD3 buffer with a depth of 64 bits was read into a memory area with a depth of 64 bits and therefore the result cannot be greater, which is logical.

But, if I changed the next line:

data = digitalIn.statusData(64)

to

data = digitalIn.statusData(2*64)

array length will be the 128.

But, if I understated correctly, this parameter in function should do the same as in original py example:

    def statusData(self, count_bytes: int) -> np.ndarray:
        """Retrieve the acquired data samples from the |DigitalIn| instrument.

        Todo:
            Figure out the data format.

        Raises:
            DwfLibraryError: An error occurred while executing the operation.
        """
        samples = np.empty(count_bytes, dtype='B')
        result = self.lib.FDwfDigitalInStatusData(
            self.hdwf,
            samples.ctypes.data_as(typespec_ctypes.c_unsigned_char_ptr),
            count_bytes)
        if result != RESULT_SUCCESS:
            raise self.dwf.exception()
        return samples

Could you please help me?)

Edited by Kirsan
Link to comment
Share on other sites

21 answers to this question

Recommended Posts

  • 0

Hi @Kirsan

The DigitalIn instrument code in pydwf is not routinely tested by me (as evidenced also by the lack of examples), and it appears you stumbled upon an issue.

I have implemented a fix for the statusData() and statusData2() methods of the DigitalIn device. Note that the 'count' parameter to be passed to those functions must be expressed in terms of samples rather than bytes (the latter is what the C API expects).

Please upgrade your pydwf to version 1.1.18 and let me know if you can get your example to work using pydwf now.


Proper testing of the DigitalIn instrument in pydwf, as well as providing examples, is on my TODO list. Thanks for spotting this problem, and if necessary I'll iterate with you until this works.

Link to comment
Share on other sites

  • 0

Hi @reddish

Thank you for help, now this script works fine.

I'm willing to help with some testing as I'm still figuring this out.

 

Also, how should I set "trigger_source" in this function?

    def triggerSourceSet(self, trigger_source: DwfTriggerSource) -> None:
        """Set |DigitalIn| instrument trigger source.

        Parameters:
            trigger_source (DwfTriggerSource): The trigger source to be configured.

        Raises:
            DwfLibraryError: An error occurred while executing the operation.
        """
        result = self.lib.FDwfDigitalInTriggerSourceSet(self.hdwf, trigger_source.value)
        if result != RESULT_SUCCESS:
            raise self.dwf.exception()

I mean, do I need to connect enum _types in script file, like:

import pydwf.core.auxiliary.enum_types

?

Link to comment
Share on other sites

  • 0

@reddishHi)

I would like to clarify a little more about the next moment.

digitalIn.bufferSizeSet(64)

If I understand correctly, this setting is responsible for the buffer depth. And in the WaveForms it is:

1070358567_.png.67694d9fd7ccb1cc0541321e351fd20a.png

So, if I set 64 it means that samples buffer depth --> 64 samples.

And then if read data:

data = digitalIn.statusData(64)

result will be:

Waiting for acquisition...
   done
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1]
64

So, here length of array is 64. This is the maximum. And looks good.

 

But, if I try read data(buffer depth the same, 64):

data = digitalIn.statusData(2*64)

result will be:

Waiting for acquisition...
   done
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1
 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1]
128

Here the length of array is 2*64=128. It’s as if we’re reading them from the memory area just in a circle.

 

In standard py example:

dwf.FDwfDigitalInStatusData(hdwf, rgwSamples, 2*cSamples)

It doesn't matter if the coefficient 2 changes to any other number. The array length will be the same as "buffer size":

# set number of sample to acquire
cSamples = 64
rgwSamples = (c_uint16*cSamples)()
dwf.FDwfDigitalInBufferSizeSet(hdwf, c_int(cSamples))

 

 

Link to comment
Share on other sites

  • 0

Hi @Kirsan

There is a difference between how the underlying C library and pydwf express the "count" parameter to the  FDwfDigitalInStatusData / DigitalIn.statusData functions.

In the FDwfDigitalInStatusData function, the "count" parameter is given as a number of bytes. In the DigitalIn.statusData() method, it is expressed as a number of samples. So you need to omit the multiplication by two (which is the number of bytes per sample) when calling DigitalIn.statusData(). Just put in the number of samples.

This is a difference between the C and pydwf API, and I think it will be useful if I provide a notice to users about it in the documentation. I will add such a notice in the next version.

As to the different behavior you're seeing ...

Using the pydwf API:

 

data = digitalIn.statusData(2*64)

Unlike the lower-level FDwfDigitalInStatusData function, the pydwf function will allocate its own numpy array of the correct size. You're telling pydwf to create an array for 128 samples here, and to fetch them. That allocates an array and fills it with 128 sample values, but only the first 64 values will be valid, the rest is nonsense.

Now, using the lower-level FDwfDigitalInStatusData from the C API:

dwf.FDwfDigitalInStatusData(hdwf, rgwSamples, 2*cSamples)

In the low-level, ctypes-based API, the "rgwSamples" array has been previously allocated. It has byte size (2*cSamples), so the call above is correct. Change the "2" to any other value than 2 is simply incorrect. If you change it to any value greater than 2 you will invoke undefined behavior by blindly copying data into unreserved space into the Python heap. This will probably crash your Python process.

The moral of the story: it is vital to use correct values for parameters; neither the low-level C API nor pydwf are guaranteed to be safe against bad parameter values. It is quite possible to crash your Python program by providing bad values using either API.

Link to comment
Share on other sites

  • 0

Hi @reddish )

I have question about the next function:

 def triggerSet(self, level_low: int, level_high: int, edge_rise: int, edge_fall: int) -> None:
        """Set |DigitalIn| trigger detector conditions.

        The *level_low* and *level_high* settings effectively mask trigger detection events to clock cycles where
        the selected pins are low or high, respectively.

        The *edge_rise* and *edge_fall* indicate channels where the specific type of transition on any of the
        included channels will lead to a trigger event, ar least if the level conditions specified by the *level_low*
        and *level_high* settings are satisfied.

        Parameters:
            level_low (int): The channels that are required to be low for a trigger event to occur (bitfield).
            level_high (int): The channels that are required to be high for a trigger event to occur (bitfield).
            edge_rise (int): The channels where a rising edge will cause a trigger event (bitfield).
            edge_fall (int): The channels where a falling edge will cause a trigger event (bitfield).

        Raises:
            DwfLibraryError: An error occurred while executing the operation.
        """
        result = self.lib.FDwfDigitalInTriggerSet(
            self.hdwf,
            level_low,
            level_high,
            edge_rise,
            edge_fall)
        if result != RESULT_SUCCESS:
            raise self.dwf.exception()

As I understand this functions is responsible for this settings:

1756829186_.png.8e7ea11c8b5a0c34f8f4196d85cb472a.png

Is the following lines correct?

For example, if I need to set on DIO0 "Rise" edge:

triggerSet(level_low = 0, level_high = 0, edge_rise = 1, edge_fall = 0)

To set "Rise" edge on "DIO0" and "DIO1":

triggerSet(level_low = 0, level_high = 0, edge_rise = 3, edge_fall = 0)

To set "Ignore" on "DIO0" and "Rise" edge on "DIO1":

triggerSet(level_low = 0, level_high = 0, edge_rise = 2, edge_fall = 0)

 

And a little clarification about the next sentence:

Quote

The *edge_rise* and *edge_fall* indicate channels where the specific type of transition on any of the included channels will lead to a trigger event, ar least if the level conditions specified by the *level_low* and *level_high* settings are satisfied.

Am I correct understand that it meas that, "level_low" and "level_high" trigger settings have higher priority then "edge_rise" and "edge_fall"?

For example:

If I set on DIO0 "Rise" edge and also set "level_high":

triggerSet(level_low = 0, level_high = 1, edge_rise = 1, edge_fall = 0)

So it means that trigger signal will be something like on next picture:

wavedrom.png.520932b759a726b1c907c7d5d28db722.png

 

I understand that such a case generally does not make sense. It's worth using just one thing: "edge_rise"/"edge_fall" or "level_high"/"level_low" not both. However, this question is more for a general understanding of how this function works.

Link to comment
Share on other sites

  • 0

Hi @attila, @reddish

Another question)

It is about counter.

65485871_.png.4fde753b829a48b9cd3bff50954c20a6.png

Am I correct understand that "Counter" instrument work with base frequency which for AD3 is from 50MHz to 125MHz?

 

And I try to use it and found some problem.

I send ~2MHz signal on DIO0.

So, I used standard  py example with little changes to see how it should works:

"""
   DWF Python Example
   Author:  Digilent, Inc.
   Revision:  2022-03-22

   Requires:                       
       Python 2.7, 3
"""

from ctypes import *
from dwfconstants import *
import time
import sys

if sys.platform.startswith("win"):
    dwf = cdll.dwf
elif sys.platform.startswith("darwin"):
    dwf = cdll.LoadLibrary("/Library/Frameworks/dwf.framework/dwf")
else:
    dwf = cdll.LoadLibrary("libdwf.so")


version = create_string_buffer(16)
dwf.FDwfGetVersion(version)
print("DWF Version: "+str(version.value))

#open device
"Opening first device..."
hdwf = c_int()
if dwf.FDwfDeviceOpen(c_int(-1), byref(hdwf)) != 1 or hdwf.value == hdwfNone.value:
    szerr = create_string_buffer(512)
    dwf.FDwfGetLastErrorMsg(szerr)
    print(szerr.value)
    print("failed to open device")
    quit()

# any internal or external trigger source can be used
if True:
    dwf.FDwfDigitalInTriggerSourceSet(hdwf, trigsrcDetectorDigitalIn)
    dwf.FDwfDigitalInTriggerSet(hdwf, c_int(0), c_int(0), c_int(1<<0), c_int(0))

timeout = 1.0 # frequency measurement refresh interval, 0 just count
dwf.FDwfDigitalInCounterSet(hdwf, c_double(timeout))

dwf.FDwfDigitalInConfigure(hdwf, c_int(1), c_int(1))

print("Press Ctrl+C to stop")
try:
    count2 = 0
    tick2 = 0
    countSum = 0
    while True:
        if timeout == 0: time.sleep(1) # wait 1 second
        else: time.sleep(timeout/2) # wait less then the specified timeout to make sure each measurement is captured

        if dwf.FDwfDigitalInStatus(hdwf, c_int(0), None) != 1:
            szerr = create_string_buffer(512)
            dwf.FDwfGetLastErrorMsg(szerr)
            print(szerr.value)
            quit()
            
        count = c_double()
        freq = c_double()
        tick = c_int()
        dwf.FDwfDigitalInCounterStatus(hdwf, byref(count), byref(freq), byref(tick))

        if timeout == 0.0: # just count
            if count.value < count2: # counter rollover
                countSum += countMax.value+1 
            print("Count: "+str(countSum+count.value))
        else: # frequency measurement
            if tick.value != tick2: # new measurement 
                countSum += count2 # sum earlier counts
                print("Count: "+str(countSum+count.value)+" Freq: "+str(freq.value)+"Hz")
        count2 = count.value
        tick2 = tick.value
        
except KeyboardInterrupt:
    pass


dwf.FDwfDeviceCloseAll()

The result is:

DWF Version: b'3.20.34'
Press Ctrl+C to stop
Count: 2071589.0 Freq: 2071589.1600899296Hz
Count: 4143142.0 Freq: 2071553.1393542266Hz
Count: 6214737.0 Freq: 2071595.38796891Hz

It looks good.

 

Then I tried to realized the same via pydwf with some little changes:

import ctypes
import time
import numpy
import pydwf.core.auxiliary.enum_types
from pydwf import *

dwf = pydwf.DwfLibrary()

device = pydwf.DeviceControl(dwf)
pydwf.DeviceEnumeration(dwf).enumerateDevices()
device.openEx("index:1,config:0")

AD3 = pydwf.DwfDevice(dwf,1)
AD3.paramSet(pydwf.DwfDeviceParameter.Frequency, 100_000_000)

digitalIn = AD3.digitalIn
digitalIn.triggerSourceSet(DwfTriggerSource.DigitalIn) #set trigger source --> Digital Input
digitalIn.triggerSet(0,0,1,0) #set edge_rise trigger on DIO0
digitalIn.counterSet(1) #set 1s timeout

print(digitalIn.counterGet()) #get timeout setting
print(digitalIn.counterInfo()) #get max Count and max timeout from device

digitalIn.configure(1,1) #Load settings to Digital Input instrument and start it
for i in range(3):
       time.sleep(0.5) #Wait + 0.5s for get results
       print(digitalIn.counterStatus()) #get result

The result is:

1.0
(4294967295, 21.47483647)
(0.0, 0.0, 0)
(0.0, 0.0, 0)
(0.0, 0.0, 0)

No data here.(

Perhaps I missed something in the configuration of the "Counter" and the "Digital Inputs", but it seems that I repeated all the same steps as in the standard example.

Link to comment
Share on other sites

  • 0

Hi @Kirsan,

It was quite a little puzzle but I think I have it. There are three issues.

First, in your digitalIn.counterStatus() loop, you need to call digitalIn.status(False) beforehand. This queries the DigitalIn status from the AD3, including the counter status.

Second, when you set up the trigger source, you need to specify the DwfTriggerSource.DetectorDigitalIn source rather than the DwfTriggerSource.DigitalInSource. The fact that both of them exist is super confusing, and the documentation about them is not very clear. Anyway, when you look at the low-level SDK integers being passed: the Digilent example passes value 5, which corresponds to DwfTriggerSource.DetectorDigitalIn.

Lastly, there appears to be an error in the counter functionality; the reported frequency (as returned by the FDwfDigitalInCounterStatus function) appears to be too low -- off by a factor of two, to be precise. @attila could you verify this with an external scope and signal source? If this is indeed a bug, it may be useful to do the same check for the AnalogIn counter functionality as well.

 

EDIT for @attila : What I appear to be seeing is that the "tick" value changes every (0.5 * duration), rather than the expected once per duration, where the duration is the value specified using FDwfDigitalInCounterSet(). This may explain the factor two.

Edited by reddish
Link to comment
Share on other sites

  • 0

Hi @reddish

Thanks for the answer.)

I fixed the code according to your recommendations:

digitalIn.triggerSourceSet(DwfTriggerSource.DetectorDigitalIn) #set trigger source --> Digital Input
digitalIn.triggerSet(0,0,1,0) #set edge_rise trigger on DIO0
digitalIn.counterSet(1) #set 1s timeout

# print(digitalIn.status(True))
print(digitalIn.counterGet()) #get timeout setting
print(digitalIn.counterInfo()) #get max Count and max timeout from device

digitalIn.configure(1,1) #Load settings to Digital Input instrument and start it

for i in range(10):
       digitalIn.status(False)
       time.sleep(0.5) #Wait + 0.5s for get results
       print(digitalIn.counterStatus()) #get result

Now result is:

1.0
(4294967295, 17.179869176)
(0.0, 0.0, 0)
(0.0, 0.0, 0)
(2070649.0, 2070648.5797816024, 1)
(2070649.0, 2070648.5797816024, 1)
(2070618.0, 2070618.358325643, 0)
(2070618.0, 2070618.358325643, 0)
(2070570.0, 2070569.8613570624, 1)
(2070570.0, 2070569.8613570624, 1)
(2070544.0, 2070544.391405831, 0)
(2070544.0, 2070544.391405831, 0)

I did not find DwfTriggerSource.DigitalInSource but I think that you mean DwfTriggerSource.DetectorDigitalIn.

And it values is 3. Also in standard py example. Now I see it.

DwfTriggerSource.DetectorDigitalIn has value 5.

Also I noticed that no matter  digitalIn.status(False) or digitalIn.status(True), result will be the same.

Link to comment
Share on other sites

  • 0
48 minutes ago, Kirsan said:

Also I noticed that no matter  digitalIn.status(False) or digitalIn.status(True), result will be the same.

Indeed. The parameter determines if the device will be asked to dump bulk data in addition to status data. For the counter status, the bulk data transfer is not needed.

Link to comment
Share on other sites

  • 0

Hi @reddish @Kirsan

The DetectorDigitalIn processes the digital signals. It can be configured for simple edge trigger, pulse length, deserialize and pattern match...
The DetectorAnalogIn processes the analog signals: edge, pulse or transition length, threshold, hysteresis...
These are configured by digital/analog-in functions but the output of it can be used by any instrument or driven out through trigger IO.

image.png

 

The counter seems to be working correctly.

image.png

image.png

Link to comment
Share on other sites

  • 0

Hi @attila

> The counter seems to be working correctly.

I re-ran my program (after a computer and AD3 reset) and indeed it functioned as expected; I apply 1 MHz, and 1 MHz is reported.

Then, quitting and re-running the same program, it reports half the frequency value (and the tick alternates by a factor 2 too fast). On the scope I still see a nice 1 MHz.

Power cycling the AD3: same behavior. First run, 1 MHz is reported as expected. I quit the program and re-run it, and after that 500 kHz is reported until the next power cycle.

Note that my program includes resets for both the digital-in and digital-out instruments.

So something fishy is going on.

I attached my pydwf-based program. If you insist I can port it to the low-level ctypes interface but I'd rather not.

Cheers, Sidney

EDIT: This is with version 3.20.1 on Windows.
 

digital_counter_test.py

Edited by reddish
Link to comment
Share on other sites

  • 0

 

I can reproduce it in Waveforms. Here's 1 MHz being generated on DIO-0 by the pattern generator, and 500 kHz being reported on DIO0 by the logic analyzer.

Cheers, Sidney

 

 

issue.png

Link to comment
Share on other sites

  • 0

Hi @attila

As I see the same thing in the scripted version, I assume that the issue is at the libary level, not at the application level. I hope you will be able to find and fix it.

Link to comment
Share on other sites

  • 0

Hi @reddish

If you have the "On Close" option Continue, change it to the (default) Stop or Shutdown.
With Continue the device continues to run (generate outputs) after close/app exit and on Select/open it does not reconfigure anything to not to affect generator outputs.

image.png

 

If you set the frequency in one app to 50MHz, close, then connect to it with another app the frequency in this one is not applied, but kept one set by the earlier app.
Even so I'm not able to reproduce to show different frequencies (in Logic and status-bar) as you had.

image.png

 

To prevent such situation, the latest version makes sure to apply the frequency.

 

Link to comment
Share on other sites

  • 0

Hi @attila

> If you set the frequency in one app to 50MHz, close, then connect to it with another app the frequency in this one is not applied, but kept one set by the earlier app.

I did not set anything in terms of frequencies. I also reset both the digitalin and digitalout instruments in my script; I'd assume no effect should persist beyond those.


> Even so I'm not able to reproduce to show different frequencies (in Logic and status-bar) as you had.

Did you try my script (digital_counter_test.py), and run it more than once?

I know you're not an avid pydwf user, but a "pip install pydwf" then running the script will show the problem.

I will try to see if it still happens with the 3.21.10 version.

EDIT: the bug does not occur with DWF library version 3.21.10.

 

Cheers, Sidney

Edited by reddish
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...