pico python interface (partial, ps2000)

Post general discussions on using our drivers to write your own software here
Post Reply
dolhop
Newbie
Posts: 1
Joined: Tue May 12, 2009 3:19 pm

pico python interface (partial, ps2000)

Post by dolhop »

all, I have implemented a partial interface to the PS2000 via python. it shouldn't be difficult for anyone to implement the missing functionality or add support for other pico models. currently it only supports windows, but linux support should be as simple as changing the library name/location.

enjoy

Code: Select all

#!/usr/bin/env python
'''picoscope ps2000 library interface'''

import sys
import time
from functools import partial
from ctypes import *  # shouldn't import *, but there are so many items to use...

LIBNAME = 'ps2000.dll'

# channel range values/codes
RANGE_20MV  = 1  # 20 mV
RANGE_50MV  = 2  # 50 mV
RANGE_100MV = 3  # 100 mV
RANGE_200MV = 4  # 200 mV
RANGE_500MV = 5  # 500 mV
RANGE_1V    = 6  # 1 V
RANGE_2V    = 7  # 2 V
RANGE_5V    = 8  # 5 V
RANGE_10V   = 9  # 10 V
RANGE_20V   = 10 # 20 V

# probe tip lights
LIGHT_OFF = 0
LIGHT_ON = 1
LIGHT_STATES = [LIGHT_ON, LIGHT_OFF]

# LED states
LED_RED = 1
LED_GREEN = 2
LED_OFF = 3
LED_STATES = [LED_RED, LED_GREEN, LED_OFF]

# channels
CHANNEL_A = 0
CHANNEL_B = 1
CHANNEL_NONE = 5
CHANNELS = [CHANNEL_A, CHANNEL_B, CHANNEL_NONE]

# coupling
COUPLING_DC = 1
COUPLING_AC = 0
COUPLINGS = [COUPLING_DC, COUPLING_AC]


# trigger direction
DIR_FALLING = 1
DIR_RISING = 0
DIRECTIONS = [DIR_RISING, DIR_FALLING]




class PicoError(Exception):
    '''pico scope error'''


# TODO: add checks in methods to ensure lib, handle are valid
class PicoWrapper(object):
    '''picoscope ps2000 interface'''

    def __init__(self):
        self.handle = None

        # load the library
        self.lib = windll.LoadLibrary(LIBNAME)
        if not self.lib:
            raise PicoError('could not open library: %s' % LIBNAME)


    ################## low level methods #####################
    def __getattr__(self, name):
        '''this method will call methods starting with ps2000_ via the library'''

        # get a handle to the requested method if name looks like a PS2000 method
        if name.lower().startswith('ps2000_'):
            try:
                func = getattr(self.lib, name)
            except AttributeError:
                raise PicoError('Library "%s" does not support method "%s"' % (LIBNAME, func_name))
            else:
                # return a partial function of the library method with handle passed in
                return partial(func, self.handle)

        # not a ps2000 request, defer
        else:
            raise AttributeError
        

    def ps2000_close_unit(self):
        '''low-level close the interface to the unit'''
        res = self.lib.ps2000_close_unit(self.handle)
        self.handle = None
        return res


    def ps2000_open_unit(self):
        '''low-level open interface to unit'''
        self.handle = self.lib.ps2000_open_unit()
        return self.handle


    ################## higher level methods #####################
    def close_unit(self):
        '''close the unit'''
        res = self.ps2000_close_unit()
        if res == 0:
            raise PicoError('flash_led: invalid handle')


    def flash_led(self):
        '''flash led on front of unit'''
        res = self.ps2000_flash_led()
        if res == 0:
            raise PicoError('flash_led: invalid handle')

            
#ps2000_get_streaming_last_values
#ps2000_get_streaming_values
#ps2000_get_streaming_values_no_aggregation


    def get_timebase(self, timebase, num_samples, oversample):
        '''return the (time_interval, time_units, max_samples) for the given parameters'''

        time_interval = c_long()
        time_units = c_short()
        max_samples = c_long()
        res = self.ps2000_get_timebase(timebase, num_samples, byref(time_interval),
                                       byref(time_units), oversample, byref(max_samples))
        if res == 0:
            raise PicoError('get_timebase: parameter out of range')
        else: 
            return time_interval.value, time_units.value, max_samples.value


    def get_times_and_values(self, time_units, num_points):
        '''return requested amount of data (times, chA, chB, overflowA, overflowB)'''
        times =     (c_long * num_points)()
        ch_a_data = (c_long * num_points)()
        ch_b_data = (c_long * num_points)()
        overflow =  c_short()
        res = self.ps2000_get_times_and_values(byref(times), byref(ch_a_data), byref(ch_b_data),
                                               None, None, byref(overflow), time_units, num_points)
        if res == 0:
            raise PicoError('get_times_and_values: failed!')
        else:
            return tuple(times), tuple(ch_a_data), tuple(ch_b_data)



#ps2000_get_unit_info


    def get_values(self, num_points):
        '''return requested amount of data (chA, chB, overflowA, overflowB)'''
        ch_a_data = (c_long * num_points)()
        ch_b_data = (c_long * num_points)()
        overflow =  c_short()
        res = self.ps2000_get_values(byref(ch_a_data), byref(ch_b_data),
                                     None, None, byref(overflow), num_points)
        if res == 0:
            raise PicoError('get_values: failed!')
        else:
            return tuple(ch_a_data), tuple(ch_b_data)


#ps2000_last_button_press


    def open_unit(self):
        '''open interface to unit'''
        res = self.ps2000_open_unit()
        if res < 0:
            raise PicoError('open: failed to open scope')
        elif res == 0:
            raise PicoError('open: could not find picoscope unit')


#ps2000_open_unit_async
#ps2000_open_unit_progress
#ps2000_overview_buffer_status


    def ready(self):
        '''indicate if previous block acquire is complete - returns bool'''
        res = self.ps2000_ready()
        if res < 1:
            raise PicoError('ready: oscilloscope not attached')
        else:
            return bool(res)


    def run_block(self, num_samples, timebase, oversample):
        '''start acquisition of block of data; return expected duration of acquisition in mS'''
        time_indisposed_ms = c_long()

        res = self.ps2000_run_block(num_samples, timebase, oversample, byref(time_indisposed_ms))
        if res == 0:
            raise PicoError('run_block: parameter out of range')
        else:
            return time_indisposed_ms.value


#ps2000_run_streaming
#ps2000_run_streaming_ns
#ps2000SetAdvTriggerChannelConditions
#ps2000SetAdvTriggerChannelDirections
#ps2000SetAdvTriggerChannelProperties
#ps2000SetAdvTriggerDelay


    def set_channel(self, channel, enabled, coupling, range):
        '''select channel and modes'''
        res = self.ps2000_set_channel(channel, enabled, coupling, range)

        if res == 0:
            raise PicoError('set_channel: failed')
            
#ps2000_set_ets


    def set_led(self, state):
        '''set colour/state of led on scope'''
        if state not in LED_STATES:
            raise PicoError('set_led: unknown led state: %s' % state)

        res = self.ps2000_set_led(state)

        if res == 0:
            raise PicoError('set_led: failed')


    def set_light(self, state):
        '''enable/disable light on probe tip'''
        if state not in LIGHT_STATES:
            raise PicoError('set_light: unknown light state: %s' % state)

        res = self.ps2000_set_light(state)

        if res == 0:
            raise PicoError('set_light: failed')


#ps2000SetPulseWidthQualifier
#ps2000SetSigGenArbitrary
#ps2000SetSigGenBuiltIn


    def set_trigger(self, source, threshold, direction, delay, auto_trigger_ms):
        '''set the trigger mode'''
        res = self.ps2000_set_trigger(source, threshold, direction, delay, auto_trigger_ms)

        if res == 0:
            raise PicoError('set_trigger: failed')


#ps2000_set_trigger2


    def stop(self):
        '''stop scope acquisition'''
        res = self.ps2000_stop()

        if res == 0:
            raise PicoError('stop: failed')


    

class PS2000(PicoWrapper):
    '''PS2000 class utilizing the PicoWrapper with higher level functionality'''

    def __init__(self):
        PicoWrapper.__init__(self)
        self.interval = None
        self.units = None
        self.timebase = None
        self.oversample = None


    def set_timebase(self, max_interval_ps, min_samples, oversample):
        '''set the timebase specifying...; return actual sample interval'''
        for i in range(256): # ??
            try:
                interval, units, samples = self.get_timebase(i, min_samples, oversample)
            except PicoError:
                pass
            else:
                # once the sample interval is more than we want, we move back one
                if interval > max_interval_ps:
                    self.timebase = i-1
                    self.oversample = oversample
                    self.sample_interval, self.units, samples = self.get_timebase(self.timebase, min_samples, oversample)
                    print 'selected', self.sample_interval, self.units
                    return self.sample_interval



    def get_data(self, num_samples):
        '''blocking call to wait for data'''
        while not self.ready():
            pass  # TODO: sleep
        return self.get_values(num_samples)


    def get_timed_data(self, num_samples):
        '''blocking call to wait for data'''
        while not self.ready():
            pass  # TODO: sleep
        return self.get_times_and_values(self.units, num_samples)


    def acquire_block(self, num_samples):
        '''acquisition of num_samples at current sample_interval'''
        self.run_block(num_samples, self.timebase, 0)
        returnself.get_data()


    def start_block(self, num_samples):
        '''start run_block with current data setup'''
        self.run_block(num_samples, self.timebase, self.oversample)



################################################################################
if __name__ == '__main__':

    # example code

    print 'creating'
    scope = PS2000()
    print 'open'
    print scope.open_unit()
    print 'set channel'
    print scope.set_channel(CHANNEL_A, True, COUPLING_DC, RANGE_5V)
    print scope.set_channel(CHANNEL_B, True, COUPLING_DC, RANGE_5V)
    print 'set trigger'
    print scope.set_trigger(CHANNEL_NONE, 0, DIR_RISING, 0, 10)
    #print scope.set_trigger(CHANNEL_A, 0x1000, DIR_RISING, -5, 100) # TODO: fix threshold
    print 'set timebase'
    print scope.set_timebase(1000000, 1000, 0)
    print 'start_block'
    print scope.start_block(1000)
    print 'get_data'
    print scope.get_data(1000)
    print 'done'



    from IPython.Shell import IPShellEmbed
    ipshell = IPShellEmbed()
    ipshell()

Attachments
ps2000.py.txt
python interface
(10.03 KiB) Downloaded 1411 times

leoruyven
Newbie
Posts: 0
Joined: Tue Jan 15, 2013 12:36 pm

Re: pico python interface (partial, ps2000)

Post by leoruyven »

Hello.

When i tried to run this script i have an error.

creating
Traceback (most recent call last):
File "C:\Python27\ps2000.py", line 332, in
scope = PS2000()
File "C:\Python27\ps2000.py", line 275, in __init__
PicoWrapper.__init__(self)
File "C:\Python27\ps2000.py", line 66, in __init__
self.lib = windll.LoadLibrary(LIBNAME)
File "C:\Python27\lib\ctypes\__init__.py", line 443, in LoadLibrary
return self._dlltype(name)
File "C:\Python27\lib\ctypes\__init__.py", line 365, in __init__
self._handle = _dlopen(self._name, mode)
WindowsError: [Error 193] %1 is geen geldige Win32-toepassing

Any suggestions?
Thanks in advance

dolhop
Newbie
Posts: 1
Joined: Tue May 12, 2009 3:19 pm

Re: pico python interface (partial, ps2000)

Post by dolhop »

That script is quite old, and I believe the pico API has changed since then. I have attached a newer version of my python interface, try that first.
Attachments
picoscope.py.zip
python picoscope API
(4.63 KiB) Downloaded 978 times

jkahrs
Newbie
Posts: 0
Joined: Fri Dec 31, 2010 2:04 pm

Re: pico python interface (partial, ps2000)

Post by jkahrs »

This is really interesting. I got it running my OpenSuSE 12.2 Linux. After using dos2unix to change the end-of-line characters, it almost worked:

Code: Select all

open
info
DRIVER_VERSION:  3.6.2.0 Linux
USB_VERSION:  2.0
HARDWARE_VERSION:  16
VARIANT_INFO:  2205
BATCH_AND_SERIAL:  AU075/222
CAL_DATE:  15Apr10
ERROR_CODE:  0

Traceback (most recent call last):
  File "./picoscope.py", line 485, in 
    scope.set_timebase(80000000)
  File "./picoscope.py", line 127, in __getattr__
    raise AttributeError
AttributeError
Any idea what goes wrong here ?

If this problem can be fixed, I might re-write an application (AM radio receiver) in Python. Thanks for posting this !

dolhop
Newbie
Posts: 1
Joined: Tue May 12, 2009 3:19 pm

Re: pico python interface (partial, ps2000)

Post by dolhop »

When attempting to call a method on this "scope" object, it first checks to see if the name of the method you are calling starts with ps2000 - if so, it attempts to call such a method from the DLL/library. If not, it defers to any method that the scope object itself has.

In this case, it looks like you're trying to run the old test code at the bottom of the file which likely hasn't been updated since I rewrote the API. Here it's trying to call a method "set_timebase" which has likely been removed/renamed.

I've attached a new file with what is likely better representative test code, but since I don't have a picoscope anymore, i can't guarantee it'll work off the top. You might need to adjust.
Attachments
picoscope.py.zip
(4.65 KiB) Downloaded 966 times

jkahrs
Newbie
Posts: 0
Joined: Fri Dec 31, 2010 2:04 pm

Re: pico python interface (partial, ps2000)

Post by jkahrs »

The new script produces different results but still fails.

Code: Select all

open
info
DRIVER_VERSION:  3.6.2.0 Linux
USB_VERSION:  2.0
HARDWARE_VERSION:  16
VARIANT_INFO:  2205
BATCH_AND_SERIAL:  AU075/222
CAL_DATE:  15Apr10
ERROR_CODE:  0

Traceback (most recent call last):
  File "./picoscope.py", line 480, in 
    chlA, chlB = scope.get_data(scaleB=10)    # 10x divider on probe
  File "./picoscope.py", line 387, in get_data
    a, b = self.get_values(PS_MAX_SAMPLES)
  File "./picoscope.py", line 215, in get_values
    raise PicoError('get_values: failed!')
__main__.PicoError: get_values: failed!
Thanks for the explanation and the code. I think I will take a closer look this weekend.

dolhop
Newbie
Posts: 1
Joined: Tue May 12, 2009 3:19 pm

Re: pico python interface (partial, ps2000)

Post by dolhop »

likely because the scope didn't trigger, so there was no data available. It's non-blocking, (thus the time.sleep() call), so if it waits for a trigger that doesn't happen, when you ask for data, none will be available.

hendrik
Newbie
Posts: 0
Joined: Thu May 16, 2013 9:39 am

Re: pico python interface (partial, ps2000)

Post by hendrik »

Hello,
I have reused the PicoWrapper class in order to load the dll. This works fine when use get_values in block mode. However the ps2000_set_sig_gen() refuses to do something. Here's my code:

Code: Select all

import sys, time
from ctypes import *
from socket import *
from functools import partial
from array import array

LIBNAME = 'ps2000.dll'
  
PS2000_SINE = 0
PS2000_UPDOWN = 2

class PicoError(Exception):
    '''pico scope error'''


# TODO: add checks in methods to ensure lib, handle are valid
class PicoWrapper(object):
    '''picoscope ps2000 interface'''

    def __init__(self):
        self.handle = None

        # load the library
        if sys.platform == 'win32':
            self.lib = windll.LoadLibrary(LIBNAME)
        else:
            self.lib = cdll.LoadLibrary(LIBNAME)
        if not self.lib:
            raise PicoError('could not open library: %s' % LIBNAME)
    def __getattr__(self, name):
        '''this method will call methods starting with ps2000_ via the library'''

        # get a handle to the requested method if name looks like a PS2000 method
        if name.lower().startswith('ps2000_'):
            try:
                func = getattr(self.lib, name)
            except AttributeError:
                raise PicoError('Library "%s" does not support method "%s"' % (LIBNAME, func_name))
            else:
                # return a partial function of the library method with handle passed in
                return partial(func, self.handle)

        # not a ps2000 request, defer
        else:
            raise AttributeError

if __name__ == '__main__':
    scoop = PicoWrapper()                   #Open scope
    scoop.handle = scoop.lib.ps2000_open_unit()
    print('Handle', scoop.handle)
    res = scoop.ps2000_set_sig_gen_built_in(
                                    c_long(0),           #Voffset
                                    c_ulong(1900),       #pk-pk voltage in mV
                                    PS2000_SINE,         #PS2000_WAVE_TYPE; sine
                                    c_float(1000),      #Start frequency
                                    c_float(9000),      #Stop frequency
                                    c_float(9.0),        #Increment
                                    c_float(2.0),      #Dwell time (sec)
                                    PS2000_UPDOWN,       #PS2000_SWEEP_TYPE
                                    c_ulong(9))          #Sweeps
    if res !=  0:
        print('Set sig gen faalt', res)
    res = scoop.ps2000_set_trigger(0,     #Source; channel A
                            100,           #Threshold
                            1,              #Direction
                            0,              #Delay; start at trigger
                            100)              #Auto trigger ms
    if res != 1:
        print('Set trigger faalt', res)
    timeBase = 2
    numSamples = 2**4
    timeInterval = c_long(0)
    timeUnits = c_short(0)
    oversample = 16
    maxSamples = c_long()
    res = scoop.ps2000_get_timebase(
                    timeBase,           #Timebase
                    numSamples,         #No of samples
                    byref(timeInterval),#Time interval
                    byref(timeUnits),   #Time units
                    oversample,         #Oversample
                    byref(maxSamples),  #Max samples
                    )
    if res != 1:
        print('Get timebase faalt', res)
    numSamples = maxSamples.value
    print('numSamples', numSamples)
    timeIndisposed = c_long(0)
    res = scoop.ps2000_run_block(maxSamples, timeBase, oversample, byref(timeIndisposed))
    if res != 1:
        raise Exception
    while not 1==scoop.ps2000_ready():
        time.sleep(1)
        print('Scoop niet klaar')
    bufferA = (c_short * numSamples)()
    bufferB = (c_short * numSamples)()
    bufferC = (c_short * numSamples)()
    bufferD = (c_short * numSamples)()
    overflow = c_short(0)
    res = scoop.ps2000_get_values(
                    byref(bufferA),
                    byref(bufferB),
                    byref(bufferC),
                    byref(bufferD),
                    byref(overflow),
                    numSamples)
    if res == 0:
        print('Get values faalt', res)
    for i in bufferA:
        print(i, end='\t')
    print('\nOverflow: ', overflow.value)
    time.sleep(10)              #wait some time in case signal generator is actually doing something
    scoop.ps2000_stop()
get_unit_info() tells me the following:
Variabele 0 in get unit info: 2.0.8.163
Variabele 1 in get unit info: 2.0
Variabele 2 in get unit info: 16
Variabele 3 in get unit info: 2205
Variabele 4 in get unit info: AP845/519
Variabele 5 in get unit info: 04Sep12
Variabele 6 in get unit info: 0
Can anyone see why the signal generator doesn't do anything? Thanks.

Hitesh

Re: pico python interface (partial, ps2000)

Post by Hitesh »

Hi,

What is the result value that is output from the call to the signal generator?

Have you tried just outputting a constant sine wave (start frequency = stop frequency) and setting increment, dwell time and sweep parameters to 0?

Regards,

MichaelCroquette
Newbie
Posts: 0
Joined: Tue Jul 22, 2014 3:17 pm

Re: pico python interface (partial, ps2000)

Post by MichaelCroquette »

Hi,

I'm also having some trouble using the signal generator, whatever parameters I enter it doesn't seem to work.
Could someone put an examle that work ?
I've tried this link but I don't what picoscope file I should import.
Thank you.

https://github.com/colinoflynn/pico-pyt ... 00_demo.py

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

Re: pico python interface (partial, ps2000)

Post by Martyn »

If you install Picoscope 6 software and it talks to the device you will have the necessary files available on your system.
Martyn
Technical Support Manager

Post Reply