diff --git a/README.md b/README.md index c182c87..78ec154 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Current version of the program was tested under Linux only. Later, I'll test it ## Supported AWG Models -As of January 30, 2019 the program supports the following models: +As of June 7, 2020 the program supports the following models: * **BK Precision BK4075** One channel 25MHz AWG. Requires a RS-232 serial port for the connection to a PC. It is compatible with the SCPI 1992.0 standard. @@ -20,6 +20,8 @@ As of January 30, 2019 the program supports the following models: * **Feeltech FY6600** Another Chinese generator which is widely sold on eBay and AliExpress. It also connects to the PC as a USB serial port. +* **Picoscope 3000a series** A UK made USB scope that includes a basic function generator/AWG in some models. There is an API that needs downloading from Picotech, also their python wrappers are needed. Tested with the Picoscope 3205 MSO only but should work with other models. + ## Program Structure TBD @@ -103,6 +105,10 @@ VXI-11 DESTROY_LINK, SCPI command: None ## Changelog +### 2020-06-07 + +* Support for the Picoscope 3205 MSO + ### 2019-01-30 * The program supports Feeltech FY6600 AWG. @@ -125,6 +131,8 @@ I'd like to add here more AWGs but it's impossible to have them all at the home * **Nick Bryant (Dundarave on EEVblog Forum)** - Driver for Feeltech FY6600 AWG. +* **Mark Watson** - Driver for Picoscope 3205 MSO. + ## Links 1. [Siglent SDS1104X-E and SDS1204X-E: Bode plot with non-Siglent AWG](http://www.eevblog.com/forum/testgear/siglent-sds1104x-e-and-sds1204x-e-bode-plot-with-non-siglent-awg/) on EEVblog Forum. diff --git a/sds1004x_bode/awg_factory.py b/sds1004x_bode/awg_factory.py index f2c1008..f31f601 100644 --- a/sds1004x_bode/awg_factory.py +++ b/sds1004x_bode/awg_factory.py @@ -4,12 +4,14 @@ @author: 4x1md Update of original file on Nov. 17 2018 by Dundarave to add entries needed for FY6600 support. +Update of original file on Jun. 7 2020 by Mark Watson to add entries needed for ps3000a support. ''' from awgdrivers.dummy_awg import DummyAWG from awgdrivers.jds6600 import JDS6600 from awgdrivers.bk4075 import BK4075 from awgdrivers.fy6600 import FY6600 +from awgdrivers.ps3000a import ps3000a class AwgFactory(object): @@ -28,4 +30,5 @@ def get_class_by_name(self, short_name): awg_factory.add_awg(JDS6600.SHORT_NAME, JDS6600) awg_factory.add_awg(BK4075.SHORT_NAME, BK4075) awg_factory.add_awg(FY6600.SHORT_NAME, FY6600) +awg_factory.add_awg(ps3000a.SHORT_NAME, ps3000a) diff --git a/sds1004x_bode/awgdrivers/ps3000a.py b/sds1004x_bode/awgdrivers/ps3000a.py new file mode 100644 index 0000000..1ee09f5 --- /dev/null +++ b/sds1004x_bode/awgdrivers/ps3000a.py @@ -0,0 +1,141 @@ +''' +Created on Jun 7, 2020 + +@author: Mark Watson + +Driver for Picoscope 3205MSO (3000a series) +''' + +import ctypes +from picosdk.ps3000a import ps3000a as ps +import time +from picosdk.functions import assert_pico_ok + +from base_awg import BaseAWG +import constants +from exceptions import UnknownChannelError + +CHANNELS = (0, 1) +CHANNELS_ERROR = "ps3000a has only one channel." +WAVEFORM_COMMANDS = { + constants.SINE: ctypes.c_int16(0), + constants.SQUARE: ctypes.c_int16(1), + constants.PULSE: ctypes.c_int16(8), #TODO: what should this be? + constants.TRIANGLE: ctypes.c_int16(2) + } +# Output impedance of the AWG +R_IN = 600.0 + +class ps3000a(BaseAWG): + ''' + Picoscope 3205MSO AWG driver + ''' + SHORT_NAME = "ps3000a" + + def __init__(self, *args): + self.connect() + self.initialize() + + def connect(self): + # Gives the device a handle + self.chandle = ctypes.c_int16() + + # Opens the device/s + status = {} + status["openunit"] = ps.ps3000aOpenUnit(ctypes.byref(self.chandle), None) + + try: + assert_pico_ok(status["openunit"]) + except: + + # powerstate becomes the status number of openunit + powerstate = status["openunit"] + + # If powerstate is the same as 282 then it will run this if statement + if powerstate == 282: + # Changes the power input to "PICO_POWER_SUPPLY_NOT_CONNECTED" + status["ChangePowerSource"] = ps.ps3000aChangePowerSource(self.chandle, 282) + # If the powerstate is the same as 286 then it will run this if statement + elif powerstate == 286: + # Changes the power input to "PICO_USB3_0_DEVICE_NON_USB3_0_PORT" + status["ChangePowerSource"] = ps.ps3000aChangePowerSource(self.chandle, 286) + else: + raise + + assert_pico_ok(status["ChangePowerSource"]) + + def disconnect(self): + status = {} + status["close"] = ps.ps3000aCloseUnit(self.chandle) + assert_pico_ok(status["close"]) + + def initialize(self): + self.wavetype = ctypes.c_int16(0) #sine + self.frequency = 1; #1Hz + self.amplitude = 0.001; #0.001v + self.offsetVoltage = 0; + z = 50 + self.v_out_coeff = z / (z + R_IN) + self.v_out_coeff = 1.0 + self.onoff = False + + def get_id(self): + return "ps3000a" + + def enable_output(self, channel, on): + if channel is not None and channel not in CHANNELS: + raise UnknownChannelError(CHANNELS_ERROR) + sweepType = ctypes.c_int32(0) + triggertype = ctypes.c_int32(0) + triggerSource = ctypes.c_int32(0) + status = {} + + amplitude = ctypes.c_uint32(int(round(self.amplitude*1000000/self.v_out_coeff))) + offsetVoltage = ctypes.c_int32(int(round(self.offsetVoltage*1000000/self.v_out_coeff))) + if on: + status["SetSigGenBuiltIn"] = ps.ps3000aSetSigGenBuiltIn(self.chandle, offsetVoltage, amplitude, self.wavetype, self.frequency, self.frequency, 0, 1, sweepType, 0, 0, 0, triggertype, triggerSource, 1) + assert_pico_ok(status["SetSigGenBuiltIn"]) + self.onoff = True + else: + status["SetSigGenBuiltIn"] = ps.ps3000aSetSigGenBuiltIn(self.chandle, ctypes.c_int32(0), ctypes.c_uint32(0), self.wavetype, self.frequency, self.frequency, 0, 1, sweepType, 0, 0, 0, triggertype, triggerSource, 1) + assert_pico_ok(status["SetSigGenBuiltIn"]) + self.onoff = False + + def set_frequency(self, channel, freq): + if channel is not None and channel not in CHANNELS: + raise UnknownChannelError(CHANNELS_ERROR) + self.frequency = ctypes.c_float(freq) + self.enable_output(channel,self.onoff) + + def set_phase(self, phase): + pass + + def set_wave_type(self, channel, wave_type): + if channel is not None and channel not in CHANNELS: + raise UnknownChannelError(CHANNELS_ERROR) + self.wavetype = WAVEFORM_COMMANDS[wave_type] + self.enable_output(channel,self.onoff) + + def set_amplitue(self, channel, amplitude): #BSWV specifies this in pk-pk + if channel is not None and channel not in CHANNELS: + raise UnknownChannelError(CHANNELS_ERROR) + self.amplitude = amplitude + self.enable_output(channel,self.onoff) + + def set_offset(self, channel, offset): + if channel is not None and channel not in CHANNELS: + raise UnknownChannelError(CHANNELS_ERROR) + self.offsetVoltage = offset + self.enable_output(channel,self.onoff) + + def set_load_impedance(self, channel, z): + if channel is not None and channel not in CHANNELS: + raise UnknownChannelError(CHANNELS_ERROR) + if z == constants.HI_Z: + v_out_coeff = 1 + else: + v_out_coeff = z / (z + R_IN) + self.v_out_coeff = v_out_coeff + self.enable_output(channel,self.onoff) + pass +