Help with triggering on pulse width

Post general discussions on using our drivers to write your own software here
Post Reply
zgingrichCZ
Newbie
Posts: 0
Joined: Thu Sep 21, 2023 11:30 pm

Help with triggering on pulse width

Post by zgingrichCZ »

Using PicoScope 4444 (uses the 4000a library) & the python wrappers

I want to trigger when channel C has a pulse width of 0.3-0.7 ms, and I want to do that whether the pulse is inverted (so 24V -> 0V -> 24V) or noninverted (0V -> 24V -> 0V)

Everything works except that as soon as I run the program, it starts triggering continuously. I tried to follow the pinned document on advanced triggering, which led me to use the following settings:

Pulse Width Qualifier Conditions:

Code: Select all

conditions = ps.PS4000A_CONDITION(2, ps.PS4000A_TRIGGER_STATE["PS4000A_TRUE"])
Trigger Channel Properties (applied to channel 2):

Code: Select all

risingThreshold = 8190 (NOTE: THIS IS THE LOWER THRESHOLD)
risingHyst = 8400
fallingThreshold = 8190 (NOTE: THIS IS THE UPPER THRESHOLD)
fallingHyst = 8000
ps.PS4000A_THRESHOLD_MODE["PS4000A_LEVEL"]
Pulse Width Qualifier Properties:

Code: Select all

ps.PS4000A_THRESHOLD_DIRECTION["PS4000A_RISING_OR_FALLING"]
maxPW = ctypes.c_uint32(70)
minPW = ctypes.c_uint32(30)
ps.PS4000A_PULSE_WIDTH_TYPE["PW_TYPE_IN_RANGE"]

It furthermore seems as though changing the ADC threshold values affects the speed of the program. I would only expect this to occur if I modify the pulse width thresholds.

I am concerned that it relates back to this statement:
Each channel of the oscilloscope (except the EXT input) has two thresholds for each direction—for example,
PSXXXX_RISING and PSXXXXX_RISING_LOWER—so that one can be used for the pulse-width qualifier and the other
for the level trigger.
The driver will not let you use the same threshold for both triggers; so, for example, you cannot use PSXXXX_RISING
as the direction argument for both psXXXXSetTriggerConditions and psXXXXSetPulseWidthQualifier at the same
time. There is no such restriction when using window triggers
But to be perfectly honest, having read through the programming guide and written the program, I don't really understand the mechanics of what this means or if it applies here.

Does anyone see what I did wrong with my trigger settings? I'm sure it's something stupid.

zgingrichCZ
Newbie
Posts: 0
Joined: Thu Sep 21, 2023 11:30 pm

Re: Help with triggering on pulse width

Post by zgingrichCZ »

Update:

So first of all, I am now aware that what I want is actually a pulse width window trigger, because I care about the voltage level. A normal pulse width uses the threshold as the CENTER of the signal, and will trigger on a pulse deviating from that. There is supposedly some hysteresis, which you would think would allow you to do what I want, but I guess not.

So now I am trying to do it with a pulse window. It still does not work. It just sits there and triggers semi-regularly, which is the same it was doing before. I've tried using the threshold direction PS4000A_OUTSIDE, PS4000A_ENTER_OR_EXIT, but no dice. Here is the updated franken code:

Code: Select all

import ctypes
import numpy as np
from picosdk.ps4000a import ps4000a as ps
import matplotlib.pyplot as plt
from picosdk.functions import adc2mV, assert_pico_ok
from datetime import datetime
import csv

# Create chandle and status ready for use
chandle = ctypes.c_int16()
status = {}

# Open 4000 series PicoScope
# Returns handle to chandle for use in future API functions
status["openunit"] = ps.ps4000aOpenUnit(ctypes.byref(chandle), None)

try:
    assert_pico_ok(status["openunit"])
except: # PicoNotOkError:
    powerStatus = status["openunit"]

    if powerStatus == 286:
        status["changePowerSource"] = ps.ps4000aChangePowerSource(chandle, powerStatus)
    elif powerStatus == 282:
        status["changePowerSource"] = ps.ps4000aChangePowerSource(chandle, powerStatus)
    else:
        raise

    assert_pico_ok(status["changePowerSource"])


# Set up channel A
# handle = chandle
# channel = PS4000a_CHANNEL_A = 0
# enabled = 1
# coupling type = PS4000a_DC = 1
# range = 5V (diff probe) = 8
# analogOffset = 0 V
chARange = 8
status["setChA"] = ps.ps4000aSetChannel(chandle, 0, 1, 1, chARange, 0)
assert_pico_ok(status["setChA"])

# Set up channel B
# handle = chandle
# channel = PS4000a_CHANNEL_B = 1
# enabled = 1
# coupling type = PS4000a_DC = 1
# range = 5V (diff probe) = 8
# analogOffset = 0 V
chBRange = 8
status["setChB"] = ps.ps4000aSetChannel(chandle, 1, 1, 1, chBRange, 0)
assert_pico_ok(status["setChB"])

# Set up channel C
# handle = chandle
# channel = PS4000a_CHANNEL_C = 2
# enabled = 1
# coupling type = PS4000a_DC = 1
# range = 50V = 11
# analogOffset = 0 V
chCRange = 11
status["setChC"] = ps.ps4000aSetChannel(chandle, 2, 0, 1, chCRange, 0)
assert_pico_ok(status["setChC"])

# Set up channel D
# handle = chandle
# channel = PS4000a_CHANNEL_D = 3
# enabled = 0
# coupling type = PS4000a_DC = 1
# range = PS4000a_2V = 7
# analogOffset = 0 V
chDRange = 7
status["setChD"] = ps.ps4000aSetChannel(chandle, 3, 0, 1, chDRange, 0)
assert_pico_ok(status["setChD"])

# Set up window pulse width trigger on C
conditions = ps.PS4000A_CONDITION(2, ps.PS4000A_TRIGGER_STATE["PS4000A_TRUE"])

status["setPWQConditions"] = ps.ps4000aSetPulseWidthQualifierConditions(chandle,
																		ctypes.byref(conditions),
																		ctypes.c_int16(1),
																		ps.PS4000A_CONDITIONS_INFO["PS4000A_ADD"])
assert_pico_ok(status["setPWQConditions"])

# threshold is 32767/4, or ~12.5V
upperThreshold = 13107  # Used for falling
upperHyst = 11000
lowerThreshold = 1310  # Used for rising
lowerHyst = 2000
properties = ps.PS4000A_TRIGGER_CHANNEL_PROPERTIES(upperThreshold, upperHyst, lowerThreshold, lowerHyst, 2,
                                                   ps.PS4000A_THRESHOLD_MODE["PS4000A_WINDOW"])
status["setTriggerProperties"] = ps.ps4000aSetTriggerChannelProperties(chandle,
                                                                       ctypes.byref(properties),
                                                                       ctypes.c_int16(1), ctypes.c_int16(0), ctypes.c_int32(0))
assert_pico_ok(status["setTriggerProperties"])

# microseconds approx. = value/100
maxPW = ctypes.c_uint32(70000)
minPW = ctypes.c_uint32(30000)
status["setPWQProperties"] = ps.ps4000aSetPulseWidthQualifierProperties(chandle,
																		ps.PS4000A_THRESHOLD_DIRECTION["PS4000A_OUTSIDE"],
																		minPW, maxPW,
																		ps.PS4000A_PULSE_WIDTH_TYPE["PW_TYPE_IN_RANGE"])
assert_pico_ok(status["setPWQProperties"])

# Set number of pre and post trigger samples to be collected
# With 3 active channels, available capture time is <0.85 seconds total
# preTriggerSamples + postTriggerSamples < 85333333
# However this is very slow so only ~75 ms are captured here
# microseconds approx. = value/100
preTriggerSamples = 5000000
postTriggerSamples = 2500000
maxSamples = preTriggerSamples + postTriggerSamples
timeIntervalns = ctypes.c_float(10)  # true for timebase = 2

numCaptures = 0
partCaptures = 0

try:
    while True:
        # Run block capture
        # handle = chandle
        # number of pre-trigger samples = preTriggerSamples
        # number of post-trigger samples = PostTriggerSamples
        # timebase = 2 = 100 Ms/s (see Programmer's guide for mre information on timebases)
        # time indisposed ms = None (not needed in the example)
        # segment index = 0
        # lpReady = None (using ps4000aIsReady rather than ps4000aBlockReady)
        # pParameter = None
        status["runBlock"] = ps.ps4000aRunBlock(chandle, preTriggerSamples, postTriggerSamples, 2, None, 0, None, None)
        assert_pico_ok(status["runBlock"])

        # Check for data collection to finish using ps4000aIsReady
        ready = ctypes.c_int16(0)
        check = ctypes.c_int16(0)
        while ready.value == check.value:
            status["isReady"] = ps.ps4000aIsReady(chandle, ctypes.byref(ready))

        print("Ready.")

        # Create buffers ready for assigning pointers for data collection
        bufferAMax = (ctypes.c_int16 * maxSamples)()
        bufferAMin = (ctypes.c_int16 * maxSamples)() # used for downsampling which isn't in the scope of this example
        bufferBMax = (ctypes.c_int16 * maxSamples)()
        bufferBMin = (ctypes.c_int16 * maxSamples)() # used for downsampling which isn't in the scope of this example
        bufferCMax = (ctypes.c_int16 * maxSamples)()
        bufferCMin = (ctypes.c_int16 * maxSamples)() # used for downsampling which isn't in the scope of this example

        # Set data buffer location for data collection from channel A
        # handle = chandle
        # source = PS4000a_CHANNEL_A = 0
        # pointer to buffer max = ctypes.byref(bufferAMax)
        # pointer to buffer min = ctypes.byref(bufferAMin)
        # buffer length = maxSamples
        # segementIndex = 0
        # mode = PS4000A_RATIO_MODE_NONE = 0
        status["setDataBuffersA"] = ps.ps4000aSetDataBuffers(chandle, 0, ctypes.byref(bufferAMax), ctypes.byref(bufferAMin), maxSamples, 0 , 0)
        assert_pico_ok(status["setDataBuffersA"])

        # Set data buffer location for data collection from channel B
        # handle = chandle
        # source = PS4000a_CHANNEL_B = 1
        # pointer to buffer max = ctypes.byref(bufferBMax)
        # pointer to buffer min = ctypes.byref(bufferBMin)
        # buffer length = maxSamples
        # segementIndex = 0
        # mode = PS4000A_RATIO_MODE_NONE = 0
        status["setDataBuffersB"] = ps.ps4000aSetDataBuffers(chandle, 1, ctypes.byref(bufferBMax), ctypes.byref(bufferBMin), maxSamples, 0 , 0)
        assert_pico_ok(status["setDataBuffersB"])

        # Set data buffer location for data collection from channel C
        # handle = chandle
        # source = PS4000a_CHANNEL_C = 2
        # pointer to buffer max = ctypes.byref(bufferCMax)
        # pointer to buffer min = ctypes.byref(bufferCMin)
        # buffer length = maxSamples
        # segementIndex = 0
        # mode = PS4000A_RATIO_MODE_NONE = 0
        status["setDataBuffersC"] = ps.ps4000aSetDataBuffers(chandle, 2, ctypes.byref(bufferCMax), ctypes.byref(bufferCMin), maxSamples, 0 , 0)
        assert_pico_ok(status["setDataBuffersC"])

        # create overflow loaction
        overflow = ctypes.c_int16()
        # create converted type maxSamples
        cmaxSamples = ctypes.c_int32(maxSamples)

        # Retrieve data from scope to buffers assigned above
        # handle = chandle
        # start index = 0
        # pointer to number of samples = ctypes.byref(cmaxSamples)
        # downsample ratio = 0
        # downsample ratio mode = PS4000a_RATIO_MODE_NONE
        # pointer to overflow = ctypes.byref(overflow))
        status["getValues"] = ps.ps4000aGetValues(chandle, 0, ctypes.byref(cmaxSamples), 0, 0, 0, ctypes.byref(overflow))
        assert_pico_ok(status["getValues"])


        # find maximum ADC count value
        # handle = chandle
        # pointer to value = ctypes.byref(maxADC)
        maxADC = ctypes.c_int16(32767)

        # convert ADC counts data to mV
        adc2mVChAMax = adc2mV(bufferAMax, chARange, maxADC)
        adc2mVChBMax = adc2mV(bufferBMax, chBRange, maxADC)
        adc2mVChCMax = adc2mV(bufferCMax, chCRange, maxADC)

        # Create time data
        time = np.linspace(0, (cmaxSamples.value - 1) * timeIntervalns.value, cmaxSamples.value)
        time = time/1000000

        partCaptures += 1

        print("Writing to file...")
        # save data to text file with unique filename
        current_datetime = datetime.now().strftime("%Y-%m-%d %H-%M-%S")
        str_current_datetime = str(current_datetime)
        file_name = str_current_datetime + ".csv"

        allData = zip(time, adc2mVChAMax, adc2mVChBMax, adc2mVChCMax)

        with open(file_name, 'w') as f:
            writer = csv.writer(f, delimiter=";", lineterminator="\n")
            writer.writerow(["T (ms)", "A", "B", "Trigger"])
            writer.writerows(allData)

        print("File created : "+f.name)
        f.close()
        numCaptures += 1
        partCaptures -= 1

        print("Plotting...")
        # plot data from all channels
        plt.figure(figsize=(25, 10))
        ax = plt.gca()
        ax.set_ylim([-5000, 5000])
        plt.plot(time, adc2mVChAMax[:], label="A")
        plt.plot(time, adc2mVChBMax[:], label="B")
        plt.plot(time, adc2mVChCMax[:], label="Trig")
        plt.legend(loc="upper left")
        plt.xlabel('Time (ms)')
        plt.ylabel('Voltage (mV)')
        file_name = str_current_datetime + ".png"
        plt.savefig(file_name)
        plt.close()
        print("Capture Plot "+str(numCaptures)+" Saved: "+file_name)

except KeyboardInterrupt:
    pass

# Stop the scope
# handle = chandle
status["stop"] = ps.ps4000aStop(chandle)
assert_pico_ok(status["stop"])

# Close unitDisconnect the scope
# handle = chandle
status["close"] = ps.ps4000aCloseUnit(chandle)
assert_pico_ok(status["close"])

# display status returns
print("Captures This Session:")
print(numCaptures)
print("Partial Captures:")
print(partCaptures)
print(status)

Martyn
Site Admin
Site Admin
Posts: 4501
Joined: Fri Jun 10, 2011 8:15 am
Location: St. Neots

Re: Help with triggering on pulse width

Post by Martyn »

Can you post a sketch of the signal and how you want it to trigger, and when it shouldn't.
Martyn
Technical Support Manager

zgingrichCZ
Newbie
Posts: 0
Joined: Thu Sep 21, 2023 11:30 pm

Re: Help with triggering on pulse width

Post by zgingrichCZ »

Martyn, I think this explanation will suffice as it's actually pretty simple. But if not let me know.

The trigger signal will nominally be sitting at either 0V or 24V. When I want a trigger, the signal will invert for 0.5 ms. So if it's already low, it will pulse to 24V. Or if it's already high, then it will pulse to 0V. In either case, as long as that pulse is between 0.3 ms and 0.7 ms long (regardless of direction), I would like a trigger.

zgingrichCZ
Newbie
Posts: 0
Joined: Thu Sep 21, 2023 11:30 pm

Re: Help with triggering on pulse width

Post by zgingrichCZ »

Go HIGH for 50 ms
--> Go LOW for 0.5 ms (this is the pulse)
--> Go HIGH for 50 ms
--> Return to whatever it was originally

So, still having problems. I am attaching my code in case anyone can help me with them.
-->
An Update. First the good:

I found out my triggers were not being set in the first place. I resolved that by:

1. I was not calling TriggerChannelDirections. I was told I didn't need to, but I think that was back when I was doing a pure pwq instead of a window pwq. With a window pwq, you definitely need to call the TriggerChannelDirections & Conditions.

2. I had the channel I wanted to trigger on set to disabled by accident. Oops.

3. Not sure if this mattered, but I also rearranged it so that the properties & directions were called BEFORE the condition statements.

So now the thing triggers. But I'm still having 2 problems:

1. It will only trigger once. After it's done saving & capturing that data, it runs back through the block statement, becomes "ready" (as reported by the polling function, not using the ISR), and then.......will never trigger again. But if I stop the program and rerun it, I will get another trigger (again only 1)

2. The trigger signal I have can nominally be sitting at 22V (high) or 1.6V (low). To produce a trigger, regardless of what the nominal value is, the signal does the below steps, in this order. When setting this trigger up in the PicoScope 7 software, it works as expected no matter what the nominal signal was. But the API version, with what I think are the same settings (obviously not), will only trigger if the signal was nominally low. This makes no sense to me.
--> Go HIGH for 50 ms
--> Go LOW for 0.5 ms (this is the pulse)
--> Go HIGH for 50 ms
--> Return to whatever it was originally

So, still having problems. I am attaching my code in case anyone can help me with them.
Attachments
main.py
(10.47 KiB) Downloaded 106 times