Python code for picoscope 4000 : 4226 , 4227

Post general discussions on using our drivers to write your own software here
Post Reply
cpc333
Newbie
Posts: 0
Joined: Sat Aug 18, 2012 5:31 pm

Python code for picoscope 4000 : 4226 , 4227

Post by cpc333 »

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.

jcmartin
Newbie
Posts: 0
Joined: Thu Jun 13, 2013 8:30 am

Re: Python code for picoscope 4000 : 4226 , 4227

Post by jcmartin »

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.

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

Re: Python code for picoscope 4000 : 4226 , 4227

Post by Martyn »

Are you able to open the unit without the serial number string using

Code: Select all

self.lib.ps4000OpenUnit(byref(handlePointer))
Martyn
Technical Support Manager

nedyoxall
Newbie
Posts: 0
Joined: Thu Feb 27, 2014 11:22 am

Re: Python code for picoscope 4000 : 4226 , 4227

Post by nedyoxall »

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.

Post Reply