Test and Measurement Forum

Python code for picoscope 4000 : 4226 , 4227

Post general discussions on using our drivers to write your own software here

Python code for picoscope 4000 : 4226 , 4227

Postby cpc333 » Sat Aug 18, 2012 5:37 pm

Hey, here is some python code I used to automate my picoscope 4227 for single shot triggering and data collection. This was my first time using python, so might not be the most efficient way to do things. Just the same, it works for me! Enjoy!


Code: Select all
#Pico4000 scope setup and data transfer
#Patrick Carle, August 2012
#Adapted from http://www.picotech.com/support/topic4926.html
#Example code from command line (assuming file is named ps4000.py):
#C:\> python
#>>> import ps4000
#>>> ps4000.runScope()

import datetime
from ctypes import *  # shouldn't import *, but there are so many items to use...
import time
from numpy import *

#Picoscope library
LIBNAME = 'ps4000.dll'

#Picoscope channel max/min ADC count value
PS4000_MAX_VALUE = 32764
PS4000_MIN_VALUE = -32764
PS4000_EXT_MAX_VALUE = 32767
PS4000_EXT_MIN_VALUE = -32767
PS4000_EXT_RANGE_VOLTS = 20
VOLTS2ADC_EXT = PS4000_EXT_MAX_VALUE / PS4000_EXT_RANGE_VOLTS

#Sampling frequencies
SAMPLE_250MHZ = 0
SAMPLE_125MHZ = 1
SAMPLE_62MHZ = 2 #actually 62.5MHz
SAMPLE_31MHZ = 3 #actually 31.25MHz

#Channel range 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
VOLTAGE_RANGES = { 1:.02, 2:.05, 3:.1, 4:.2, 5:.5, 6:1, 7:2, 8:5, 9:10, 10:20 }

#Channels
CHANNEL_A = 0
CHANNEL_B = 1
CHANNEL_EXT = 4

#Coupling
DC_COUPLING = 1
AC_COUPLING = 0

#Enabled/disabled
ENABLED = 1
DISABLED = 0

#Trigger direction
DIR_FALLING = 1
DIR_RISING = 0

#Downsample mode
RATIO_MODE_NONE = 0
RATIO_MODE_AGGREGATE = 1

#=============================================================================================
#=============================================================================================

class PS4000(object):
   #Initialize
   def __init__(self):
      self.lib = windll.LoadLibrary(LIBNAME)         
      self.handle = None
      self.serial = None
      self.chRange = [ None, None ]
      self.coupling = [ None, None ]
      self.chEnabled = [ None, None ]
      self.chName = [ None, None ]
      self.timeInterval = None
   
   #Open scope by serial number
   def openScope(self):
      handlePointer = c_short()
      serialNullTermStr = c_char_p( bytes(self.serial,'ascii') )
      message = self.lib.ps4000OpenUnitEx( byref(handlePointer), serialNullTermStr )
      self.handle = handlePointer.value
      if message != 0: print('Error openScope:', message)
   
   #Setup channel
   def setChannel( self ):
      message = self.lib.ps4000SetChannel( self.handle, CHANNEL_A, self.chEnabled[CHANNEL_A], self.coupling[CHANNEL_A], self.chRange[CHANNEL_A] )
      if message != 0: print('Error setChannel A:', message)
      message = self.lib.ps4000SetChannel( self.handle, CHANNEL_B, self.chEnabled[CHANNEL_B], self.coupling[CHANNEL_B], self.chRange[CHANNEL_B] )
      if message != 0: print('Error setChannel B:', message)
      
   #Setup trigger
   def setTrigger( self, threshold, delay, autoTrigger_ms ):
      message = self.lib.ps4000SetSimpleTrigger( self.handle, ENABLED, CHANNEL_EXT, threshold, DIR_RISING, delay, autoTrigger_ms )
      if message != 0: print('Error setTrigger:', message)
      
   #Setup timebase
   def getTimebase( self, timebase, numSamples, oversample, segmentIndex ):
      timeInterval_ns = c_long()
      maxSamples = c_long()
      message = self.lib.ps4000GetTimebase( self.handle, timebase, numSamples, byref(timeInterval_ns), oversample, byref(maxSamples), segmentIndex )
      if message != 0: print('Error getTimebase:', message)
      else: self.timeInterval = timeInterval_ns.value / 1e9
      
   #Run block (record data on scope)
   def runBlock( self, numPreTrigSamples, numPostTrigSamples, timebase, oversample, segmentIndex ):
      timeIndisposedMs = c_long()
      pParameter = c_void_p()
      message = self.lib.ps4000RunBlock( self.handle, numPreTrigSamples, numPostTrigSamples, timebase, oversample, byref(timeIndisposedMs), segmentIndex, None, byref(pParameter) )
      if message != 0: print('Error runBlock:', message)
      
   #Determine if scope is ready to transfer data
   def isReady( self ):
      ready = c_short()
      message = self.lib.ps4000IsReady( self.handle, byref(ready) )
      if ready.value: print('Triggered')
      if message != 0: print('Error isReady:', message)
      return ready.value
   
   #Transfers data to computer
   def getValues( self, channel, numSamples, startIndex, downSampleRatio, downSampleRatioMode, segmentIndex ):
      #Set channel from which to get data
      dataPointer = ( c_short * numSamples )()
      message = self.lib.ps4000SetDataBuffer( self.handle, channel, byref(dataPointer), numSamples )
      if message != 0: print('Error setDataBuffer:', message)
      
      #Get data
      numSamplesReturned = c_long()
      numSamplesReturned.value = numSamples
      overflow = c_short()
      message = self.lib.ps4000GetValues( self.handle, startIndex, byref(numSamplesReturned), downSampleRatio, downSampleRatioMode, segmentIndex, byref(overflow) )   
      print('Scope', self.serial, 'channel', channel,':',numSamplesReturned.value, 'samples returned')
      if message != 0:
         print('Error getValues:', message)
         return 0
      #ADC count to voltage conversion factor
      newData = asarray(list(dataPointer))
      a2v = VOLTAGE_RANGES[self.chRange[channel]] / PS4000_MAX_VALUE
      newData = newData * a2v
      return newData
         
   #Close scope
   def closeScope(self):
      message = self.lib.ps4000CloseUnit(self.handle)
      self.handle = None
      if message != 0: print('Error closeScope:', message)
      else: print('Scope', self.serial, 'closed')
      
   
#==================================================================================
#==================================================================================

def runScope(shotnum=654321,filedir='C:/'):
   #Sets up scope, waits for trigger, collects data and saves to file
   #Inputs:
   #   shotnum      Integer identifying the shot number (ie. to give the data an ID)
   #   filtedir   Directory in which to save the file

   #Sampling parameters
   timebase = SAMPLE_125MHZ
   numSamples = int(10e6) #number of samples to record from each channel
   
   #Create scope objects   
   scopes = [ PS4000() ]
   # scopes = [ PS4000(), PS4000(), PS4000() ]
   #Serial numbers
   scopes[0].serial = 'AT630/060'
   # scopes[1].serial = 'AT630/090'
   # scopes[2].serial = 'XXXXX/XXX'
   #Channel names
   scopes[0].chName = [ 'ch1a', 'ch1b' ]
   # scopes[1].chName = [ 'ch2a', 'ch2b' ]
   # scopes[2].chName = [ 'ch3a', 'ch3b' ]
   #Channel coupling
   scopes[0].coupling = [ AC_COUPLING, AC_COUPLING ]
   # scopes[1].coupling = [ AC_COUPLING, AC_COUPLING ]
   # scopes[2].coupling = [ AC_COUPLING, AC_COUPLING ]
   #Channel enabled
   scopes[0].chEnabled = [ ENABLED, ENABLED ]
   # scopes[1].chEnabled = [ DISABLED, DISABLED ]
   # scopes[2].chEnabled = [ DISABLED, DISABLED ]
   #Channel range
   scopes[0].chRange = [ RANGE_200MV, RANGE_200MV ]
   # scopes[1].chRange = [ RANGE_200MV, RANGE_200MV ]
   # scopes[2].chRange = [ RANGE_200MV, RANGE_200MV ]

   #Other parameters
   #Sampling
   oversample = 1 #1 means no oversampling
   segmentIndex = 0
   #Triggering
   threshold_volts = 10 #[V]
   threshold_adc = int( threshold_volts * VOLTS2ADC_EXT ) #trigger threshold in ADC counts
   delay = 0 #[s]
   autoTrigger_ms = 0 #[ms] time before automatic trigger... 0 means wait indefinitely for trigger
   maxTriggerSeconds = 60 #[s] maximum number of seconds to wait for trigger
   #Data collection
   numPreTrigSamples = 0
   numPostTrigSamples = numSamples
   startIndex = 1
   downSampleRatio = 1
   downSampleRatioMode = RATIO_MODE_NONE

   #Setup scopes
   for scopeNum in range(0,len(scopes)):
      #Open scope
      scopes[scopeNum].openScope()
      #If scope exists, set it up
      if scopes[scopeNum].handle != 0:
         print('Configuring scope', scopes[scopeNum].serial, '...'),
         #Channels
         scopes[scopeNum].setChannel()
         scopes[scopeNum].getTimebase( timebase, numSamples, oversample, segmentIndex )
         #Trigger
         scopes[scopeNum].setTrigger( threshold_adc, delay, autoTrigger_ms )
         #Activate scope to save data to memory
         scopes[scopeNum].runBlock( numPreTrigSamples, numPostTrigSamples, timebase, oversample, segmentIndex )

   #Wait for trigger
   print('Waiting for trigger ...')
   secondsElapsed = 0
   while secondsElapsed < maxTriggerSeconds and not scopes[0].isReady():
      time.sleep(1)
      secondsElapsed += 1
   #Trigger time out
   if secondsElapsed == maxTriggerSeconds:
      print('Trigger timed out')
      closeAllScopes(scopes)
      return

   #Download data from scope
   data = []
   dataNames = []
   for scopeNum in range(len(scopes)):
      #If scope exists and channel enabled, download data
      #Channel A
      if scopes[scopeNum].handle != 0 and scopes[scopeNum].chEnabled[CHANNEL_A]:
         newData = scopes[scopeNum].getValues( CHANNEL_A, numSamples, startIndex, downSampleRatio, downSampleRatioMode, segmentIndex )
         data.append(newData)
         dataNames = dataNames + [scopes[scopeNum].chName[CHANNEL_A]]
      #Channel B
      if scopes[scopeNum].handle != 0 and scopes[scopeNum].chEnabled[CHANNEL_B]:
         newData = scopes[scopeNum].getValues( CHANNEL_B, numSamples, startIndex, downSampleRatio, downSampleRatioMode, segmentIndex )
         data.append(newData)
         dataNames = dataNames + [scopes[scopeNum].chName[CHANNEL_B]]
   #Exit if no data
   if len(data)==0:   
      closeAllScopes(scopes)
      return
      
   #Write data to file
   #Filepath
   filepath = filedir + 'pol' + str(shotnum).zfill(6) + '.csv'
   print('Writing',filepath,'...')
   #Open file
   f = open(filepath, 'w', newline='') # open for 'w'riting
   #Date and time
   f.write('Date and time:\n')
   f.write('%s\n' % datetime.datetime.now())
   #Shot number
   f.write('Shot number:\n')
   f.write('%06d\n' % shotnum)
   #Sampling time interval
   f.write('Sampling time interval in seconds (dt):\n')
   f.write('%s\n' % scopes[0].timeInterval)
   #Initial sample time
   f.write('Initial sample time in seconds (t0):\n')
   f.write('0\n')
   #Column names
   for name in dataNames[:-1]:
      f.write(name+',')
   f.write(dataNames[-1]+'\n')
   #Data columns
   for row in range(len(data[0])):
      for col in range(len(data[:-1])):
         f.write('%.5f,' % data[col][row])
      f.write('%.5f\n' % data[-1][row])
   #Close file
   f.close() # close the file
   print('File written')

   #Close scopes
   closeAllScopes(scopes)
   
def closeAllScopes(scopes):
   #Close all scopes
   for scopeNum in range(0,len(scopes)):
      scopes[scopeNum].closeScope()
   
   

#Error Codes
# 00 PICO_OK. The PicoScope 4000 is functioning correctly.
# 01 PICO_MAX_UNITS_OPENED. An attempt has been made to open more than PS4000_MAX_UNITS. (Reserved)
# 02 PICO_MEMORY_FAIL. Not enough memory could be allocated on the host machine.
# 03 PICO_NOT_FOUND. No PicoScope 4000 could be found.
# 04 PICO_FW_FAIL. Unable to download firmware.
# 05 PICO_OPEN_OPERATION_IN_PROGRESS. The driver is busy opening a device.
# 06 PICO_OPERATION_FAILED. An unspecified error occurred.
# 07 PICO_NOT_RESPONDING. The PicoScope 4000 is not responding to commands from the PC.
# 08 PICO_CONFIG_FAIL. The configuration information in the PicoScope 4000 has become corrupt or is missing.
# 09 PICO_KERNEL_DRIVER_TOO_OLD. The picopp.sys file is too old to be used with the device driver.
# 0A PICO_EEPROM_CORRUPT. The EEPROM has become corrupt, so the device will use a default setting.
# 0B PICO_OS_NOT_SUPPORTED. The operating system on the PC is not supported by this driver.
# 0C PICO_INVALID_HANDLE. There is no device with the handle value passed.
# 0D PICO_INVALID_PARAMETER. A parameter value is not valid.
# 0E PICO_INVALID_TIMEBASE. The time base is not supported or is invalid.
# 0F PICO_INVALID_VOLTAGE_RANGE. The voltage range is not supported or is invalid.
# 10 PICO_INVALID_CHANNEL. The channel number is not valid on this device or no channels have been set.
# 11 PICO_INVALID_TRIGGER_CHANNEL. The channel set for a trigger is not available on this device.
# 12 PICO_INVALID_CONDITION_CHANNEL. The channel set for a condition is not available on this device.
# 13 PICO_NO_SIGNAL_GENERATOR. The device does not have a signal generator.
# 14 PICO_STREAMING_FAILED. Streaming has failed to start or has stopped without user request.
# 15 PICO_BLOCK_MODE_FAILED. Block failed to start - a parameter may have been set wrongly.
# 16 PICO_NULL_PARAMETER. A parameter that was required is NULL.
# 17 PICO_ETS_MODE_SET. The function call failed because ETS mode is being used.
# 18 PICO_DATA_NOT_AVAILABLE. No data is available from a run block call.
# 19 PICO_STRING_BUFFER_TOO_SMALL. The buffer passed was too small for the string to be returned.
# 1A PICO_ETS_NOT_SUPPORTED. ETS is not supported on this device variant.
# 1B PICO_AUTO_TRIGGER_TIME_TOO_SHORT. The auto trigger time is less than the time it will take to collect the data.
# 1C PICO_BUFFER_STALL. The collection of data has stalled as unread data would be overwritten.
# 1D PICO_TOO_MANY_SAMPLES. Number of samples requested is more than available in the current memory segment.
# 1E PICO_TOO_MANY_SEGMENTS. Not possible to create number of segments requested.
# 1F PICO_PULSE_WIDTH_QUALIFIER. A null pointer has been passed in the trigger function or one of the parameters is out of range.
# 20 PICO_DELAY. One or more of the hold-off parameters are out of range.
# 21 PICO_SOURCE_DETAILS. One or more of the source details are incorrect.
# 22 PICO_CONDITIONS. One or more of the conditions are incorrect.
# 23 PICO_USER_CALLBACK. The driver's thread is currently in the ps4000...Ready callback function and therefore the action cannot be carried out.
# 24 PICO_DEVICE_SAMPLING. An attempt is being made to get stored data while streaming. Either stop streaming by calling ps4000Stop, or use ps4000GetStreamingLatestValues.
# 25 PICO_NO_SAMPLES_AVAILABLE. ...because a run has not been completed.
# 26 PICO_SEGMENT_OUT_OF_RANGE. The memory index is out of range.
# 27 PICO_BUSY. The driver cannot return data yet.
# 28 PICO_STARTINDEX_INVALID. The start time to get stored data is out of range.
# 29 PICO_INVALID_INFO. The information number requested is not a valid number.
# 2A PICO_INFO_UNAVAILABLE. The handle is invalid so no information is available about the device. Only PICO_DRIVER_VERSION is available.
# 2B PICO_INVALID_SAMPLE_INTERVAL. The sample interval selected for streaming is out of range.
# 2C PICO_TRIGGER_ERROR. ETS is set but no trigger has been set. A trigger setting is required for ETS.
# 2D PICO_MEMORY. Driver cannot allocate memory
# 2E PICO_SIG_GEN_PARAM. Error in signal generator parameter
# 2F PICO_SHOTS_SWEEPS_WARNING. The signal generator will output the signal required but sweeps and shots will be ignored. Only one parameter can be non-zero.
# 30 PICO_SIGGEN_TRIGGER_SOURCE. A software trigger has been sent but the trigger source is not a software trigger.
# 31 PICO_AUX_OUTPUT_CONFLICT. A ps4000SetTrigger... call has found a conflict between the trigger source and the AUX output enable.
# 32 PICO_AUX_OUTPUT_ETS_CONFLICT. ETS mode is being used and AUX is set as an input.
# 33 PICO_WARNING_EXT_THRESHOLD_CONFLICT. The EXT threshold is being set in both a ps4000SetTrigger... function and in the signal generator but the threshold values differ. The last value set will be used.
# 34 PICO_WARNING_AUX_OUTPUT_CONFLICT. A ps4000SetTrigger... function has set AUX as an output and the signal generator is using it as a trigger.
# 35 PICO_SIGGEN_OUTPUT_OVER_VOLTAGE. The requested voltage and offset levels combine to give an overvoltage.
# 36 PICO_DELAY_NULL. NULL pointer passed as delay parameter.
# 37 PICO_INVALID_BUFFER. The buffers for overview data have not been set while streaming.
# 38 PICO_SIGGEN_OFFSET_VOLTAGE. The signal generator offset voltage is higher than allowed.
# 39 PICO_SIGGEN_PK_TO_PK. The signal generator peak-to-peak voltage is higher than allowed.
# 3A PICO_CANCELLED. A block collection has been cancelled.
# 3B PICO_SEGMENT_NOT_USED. The specified segment index is not in use.
# 3C PICO_INVALID_CALL. The wrong GetValues function has been called for the collection mode in use.
# 3D PICO_GET_VALUES_INTERRUPTED
# 3F PICO_NOT_USED. The function is not available.
# 40 PICO_INVALID_SAMPLERATIO. The aggregation ratio requested is out of range.
# 41 PICO_INVALID_STATE. Device is in an invalid state.
# 42 PICO_NOT_ENOUGH_SEGMENTS. The number of segments allocated is fewer than the number of captures requested.
# 43 PICO_DRIVER_FUNCTION. You called a driver function while another driver function was still being processed.
# 44 PICO_RESERVED
# 45 PICO_INVALID_COUPLING. The dc argument passed to ps4000SetChannel was invalid.
# 46 PICO_BUFFERS_NOT_SET. Memory buffers were not set up before calling one of the ps4000Run... functions.
# 47 PICO_RATIO_MODE_NOT_SUPPORTED. downSampleRatioMode is not valid for the connected device.
# 48 PICO_RAPID_NOT_SUPPORT_AGGREGATION. Aggregation was requested in rapid block mode.
# 49 PICO_INVALID_TRIGGER_PROPERTY. An incorrect value was passed to ps4000SetTriggerChannelProperties.
cpc333
Newbie
 
Posts: 0
Joined: Sat Aug 18, 2012 5:31 pm

Re: Python code for picoscope 4000 : 4226 , 4227

Postby jcmartin » Thu Jun 13, 2013 8:41 am

Hello,

I am having some issues implementing your code on my laptop. I am using a picoscope 4227, and python 2.7. The problem I am having is that the scope is never activated and therefore there is not communication between the computer and the scope. Are you using python 2.7?

I am also having an error in the following line:

Code: Select all
#Open scope by serial number
   def openScope(self):
      handlePointer = c_short()
      serialNullTermStr = c_char_p( bytes(self.serial,'ascii') )  <<<<<<<<<<<<
      message = self.lib.ps4000OpenUnitEx( byref(handlePointer), serialNullTermStr )
      self.handle = handlePointer.value
      if message != 0: print('Error openScope:', message)


python shows me an error because "byte" should have only one variable.
jcmartin
Newbie
 
Posts: 0
Joined: Thu Jun 13, 2013 8:30 am

Re: Python code for picoscope 4000 : 4226 , 4227

Postby Martyn » Mon Jul 01, 2013 8:17 am

Are you able to open the unit without the serial number string using
Code: Select all
self.lib.ps4000OpenUnit(byref(handlePointer))
Martyn
Technical Specialist
Martyn
Site Admin
Site Admin
 
Posts: 2280
Joined: Fri Jun 10, 2011 8:15 am
Location: St. Neots

Re: Python code for picoscope 4000 : 4226 , 4227

Postby nedyoxall » Thu Feb 27, 2014 11:40 am

Hi all,
Sorry for resurrecting an old post, but every time I try and run this code I get a WindowsError [Error 193]. I believe this has something to do with 32/64 bit machines...? I'm running the code on a 64 bit version of Python. I'd be very grateful if anyone could shed some light on this!
Many thanks.
nedyoxall
Newbie
 
Posts: 0
Joined: Thu Feb 27, 2014 11:22 am


Return to General Software Discussions

Who is online

Users browsing this forum: No registered users and 0 guests