From 6a12dbc2828295b2bff2dd2eefa253c3770d4bf2 Mon Sep 17 00:00:00 2001 From: Lasse Beyer Date: Fri, 31 Oct 2025 14:53:48 +0100 Subject: [PATCH 01/12] Add files via upload --- README.md | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/README.md b/README.md index b81fd2d..0b3e12d 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,72 @@ # 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:5024:5025** +4. Click Add. +5. If this is your first time using ngscopeclient, continue reading [the tutorial here](https://www.ngscopeclient.org/manual/Tutorials.html). + + +## 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. From be8c4d6e0068142f43cf1899205b31bdfb509e44 Mon Sep 17 00:00:00 2001 From: Lasse Beyer Date: Fri, 31 Oct 2025 15:38:11 +0100 Subject: [PATCH 02/12] Add files via upload --- README.md | 47 ++++++++++++++++++++++++++++++++++- supported_model_features.ods | Bin 0 -> 32057 bytes 2 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 supported_model_features.ods diff --git a/README.md b/README.md index 0b3e12d..c0512fc 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,51 @@ Socket servers for Pico Technology instruments allowing remote access via libsco 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**: @@ -69,4 +114,4 @@ The process to build from source is basically [the same as for ngscopeclient](ht 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. + The binary can be found in the build directory, such as $HOME\scopehal-pico-bridge\build\src\ps6000d. diff --git a/supported_model_features.ods b/supported_model_features.ods new file mode 100644 index 0000000000000000000000000000000000000000..98ce11afb113bffbe1603844354e523de0ec63fe GIT binary patch literal 32057 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�Q&m; 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 Date: Fri, 31 Oct 2025 17:06:06 +0100 Subject: [PATCH 03/12] Add support for ps4000a, ps5000a --- README.md | 21 +- src/ps6000d/CMakeLists.txt | 13 +- src/ps6000d/PicoSCPIServer.cpp | 1557 +++++++++++++++++++++----- src/ps6000d/README.md | 2 +- src/ps6000d/WaveformServerThread.cpp | 62 +- src/ps6000d/main.cpp | 134 ++- src/ps6000d/ps6000d.h | 20 +- 7 files changed, 1469 insertions(+), 340 deletions(-) diff --git a/README.md b/README.md index c0512fc..11a814f 100644 --- a/README.md +++ b/README.md @@ -29,21 +29,21 @@ As of October 2025, there should be a total of **72** different devices supporte | ps3000a | ps4000a | ps5000a | ps6000a | | :---- | :---- | :---- | :---- | -| 3203D | 4444 | 5242A | 6403E | +| 3203D | ~~4444~~ | 5242A | 6403E | | 3203D MSO | **4824** | 5242B | 6404E | -| 3204A | 4224A | 5242D | 6405E | -| 3204B | 4424A | 5242D MSO | 6406E | -| 3204 MSO | 4824A | 5243A | 6424E | +| ~~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** | +| ~~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 | | +| ~~3206A~~ | | 5442A | | +| ~~3206B~~ | | 5442B | | +| ~~3206 MSO~~ | | 5442D | | | 3206D | | 5442D MSO | | | 3206D MSO | | 5443A | | | 3207A | | 5443B | | @@ -64,6 +64,7 @@ As of October 2025, there should be a total of **72** different devices supporte | 3406D MSO | | | | Models shown in bold were used for, and tested during, the development of the pico-bridge. +Models striked out need some additional work and are not fully supported yet. ## How to compile 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..09e32d8 100644 --- a/src/ps6000d/PicoSCPIServer.cpp +++ b/src/ps6000d/PicoSCPIServer.cpp @@ -146,11 +146,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 +166,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 +197,43 @@ float g_awgRange = 0; float g_awgOffset = 0; bool g_awgOn = false; double g_awgFreq = 1000; +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_WHITE_NOISE, PS5000A_ES_OFF}}, + {"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 +}; + +#define SIG_GEN_BUFFER_SIZE 8192 //TODO: allow model specific/variable buffer size. Must be power of 2 for most AWGs PS3000: 2^13, PS3207B: 2^14 PS3206B: 2^15 +int16_t* g_arbitraryWaveform = new int16_t[SIG_GEN_BUFFER_SIZE]; // +void GenerateSquareWave(int16_t* &waveform, size_t bufferSize, double dutyCycle, int16_t amplitude = 32767); void ReconfigAWG(); //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -210,12 +254,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 +276,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 +325,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,6 +369,11 @@ bool PicoSCPIServer::OnQuery( } } break; + + default: + { + SendReply("0"); + } } } else @@ -364,28 +417,41 @@ vector PicoSCPIServer::GetSampleRates() //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 + //TODO: Use API-call to obtain some neat and evenly spaced values size_t vec[] = { 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 + 10, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131027, 262144, 524288, 1048576, + 2097152, 4194304, 8388608, 16777216 + //14, 29, 54, 104, 129, 254, 504, 629, 1254, 2504, 3129, 5004, 6254, 12504, 15629, 25004, 31254, + //62504, 125004, 156254, 250004, 312504, 500004, 625004, 1000004, 1562504 }; 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; } @@ -414,7 +480,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 +490,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 +515,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 +554,105 @@ 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; + } + 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; + } + 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; + } + else + { + g_awgOn = false; + ReconfigAWG(); + } } else if(args.size() == 1) @@ -487,21 +661,17 @@ bool PicoSCPIServer::OnCommand( { lock_guard lock(g_mutex); g_awgFreq = stof(args[0]); - switch(g_pico_type) + 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 +687,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, SIG_GEN_BUFFER_SIZE, (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, SIG_GEN_BUFFER_SIZE, (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, SIG_GEN_BUFFER_SIZE, (double) duty); + else + LogError("PICO5000A DUTY TODO code\n"); + } + break; + + case 6: { auto status = ps6000aSigGenWaveformDutyCycle(g_hScope, duty); if(status != PICO_OK) @@ -543,6 +732,7 @@ bool PicoSCPIServer::OnCommand( } break; } + ReconfigAWG(); } else if(cmd == "OFFS") @@ -565,61 +755,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()) + { + LogError("Invalid waveform type: %s\n", args[0].c_str()); + return true; + } + + switch(g_series) { - case PICO3000A: + 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, SIG_GEN_BUFFER_SIZE, 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, SIG_GEN_BUFFER_SIZE, 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, SIG_GEN_BUFFER_SIZE, 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 +864,155 @@ 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 -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, SIG_GEN_BUFFER_SIZE, &delta); + if(status != PICO_OK) + LogError("ps3000aSigGenFrequencyToPhase failed, code 0x%x\n", status); + status = ps4000aSetSigGenArbitrary( + g_hScope, + g_awgOffset*1e6, + g_awgRange*1e6*2, + delta, + delta, + 0, + 0, + g_arbitraryWaveform, + SIG_GEN_BUFFER_SIZE, + 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, + g_awgOffset*1e6, //Offset Voltage in µV + g_awgRange *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 PICO6000A: + 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, SIG_GEN_BUFFER_SIZE, &delta); + if(status != PICO_OK) + LogError("ps5000aSigGenFrequencyToPhase failed, code 0x%x\n", status); + status = ps5000aSetSigGenArbitrary( + g_hScope, + g_awgOffset*1e6, + g_awgRange*1e6*2, + delta, + delta, + 0, + 0, + g_arbitraryWaveform, + SIG_GEN_BUFFER_SIZE, + 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, + g_awgOffset*1e6, //Offset Voltage in µV + g_awgRange *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 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 +1356,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 +1396,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 PICO6000A: + 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 6: { auto status = ps6000aSetDigitalPortOff(g_hScope, podId); if(status != PICO_OK) @@ -954,102 +1460,299 @@ void PicoSCPIServer::SetAnalogRange(size_t chIndex, double range_V) { lock_guard lock(g_mutex); size_t channelId = chIndex & 0xff; - auto range = range_V; + //range_V is peak-to-peak whereas the Pico modes are V-peak, + //i.e. PS5000_20V = +-20V = 40Vpp = 'range_V = 40' + auto range = range_V/2; - //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) + switch(g_series) { - 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; + case 3: + { + //3000D series uses passive probes only, 20mV to 20V, no 50 ohm mode available + if(range > 10) + { + g_range_3000a[channelId] = PS3000A_20V; + g_roundedRange[channelId] = 20; + } + else if(range > 5) + { + g_range_3000a[channelId] = PS3000A_10V; + g_roundedRange[channelId] = 10; + } + else if(range > 2) + { + g_range_3000a[channelId] = PS3000A_5V; + g_roundedRange[channelId] = 5; + } + else if(range > 1) + { + g_range_3000a[channelId] = PS3000A_2V; + g_roundedRange[channelId] = 2; + } + else if(range > 0.5) + { + g_range_3000a[channelId] = PS3000A_1V; + g_roundedRange[channelId] = 1; + } + else if(range > 0.2) + { + g_range_3000a[channelId] = PS3000A_500MV; + g_roundedRange[channelId] = 0.5; + } + else if(range > 0.1) + { + g_range_3000a[channelId] = PS3000A_200MV; + g_roundedRange[channelId] = 0.2; + } + else if(range > 0.05) + { + g_range_3000a[channelId] = PS3000A_100MV; + g_roundedRange[channelId] = 0.1; + } + else if(range > 0.02) + { + 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 > 20) + { + g_range_4000a[channelId] = PS4000A_50V; + g_range[channelId] = PICO_X1_PROBE_50V; + g_roundedRange[channelId] = 50; + } + else if(range > 10) + { + g_range_4000a[channelId] = PS4000A_20V; + g_range[channelId] = PICO_X1_PROBE_20V; + g_roundedRange[channelId] = 20; + } + else if(range > 5) + { + g_range_4000a[channelId] = PS4000A_10V; + g_range[channelId] = PICO_X1_PROBE_10V; + g_roundedRange[channelId] = 10; + } + else if(range > 2) + { + g_range_4000a[channelId] = PS4000A_5V; + g_range[channelId] = PICO_X1_PROBE_5V; + g_roundedRange[channelId] = 5; + } + else if(range > 1) + { + g_range_4000a[channelId] = PS4000A_2V; + g_range[channelId] = PICO_X1_PROBE_2V; + g_roundedRange[channelId] = 2; + } + else if(range > 0.5) + { + g_range_4000a[channelId] = PS4000A_1V; + g_range[channelId] = PICO_X1_PROBE_1V; + g_roundedRange[channelId] = 1; + } + else if(range > 0.2) + { + g_range_4000a[channelId] = PS4000A_500MV; + g_range[channelId] = PICO_X1_PROBE_500MV; + g_roundedRange[channelId] = 0.5; + } + else if(range > 0.1) + { + g_range_4000a[channelId] = PS4000A_200MV; + g_range[channelId] = PICO_X1_PROBE_200MV; + g_roundedRange[channelId] = 0.2; + } + else if(range > 0.05) + { + g_range_4000a[channelId] = PS4000A_100MV; + g_range[channelId] = PICO_X1_PROBE_100MV; + g_roundedRange[channelId] = 0.1; + } + else if(range > 0.02) + { + g_range_4000a[channelId] = PS4000A_50MV; + g_range[channelId] = PICO_X1_PROBE_50MV; + g_roundedRange[channelId] = 0.05; + } + else if(range > 0.01) + { + 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 > 10) + { + g_range_5000a[channelId] = PS5000A_20V; + g_roundedRange[channelId] = 20; + } + else if(range > 5) + { + g_range_5000a[channelId] = PS5000A_10V; + g_roundedRange[channelId] = 10; + } + else if(range > 2) + { + g_range_5000a[channelId] = PS5000A_5V; + g_roundedRange[channelId] = 5; + } + else if(range > 1) + { + g_range_5000a[channelId] = PS5000A_2V; + g_roundedRange[channelId] = 2; + } + else if(range > 0.5) + { + g_range_5000a[channelId] = PS5000A_1V; + g_roundedRange[channelId] = 1; + } + else if(range > 0.2) + { + g_range_5000a[channelId] = PS5000A_500MV; + g_roundedRange[channelId] = 0.5; + } + else if(range > 0.1) + { + g_range_5000a[channelId] = PS5000A_200MV; + g_roundedRange[channelId] = 0.2; + } + else if(range > 0.05) + { + g_range_5000a[channelId] = PS5000A_100MV; + g_roundedRange[channelId] = 0.1; + } + else if(range > 0.02) + { + g_range_5000a[channelId] = PS5000A_50MV; + g_roundedRange[channelId] = 0.05; + } + else if(range > 0.01) + { + 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 = min(range, 5.0); + + if(range > 100) + { + g_range[channelId] = PICO_X1_PROBE_200V; + g_roundedRange[channelId] = 200; + } + else if(range > 50) + { + g_range[channelId] = PICO_X1_PROBE_100V; + g_roundedRange[channelId] = 100; + } + else if(range > 20) + { + g_range[channelId] = PICO_X1_PROBE_50V; + g_roundedRange[channelId] = 50; + } + else if(range > 10) + { + g_range[channelId] = PICO_X1_PROBE_20V; + g_roundedRange[channelId] = 20; + } + else if(range > 5) + { + g_range[channelId] = PICO_X1_PROBE_10V; + g_roundedRange[channelId] = 10; + } + else if(range > 2) + { + g_range[channelId] = PICO_X1_PROBE_5V; + g_roundedRange[channelId] = 5; + } + else if(range > 1) + { + g_range[channelId] = PICO_X1_PROBE_2V; + g_roundedRange[channelId] = 2; + } + else if(range > 0.5) + { + g_range[channelId] = PICO_X1_PROBE_1V; + g_roundedRange[channelId] = 1; + } + else if(range > 0.2) + { + g_range[channelId] = PICO_X1_PROBE_500MV; + g_roundedRange[channelId] = 0.5; + } + else if(range > 0.1) + { + g_range[channelId] = PICO_X1_PROBE_200MV; + g_roundedRange[channelId] = 0.2; + } + else if(range >= 0.05) + { + g_range[channelId] = PICO_X1_PROBE_100MV; + g_roundedRange[channelId] = 0.1; + } + else if(range >= 0.02) + { + g_range[channelId] = PICO_X1_PROBE_50MV; + g_roundedRange[channelId] = 0.05; + } + else if(range >= 0.01) + { + 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; } + //SetAnalogOffset(channelId, g_offset[channelId]); + 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 +1767,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 +1799,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 +1832,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 +1865,98 @@ 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) + //TODO: + //!! 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 + //!! A different implementation is needed for: + //!! PicoScope 3000A and 3000B Series 2-Channel USB 2.0 Oscilloscopes + //!! And another one for: + //!! PicoScope 3000 Series USB 2.0 MSOs + if(period_ns < 2) timebase = 0; + else if(period_ns < 8) + timebase = round(log(1e9/rate_hz)/log(2)); else - timebase = round(log(clkdiv)/log(2)); + timebase = round((125e6/rate_hz)+2); } break; - case PICO6000A: + case 4: + { + //Convert sample rate to sample period + g_sampleInterval = 1e15 / rate_hz; + + //Find closest timebase setting + //TODO add support for 4444 + 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 6: { //Convert sample rate to sample period g_sampleInterval = 1e15 / rate_hz; @@ -1159,6 +1968,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 +1985,12 @@ 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; } void PicoSCPIServer::SetSampleDepth(uint64_t depth) @@ -1278,14 +2097,54 @@ 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(); + return; + } + 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(); + return; + } + 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; //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. @@ -1296,12 +2155,17 @@ void UpdateChannel(size_t chan) } 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. @@ -1341,7 +2205,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 +2222,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) { @@ -1487,7 +2351,84 @@ void UpdateTrigger(bool force) } break; - case PICO6000A: + case 4: + { + if(g_triggerChannel == PICO_TRIGGER_AUX) + { + /* TODO PICO_TRIGGER_AUX PICO3000A similarly to PICO6000A... */ + /* API is same as 6000a API */ + int ret = ps4000aSetSimpleTrigger( + g_hScope, + 1, + (PS4000A_CHANNEL)PICO_TRIGGER_AUX, + 0, + (enPS4000AThresholdDirection)g_triggerDirection, + delay, + timeout); + if(ret != PICO_OK) + LogError("ps4000aSetSimpleTrigger failed: %x\n", ret); + } + 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 + { + /* TODO PICO3000A Trigger Digital Chan */ + LogError("PICO4000A Trigger Digital Chan code TODO\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, + round(trig_code), + (enPS5000AThresholdDirection)g_triggerDirection, // same as 6000a api + delay, + timeout); + if(ret != PICO_OK) + LogError("ps5000aSetSimpleTrigger failed: %x\n", ret); + } + else + { + /* TODO PICO3000A Trigger Digital Chan */ + LogError("PICO5000A Trigger Digital Chan code TODO\n"); + } + } + break; + + case 6: { if(g_triggerChannel == PICO_TRIGGER_AUX) { @@ -1615,13 +2556,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,15 +2582,22 @@ 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 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 PICO6000A: + case 6: return ps6000aRunBlock(g_hScope, nPreTrigger, nPostTrigger, g_timebase, NULL, 0, NULL, NULL); default: @@ -1700,12 +2656,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 +2671,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 +2704,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/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..888e0c6 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); @@ -114,10 +122,16 @@ void WaveformServerThread() //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,10 +179,23 @@ 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? if(PICO_OK != status) @@ -180,7 +213,9 @@ void WaveformServerThread() if(g_msoPodEnabledDuringArm[i]) numchans ++; } - + //LogVerbose("numSamples_int: %d\n", numSamples_int); + //LogVerbose("g_captureMemDepth: %lld\n", g_captureMemDepth); + //LogVerbose("waveformBuffers: %hs\n", waveformBuffers[0]); } @@ -231,7 +266,7 @@ 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; @@ -300,7 +335,8 @@ float InterpolateTriggerTime(int16_t* buf) { if(g_triggerSampleIndex <= 0) return 0; - + + //TODO trigger scale value depends on ADC setting and is different for EXT trig input float trigscale = g_roundedRange[g_triggerChannel] / 32512; float trigoff = g_offsetDuringArm[g_triggerChannel]; diff --git a/src/ps6000d/main.cpp b/src/ps6000d/main.cpp index fa2b109..270d53c 100644 --- a/src/ps6000d/main.cpp +++ b/src/ps6000d/main.cpp @@ -69,8 +69,8 @@ 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; @@ -87,6 +87,8 @@ int main(int argc, char* argv[]) { //Global settings Severity console_verbosity = Severity::NOTICE; + bool limitChannels = false; + g_series = 0; //Parse command-line arguments uint16_t scpi_port = 5025; @@ -125,7 +127,8 @@ int main(int argc, char* argv[]) } //Set up logging - g_log_sinks.emplace(g_log_sinks.begin(), new ColoredSTDLogSink(console_verbosity)); + //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 @@ -133,29 +136,66 @@ int main(int argc, char* argv[]) auto status = ps6000aOpenUnit(&g_hScope, NULL, PICO_DR_8BIT); if(PICO_OK != status) { - LogNotice("Looking for a PicoScope 3000 series instrument to open...\n"); - status = ps3000aOpenUnit(&g_hScope, NULL); - if(status == PICO_POWER_SUPPLY_NOT_CONNECTED) + LogNotice("Looking for a PicoScope 5000 series instrument to open...\n"); + 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 = ps3000aChangePowerSource(g_hScope, PICO_POWER_SUPPLY_NOT_CONNECTED); + status = ps5000aChangePowerSource(g_hScope, PICO_POWER_SUPPLY_NOT_CONNECTED); } if(PICO_OK != status) { - LogError("Failed to open unit (code %d)\n", status); - return 1; + LogNotice("Looking for a PicoScope 4000 series instrument to open...\n"); + 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(PICO_OK != status) + { + LogNotice("Looking for a PicoScope 3000 series instrument to open...\n"); + 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(PICO_OK != status) + { + LogError("Failed to open unit (code %d)\n", status); + return 1; + } + else + { + g_series = 3; + picoGetUnitInfo = ps3000aGetUnitInfo; + } + } + else + { + g_series = 4; + picoGetUnitInfo = ps4000aGetUnitInfo; + } } else { - g_pico_type = PICO3000A; - picoGetUnitInfo = ps3000aGetUnitInfo; + g_series = 5; + picoGetUnitInfo = ps5000aGetUnitInfo; } } else { - g_pico_type = PICO6000A; + g_series = 6; picoGetUnitInfo = ps6000aGetUnitInfo; } @@ -244,17 +284,27 @@ int main(int argc, char* argv[]) LogVerbose("IPP version: %s\n", buf); } - 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; } 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]; From 0f1a529cb961efb092c79df9b2362d9ab22150ec Mon Sep 17 00:00:00 2001 From: Lasse Beyer Date: Sat, 1 Nov 2025 16:00:17 +0100 Subject: [PATCH 04/12] Add digital trigger for 3000 and 5000 Added support for digital trigger on 5000 series (tested) and 3000 series (untested). 4000 does not have a digital option. Added the missing timebase calculations for formerly unsupported models. Fixed a bug where the forced trigger was not properly reset after every acquisition. --- README.md | 21 ++-- src/ps6000d/PicoSCPIServer.cpp | 155 ++++++++++++++++++--------- src/ps6000d/WaveformServerThread.cpp | 20 +++- 3 files changed, 133 insertions(+), 63 deletions(-) diff --git a/README.md b/README.md index 11a814f..c0512fc 100644 --- a/README.md +++ b/README.md @@ -29,21 +29,21 @@ As of October 2025, there should be a total of **72** different devices supporte | ps3000a | ps4000a | ps5000a | ps6000a | | :---- | :---- | :---- | :---- | -| 3203D | ~~4444~~ | 5242A | 6403E | +| 3203D | 4444 | 5242A | 6403E | | 3203D MSO | **4824** | 5242B | 6404E | -| ~~3204A~~ | 4224A | 5242D | 6405E | -| ~~3204B~~ | 4424A | 5242D MSO | 6406E | -| ~~3204 MSO~~ | 4824A | 5243A | 6424E | +| 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** | +| 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 | | +| 3206A | | 5442A | | +| 3206B | | 5442B | | +| 3206 MSO | | 5442D | | | 3206D | | 5442D MSO | | | 3206D MSO | | 5443A | | | 3207A | | 5443B | | @@ -64,7 +64,6 @@ As of October 2025, there should be a total of **72** different devices supporte | 3406D MSO | | | | Models shown in bold were used for, and tested during, the development of the pico-bridge. -Models striked out need some additional work and are not fully supported yet. ## How to compile diff --git a/src/ps6000d/PicoSCPIServer.cpp b/src/ps6000d/PicoSCPIServer.cpp index 09e32d8..2e695d8 100644 --- a/src/ps6000d/PicoSCPIServer.cpp +++ b/src/ps6000d/PicoSCPIServer.cpp @@ -661,6 +661,10 @@ bool PicoSCPIServer::OnCommand( { lock_guard lock(g_mutex); g_awgFreq = stof(args[0]); + //Frequency must not be zero + if(g_awgFreq<1e-3) + g_awgFreq = 1; + switch(g_series) { case 3: @@ -1439,6 +1443,7 @@ void PicoSCPIServer::SetChannelEnabled(size_t chIndex, bool enabled) //We need to allocate new buffers for this channel g_memDepthChanged = true; + UpdateTrigger(); //TESTING lasse } void PicoSCPIServer::SetAnalogCoupling(size_t chIndex, const std::string& coupling) @@ -1874,21 +1879,41 @@ void PicoSCPIServer::SetSampleRate(uint64_t rate_hz) period_ns = 1e9 / rate_hz; //Find closest timebase setting - //TODO: - //!! 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 - //!! A different implementation is needed for: - //!! PicoScope 3000A and 3000B Series 2-Channel USB 2.0 Oscilloscopes - //!! And another one for: - //!! PicoScope 3000 Series USB 2.0 MSOs - if(period_ns < 2) - timebase = 0; - else if(period_ns < 8) - timebase = round(log(1e9/rate_hz)/log(2)); + 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((125e6/rate_hz)+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; @@ -1896,10 +1921,20 @@ void PicoSCPIServer::SetSampleRate(uint64_t rate_hz) { //Convert sample rate to sample period g_sampleInterval = 1e15 / rate_hz; + period_ns = 1e9 / rate_hz; //Find closest timebase setting - //TODO add support for 4444 - timebase = trunc((80e6/rate_hz)-1); + 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; @@ -1991,6 +2026,7 @@ void PicoSCPIServer::SetSampleRate(uint64_t rate_hz) g_timebase = timebase; g_sampleRate = rate_hz; + UpdateTrigger(); //TESTING lasse } void PicoSCPIServer::SetSampleDepth(uint64_t depth) @@ -2192,6 +2228,7 @@ void UpdateTrigger(bool force) { timeout = 1; g_lastTriggerWasForced = true; + g_triggerOneShot = true; //TESTING lasse } else g_lastTriggerWasForced = false; @@ -2309,36 +2346,34 @@ 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, - PICO_CLEAR_ALL); + 0); //Set up new conditions int ntrig = g_triggerChannel - g_numChannels; - int trigpod = ntrig / 8; + //int trigpod = ntrig / 8; int triglane = ntrig % 8; - PICO_CONDITION cond; - cond.source = static_cast(PICO_PORT0 + trigpod); - cond.condition = PICO_CONDITION_TRUE; - ps6000aSetTriggerChannelConditions( + 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, - PICO_ADD); + 1); //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( + 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, - cond.source, &dirs, 1); @@ -2346,7 +2381,6 @@ 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; @@ -2355,18 +2389,7 @@ void UpdateTrigger(bool force) { if(g_triggerChannel == PICO_TRIGGER_AUX) { - /* TODO PICO_TRIGGER_AUX PICO3000A similarly to PICO6000A... */ - /* API is same as 6000a API */ - int ret = ps4000aSetSimpleTrigger( - g_hScope, - 1, - (PS4000A_CHANNEL)PICO_TRIGGER_AUX, - 0, - (enPS4000AThresholdDirection)g_triggerDirection, - delay, - timeout); - if(ret != PICO_OK) - LogError("ps4000aSetSimpleTrigger failed: %x\n", ret); + LogError("PS4000 has no external trigger input\n"); } else if(g_triggerChannel < g_numChannels) { @@ -2384,8 +2407,7 @@ void UpdateTrigger(bool force) } else { - /* TODO PICO3000A Trigger Digital Chan */ - LogError("PICO4000A Trigger Digital Chan code TODO\n"); + LogError("PS4000 has no digital trigger option\n"); } } @@ -2422,8 +2444,39 @@ void UpdateTrigger(bool force) } else { - /* TODO PICO3000A Trigger Digital Chan */ - LogError("PICO5000A Trigger Digital Chan code TODO\n"); + //Remove old trigger conditions + ps5000aSetTriggerChannelConditionsV2( + g_hScope, + NULL, + 0, + PS5000A_CLEAR); + + //Set up new conditions + int ntrig = g_triggerChannel - g_numChannels; + int trigpod = ntrig / 8; + int triglane = ntrig % 8; + PS5000A_CONDITION cond; + cond.source = static_cast(PS5000A_DIGITAL_PORT0 + trigpod); + cond.condition = PS5000A_CONDITION_TRUE; + ps5000aSetTriggerChannelConditionsV2( + g_hScope, + &cond, + 1, + PS5000A_ADD); + + //Set up configuration on the selected channel + 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, + &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; diff --git a/src/ps6000d/WaveformServerThread.cpp b/src/ps6000d/WaveformServerThread.cpp index 888e0c6..07426f0 100644 --- a/src/ps6000d/WaveformServerThread.cpp +++ b/src/ps6000d/WaveformServerThread.cpp @@ -112,12 +112,16 @@ void WaveformServerThread() if(PICO_OK != status) LogFatal("ps6000aStop failed (code 0x%x)\n", status); +auto now = chrono::system_clock::now(); +auto duration = now.time_since_epoch(); +uint64_t ts = chrono::duration_cast(duration).count(); +LogVerbose("%lli -- psIsReady: %i\n", ts, ready); //Verify it's actually stopped //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) @@ -197,7 +201,12 @@ void WaveformServerThread() numSamples = numSamples_int; } if(status == PICO_NO_SAMPLES_AVAILABLE) + { +LogVerbose("PICO_NO_SAMPLES_AVAILABLE\n"); + g_memDepthChanged = true; //TESTING lasse flush buffers and disarm + g_triggerArmed = false; //TESTING lasse on adding 2nd channel continue; // state changed while mutex was unlocked? + } if(PICO_OK != status) LogFatal("psXXXXGetValues (code 0x%x)\n", status); @@ -269,6 +278,11 @@ void WaveformServerThread() chdrs.scale = g_roundedRange[i] / g_scaleValue; chdrs.offset = g_offsetDuringArm[i]; chdrs.trigphase = trigphase; + +auto now = chrono::system_clock::now(); +auto duration = now.time_since_epoch(); +uint64_t ts = chrono::duration_cast(duration).count(); +//LogVerbose("%lli Channel: %lli, numSamples: %lli\n", ts, i, numSamples); //Send channel headers if(!client.SendLooped((uint8_t*)&chdrs, sizeof(chdrs))) @@ -308,7 +322,10 @@ void WaveformServerThread() //Re-arm the trigger if doing repeating triggers if(g_triggerOneShot) + { g_triggerArmed = false; +LogVerbose("Disarm g_triggerOneShot\n"); + } else { if(g_captureMemDepth != g_memDepth) @@ -316,6 +333,7 @@ void WaveformServerThread() //Restart StartCapture(false); +LogVerbose("Restart Capture\n"); } } } From a1b8c9add0c55bf151e5df855e2a529b45aa0ac4 Mon Sep 17 00:00:00 2001 From: Lasse Beyer Date: Tue, 4 Nov 2025 19:11:52 +0100 Subject: [PATCH 05/12] Added bandwidth limiter functionality --- README.md | 2 +- src/ps6000d/PicoSCPIServer.cpp | 114 +- src/ps6000d/PicoSCPIServer.cpp.old | 2784 ++++++++++++++++++++++++++ src/ps6000d/PicoSCPIServer.h | 1 + src/ps6000d/PicoSCPIServer.h.old | 96 + src/ps6000d/WaveformServerThread.cpp | 26 +- 6 files changed, 2999 insertions(+), 24 deletions(-) create mode 100644 src/ps6000d/PicoSCPIServer.cpp.old create mode 100644 src/ps6000d/PicoSCPIServer.h.old diff --git a/README.md b/README.md index c0512fc..b0451ed 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Socket servers for Pico Technology instruments allowing remote access via libsco * Nickname: (choose a display name for your scope.) * Driver: **pico** * Transport: **twinlan** - * Path: **localhost:5024:5025** + * 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). diff --git a/src/ps6000d/PicoSCPIServer.cpp b/src/ps6000d/PicoSCPIServer.cpp index 2e695d8..3249207 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 @@ -376,6 +382,44 @@ bool PicoSCPIServer::OnQuery( } } } + + 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()); @@ -1016,6 +1060,20 @@ bool PicoSCPIServer::OnCommand( } //TODO: bandwidth limiter + else if( (cmd == "BWLIM") && (args.size() == 1) ) + { + //Extract channel ID from subject and clamp bounds + size_t channelId = 0; + if(isalpha(subject[0])) + { + channelId = min(static_cast(subject[0] - 'A'), g_numChannels); + //channelIsDigital = false; + } + lock_guard lock(g_mutex); + + int freq_mhz = stoi(args[0]); + SetChannelBandwidthLimiter(channelId, freq_mhz); + } else { @@ -1031,6 +1089,54 @@ 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 */ @@ -1467,6 +1573,7 @@ void PicoSCPIServer::SetAnalogRange(size_t chIndex, double range_V) 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' + //auto range = range_V/2; auto range = range_V/2; switch(g_series) @@ -1748,8 +1855,6 @@ void PicoSCPIServer::SetAnalogRange(size_t chIndex, double range_V) break; } - //SetAnalogOffset(channelId, g_offset[channelId]); - UpdateChannel(channelId); //Update trigger if this is the trigger channel. @@ -2435,8 +2540,8 @@ void UpdateTrigger(bool force) g_hScope, 1, (PS5000A_CHANNEL)g_triggerChannel, - round(trig_code), - (enPS5000AThresholdDirection)g_triggerDirection, // same as 6000a api + trunc(trig_code), + (PS5000A_THRESHOLD_DIRECTION)g_triggerDirection, delay, timeout); if(ret != PICO_OK) @@ -2663,6 +2768,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; //TESTING lasse Stop(); UpdateTrigger(); } diff --git a/src/ps6000d/PicoSCPIServer.cpp.old b/src/ps6000d/PicoSCPIServer.cpp.old new file mode 100644 index 0000000..2e695d8 --- /dev/null +++ b/src/ps6000d/PicoSCPIServer.cpp.old @@ -0,0 +1,2784 @@ +/*********************************************************************************************************************** +* * +* ps6000d * +* * +* Copyright (c) 2012-2022 Andrew D. Zonenberg * +* All rights reserved. * +* * +* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * +* following conditions are met: * +* * +* * Redistributions of source code must retain the above copyright notice, this list of conditions, and the * +* following disclaimer. * +* * +* * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * +* following disclaimer in the documentation and/or other materials provided with the distribution. * +* * +* * Neither the name of the author nor the names of any contributors may be used to endorse or promote products * +* derived from this software without specific prior written permission. * +* * +* THIS SOFTWARE IS PROVIDED BY THE AUTHORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * +* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * +* THE AUTHORS BE HELD LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * +* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR * +* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * +* POSSIBILITY OF SUCH DAMAGE. * +* * +***********************************************************************************************************************/ + +/** + @file + @author Andrew D. Zonenberg + @brief SCPI server. Control plane traffic only, no waveform data. + + SCPI commands supported: + + *IDN? + Returns a standard SCPI instrument identification string + + CHANS? + Returns the number of channels on the instrument. + + [1|2]D:PRESENT? + Returns 1 = MSO pod present, 0 = MSO pod not present + + [chan]:COUP [DC1M|AC1M|DC50] + Sets channel coupling + + [chan]:HYS [mV] + Sets MSO channel hysteresis to mV millivolts + + [chan]:OFF + Turns the channel off + + [chan]:OFFS [num] + Sets channel offset to num volts + + [chan]:ON + Turns the channel on + + [chan]:RANGE [num] + Sets channel full-scale range to num volts + + [chan]:THRESH [mV] + Sets MSO channel threshold to mV millivolts + + BITS [num] + Sets ADC bit depth + + DEPTH [num] + Sets memory depth + + DEPTHS? + Returns the set of available memory depths + + EXIT + Terminates the connection + + FORCE + Forces a single acquisition + + RATE [num] + Sets sample rate + + RATES? + Returns a comma separated list of sampling rates (in femtoseconds) + + SINGLE + Arms the trigger in one-shot mode + + START + Arms the trigger + + STOP + Disarms the trigger + + TRIG:DELAY [delay] + Sets trigger delay (in fs) + + TRIG:EDGE:DIR [direction] + Sets trigger direction. Legal values are RISING, FALLING, or ANY. + + TRIG:LEV [level] + Selects trigger level (in volts) + + TRIG:SOU [chan] + Selects the channel as the trigger source + + TODO: SetDigitalPortInteractionCallback to determine when pods are connected/removed + + AWG:DUTY [duty cycle] + Sets duty cycle of function generator output + + AWG:FREQ [freq] + Sets function generator frequency, in Hz + + AWG:OFF [offset] + Sets offset of the function generator output + + AWG:RANGE [range] + Sets p-p voltage of the function generator output + + AWG:SHAPE [waveform type] + Sets waveform type + + AWG:START + Starts the function generator + + AWG:STOP + Stops the function generator + */ + +#include "ps6000d.h" +#include "PicoSCPIServer.h" +#include +#include + +#define __USE_MINGW_ANSI_STDIO 1 // Required for MSYS2 mingw64 to support format "%z" ... + +#define FS_PER_SECOND 1e15 + +using namespace std; + +//Channel state +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_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 +map g_channelOnDuringArm; +int64_t g_sampleIntervalDuringArm = 0; +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; +bool g_memDepthChanged = false; + +//Trigger state (for now, only simple single-channel trigger supported) +int64_t g_triggerDelay = 0; +PICO_THRESHOLD_DIRECTION g_triggerDirection = PICO_RISING; +float g_triggerVoltage = 0; +size_t g_triggerChannel = 0; +size_t g_triggerSampleIndex; + +//Thresholds for MSO pods +size_t g_numDigitalPods = 2; +int16_t g_msoPodThreshold[2][8] = { {0}, {0} }; +PICO_DIGITAL_PORT_HYSTERESIS g_msoHysteresis[2] = {PICO_NORMAL_100MV, PICO_NORMAL_100MV}; +bool g_msoPodEnabled[2] = {false}; +bool g_msoPodEnabledDuringArm[2] = {false}; + +bool EnableMsoPod(size_t npod); + +bool g_lastTriggerWasForced = false; + +std::mutex g_mutex; + +//AWG config +float g_awgRange = 0; +float g_awgOffset = 0; +bool g_awgOn = false; +double g_awgFreq = 1000; +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_WHITE_NOISE, PS5000A_ES_OFF}}, + {"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 +}; + +#define SIG_GEN_BUFFER_SIZE 8192 //TODO: allow model specific/variable buffer size. Must be power of 2 for most AWGs PS3000: 2^13, PS3207B: 2^14 PS3206B: 2^15 +int16_t* g_arbitraryWaveform = new int16_t[SIG_GEN_BUFFER_SIZE]; // +void GenerateSquareWave(int16_t* &waveform, size_t bufferSize, double dutyCycle, int16_t amplitude = 32767); +void ReconfigAWG(); + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Construction / destruction + +PicoSCPIServer::PicoSCPIServer(ZSOCKET sock) + : BridgeSCPIServer(sock) +{ + //external trigger is fixed range of -1 to +1V + g_roundedRange[PICO_TRIGGER_AUX] = 2; + g_offset[PICO_TRIGGER_AUX] = 0; +} + +PicoSCPIServer::~PicoSCPIServer() +{ + LogVerbose("Client disconnected\n"); + + //Disable all channels when a client disconnects to put the scope in a "safe" state + for(auto& it : g_channelOn) + { + switch(g_series) + { + case 3: + ps3000aSetChannel(g_hScope, (PS3000A_CHANNEL)it.first, 0, PS3000A_DC, PS3000A_1V, 0.0f); + break; + 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; + } + + it.second = false; + g_channelOnDuringArm[it.first] = false; + } + + for(int i=0; i<2; i++) + { + switch(g_series) + { + case 3: + ps3000aSetDigitalPort(g_hScope, (PS3000A_DIGITAL_PORT)(PICO_PORT0 + i), 0, 0); + break; + case 5: + ps5000aSetDigitalPort(g_hScope, (PS5000A_CHANNEL)(PICO_PORT0 + i), 0, 0); + break; + case 6: + ps6000aSetDigitalPortOff(g_hScope, (PICO_CHANNEL)(PICO_PORT0 + i)); + break; + } + g_msoPodEnabled[i] = false; + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Command parsing + +bool PicoSCPIServer::OnQuery( + const string& line, + const string& subject, + const string& cmd) +{ + //Extract channel ID from subject and clamp bounds + size_t channelId = 0; + //size_t laneId = 0; + //bool channelIsDigital = false; + if(isalpha(subject[0])) + { + channelId = min(static_cast(subject[0] - 'A'), g_numChannels); + //channelIsDigital = false; + } + else if(isdigit(subject[0])) + { + channelId = min(subject[0] - '0', 2) - 1; + //channelIsDigital = true; + //if(subject.length() >= 3) + // laneId = min(subject[2] - '0', 7); + } + + if(BridgeSCPIServer::OnQuery(line, subject, cmd)) + { + return true; + } + else if(cmd == "PRESENT") + { + lock_guard lock(g_mutex); + + switch(g_series) + { + case 3: + case 5: + { + //All MSO models have two pods. + if(g_model.find("MSO") != string::npos) + { + SendReply("1"); + } + else + { + SendReply("0"); + } + break; + } + break; + + 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. + PICO_CHANNEL podId = (PICO_CHANNEL)(PICO_PORT0 + channelId); + auto status = ps6000aSetDigitalPortOn( + g_hScope, + podId, + g_msoPodThreshold[channelId], + 8, + g_msoHysteresis[channelId]); + + if(status == PICO_NO_MSO_POD_CONNECTED) + { + SendReply("0"); + } + else + { + // The pod is here. If we don't need it on, shut it back off + if(!g_msoPodEnabled[channelId]) + ps6000aSetDigitalPortOff(g_hScope, podId); + + SendReply("1"); + } + } + break; + + default: + { + SendReply("0"); + } + } + } + else + { + LogDebug("Unrecognized query received: %s\n", line.c_str()); + } + return false; +} + +string PicoSCPIServer::GetMake() +{ + return "Pico Technology"; +} + +string PicoSCPIServer::GetModel() +{ + return g_model; +} + +string PicoSCPIServer::GetSerial() +{ + return g_serial; +} + +string PicoSCPIServer::GetFirmwareVersion() +{ + return g_fwver; +} + +size_t PicoSCPIServer::GetAnalogChannelCount() +{ + return g_numChannels; +} + +vector PicoSCPIServer::GetSampleRates() +{ + vector rates; + + 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 + //TODO: Use API-call to obtain some neat and evenly spaced values + size_t vec[] = + { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131027, 262144, 524288, 1048576, + 2097152, 4194304, 8388608, 16777216 + //14, 29, 54, 104, 129, 254, 504, 629, 1254, 2504, 3129, 5004, 6254, 12504, 15629, 25004, 31254, + //62504, 125004, 156254, 250004, 312504, 500004, 625004, 1000004, 1562504 + }; + for(auto i : vec) + { + double intervalNs; + float intervalNs_f; + uint64_t maxSamples; + int32_t maxSamples_int; + PICO_STATUS status = PICO_RESERVED_1; + + switch(g_series) + { + 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 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; + } + + if(PICO_OK == status) + { + size_t intervalFs = intervalNs * 1e6f; + rates.push_back(FS_PER_SECOND / intervalFs); + } + else if(PICO_INVALID_TIMEBASE == status) + { + //Requested timebase not possible + //This is common and harmless if we ask for e.g. timebase 0 when too many channels are active. + continue; + } + else + LogWarning("GetTimebase failed, code %d / 0x%x\n", status, status); + } + + return rates; +} + +vector PicoSCPIServer::GetSampleDepths() +{ + vector depths; + + lock_guard lock(g_mutex); + double intervalNs; + float intervalNs_f; + uint64_t maxSamples; + int32_t maxSamples_int; + + PICO_STATUS status; + status = PICO_RESERVED_1; + + //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_series) + { + 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 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; + } + + 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 1K samples up to the actual max depth + + for(size_t base = 1000; base < maxSamples; base *= 10) + { + const size_t muls[] = {1, 2, 5}; + for(auto m : muls) + { + size_t depth = m * base; + if(depth < maxSamples) + depths.push_back(depth); + } + } + + depths.push_back(maxSamples); + } + + return depths; +} + +bool PicoSCPIServer::OnCommand( + const string& line, + const string& subject, + const string& cmd, + const vector& args) +{ + //Function generator is different from normal channels + //(uses range/offs commands so must go before normal bridge processing!) + if(subject == "AWG") + { + if(cmd == "START") + { + lock_guard lock(g_mutex); + g_awgOn = true; + ReconfigAWG(); + } + + else if(cmd == "STOP") + { + lock_guard lock(g_mutex); + 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; + } + 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; + } + 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; + } + else + { + g_awgOn = false; + ReconfigAWG(); + } + } + + else if(args.size() == 1) + { + if(cmd == "FREQ") + { + lock_guard lock(g_mutex); + g_awgFreq = stof(args[0]); + //Frequency must not be zero + if(g_awgFreq<1e-3) + g_awgFreq = 1; + + switch(g_series) + { + case 3: + case 4: + case 5: + { + //handled by ReconfigAWG() + } + break; + + case 6: + { + auto status = ps6000aSigGenFrequency(g_hScope, g_awgFreq); + if(status != PICO_OK) + LogError("ps6000aSigGenFrequency failed, code 0x%x (freq=%f)\n", status, g_awgFreq); + } + break; + } + ReconfigAWG(); + } + + else if(cmd == "DUTY") + { + lock_guard lock(g_mutex); + auto duty = stof(args[0]) * 100; + + switch(g_series) + { + case 3: + { + /* 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, SIG_GEN_BUFFER_SIZE, (double) duty); + else + LogError("PICO3000A DUTY TODO code\n"); + } + break; + + 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, SIG_GEN_BUFFER_SIZE, (double) duty); + else + LogError("PICO4000A DUTY TODO code\n"); + } + break; + + 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, SIG_GEN_BUFFER_SIZE, (double) duty); + else + LogError("PICO5000A DUTY TODO code\n"); + } + break; + + case 6: + { + auto status = ps6000aSigGenWaveformDutyCycle(g_hScope, duty); + if(status != PICO_OK) + LogError("ps6000aSigGenWaveformDutyCycle failed, code 0x%x\n", status); + + ReconfigAWG(); + } + break; + } + ReconfigAWG(); + } + + else if(cmd == "OFFS") + { + lock_guard lock(g_mutex); + g_awgOffset = stof(args[0]); + + ReconfigAWG(); + } + + else if(cmd == "RANGE") + { + lock_guard lock(g_mutex); + g_awgRange = stof(args[0]); + + ReconfigAWG(); + } + + else if(cmd == "SHAPE") + { + lock_guard lock(g_mutex); + + auto waveform = g_waveformTypes.find(args[0]); + if(waveform == g_waveformTypes.end()) + { + LogError("Invalid waveform type: %s\n", args[0].c_str()); + return true; + } + + switch(g_series) + { + case 3: + { + 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, SIG_GEN_BUFFER_SIZE, 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; + + 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, SIG_GEN_BUFFER_SIZE, 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 5: + { + if( (args[0] == "RPBS") ) + { + LogError("RPBS generation not supported by 5000 series\n"); + return true; + } + if( (g_awgPS5000AWaveType == PS5000A_SQUARE) ) + { + GenerateSquareWave(g_arbitraryWaveform, SIG_GEN_BUFFER_SIZE, 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(); + + 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()); + } + else + LogError("Unrecognized AWG command %s\n", line.c_str()); + } + else if(BridgeSCPIServer::OnCommand(line, subject, cmd, args)) + return true; + + else if( (cmd == "BITS") && (args.size() == 1) ) + { + lock_guard lock(g_mutex); + switch(g_series) + { + case 3: + { + g_adcBits = 8; + return false; + } + break; + + case 4: + { + if(g_model.find("4444") != string::npos) + { + ps4000aStop(g_hScope); + + //Changing the ADC resolution necessitates reallocation of the buffers + //due to different memory usage. + 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); + } + + if(g_triggerArmed) + StartCapture(false); + //update all active channels + for(size_t i=0; i -5 to 5 V) + if(PICO_OK != status) + LogError("ps3000aSetSigGenBuiltInV2 failed, code 0x%x\n", status); + } + if(g_triggerArmed) + StartCapture(false); + } + break; + + 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, SIG_GEN_BUFFER_SIZE, &delta); + if(status != PICO_OK) + LogError("ps3000aSigGenFrequencyToPhase failed, code 0x%x\n", status); + status = ps4000aSetSigGenArbitrary( + g_hScope, + g_awgOffset*1e6, + g_awgRange*1e6*2, + delta, + delta, + 0, + 0, + g_arbitraryWaveform, + SIG_GEN_BUFFER_SIZE, + 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, + g_awgOffset*1e6, //Offset Voltage in µV + g_awgRange *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, SIG_GEN_BUFFER_SIZE, &delta); + if(status != PICO_OK) + LogError("ps5000aSigGenFrequencyToPhase failed, code 0x%x\n", status); + status = ps5000aSetSigGenArbitrary( + g_hScope, + g_awgOffset*1e6, + g_awgRange*1e6*2, + delta, + delta, + 0, + 0, + g_arbitraryWaveform, + SIG_GEN_BUFFER_SIZE, + 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, + g_awgOffset*1e6, //Offset Voltage in µV + g_awgRange *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 6: + { + auto status = ps6000aSigGenRange(g_hScope, g_awgRange, g_awgOffset); + if(PICO_OK != status) + LogError("ps6000aSigGenRange failed, code 0x%x\n", status); + + 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); + } + break; + } +} + +bool PicoSCPIServer::GetChannelID(const std::string& subject, size_t& id_out) +{ + if(subject == "EX") + { + id_out = PICO_TRIGGER_AUX; + return true; + } + + //Extract channel ID from subject and clamp bounds + size_t channelId = 0; + size_t laneId = 0; + bool channelIsDigital = false; + if(isalpha(subject[0])) + { + channelId = min(static_cast(subject[0] - 'A'), g_numChannels); + channelIsDigital = false; + } + else if(isdigit(subject[0])) + { + channelId = min(subject[0] - '0', 2) - 1; + channelIsDigital = true; + if(subject.length() >= 3) + laneId = min(subject[2] - '0', 7); + } + else + return false; + + //Pack channel IDs into bytes + //Byte 0: channel / pod ID + //Byte 1: lane ID + //Byte 2: digital flag + id_out = channelId; + if(channelIsDigital) + id_out |= 0x800000 | (laneId << 8); + + return true; +} + +BridgeSCPIServer::ChannelType PicoSCPIServer::GetChannelType(size_t channel) +{ + if(channel == PICO_TRIGGER_AUX) + return CH_EXTERNAL_TRIGGER; + else if(channel > 0xff) + return CH_DIGITAL; + else + return CH_ANALOG; +} + +void PicoSCPIServer::AcquisitionStart(bool oneShot) +{ + lock_guard lock(g_mutex); + + if(g_triggerArmed) + { + LogVerbose("Ignoring START command because trigger is already armed\n"); + return; + } + + //Make sure we've got something to capture + bool anyChannels = false; + for(size_t i=0; i lock(g_mutex); + + //Clear out any old trigger config + if(g_triggerArmed) + { + Stop(); + g_triggerArmed = false; + } + + UpdateTrigger(true); + StartCapture(true, true); +} + +void PicoSCPIServer::AcquisitionStop() +{ + lock_guard lock(g_mutex); + + Stop(); + + //Convert any in-progress trigger to one shot. + //This ensures that if a waveform is halfway through being downloaded, we won't re-arm the trigger after it finishes. + g_triggerOneShot = true; + g_triggerArmed = false; +} + +void PicoSCPIServer::SetChannelEnabled(size_t chIndex, bool enabled) +{ + lock_guard lock(g_mutex); + + if(GetChannelType(chIndex) == CH_DIGITAL) + { + int podIndex = (chIndex & 0xff); + PICO_CHANNEL podId = (PICO_CHANNEL)(PICO_PORT0 + podIndex); + + if(enabled) + { + switch(g_series) + { + case 3: + { + auto status = ps3000aSetDigitalPort(g_hScope, (PS3000A_DIGITAL_PORT)podId, 1, g_msoPodThreshold[podIndex][0]); + if(status != PICO_OK) + 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 6: + { + auto status = ps6000aSetDigitalPortOn( + g_hScope, + podId, + g_msoPodThreshold[podIndex], + 8, + g_msoHysteresis[podIndex]); + if(status != PICO_OK) + LogError("ps6000aSetDigitalPortOn failed with code %x\n", status); + else + g_msoPodEnabled[podIndex] = true; + } + break; + } + } + else + { + switch(g_series) + { + case 3: + { + auto status = ps3000aSetDigitalPort(g_hScope, (PS3000A_DIGITAL_PORT)podId, 0, 0); + if(status != PICO_OK) + 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 6: + { + auto status = ps6000aSetDigitalPortOff(g_hScope, podId); + if(status != PICO_OK) + LogError("ps6000aSetDigitalPortOff failed with code %x\n", status); + else + g_msoPodEnabled[podIndex] = false; + } + break; + } + } + } + else + { + int chId = chIndex & 0xff; + g_channelOn[chId] = enabled; + UpdateChannel(chId); + } + + //We need to allocate new buffers for this channel + g_memDepthChanged = true; + 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' + auto range = range_V/2; + + switch(g_series) + { + case 3: + { + //3000D series uses passive probes only, 20mV to 20V, no 50 ohm mode available + if(range > 10) + { + g_range_3000a[channelId] = PS3000A_20V; + g_roundedRange[channelId] = 20; + } + else if(range > 5) + { + g_range_3000a[channelId] = PS3000A_10V; + g_roundedRange[channelId] = 10; + } + else if(range > 2) + { + g_range_3000a[channelId] = PS3000A_5V; + g_roundedRange[channelId] = 5; + } + else if(range > 1) + { + g_range_3000a[channelId] = PS3000A_2V; + g_roundedRange[channelId] = 2; + } + else if(range > 0.5) + { + g_range_3000a[channelId] = PS3000A_1V; + g_roundedRange[channelId] = 1; + } + else if(range > 0.2) + { + g_range_3000a[channelId] = PS3000A_500MV; + g_roundedRange[channelId] = 0.5; + } + else if(range > 0.1) + { + g_range_3000a[channelId] = PS3000A_200MV; + g_roundedRange[channelId] = 0.2; + } + else if(range > 0.05) + { + g_range_3000a[channelId] = PS3000A_100MV; + g_roundedRange[channelId] = 0.1; + } + else if(range > 0.02) + { + 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 > 20) + { + g_range_4000a[channelId] = PS4000A_50V; + g_range[channelId] = PICO_X1_PROBE_50V; + g_roundedRange[channelId] = 50; + } + else if(range > 10) + { + g_range_4000a[channelId] = PS4000A_20V; + g_range[channelId] = PICO_X1_PROBE_20V; + g_roundedRange[channelId] = 20; + } + else if(range > 5) + { + g_range_4000a[channelId] = PS4000A_10V; + g_range[channelId] = PICO_X1_PROBE_10V; + g_roundedRange[channelId] = 10; + } + else if(range > 2) + { + g_range_4000a[channelId] = PS4000A_5V; + g_range[channelId] = PICO_X1_PROBE_5V; + g_roundedRange[channelId] = 5; + } + else if(range > 1) + { + g_range_4000a[channelId] = PS4000A_2V; + g_range[channelId] = PICO_X1_PROBE_2V; + g_roundedRange[channelId] = 2; + } + else if(range > 0.5) + { + g_range_4000a[channelId] = PS4000A_1V; + g_range[channelId] = PICO_X1_PROBE_1V; + g_roundedRange[channelId] = 1; + } + else if(range > 0.2) + { + g_range_4000a[channelId] = PS4000A_500MV; + g_range[channelId] = PICO_X1_PROBE_500MV; + g_roundedRange[channelId] = 0.5; + } + else if(range > 0.1) + { + g_range_4000a[channelId] = PS4000A_200MV; + g_range[channelId] = PICO_X1_PROBE_200MV; + g_roundedRange[channelId] = 0.2; + } + else if(range > 0.05) + { + g_range_4000a[channelId] = PS4000A_100MV; + g_range[channelId] = PICO_X1_PROBE_100MV; + g_roundedRange[channelId] = 0.1; + } + else if(range > 0.02) + { + g_range_4000a[channelId] = PS4000A_50MV; + g_range[channelId] = PICO_X1_PROBE_50MV; + g_roundedRange[channelId] = 0.05; + } + else if(range > 0.01) + { + 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 > 10) + { + g_range_5000a[channelId] = PS5000A_20V; + g_roundedRange[channelId] = 20; + } + else if(range > 5) + { + g_range_5000a[channelId] = PS5000A_10V; + g_roundedRange[channelId] = 10; + } + else if(range > 2) + { + g_range_5000a[channelId] = PS5000A_5V; + g_roundedRange[channelId] = 5; + } + else if(range > 1) + { + g_range_5000a[channelId] = PS5000A_2V; + g_roundedRange[channelId] = 2; + } + else if(range > 0.5) + { + g_range_5000a[channelId] = PS5000A_1V; + g_roundedRange[channelId] = 1; + } + else if(range > 0.2) + { + g_range_5000a[channelId] = PS5000A_500MV; + g_roundedRange[channelId] = 0.5; + } + else if(range > 0.1) + { + g_range_5000a[channelId] = PS5000A_200MV; + g_roundedRange[channelId] = 0.2; + } + else if(range > 0.05) + { + g_range_5000a[channelId] = PS5000A_100MV; + g_roundedRange[channelId] = 0.1; + } + else if(range > 0.02) + { + g_range_5000a[channelId] = PS5000A_50MV; + g_roundedRange[channelId] = 0.05; + } + else if(range > 0.01) + { + 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 = min(range, 5.0); + + if(range > 100) + { + g_range[channelId] = PICO_X1_PROBE_200V; + g_roundedRange[channelId] = 200; + } + else if(range > 50) + { + g_range[channelId] = PICO_X1_PROBE_100V; + g_roundedRange[channelId] = 100; + } + else if(range > 20) + { + g_range[channelId] = PICO_X1_PROBE_50V; + g_roundedRange[channelId] = 50; + } + else if(range > 10) + { + g_range[channelId] = PICO_X1_PROBE_20V; + g_roundedRange[channelId] = 20; + } + else if(range > 5) + { + g_range[channelId] = PICO_X1_PROBE_10V; + g_roundedRange[channelId] = 10; + } + else if(range > 2) + { + g_range[channelId] = PICO_X1_PROBE_5V; + g_roundedRange[channelId] = 5; + } + else if(range > 1) + { + g_range[channelId] = PICO_X1_PROBE_2V; + g_roundedRange[channelId] = 2; + } + else if(range > 0.5) + { + g_range[channelId] = PICO_X1_PROBE_1V; + g_roundedRange[channelId] = 1; + } + else if(range > 0.2) + { + g_range[channelId] = PICO_X1_PROBE_500MV; + g_roundedRange[channelId] = 0.5; + } + else if(range > 0.1) + { + g_range[channelId] = PICO_X1_PROBE_200MV; + g_roundedRange[channelId] = 0.2; + } + else if(range >= 0.05) + { + g_range[channelId] = PICO_X1_PROBE_100MV; + g_roundedRange[channelId] = 0.1; + } + else if(range >= 0.02) + { + g_range[channelId] = PICO_X1_PROBE_50MV; + g_roundedRange[channelId] = 0.05; + } + else if(range >= 0.01) + { + 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; + } + + //SetAnalogOffset(channelId, g_offset[channelId]); + + 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. + // !! this is done in UpdateChannel() already !! + //if(g_triggerChannel == channelId) + // UpdateTrigger(); +} + +void PicoSCPIServer::SetAnalogOffset(size_t chIndex, double offset_V) +{ + lock_guard lock(g_mutex); + + int channelId = chIndex & 0xff; + + double maxoff; + double minoff; + float maxoff_f; + float minoff_f; + + //Clamp to allowed range + switch(g_series) + { + 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 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; + } + offset_V = min(maxoff, offset_V); + offset_V = max(minoff, offset_V); + + g_offset[channelId] = offset_V; + UpdateChannel(channelId); +} + +void PicoSCPIServer::SetDigitalThreshold(size_t chIndex, double threshold_V) +{ + int channelId = chIndex & 0xff; + int laneId = (chIndex >> 8) & 0xff; + int16_t code = 0; + + 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); + + lock_guard lock(g_mutex); + + //Update the pod if currently active + if(g_msoPodEnabled[channelId]) + EnableMsoPod(channelId); +} + +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; + + //Calculate hysteresis + int level = hysteresis; + if(level <= 50) + g_msoHysteresis[channelId] = PICO_LOW_50MV; + else if(level <= 100) + g_msoHysteresis[channelId] = PICO_NORMAL_100MV; + else if(level <= 200) + g_msoHysteresis[channelId] = PICO_HIGH_200MV; + else + g_msoHysteresis[channelId] = PICO_VERY_HIGH_400MV; + + LogTrace("Setting MSO pod %d hysteresis to %d mV (code %d)\n", + channelId, level, g_msoHysteresis[channelId]); + + //Update the pod if currently active + if(g_msoPodEnabled[channelId]) + EnableMsoPod(channelId); +} + +void PicoSCPIServer::SetSampleRate(uint64_t rate_hz) +{ + lock_guard lock(g_mutex); + int timebase; + double period_ns; + + switch(g_series) + { + case 3: + { + //Convert sample rate to sample period + g_sampleInterval = 1e15 / rate_hz; + period_ns = 1e9 / rate_hz; + + //Find closest timebase setting + 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 + { + //!! 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 6: + { + //Convert sample rate to sample period + g_sampleInterval = 1e15 / rate_hz; + period_ns = 1e9 / rate_hz; + + //Find closest timebase setting + double clkdiv = period_ns / 0.2; + if(period_ns < 5) + 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; + + default: /* Unknown Pico Type */ + { + + g_sampleInterval = 1e15 / rate_hz; + timebase = 0; + LogError("SetSampleRate Error unknown g_series\n"); + } + } + + g_timebase = timebase; + g_sampleRate = rate_hz; + UpdateTrigger(); //TESTING lasse +} + +void PicoSCPIServer::SetSampleDepth(uint64_t depth) +{ + lock_guard lock(g_mutex); + g_memDepth = depth; + + UpdateTrigger(); +} + +void PicoSCPIServer::SetTriggerDelay(uint64_t delay_fs) +{ + lock_guard lock(g_mutex); + + g_triggerDelay = delay_fs; + UpdateTrigger(); +} + +void PicoSCPIServer::SetTriggerSource(size_t chIndex) +{ + lock_guard lock(g_mutex); + + auto type = GetChannelType(chIndex); + switch(type) + { + case CH_ANALOG: + g_triggerChannel = chIndex & 0xff; + if(!g_channelOn[g_triggerChannel]) + { + LogDebug("Trigger channel wasn't on, enabling it\n"); + g_channelOn[g_triggerChannel] = true; + UpdateChannel(g_triggerChannel); + } + break; + + case CH_DIGITAL: + { + int npod = chIndex & 0xff; + int nchan = (chIndex >> 8) & 0xff; + g_triggerChannel = g_numChannels + npod*8 + nchan; + + if(!g_msoPodEnabled[npod]) + { + LogDebug("Trigger pod wasn't on, enabling it\n"); + EnableMsoPod(npod); + } + } + break; + + case CH_EXTERNAL_TRIGGER: + { + g_triggerChannel = PICO_TRIGGER_AUX; + UpdateTrigger(); + }; + + default: + //TODO + break; + } + + bool wasOn = g_triggerArmed; + Stop(); + + UpdateTrigger(); + + if(wasOn) + StartCapture(false); +} + +void PicoSCPIServer::SetTriggerLevel(double level_V) +{ + lock_guard lock(g_mutex); + + g_triggerVoltage = level_V; + UpdateTrigger(); +} + +void PicoSCPIServer::SetTriggerTypeEdge() +{ + //all triggers are edge, nothing to do here until we start supporting other trigger types +} + +bool PicoSCPIServer::IsTriggerArmed() +{ + return g_triggerArmed; +} + +void PicoSCPIServer::SetEdgeTriggerEdge(const string& edge) +{ + lock_guard lock(g_mutex); + + if(edge == "RISING") + g_triggerDirection = PICO_RISING; + else if(edge == "FALLING") + g_triggerDirection = PICO_FALLING; + else if(edge == "ANY") + g_triggerDirection = PICO_RISING_OR_FALLING; + + UpdateTrigger(); +} + +/** + @brief Pushes channel configuration to the instrument + */ +void UpdateChannel(size_t chan) +{ + int16_t scaleVal; + + switch(g_series) + { + 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_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(); + return; + } + 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(); + return; + } + 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; + + //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 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. + //TODO: handle multi-input triggers + if(chan == g_triggerChannel) + UpdateTrigger(); + } + else + ps6000aSetChannelOff(g_hScope, (PICO_CHANNEL)chan); + } + break; + } +} + +/** + @brief Pushes trigger configuration to the instrument + */ +void UpdateTrigger(bool force) +{ + //Timeout, in microseconds, before initiating a trigger + //Force trigger is really just a one-shot auto trigger with a 1us delay. + uint32_t timeout = 0; + if(force) + { + timeout = 1; + g_lastTriggerWasForced = true; + g_triggerOneShot = true; //TESTING lasse + } + else + g_lastTriggerWasForced = false; + + bool triggerIsAnalog = (g_triggerChannel < g_numChannels) || (g_triggerChannel == PICO_TRIGGER_AUX); + + //Convert threshold from volts to ADC counts + float offset = 0; + if(triggerIsAnalog) + offset = g_offset[g_triggerChannel]; + float scale = 1; + if(triggerIsAnalog) + { + scale = g_roundedRange[g_triggerChannel] / 32767; + if(scale == 0) + scale = 1; + } + float trig_code = (g_triggerVoltage - offset) / scale; + + //This can happen early on during initialization. + //Bail rather than dividing by zero. + if(g_sampleInterval == 0) + return; + + //Add delay before start of capture if needed + int64_t triggerDelaySamples = g_triggerDelay / g_sampleInterval; + uint64_t delay = 0; + if(triggerDelaySamples < 0) + delay = -triggerDelaySamples; + + switch(g_series) + { + case 3: + { + if(g_triggerChannel == PICO_TRIGGER_AUX) + { + /* TODO PICO_TRIGGER_AUX PICO3000A similarly to PICO6000A... */ + /* + //Seems external trigger only supports zero crossing??? + trig_code = 0; + + //Remove old trigger conditions + ps6000aSetTriggerChannelConditions( + g_hScope, + NULL, + 0, + PICO_CLEAR_ALL); + + //Set up new conditions + PICO_CONDITION cond; + cond.source = PICO_TRIGGER_AUX; + cond.condition = PICO_CONDITION_TRUE; + int ret = ps6000aSetTriggerChannelConditions( + g_hScope, + &cond, + 1, + PICO_ADD); + if(ret != PICO_OK) + LogError("ps6000aSetTriggerChannelConditions failed: %x\n", ret); + + PICO_DIRECTION dir; + dir.channel = PICO_TRIGGER_AUX; + dir.direction = PICO_RISING; + dir.thresholdMode = PICO_LEVEL; + ret = ps6000aSetTriggerChannelDirections( + g_hScope, + &dir, + 1); + if(ret != PICO_OK) + LogError("ps6000aSetTriggerChannelDirections failed: %x\n", ret); + + PICO_TRIGGER_CHANNEL_PROPERTIES prop; + prop.thresholdUpper = trig_code; + prop.thresholdUpperHysteresis = 32; + prop.thresholdLower = 0; + prop.thresholdLowerHysteresis = 0; + prop.channel = PICO_TRIGGER_AUX; + ret = ps6000aSetTriggerChannelProperties( + g_hScope, + &prop, + 1, + 0, + 0); + if(ret != PICO_OK) + LogError("ps6000aSetTriggerChannelProperties failed: %x\n", ret); + + if(force) + LogWarning("Force trigger doesn't currently work if trigger source is external\n"); + */ + /* API is same as 6000a API */ + int ret = ps3000aSetSimpleTrigger( + g_hScope, + 1, + (PS3000A_CHANNEL)PICO_TRIGGER_AUX, + 0, + (enPS3000AThresholdDirection)g_triggerDirection, + delay, + timeout); + if(ret != PICO_OK) + LogError("ps6000aSetSimpleTrigger failed: %x\n", ret); + } + else if(g_triggerChannel < g_numChannels) + { + /* API is same as 6000a API */ + int ret = ps3000aSetSimpleTrigger( + g_hScope, + 1, + (PS3000A_CHANNEL)g_triggerChannel, + round(trig_code), + (enPS3000AThresholdDirection)g_triggerDirection, // same as 6000a api + delay, + timeout); + if(ret != PICO_OK) + LogError("ps3000aSetSimpleTrigger failed: %x\n", ret); + } + else + { + //Remove old trigger conditions + 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, + round(trig_code), + (enPS5000AThresholdDirection)g_triggerDirection, // same as 6000a api + delay, + timeout); + if(ret != PICO_OK) + LogError("ps5000aSetSimpleTrigger failed: %x\n", ret); + } + else + { + //Remove old trigger conditions + ps5000aSetTriggerChannelConditionsV2( + g_hScope, + NULL, + 0, + PS5000A_CLEAR); + + //Set up new conditions + int ntrig = g_triggerChannel - g_numChannels; + int trigpod = ntrig / 8; + int triglane = ntrig % 8; + PS5000A_CONDITION cond; + cond.source = static_cast(PS5000A_DIGITAL_PORT0 + trigpod); + cond.condition = PS5000A_CONDITION_TRUE; + ps5000aSetTriggerChannelConditionsV2( + g_hScope, + &cond, + 1, + PS5000A_ADD); + + //Set up configuration on the selected channel + 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, + &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 6: + { + if(g_triggerChannel == PICO_TRIGGER_AUX) + { + /* + //Seems external trigger only supports zero crossing??? + trig_code = 0; + + //Remove old trigger conditions + ps6000aSetTriggerChannelConditions( + g_hScope, + NULL, + 0, + PICO_CLEAR_ALL); + + //Set up new conditions + PICO_CONDITION cond; + cond.source = PICO_TRIGGER_AUX; + cond.condition = PICO_CONDITION_TRUE; + int ret = ps6000aSetTriggerChannelConditions( + g_hScope, + &cond, + 1, + PICO_ADD); + if(ret != PICO_OK) + LogError("ps6000aSetTriggerChannelConditions failed: %x\n", ret); + + PICO_DIRECTION dir; + dir.channel = PICO_TRIGGER_AUX; + dir.direction = PICO_RISING; + dir.thresholdMode = PICO_LEVEL; + ret = ps6000aSetTriggerChannelDirections( + g_hScope, + &dir, + 1); + if(ret != PICO_OK) + LogError("ps6000aSetTriggerChannelDirections failed: %x\n", ret); + + PICO_TRIGGER_CHANNEL_PROPERTIES prop; + prop.thresholdUpper = trig_code; + prop.thresholdUpperHysteresis = 32; + prop.thresholdLower = 0; + prop.thresholdLowerHysteresis = 0; + prop.channel = PICO_TRIGGER_AUX; + ret = ps6000aSetTriggerChannelProperties( + g_hScope, + &prop, + 1, + 0, + 0); + if(ret != PICO_OK) + LogError("ps6000aSetTriggerChannelProperties failed: %x\n", ret); + + if(force) + LogWarning("Force trigger doesn't currently work if trigger source is external\n"); + */ + + int ret = ps6000aSetSimpleTrigger( + g_hScope, + 1, + PICO_TRIGGER_AUX, + 0, + g_triggerDirection, + delay, + timeout); + if(ret != PICO_OK) + LogError("ps6000aSetSimpleTrigger failed: %x\n", ret); + } + else if(g_triggerChannel < g_numChannels) + { + int ret = ps6000aSetSimpleTrigger( + g_hScope, + 1, + (PICO_CHANNEL)g_triggerChannel, + round(trig_code), + g_triggerDirection, + delay, + timeout); + if(ret != PICO_OK) + LogError("ps6000aSetSimpleTrigger failed: %x\n", ret); + } + else + { + //Remove old trigger conditions + ps6000aSetTriggerChannelConditions( + g_hScope, + NULL, + 0, + PICO_CLEAR_ALL); + + //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( + g_hScope, + &cond, + 1, + PICO_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( + g_hScope, + cond.source, + &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; + } + + if(g_triggerArmed) + StartCapture(true); +} + +void Stop() +{ + switch(g_series) + { + case 3: + ps3000aStop(g_hScope); + break; + + case 4: + ps4000aStop(g_hScope); + break; + + case 5: + ps5000aStop(g_hScope); + break; + + case 6: + ps6000aStop(g_hScope); + break; + } +} + +PICO_STATUS StartInternal() +{ + //Calculate pre/post trigger time configuration based on trigger delay + 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_series) + { + case 3: + return ps3000aRunBlock(g_hScope, nPreTrigger_int, nPostTrigger_int, g_timebase, 1, NULL, 0, NULL, NULL); + + 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; + } +} + +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) + { + Stop(); + UpdateTrigger(); + } + + g_offsetDuringArm = g_offset; + g_channelOnDuringArm = g_channelOn; + for(size_t i=0; i(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/PicoSCPIServer.h.old b/src/ps6000d/PicoSCPIServer.h.old new file mode 100644 index 0000000..3be95c8 --- /dev/null +++ b/src/ps6000d/PicoSCPIServer.h.old @@ -0,0 +1,96 @@ +/*********************************************************************************************************************** +* * +* ps6000d * +* * +* Copyright (c) 2012-2022 Andrew D. Zonenberg * +* All rights reserved. * +* * +* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * +* following conditions are met: * +* * +* * Redistributions of source code must retain the above copyright notice, this list of conditions, and the * +* following disclaimer. * +* * +* * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * +* following disclaimer in the documentation and/or other materials provided with the distribution. * +* * +* * Neither the name of the author nor the names of any contributors may be used to endorse or promote products * +* derived from this software without specific prior written permission. * +* * +* THIS SOFTWARE IS PROVIDED BY THE AUTHORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * +* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * +* THE AUTHORS BE HELD LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * +* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR * +* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * +* POSSIBILITY OF SUCH DAMAGE. * +* * +***********************************************************************************************************************/ + +/** + @file + @author Andrew D. Zonenberg + @brief Declaration of PicoSCPIServer + */ + +#ifndef PicoSCPIServer_h +#define PicoSCPIServer_h + +#include "../../lib/scpi-server-tools/BridgeSCPIServer.h" + +/** + @brief SCPI server for managing control plane traffic to a single client + */ +class PicoSCPIServer : public BridgeSCPIServer +{ +public: + PicoSCPIServer(ZSOCKET sock); + virtual ~PicoSCPIServer(); + +protected: + virtual std::string GetMake(); + virtual std::string GetModel(); + virtual std::string GetSerial(); + virtual std::string GetFirmwareVersion(); + virtual size_t GetAnalogChannelCount(); + virtual std::vector GetSampleRates(); + virtual std::vector GetSampleDepths(); + + virtual bool OnCommand( + const std::string& line, + const std::string& subject, + const std::string& cmd, + const std::vector& args); + + virtual bool OnQuery( + const std::string& line, + const std::string& subject, + const std::string& cmd); + + virtual bool GetChannelID(const std::string& subject, size_t& id_out); + virtual ChannelType GetChannelType(size_t channel); + + void ReconfigAWG(); + + //Command methods + virtual void AcquisitionStart(bool oneShot = false); + virtual void AcquisitionForceTrigger(); + virtual void AcquisitionStop(); + virtual void SetChannelEnabled(size_t chIndex, bool enabled); + virtual void SetAnalogCoupling(size_t chIndex, const std::string& coupling); + virtual void SetAnalogRange(size_t chIndex, double range_V); + 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 SetSampleRate(uint64_t rate_hz); + virtual void SetSampleDepth(uint64_t depth); + virtual void SetTriggerDelay(uint64_t delay_fs); + virtual void SetTriggerSource(size_t chIndex); + virtual void SetTriggerLevel(double level_V); + virtual void SetTriggerTypeEdge(); + virtual void SetEdgeTriggerEdge(const std::string& edge); + virtual bool IsTriggerArmed(); +}; + +#endif diff --git a/src/ps6000d/WaveformServerThread.cpp b/src/ps6000d/WaveformServerThread.cpp index 07426f0..3c4945f 100644 --- a/src/ps6000d/WaveformServerThread.cpp +++ b/src/ps6000d/WaveformServerThread.cpp @@ -112,16 +112,12 @@ void WaveformServerThread() if(PICO_OK != status) LogFatal("ps6000aStop failed (code 0x%x)\n", status); -auto now = chrono::system_clock::now(); -auto duration = now.time_since_epoch(); -uint64_t ts = chrono::duration_cast(duration).count(); -LogVerbose("%lli -- psIsReady: %i\n", ts, ready); //Verify it's actually stopped //Set up buffers if needed if(g_memDepthChanged || waveformBuffers.empty()) { - LogVerbose("Reallocating buffers\n"); + //LogVerbose("Reallocating buffers\n"); //Clear out old buffers for(auto ch : g_channelIDs) @@ -202,10 +198,13 @@ LogVerbose("%lli -- psIsReady: %i\n", ts, ready); } if(status == PICO_NO_SAMPLES_AVAILABLE) { -LogVerbose("PICO_NO_SAMPLES_AVAILABLE\n"); + LogVerbose("PICO_NO_SAMPLES_AVAILABLE\n"); g_memDepthChanged = true; //TESTING lasse flush buffers and disarm - g_triggerArmed = false; //TESTING lasse on adding 2nd channel - continue; // state changed while mutex was unlocked? + UpdateTrigger(true); //TESTING lasse + //This response will occur if some setting like vertical scale changed just before aGetValues. + //Allow some more time for the scope to become responsive again. + std::this_thread::sleep_for(std::chrono::microseconds(100000)); //TESTING lasse + continue; } if(PICO_OK != status) LogFatal("psXXXXGetValues (code 0x%x)\n", status); @@ -222,10 +221,6 @@ LogVerbose("PICO_NO_SAMPLES_AVAILABLE\n"); if(g_msoPodEnabledDuringArm[i]) numchans ++; } - //LogVerbose("numSamples_int: %d\n", numSamples_int); - //LogVerbose("g_captureMemDepth: %lld\n", g_captureMemDepth); - //LogVerbose("waveformBuffers: %hs\n", waveformBuffers[0]); - } //Do *not* hold mutex while sending data to the client @@ -279,11 +274,6 @@ LogVerbose("PICO_NO_SAMPLES_AVAILABLE\n"); chdrs.offset = g_offsetDuringArm[i]; chdrs.trigphase = trigphase; -auto now = chrono::system_clock::now(); -auto duration = now.time_since_epoch(); -uint64_t ts = chrono::duration_cast(duration).count(); -//LogVerbose("%lli Channel: %lli, numSamples: %lli\n", ts, i, numSamples); - //Send channel headers if(!client.SendLooped((uint8_t*)&chdrs, sizeof(chdrs))) break; @@ -324,7 +314,6 @@ uint64_t ts = chrono::duration_cast(duration).count(); if(g_triggerOneShot) { g_triggerArmed = false; -LogVerbose("Disarm g_triggerOneShot\n"); } else { @@ -333,7 +322,6 @@ LogVerbose("Disarm g_triggerOneShot\n"); //Restart StartCapture(false); -LogVerbose("Restart Capture\n"); } } } From 1037cc41c78eb4a5ea9b50cb3cb659940f866da7 Mon Sep 17 00:00:00 2001 From: Lasse Beyer Date: Sat, 8 Nov 2025 17:53:05 +0100 Subject: [PATCH 06/12] Fix lockup on changing Vrange during acquisitions Changing the voltage range on analog channels obviously necessitates clearing out the buffers in WaveformServerThread every time, lest the Thread will lock up occasionally. --- src/ps6000d/PicoSCPIServer.cpp | 101 ++++++++++++++------------- src/ps6000d/WaveformServerThread.cpp | 4 +- 2 files changed, 53 insertions(+), 52 deletions(-) diff --git a/src/ps6000d/PicoSCPIServer.cpp b/src/ps6000d/PicoSCPIServer.cpp index 3249207..4885630 100644 --- a/src/ps6000d/PicoSCPIServer.cpp +++ b/src/ps6000d/PicoSCPIServer.cpp @@ -1570,58 +1570,57 @@ void PicoSCPIServer::SetAnalogCoupling(size_t chIndex, const std::string& coupli 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' - //auto range = range_V/2; - auto range = range_V/2; switch(g_series) { case 3: { //3000D series uses passive probes only, 20mV to 20V, no 50 ohm mode available - if(range > 10) + if(range_V > 20) { g_range_3000a[channelId] = PS3000A_20V; g_roundedRange[channelId] = 20; } - else if(range > 5) + else if(range_V > 10) { g_range_3000a[channelId] = PS3000A_10V; g_roundedRange[channelId] = 10; } - else if(range > 2) + else if(range_V > 5) { g_range_3000a[channelId] = PS3000A_5V; g_roundedRange[channelId] = 5; } - else if(range > 1) + else if(range_V > 2) { g_range_3000a[channelId] = PS3000A_2V; g_roundedRange[channelId] = 2; } - else if(range > 0.5) + else if(range_V > 1) { g_range_3000a[channelId] = PS3000A_1V; g_roundedRange[channelId] = 1; } - else if(range > 0.2) + else if(range_V > 0.5) { g_range_3000a[channelId] = PS3000A_500MV; g_roundedRange[channelId] = 0.5; } - else if(range > 0.1) + else if(range_V > 0.2) { g_range_3000a[channelId] = PS3000A_200MV; g_roundedRange[channelId] = 0.2; } - else if(range > 0.05) + else if(range_V > 0.1) { g_range_3000a[channelId] = PS3000A_100MV; g_roundedRange[channelId] = 0.1; } - else if(range > 0.02) + else if(range_V > 0.05) { g_range_3000a[channelId] = PS3000A_50MV; g_roundedRange[channelId] = 0.05; @@ -1637,67 +1636,67 @@ void PicoSCPIServer::SetAnalogRange(size_t chIndex, double range_V) case 4: { //4000 series uses passive probes only, 10mV to 50V, no 50 ohm mode available - if(range > 20) + if(range_V > 50) { g_range_4000a[channelId] = PS4000A_50V; g_range[channelId] = PICO_X1_PROBE_50V; g_roundedRange[channelId] = 50; } - else if(range > 10) + 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 > 5) + 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 > 2) + 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 > 1) + 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 > 0.5) + 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 > 0.2) + 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 > 0.1) + 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 > 0.05) + 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 > 0.02) + 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 > 0.01) + else if(range_V > 0.02) { g_range_4000a[channelId] = PS4000A_20MV; g_range[channelId] = PICO_X1_PROBE_20MV; @@ -1715,52 +1714,52 @@ void PicoSCPIServer::SetAnalogRange(size_t chIndex, double range_V) case 5: { //5000D series uses passive probes only, 10mV to 20V, no 50 ohm mode available - if(range > 10) + if(range_V > 20) { g_range_5000a[channelId] = PS5000A_20V; g_roundedRange[channelId] = 20; } - else if(range > 5) + else if(range_V > 10) { g_range_5000a[channelId] = PS5000A_10V; g_roundedRange[channelId] = 10; } - else if(range > 2) + else if(range_V > 5) { g_range_5000a[channelId] = PS5000A_5V; g_roundedRange[channelId] = 5; } - else if(range > 1) + else if(range_V > 2) { g_range_5000a[channelId] = PS5000A_2V; g_roundedRange[channelId] = 2; } - else if(range > 0.5) + else if(range_V > 1) { g_range_5000a[channelId] = PS5000A_1V; g_roundedRange[channelId] = 1; } - else if(range > 0.2) + else if(range_V > 0.5) { g_range_5000a[channelId] = PS5000A_500MV; g_roundedRange[channelId] = 0.5; } - else if(range > 0.1) + else if(range_V > 0.2) { g_range_5000a[channelId] = PS5000A_200MV; g_roundedRange[channelId] = 0.2; } - else if(range > 0.05) + else if(range_V > 0.1) { g_range_5000a[channelId] = PS5000A_100MV; g_roundedRange[channelId] = 0.1; } - else if(range > 0.02) + else if(range_V > 0.05) { g_range_5000a[channelId] = PS5000A_50MV; g_roundedRange[channelId] = 0.05; } - else if(range > 0.01) + else if(range_V > 0.02) { g_range_5000a[channelId] = PS5000A_20MV; g_roundedRange[channelId] = 0.02; @@ -1779,69 +1778,69 @@ void PicoSCPIServer::SetAnalogRange(size_t chIndex, double range_V) //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 = min(range, 5.0); + range_V = min(range_V, 5.0); - if(range > 100) + if(range_V > 200) { g_range[channelId] = PICO_X1_PROBE_200V; g_roundedRange[channelId] = 200; } - else if(range > 50) + else if(range_V > 100) { g_range[channelId] = PICO_X1_PROBE_100V; g_roundedRange[channelId] = 100; } - else if(range > 20) + else if(range_V > 50) { g_range[channelId] = PICO_X1_PROBE_50V; g_roundedRange[channelId] = 50; } - else if(range > 10) + else if(range_V > 20) { g_range[channelId] = PICO_X1_PROBE_20V; g_roundedRange[channelId] = 20; } - else if(range > 5) + else if(range_V > 10) { g_range[channelId] = PICO_X1_PROBE_10V; g_roundedRange[channelId] = 10; } - else if(range > 2) + else if(range_V > 5) { g_range[channelId] = PICO_X1_PROBE_5V; g_roundedRange[channelId] = 5; } - else if(range > 1) + else if(range_V > 2) { g_range[channelId] = PICO_X1_PROBE_2V; g_roundedRange[channelId] = 2; } - else if(range > 0.5) + else if(range_V > 1) { g_range[channelId] = PICO_X1_PROBE_1V; g_roundedRange[channelId] = 1; } - else if(range > 0.2) + else if(range_V > 0.5) { g_range[channelId] = PICO_X1_PROBE_500MV; g_roundedRange[channelId] = 0.5; } - else if(range > 0.1) + else if(range_V > 0.2) { g_range[channelId] = PICO_X1_PROBE_200MV; g_roundedRange[channelId] = 0.2; } - else if(range >= 0.05) + else if(range_V > 0.1) { g_range[channelId] = PICO_X1_PROBE_100MV; g_roundedRange[channelId] = 0.1; } - else if(range >= 0.02) + else if(range_V > 0.05) { g_range[channelId] = PICO_X1_PROBE_50MV; g_roundedRange[channelId] = 0.05; } - else if(range >= 0.01) + else if(range_V > 0.02) { g_range[channelId] = PICO_X1_PROBE_20MV; g_roundedRange[channelId] = 0.02; @@ -1855,6 +1854,8 @@ void PicoSCPIServer::SetAnalogRange(size_t chIndex, double range_V) break; } + //We need to allocate new buffers for this channel + g_memDepthChanged = true; UpdateChannel(channelId); //Update trigger if this is the trigger channel. @@ -2256,7 +2257,6 @@ void UpdateChannel(size_t chan) //TODO: handle multi-input triggers if(chan == g_triggerChannel) UpdateTrigger(); - return; } break; @@ -2274,7 +2274,6 @@ void UpdateChannel(size_t chan) //TODO: handle multi-input triggers if(chan == g_triggerChannel) UpdateTrigger(); - return; } break; @@ -2287,12 +2286,13 @@ void UpdateChannel(size_t 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; @@ -2759,7 +2759,8 @@ PICO_STATUS StartInternal() return ps6000aRunBlock(g_hScope, nPreTrigger, nPostTrigger, g_timebase, NULL, 0, NULL, NULL); default: - return PICO_OK; + //return PICO_OK; + return PICO_CANCELLED; } } diff --git a/src/ps6000d/WaveformServerThread.cpp b/src/ps6000d/WaveformServerThread.cpp index 3c4945f..0c928a5 100644 --- a/src/ps6000d/WaveformServerThread.cpp +++ b/src/ps6000d/WaveformServerThread.cpp @@ -199,11 +199,11 @@ void WaveformServerThread() if(status == PICO_NO_SAMPLES_AVAILABLE) { LogVerbose("PICO_NO_SAMPLES_AVAILABLE\n"); - g_memDepthChanged = true; //TESTING lasse flush buffers and disarm - UpdateTrigger(true); //TESTING lasse //This response will occur if some setting like vertical scale changed just before aGetValues. //Allow some more time for the scope to become responsive again. std::this_thread::sleep_for(std::chrono::microseconds(100000)); //TESTING lasse + g_memDepthChanged = true; //TESTING lasse flush buffers and disarm + UpdateTrigger(true); //TESTING lasse continue; } if(PICO_OK != status) From 61ec6c4aaca9ec609ee4d4a2aa392fa3ea7dbf95 Mon Sep 17 00:00:00 2001 From: Lasse Beyer Date: Sun, 9 Nov 2025 13:15:14 +0100 Subject: [PATCH 07/12] Implemented --series parameter Specify a certain series to open a model from via command line parameter --series. This is useful for preparing shell scripts or desktop shortcuts that specify different ports for a certain series model. Valid arguments are 3, 4, 5, 6 or anything else starting with those numbers, like 6000E or 4000A. Unrecognized arguments will fall back to the default behaviour of trying all supported series in ascending order, as if the parameter was not given at all. --- src/ps6000d/PicoSCPIServer.cpp | 4 +- src/ps6000d/WaveformServerThread.cpp | 7 +- src/ps6000d/main.cpp | 186 ++++++++++++++++++--------- 3 files changed, 131 insertions(+), 66 deletions(-) diff --git a/src/ps6000d/PicoSCPIServer.cpp b/src/ps6000d/PicoSCPIServer.cpp index 4885630..ee8cc22 100644 --- a/src/ps6000d/PicoSCPIServer.cpp +++ b/src/ps6000d/PicoSCPIServer.cpp @@ -2333,7 +2333,7 @@ void UpdateTrigger(bool force) { timeout = 1; g_lastTriggerWasForced = true; - g_triggerOneShot = true; //TESTING lasse + g_triggerOneShot = true; } else g_lastTriggerWasForced = false; @@ -2769,7 +2769,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; //TESTING lasse + g_triggerOneShot = false; Stop(); UpdateTrigger(); } diff --git a/src/ps6000d/WaveformServerThread.cpp b/src/ps6000d/WaveformServerThread.cpp index 0c928a5..2595dc8 100644 --- a/src/ps6000d/WaveformServerThread.cpp +++ b/src/ps6000d/WaveformServerThread.cpp @@ -200,10 +200,9 @@ void WaveformServerThread() { LogVerbose("PICO_NO_SAMPLES_AVAILABLE\n"); //This response will occur if some setting like vertical scale changed just before aGetValues. - //Allow some more time for the scope to become responsive again. - std::this_thread::sleep_for(std::chrono::microseconds(100000)); //TESTING lasse - g_memDepthChanged = true; //TESTING lasse flush buffers and disarm - UpdateTrigger(true); //TESTING lasse + //flush buffers and update channel + g_memDepthChanged = true; + UpdateTrigger(true); continue; } if(PICO_OK != status) diff --git a/src/ps6000d/main.cpp b/src/ps6000d/main.cpp index 270d53c..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" @@ -73,6 +78,7 @@ size_t g_series; 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,7 +93,6 @@ int main(int argc, char* argv[]) { //Global settings Severity console_verbosity = Severity::NOTICE; - bool limitChannels = false; g_series = 0; //Parse command-line arguments @@ -107,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) @@ -127,80 +143,58 @@ int main(int argc, char* argv[]) } //Set up logging - //g_log_sinks.emplace(g_log_sinks.begin(), new ColoredSTDLogSink(console_verbosity)); - g_log_sinks.emplace(g_log_sinks.begin(), new STDLogSink(console_verbosity)); + //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 5000 series instrument to open...\n"); - 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) ) + case 0: { - // 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); + status = Open3000(); + if(status == 0) + break; + status = Open4000(); + if(status == 0) + break; + status = Open5000(); + if(status == 0) + break; + status = Open6000(); + break; } - if(PICO_OK != status) + case 3: { - LogNotice("Looking for a PicoScope 4000 series instrument to open...\n"); - 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(PICO_OK != status) - { - LogNotice("Looking for a PicoScope 3000 series instrument to open...\n"); - 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(PICO_OK != status) - { - LogError("Failed to open unit (code %d)\n", status); - return 1; - } - else - { - g_series = 3; - picoGetUnitInfo = ps3000aGetUnitInfo; - } - } - else - { - g_series = 4; - picoGetUnitInfo = ps4000aGetUnitInfo; - } + status = Open3000(); + break; } - else + case 4: + { + status = Open4000(); + break; + } + case 5: + { + status = Open5000(); + break; + } + case 6: { - g_series = 5; - picoGetUnitInfo = ps5000aGetUnitInfo; + status = Open5000(); + break; } } - else + + if(PICO_OK != status) { - g_series = 6; - picoGetUnitInfo = ps6000aGetUnitInfo; + LogError("Failed to open unit (code %d)\n", status); + return 1; } //See what we got - LogNotice("Successfully opened instrument\n"); { LogIndenter li; @@ -283,6 +277,7 @@ 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); //Limit to two channels only while on USB power if(limitChannels) @@ -466,3 +461,74 @@ void OnQuit(int /*signal*/) } 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; +} From 07dcb27114039300384b57cbd966e3ea00482da5 Mon Sep 17 00:00:00 2001 From: Lasse Beyer Date: Sun, 9 Nov 2025 13:19:44 +0100 Subject: [PATCH 08/12] Delete src/ps6000d/PicoSCPIServer.h.old --- src/ps6000d/PicoSCPIServer.h.old | 96 -------------------------------- 1 file changed, 96 deletions(-) delete mode 100644 src/ps6000d/PicoSCPIServer.h.old diff --git a/src/ps6000d/PicoSCPIServer.h.old b/src/ps6000d/PicoSCPIServer.h.old deleted file mode 100644 index 3be95c8..0000000 --- a/src/ps6000d/PicoSCPIServer.h.old +++ /dev/null @@ -1,96 +0,0 @@ -/*********************************************************************************************************************** -* * -* ps6000d * -* * -* Copyright (c) 2012-2022 Andrew D. Zonenberg * -* All rights reserved. * -* * -* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * -* following conditions are met: * -* * -* * Redistributions of source code must retain the above copyright notice, this list of conditions, and the * -* following disclaimer. * -* * -* * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * -* following disclaimer in the documentation and/or other materials provided with the distribution. * -* * -* * Neither the name of the author nor the names of any contributors may be used to endorse or promote products * -* derived from this software without specific prior written permission. * -* * -* THIS SOFTWARE IS PROVIDED BY THE AUTHORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * -* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * -* THE AUTHORS BE HELD LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * -* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR * -* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * -* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * -* POSSIBILITY OF SUCH DAMAGE. * -* * -***********************************************************************************************************************/ - -/** - @file - @author Andrew D. Zonenberg - @brief Declaration of PicoSCPIServer - */ - -#ifndef PicoSCPIServer_h -#define PicoSCPIServer_h - -#include "../../lib/scpi-server-tools/BridgeSCPIServer.h" - -/** - @brief SCPI server for managing control plane traffic to a single client - */ -class PicoSCPIServer : public BridgeSCPIServer -{ -public: - PicoSCPIServer(ZSOCKET sock); - virtual ~PicoSCPIServer(); - -protected: - virtual std::string GetMake(); - virtual std::string GetModel(); - virtual std::string GetSerial(); - virtual std::string GetFirmwareVersion(); - virtual size_t GetAnalogChannelCount(); - virtual std::vector GetSampleRates(); - virtual std::vector GetSampleDepths(); - - virtual bool OnCommand( - const std::string& line, - const std::string& subject, - const std::string& cmd, - const std::vector& args); - - virtual bool OnQuery( - const std::string& line, - const std::string& subject, - const std::string& cmd); - - virtual bool GetChannelID(const std::string& subject, size_t& id_out); - virtual ChannelType GetChannelType(size_t channel); - - void ReconfigAWG(); - - //Command methods - virtual void AcquisitionStart(bool oneShot = false); - virtual void AcquisitionForceTrigger(); - virtual void AcquisitionStop(); - virtual void SetChannelEnabled(size_t chIndex, bool enabled); - virtual void SetAnalogCoupling(size_t chIndex, const std::string& coupling); - virtual void SetAnalogRange(size_t chIndex, double range_V); - 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 SetSampleRate(uint64_t rate_hz); - virtual void SetSampleDepth(uint64_t depth); - virtual void SetTriggerDelay(uint64_t delay_fs); - virtual void SetTriggerSource(size_t chIndex); - virtual void SetTriggerLevel(double level_V); - virtual void SetTriggerTypeEdge(); - virtual void SetEdgeTriggerEdge(const std::string& edge); - virtual bool IsTriggerArmed(); -}; - -#endif From 1d06beb404ab16b6b6f2aa823b52166422b93a77 Mon Sep 17 00:00:00 2001 From: Lasse Beyer Date: Sun, 9 Nov 2025 13:20:10 +0100 Subject: [PATCH 09/12] Delete src/ps6000d/PicoSCPIServer.cpp.old --- src/ps6000d/PicoSCPIServer.cpp.old | 2784 ---------------------------- 1 file changed, 2784 deletions(-) delete mode 100644 src/ps6000d/PicoSCPIServer.cpp.old diff --git a/src/ps6000d/PicoSCPIServer.cpp.old b/src/ps6000d/PicoSCPIServer.cpp.old deleted file mode 100644 index 2e695d8..0000000 --- a/src/ps6000d/PicoSCPIServer.cpp.old +++ /dev/null @@ -1,2784 +0,0 @@ -/*********************************************************************************************************************** -* * -* ps6000d * -* * -* Copyright (c) 2012-2022 Andrew D. Zonenberg * -* All rights reserved. * -* * -* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * -* following conditions are met: * -* * -* * Redistributions of source code must retain the above copyright notice, this list of conditions, and the * -* following disclaimer. * -* * -* * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * -* following disclaimer in the documentation and/or other materials provided with the distribution. * -* * -* * Neither the name of the author nor the names of any contributors may be used to endorse or promote products * -* derived from this software without specific prior written permission. * -* * -* THIS SOFTWARE IS PROVIDED BY THE AUTHORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * -* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * -* THE AUTHORS BE HELD LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * -* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR * -* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * -* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * -* POSSIBILITY OF SUCH DAMAGE. * -* * -***********************************************************************************************************************/ - -/** - @file - @author Andrew D. Zonenberg - @brief SCPI server. Control plane traffic only, no waveform data. - - SCPI commands supported: - - *IDN? - Returns a standard SCPI instrument identification string - - CHANS? - Returns the number of channels on the instrument. - - [1|2]D:PRESENT? - Returns 1 = MSO pod present, 0 = MSO pod not present - - [chan]:COUP [DC1M|AC1M|DC50] - Sets channel coupling - - [chan]:HYS [mV] - Sets MSO channel hysteresis to mV millivolts - - [chan]:OFF - Turns the channel off - - [chan]:OFFS [num] - Sets channel offset to num volts - - [chan]:ON - Turns the channel on - - [chan]:RANGE [num] - Sets channel full-scale range to num volts - - [chan]:THRESH [mV] - Sets MSO channel threshold to mV millivolts - - BITS [num] - Sets ADC bit depth - - DEPTH [num] - Sets memory depth - - DEPTHS? - Returns the set of available memory depths - - EXIT - Terminates the connection - - FORCE - Forces a single acquisition - - RATE [num] - Sets sample rate - - RATES? - Returns a comma separated list of sampling rates (in femtoseconds) - - SINGLE - Arms the trigger in one-shot mode - - START - Arms the trigger - - STOP - Disarms the trigger - - TRIG:DELAY [delay] - Sets trigger delay (in fs) - - TRIG:EDGE:DIR [direction] - Sets trigger direction. Legal values are RISING, FALLING, or ANY. - - TRIG:LEV [level] - Selects trigger level (in volts) - - TRIG:SOU [chan] - Selects the channel as the trigger source - - TODO: SetDigitalPortInteractionCallback to determine when pods are connected/removed - - AWG:DUTY [duty cycle] - Sets duty cycle of function generator output - - AWG:FREQ [freq] - Sets function generator frequency, in Hz - - AWG:OFF [offset] - Sets offset of the function generator output - - AWG:RANGE [range] - Sets p-p voltage of the function generator output - - AWG:SHAPE [waveform type] - Sets waveform type - - AWG:START - Starts the function generator - - AWG:STOP - Stops the function generator - */ - -#include "ps6000d.h" -#include "PicoSCPIServer.h" -#include -#include - -#define __USE_MINGW_ANSI_STDIO 1 // Required for MSYS2 mingw64 to support format "%z" ... - -#define FS_PER_SECOND 1e15 - -using namespace std; - -//Channel state -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_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 -map g_channelOnDuringArm; -int64_t g_sampleIntervalDuringArm = 0; -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; -bool g_memDepthChanged = false; - -//Trigger state (for now, only simple single-channel trigger supported) -int64_t g_triggerDelay = 0; -PICO_THRESHOLD_DIRECTION g_triggerDirection = PICO_RISING; -float g_triggerVoltage = 0; -size_t g_triggerChannel = 0; -size_t g_triggerSampleIndex; - -//Thresholds for MSO pods -size_t g_numDigitalPods = 2; -int16_t g_msoPodThreshold[2][8] = { {0}, {0} }; -PICO_DIGITAL_PORT_HYSTERESIS g_msoHysteresis[2] = {PICO_NORMAL_100MV, PICO_NORMAL_100MV}; -bool g_msoPodEnabled[2] = {false}; -bool g_msoPodEnabledDuringArm[2] = {false}; - -bool EnableMsoPod(size_t npod); - -bool g_lastTriggerWasForced = false; - -std::mutex g_mutex; - -//AWG config -float g_awgRange = 0; -float g_awgOffset = 0; -bool g_awgOn = false; -double g_awgFreq = 1000; -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_WHITE_NOISE, PS5000A_ES_OFF}}, - {"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 -}; - -#define SIG_GEN_BUFFER_SIZE 8192 //TODO: allow model specific/variable buffer size. Must be power of 2 for most AWGs PS3000: 2^13, PS3207B: 2^14 PS3206B: 2^15 -int16_t* g_arbitraryWaveform = new int16_t[SIG_GEN_BUFFER_SIZE]; // -void GenerateSquareWave(int16_t* &waveform, size_t bufferSize, double dutyCycle, int16_t amplitude = 32767); -void ReconfigAWG(); - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Construction / destruction - -PicoSCPIServer::PicoSCPIServer(ZSOCKET sock) - : BridgeSCPIServer(sock) -{ - //external trigger is fixed range of -1 to +1V - g_roundedRange[PICO_TRIGGER_AUX] = 2; - g_offset[PICO_TRIGGER_AUX] = 0; -} - -PicoSCPIServer::~PicoSCPIServer() -{ - LogVerbose("Client disconnected\n"); - - //Disable all channels when a client disconnects to put the scope in a "safe" state - for(auto& it : g_channelOn) - { - switch(g_series) - { - case 3: - ps3000aSetChannel(g_hScope, (PS3000A_CHANNEL)it.first, 0, PS3000A_DC, PS3000A_1V, 0.0f); - break; - 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; - } - - it.second = false; - g_channelOnDuringArm[it.first] = false; - } - - for(int i=0; i<2; i++) - { - switch(g_series) - { - case 3: - ps3000aSetDigitalPort(g_hScope, (PS3000A_DIGITAL_PORT)(PICO_PORT0 + i), 0, 0); - break; - case 5: - ps5000aSetDigitalPort(g_hScope, (PS5000A_CHANNEL)(PICO_PORT0 + i), 0, 0); - break; - case 6: - ps6000aSetDigitalPortOff(g_hScope, (PICO_CHANNEL)(PICO_PORT0 + i)); - break; - } - g_msoPodEnabled[i] = false; - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Command parsing - -bool PicoSCPIServer::OnQuery( - const string& line, - const string& subject, - const string& cmd) -{ - //Extract channel ID from subject and clamp bounds - size_t channelId = 0; - //size_t laneId = 0; - //bool channelIsDigital = false; - if(isalpha(subject[0])) - { - channelId = min(static_cast(subject[0] - 'A'), g_numChannels); - //channelIsDigital = false; - } - else if(isdigit(subject[0])) - { - channelId = min(subject[0] - '0', 2) - 1; - //channelIsDigital = true; - //if(subject.length() >= 3) - // laneId = min(subject[2] - '0', 7); - } - - if(BridgeSCPIServer::OnQuery(line, subject, cmd)) - { - return true; - } - else if(cmd == "PRESENT") - { - lock_guard lock(g_mutex); - - switch(g_series) - { - case 3: - case 5: - { - //All MSO models have two pods. - if(g_model.find("MSO") != string::npos) - { - SendReply("1"); - } - else - { - SendReply("0"); - } - break; - } - break; - - 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. - PICO_CHANNEL podId = (PICO_CHANNEL)(PICO_PORT0 + channelId); - auto status = ps6000aSetDigitalPortOn( - g_hScope, - podId, - g_msoPodThreshold[channelId], - 8, - g_msoHysteresis[channelId]); - - if(status == PICO_NO_MSO_POD_CONNECTED) - { - SendReply("0"); - } - else - { - // The pod is here. If we don't need it on, shut it back off - if(!g_msoPodEnabled[channelId]) - ps6000aSetDigitalPortOff(g_hScope, podId); - - SendReply("1"); - } - } - break; - - default: - { - SendReply("0"); - } - } - } - else - { - LogDebug("Unrecognized query received: %s\n", line.c_str()); - } - return false; -} - -string PicoSCPIServer::GetMake() -{ - return "Pico Technology"; -} - -string PicoSCPIServer::GetModel() -{ - return g_model; -} - -string PicoSCPIServer::GetSerial() -{ - return g_serial; -} - -string PicoSCPIServer::GetFirmwareVersion() -{ - return g_fwver; -} - -size_t PicoSCPIServer::GetAnalogChannelCount() -{ - return g_numChannels; -} - -vector PicoSCPIServer::GetSampleRates() -{ - vector rates; - - 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 - //TODO: Use API-call to obtain some neat and evenly spaced values - size_t vec[] = - { - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, - 10, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131027, 262144, 524288, 1048576, - 2097152, 4194304, 8388608, 16777216 - //14, 29, 54, 104, 129, 254, 504, 629, 1254, 2504, 3129, 5004, 6254, 12504, 15629, 25004, 31254, - //62504, 125004, 156254, 250004, 312504, 500004, 625004, 1000004, 1562504 - }; - for(auto i : vec) - { - double intervalNs; - float intervalNs_f; - uint64_t maxSamples; - int32_t maxSamples_int; - PICO_STATUS status = PICO_RESERVED_1; - - switch(g_series) - { - 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 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; - } - - if(PICO_OK == status) - { - size_t intervalFs = intervalNs * 1e6f; - rates.push_back(FS_PER_SECOND / intervalFs); - } - else if(PICO_INVALID_TIMEBASE == status) - { - //Requested timebase not possible - //This is common and harmless if we ask for e.g. timebase 0 when too many channels are active. - continue; - } - else - LogWarning("GetTimebase failed, code %d / 0x%x\n", status, status); - } - - return rates; -} - -vector PicoSCPIServer::GetSampleDepths() -{ - vector depths; - - lock_guard lock(g_mutex); - double intervalNs; - float intervalNs_f; - uint64_t maxSamples; - int32_t maxSamples_int; - - PICO_STATUS status; - status = PICO_RESERVED_1; - - //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_series) - { - 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 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; - } - - 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 1K samples up to the actual max depth - - for(size_t base = 1000; base < maxSamples; base *= 10) - { - const size_t muls[] = {1, 2, 5}; - for(auto m : muls) - { - size_t depth = m * base; - if(depth < maxSamples) - depths.push_back(depth); - } - } - - depths.push_back(maxSamples); - } - - return depths; -} - -bool PicoSCPIServer::OnCommand( - const string& line, - const string& subject, - const string& cmd, - const vector& args) -{ - //Function generator is different from normal channels - //(uses range/offs commands so must go before normal bridge processing!) - if(subject == "AWG") - { - if(cmd == "START") - { - lock_guard lock(g_mutex); - g_awgOn = true; - ReconfigAWG(); - } - - else if(cmd == "STOP") - { - lock_guard lock(g_mutex); - 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; - } - 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; - } - 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; - } - else - { - g_awgOn = false; - ReconfigAWG(); - } - } - - else if(args.size() == 1) - { - if(cmd == "FREQ") - { - lock_guard lock(g_mutex); - g_awgFreq = stof(args[0]); - //Frequency must not be zero - if(g_awgFreq<1e-3) - g_awgFreq = 1; - - switch(g_series) - { - case 3: - case 4: - case 5: - { - //handled by ReconfigAWG() - } - break; - - case 6: - { - auto status = ps6000aSigGenFrequency(g_hScope, g_awgFreq); - if(status != PICO_OK) - LogError("ps6000aSigGenFrequency failed, code 0x%x (freq=%f)\n", status, g_awgFreq); - } - break; - } - ReconfigAWG(); - } - - else if(cmd == "DUTY") - { - lock_guard lock(g_mutex); - auto duty = stof(args[0]) * 100; - - switch(g_series) - { - case 3: - { - /* 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, SIG_GEN_BUFFER_SIZE, (double) duty); - else - LogError("PICO3000A DUTY TODO code\n"); - } - break; - - 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, SIG_GEN_BUFFER_SIZE, (double) duty); - else - LogError("PICO4000A DUTY TODO code\n"); - } - break; - - 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, SIG_GEN_BUFFER_SIZE, (double) duty); - else - LogError("PICO5000A DUTY TODO code\n"); - } - break; - - case 6: - { - auto status = ps6000aSigGenWaveformDutyCycle(g_hScope, duty); - if(status != PICO_OK) - LogError("ps6000aSigGenWaveformDutyCycle failed, code 0x%x\n", status); - - ReconfigAWG(); - } - break; - } - ReconfigAWG(); - } - - else if(cmd == "OFFS") - { - lock_guard lock(g_mutex); - g_awgOffset = stof(args[0]); - - ReconfigAWG(); - } - - else if(cmd == "RANGE") - { - lock_guard lock(g_mutex); - g_awgRange = stof(args[0]); - - ReconfigAWG(); - } - - else if(cmd == "SHAPE") - { - lock_guard lock(g_mutex); - - auto waveform = g_waveformTypes.find(args[0]); - if(waveform == g_waveformTypes.end()) - { - LogError("Invalid waveform type: %s\n", args[0].c_str()); - return true; - } - - switch(g_series) - { - case 3: - { - 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, SIG_GEN_BUFFER_SIZE, 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; - - 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, SIG_GEN_BUFFER_SIZE, 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 5: - { - if( (args[0] == "RPBS") ) - { - LogError("RPBS generation not supported by 5000 series\n"); - return true; - } - if( (g_awgPS5000AWaveType == PS5000A_SQUARE) ) - { - GenerateSquareWave(g_arbitraryWaveform, SIG_GEN_BUFFER_SIZE, 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(); - - 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()); - } - else - LogError("Unrecognized AWG command %s\n", line.c_str()); - } - else if(BridgeSCPIServer::OnCommand(line, subject, cmd, args)) - return true; - - else if( (cmd == "BITS") && (args.size() == 1) ) - { - lock_guard lock(g_mutex); - switch(g_series) - { - case 3: - { - g_adcBits = 8; - return false; - } - break; - - case 4: - { - if(g_model.find("4444") != string::npos) - { - ps4000aStop(g_hScope); - - //Changing the ADC resolution necessitates reallocation of the buffers - //due to different memory usage. - 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); - } - - if(g_triggerArmed) - StartCapture(false); - //update all active channels - for(size_t i=0; i -5 to 5 V) - if(PICO_OK != status) - LogError("ps3000aSetSigGenBuiltInV2 failed, code 0x%x\n", status); - } - if(g_triggerArmed) - StartCapture(false); - } - break; - - 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, SIG_GEN_BUFFER_SIZE, &delta); - if(status != PICO_OK) - LogError("ps3000aSigGenFrequencyToPhase failed, code 0x%x\n", status); - status = ps4000aSetSigGenArbitrary( - g_hScope, - g_awgOffset*1e6, - g_awgRange*1e6*2, - delta, - delta, - 0, - 0, - g_arbitraryWaveform, - SIG_GEN_BUFFER_SIZE, - 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, - g_awgOffset*1e6, //Offset Voltage in µV - g_awgRange *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, SIG_GEN_BUFFER_SIZE, &delta); - if(status != PICO_OK) - LogError("ps5000aSigGenFrequencyToPhase failed, code 0x%x\n", status); - status = ps5000aSetSigGenArbitrary( - g_hScope, - g_awgOffset*1e6, - g_awgRange*1e6*2, - delta, - delta, - 0, - 0, - g_arbitraryWaveform, - SIG_GEN_BUFFER_SIZE, - 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, - g_awgOffset*1e6, //Offset Voltage in µV - g_awgRange *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 6: - { - auto status = ps6000aSigGenRange(g_hScope, g_awgRange, g_awgOffset); - if(PICO_OK != status) - LogError("ps6000aSigGenRange failed, code 0x%x\n", status); - - 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); - } - break; - } -} - -bool PicoSCPIServer::GetChannelID(const std::string& subject, size_t& id_out) -{ - if(subject == "EX") - { - id_out = PICO_TRIGGER_AUX; - return true; - } - - //Extract channel ID from subject and clamp bounds - size_t channelId = 0; - size_t laneId = 0; - bool channelIsDigital = false; - if(isalpha(subject[0])) - { - channelId = min(static_cast(subject[0] - 'A'), g_numChannels); - channelIsDigital = false; - } - else if(isdigit(subject[0])) - { - channelId = min(subject[0] - '0', 2) - 1; - channelIsDigital = true; - if(subject.length() >= 3) - laneId = min(subject[2] - '0', 7); - } - else - return false; - - //Pack channel IDs into bytes - //Byte 0: channel / pod ID - //Byte 1: lane ID - //Byte 2: digital flag - id_out = channelId; - if(channelIsDigital) - id_out |= 0x800000 | (laneId << 8); - - return true; -} - -BridgeSCPIServer::ChannelType PicoSCPIServer::GetChannelType(size_t channel) -{ - if(channel == PICO_TRIGGER_AUX) - return CH_EXTERNAL_TRIGGER; - else if(channel > 0xff) - return CH_DIGITAL; - else - return CH_ANALOG; -} - -void PicoSCPIServer::AcquisitionStart(bool oneShot) -{ - lock_guard lock(g_mutex); - - if(g_triggerArmed) - { - LogVerbose("Ignoring START command because trigger is already armed\n"); - return; - } - - //Make sure we've got something to capture - bool anyChannels = false; - for(size_t i=0; i lock(g_mutex); - - //Clear out any old trigger config - if(g_triggerArmed) - { - Stop(); - g_triggerArmed = false; - } - - UpdateTrigger(true); - StartCapture(true, true); -} - -void PicoSCPIServer::AcquisitionStop() -{ - lock_guard lock(g_mutex); - - Stop(); - - //Convert any in-progress trigger to one shot. - //This ensures that if a waveform is halfway through being downloaded, we won't re-arm the trigger after it finishes. - g_triggerOneShot = true; - g_triggerArmed = false; -} - -void PicoSCPIServer::SetChannelEnabled(size_t chIndex, bool enabled) -{ - lock_guard lock(g_mutex); - - if(GetChannelType(chIndex) == CH_DIGITAL) - { - int podIndex = (chIndex & 0xff); - PICO_CHANNEL podId = (PICO_CHANNEL)(PICO_PORT0 + podIndex); - - if(enabled) - { - switch(g_series) - { - case 3: - { - auto status = ps3000aSetDigitalPort(g_hScope, (PS3000A_DIGITAL_PORT)podId, 1, g_msoPodThreshold[podIndex][0]); - if(status != PICO_OK) - 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 6: - { - auto status = ps6000aSetDigitalPortOn( - g_hScope, - podId, - g_msoPodThreshold[podIndex], - 8, - g_msoHysteresis[podIndex]); - if(status != PICO_OK) - LogError("ps6000aSetDigitalPortOn failed with code %x\n", status); - else - g_msoPodEnabled[podIndex] = true; - } - break; - } - } - else - { - switch(g_series) - { - case 3: - { - auto status = ps3000aSetDigitalPort(g_hScope, (PS3000A_DIGITAL_PORT)podId, 0, 0); - if(status != PICO_OK) - 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 6: - { - auto status = ps6000aSetDigitalPortOff(g_hScope, podId); - if(status != PICO_OK) - LogError("ps6000aSetDigitalPortOff failed with code %x\n", status); - else - g_msoPodEnabled[podIndex] = false; - } - break; - } - } - } - else - { - int chId = chIndex & 0xff; - g_channelOn[chId] = enabled; - UpdateChannel(chId); - } - - //We need to allocate new buffers for this channel - g_memDepthChanged = true; - 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' - auto range = range_V/2; - - switch(g_series) - { - case 3: - { - //3000D series uses passive probes only, 20mV to 20V, no 50 ohm mode available - if(range > 10) - { - g_range_3000a[channelId] = PS3000A_20V; - g_roundedRange[channelId] = 20; - } - else if(range > 5) - { - g_range_3000a[channelId] = PS3000A_10V; - g_roundedRange[channelId] = 10; - } - else if(range > 2) - { - g_range_3000a[channelId] = PS3000A_5V; - g_roundedRange[channelId] = 5; - } - else if(range > 1) - { - g_range_3000a[channelId] = PS3000A_2V; - g_roundedRange[channelId] = 2; - } - else if(range > 0.5) - { - g_range_3000a[channelId] = PS3000A_1V; - g_roundedRange[channelId] = 1; - } - else if(range > 0.2) - { - g_range_3000a[channelId] = PS3000A_500MV; - g_roundedRange[channelId] = 0.5; - } - else if(range > 0.1) - { - g_range_3000a[channelId] = PS3000A_200MV; - g_roundedRange[channelId] = 0.2; - } - else if(range > 0.05) - { - g_range_3000a[channelId] = PS3000A_100MV; - g_roundedRange[channelId] = 0.1; - } - else if(range > 0.02) - { - 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 > 20) - { - g_range_4000a[channelId] = PS4000A_50V; - g_range[channelId] = PICO_X1_PROBE_50V; - g_roundedRange[channelId] = 50; - } - else if(range > 10) - { - g_range_4000a[channelId] = PS4000A_20V; - g_range[channelId] = PICO_X1_PROBE_20V; - g_roundedRange[channelId] = 20; - } - else if(range > 5) - { - g_range_4000a[channelId] = PS4000A_10V; - g_range[channelId] = PICO_X1_PROBE_10V; - g_roundedRange[channelId] = 10; - } - else if(range > 2) - { - g_range_4000a[channelId] = PS4000A_5V; - g_range[channelId] = PICO_X1_PROBE_5V; - g_roundedRange[channelId] = 5; - } - else if(range > 1) - { - g_range_4000a[channelId] = PS4000A_2V; - g_range[channelId] = PICO_X1_PROBE_2V; - g_roundedRange[channelId] = 2; - } - else if(range > 0.5) - { - g_range_4000a[channelId] = PS4000A_1V; - g_range[channelId] = PICO_X1_PROBE_1V; - g_roundedRange[channelId] = 1; - } - else if(range > 0.2) - { - g_range_4000a[channelId] = PS4000A_500MV; - g_range[channelId] = PICO_X1_PROBE_500MV; - g_roundedRange[channelId] = 0.5; - } - else if(range > 0.1) - { - g_range_4000a[channelId] = PS4000A_200MV; - g_range[channelId] = PICO_X1_PROBE_200MV; - g_roundedRange[channelId] = 0.2; - } - else if(range > 0.05) - { - g_range_4000a[channelId] = PS4000A_100MV; - g_range[channelId] = PICO_X1_PROBE_100MV; - g_roundedRange[channelId] = 0.1; - } - else if(range > 0.02) - { - g_range_4000a[channelId] = PS4000A_50MV; - g_range[channelId] = PICO_X1_PROBE_50MV; - g_roundedRange[channelId] = 0.05; - } - else if(range > 0.01) - { - 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 > 10) - { - g_range_5000a[channelId] = PS5000A_20V; - g_roundedRange[channelId] = 20; - } - else if(range > 5) - { - g_range_5000a[channelId] = PS5000A_10V; - g_roundedRange[channelId] = 10; - } - else if(range > 2) - { - g_range_5000a[channelId] = PS5000A_5V; - g_roundedRange[channelId] = 5; - } - else if(range > 1) - { - g_range_5000a[channelId] = PS5000A_2V; - g_roundedRange[channelId] = 2; - } - else if(range > 0.5) - { - g_range_5000a[channelId] = PS5000A_1V; - g_roundedRange[channelId] = 1; - } - else if(range > 0.2) - { - g_range_5000a[channelId] = PS5000A_500MV; - g_roundedRange[channelId] = 0.5; - } - else if(range > 0.1) - { - g_range_5000a[channelId] = PS5000A_200MV; - g_roundedRange[channelId] = 0.2; - } - else if(range > 0.05) - { - g_range_5000a[channelId] = PS5000A_100MV; - g_roundedRange[channelId] = 0.1; - } - else if(range > 0.02) - { - g_range_5000a[channelId] = PS5000A_50MV; - g_roundedRange[channelId] = 0.05; - } - else if(range > 0.01) - { - 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 = min(range, 5.0); - - if(range > 100) - { - g_range[channelId] = PICO_X1_PROBE_200V; - g_roundedRange[channelId] = 200; - } - else if(range > 50) - { - g_range[channelId] = PICO_X1_PROBE_100V; - g_roundedRange[channelId] = 100; - } - else if(range > 20) - { - g_range[channelId] = PICO_X1_PROBE_50V; - g_roundedRange[channelId] = 50; - } - else if(range > 10) - { - g_range[channelId] = PICO_X1_PROBE_20V; - g_roundedRange[channelId] = 20; - } - else if(range > 5) - { - g_range[channelId] = PICO_X1_PROBE_10V; - g_roundedRange[channelId] = 10; - } - else if(range > 2) - { - g_range[channelId] = PICO_X1_PROBE_5V; - g_roundedRange[channelId] = 5; - } - else if(range > 1) - { - g_range[channelId] = PICO_X1_PROBE_2V; - g_roundedRange[channelId] = 2; - } - else if(range > 0.5) - { - g_range[channelId] = PICO_X1_PROBE_1V; - g_roundedRange[channelId] = 1; - } - else if(range > 0.2) - { - g_range[channelId] = PICO_X1_PROBE_500MV; - g_roundedRange[channelId] = 0.5; - } - else if(range > 0.1) - { - g_range[channelId] = PICO_X1_PROBE_200MV; - g_roundedRange[channelId] = 0.2; - } - else if(range >= 0.05) - { - g_range[channelId] = PICO_X1_PROBE_100MV; - g_roundedRange[channelId] = 0.1; - } - else if(range >= 0.02) - { - g_range[channelId] = PICO_X1_PROBE_50MV; - g_roundedRange[channelId] = 0.05; - } - else if(range >= 0.01) - { - 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; - } - - //SetAnalogOffset(channelId, g_offset[channelId]); - - 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. - // !! this is done in UpdateChannel() already !! - //if(g_triggerChannel == channelId) - // UpdateTrigger(); -} - -void PicoSCPIServer::SetAnalogOffset(size_t chIndex, double offset_V) -{ - lock_guard lock(g_mutex); - - int channelId = chIndex & 0xff; - - double maxoff; - double minoff; - float maxoff_f; - float minoff_f; - - //Clamp to allowed range - switch(g_series) - { - 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 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; - } - offset_V = min(maxoff, offset_V); - offset_V = max(minoff, offset_V); - - g_offset[channelId] = offset_V; - UpdateChannel(channelId); -} - -void PicoSCPIServer::SetDigitalThreshold(size_t chIndex, double threshold_V) -{ - int channelId = chIndex & 0xff; - int laneId = (chIndex >> 8) & 0xff; - int16_t code = 0; - - 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); - - lock_guard lock(g_mutex); - - //Update the pod if currently active - if(g_msoPodEnabled[channelId]) - EnableMsoPod(channelId); -} - -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; - - //Calculate hysteresis - int level = hysteresis; - if(level <= 50) - g_msoHysteresis[channelId] = PICO_LOW_50MV; - else if(level <= 100) - g_msoHysteresis[channelId] = PICO_NORMAL_100MV; - else if(level <= 200) - g_msoHysteresis[channelId] = PICO_HIGH_200MV; - else - g_msoHysteresis[channelId] = PICO_VERY_HIGH_400MV; - - LogTrace("Setting MSO pod %d hysteresis to %d mV (code %d)\n", - channelId, level, g_msoHysteresis[channelId]); - - //Update the pod if currently active - if(g_msoPodEnabled[channelId]) - EnableMsoPod(channelId); -} - -void PicoSCPIServer::SetSampleRate(uint64_t rate_hz) -{ - lock_guard lock(g_mutex); - int timebase; - double period_ns; - - switch(g_series) - { - case 3: - { - //Convert sample rate to sample period - g_sampleInterval = 1e15 / rate_hz; - period_ns = 1e9 / rate_hz; - - //Find closest timebase setting - 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 - { - //!! 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 6: - { - //Convert sample rate to sample period - g_sampleInterval = 1e15 / rate_hz; - period_ns = 1e9 / rate_hz; - - //Find closest timebase setting - double clkdiv = period_ns / 0.2; - if(period_ns < 5) - 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; - - default: /* Unknown Pico Type */ - { - - g_sampleInterval = 1e15 / rate_hz; - timebase = 0; - LogError("SetSampleRate Error unknown g_series\n"); - } - } - - g_timebase = timebase; - g_sampleRate = rate_hz; - UpdateTrigger(); //TESTING lasse -} - -void PicoSCPIServer::SetSampleDepth(uint64_t depth) -{ - lock_guard lock(g_mutex); - g_memDepth = depth; - - UpdateTrigger(); -} - -void PicoSCPIServer::SetTriggerDelay(uint64_t delay_fs) -{ - lock_guard lock(g_mutex); - - g_triggerDelay = delay_fs; - UpdateTrigger(); -} - -void PicoSCPIServer::SetTriggerSource(size_t chIndex) -{ - lock_guard lock(g_mutex); - - auto type = GetChannelType(chIndex); - switch(type) - { - case CH_ANALOG: - g_triggerChannel = chIndex & 0xff; - if(!g_channelOn[g_triggerChannel]) - { - LogDebug("Trigger channel wasn't on, enabling it\n"); - g_channelOn[g_triggerChannel] = true; - UpdateChannel(g_triggerChannel); - } - break; - - case CH_DIGITAL: - { - int npod = chIndex & 0xff; - int nchan = (chIndex >> 8) & 0xff; - g_triggerChannel = g_numChannels + npod*8 + nchan; - - if(!g_msoPodEnabled[npod]) - { - LogDebug("Trigger pod wasn't on, enabling it\n"); - EnableMsoPod(npod); - } - } - break; - - case CH_EXTERNAL_TRIGGER: - { - g_triggerChannel = PICO_TRIGGER_AUX; - UpdateTrigger(); - }; - - default: - //TODO - break; - } - - bool wasOn = g_triggerArmed; - Stop(); - - UpdateTrigger(); - - if(wasOn) - StartCapture(false); -} - -void PicoSCPIServer::SetTriggerLevel(double level_V) -{ - lock_guard lock(g_mutex); - - g_triggerVoltage = level_V; - UpdateTrigger(); -} - -void PicoSCPIServer::SetTriggerTypeEdge() -{ - //all triggers are edge, nothing to do here until we start supporting other trigger types -} - -bool PicoSCPIServer::IsTriggerArmed() -{ - return g_triggerArmed; -} - -void PicoSCPIServer::SetEdgeTriggerEdge(const string& edge) -{ - lock_guard lock(g_mutex); - - if(edge == "RISING") - g_triggerDirection = PICO_RISING; - else if(edge == "FALLING") - g_triggerDirection = PICO_FALLING; - else if(edge == "ANY") - g_triggerDirection = PICO_RISING_OR_FALLING; - - UpdateTrigger(); -} - -/** - @brief Pushes channel configuration to the instrument - */ -void UpdateChannel(size_t chan) -{ - int16_t scaleVal; - - switch(g_series) - { - 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_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(); - return; - } - 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(); - return; - } - 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; - - //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 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. - //TODO: handle multi-input triggers - if(chan == g_triggerChannel) - UpdateTrigger(); - } - else - ps6000aSetChannelOff(g_hScope, (PICO_CHANNEL)chan); - } - break; - } -} - -/** - @brief Pushes trigger configuration to the instrument - */ -void UpdateTrigger(bool force) -{ - //Timeout, in microseconds, before initiating a trigger - //Force trigger is really just a one-shot auto trigger with a 1us delay. - uint32_t timeout = 0; - if(force) - { - timeout = 1; - g_lastTriggerWasForced = true; - g_triggerOneShot = true; //TESTING lasse - } - else - g_lastTriggerWasForced = false; - - bool triggerIsAnalog = (g_triggerChannel < g_numChannels) || (g_triggerChannel == PICO_TRIGGER_AUX); - - //Convert threshold from volts to ADC counts - float offset = 0; - if(triggerIsAnalog) - offset = g_offset[g_triggerChannel]; - float scale = 1; - if(triggerIsAnalog) - { - scale = g_roundedRange[g_triggerChannel] / 32767; - if(scale == 0) - scale = 1; - } - float trig_code = (g_triggerVoltage - offset) / scale; - - //This can happen early on during initialization. - //Bail rather than dividing by zero. - if(g_sampleInterval == 0) - return; - - //Add delay before start of capture if needed - int64_t triggerDelaySamples = g_triggerDelay / g_sampleInterval; - uint64_t delay = 0; - if(triggerDelaySamples < 0) - delay = -triggerDelaySamples; - - switch(g_series) - { - case 3: - { - if(g_triggerChannel == PICO_TRIGGER_AUX) - { - /* TODO PICO_TRIGGER_AUX PICO3000A similarly to PICO6000A... */ - /* - //Seems external trigger only supports zero crossing??? - trig_code = 0; - - //Remove old trigger conditions - ps6000aSetTriggerChannelConditions( - g_hScope, - NULL, - 0, - PICO_CLEAR_ALL); - - //Set up new conditions - PICO_CONDITION cond; - cond.source = PICO_TRIGGER_AUX; - cond.condition = PICO_CONDITION_TRUE; - int ret = ps6000aSetTriggerChannelConditions( - g_hScope, - &cond, - 1, - PICO_ADD); - if(ret != PICO_OK) - LogError("ps6000aSetTriggerChannelConditions failed: %x\n", ret); - - PICO_DIRECTION dir; - dir.channel = PICO_TRIGGER_AUX; - dir.direction = PICO_RISING; - dir.thresholdMode = PICO_LEVEL; - ret = ps6000aSetTriggerChannelDirections( - g_hScope, - &dir, - 1); - if(ret != PICO_OK) - LogError("ps6000aSetTriggerChannelDirections failed: %x\n", ret); - - PICO_TRIGGER_CHANNEL_PROPERTIES prop; - prop.thresholdUpper = trig_code; - prop.thresholdUpperHysteresis = 32; - prop.thresholdLower = 0; - prop.thresholdLowerHysteresis = 0; - prop.channel = PICO_TRIGGER_AUX; - ret = ps6000aSetTriggerChannelProperties( - g_hScope, - &prop, - 1, - 0, - 0); - if(ret != PICO_OK) - LogError("ps6000aSetTriggerChannelProperties failed: %x\n", ret); - - if(force) - LogWarning("Force trigger doesn't currently work if trigger source is external\n"); - */ - /* API is same as 6000a API */ - int ret = ps3000aSetSimpleTrigger( - g_hScope, - 1, - (PS3000A_CHANNEL)PICO_TRIGGER_AUX, - 0, - (enPS3000AThresholdDirection)g_triggerDirection, - delay, - timeout); - if(ret != PICO_OK) - LogError("ps6000aSetSimpleTrigger failed: %x\n", ret); - } - else if(g_triggerChannel < g_numChannels) - { - /* API is same as 6000a API */ - int ret = ps3000aSetSimpleTrigger( - g_hScope, - 1, - (PS3000A_CHANNEL)g_triggerChannel, - round(trig_code), - (enPS3000AThresholdDirection)g_triggerDirection, // same as 6000a api - delay, - timeout); - if(ret != PICO_OK) - LogError("ps3000aSetSimpleTrigger failed: %x\n", ret); - } - else - { - //Remove old trigger conditions - 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, - round(trig_code), - (enPS5000AThresholdDirection)g_triggerDirection, // same as 6000a api - delay, - timeout); - if(ret != PICO_OK) - LogError("ps5000aSetSimpleTrigger failed: %x\n", ret); - } - else - { - //Remove old trigger conditions - ps5000aSetTriggerChannelConditionsV2( - g_hScope, - NULL, - 0, - PS5000A_CLEAR); - - //Set up new conditions - int ntrig = g_triggerChannel - g_numChannels; - int trigpod = ntrig / 8; - int triglane = ntrig % 8; - PS5000A_CONDITION cond; - cond.source = static_cast(PS5000A_DIGITAL_PORT0 + trigpod); - cond.condition = PS5000A_CONDITION_TRUE; - ps5000aSetTriggerChannelConditionsV2( - g_hScope, - &cond, - 1, - PS5000A_ADD); - - //Set up configuration on the selected channel - 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, - &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 6: - { - if(g_triggerChannel == PICO_TRIGGER_AUX) - { - /* - //Seems external trigger only supports zero crossing??? - trig_code = 0; - - //Remove old trigger conditions - ps6000aSetTriggerChannelConditions( - g_hScope, - NULL, - 0, - PICO_CLEAR_ALL); - - //Set up new conditions - PICO_CONDITION cond; - cond.source = PICO_TRIGGER_AUX; - cond.condition = PICO_CONDITION_TRUE; - int ret = ps6000aSetTriggerChannelConditions( - g_hScope, - &cond, - 1, - PICO_ADD); - if(ret != PICO_OK) - LogError("ps6000aSetTriggerChannelConditions failed: %x\n", ret); - - PICO_DIRECTION dir; - dir.channel = PICO_TRIGGER_AUX; - dir.direction = PICO_RISING; - dir.thresholdMode = PICO_LEVEL; - ret = ps6000aSetTriggerChannelDirections( - g_hScope, - &dir, - 1); - if(ret != PICO_OK) - LogError("ps6000aSetTriggerChannelDirections failed: %x\n", ret); - - PICO_TRIGGER_CHANNEL_PROPERTIES prop; - prop.thresholdUpper = trig_code; - prop.thresholdUpperHysteresis = 32; - prop.thresholdLower = 0; - prop.thresholdLowerHysteresis = 0; - prop.channel = PICO_TRIGGER_AUX; - ret = ps6000aSetTriggerChannelProperties( - g_hScope, - &prop, - 1, - 0, - 0); - if(ret != PICO_OK) - LogError("ps6000aSetTriggerChannelProperties failed: %x\n", ret); - - if(force) - LogWarning("Force trigger doesn't currently work if trigger source is external\n"); - */ - - int ret = ps6000aSetSimpleTrigger( - g_hScope, - 1, - PICO_TRIGGER_AUX, - 0, - g_triggerDirection, - delay, - timeout); - if(ret != PICO_OK) - LogError("ps6000aSetSimpleTrigger failed: %x\n", ret); - } - else if(g_triggerChannel < g_numChannels) - { - int ret = ps6000aSetSimpleTrigger( - g_hScope, - 1, - (PICO_CHANNEL)g_triggerChannel, - round(trig_code), - g_triggerDirection, - delay, - timeout); - if(ret != PICO_OK) - LogError("ps6000aSetSimpleTrigger failed: %x\n", ret); - } - else - { - //Remove old trigger conditions - ps6000aSetTriggerChannelConditions( - g_hScope, - NULL, - 0, - PICO_CLEAR_ALL); - - //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( - g_hScope, - &cond, - 1, - PICO_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( - g_hScope, - cond.source, - &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; - } - - if(g_triggerArmed) - StartCapture(true); -} - -void Stop() -{ - switch(g_series) - { - case 3: - ps3000aStop(g_hScope); - break; - - case 4: - ps4000aStop(g_hScope); - break; - - case 5: - ps5000aStop(g_hScope); - break; - - case 6: - ps6000aStop(g_hScope); - break; - } -} - -PICO_STATUS StartInternal() -{ - //Calculate pre/post trigger time configuration based on trigger delay - 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_series) - { - case 3: - return ps3000aRunBlock(g_hScope, nPreTrigger_int, nPostTrigger_int, g_timebase, 1, NULL, 0, NULL, NULL); - - 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; - } -} - -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) - { - Stop(); - UpdateTrigger(); - } - - g_offsetDuringArm = g_offset; - g_channelOnDuringArm = g_channelOn; - for(size_t i=0; i(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 - } - } -} - From 7220cd6226bbba48de55a0f74733c5a2a8792aa1 Mon Sep 17 00:00:00 2001 From: Lasse Beyer Date: Sun, 9 Nov 2025 16:10:47 +0100 Subject: [PATCH 10/12] Implemented model dependent AWG buffer size Implemented model dependent AWG buffer size and fixed a bug where the function generator would start running on changing any parameter, even if it was set to OFF. --- src/ps6000d/PicoSCPIServer.cpp | 100 ++++++++++++++++++++++++--------- 1 file changed, 73 insertions(+), 27 deletions(-) diff --git a/src/ps6000d/PicoSCPIServer.cpp b/src/ps6000d/PicoSCPIServer.cpp index ee8cc22..5e85bcd 100644 --- a/src/ps6000d/PicoSCPIServer.cpp +++ b/src/ps6000d/PicoSCPIServer.cpp @@ -203,6 +203,7 @@ 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; @@ -232,13 +233,12 @@ const map g_waveformTypes = {"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_WHITE_NOISE, 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 }; -#define SIG_GEN_BUFFER_SIZE 8192 //TODO: allow model specific/variable buffer size. Must be power of 2 for most AWGs PS3000: 2^13, PS3207B: 2^14 PS3206B: 2^15 -int16_t* g_arbitraryWaveform = new int16_t[SIG_GEN_BUFFER_SIZE]; // +int16_t* g_arbitraryWaveform; void GenerateSquareWave(int16_t* &waveform, size_t bufferSize, double dutyCycle, int16_t amplitude = 32767); void ReconfigAWG(); @@ -251,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() @@ -635,6 +671,7 @@ bool PicoSCPIServer::OnCommand( g_awgRange = tempRange; g_awgOffset = tempOffset; + g_awgOn = false; } else if(g_series == 4) { @@ -663,6 +700,7 @@ bool PicoSCPIServer::OnCommand( g_awgRange = tempRange; g_awgOffset = tempOffset; + g_awgOn = false; } else if(g_series == 5) { @@ -691,6 +729,7 @@ bool PicoSCPIServer::OnCommand( g_awgRange = tempRange; g_awgOffset = tempOffset; + g_awgOn = false; } else { @@ -742,7 +781,7 @@ bool PicoSCPIServer::OnCommand( /* 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, SIG_GEN_BUFFER_SIZE, (double) duty); + GenerateSquareWave(g_arbitraryWaveform, g_awgBufferSize, (double) duty); else LogError("PICO3000A DUTY TODO code\n"); } @@ -753,7 +792,7 @@ bool PicoSCPIServer::OnCommand( /* 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, SIG_GEN_BUFFER_SIZE, (double) duty); + GenerateSquareWave(g_arbitraryWaveform, g_awgBufferSize, (double) duty); else LogError("PICO4000A DUTY TODO code\n"); } @@ -764,7 +803,7 @@ bool PicoSCPIServer::OnCommand( /* 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, SIG_GEN_BUFFER_SIZE, (double) duty); + GenerateSquareWave(g_arbitraryWaveform, g_awgBufferSize, (double) duty); else LogError("PICO5000A DUTY TODO code\n"); } @@ -822,7 +861,7 @@ bool PicoSCPIServer::OnCommand( } if( (g_awgPS3000AWaveType == PS3000A_SQUARE) ) { - GenerateSquareWave(g_arbitraryWaveform, SIG_GEN_BUFFER_SIZE, 50); + GenerateSquareWave(g_arbitraryWaveform, g_awgBufferSize, 50); } g_awgPS3000AWaveType = waveform->second.type3000; g_awgPS3000AOperation = waveform->second.op3000; @@ -845,7 +884,7 @@ bool PicoSCPIServer::OnCommand( } if( (g_awgPS4000AWaveType == PS4000A_SQUARE) ) { - GenerateSquareWave(g_arbitraryWaveform, SIG_GEN_BUFFER_SIZE, 50); + GenerateSquareWave(g_arbitraryWaveform, g_awgBufferSize, 50); } g_awgPS4000AWaveType = waveform->second.type4000; g_awgPS4000AOperation = waveform->second.op4000; @@ -868,7 +907,7 @@ bool PicoSCPIServer::OnCommand( } if( (g_awgPS5000AWaveType == PS5000A_SQUARE) ) { - GenerateSquareWave(g_arbitraryWaveform, SIG_GEN_BUFFER_SIZE, 50); + GenerateSquareWave(g_arbitraryWaveform, g_awgBufferSize, 50); } g_awgPS5000AWaveType = waveform->second.type5000; g_awgPS5000AOperation = waveform->second.op5000; @@ -1145,6 +1184,13 @@ void PicoSCPIServer::ReconfigAWG() double freq = g_awgFreq; double inc = 0; double dwell = 0; + float tempRange = g_awgRange; + float tempOffset = g_awgOffset; + if(!g_awgOn) + { + tempRange = 0; + tempOffset = 0; + } switch(g_series) { @@ -1154,19 +1200,19 @@ void PicoSCPIServer::ReconfigAWG() if(g_awgPS3000AWaveType == PS3000A_SQUARE || g_awgPS3000AWaveType == PS3000A_MAX_WAVE_TYPES) { uint32_t delta= 0; - auto status = ps3000aSigGenFrequencyToPhase(g_hScope, g_awgFreq, PS3000A_SINGLE, SIG_GEN_BUFFER_SIZE, &delta); + 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, - g_awgOffset*1e6, - g_awgRange*1e6*2, + tempOffset*1e6, + tempRange*1e6*2, delta, delta, 0, 0, g_arbitraryWaveform, - SIG_GEN_BUFFER_SIZE, + g_awgBufferSize, PS3000A_UP, // sweepType PS3000A_ES_OFF, // operation PS3000A_SINGLE, // indexMode @@ -1182,8 +1228,8 @@ void PicoSCPIServer::ReconfigAWG() { auto status = ps3000aSetSigGenBuiltInV2( g_hScope, - g_awgOffset*1e6, //Offset Voltage in µV - g_awgRange *1e6*2, // Peak to Peak Range in µV + tempOffset*1e6, //Offset Voltage in µV + tempRange *1e6*2, // Peak to Peak Range in µV g_awgPS3000AWaveType, freq, freq, @@ -1210,19 +1256,19 @@ void PicoSCPIServer::ReconfigAWG() if(g_awgPS4000AWaveType == PS4000A_SQUARE || g_awgPS4000AWaveType == PS4000A_MAX_WAVE_TYPES) { uint32_t delta= 0; - auto status = ps4000aSigGenFrequencyToPhase(g_hScope, g_awgFreq, PS4000A_SINGLE, SIG_GEN_BUFFER_SIZE, &delta); + 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, - g_awgOffset*1e6, - g_awgRange*1e6*2, + tempOffset*1e6, + tempRange*1e6*2, delta, delta, 0, 0, g_arbitraryWaveform, - SIG_GEN_BUFFER_SIZE, + g_awgBufferSize, PS4000A_UP, // sweepType PS4000A_ES_OFF, // operation PS4000A_SINGLE, // indexMode @@ -1238,8 +1284,8 @@ void PicoSCPIServer::ReconfigAWG() { auto status = ps4000aSetSigGenBuiltInV2( g_hScope, - g_awgOffset*1e6, //Offset Voltage in µV - g_awgRange *1e6*2, // Peak to Peak Range in µV + tempOffset*1e6, //Offset Voltage in µV + tempRange *1e6*2, // Peak to Peak Range in µV g_awgPS4000AWaveType, freq, freq, @@ -1266,19 +1312,19 @@ void PicoSCPIServer::ReconfigAWG() if(g_awgPS5000AWaveType == PS5000A_SQUARE || g_awgPS5000AWaveType == PS5000A_MAX_WAVE_TYPES) { uint32_t delta= 0; - auto status = ps5000aSigGenFrequencyToPhase(g_hScope, g_awgFreq, PS5000A_SINGLE, SIG_GEN_BUFFER_SIZE, &delta); + 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, - g_awgOffset*1e6, - g_awgRange*1e6*2, + tempOffset*1e6, + tempRange*1e6*2, delta, delta, 0, 0, g_arbitraryWaveform, - SIG_GEN_BUFFER_SIZE, + g_awgBufferSize, PS5000A_UP, // sweepType PS5000A_ES_OFF, // operation PS5000A_SINGLE, // indexMode @@ -1294,8 +1340,8 @@ void PicoSCPIServer::ReconfigAWG() { auto status = ps5000aSetSigGenBuiltInV2( g_hScope, - g_awgOffset*1e6, //Offset Voltage in µV - g_awgRange *1e6*2, // Peak to Peak Range in µV + tempOffset*1e6, //Offset Voltage in µV + tempRange *1e6*2, // Peak to Peak Range in µV g_awgPS5000AWaveType, freq, freq, From 37613039944c94cb4f38ba43627415a62ac43ee6 Mon Sep 17 00:00:00 2001 From: Lasse Beyer Date: Sun, 9 Nov 2025 20:55:36 +0100 Subject: [PATCH 11/12] Corrected scaling for trigger channel The scale value for the current trigger channel is always the same for the 6000E series (and the same for the external trigger input), but it varies with ADC resolution for other series. On some models it is even different for the external input and the analog channels. This is now taken into account. Filtered out some illegal ":RANGE" commands that would otherwise spam the Debug Log. They occur if one or more digital channels are added to a WaveformView with analog channels and those voltage range gets adjusted, which does not work for digital channels. --- src/ps6000d/PicoSCPIServer.cpp | 8 +++++++- src/ps6000d/WaveformServerThread.cpp | 13 +++++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/ps6000d/PicoSCPIServer.cpp b/src/ps6000d/PicoSCPIServer.cpp index 5e85bcd..9b9b91a 100644 --- a/src/ps6000d/PicoSCPIServer.cpp +++ b/src/ps6000d/PicoSCPIServer.cpp @@ -1098,7 +1098,6 @@ bool PicoSCPIServer::OnCommand( } } - //TODO: bandwidth limiter else if( (cmd == "BWLIM") && (args.size() == 1) ) { //Extract channel ID from subject and clamp bounds @@ -1114,6 +1113,13 @@ bool PicoSCPIServer::OnCommand( SetChannelBandwidthLimiter(channelId, freq_mhz); } + 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()); diff --git a/src/ps6000d/WaveformServerThread.cpp b/src/ps6000d/WaveformServerThread.cpp index 2595dc8..30fe176 100644 --- a/src/ps6000d/WaveformServerThread.cpp +++ b/src/ps6000d/WaveformServerThread.cpp @@ -341,8 +341,17 @@ float InterpolateTriggerTime(int16_t* buf) if(g_triggerSampleIndex <= 0) return 0; - //TODO trigger scale value depends on ADC setting and is different for EXT trig input - 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; From 58d55f982eb301bdc8134933b3732917d5fbc95b Mon Sep 17 00:00:00 2001 From: Lasse Beyer Date: Tue, 11 Nov 2025 19:22:12 +0100 Subject: [PATCH 12/12] Tidied up the list with available Sample Rates Selectable sample rates are now evenly spaced and span from 1kS/s as the lowest setting up to the highest available value. Since not all values are available on every device, there will be at most 8 different values per magnitude. --- src/ps6000d/PicoSCPIServer.cpp | 142 +++++++++++++++++++++++++++++---- 1 file changed, 128 insertions(+), 14 deletions(-) diff --git a/src/ps6000d/PicoSCPIServer.cpp b/src/ps6000d/PicoSCPIServer.cpp index 9b9b91a..3b3ee40 100644 --- a/src/ps6000d/PicoSCPIServer.cpp +++ b/src/ps6000d/PicoSCPIServer.cpp @@ -491,21 +491,136 @@ 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 - //TODO: Use API-call to obtain some neat and evenly spaced values - size_t vec[] = + switch(g_series) { - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, - 10, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131027, 262144, 524288, 1048576, - 2097152, 4194304, 8388608, 16777216 - //14, 29, 54, 104, 129, 254, 504, 629, 1254, 2504, 3129, 5004, 6254, 12504, 15629, 25004, 31254, - //62504, 125004, 156254, 250004, 312504, 500004, 625004, 1000004, 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; @@ -541,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. @@ -550,7 +665,6 @@ vector PicoSCPIServer::GetSampleRates() else LogWarning("GetTimebase failed, code %d / 0x%x\n", status, status); } - return rates; }