diff --git a/README.md b/README.md index b81fd2d..b0451ed 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,117 @@ # scopehal-pico-bridge Socket servers for Pico Technology instruments allowing remote access via libscopehal. + + +## How to use + +##### Prerequisites +* Install the Pico SDK (https://picotech.com/downloads) +* Compile [scopehal-apps](https://www.ngscopeclient.org/manual/GettingStarted.html) +* Compile scopehal-pico-bridge (see below for instructions) +--- +1. Connect your PicoScope to the computer. +2. Start the scopehal-pico-bridge (ps6000d.exe). It should open a console window and read "Successfully opened instrument", listing some info about the device like model and serial number. +3. Now start [ngscopeclient](https://github.com/ngscopeclient/scopehal-apps). Select Add / Oscilloscope / Connect... from the top menu. Enter or select the following values: + * Nickname: (choose a display name for your scope.) + * Driver: **pico** + * Transport: **twinlan** + * Path: **localhost:5025:5026** +4. Click Add. +5. If this is your first time using ngscopeclient, continue reading [the tutorial here](https://www.ngscopeclient.org/manual/Tutorials.html). + + +## Supported models + +Currently supported are these four APIs from Pico Technology: **ps3000a, ps4000a, ps5000a, ps6000a**. +Note that these are all "A APIs" and thus do not support some older and discontinued models. +As of October 2025, there should be a total of **72** different devices supported. + +| ps3000a | ps4000a | ps5000a | ps6000a | +| :---- | :---- | :---- | :---- | +| 3203D | 4444 | 5242A | 6403E | +| 3203D MSO | **4824** | 5242B | 6404E | +| 3204A | 4224A | 5242D | 6405E | +| 3204B | 4424A | 5242D MSO | 6406E | +| 3204 MSO | 4824A | 5243A | 6424E | +| 3204D | | 5243B | 6425E | +| 3204D MSO | | 5243D | 6426E | +| 3205A | | 5243D MSO | 6428E-D | +| 3205B | | 5244A | 6804E | +| 3205 MSO | | 5244B | **6824E** | +| 3205D | | 5244D | | +| 3205D MSO | | 5244D MSO | | +| 3206A | | 5442A | | +| 3206B | | 5442B | | +| 3206 MSO | | 5442D | | +| 3206D | | 5442D MSO | | +| 3206D MSO | | 5443A | | +| 3207A | | 5443B | | +| 3207B | | 5443D | | +| 3403D | | 5443D MSO | | +| 3403D MSO | | 5444A | | +| 3404A | | 5444B | | +| 3404B | | 5444D | | +| 3404D | | **5444D MSO** | | +| 3404D MSO | | | | +| 3405A | | | | +| 3405B | | | | +| 3405D | | | | +| 3405D MSO | | | | +| 3406A | | | | +| 3406B | | | | +| 3406D | | | | +| 3406D MSO | | | | + +Models shown in bold were used for, and tested during, the development of the pico-bridge. + + +## How to compile + +The process to build from source is basically [the same as for ngscopeclient](https://www.ngscopeclient.org/manual/GettingStarted.html), shown here for **Windows**: + +1. + Download and install MSYS2. You can download it from [msys2.org](https://www.msys2.org/) or [github.com/msys2/msys2-installer/releases](https://github.com/msys2/msys2-installer/releases). + The following steps can be done in any MSYS-provided shell (it will start up with a UCRT64 shell after installation). +2. + Install git and the toolchain: + ``` + pacman -S git wget mingw-w64-ucrt-x86_64-cmake mingw-w64-ucrt-x86_64-toolchain + ``` +3. + Install general dependencies: + ``` + pacman -S mingw-w64-ucrt-x86_64-libsigc++ mingw-w64-ucrt-x86_64-yaml-cpp mingw-w64-ucrt-x86_64-glfw mingw-w64-ucrt-x86_64-catch mingw-w64-ucrt-x86_64-hidapi mingw-w64-ucrt-x86_64-libpng + ``` +4. + Install Vulkan dependencies: + ``` + pacman -S mingw-w64-ucrt-x86_64-vulkan-headers mingw-w64-ucrt-x86_64-vulkan-loader mingw-w64-ucrt-x86_64-shaderc mingw-w64-ucrt-x86_64-glslang mingw-w64-ucrt-x86_64-spirv-tools + ``` +5. + Install FFTS: + ``` + pacman -S mingw-w64-ucrt-x86_64-ffts + ``` +6. + Check out the code + ``` + cd ~ + git clone --recursive https://github.com/ngscopeclient/scopehal-pico-bridge + ``` + + **All following steps are to be done in a UCRT64 shell.** + +7. + Build manually: + ``` + cd scopehal-pico-bridge + mkdir build + cd build + cmake .. + ninja -j4 + ``` + If the build fails with a lot of missing files, try adding your MSYS2 bin folder to your $PATH variable, i.e. "C:\msys64\ucrt64\bin" if you installed it to "C:\msys64". +8. + To run scopehal-pico-bridge: + The binary can be found in the build directory, such as $HOME\scopehal-pico-bridge\build\src\ps6000d. diff --git a/src/ps6000d/CMakeLists.txt b/src/ps6000d/CMakeLists.txt index 424223c..6bd14f3 100644 --- a/src/ps6000d/CMakeLists.txt +++ b/src/ps6000d/CMakeLists.txt @@ -22,10 +22,14 @@ if(WIN32) include_directories(${PICO_SDK_PATH}/inc/) elseif(APPLE) include_directories(${PICO_SDK_FRAMEWORK}/Headers/libps3000a) + include_directories(${PICO_SDK_FRAMEWORK}/Headers/libps4000a) + include_directories(${PICO_SDK_FRAMEWORK}/Headers/libps5000a) include_directories(${PICO_SDK_FRAMEWORK}/Headers/libps6000a) else() # Linux specific include paths include_directories(${PICO_SDK_PATH}/include/libps3000a) + include_directories(${PICO_SDK_PATH}/include/libps4000a) + include_directories(${PICO_SDK_PATH}/include/libps5000a) include_directories(${PICO_SDK_PATH}/include/libps6000a) endif() @@ -46,6 +50,8 @@ if(WIN32) log scpi-server-tools ${PICO_SDK_PATH}/lib/ps3000a.lib + ${PICO_SDK_PATH}/lib/ps4000a.lib + ${PICO_SDK_PATH}/lib/ps5000a.lib ${PICO_SDK_PATH}/lib/ps6000a.lib ) elseif(APPLE) @@ -54,7 +60,10 @@ elseif(APPLE) log scpi-server-tools ${PICO_SDK_FRAMEWORK}/Libraries/libps3000a/libps3000a.dylib - ${PICO_SDK_FRAMEWORK}/Libraries/libps6000a/libps6000a.dylib) + ${PICO_SDK_FRAMEWORK}/Libraries/libps3000a/libps4000a.dylib + ${PICO_SDK_FRAMEWORK}/Libraries/libps5000a/libps5000a.dylib + ${PICO_SDK_FRAMEWORK}/Libraries/libps6000a/libps6000a.dylib + ) else() # Linux specific linker target_link_libraries(ps6000d @@ -62,6 +71,8 @@ else() log scpi-server-tools ${PICO_SDK_PATH}/lib/libps3000a.so + ${PICO_SDK_PATH}/lib/libps4000a.so + ${PICO_SDK_PATH}/lib/libps5000a.so ${PICO_SDK_PATH}/lib/libps6000a.so ) endif() diff --git a/src/ps6000d/PicoSCPIServer.cpp b/src/ps6000d/PicoSCPIServer.cpp index b2db928..3b3ee40 100644 --- a/src/ps6000d/PicoSCPIServer.cpp +++ b/src/ps6000d/PicoSCPIServer.cpp @@ -43,6 +43,12 @@ [1|2]D:PRESENT? Returns 1 = MSO pod present, 0 = MSO pod not present + [chan]:BWLIM [freq] + Sets the channel's bandwith limiter to freq in MHz, 0 for full bandwidth. + + [chan]:BWLIM? + Returns the channel's bandwith limiter frequency in MHz, 0 for full bandwidth. + [chan]:COUP [DC1M|AC1M|DC50] Sets channel coupling @@ -146,11 +152,17 @@ map g_channelOn; map g_coupling; map g_range; map g_range_3000a; +map g_range_4000a; +map g_range_5000a; map g_roundedRange; map g_offset; map g_bandwidth; -map g_bandwidth_legacy; +map g_bandwidth_3000a; +map g_bandwidth_4000a; +map g_bandwidth_5000a; size_t g_memDepth = 1000000; +size_t g_scaleValue = 32512; +size_t g_adcBits = 8; int64_t g_sampleInterval = 0; //in fs //Copy of state at timestamp of last arm event @@ -160,6 +172,7 @@ size_t g_captureMemDepth = 0; map g_offsetDuringArm; uint32_t g_timebase = 0; +uint32_t g_sampleRate = 0; bool g_triggerArmed = false; bool g_triggerOneShot = false; @@ -190,6 +203,43 @@ float g_awgRange = 0; float g_awgOffset = 0; bool g_awgOn = false; double g_awgFreq = 1000; +int32_t g_awgBufferSize = 8192; +PS3000A_EXTRA_OPERATIONS g_awgPS3000AOperation = PS3000A_ES_OFF; // Noise and PRBS generation is not a WaveType +PS3000A_WAVE_TYPE g_awgPS3000AWaveType = PS3000A_SINE; // Waveform must be set in ReconfigAWG(), holds the WaveType; +PS4000A_EXTRA_OPERATIONS g_awgPS4000AOperation = PS4000A_ES_OFF; +PS4000A_WAVE_TYPE g_awgPS4000AWaveType = PS4000A_SINE; +PS5000A_EXTRA_OPERATIONS g_awgPS5000AOperation = PS5000A_ES_OFF; +PS5000A_WAVE_TYPE g_awgPS5000AWaveType = PS5000A_SINE; + +//Struct easily allows for adding new models +struct WaveformType +{ + PICO_WAVE_TYPE type6000; + PS3000A_WAVE_TYPE type3000; + PS3000A_EXTRA_OPERATIONS op3000; + PS4000A_WAVE_TYPE type4000; + PS4000A_EXTRA_OPERATIONS op4000; + PS5000A_WAVE_TYPE type5000; + PS5000A_EXTRA_OPERATIONS op5000; +}; +const map g_waveformTypes = +{ + {"SINE", {PICO_SINE, PS3000A_SINE, PS3000A_ES_OFF, PS4000A_SINE, PS4000A_ES_OFF, PS5000A_SINE, PS5000A_ES_OFF}}, + {"SQUARE", {PICO_SQUARE, PS3000A_SQUARE, PS3000A_ES_OFF, PS4000A_SQUARE, PS4000A_ES_OFF, PS5000A_SQUARE, PS5000A_ES_OFF}}, + {"TRIANGLE", {PICO_TRIANGLE, PS3000A_TRIANGLE, PS3000A_ES_OFF, PS4000A_TRIANGLE, PS4000A_ES_OFF, PS5000A_TRIANGLE, PS5000A_ES_OFF}}, + {"RAMP_UP", {PICO_RAMP_UP, PS3000A_RAMP_UP, PS3000A_ES_OFF, PS4000A_RAMP_UP, PS4000A_ES_OFF, PS5000A_RAMP_UP, PS5000A_ES_OFF}}, + {"RAMP_DOWN", {PICO_RAMP_DOWN, PS3000A_RAMP_DOWN, PS3000A_ES_OFF, PS4000A_RAMP_DOWN, PS4000A_ES_OFF, PS5000A_RAMP_DOWN, PS5000A_ES_OFF}}, + {"SINC", {PICO_SINC, PS3000A_SINC, PS3000A_ES_OFF, PS4000A_SINC, PS4000A_ES_OFF, PS5000A_SINC, PS5000A_ES_OFF}}, + {"GAUSSIAN", {PICO_GAUSSIAN, PS3000A_GAUSSIAN, PS3000A_ES_OFF, PS4000A_GAUSSIAN, PS4000A_ES_OFF, PS5000A_GAUSSIAN, PS5000A_ES_OFF}}, + {"HALF_SINE", {PICO_HALF_SINE, PS3000A_HALF_SINE, PS3000A_ES_OFF, PS4000A_HALF_SINE, PS4000A_ES_OFF, PS5000A_HALF_SINE, PS5000A_ES_OFF}}, + {"DC", {PICO_DC_VOLTAGE, PS3000A_DC_VOLTAGE, PS3000A_ES_OFF, PS4000A_DC_VOLTAGE, PS4000A_ES_OFF, PS5000A_DC_VOLTAGE, PS5000A_ES_OFF}}, + {"WHITENOISE", {PICO_WHITENOISE, PS3000A_SINE, PS3000A_WHITENOISE, PS4000A_SINE, PS4000A_WHITENOISE, PS5000A_SINE, PS5000A_WHITENOISE}}, + {"PRBS", {PICO_PRBS, PS3000A_SINE, PS3000A_PRBS, PS4000A_SINE, PS4000A_PRBS, PS5000A_SINE, PS5000A_PRBS }}, + {"ARBITRARY", {PICO_ARBITRARY, PS3000A_MAX_WAVE_TYPES, PS3000A_ES_OFF, PS4000A_MAX_WAVE_TYPES, PS4000A_ES_OFF, PS5000A_MAX_WAVE_TYPES, PS5000A_ES_OFF}} //FIX: PS3000A_MAX_WAVE_TYPES is used as placeholder for arbitrary generation till a better workaround is found +}; + +int16_t* g_arbitraryWaveform; +void GenerateSquareWave(int16_t* &waveform, size_t bufferSize, double dutyCycle, int16_t amplitude = 32767); void ReconfigAWG(); //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -201,6 +251,42 @@ PicoSCPIServer::PicoSCPIServer(ZSOCKET sock) //external trigger is fixed range of -1 to +1V g_roundedRange[PICO_TRIGGER_AUX] = 2; g_offset[PICO_TRIGGER_AUX] = 0; + + //set model dependent AWG buffer size + switch(g_series) + { + case 3: + { + g_awgBufferSize = 32768; + if( (g_model.find("06A") != string::npos) || (g_model.find("06B") != string::npos) ) + g_awgBufferSize = 16384; + if( (g_model.find("05A") != string::npos) || (g_model.find("05B") != string::npos) ) + g_awgBufferSize = 8192; + if( (g_model.find("04A") != string::npos) || (g_model.find("04B") != string::npos) ) + g_awgBufferSize = 8192; + break; + } + case 4: + { + g_awgBufferSize = 16384; + break; + } + case 5: + { + g_awgBufferSize = 32768; + if(g_model.find("42B") != string::npos) + g_awgBufferSize = 16384; + if(g_model.find("44B") != string::npos) + g_awgBufferSize = 49152; + break; + } + case 6: + { + g_awgBufferSize = 40960; + break; + } + } + g_arbitraryWaveform = new int16_t[g_awgBufferSize]; } PicoSCPIServer::~PicoSCPIServer() @@ -210,12 +296,18 @@ PicoSCPIServer::~PicoSCPIServer() //Disable all channels when a client disconnects to put the scope in a "safe" state for(auto& it : g_channelOn) { - switch(g_pico_type) + switch(g_series) { - case PICO3000A: + case 3: ps3000aSetChannel(g_hScope, (PS3000A_CHANNEL)it.first, 0, PS3000A_DC, PS3000A_1V, 0.0f); break; - case PICO6000A: + case 4: + ps4000aSetChannel(g_hScope, (PS4000A_CHANNEL)it.first, 0, PS4000A_DC, PICO_X1_PROBE_1V, 0.0f); + break; + case 5: + ps5000aSetChannel(g_hScope, (PS5000A_CHANNEL)it.first, 0, PS5000A_DC, PS5000A_1V, 0.0f); + break; + case 6: ps6000aSetChannelOff(g_hScope, (PICO_CHANNEL)it.first); break; } @@ -226,12 +318,15 @@ PicoSCPIServer::~PicoSCPIServer() for(int i=0; i<2; i++) { - switch(g_pico_type) + switch(g_series) { - case PICO3000A: + case 3: ps3000aSetDigitalPort(g_hScope, (PS3000A_DIGITAL_PORT)(PICO_PORT0 + i), 0, 0); break; - case PICO6000A: + case 5: + ps5000aSetDigitalPort(g_hScope, (PS5000A_CHANNEL)(PICO_PORT0 + i), 0, 0); + break; + case 6: ps6000aSetDigitalPortOff(g_hScope, (PICO_CHANNEL)(PICO_PORT0 + i)); break; } @@ -272,30 +367,25 @@ bool PicoSCPIServer::OnQuery( { lock_guard lock(g_mutex); - switch(g_pico_type) + switch(g_series) { - case PICO3000A: + case 3: + case 5: { - //There's no API to test for presence of a MSO pod without trying to enable it. - //If no pod is present, this call will return an error. - PICO_CHANNEL podId = (PICO_CHANNEL)(PICO_PORT0 + channelId); - auto status = ps3000aSetDigitalPort(g_hScope, (PS3000A_DIGITAL_PORT)podId, 1, g_msoPodThreshold[channelId][0]); - if(status == PICO_OK) + //All MSO models have two pods. + if(g_model.find("MSO") != string::npos) { - // The pod is here. If we don't need it on, shut it back off - if(!g_msoPodEnabled[channelId]) - ps3000aSetDigitalPort(g_hScope, (PS3000A_DIGITAL_PORT)podId, 0, 0); - SendReply("1"); } else { SendReply("0"); } + break; } break; - case PICO6000A: + case 6: { //There's no API to test for presence of a MSO pod without trying to enable it. //If no pod is present, this call will return PICO_NO_MSO_POD_CONNECTED. @@ -321,8 +411,51 @@ bool PicoSCPIServer::OnQuery( } } break; + + default: + { + SendReply("0"); + } + } + } + + else if(cmd == "BWLIM") + { + lock_guard lock(g_mutex); + string ret = "0"; + + switch(g_series) + { + case 3: + { + if(g_bandwidth_3000a[channelId] == PS3000A_BW_20MHZ) + ret = "20"; + break; + } + case 4: + { + if(g_bandwidth_4000a[channelId] == PS4000A_BW_1MHZ) + ret = "1"; + break; + } + case 5: + { + if(g_bandwidth_5000a[channelId] == PS5000A_BW_20MHZ) + ret = "20"; + break; + } + case 6: + { + if(g_bandwidth[channelId] == PICO_BW_20MHZ) + ret = "20"; + else if(g_bandwidth[channelId] == PICO_BW_200MHZ) + ret = "200"; + break; + } } + SendReply(ret); } + else { LogDebug("Unrecognized query received: %s\n", line.c_str()); @@ -358,34 +491,162 @@ size_t PicoSCPIServer::GetAnalogChannelCount() vector PicoSCPIServer::GetSampleRates() { vector rates; - + vector vec; lock_guard lock(g_mutex); - //Enumerate timebases - //Don't report every single legal timebase as there's way too many, the list box would be huge! - //Report the first nine, then go to larger steps - size_t vec[] = + switch(g_series) { - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, - 14, 29, 54, 104, 129, 254, 504, 629, 1254, 2504, 3129, 5004, 6254, 15629, 31254, - 62504, 156254, 312504, 625004, 1562504 - }; + case 3: + { + if( (g_model[1]=='2') and (g_model[4]=='A' or g_model[4]=='B') ) + { + //PicoScope 3000A and 3000B Series 2-Channel USB 2.0 Oscilloscopes + vec = + { + 0,1,2,3,4,6,7,10,12,22,27,42,52,82,102,202,252,402,502,627,802,1002,2002,2502,4002,5002,6252,8002,10002,20002,25002,40002,50002,62502 + }; + } + if( (g_model.find("MSO") != string::npos) and (g_model[4]!='D') ) + { + //PicoScope 3000 Series USB 2.0 MSOs + vec = + { + 0,1,2,3,5,6,9,11,17,21,41,51,81,101,126,161,201,401,501,801,1001,1251,1601,2001,4001,5001,8001,10001,12501,16001,20001,40001,50001,80001,100001,125001 + }; + } + else + { + //PicoScope 3000A and 3000B Series 4-Channel USB 2.0 Oscilloscopes + //PicoScope 3207A and 3207B USB 3.0 Oscilloscopes + //PicoScope 3000D Series USB 3.0 Oscilloscopes and MSOs + vec = + { + 0,1,2,3,4,6,7,10,12,18,22,42,52,82,102,127,162,202,402,502,802,1002,1252,1602,2002,4002,5002,8002,10002,12502,16002,20002,40002,50002,80002,100002,125002 + }; + } + } + break; + + case 4: + { + if(g_model.find("4444") != string::npos) + { + //PicoScope 4444 + vec = + { + 0,1,2,3,4,6,7,12,22,27,42,52,102,127,202,252,402,502,627,1002,1252,2002,2502,4002,5002,6252,10002,12502,20002,25002,40002,50002 + }; + } + else + { + //PicoScope 4824 and 4000A Series + vec = + { + 0,1,3,7,9,15,19,31,39,63,79,99,159,199,319,399,639,799,999,1599,1999,3199,3999,6399,7999,9999,15999,19999,31999,39999,63999,79999 + }; + } + + } + break; + + case 5: + { + switch(g_adcBits) + { + case 8: + { + vec = + { + 0,1,2,3,4,6,7,10,12,18,22,42,52,82,102,127,162,202,402,502,802,1002,1252,1602,2002,4002,5002,8002,10002,12502,16002,20002,40002,50002,80002,100002,125002 + }; + break; + } + + case 12: + { + vec = + { + 1,2,3,4,5,7,8,11,13,23,28,43,53,83,103,203,253,403,503,628,803,1003,2003,2503,4003,5003,6253,8003,10003,20003,25003,40003,50003,62503 + }; + break; + } + + case 14: + { + vec = + { + 3,4,6,7,10,12,18,22,42,52,82,102,127,162,202,402,502,802,1002,1252,1602,2002,4002,5002,8002,10002,12502,16002,20002,40002,50002,80002,100002,125002 + }; + break; + } + + case 15: + { + vec = + { + 3,4,6,7,10,12,18,22,42,52,82,102,127,162,202,402,502,802,1002,1252,1602,2002,4002,5002,8002,10002,12502,16002,20002,40002,50002,80002,100002,125002 + }; + break; + } + + case 16: + { + vec = + { + 4,5,7,8,11,13,23,28,43,53,83,103,203,253,403,503,628,803,1003,2003,2503,4003,5003,6253,8003,10003,20003,25003,40003,50003,62503 + }; + } + } + } + break; + + case 6: + { + //PicoScope 6428E-D + if(g_model[3] == '8') + { + vec = + { + 0,1,2,3,4,5,6,7,10,15,25,30,55,105,130,205,255,505,630,1005,1255,2005,2505,5005,6255,10005,12505,15630,20005,25005,50005,62505,100005,125005,156255 + }; + } + //PicoScope 6000E Series except the PicoScope 6428E-D + else + { + vec = + { + 0,1,2,3,4,5,6,9,14,24,29,54,66,5,104,129,204,254,504,629,1004,1254,2004,2504,5004,6254,10004,12504,15629,20004,25004,50004,62504,100004,125004,156254 + }; + } + } + } + for(auto i : vec) { double intervalNs; - int32_t intervalNs_int; + float intervalNs_f; uint64_t maxSamples; int32_t maxSamples_int; PICO_STATUS status = PICO_RESERVED_1; - switch(g_pico_type) + switch(g_series) { - case PICO3000A: - status = ps3000aGetTimebase(g_hScope, i, 1, &intervalNs_int, 0, &maxSamples_int, 0); - intervalNs = intervalNs_int; + case 3: + status = ps3000aGetTimebase2(g_hScope, i, 1, &intervalNs_f, 1, &maxSamples_int, 0); + maxSamples = maxSamples_int; + intervalNs = intervalNs_f; + break; + case 4: + status = ps4000aGetTimebase2(g_hScope, i, 1, &intervalNs_f, &maxSamples_int, 0); maxSamples = maxSamples_int; + intervalNs = intervalNs_f; break; - case PICO6000A: + case 5: + status = ps5000aGetTimebase2(g_hScope, i, 1, &intervalNs_f, &maxSamples_int, 0); + maxSamples = maxSamples_int; + intervalNs = intervalNs_f; + break; + case 6: status = ps6000aGetTimebase(g_hScope, i, 1, &intervalNs, &maxSamples, 0); break; } @@ -395,7 +656,7 @@ vector PicoSCPIServer::GetSampleRates() size_t intervalFs = intervalNs * 1e6f; rates.push_back(FS_PER_SECOND / intervalFs); } - else if(PICO_INVALID_TIMEBASE == status) + else if( (PICO_INVALID_TIMEBASE == status) || (PICO_INVALID_CHANNEL == status) ) { //Requested timebase not possible //This is common and harmless if we ask for e.g. timebase 0 when too many channels are active. @@ -404,7 +665,6 @@ vector PicoSCPIServer::GetSampleRates() else LogWarning("GetTimebase failed, code %d / 0x%x\n", status, status); } - return rates; } @@ -414,7 +674,7 @@ vector PicoSCPIServer::GetSampleDepths() lock_guard lock(g_mutex); double intervalNs; - int32_t intervalNs_int; + float intervalNs_f; uint64_t maxSamples; int32_t maxSamples_int; @@ -424,13 +684,24 @@ vector PicoSCPIServer::GetSampleDepths() //Ask for max memory depth at timebase number 10 //We cannot use the first few timebases because those are sometimes not available depending on channel count etc int ntimebase = 10; - switch(g_pico_type) + switch(g_series) { - case PICO3000A: - status = ps3000aGetTimebase(g_hScope, ntimebase, 1, &intervalNs_int, 0, &maxSamples_int, 0); + case 3: + status = ps3000aGetTimebase2(g_hScope, ntimebase, 1, &intervalNs_f, 1, &maxSamples_int, 0); + maxSamples = maxSamples_int; + intervalNs = intervalNs_f; + break; + case 4: + status = ps4000aGetTimebase2(g_hScope, ntimebase, 1, &intervalNs_f, &maxSamples_int, 0); maxSamples = maxSamples_int; + intervalNs = intervalNs_f; break; - case PICO6000A: + case 5: + status = ps5000aGetTimebase2(g_hScope, ntimebase, 1, &intervalNs_f, &maxSamples_int, 0); + maxSamples = maxSamples_int; + intervalNs = intervalNs_f; + break; + case 6: status = ps6000aGetTimebase(g_hScope, ntimebase, 1, &intervalNs, &maxSamples, 0); break; } @@ -438,9 +709,9 @@ vector PicoSCPIServer::GetSampleDepths() if(PICO_OK == status) { //Seems like there's no restrictions on actual memory depth other than an upper bound. - //To keep things simple, report 1-2-5 series from 10K samples up to the actual max depth + //To keep things simple, report 1-2-5 series from 1K samples up to the actual max depth - for(size_t base = 10000; base < maxSamples; base *= 10) + for(size_t base = 1000; base < maxSamples; base *= 10) { const size_t muls[] = {1, 2, 5}; for(auto m : muls) @@ -477,8 +748,108 @@ bool PicoSCPIServer::OnCommand( else if(cmd == "STOP") { lock_guard lock(g_mutex); - g_awgOn = false; - ReconfigAWG(); + if(g_series == 3) + { + /* + * Special handling for Pico 3000/4000/5000 series oscilloscopes: + * Since they lack a dedicated stop command for signal generation, + * we achieve this by: + * 1. Temporarily setting AWG amplitude and offset to zero + * 2. Switching to software trigger mode + * 3. Restoring original AWG settings + * + * This ensures clean signal termination without residual voltage levels. + */ + float tempRange = g_awgRange; + float tempOffset = g_awgOffset; + g_awgRange = 0; + g_awgOffset = 0; + ReconfigAWG(); + + auto status = ps3000aSetSigGenPropertiesBuiltIn( + g_hScope, + g_awgFreq, + g_awgFreq, + 0, + 0, + PS3000A_SWEEP_TYPE (0), + 1, + 0, + PS3000A_SIGGEN_RISING, + PS3000A_SIGGEN_SOFT_TRIG, + 0 + ); + + if(status != PICO_OK) + LogError("ps3000aSetSigGenPropertiesBuiltIn failed, code 0x%x \n", status); + + g_awgRange = tempRange; + g_awgOffset = tempOffset; + g_awgOn = false; + } + else if(g_series == 4) + { + float tempRange = g_awgRange; + float tempOffset = g_awgOffset; + g_awgRange = 0; + g_awgOffset = 0; + ReconfigAWG(); + + auto status = ps4000aSetSigGenPropertiesBuiltIn( + g_hScope, + g_awgFreq, + g_awgFreq, + 0, + 0, + PS4000A_SWEEP_TYPE (0), + 1, + 0, + PS4000A_SIGGEN_RISING, + PS4000A_SIGGEN_SOFT_TRIG, + 0 + ); + + if(status != PICO_OK) + LogError("ps4000aSetSigGenPropertiesBuiltIn failed, code 0x%x \n", status); + + g_awgRange = tempRange; + g_awgOffset = tempOffset; + g_awgOn = false; + } + else if(g_series == 5) + { + float tempRange = g_awgRange; + float tempOffset = g_awgOffset; + g_awgRange = 0; + g_awgOffset = 0; + ReconfigAWG(); + + auto status = ps5000aSetSigGenPropertiesBuiltIn( + g_hScope, + g_awgFreq, + g_awgFreq, + 0, + 0, + PS5000A_SWEEP_TYPE (0), + 1, + 0, + PS5000A_SIGGEN_RISING, + PS5000A_SIGGEN_SOFT_TRIG, + 0 + ); + + if(status != PICO_OK) + LogError("ps5000aSetSigGenPropertiesBuiltIn failed, code 0x%x \n", status); + + g_awgRange = tempRange; + g_awgOffset = tempOffset; + g_awgOn = false; + } + else + { + g_awgOn = false; + ReconfigAWG(); + } } else if(args.size() == 1) @@ -487,21 +858,21 @@ bool PicoSCPIServer::OnCommand( { lock_guard lock(g_mutex); g_awgFreq = stof(args[0]); - switch(g_pico_type) + //Frequency must not be zero + if(g_awgFreq<1e-3) + g_awgFreq = 1; + + switch(g_series) { - case PICO3000A: + case 3: + case 4: + case 5: { - /* TODO PICO3000A FREQ */ - LogError("PICO3000A FREQ TODO code\n"); - /* - auto status = ps6000aSigGenFrequency(g_hScope, g_awgFreq); - if(status != PICO_OK) - LogError("ps6000aSigGenFrequency failed, code 0x%x (freq=%f)\n", status, g_awgFreq); - */ + //handled by ReconfigAWG() } break; - case PICO6000A: + case 6: { auto status = ps6000aSigGenFrequency(g_hScope, g_awgFreq); if(status != PICO_OK) @@ -517,23 +888,42 @@ bool PicoSCPIServer::OnCommand( lock_guard lock(g_mutex); auto duty = stof(args[0]) * 100; - switch(g_pico_type) + switch(g_series) { - case PICO3000A: + case 3: { - /* TODO PICO3000A DUTY */ - LogError("PICO3000A DUTY TODO code\n"); - /* - auto status = ps6000aSigGenWaveformDutyCycle(g_hScope, duty); - if(status != PICO_OK) - LogError("ps6000aSigGenWaveformDutyCycle failed, code 0x%x\n", status); + /* DutyCycle of square wave can not be controlled in ps3000a built in generator, + Must be implemented via Arbitrary*/ + if( g_awgPS3000AWaveType == PS3000A_SQUARE ) + GenerateSquareWave(g_arbitraryWaveform, g_awgBufferSize, (double) duty); + else + LogError("PICO3000A DUTY TODO code\n"); + } + break; - ReconfigAWG(); - */ + case 4: + { + /* DutyCycle of square wave can not be controlled in ps4000a built in generator, + Must be implemented via Arbitrary*/ + if( g_awgPS4000AWaveType == PS4000A_SQUARE ) + GenerateSquareWave(g_arbitraryWaveform, g_awgBufferSize, (double) duty); + else + LogError("PICO4000A DUTY TODO code\n"); } break; - case PICO6000A: + case 5: + { + /* DutyCycle of square wave can not be controlled in ps3000a built in generator, + Must be implemented via Arbitrary*/ + if( g_awgPS5000AWaveType == PS5000A_SQUARE ) + GenerateSquareWave(g_arbitraryWaveform, g_awgBufferSize, (double) duty); + else + LogError("PICO5000A DUTY TODO code\n"); + } + break; + + case 6: { auto status = ps6000aSigGenWaveformDutyCycle(g_hScope, duty); if(status != PICO_OK) @@ -543,6 +933,7 @@ bool PicoSCPIServer::OnCommand( } break; } + ReconfigAWG(); } else if(cmd == "OFFS") @@ -565,61 +956,102 @@ bool PicoSCPIServer::OnCommand( { lock_guard lock(g_mutex); - PICO_WAVE_TYPE type = PICO_SINE; - if(args[0] == "SINE") - type = PICO_SINE; - else if(args[0] == "SQUARE") - type = PICO_SQUARE; - else if(args[0] == "TRIANGLE") - type = PICO_TRIANGLE; - else if(args[0] == "RAMP_UP") - type = PICO_RAMP_UP; - else if(args[0] == "RAMP_DOWN") - type = PICO_RAMP_DOWN; - else if(args[0] == "SINC") - type = PICO_SINC; - else if(args[0] == "GAUSSIAN") - type = PICO_GAUSSIAN; - else if(args[0] == "HALF_SINE") - type = PICO_HALF_SINE; - else if(args[0] == "DC") - type = PICO_DC_VOLTAGE; - //PICO_PWM is in header file but doesn't seem to be implemented - else if(args[0] == "WHITENOISE") - type = PICO_WHITENOISE; - else if(args[0] == "PRBS") //custom 42-bit LFSR, not standard polynomial - type = PICO_PRBS; - else if(args[0] == "ARBITRARY") //TODO: specify arb buffer - type = PICO_ARBITRARY; - - switch(g_pico_type) + auto waveform = g_waveformTypes.find(args[0]); + if(waveform == g_waveformTypes.end()) { - case PICO3000A: + LogError("Invalid waveform type: %s\n", args[0].c_str()); + return true; + } + + switch(g_series) + { + case 3: { - /* TODO PICO3000A SHAPE */ - LogError("PICO3000A SHAPE TODO code\n"); - /* - //Set waveform type - auto status = ps6000aSigGenWaveform(g_hScope, type, NULL, 0); - if(PICO_OK != status) - LogError("ps6000aSigGenWaveform failed, code 0x%x\n", status); + if( ( (args[0] == "WHITENOISE") || (args[0] == "RPBS") ) + && (g_model[4] == 'A' ) ) + { + LogError("Noise/RPBS generation not supported by 3xxxA Models\n"); + return true; + } + if( (g_awgPS3000AWaveType == PS3000A_SQUARE) ) + { + GenerateSquareWave(g_arbitraryWaveform, g_awgBufferSize, 50); + } + g_awgPS3000AWaveType = waveform->second.type3000; + g_awgPS3000AOperation = waveform->second.op3000; + + + if(args[0] == "ARBITRARY") + { + //TODO: find a more flexible way to specify arb buffer + LogError("PICO3000A ARBITRARY TODO code\n"); + } + } + break; - ReconfigAWG(); - */ + case 4: + { + if( (args[0] == "RPBS") ) + { + LogError("RPBS generation not supported by 4000 series\n"); + return true; + } + if( (g_awgPS4000AWaveType == PS4000A_SQUARE) ) + { + GenerateSquareWave(g_arbitraryWaveform, g_awgBufferSize, 50); + } + g_awgPS4000AWaveType = waveform->second.type4000; + g_awgPS4000AOperation = waveform->second.op4000; + + + if(args[0] == "ARBITRARY") + { + //TODO: find a more flexible way to specify arb buffer + LogError("PICO4000A ARBITRARY TODO code\n"); + } } break; - case PICO6000A: + case 5: { - //Set waveform type - auto status = ps6000aSigGenWaveform(g_hScope, type, NULL, 0); + if( (args[0] == "RPBS") ) + { + LogError("RPBS generation not supported by 5000 series\n"); + return true; + } + if( (g_awgPS5000AWaveType == PS5000A_SQUARE) ) + { + GenerateSquareWave(g_arbitraryWaveform, g_awgBufferSize, 50); + } + g_awgPS5000AWaveType = waveform->second.type5000; + g_awgPS5000AOperation = waveform->second.op5000; + + + if(args[0] == "ARBITRARY") + { + //TODO: find a more flexible way to specify arb buffer + LogError("PICO5000A ARBITRARY TODO code\n"); + } + } + break; + + case 6: + { + auto status = ps6000aSigGenWaveform(g_hScope, waveform->second.type6000, NULL, 0); if(PICO_OK != status) LogError("ps6000aSigGenWaveform failed, code 0x%x\n", status); - - ReconfigAWG(); + ReconfigAWG(); + + if(args[0] == "ARBITRARY") + { + //TODO: ReconfigAWG() can handle this already, must only fill the buffer + LogError("PICO6000A ARBITRARY TODO code\n"); + } } break; } + + ReconfigAWG(); } else LogError("Unrecognized AWG command %s\n", line.c_str()); @@ -633,39 +1065,175 @@ bool PicoSCPIServer::OnCommand( else if( (cmd == "BITS") && (args.size() == 1) ) { lock_guard lock(g_mutex); + switch(g_series) + { + case 3: + { + g_adcBits = 8; + return false; + } + break; - if(g_pico_type != PICO6000A) - return false; + case 4: + { + if(g_model.find("4444") != string::npos) + { + ps4000aStop(g_hScope); - ps6000aStop(g_hScope); + //Changing the ADC resolution necessitates reallocation of the buffers + //due to different memory usage. + g_memDepthChanged = true; - //Even though we didn't actually change memory, apparently calling ps6000aSetDeviceResolution - //will invalidate the existing buffers and make ps6000aGetValues() fail with PICO_BUFFERS_NOT_SET. - g_memDepthChanged = true; + int bits = stoi(args[0]); + switch(bits) + { + case 12: + g_adcBits = bits; + ps4000aSetDeviceResolution(g_hScope, PS4000A_DR_12BIT); + break; + + case 14: + g_adcBits = bits; + ps4000aSetDeviceResolution(g_hScope, PS4000A_DR_14BIT); + break; + + default: + LogError("User requested invalid resolution (%d bits)\n", bits); + } - int bits = stoi(args[0]); - switch(bits) - { - case 8: - ps6000aSetDeviceResolution(g_hScope, PICO_DR_8BIT); - break; + if(g_triggerArmed) + StartCapture(false); + //update all active channels + for(size_t i=0; i(subject[0] - 'A'), g_numChannels); + //channelIsDigital = false; + } + lock_guard lock(g_mutex); + + int freq_mhz = stoi(args[0]); + SetChannelBandwidthLimiter(channelId, freq_mhz); } - //TODO: bandwidth limiter + + else if( (cmd == "RANGE") ) + { + //This will be called when digital channels are on the same View with analog channels and voltage range is changed. + //Just do nothing here to avoid spamming the debug log with Unrecognized command received. + return false; + } + else { LogDebug("Unrecognized command received: %s\n", line.c_str()); @@ -680,52 +1248,246 @@ bool PicoSCPIServer::OnCommand( return true; } +void PicoSCPIServer::SetChannelBandwidthLimiter(size_t chan, unsigned int limit_mhz) +{ + + switch(g_series) + { + case 3: + { + if(limit_mhz == 20) + g_bandwidth_3000a[chan] = PS3000A_BW_20MHZ; + else + g_bandwidth_3000a[chan] = PS3000A_BW_FULL; + break; + } + case 4: + { + //if(limit_mhz == 20000) + // g_bandwidth_4000a[chan] = PS4000A_BW_20KHZ; + //else if(limit_mhz == 100000) + // g_bandwidth_4000a[chan] = PS4000A_BW_100KHZ; + if(limit_mhz == 1) + g_bandwidth_4000a[chan] = PS4000A_BW_1MHZ; + else + g_bandwidth_4000a[chan] = PS4000A_BW_FULL; + break; + } + case 5: + { + if(limit_mhz == 20) + g_bandwidth_5000a[chan] = PS5000A_BW_20MHZ; + else + g_bandwidth_5000a[chan] = PS5000A_BW_FULL; + break; + } + case 6: + { + if(limit_mhz == 20) + g_bandwidth[chan] = PICO_BW_20MHZ; + else if(limit_mhz == 200) + g_bandwidth[chan] = PICO_BW_200MHZ; + else + g_bandwidth[chan] = PICO_BW_FULL; + break; + } + } + + UpdateChannel(chan); +} + /** @brief Reconfigures the function generator */ void PicoSCPIServer::ReconfigAWG() { - // TODO PS3000A - switch(g_pico_type) + double freq = g_awgFreq; + double inc = 0; + double dwell = 0; + float tempRange = g_awgRange; + float tempOffset = g_awgOffset; + if(!g_awgOn) { - case PICO3000A: + tempRange = 0; + tempOffset = 0; + } + + switch(g_series) + { + case 3: { - /* TODO PICO3000A ReconfigAWG */ - LogError("PICO3000A ReconfigAWG TODO code\n"); - /* - auto status = ps6000aSigGenRange(g_hScope, g_awgRange, g_awgOffset); - if(PICO_OK != status) - LogError("ps6000aSigGenRange failed, code 0x%x\n", status); + Stop(); // Need to stop acquisition when setting the AWG to avoid "PICO_BUSY" errors + if(g_awgPS3000AWaveType == PS3000A_SQUARE || g_awgPS3000AWaveType == PS3000A_MAX_WAVE_TYPES) + { + uint32_t delta= 0; + auto status = ps3000aSigGenFrequencyToPhase(g_hScope, g_awgFreq, PS3000A_SINGLE, g_awgBufferSize, &delta); + if(status != PICO_OK) + LogError("ps3000aSigGenFrequencyToPhase failed, code 0x%x\n", status); + status = ps3000aSetSigGenArbitrary( + g_hScope, + tempOffset*1e6, + tempRange*1e6*2, + delta, + delta, + 0, + 0, + g_arbitraryWaveform, + g_awgBufferSize, + PS3000A_UP, // sweepType + PS3000A_ES_OFF, // operation + PS3000A_SINGLE, // indexMode + PS3000A_SHOT_SWEEP_TRIGGER_CONTINUOUS_RUN, + 0, + PS3000A_SIGGEN_RISING, + PS3000A_SIGGEN_NONE, + 0); + if(status != PICO_OK) + LogError("ps3000aSetSigGenArbitrary failed, code 0x%x\n", status); + } + else + { + auto status = ps3000aSetSigGenBuiltInV2( + g_hScope, + tempOffset*1e6, //Offset Voltage in µV + tempRange *1e6*2, // Peak to Peak Range in µV + g_awgPS3000AWaveType, + freq, + freq, + inc, + dwell, + PS3000A_UP, + g_awgPS3000AOperation, + PS3000A_SHOT_SWEEP_TRIGGER_CONTINUOUS_RUN, //run forever + 0, //dont use sweeps + PS3000A_SIGGEN_RISING, + PS3000A_SIGGEN_NONE, + 0); // Tigger level (-32767 to 32767 -> -5 to 5 V) + if(PICO_OK != status) + LogError("ps3000aSetSigGenBuiltInV2 failed, code 0x%x\n", status); + } + if(g_triggerArmed) + StartCapture(false); + } + break; - double freq = g_awgFreq; - double inc = 0; - double dwell = 0; - status = ps6000aSigGenApply( - g_hScope, - g_awgOn, - false, //sweep enable - false, //trigger enable - true, //automatic DDS sample frequency - false, //do not override clock and prescale - &freq, - &freq, - &inc, - &dwell); - if(PICO_OK != status) - LogError("ps6000aSigGenApply failed, code 0x%x\n", status); - */ + case 4: + { + Stop(); // Need to stop acquisition when setting the AWG to avoid "PICO_BUSY" errors + if(g_awgPS4000AWaveType == PS4000A_SQUARE || g_awgPS4000AWaveType == PS4000A_MAX_WAVE_TYPES) + { + uint32_t delta= 0; + auto status = ps4000aSigGenFrequencyToPhase(g_hScope, g_awgFreq, PS4000A_SINGLE, g_awgBufferSize, &delta); + if(status != PICO_OK) + LogError("ps3000aSigGenFrequencyToPhase failed, code 0x%x\n", status); + status = ps4000aSetSigGenArbitrary( + g_hScope, + tempOffset*1e6, + tempRange*1e6*2, + delta, + delta, + 0, + 0, + g_arbitraryWaveform, + g_awgBufferSize, + PS4000A_UP, // sweepType + PS4000A_ES_OFF, // operation + PS4000A_SINGLE, // indexMode + PS3000A_SHOT_SWEEP_TRIGGER_CONTINUOUS_RUN, + 0, + PS4000A_SIGGEN_RISING, + PS4000A_SIGGEN_NONE, + 0); + if(status != PICO_OK) + LogError("ps4000aSetSigGenArbitrary failed, code 0x%x\n", status); + } + else + { + auto status = ps4000aSetSigGenBuiltInV2( + g_hScope, + tempOffset*1e6, //Offset Voltage in µV + tempRange *1e6*2, // Peak to Peak Range in µV + g_awgPS4000AWaveType, + freq, + freq, + inc, + dwell, + PS4000A_UP, + g_awgPS4000AOperation, + PS3000A_SHOT_SWEEP_TRIGGER_CONTINUOUS_RUN, //run forever + 0, //dont use sweeps + PS4000A_SIGGEN_RISING, + PS4000A_SIGGEN_NONE, + 0); // Tigger level (-32767 to 32767 -> -5 to 5 V) + if(PICO_OK != status) + LogError("ps4000aSetSigGenBuiltInV2 failed, code 0x%x\n", status); + } + if(g_triggerArmed) + StartCapture(false); + } + break; + + case 5: + { + Stop(); // Need to stop acquisition when setting the AWG to avoid "PICO_BUSY" errors + if(g_awgPS5000AWaveType == PS5000A_SQUARE || g_awgPS5000AWaveType == PS5000A_MAX_WAVE_TYPES) + { + uint32_t delta= 0; + auto status = ps5000aSigGenFrequencyToPhase(g_hScope, g_awgFreq, PS5000A_SINGLE, g_awgBufferSize, &delta); + if(status != PICO_OK) + LogError("ps5000aSigGenFrequencyToPhase failed, code 0x%x\n", status); + status = ps5000aSetSigGenArbitrary( + g_hScope, + tempOffset*1e6, + tempRange*1e6*2, + delta, + delta, + 0, + 0, + g_arbitraryWaveform, + g_awgBufferSize, + PS5000A_UP, // sweepType + PS5000A_ES_OFF, // operation + PS5000A_SINGLE, // indexMode + PS3000A_SHOT_SWEEP_TRIGGER_CONTINUOUS_RUN, + 0, + PS5000A_SIGGEN_RISING, + PS5000A_SIGGEN_NONE, + 0); + if(status != PICO_OK) + LogError("ps5000aSetSigGenArbitrary failed, code 0x%x\n", status); + } + else + { + auto status = ps5000aSetSigGenBuiltInV2( + g_hScope, + tempOffset*1e6, //Offset Voltage in µV + tempRange *1e6*2, // Peak to Peak Range in µV + g_awgPS5000AWaveType, + freq, + freq, + inc, + dwell, + PS5000A_UP, + g_awgPS5000AOperation, + PS3000A_SHOT_SWEEP_TRIGGER_CONTINUOUS_RUN, //run forever + 0, //dont use sweeps + PS5000A_SIGGEN_RISING, + PS5000A_SIGGEN_NONE, + 0); // Tigger level (-32767 to 32767 -> -5 to 5 V) + if(PICO_OK != status) + LogError("ps5000aSetSigGenBuiltInV2 failed, code 0x%x\n", status); + } + if(g_triggerArmed) + StartCapture(false); } break; - case PICO6000A: + case 6: { auto status = ps6000aSigGenRange(g_hScope, g_awgRange, g_awgOffset); if(PICO_OK != status) LogError("ps6000aSigGenRange failed, code 0x%x\n", status); - double freq = g_awgFreq; - double inc = 0; - double dwell = 0; status = ps6000aSigGenApply( g_hScope, g_awgOn, @@ -870,19 +1632,29 @@ void PicoSCPIServer::SetChannelEnabled(size_t chIndex, bool enabled) if(enabled) { - switch(g_pico_type) + switch(g_series) { - case PICO3000A: + case 3: { auto status = ps3000aSetDigitalPort(g_hScope, (PS3000A_DIGITAL_PORT)podId, 1, g_msoPodThreshold[podIndex][0]); if(status != PICO_OK) - LogError("ps3000aSetDigitalPort failed with code %x\n", status); + LogError("ps3000aSetDigitalPort to on failed with code %x\n", status); + else + g_msoPodEnabled[podIndex] = true; + } + break; + + case 5: + { + auto status = ps5000aSetDigitalPort(g_hScope, (PS5000A_CHANNEL)podId, 1, g_msoPodThreshold[podIndex][0]); + if(status != PICO_OK) + LogError("ps5000aSetDigitalPort to on failed with code %x\n", status); else g_msoPodEnabled[podIndex] = true; } break; - case PICO6000A: + case 6: { auto status = ps6000aSetDigitalPortOn( g_hScope, @@ -900,19 +1672,29 @@ void PicoSCPIServer::SetChannelEnabled(size_t chIndex, bool enabled) } else { - switch(g_pico_type) + switch(g_series) { - case PICO3000A: + case 3: { auto status = ps3000aSetDigitalPort(g_hScope, (PS3000A_DIGITAL_PORT)podId, 0, 0); if(status != PICO_OK) - LogError("ps3000aSetDigitalPort failed with code %x\n", status); + LogError("ps3000aSetDigitalPort to off failed with code %x\n", status); + else + g_msoPodEnabled[podIndex] = false; + } + break; + + case 5: + { + auto status = ps5000aSetDigitalPort(g_hScope, (PS5000A_CHANNEL)podId, 0, 0); + if(status != PICO_OK) + LogError("ps5000aSetDigitalPort to off failed with code %x\n", status); else g_msoPodEnabled[podIndex] = false; } break; - case PICO6000A: + case 6: { auto status = ps6000aSetDigitalPortOff(g_hScope, podId); if(status != PICO_OK) @@ -933,123 +1715,321 @@ void PicoSCPIServer::SetChannelEnabled(size_t chIndex, bool enabled) //We need to allocate new buffers for this channel g_memDepthChanged = true; -} - -void PicoSCPIServer::SetAnalogCoupling(size_t chIndex, const std::string& coupling) -{ - lock_guard lock(g_mutex); - int channelId = chIndex & 0xff; - - if(coupling == "DC1M") - g_coupling[channelId] = PICO_DC; - else if(coupling == "AC1M") - g_coupling[channelId] = PICO_AC; - else if(coupling == "DC50") - g_coupling[channelId] = PICO_DC_50OHM; - - UpdateChannel(channelId); -} - -void PicoSCPIServer::SetAnalogRange(size_t chIndex, double range_V) -{ - lock_guard lock(g_mutex); - size_t channelId = chIndex & 0xff; - auto range = range_V; - - //If 50 ohm coupling, cap hardware voltage range to 5V - if(g_coupling[channelId] == PICO_DC_50OHM) - range = min(range, 5.0); - - if(range > 100 && g_pico_type == PICO6000A) - { - g_range[channelId] = PICO_X1_PROBE_200V; - g_roundedRange[channelId] = 200; - } - else if(range > 50 && g_pico_type == PICO6000A) - { - g_range[channelId] = PICO_X1_PROBE_100V; - g_roundedRange[channelId] = 100; - } - else if(range > 20) - { - g_range[channelId] = PICO_X1_PROBE_50V; - g_range_3000a[channelId] = PS3000A_50V; - g_roundedRange[channelId] = 50; - } - else if(range > 10) - { - g_range[channelId] = PICO_X1_PROBE_20V; - g_range_3000a[channelId] = PS3000A_20V; - g_roundedRange[channelId] = 20; - } - else if(range > 5) - { - g_range[channelId] = PICO_X1_PROBE_10V; - g_range_3000a[channelId] = PS3000A_10V; - g_roundedRange[channelId] = 10; - } - else if(range > 2) - { - g_range[channelId] = PICO_X1_PROBE_5V; - g_range_3000a[channelId] = PS3000A_5V; - g_roundedRange[channelId] = 5; - } - else if(range > 1) - { - g_range[channelId] = PICO_X1_PROBE_2V; - g_range_3000a[channelId] = PS3000A_2V; - g_roundedRange[channelId] = 2; - } - else if(range > 0.5) - { - g_range[channelId] = PICO_X1_PROBE_1V; - g_range_3000a[channelId] = PS3000A_1V; - g_roundedRange[channelId] = 1; - } - else if(range > 0.2) - { - g_range[channelId] = PICO_X1_PROBE_500MV; - g_range_3000a[channelId] = PS3000A_500MV; - g_roundedRange[channelId] = 0.5; - } - else if(range > 0.1) - { - g_range[channelId] = PICO_X1_PROBE_200MV; - g_range_3000a[channelId] = PS3000A_200MV; - g_roundedRange[channelId] = 0.2; - } - else if(range >= 0.05) - { - g_range[channelId] = PICO_X1_PROBE_100MV; - g_range_3000a[channelId] = PS3000A_100MV; - g_roundedRange[channelId] = 0.1; - } - else if(range >= 0.02) - { - g_range[channelId] = PICO_X1_PROBE_50MV; - g_range_3000a[channelId] = PS3000A_50MV; - g_roundedRange[channelId] = 0.05; - } - else if(range >= 0.01) - { - g_range[channelId] = PICO_X1_PROBE_20MV; - g_range_3000a[channelId] = PS3000A_20MV; - g_roundedRange[channelId] = 0.02; - } - else - { - g_range[channelId] = PICO_X1_PROBE_10MV; - g_range_3000a[channelId] = PS3000A_10MV; - g_roundedRange[channelId] = 0.01; - } - + UpdateTrigger(); //TESTING lasse +} + +void PicoSCPIServer::SetAnalogCoupling(size_t chIndex, const std::string& coupling) +{ + lock_guard lock(g_mutex); + int channelId = chIndex & 0xff; + + if(coupling == "DC1M") + g_coupling[channelId] = PICO_DC; + else if(coupling == "AC1M") + g_coupling[channelId] = PICO_AC; + else if(coupling == "DC50") + g_coupling[channelId] = PICO_DC_50OHM; + + UpdateChannel(channelId); +} + +void PicoSCPIServer::SetAnalogRange(size_t chIndex, double range_V) +{ + lock_guard lock(g_mutex); + + size_t channelId = chIndex & 0xff; + //range_V is peak-to-peak whereas the Pico modes are V-peak, + //i.e. PS5000_20V = +-20V = 40Vpp = 'range_V = 40' + + switch(g_series) + { + case 3: + { + //3000D series uses passive probes only, 20mV to 20V, no 50 ohm mode available + if(range_V > 20) + { + g_range_3000a[channelId] = PS3000A_20V; + g_roundedRange[channelId] = 20; + } + else if(range_V > 10) + { + g_range_3000a[channelId] = PS3000A_10V; + g_roundedRange[channelId] = 10; + } + else if(range_V > 5) + { + g_range_3000a[channelId] = PS3000A_5V; + g_roundedRange[channelId] = 5; + } + else if(range_V > 2) + { + g_range_3000a[channelId] = PS3000A_2V; + g_roundedRange[channelId] = 2; + } + else if(range_V > 1) + { + g_range_3000a[channelId] = PS3000A_1V; + g_roundedRange[channelId] = 1; + } + else if(range_V > 0.5) + { + g_range_3000a[channelId] = PS3000A_500MV; + g_roundedRange[channelId] = 0.5; + } + else if(range_V > 0.2) + { + g_range_3000a[channelId] = PS3000A_200MV; + g_roundedRange[channelId] = 0.2; + } + else if(range_V > 0.1) + { + g_range_3000a[channelId] = PS3000A_100MV; + g_roundedRange[channelId] = 0.1; + } + else if(range_V > 0.05) + { + g_range_3000a[channelId] = PS3000A_50MV; + g_roundedRange[channelId] = 0.05; + } + else + { + g_range_3000a[channelId] = PS3000A_20MV; + g_roundedRange[channelId] = 0.02; + } + } + break; + + case 4: + { + //4000 series uses passive probes only, 10mV to 50V, no 50 ohm mode available + if(range_V > 50) + { + g_range_4000a[channelId] = PS4000A_50V; + g_range[channelId] = PICO_X1_PROBE_50V; + g_roundedRange[channelId] = 50; + } + else if(range_V > 20) + { + g_range_4000a[channelId] = PS4000A_20V; + g_range[channelId] = PICO_X1_PROBE_20V; + g_roundedRange[channelId] = 20; + } + else if(range_V > 10) + { + g_range_4000a[channelId] = PS4000A_10V; + g_range[channelId] = PICO_X1_PROBE_10V; + g_roundedRange[channelId] = 10; + } + else if(range_V > 5) + { + g_range_4000a[channelId] = PS4000A_5V; + g_range[channelId] = PICO_X1_PROBE_5V; + g_roundedRange[channelId] = 5; + } + else if(range_V > 2) + { + g_range_4000a[channelId] = PS4000A_2V; + g_range[channelId] = PICO_X1_PROBE_2V; + g_roundedRange[channelId] = 2; + } + else if(range_V > 1) + { + g_range_4000a[channelId] = PS4000A_1V; + g_range[channelId] = PICO_X1_PROBE_1V; + g_roundedRange[channelId] = 1; + } + else if(range_V > 0.5) + { + g_range_4000a[channelId] = PS4000A_500MV; + g_range[channelId] = PICO_X1_PROBE_500MV; + g_roundedRange[channelId] = 0.5; + } + else if(range_V > 0.2) + { + g_range_4000a[channelId] = PS4000A_200MV; + g_range[channelId] = PICO_X1_PROBE_200MV; + g_roundedRange[channelId] = 0.2; + } + else if(range_V > 0.1) + { + g_range_4000a[channelId] = PS4000A_100MV; + g_range[channelId] = PICO_X1_PROBE_100MV; + g_roundedRange[channelId] = 0.1; + } + else if(range_V > 0.05) + { + g_range_4000a[channelId] = PS4000A_50MV; + g_range[channelId] = PICO_X1_PROBE_50MV; + g_roundedRange[channelId] = 0.05; + } + else if(range_V > 0.02) + { + g_range_4000a[channelId] = PS4000A_20MV; + g_range[channelId] = PICO_X1_PROBE_20MV; + g_roundedRange[channelId] = 0.02; + } + else + { + g_range_4000a[channelId] = PS4000A_10MV; + g_range[channelId] = PICO_X1_PROBE_10MV; + g_roundedRange[channelId] = 0.01; + } + } + break; + + case 5: + { + //5000D series uses passive probes only, 10mV to 20V, no 50 ohm mode available + if(range_V > 20) + { + g_range_5000a[channelId] = PS5000A_20V; + g_roundedRange[channelId] = 20; + } + else if(range_V > 10) + { + g_range_5000a[channelId] = PS5000A_10V; + g_roundedRange[channelId] = 10; + } + else if(range_V > 5) + { + g_range_5000a[channelId] = PS5000A_5V; + g_roundedRange[channelId] = 5; + } + else if(range_V > 2) + { + g_range_5000a[channelId] = PS5000A_2V; + g_roundedRange[channelId] = 2; + } + else if(range_V > 1) + { + g_range_5000a[channelId] = PS5000A_1V; + g_roundedRange[channelId] = 1; + } + else if(range_V > 0.5) + { + g_range_5000a[channelId] = PS5000A_500MV; + g_roundedRange[channelId] = 0.5; + } + else if(range_V > 0.2) + { + g_range_5000a[channelId] = PS5000A_200MV; + g_roundedRange[channelId] = 0.2; + } + else if(range_V > 0.1) + { + g_range_5000a[channelId] = PS5000A_100MV; + g_roundedRange[channelId] = 0.1; + } + else if(range_V > 0.05) + { + g_range_5000a[channelId] = PS5000A_50MV; + g_roundedRange[channelId] = 0.05; + } + else if(range_V > 0.02) + { + g_range_5000a[channelId] = PS5000A_20MV; + g_roundedRange[channelId] = 0.02; + } + else + { + g_range_5000a[channelId] = PS5000A_10MV; + g_roundedRange[channelId] = 0.01; + } + } + break; + + case 6: + { + //6000E series can use intelligent probes. + //Model 6428E-D is 50 ohm only and has a limited range. + //If 50 ohm coupling, cap hardware voltage range to 5V + if(g_coupling[channelId] == PICO_DC_50OHM) + range_V = min(range_V, 5.0); + + if(range_V > 200) + { + g_range[channelId] = PICO_X1_PROBE_200V; + g_roundedRange[channelId] = 200; + } + else if(range_V > 100) + { + g_range[channelId] = PICO_X1_PROBE_100V; + g_roundedRange[channelId] = 100; + } + else if(range_V > 50) + { + g_range[channelId] = PICO_X1_PROBE_50V; + g_roundedRange[channelId] = 50; + } + else if(range_V > 20) + { + g_range[channelId] = PICO_X1_PROBE_20V; + g_roundedRange[channelId] = 20; + } + else if(range_V > 10) + { + g_range[channelId] = PICO_X1_PROBE_10V; + g_roundedRange[channelId] = 10; + } + else if(range_V > 5) + { + g_range[channelId] = PICO_X1_PROBE_5V; + g_roundedRange[channelId] = 5; + } + else if(range_V > 2) + { + g_range[channelId] = PICO_X1_PROBE_2V; + g_roundedRange[channelId] = 2; + } + else if(range_V > 1) + { + g_range[channelId] = PICO_X1_PROBE_1V; + g_roundedRange[channelId] = 1; + } + else if(range_V > 0.5) + { + g_range[channelId] = PICO_X1_PROBE_500MV; + g_roundedRange[channelId] = 0.5; + } + else if(range_V > 0.2) + { + g_range[channelId] = PICO_X1_PROBE_200MV; + g_roundedRange[channelId] = 0.2; + } + else if(range_V > 0.1) + { + g_range[channelId] = PICO_X1_PROBE_100MV; + g_roundedRange[channelId] = 0.1; + } + else if(range_V > 0.05) + { + g_range[channelId] = PICO_X1_PROBE_50MV; + g_roundedRange[channelId] = 0.05; + } + else if(range_V > 0.02) + { + g_range[channelId] = PICO_X1_PROBE_20MV; + g_roundedRange[channelId] = 0.02; + } + else + { + g_range[channelId] = PICO_X1_PROBE_10MV; + g_roundedRange[channelId] = 0.01; + } + } + break; + } + + //We need to allocate new buffers for this channel + g_memDepthChanged = true; UpdateChannel(channelId); //Update trigger if this is the trigger channel. //Trigger is digital and threshold is specified in ADC counts. //We want to maintain constant trigger level in volts, not ADC counts. - if(g_triggerChannel == channelId) - UpdateTrigger(); + // !! this is done in UpdateChannel() already !! + //if(g_triggerChannel == channelId) + // UpdateTrigger(); } void PicoSCPIServer::SetAnalogOffset(size_t chIndex, double offset_V) @@ -1064,14 +2044,24 @@ void PicoSCPIServer::SetAnalogOffset(size_t chIndex, double offset_V) float minoff_f; //Clamp to allowed range - switch(g_pico_type) + switch(g_series) { - case PICO3000A: + case 3: ps3000aGetAnalogueOffset(g_hScope, g_range_3000a[channelId], (PS3000A_COUPLING)g_coupling[channelId], &maxoff_f, &minoff_f); maxoff = maxoff_f; minoff = minoff_f; break; - case PICO6000A: + case 4: + ps4000aGetAnalogueOffset(g_hScope, g_range[channelId], (PS4000A_COUPLING)g_coupling[channelId], &maxoff_f, &minoff_f); + maxoff = maxoff_f; + minoff = minoff_f; + break; + case 5: + ps5000aGetAnalogueOffset(g_hScope, g_range_5000a[channelId], (PS5000A_COUPLING)g_coupling[channelId], &maxoff_f, &minoff_f); + maxoff = maxoff_f; + minoff = minoff_f; + break; + case 6: ps6000aGetAnalogueOffsetLimits(g_hScope, g_range[channelId], g_coupling[channelId], &maxoff, &minoff); break; } @@ -1086,9 +2076,27 @@ void PicoSCPIServer::SetDigitalThreshold(size_t chIndex, double threshold_V) { int channelId = chIndex & 0xff; int laneId = (chIndex >> 8) & 0xff; + int16_t code = 0; - int16_t code = round( (threshold_V * 32767) / 5.0); - g_msoPodThreshold[channelId][laneId] = code; + switch(g_series) + { + case 3: + case 5: + //Threshold voltage range is 5V for MSO scopes + code = round( (threshold_V * 32767) / 5.0); + + //Threshold voltage cannot be set individually, but only for each channel, + //so we set the threshold value for all 8 lanes at once + for(int i=0; i<7; i++) + g_msoPodThreshold[channelId][i] = code; + + break; + case 6: + //Threshold voltage range is 8V for TA369 pods + code = round( (threshold_V * 32767) / 8.0); + g_msoPodThreshold[channelId][laneId] = code; + break; + } LogTrace("Setting MSO pod %d lane %d threshold to %f (code %d)\n", channelId, laneId, threshold_V, code); @@ -1101,6 +2109,10 @@ void PicoSCPIServer::SetDigitalThreshold(size_t chIndex, double threshold_V) void PicoSCPIServer::SetDigitalHysteresis(size_t chIndex, double hysteresis) { + //Hysteresis is fixed to 250mV on 3000 and 5000 series (4000 has no digital option) + if( (g_series != 6) ) + return; + lock_guard lock(g_mutex); int channelId = chIndex & 0xff; @@ -1130,24 +2142,128 @@ void PicoSCPIServer::SetSampleRate(uint64_t rate_hz) int timebase; double period_ns; - switch(g_pico_type) + switch(g_series) { - case PICO3000A: + case 3: { //Convert sample rate to sample period g_sampleInterval = 1e15 / rate_hz; period_ns = 1e9 / rate_hz; //Find closest timebase setting - double clkdiv = period_ns; - if(period_ns < 1) - timebase = 0; + if( (g_model[1]=='2') and (g_model[4]=='A' or g_model[4]=='B') ) + { + //!! A different implementation is needed for: + //!! PicoScope 3000A and 3000B Series 2-Channel USB 2.0 Oscilloscopes + if(period_ns < 4) + timebase = 0; + else if(period_ns < 16) + timebase = round(log(5e8/rate_hz)/log(2)); + else + timebase = round((625e5/rate_hz)+2); + } + if( (g_model.find("MSO") != string::npos) and (g_model[4]!='D') ) + { + //!! And another one for: + //!! PicoScope 3000 Series USB 2.0 MSOs + if(period_ns < 4) + timebase = 0; + else if(period_ns < 8) + timebase = round(log(5e8/rate_hz)/log(2)); + else + timebase = round((125e6/rate_hz)+1); + } else - timebase = round(log(clkdiv)/log(2)); + { + //!! This part is applicable to the following devices: + //!! PicoScope 3000A and 3000B Series 4-Channel USB 2.0 Oscilloscopes + //!! PicoScope 3207A and 3207B USB 3.0 Oscilloscopes + //!! PicoScope 3000D Series USB 3.0 Oscilloscopes and MSOs + if(period_ns < 2) + timebase = 0; + else if(period_ns < 8) + timebase = round(log(1e9/rate_hz)/log(2)); + else + timebase = round((125e6/rate_hz)+2); + } + } + break; + + case 4: + { + //Convert sample rate to sample period + g_sampleInterval = 1e15 / rate_hz; + period_ns = 1e9 / rate_hz; + + //Find closest timebase setting + if(g_model.find("4444") != string::npos) + { + if(period_ns < 5) + timebase = 0; + else if(period_ns < 40) + timebase = round(log(4e8/rate_hz)/log(2)); + else + timebase = round((50e6/rate_hz)+2); + } + else + timebase = trunc((80e6/rate_hz)-1); + } + break; + + case 5: + { + //Convert sample rate to sample period + g_sampleInterval = 1e15 / rate_hz; + period_ns = 1e9 / rate_hz; + + //Find closest timebase setting + switch(g_adcBits) + { + case 8: + { + if(period_ns < 2) + timebase = 0; + else if(period_ns < 8) + timebase = round(log(1e9/rate_hz)/log(2)); + else + timebase = round((125e6/rate_hz)+2); + break; + } + + case 12: + { + if(period_ns < 4) + timebase = 1; + else if(period_ns < 16) + timebase = round(log(5e8/rate_hz)/log(2)+1); + else + timebase = round((625e5/rate_hz)+3); + break; + } + + case 14: + case 15: + { + if(period_ns < 16) + timebase = 3; + else + timebase = round((125e6/rate_hz)+2); + break; + } + + case 16: + { + if(period_ns < 32) + timebase = 4; + else + timebase = round((625e5/rate_hz)+3); + break; + } + } } break; - case PICO6000A: + case 6: { //Convert sample rate to sample period g_sampleInterval = 1e15 / rate_hz; @@ -1159,6 +2275,15 @@ void PicoSCPIServer::SetSampleRate(uint64_t rate_hz) timebase = round(log(clkdiv)/log(2)); else timebase = round(clkdiv/32) + 4; + + //6428E-D is calculated differently + if(g_model[3] == '8') + { + if(clkdiv < 1) + timebase = 0; + else + timebase = timebase + 1; + } } break; @@ -1167,11 +2292,13 @@ void PicoSCPIServer::SetSampleRate(uint64_t rate_hz) g_sampleInterval = 1e15 / rate_hz; timebase = 0; - LogError("SetSampleRate Error unknown g_pico_type\n"); + LogError("SetSampleRate Error unknown g_series\n"); } } g_timebase = timebase; + g_sampleRate = rate_hz; + UpdateTrigger(); //TESTING lasse } void PicoSCPIServer::SetSampleDepth(uint64_t depth) @@ -1278,30 +2405,74 @@ void PicoSCPIServer::SetEdgeTriggerEdge(const string& edge) */ void UpdateChannel(size_t chan) { - switch(g_pico_type) + int16_t scaleVal; + + switch(g_series) { - case PICO3000A: + case 3: { ps3000aSetChannel(g_hScope, (PS3000A_CHANNEL)chan, g_channelOn[chan], (PS3000A_COUPLING)g_coupling[chan], g_range_3000a[chan], -g_offset[chan]); ps3000aSetBandwidthFilter(g_hScope, (PS3000A_CHANNEL)chan, - (PS3000A_BANDWIDTH_LIMITER)g_bandwidth_legacy[chan]); + (PS3000A_BANDWIDTH_LIMITER)g_bandwidth_3000a[chan]); + ps3000aMaximumValue(g_hScope, &scaleVal); + g_scaleValue = scaleVal; + + //We use software triggering based on raw ADC codes. + //Any time we change the frontend configuration on the trigger channel, it has to be reconfigured. + //TODO: handle multi-input triggers + if(chan == g_triggerChannel) + UpdateTrigger(); + } + break; + + case 4: + { + ps4000aSetChannel(g_hScope, (PS4000A_CHANNEL)chan, g_channelOn[chan], + (PS4000A_COUPLING)g_coupling[chan], g_range[chan], -g_offset[chan]); + ps4000aSetBandwidthFilter(g_hScope, (PS4000A_CHANNEL)chan, + (PS4000A_BANDWIDTH_LIMITER)g_bandwidth_5000a[chan]); + ps4000aMaximumValue(g_hScope, &scaleVal); + g_scaleValue = scaleVal; + + //We use software triggering based on raw ADC codes. + //Any time we change the frontend configuration on the trigger channel, it has to be reconfigured. + //TODO: handle multi-input triggers + if(chan == g_triggerChannel) + UpdateTrigger(); + } + break; + + case 5: + { + ps5000aSetChannel(g_hScope, (PS5000A_CHANNEL)chan, g_channelOn[chan], + (PS5000A_COUPLING)g_coupling[chan], g_range_5000a[chan], -g_offset[chan]); + ps5000aSetBandwidthFilter(g_hScope, (PS5000A_CHANNEL)chan, + (PS5000A_BANDWIDTH_LIMITER)g_bandwidth_5000a[chan]); + ps5000aMaximumValue(g_hScope, &scaleVal); + g_scaleValue = scaleVal; + + //LogDebug(" - UpdateChannel %zu, range %f, scaleVal %d \n", chan, g_roundedRange[chan], scaleVal); //We use software triggering based on raw ADC codes. //Any time we change the frontend configuration on the trigger channel, it has to be reconfigured. //TODO: handle multi-input triggers if(chan == g_triggerChannel) UpdateTrigger(); - return; } break; - case PICO6000A: + case 6: { if(g_channelOn[chan]) { + PICO_DEVICE_RESOLUTION currentRes; + ps6000aSetChannelOn(g_hScope, (PICO_CHANNEL)chan, g_coupling[chan], g_range[chan], -g_offset[chan], g_bandwidth[chan]); + ps6000aGetDeviceResolution(g_hScope, ¤tRes); + ps6000aGetAdcLimits(g_hScope, currentRes, 0, &scaleVal); + g_scaleValue = scaleVal; //We use software triggering based on raw ADC codes. //Any time we change the frontend configuration on the trigger channel, it has to be reconfigured. @@ -1328,6 +2499,7 @@ void UpdateTrigger(bool force) { timeout = 1; g_lastTriggerWasForced = true; + g_triggerOneShot = true; } else g_lastTriggerWasForced = false; @@ -1341,7 +2513,7 @@ void UpdateTrigger(bool force) float scale = 1; if(triggerIsAnalog) { - scale = g_roundedRange[g_triggerChannel] / 32512; + scale = g_roundedRange[g_triggerChannel] / 32767; if(scale == 0) scale = 1; } @@ -1358,9 +2530,9 @@ void UpdateTrigger(bool force) if(triggerDelaySamples < 0) delay = -triggerDelaySamples; - switch(g_pico_type) + switch(g_series) { - case PICO3000A: + case 3: { if(g_triggerChannel == PICO_TRIGGER_AUX) { @@ -1445,36 +2617,130 @@ void UpdateTrigger(bool force) } else { - /* TODO PICO3000A Trigger Digital Chan */ - LogError("PICO3000A Trigger Digital Chan code TODO\n"); - /* //Remove old trigger conditions - ps6000aSetTriggerChannelConditions( + ps3000aSetTriggerChannelConditionsV2( + g_hScope, + NULL, + 0); + + //Set up new conditions + int ntrig = g_triggerChannel - g_numChannels; + //int trigpod = ntrig / 8; + int triglane = ntrig % 8; + PS3000A_TRIGGER_CONDITIONS_V2 cond; + cond.digital = PS3000A_CONDITION_TRUE; + //cond.external = PS3000A_CONDITION_FALSE; + //cond.channelA = PS3000A_CONDITION_FALSE; + //cond.channelB = PS3000A_CONDITION_FALSE; + //cond.channelC = PS3000A_CONDITION_FALSE; + //cond.channelD = PS3000A_CONDITION_FALSE; + ps3000aSetTriggerChannelConditionsV2( + g_hScope, + &cond, + 1); + + //Set up configuration on the selected channel + PS3000A_DIGITAL_CHANNEL_DIRECTIONS dirs; + dirs.channel = static_cast(PS3000A_DIGITAL_CHANNEL_0 + triglane); + dirs.direction = PS3000A_DIGITAL_DIRECTION_RISING; //TODO: configurable + ps3000aSetTriggerDigitalPortProperties( + g_hScope, + &dirs, + 1); + + //ps6000aSetTriggerDigitalPortProperties doesn't have a timeout! + //Should we call ps6000aSetTriggerChannelProperties with no elements to do this? + if(force) + LogWarning("Force trigger doesn't currently work if trigger source is digital\n"); + } + } + break; + + case 4: + { + if(g_triggerChannel == PICO_TRIGGER_AUX) + { + LogError("PS4000 has no external trigger input\n"); + } + else if(g_triggerChannel < g_numChannels) + { + /* API is same as 6000a API */ + int ret = ps4000aSetSimpleTrigger( + g_hScope, + 1, + (PS4000A_CHANNEL)g_triggerChannel, + round(trig_code), + (enPS4000AThresholdDirection)g_triggerDirection, // same as 6000a api + delay, + timeout); + if(ret != PICO_OK) + LogError("ps4000aSetSimpleTrigger failed: %x\n", ret); + } + else + { + LogError("PS4000 has no digital trigger option\n"); + + } + } + break; + + case 5: + { + if(g_triggerChannel == PICO_TRIGGER_AUX) + { + int ret = ps5000aSetSimpleTrigger( + g_hScope, + 1, + (PS5000A_CHANNEL)PICO_TRIGGER_AUX, + 0, + (enPS5000AThresholdDirection)g_triggerDirection, + delay, + timeout); + if(ret != PICO_OK) + LogError("ps5000aSetSimpleTrigger failed: %x\n", ret); + } + else if(g_triggerChannel < g_numChannels) + { + /* API is same as 6000a API */ + int ret = ps5000aSetSimpleTrigger( + g_hScope, + 1, + (PS5000A_CHANNEL)g_triggerChannel, + trunc(trig_code), + (PS5000A_THRESHOLD_DIRECTION)g_triggerDirection, + delay, + timeout); + if(ret != PICO_OK) + LogError("ps5000aSetSimpleTrigger failed: %x\n", ret); + } + else + { + //Remove old trigger conditions + ps5000aSetTriggerChannelConditionsV2( g_hScope, NULL, 0, - PICO_CLEAR_ALL); + PS5000A_CLEAR); //Set up new conditions int ntrig = g_triggerChannel - g_numChannels; int trigpod = ntrig / 8; int triglane = ntrig % 8; - PICO_CONDITION cond; - cond.source = static_cast(PICO_PORT0 + trigpod); - cond.condition = PICO_CONDITION_TRUE; - ps6000aSetTriggerChannelConditions( + PS5000A_CONDITION cond; + cond.source = static_cast(PS5000A_DIGITAL_PORT0 + trigpod); + cond.condition = PS5000A_CONDITION_TRUE; + ps5000aSetTriggerChannelConditionsV2( g_hScope, &cond, 1, - PICO_ADD); + PS5000A_ADD); //Set up configuration on the selected channel - PICO_DIGITAL_CHANNEL_DIRECTIONS dirs; - dirs.channel = static_cast(PICO_PORT_DIGITAL_CHANNEL0 + triglane); - dirs.direction = PICO_DIGITAL_DIRECTION_RISING; //TODO: configurable - ps6000aSetTriggerDigitalPortProperties( + PS5000A_DIGITAL_CHANNEL_DIRECTIONS dirs; + dirs.channel = static_cast(PS5000A_DIGITAL_CHANNEL_0 + triglane); + dirs.direction = PS5000A_DIGITAL_DIRECTION_RISING; //TODO: configurable + ps5000aSetTriggerDigitalPortProperties( g_hScope, - cond.source, &dirs, 1); @@ -1482,12 +2748,11 @@ void UpdateTrigger(bool force) //Should we call ps6000aSetTriggerChannelProperties with no elements to do this? if(force) LogWarning("Force trigger doesn't currently work if trigger source is digital\n"); - */ } } break; - case PICO6000A: + case 6: { if(g_triggerChannel == PICO_TRIGGER_AUX) { @@ -1615,13 +2880,21 @@ void UpdateTrigger(bool force) void Stop() { - switch(g_pico_type) + switch(g_series) { - case PICO3000A: + case 3: ps3000aStop(g_hScope); break; - case PICO6000A: + case 4: + ps4000aStop(g_hScope); + break; + + case 5: + ps5000aStop(g_hScope); + break; + + case 6: ps6000aStop(g_hScope); break; } @@ -1633,19 +2906,27 @@ PICO_STATUS StartInternal() int64_t triggerDelaySamples = g_triggerDelay / g_sampleInterval; size_t nPreTrigger = min(max(triggerDelaySamples, (int64_t)0L), (int64_t)g_memDepth); size_t nPostTrigger = g_memDepth - nPreTrigger; + int32_t nPreTrigger_int = nPreTrigger; + int32_t nPostTrigger_int = nPostTrigger; g_triggerSampleIndex = nPreTrigger; - - switch(g_pico_type) + + switch(g_series) { - case PICO3000A: - // TODO: why the 1 - return ps3000aRunBlock(g_hScope, nPreTrigger, nPostTrigger, g_timebase, 1, NULL, 0, NULL, NULL); + case 3: + return ps3000aRunBlock(g_hScope, nPreTrigger_int, nPostTrigger_int, g_timebase, 1, NULL, 0, NULL, NULL); - case PICO6000A: + case 4: + return ps4000aRunBlock(g_hScope, nPreTrigger_int, nPostTrigger_int, g_timebase, NULL, 0, NULL, NULL); + + case 5: + return ps5000aRunBlock(g_hScope, nPreTrigger_int, nPostTrigger_int, g_timebase, NULL, 0, NULL, NULL); + + case 6: return ps6000aRunBlock(g_hScope, nPreTrigger, nPostTrigger, g_timebase, NULL, 0, NULL, NULL); default: - return PICO_OK; + //return PICO_OK; + return PICO_CANCELLED; } } @@ -1654,6 +2935,7 @@ void StartCapture(bool stopFirst, bool force) //If previous trigger was forced, we need to reconfigure the trigger to be not-forced now if(g_lastTriggerWasForced && !force) { + g_triggerOneShot = false; Stop(); UpdateTrigger(); } @@ -1700,12 +2982,12 @@ void StartCapture(bool stopFirst, bool force) bool EnableMsoPod(size_t npod) { g_msoPodEnabled[npod] = true; - PICO_CHANNEL podId = (PICO_CHANNEL)(PICO_PORT0 + npod); - switch(g_pico_type) + switch(g_series) { - case PICO3000A: + case 3: { + PS3000A_DIGITAL_PORT podId = (PS3000A_DIGITAL_PORT)(PS3000A_DIGITAL_PORT0 + npod); auto status = ps3000aSetDigitalPort(g_hScope, (PS3000A_DIGITAL_PORT)podId, 1, g_msoPodThreshold[npod][0]); if(status != PICO_OK) { @@ -1715,8 +2997,22 @@ bool EnableMsoPod(size_t npod) } break; - case PICO6000A: + case 5: + { + PS5000A_CHANNEL podId = (PS5000A_CHANNEL)(PS5000A_DIGITAL_PORT0 + npod); + auto status = ps5000aSetDigitalPort(g_hScope, (PS5000A_CHANNEL)podId, 1, g_msoPodThreshold[npod][0]); + LogTrace("ps5000aSetDigitalPort Threshold: %i \n", g_msoPodThreshold[npod][0]); + if(status != PICO_OK) + { + LogError("ps5000aSetDigitalPort failed with code %x\n", status); + return false; + } + } + break; + + case 6: { + PICO_CHANNEL podId = (PICO_CHANNEL)(PICO_PORT0 + npod); auto status = ps6000aSetDigitalPortOn( g_hScope, podId, @@ -1734,3 +3030,28 @@ bool EnableMsoPod(size_t npod) return true; } +void GenerateSquareWave(int16_t* &waveform, size_t bufferSize, double dutyCycle, int16_t amplitude) +{ + // Validate inputs + if (!waveform || bufferSize == 0) + { + LogError("GenerateSquareWave has Invalid input \n"); + } + + // Calculate number of high samples based on duty cycle + size_t highSamples = static_cast(bufferSize * (dutyCycle / 100.0)); + + // Generate square wave + for (size_t i = 0; i < bufferSize; i++) + { + if (i < highSamples) + { + waveform[i] = amplitude; // High level + } + else + { + waveform[i] = -amplitude; // Low level + } + } +} + diff --git a/src/ps6000d/PicoSCPIServer.h b/src/ps6000d/PicoSCPIServer.h index 3be95c8..f788d14 100644 --- a/src/ps6000d/PicoSCPIServer.h +++ b/src/ps6000d/PicoSCPIServer.h @@ -82,6 +82,7 @@ class PicoSCPIServer : public BridgeSCPIServer virtual void SetAnalogOffset(size_t chIndex, double offset_V); virtual void SetDigitalThreshold(size_t chIndex, double threshold_V); virtual void SetDigitalHysteresis(size_t chIndex, double hysteresis); + virtual void SetChannelBandwidthLimiter(size_t i, unsigned int limit_mhz); virtual void SetSampleRate(uint64_t rate_hz); virtual void SetSampleDepth(uint64_t depth); diff --git a/src/ps6000d/README.md b/src/ps6000d/README.md index ff5578a..5a07e3d 100644 --- a/src/ps6000d/README.md +++ b/src/ps6000d/README.md @@ -1,4 +1,4 @@ # ps6000d Socket servers for Pico Technology instruments allowing remote access via libscopehal. -The ps6000d source code support Picoscope 6000 series and Picoscope 3000 series \ No newline at end of file +The ps6000d source code supports Picoscope 6000E, 5000D, 4000A and 3000D series. \ No newline at end of file diff --git a/src/ps6000d/WaveformServerThread.cpp b/src/ps6000d/WaveformServerThread.cpp index 90d48c2..30fe176 100644 --- a/src/ps6000d/WaveformServerThread.cpp +++ b/src/ps6000d/WaveformServerThread.cpp @@ -72,9 +72,13 @@ void WaveformServerThread() int16_t ready; { lock_guard lock(g_mutex); - if(g_pico_type == PICO6000A) + if(g_series == 6) ps6000aIsReady(g_hScope, &ready); - else if(g_pico_type == PICO3000A) + else if(g_series == 5) + ps5000aIsReady(g_hScope, &ready); + else if(g_series == 4) + ps4000aIsReady(g_hScope, &ready); + else if(g_series == 3) ps3000aIsReady(g_hScope, &ready); } @@ -97,9 +101,13 @@ void WaveformServerThread() //Stop the trigger PICO_STATUS status = PICO_OPERATION_FAILED; - if(g_pico_type == PICO6000A) + if(g_series == 6) status = ps6000aStop(g_hScope); - else if(g_pico_type == PICO3000A) + else if(g_series == 5) + status = ps5000aStop(g_hScope); + else if(g_series == 4) + status = ps4000aStop(g_hScope); + else if(g_series == 3) status = ps3000aStop(g_hScope); if(PICO_OK != status) LogFatal("ps6000aStop failed (code 0x%x)\n", status); @@ -109,15 +117,21 @@ void WaveformServerThread() //Set up buffers if needed if(g_memDepthChanged || waveformBuffers.empty()) { - LogTrace("Reallocating buffers\n"); + //LogVerbose("Reallocating buffers\n"); //Clear out old buffers for(auto ch : g_channelIDs) { - if(g_pico_type == PICO6000A) + if(g_series == 6) ps6000aSetDataBuffer(g_hScope, ch, NULL, 0, PICO_INT16_T, 0, PICO_RATIO_MODE_RAW, PICO_CLEAR_ALL); - else if(g_pico_type == PICO3000A) + else if(g_series == 5) + ps5000aSetDataBuffer(g_hScope, (PS5000A_CHANNEL)ch, NULL, + 0, 0, PS5000A_RATIO_MODE_NONE); + else if(g_series == 4) + ps4000aSetDataBuffer(g_hScope, (PS4000A_CHANNEL)ch, NULL, + 0, 0, PS4000A_RATIO_MODE_NONE); + else if(g_series == 3) ps3000aSetDataBuffer(g_hScope, (PS3000A_CHANNEL)ch, NULL, 0, 0, PS3000A_RATIO_MODE_NONE); } @@ -142,10 +156,16 @@ void WaveformServerThread() //Give it to the scope, removing any other buffer we might have auto ch = g_channelIDs[i]; - if(g_pico_type == PICO6000A) + if(g_series == 6) status = ps6000aSetDataBuffer(g_hScope, (PICO_CHANNEL)ch, waveformBuffers[i], g_captureMemDepth, PICO_INT16_T, 0, PICO_RATIO_MODE_RAW, PICO_ADD); - else if(g_pico_type == PICO3000A) + else if(g_series == 5) + status = ps5000aSetDataBuffer(g_hScope, (PS5000A_CHANNEL)ch, waveformBuffers[i], + g_captureMemDepth, 0, PS5000A_RATIO_MODE_NONE); + else if(g_series == 4) + status = ps4000aSetDataBuffer(g_hScope, (PS4000A_CHANNEL)ch, waveformBuffers[i], + g_captureMemDepth, 0, PS4000A_RATIO_MODE_NONE); + else if(g_series == 3) status = ps3000aSetDataBuffer(g_hScope, (PS3000A_CHANNEL)ch, waveformBuffers[i], g_captureMemDepth, 0, PS3000A_RATIO_MODE_NONE); if(status != PICO_OK) @@ -159,12 +179,32 @@ void WaveformServerThread() numSamples = g_captureMemDepth; numSamples_int = g_captureMemDepth; int16_t overflow = 0; - if(g_pico_type == PICO6000A) + if(g_series == 6) status = ps6000aGetValues(g_hScope, 0, &numSamples, 1, PICO_RATIO_MODE_RAW, 0, &overflow); - else if(g_pico_type == PICO3000A) + else if(g_series == 5) + { + status = ps5000aGetValues(g_hScope, 0, &numSamples_int, 1, PS5000A_RATIO_MODE_NONE, 0, &overflow); + numSamples = numSamples_int; + } + else if(g_series == 4) + { + status = ps4000aGetValues(g_hScope, 0, &numSamples_int, 1, PS4000A_RATIO_MODE_NONE, 0, &overflow); + numSamples = numSamples_int; + } + else if(g_series == 3) + { status = ps3000aGetValues(g_hScope, 0, &numSamples_int, 1, PS3000A_RATIO_MODE_NONE, 0, &overflow); + numSamples = numSamples_int; + } if(status == PICO_NO_SAMPLES_AVAILABLE) - continue; // state changed while mutex was unlocked? + { + LogVerbose("PICO_NO_SAMPLES_AVAILABLE\n"); + //This response will occur if some setting like vertical scale changed just before aGetValues. + //flush buffers and update channel + g_memDepthChanged = true; + UpdateTrigger(true); + continue; + } if(PICO_OK != status) LogFatal("psXXXXGetValues (code 0x%x)\n", status); @@ -180,8 +220,6 @@ void WaveformServerThread() if(g_msoPodEnabledDuringArm[i]) numchans ++; } - - } //Do *not* hold mutex while sending data to the client @@ -231,10 +269,10 @@ void WaveformServerThread() chdrs.nchan = i; chdrs.numSamples = numSamples; - chdrs.scale = g_roundedRange[i] / 32512; + chdrs.scale = g_roundedRange[i] / g_scaleValue; chdrs.offset = g_offsetDuringArm[i]; chdrs.trigphase = trigphase; - + //Send channel headers if(!client.SendLooped((uint8_t*)&chdrs, sizeof(chdrs))) break; @@ -273,7 +311,9 @@ void WaveformServerThread() //Re-arm the trigger if doing repeating triggers if(g_triggerOneShot) + { g_triggerArmed = false; + } else { if(g_captureMemDepth != g_memDepth) @@ -300,8 +340,18 @@ float InterpolateTriggerTime(int16_t* buf) { if(g_triggerSampleIndex <= 0) return 0; - - float trigscale = g_roundedRange[g_triggerChannel] / 32512; + + //trigger scale value depends on ADC setting and is different for EXT trig input + size_t trigmaxcount = g_scaleValue; + if(g_triggerChannel == PICO_TRIGGER_AUX) + { + //set model dependent EXT trig scale value + trigmaxcount = 32767; + if(g_series == 6) + trigmaxcount = 32512; + } + + float trigscale = g_roundedRange[g_triggerChannel] / trigmaxcount; float trigoff = g_offsetDuringArm[g_triggerChannel]; float fa = buf[g_triggerSampleIndex-1] * trigscale + trigoff; diff --git a/src/ps6000d/main.cpp b/src/ps6000d/main.cpp index fa2b109..974eaee 100644 --- a/src/ps6000d/main.cpp +++ b/src/ps6000d/main.cpp @@ -38,6 +38,10 @@ #include PICO_STATUS (*picoGetUnitInfo) (int16_t, int8_t *, int16_t, int16_t *, PICO_INFO); +PICO_INFO Open3000(); +PICO_INFO Open4000(); +PICO_INFO Open5000(); +PICO_INFO Open6000(); using namespace std; @@ -50,6 +54,7 @@ void help() "\n" " [general options]:\n" " --help : this message...\n" + " --series : specifies the model series to look for (3000, 4000, 5000, 6000)\n" " --scpi-port port : specifies the SCPI control plane port (default 5025)\n" " --waveform-port port : specifies the binary waveform data port (default 5026)\n" "\n" @@ -69,10 +74,11 @@ void help() string g_model; string g_serial; string g_fwver; +size_t g_series; -PicoScopeType g_pico_type; int16_t g_hScope = 0; size_t g_numChannels = 0; +bool limitChannels = false; Socket g_scpiSocket(AF_INET6, SOCK_STREAM, IPPROTO_TCP); Socket g_dataSocket(AF_INET6, SOCK_STREAM, IPPROTO_TCP); @@ -87,6 +93,7 @@ int main(int argc, char* argv[]) { //Global settings Severity console_verbosity = Severity::NOTICE; + g_series = 0; //Parse command-line arguments uint16_t scpi_port = 5025; @@ -105,6 +112,17 @@ int main(int argc, char* argv[]) return 0; } + else if(s == "--series") + { + if(i+1 < argc) + { + string tmp(argv[++i]); + g_series = tmp[0] - '0'; + if( !( (g_series==3) || (g_series==4) || (g_series==5) || (g_series==6) ) ) + g_series = 0; + } + } + else if(s == "--scpi-port") { if(i+1 < argc) @@ -125,42 +143,58 @@ int main(int argc, char* argv[]) } //Set up logging + //TODO: fix color on Windows g_log_sinks.emplace(g_log_sinks.begin(), new ColoredSTDLogSink(console_verbosity)); + //g_log_sinks.emplace(g_log_sinks.begin(), new STDLogSink(console_verbosity)); //For now, open the first instrument we can find. //TODO: implement device selection logic - LogNotice("Looking for a PicoScope 6000 series instrument to open...\n"); - auto status = ps6000aOpenUnit(&g_hScope, NULL, PICO_DR_8BIT); - if(PICO_OK != status) + PICO_INFO status = PICO_NOT_FOUND; + switch(g_series) { - LogNotice("Looking for a PicoScope 3000 series instrument to open...\n"); - status = ps3000aOpenUnit(&g_hScope, NULL); - if(status == PICO_POWER_SUPPLY_NOT_CONNECTED) + case 0: + { + status = Open3000(); + if(status == 0) + break; + status = Open4000(); + if(status == 0) + break; + status = Open5000(); + if(status == 0) + break; + status = Open6000(); + break; + } + case 3: { - // switch to USB power - // TODO: maybe require the user to specify this is ok - LogNotice("Switching to USB power...\n"); - status = ps3000aChangePowerSource(g_hScope, PICO_POWER_SUPPLY_NOT_CONNECTED); + status = Open3000(); + break; } - if(PICO_OK != status) + case 4: { - LogError("Failed to open unit (code %d)\n", status); - return 1; + status = Open4000(); + break; } - else + case 5: { - g_pico_type = PICO3000A; - picoGetUnitInfo = ps3000aGetUnitInfo; + status = Open5000(); + break; + } + case 6: + { + status = Open5000(); + break; } } - else + + if(PICO_OK != status) { - g_pico_type = PICO6000A; - picoGetUnitInfo = ps6000aGetUnitInfo; + LogError("Failed to open unit (code %d)\n", status); + return 1; } //See what we got - LogNotice("Successfully opened instrument\n"); { LogIndenter li; @@ -243,18 +277,29 @@ int main(int argc, char* argv[]) if(status == PICO_OK) LogVerbose("IPP version: %s\n", buf); } + LogNotice("Successfully opened instrument %s (%s) on ports %i, %i\n", g_model.c_str(), g_serial.c_str(), scpi_port, waveform_port); - g_numChannels = g_model[1] - '0'; + //Limit to two channels only while on USB power + if(limitChannels) + g_numChannels = '2' - '0'; + else + g_numChannels = g_model[1] - '0'; //Initial channel state setup for(size_t i=0; i lock(g_mutex); - switch (g_pico_type) + switch (g_series) { - case PICO3000A: + case 3: ps3000aCloseUnit(g_hScope); break; - case PICO6000A: + case 4: + ps4000aCloseUnit(g_hScope); + break; + case 5: + ps5000aCloseUnit(g_hScope); + break; + case 6: ps6000aCloseUnit(g_hScope); break; } exit(0); } + +PICO_INFO Open3000() +{ + LogNotice("Looking for a PicoScope 3000 series instrument to open...\n"); + PICO_INFO status = ps3000aOpenUnit(&g_hScope, NULL); + if(status == PICO_POWER_SUPPLY_NOT_CONNECTED) + { + // switch to USB power + // TODO: maybe require the user to specify this is ok + limitChannels = true; + LogNotice("Switching to USB power...\n"); + status = ps3000aChangePowerSource(g_hScope, PICO_POWER_SUPPLY_NOT_CONNECTED); + } + if(status == PICO_OK) + { + g_series = 3; + picoGetUnitInfo = ps3000aGetUnitInfo; + } + return status; +} + +PICO_INFO Open4000() +{ + LogNotice("Looking for a PicoScope 4000 series instrument to open...\n"); + PICO_INFO status = ps4000aOpenUnit(&g_hScope, NULL); + if(status == PICO_POWER_SUPPLY_NOT_CONNECTED) + { + // switch to USB power + // only applies to model 4444 + limitChannels = true; + LogNotice("Switching to USB power...\n"); + status = ps4000aChangePowerSource(g_hScope, PICO_POWER_SUPPLY_NOT_CONNECTED); + } + if(status == PICO_OK) + { + g_series = 4; + picoGetUnitInfo = ps4000aGetUnitInfo; + } + return status; +} + +PICO_INFO Open5000() +{ + LogNotice("Looking for a PicoScope 5000 series instrument to open...\n"); + PICO_INFO status = ps5000aOpenUnit(&g_hScope, NULL, PS5000A_DR_8BIT); + if( (status == PICO_POWER_SUPPLY_NOT_CONNECTED) or (status == PICO_USB3_0_DEVICE_NON_USB3_0_PORT) ) + { + // switch to USB power + // TODO: maybe require the user to specify this is ok + limitChannels = true; + LogNotice("Switching to USB power...\n"); + status = ps5000aChangePowerSource(g_hScope, PICO_POWER_SUPPLY_NOT_CONNECTED); + } + if(status == PICO_OK) + { + g_series = 5; + picoGetUnitInfo = ps5000aGetUnitInfo; + } + return status; +} +PICO_INFO Open6000() +{ + LogNotice("Looking for a PicoScope 6000 series instrument to open...\n"); + PICO_INFO status = ps6000aOpenUnit(&g_hScope, NULL, PICO_DR_8BIT); + if(status == PICO_OK) + { + g_series = 6; + picoGetUnitInfo = ps6000aGetUnitInfo; + } + return status; +} diff --git a/src/ps6000d/ps6000d.h b/src/ps6000d/ps6000d.h index ede7eb2..0c7f8ba 100644 --- a/src/ps6000d/ps6000d.h +++ b/src/ps6000d/ps6000d.h @@ -43,42 +43,44 @@ #include #include "ps3000aApi.h" +#include "ps5000aApi.h" +#include "ps4000aApi.h" #include "ps6000aApi.h" #include "PicoStatus.h" #include "PicoVersion.h" -enum PicoScopeType -{ - PICO3000A, - PICO6000A -}; - extern Socket g_scpiSocket; extern Socket g_dataSocket; extern int16_t g_hScope; void WaveformServerThread(); -extern PicoScopeType g_pico_type; -extern std::string g_model; +extern std::string g_model; //model number, used to discern features extern std::string g_serial; extern std::string g_fwver; +extern std::size_t g_series; //single 1st digit: 2,3,4,5,6 +extern bool g_limitChannels; extern size_t g_numChannels; extern size_t g_numDigitalPods; extern volatile bool g_waveformThreadQuit; extern size_t g_captureMemDepth; extern size_t g_memDepth; +extern size_t g_scaleValue; extern std::map g_channelOnDuringArm; extern std::map g_channelOn; extern std::map g_roundedRange; extern std::map g_coupling; extern std::map g_range; extern std::map g_range_3000a; +extern std::map g_range_4000a; +extern std::map g_range_5000a; extern std::map g_offset; extern std::map g_bandwidth; -extern std::map g_bandwidth_legacy; +extern std::map g_bandwidth_3000a; +extern std::map g_bandwidth_4000a; +extern std::map g_bandwidth_5000a; extern bool g_msoPodEnabled[2]; extern bool g_msoPodEnabledDuringArm[2]; diff --git a/supported_model_features.ods b/supported_model_features.ods new file mode 100644 index 0000000..98ce11a Binary files /dev/null and b/supported_model_features.ods differ