Created
January 20, 2022 20:23
-
-
Save dannas/cf01ba5687d3ed4d00baa84661c33f26 to your computer and use it in GitHub Desktop.
Mubes changes to SiglentSCPIOscilloscope.cpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*********************************************************************************************************************** | |
* * | |
* libscopehal v0.1 * | |
* * | |
* Copyright (c) 2012-2021 Andrew D. Zonenberg and contributors * | |
* 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. * | |
* * | |
***********************************************************************************************************************/ | |
/* | |
* Generic Siglent scope driver. Currently supports SDS2000X+ and SDS1104X-E. | |
* This file was originally derived from the LeCroy driver but has been modified extensively. | |
* Note that this port replaces the previous Siglent driver, which was non-functional. That is available in the git | |
* archive if needed. | |
* | |
* SDS2000/5000/6000 port (c) 2021 Dave Marples. | |
* Tested on SDS2000X+. If someone wants to loan an SDS5000/6000A/6000Pro for testing those | |
* can be integrated and the changes required should be limited. | |
* | |
* Starting SDS1104X-E port @@@_ADD_COPYRIGHT_ | |
* Using Programming Guide PG01-E02D an Firmware 6.1.37R8 | |
* Tested on SDS1104X-E. Should also support SDS10000CML+/CNL+/Dl+/E+/F+, SDS2000/2000x, SDS1000x/1000x+, | |
* SDS1000X-E/X-C but these are untested. Feedback and/or loan instruments appreciated. | |
* | |
* | |
* Current State | |
* ============= | |
* | |
* SDS2000XP | |
* | |
* - Basic functionality for analog channels works. | |
* - There is no feature detection because the scope does not support *OPT? (Request made) | |
* - Digital channels are not implemented (code in here is leftover from LeCroy) | |
* - Triggers are untested. | |
* - Sampling lengths up to 10MSamples are supported. 50M and 100M need to be batched and will be | |
* horribly slow. | |
* | |
* SDS1104X-E | |
* | |
* Using Programming Guide PG01-E02D and Firmware 6.1.37R8 | |
* receive data from scope on c1 c2 c3 c4 | |
* set EDGE Trigger on channel C1 | |
* using 4 Channels ( 70 kS 25 MS/s) got 4,23 WFM/s | |
* using 4 Channels ( 700 kpts 100 MSa/s) got 1,62 WFM/s | |
* using 1 Channels ( 1.75 Mpts 250 MSa/s) got 2,38 WFM/s | |
* using 4 Channels ( 3.5 Mpts 500 MSa/s) got 0,39 WFM/s | |
* | |
*/ | |
#include "scopehal.h" | |
#include "SiglentSCPIOscilloscope.h" | |
#include "base64.h" | |
#include <locale> | |
#include <stdarg.h> | |
#include <omp.h> | |
#include <thread> | |
#include <chrono> | |
#include "DropoutTrigger.h" | |
#include "EdgeTrigger.h" | |
#include "PulseWidthTrigger.h" | |
#include "RuntTrigger.h" | |
#include "SlewRateTrigger.h" | |
#include "UartTrigger.h" | |
#include "WindowTrigger.h" | |
using namespace std; | |
static const struct | |
{ | |
const char* name; | |
float val; | |
} c_sds2000xp_threshold_table[] = {{"TTL", 1.5F}, {"CMOS", 1.65F}, {"LVCMOS33", 1.65F}, {"LVCMOS25", 1.25F}, {NULL, 0}}; | |
static const std::chrono::milliseconds c_setting_delay(50); // Delay required when setting parameters via SCPI | |
static const std::chrono::milliseconds c_trigger_delay(1000); // Delay required when forcing trigger | |
static const char* c_custom_thresh = "CUSTOM,"; // Prepend string for custom digital threshold | |
static const float c_thresh_thresh = 0.01f; // Zero equivalence threshold for fp comparisons | |
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
// Construction / destruction | |
SiglentSCPIOscilloscope::SiglentSCPIOscilloscope(SCPITransport* transport) | |
: SCPIOscilloscope(transport) | |
, m_hasLA(false) | |
, m_hasDVM(false) | |
, m_hasFunctionGen(false) | |
, m_hasFastSampleRate(false) | |
, m_memoryDepthOption(0) | |
, m_hasI2cTrigger(false) | |
, m_hasSpiTrigger(false) | |
, m_hasUartTrigger(false) | |
, m_maxBandwidth(10000) | |
, m_triggerArmed(false) | |
, m_triggerOneShot(false) | |
, m_sampleRateValid(false) | |
, m_sampleRate(1) | |
, m_memoryDepthValid(false) | |
, m_memoryDepth(1) | |
, m_triggerOffsetValid(false) | |
, m_triggerOffset(0) | |
, m_interleaving(false) | |
, m_interleavingValid(false) | |
, m_highDefinition(false) | |
{ | |
// Set a base read time | |
next_tx = chrono::system_clock::now(); | |
//standard initialization | |
FlushConfigCache(); | |
IdentifyHardware(); | |
DetectAnalogChannels(); | |
SharedCtorInit(); | |
DetectOptions(); | |
} | |
string SiglentSCPIOscilloscope::converse(const char* fmt, ...) | |
{ | |
string ret; | |
char opString[128]; | |
va_list va; | |
va_start(va, fmt); | |
vsnprintf(opString, sizeof(opString), fmt, va); | |
va_end(va); | |
LogTrace("TX: %s\r\n", opString); | |
this_thread::sleep_until(next_tx); | |
m_transport->FlushRXBuffer(); | |
m_transport->SendCommand(opString); | |
ret = m_transport->ReadReply(); | |
LogTrace("RX: %s\r\n\r\n", ret.c_str()); | |
return ret; | |
} | |
void SiglentSCPIOscilloscope::sendOnly(const char* fmt, ...) | |
{ | |
char opString[128]; | |
va_list va; | |
va_start(va, fmt); | |
vsnprintf(opString, sizeof(opString), fmt, va); | |
va_end(va); | |
LogTrace("TXO: %s\r\n", opString); | |
this_thread::sleep_until(next_tx); | |
m_transport->FlushRXBuffer(); | |
m_transport->SendCommand(opString); | |
next_tx = chrono::system_clock::now() + c_setting_delay; | |
} | |
void SiglentSCPIOscilloscope::SharedCtorInit() | |
{ | |
m_digitalChannelCount = 0; | |
//Add the external trigger input | |
m_extTrigChannel = | |
new OscilloscopeChannel(this, "Ext", OscilloscopeChannel::CHANNEL_TYPE_TRIGGER, "", 1, m_channels.size(), true); | |
m_channels.push_back(m_extTrigChannel); | |
switch(m_modelid) | |
{ | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS1000: | |
// Omit header and units in numbers for responses to queries. | |
sendOnly("CHDR OFF"); | |
break; | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS2000XP: | |
case MODEL_SIGLENT_SDS5000X: | |
//Desired format for waveform data | |
//Only use increased bit depth if the scope actually puts content there! | |
sendOnly(":WAVEFORM:WIDTH %s", m_highDefinition ? "WORD" : "BYTE"); | |
break; | |
// -------------------------------------------------- | |
default: | |
LogError("Unknown scope type\n"); | |
break; | |
// -------------------------------------------------- | |
} | |
//Clear the state-change register to we get rid of any history we don't care about | |
PollTrigger(); | |
} | |
void SiglentSCPIOscilloscope::IdentifyHardware() | |
{ | |
//Ask for the ID | |
string reply = converse("*IDN?"); | |
char vendor[128] = ""; | |
char model[128] = ""; | |
char serial[128] = ""; | |
char version[128] = ""; | |
if(4 != sscanf(reply.c_str(), "%127[^,],%127[^,],%127[^,],%127s", vendor, model, serial, version)) | |
{ | |
LogError("Bad IDN response %s\n", reply.c_str()); | |
return; | |
} | |
m_vendor = vendor; | |
m_model = model; | |
m_serial = serial; | |
m_fwVersion = version; | |
//Look up model info | |
m_modelid = MODEL_UNKNOWN; | |
m_maxBandwidth = 0; | |
if(m_vendor.compare("Siglent Technologies") == 0) | |
{ | |
// TODO(dannas): Tighten this check | |
// The Programming Guide says that we support SDS1000CFL, SDS1000A, | |
// SDS10000CML+/CNL+/Dl+/E+/F+, SDS2000/2000x, SDS1000x/1000x+, | |
// SDS1000X-E/X-C. But I only have a SDS1004X-E so we should only check for that | |
if(m_model.compare(0, 4, "SDS1") == 0) | |
{ | |
m_modelid = MODEL_SIGLENT_SDS1000; | |
m_maxBandwidth = 100; | |
if(m_model.compare(4, 1, "2") == 0) | |
m_maxBandwidth = 200; | |
if(m_fwVersion != "8.1.6.1.37R8") | |
LogWarning("Siglent firmware \"%s\" is not tested\n", m_fwVersion.c_str()); | |
return; | |
} | |
else if(m_model.compare(0, 4, "SDS2") == 0 && m_model.back() == 's') | |
{ | |
m_modelid = MODEL_SIGLENT_SDS2000XP; | |
m_maxBandwidth = 100; | |
if(m_model.compare(4, 1, "2") == 0) | |
m_maxBandwidth = 200; | |
else if(m_model.compare(4, 1, "3") == 0) | |
m_maxBandwidth = 350; | |
if(m_model.compare(4, 1, "5") == 0) | |
m_maxBandwidth = 500; | |
} | |
else if(m_model.compare(0, 4, "SDS5") == 0) | |
{ | |
m_modelid = MODEL_SIGLENT_SDS5000X; | |
m_maxBandwidth = 350; | |
if(m_model.compare(5, 1, "5") == 0) | |
m_maxBandwidth = 500; | |
if(m_model.compare(5, 1, "0") == 0) | |
m_maxBandwidth = 1000; | |
} | |
else | |
{ | |
LogWarning("Model \"%s\" is unknown, available sample rates/memory depths may not be properly detected\n", | |
m_model.c_str()); | |
} | |
} | |
else | |
{ | |
LogWarning("Vendor \"%s\" is unknown\n", m_vendor.c_str()); | |
} | |
} | |
void SiglentSCPIOscilloscope::DetectOptions() | |
{ | |
//AddDigitalChannels(16); | |
/* SDS2000XP has no capability to find the options :-( */ | |
return; | |
} | |
/** | |
@brief Creates digital channels for the oscilloscope | |
*/ | |
void SiglentSCPIOscilloscope::AddDigitalChannels(unsigned int count) | |
{ | |
m_digitalChannelCount = count; | |
m_digitalChannelBase = m_channels.size(); | |
char chn[32]; | |
for(unsigned int i = 0; i < count; i++) | |
{ | |
snprintf(chn, sizeof(chn), "D%u", i); | |
auto chan = new OscilloscopeChannel(this, | |
chn, | |
OscilloscopeChannel::CHANNEL_TYPE_DIGITAL, | |
GetDefaultChannelColor(m_channels.size()), | |
1, | |
m_channels.size(), | |
true); | |
m_channels.push_back(chan); | |
m_digitalChannels.push_back(chan); | |
} | |
} | |
/** | |
@brief Figures out how many analog channels we have, and add them to the device | |
*/ | |
void SiglentSCPIOscilloscope::DetectAnalogChannels() | |
{ | |
int nchans = 1; | |
// Char 7 of the model name is the number of channels | |
if(m_model.length() > 7) | |
{ | |
switch(m_model[6]) | |
{ | |
case '2': | |
nchans = 2; | |
break; | |
case '4': | |
nchans = 4; | |
break; | |
} | |
} | |
for(int i = 0; i < nchans; i++) | |
{ | |
//Hardware name of the channel | |
string chname = string("C1"); | |
chname[1] += i; | |
//Color the channels based on Siglents standard color sequence | |
//yellow-pink-cyan-green-lightgreen | |
string color = "#ffffff"; | |
switch(i % 4) | |
{ | |
case 0: | |
color = "#ffff00"; | |
break; | |
case 1: | |
color = "#ff6abc"; | |
break; | |
case 2: | |
color = "#00ffff"; | |
break; | |
case 3: | |
color = "#00c100"; | |
break; | |
} | |
//Create the channel | |
m_channels.push_back( | |
new OscilloscopeChannel(this, chname, OscilloscopeChannel::CHANNEL_TYPE_ANALOG, color, 1, i, true)); | |
} | |
m_analogChannelCount = nchans; | |
} | |
SiglentSCPIOscilloscope::~SiglentSCPIOscilloscope() | |
{ | |
} | |
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
// Device information | |
string SiglentSCPIOscilloscope::GetDriverNameInternal() | |
{ | |
return "siglent"; | |
} | |
OscilloscopeChannel* SiglentSCPIOscilloscope::GetExternalTrigger() | |
{ | |
return m_extTrigChannel; | |
} | |
void SiglentSCPIOscilloscope::FlushConfigCache() | |
{ | |
lock_guard<recursive_mutex> lock(m_cacheMutex); | |
if(m_trigger) | |
delete m_trigger; | |
m_trigger = NULL; | |
m_channelVoltageRanges.clear(); | |
m_channelOffsets.clear(); | |
m_channelsEnabled.clear(); | |
m_channelDeskew.clear(); | |
m_channelDisplayNames.clear(); | |
m_probeIsActive.clear(); | |
m_sampleRateValid = false; | |
m_memoryDepthValid = false; | |
m_triggerOffsetValid = false; | |
m_interleavingValid = false; | |
m_meterModeValid = false; | |
} | |
/** | |
@brief See what measurement capabilities we have | |
*/ | |
unsigned int SiglentSCPIOscilloscope::GetMeasurementTypes() | |
{ | |
unsigned int type = 0; | |
return type; | |
} | |
/** | |
@brief See what features we have | |
*/ | |
unsigned int SiglentSCPIOscilloscope::GetInstrumentTypes() | |
{ | |
unsigned int type = INST_OSCILLOSCOPE; | |
if(m_hasFunctionGen) | |
type |= INST_FUNCTION; | |
return type; | |
} | |
string SiglentSCPIOscilloscope::GetName() | |
{ | |
return m_model; | |
} | |
string SiglentSCPIOscilloscope::GetVendor() | |
{ | |
return m_vendor; | |
} | |
string SiglentSCPIOscilloscope::GetSerial() | |
{ | |
return m_serial; | |
} | |
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
// Channel configuration | |
bool SiglentSCPIOscilloscope::IsChannelEnabled(size_t i) | |
{ | |
//ext trigger should never be displayed | |
if(i == m_extTrigChannel->GetIndex()) | |
return false; | |
//Early-out if status is in cache | |
{ | |
lock_guard<recursive_mutex> lock2(m_cacheMutex); | |
if(m_channelsEnabled.find(i) != m_channelsEnabled.end()) | |
return m_channelsEnabled[i]; | |
} | |
//Need to lock the main mutex first to prevent deadlocks | |
lock_guard<recursive_mutex> lock(m_mutex); | |
lock_guard<recursive_mutex> lock2(m_cacheMutex); | |
//Analog | |
if(i < m_analogChannelCount) | |
{ | |
//See if the channel is enabled, hide it if not | |
string reply; | |
switch(m_modelid) | |
{ | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS1000: | |
reply = converse("C%d:TRACE?", i + 1); | |
m_channelsEnabled[i] = (reply.find("OFF") != 0); //may have a trailing newline, ignore that | |
break; | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS2000XP: | |
case MODEL_SIGLENT_SDS5000X: | |
reply = converse(":CHANNEL%d:SWITCH?", i + 1); | |
m_channelsEnabled[i] = (reply.find("OFF") != 0); //may have a trailing newline, ignore that | |
break; | |
// -------------------------------------------------- | |
default: | |
LogError("Unknown scope type\n"); | |
break; | |
// -------------------------------------------------- | |
} | |
} | |
else | |
{ | |
//Digital | |
//See if the channel is on | |
size_t nchan = i - (m_analogChannelCount + 1); | |
string str = converse(":DIGITAL:D%d?", nchan); | |
m_channelsEnabled[i] = (str == "OFF") ? false : true; | |
} | |
return m_channelsEnabled[i]; | |
} | |
void SiglentSCPIOscilloscope::EnableChannel(size_t i) | |
{ | |
lock_guard<recursive_mutex> lock(m_mutex); | |
//If this is an analog channel, just toggle it | |
if(i < m_analogChannelCount) | |
{ | |
switch(m_modelid) | |
{ | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS1000: | |
sendOnly(":C%d:TRACE ON", i + 1); | |
break; | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS2000XP: | |
case MODEL_SIGLENT_SDS5000X: | |
sendOnly(":CHANNEL%d:SWITCH ON", i + 1); | |
break; | |
// -------------------------------------------------- | |
default: | |
LogError("Unknown scope type\n"); | |
break; | |
// -------------------------------------------------- | |
} | |
} | |
else if(i == m_extTrigChannel->GetIndex()) | |
{ | |
//Trigger can't be enabled | |
} | |
else | |
{ | |
//Digital channel | |
sendOnly(":DIGITAL:D%d ON", i - (m_analogChannelCount + 1)); | |
} | |
m_channelsEnabled[i] = true; | |
} | |
bool SiglentSCPIOscilloscope::CanEnableChannel(size_t i) | |
{ | |
// Can enable all channels except trigger | |
return !(i == m_extTrigChannel->GetIndex()); | |
} | |
void SiglentSCPIOscilloscope::DisableChannel(size_t i) | |
{ | |
lock_guard<recursive_mutex> lock(m_mutex); | |
m_channelsEnabled[i] = false; | |
if(i < m_analogChannelCount) | |
{ | |
switch(m_modelid) | |
{ | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS1000: | |
sendOnly("C%d:TRACE OFF", i + 1); | |
break; | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS2000XP: | |
case MODEL_SIGLENT_SDS5000X: | |
//If this is an analog channel, just toggle it | |
if(i < m_analogChannelCount) | |
sendOnly(":CHANNEL%d:TRACE OFF", i + 1); | |
break; | |
// -------------------------------------------------- | |
default: | |
LogError("Unknown scope type\n"); | |
break; | |
// -------------------------------------------------- | |
} | |
} | |
else if(i == m_extTrigChannel->GetIndex()) | |
{ | |
//Trigger can't be enabled | |
} | |
else | |
{ | |
//Digital channel | |
//Disable this channel | |
sendOnly(":DIGITAL:D%d OFF", i - (m_analogChannelCount + 1)); | |
//If we have NO digital channels enabled, disable the appropriate digital bus | |
//bool anyDigitalEnabled = false; | |
// for (uint32_t c=m_analogChannelCount+1+((chNum/8)*8); c<(m_analogChannelCount+1+((chNum/8)*8)+c_digiChannelsPerBus); c++) | |
// anyDigitalEnabled |= m_channelsEnabled[c]; | |
// if(!anyDigitalEnabled) | |
//sendOnly(":DIGITAL:BUS%d:DISP OFF",chNum/8); | |
} | |
} | |
vector<OscilloscopeChannel::CouplingType> SiglentSCPIOscilloscope::GetAvailableCouplings(size_t /*i*/) | |
{ | |
vector<OscilloscopeChannel::CouplingType> ret; | |
switch(m_modelid) | |
{ | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS1000: | |
ret.push_back(OscilloscopeChannel::COUPLE_DC_1M); | |
ret.push_back(OscilloscopeChannel::COUPLE_AC_1M); | |
ret.push_back(OscilloscopeChannel::COUPLE_GND); | |
break; | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS2000XP: | |
case MODEL_SIGLENT_SDS5000X: | |
ret.push_back(OscilloscopeChannel::COUPLE_DC_1M); | |
ret.push_back(OscilloscopeChannel::COUPLE_AC_1M); | |
ret.push_back(OscilloscopeChannel::COUPLE_DC_50); | |
ret.push_back(OscilloscopeChannel::COUPLE_AC_50); | |
ret.push_back(OscilloscopeChannel::COUPLE_GND); | |
break; | |
// -------------------------------------------------- | |
default: | |
LogError("Unknown scope type\n"); | |
break; | |
// -------------------------------------------------- | |
} | |
return ret; | |
} | |
OscilloscopeChannel::CouplingType SiglentSCPIOscilloscope::GetChannelCoupling(size_t i) | |
{ | |
if(i >= m_analogChannelCount) | |
return OscilloscopeChannel::COUPLE_SYNTHETIC; | |
string replyType; | |
string replyImp; | |
lock_guard<recursive_mutex> lock(m_mutex); | |
lock_guard<recursive_mutex> lock2(m_cacheMutex); // WARNING: Moved outside switch | |
m_probeIsActive[i] = false; | |
switch(m_modelid) | |
{ | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS1000: | |
replyType = Trim(converse("C%d:COUPLING?", i + 1)); | |
if(replyType == "A50") | |
return OscilloscopeChannel::COUPLE_AC_50; | |
else if(replyType == "D50") | |
return OscilloscopeChannel::COUPLE_DC_50; | |
else if(replyType == "A1M") | |
return OscilloscopeChannel::COUPLE_AC_1M; | |
else if(replyType == "D1M") | |
return OscilloscopeChannel::COUPLE_DC_1M; | |
else if(replyType == "GND") | |
return OscilloscopeChannel::COUPLE_GND; | |
break; | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS2000XP: | |
case MODEL_SIGLENT_SDS5000X: | |
replyType = Trim(converse(":CHANNEL%d:COUPLING?", i + 1).substr(0, 2)); | |
replyImp = Trim(converse(":CHANNEL%d:IMPEDANCE?", i + 1).substr(0, 3)); | |
if(replyType == "AC") | |
return (replyImp == "FIFT") ? OscilloscopeChannel::COUPLE_AC_50 : OscilloscopeChannel::COUPLE_AC_1M; | |
else if(replyType == "DC") | |
return (replyImp == "FIFT") ? OscilloscopeChannel::COUPLE_DC_50 : OscilloscopeChannel::COUPLE_DC_1M; | |
else if(replyType == "GN") | |
return OscilloscopeChannel::COUPLE_GND; | |
break; | |
// -------------------------------------------------- | |
default: | |
LogError("Unknown scope type\n"); | |
break; | |
// -------------------------------------------------- | |
} | |
//invalid | |
LogWarning("SiglentSCPIOscilloscope::GetChannelCoupling got invalid coupling [%s] [%s]\n", | |
replyType.c_str(), | |
replyImp.c_str()); | |
return OscilloscopeChannel::COUPLE_SYNTHETIC; | |
} | |
void SiglentSCPIOscilloscope::SetChannelCoupling(size_t i, OscilloscopeChannel::CouplingType type) | |
{ | |
if(i >= m_analogChannelCount) | |
return; | |
//Get the old coupling value first. | |
//This ensures that m_probeIsActive[i] is valid | |
GetChannelCoupling(i); | |
//If we have an active probe, don't touch the hardware config | |
if(m_probeIsActive[i]) | |
return; | |
lock_guard<recursive_mutex> lock(m_mutex); | |
switch(type) | |
{ | |
case OscilloscopeChannel::COUPLE_AC_1M: | |
sendOnly(":CHANNEL%d:COUPLING AC", i + 1); | |
sendOnly(":CHANNEL%d:IMPEDANCE ONEMEG", i + 1); | |
break; | |
case OscilloscopeChannel::COUPLE_DC_1M: | |
sendOnly(":CHANNEL%d:COUPLING DC", i + 1); | |
sendOnly(":CHANNEL%d:IMPEDANCE ONEMEG", i + 1); | |
break; | |
case OscilloscopeChannel::COUPLE_DC_50: | |
sendOnly(":CHANNEL%d:COUPLING DC", i + 1); | |
sendOnly(":CHANNEL%d:IMPEDANCE FIFTY", i + 1); | |
break; | |
case OscilloscopeChannel::COUPLE_AC_50: | |
sendOnly(":CHANNEL%d:COUPLING AC", i + 1); | |
sendOnly(":CHANNEL%d:IMPEDANCE FIFTY", i + 1); | |
break; | |
//treat unrecognized as ground | |
case OscilloscopeChannel::COUPLE_GND: | |
default: | |
sendOnly(":CHANNEL%d:COUPLING GND", i + 1); | |
break; | |
} | |
} | |
double SiglentSCPIOscilloscope::GetChannelAttenuation(size_t i) | |
{ | |
if(i > m_analogChannelCount) | |
return 1; | |
//TODO: support ext/10 | |
if(i == m_extTrigChannel->GetIndex()) | |
return 1; | |
lock_guard<recursive_mutex> lock(m_mutex); | |
string reply; | |
switch(m_modelid) | |
{ | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS1000: | |
reply = converse("C%d:ATTENUATION?", i + 1); | |
break; | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS2000XP: | |
case MODEL_SIGLENT_SDS5000X: | |
reply = converse(":CHANNEL%d:PROBE?", i + 1); | |
break; | |
// -------------------------------------------------- | |
default: | |
LogError("Unknown scope type\n"); | |
break; | |
// -------------------------------------------------- | |
} | |
double d; | |
sscanf(reply.c_str(), "%lf", &d); | |
return d; | |
} | |
void SiglentSCPIOscilloscope::SetChannelAttenuation(size_t i, double atten) | |
{ | |
if(i >= m_analogChannelCount) | |
return; | |
//Get the old coupling value first. | |
//This ensures that m_probeIsActive[i] is valid | |
GetChannelCoupling(i); | |
//Don't allow changing attenuation on active probes | |
{ | |
lock_guard<recursive_mutex> lock(m_cacheMutex); | |
if(m_probeIsActive[i]) | |
return; | |
} | |
lock_guard<recursive_mutex> lock(m_mutex); | |
switch(m_modelid) | |
{ | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS1000: | |
// Values larger than 1x should be sent as integers, and values smaller | |
// should be sent as floating point numbers with one decimal. | |
if(atten >= 1) | |
{ | |
sendOnly("C%d:ATTENUATION %d", i + 1, (int)atten); | |
} | |
else | |
{ | |
sendOnly("C%d:ATTENUATION %.1lf", i + 1, atten); | |
} | |
break; | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS2000XP: | |
case MODEL_SIGLENT_SDS5000X: | |
sendOnly(":CHANNEL%d:PROBE %lf", i + 1, atten); | |
break; | |
// -------------------------------------------------- | |
default: | |
LogError("Unknown scope type\n"); | |
break; | |
// -------------------------------------------------- | |
} | |
} | |
vector<unsigned int> SiglentSCPIOscilloscope::GetChannelBandwidthLimiters(size_t /*i*/) | |
{ | |
vector<unsigned int> ret; | |
switch(m_modelid) | |
{ | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS1000: | |
//"no limit" | |
ret.push_back(0); | |
//Supported by all models | |
ret.push_back(20); | |
break; | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS2000XP: | |
case MODEL_SIGLENT_SDS5000X: | |
//"no limit" | |
ret.push_back(0); | |
//Supported by all models | |
ret.push_back(20); | |
if(m_maxBandwidth > 200) | |
ret.push_back(200); | |
break; | |
// -------------------------------------------------- | |
default: | |
LogError("Unknown scope type\n"); | |
break; | |
// -------------------------------------------------- | |
} | |
return ret; | |
} | |
int SiglentSCPIOscilloscope::GetChannelBandwidthLimit(size_t i) | |
{ | |
if(i > m_analogChannelCount) | |
return 0; | |
lock_guard<recursive_mutex> lock(m_mutex); | |
string reply; | |
switch(m_modelid) | |
{ | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS1000: | |
reply = converse("C%d:BANDWIDTH_LIMIT?", i + 1); | |
if(reply == "OFF") | |
return 0; | |
else if(reply == "ON") | |
return 20; | |
break; | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS2000XP: | |
case MODEL_SIGLENT_SDS5000X: | |
reply = converse(":CHANNEL%d:BWLIMIT?", i + 1); | |
if(reply == "FULL") | |
return 0; | |
else if(reply == "20M") | |
return 20; | |
else if(reply == "200M") | |
return 200; | |
break; | |
// -------------------------------------------------- | |
default: | |
LogError("Unknown scope type\n"); | |
break; | |
// -------------------------------------------------- | |
} | |
LogWarning("SiglentSCPIOscilloscope::GetChannelCoupling got invalid bwlimit %s\n", reply.c_str()); | |
return 0; | |
} | |
void SiglentSCPIOscilloscope::SetChannelBandwidthLimit(size_t i, unsigned int limit_mhz) | |
{ | |
lock_guard<recursive_mutex> lock(m_mutex); | |
switch(m_modelid) | |
{ | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS1000: | |
switch(limit_mhz) | |
{ | |
case 0: | |
sendOnly("BANDWIDTH_LIMIT C%d,OFF", i + 1); | |
break; | |
case 20: | |
sendOnly("BANDWIDTH_LIMIT C%d,ON", i + 1); | |
break; | |
default: | |
LogWarning("SiglentSCPIOscilloscope::invalid bwlimit set request (%dMhz)\n", limit_mhz); | |
} | |
break; | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS2000XP: | |
case MODEL_SIGLENT_SDS5000X: | |
switch(limit_mhz) | |
{ | |
case 0: | |
sendOnly(":CHANNEL%d:BWLIMIT FULL", i + 1); | |
break; | |
case 20: | |
sendOnly(":CHANNEL%d:BWLIMIT 20M", i + 1); | |
break; | |
case 200: | |
sendOnly(":CHANNEL%d:BWLIMIT 200M", i + 1); | |
break; | |
default: | |
LogWarning("SiglentSCPIOscilloscope::invalid bwlimit set request (%dMhz)\n", limit_mhz); | |
} | |
break; | |
// -------------------------------------------------- | |
default: | |
LogError("Unknown scope type\n"); | |
break; | |
// -------------------------------------------------- | |
} | |
} | |
bool SiglentSCPIOscilloscope::CanInvert(size_t i) | |
{ | |
//All analog channels, and only analog channels, can be inverted | |
return (i < m_analogChannelCount); | |
} | |
void SiglentSCPIOscilloscope::Invert(size_t i, bool invert) | |
{ | |
if(i >= m_analogChannelCount) | |
return; | |
lock_guard<recursive_mutex> lock(m_mutex); | |
switch(m_modelid) | |
{ | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS1000: | |
sendOnly("C%d:INVERTSET %s", i + 1, invert ? "ON" : "OFF"); | |
break; | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS2000XP: | |
case MODEL_SIGLENT_SDS5000X: | |
sendOnly(":CHANNEL%d:INVERT %s", i + 1, invert ? "ON" : "OFF"); | |
break; | |
// -------------------------------------------------- | |
default: | |
LogError("Unknown scope type\n"); | |
break; | |
// -------------------------------------------------- | |
} | |
} | |
bool SiglentSCPIOscilloscope::IsInverted(size_t i) | |
{ | |
if(i >= m_analogChannelCount) | |
return false; | |
lock_guard<recursive_mutex> lock(m_mutex); | |
string reply; | |
switch(m_modelid) | |
{ | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS1000: | |
reply = Trim(converse("C%d:INVERTSET?", i + 1)); | |
break; | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS2000XP: | |
case MODEL_SIGLENT_SDS5000X: | |
reply = Trim(converse(":CHANNEL%d:INVERT?", i + 1)); | |
break; | |
// -------------------------------------------------- | |
default: | |
LogError("Unknown scope type\n"); | |
reply = ""; | |
break; | |
// -------------------------------------------------- | |
} | |
return (reply == "ON"); | |
} | |
void SiglentSCPIOscilloscope::SetChannelDisplayName(size_t i, string name) | |
{ | |
auto chan = m_channels[i]; | |
//External trigger cannot be renamed in hardware. | |
//TODO: allow clientside renaming? | |
if(chan == m_extTrigChannel) | |
return; | |
//Update cache | |
{ | |
lock_guard<recursive_mutex> lock(m_cacheMutex); | |
m_channelDisplayNames[m_channels[i]] = name; | |
} | |
//Update in hardware | |
lock_guard<recursive_mutex> lock(m_mutex); | |
if(i < m_analogChannelCount) | |
{ | |
sendOnly(":CHANNEL%ld:LABEL:TEXT \"%s\"", i + 1, name.c_str()); | |
sendOnly(":CHANNEL%ld:LABEL ON", i + 1); | |
} | |
else | |
{ | |
sendOnly(":DIGITAL:LABEL%ld \"%s\"", i - (m_analogChannelCount + 1), name.c_str()); | |
} | |
} | |
string SiglentSCPIOscilloscope::GetChannelDisplayName(size_t i) | |
{ | |
auto chan = m_channels[i]; | |
//External trigger cannot be renamed in hardware. | |
//TODO: allow clientside renaming? | |
if(chan == m_extTrigChannel) | |
return m_extTrigChannel->GetHwname(); | |
//Check cache first | |
{ | |
lock_guard<recursive_mutex> lock(m_cacheMutex); | |
if(m_channelDisplayNames.find(chan) != m_channelDisplayNames.end()) | |
return m_channelDisplayNames[chan]; | |
} | |
lock_guard<recursive_mutex> lock(m_mutex); | |
//Analog and digital channels use completely different namespaces, as usual. | |
//Because clean, orthogonal APIs are apparently for losers? | |
string name = ""; | |
switch(m_modelid) | |
{ | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS1000: | |
break; | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS2000XP: | |
case MODEL_SIGLENT_SDS5000X: | |
if(i < m_analogChannelCount) | |
{ | |
name = converse(":CHANNEL%d:LABEL:TEXT?", i + 1); | |
// Remove "'s around the name | |
if(name.length() > 2) | |
name = name.substr(1, name.length() - 2); | |
} | |
else | |
{ | |
name = converse(":DIGITAL:LABEL%d?", i - (m_analogChannelCount + 1)); | |
// Remove "'s around the name | |
if(name.length() > 2) | |
name = name.substr(1, name.length() - 2); | |
} | |
break; | |
// -------------------------------------------------- | |
default: | |
LogError("Unknown scope type\n"); | |
break; | |
// -------------------------------------------------- | |
} | |
//Default to using hwname if no alias defined | |
if(name == "") | |
name = chan->GetHwname(); | |
lock_guard<recursive_mutex> lock2(m_cacheMutex); | |
m_channelDisplayNames[chan] = name; | |
return name; | |
} | |
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
// Triggering | |
bool SiglentSCPIOscilloscope::IsTriggerArmed() | |
{ | |
return m_triggerArmed; | |
} | |
Oscilloscope::TriggerMode SiglentSCPIOscilloscope::PollTrigger() | |
{ | |
//Read the Internal State Change Register | |
string sinr = ""; | |
lock_guard<recursive_mutex> lock(m_mutex); | |
if(m_triggerForced) | |
{ | |
// The force trigger completed, return the sample set | |
m_triggerForced = false; | |
m_triggerArmed = false; | |
return TRIGGER_MODE_TRIGGERED; | |
} | |
switch(m_modelid) | |
{ | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS1000: | |
sinr = converse("SAMPLE_STATUS?"); | |
break; | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS2000XP: | |
case MODEL_SIGLENT_SDS5000X: | |
sinr = converse(":TRIGGER:STATUS?"); | |
break; | |
// -------------------------------------------------- | |
default: | |
LogError("Unknown scope type\n"); | |
break; | |
// -------------------------------------------------- | |
} | |
//No waveform, but ready for one? | |
if((sinr == "Arm") || (sinr == "Ready")) | |
{ | |
m_triggerArmed = true; | |
return TRIGGER_MODE_RUN; | |
} | |
//Stopped, no data available | |
if(sinr == "Stop") | |
{ | |
if(m_triggerArmed) | |
{ | |
m_triggerArmed = false; | |
return TRIGGER_MODE_TRIGGERED; | |
} | |
else | |
return TRIGGER_MODE_STOP; | |
} | |
return TRIGGER_MODE_RUN; | |
} | |
int SiglentSCPIOscilloscope::ReadWaveformBlock(uint32_t maxsize, char* data) | |
{ | |
char packetSizeSequence[17]; | |
uint32_t getLength; | |
// Get size of this sequence | |
m_transport->ReadRawData(7, (unsigned char*)packetSizeSequence); | |
// This is an aweful cludge, but the response can be in different formats depending on | |
// if this was a direct trigger or a forced trigger. This is the report format for a direct trigger | |
if((!strncmp(packetSizeSequence, "DESC,#9", 7)) || (!strncmp(packetSizeSequence, "DAT2,#9", 7))) | |
{ | |
m_transport->ReadRawData(9, (unsigned char*)packetSizeSequence); | |
} | |
// This is the report format for a forced trigger | |
if(!strncmp(&packetSizeSequence[2], ":WF D", 5)) | |
{ | |
// Read the front end junk, then the actually number we're looking for | |
m_transport->ReadRawData(6, (unsigned char*)packetSizeSequence); | |
m_transport->ReadRawData(9, (unsigned char*)packetSizeSequence); | |
} | |
packetSizeSequence[9] = 0; | |
LogTrace("INITIAL PACKET [%s]\n", packetSizeSequence); | |
getLength = atoi(packetSizeSequence); | |
// Now get the data | |
m_transport->ReadRawData((getLength > maxsize) ? maxsize : getLength, (unsigned char*)data); | |
return getLength; | |
} | |
/** | |
@brief Optimized function for checking channel enable status en masse with less round trips to the scope | |
*/ | |
void SiglentSCPIOscilloscope::BulkCheckChannelEnableState() | |
{ | |
lock_guard<recursive_mutex> lock(m_cacheMutex); | |
//Check enable state in the cache. | |
vector<int> uncached; | |
for(unsigned int i = 0; i < m_analogChannelCount; i++) | |
{ | |
if(m_channelsEnabled.find(i) == m_channelsEnabled.end()) | |
uncached.push_back(i); | |
} | |
lock_guard<recursive_mutex> lock2(m_mutex); | |
for(auto i : uncached) | |
{ | |
string reply = converse(":CHANNEL%d:SWITCH?", i + 1); | |
if(reply == "OFF") | |
m_channelsEnabled[i] = false; | |
else if(reply == "ON") | |
m_channelsEnabled[i] = true; | |
else | |
LogWarning("BulkCheckChannelEnableState: Unrecognised reply [%s]\n", reply.c_str()); | |
} | |
//Check digital status | |
for(unsigned int i = 0; i < m_digitalChannelCount; i++) | |
{ | |
string reply = converse(":DIGITAL:D%d?", i); | |
if(reply == "ON") | |
{ | |
m_channelsEnabled[m_digitalChannels[i]->GetIndex()] = true; | |
} | |
else if(reply == "OFF") | |
{ | |
m_channelsEnabled[m_digitalChannels[i]->GetIndex()] = false; | |
} | |
else | |
LogWarning("BulkCheckChannelEnableState: Unrecognised reply [%s]\n", reply.c_str()); | |
} | |
} | |
bool SiglentSCPIOscilloscope::ReadWavedescs( | |
char wavedescs[MAX_ANALOG][WAVEDESC_SIZE], bool* enabled, unsigned int& firstEnabledChannel, bool& any_enabled) | |
{ | |
BulkCheckChannelEnableState(); | |
for(unsigned int i = 0; i < m_analogChannelCount; i++) | |
{ | |
enabled[i] = IsChannelEnabled(i); | |
any_enabled |= enabled[i]; | |
} | |
for(unsigned int i = 0; i < m_analogChannelCount; i++) | |
{ | |
if(enabled[i] || (!any_enabled && i == 0)) | |
{ | |
if(firstEnabledChannel == UINT_MAX) | |
firstEnabledChannel = i; | |
m_transport->SendCommand(":WAVEFORM:SOURCE C" + to_string(i + 1) + ";:WAVEFORM:PREAMBLE?"); | |
if(WAVEDESC_SIZE != ReadWaveformBlock(WAVEDESC_SIZE, wavedescs[i])) | |
LogError("ReadWaveformBlock for wavedesc %u failed\n", i); | |
// I have no idea why this is needed, but it certainly is | |
m_transport->ReadReply(); | |
} | |
} | |
return true; | |
} | |
time_t SiglentSCPIOscilloscope::ExtractTimestamp(unsigned char* wavedesc, double& basetime) | |
{ | |
/* | |
TIMESTAMP is shown as Reserved In Siglent data format. | |
This information is from LeCroy which uses the same wavedesc header. | |
Timestamp is a somewhat complex format that needs some shuffling around. | |
Timestamp starts at offset 296 bytes in the wavedesc | |
(296-303) double seconds | |
(304) byte minutes | |
(305) byte hours | |
(306) byte days | |
(307) byte months | |
(308-309) uint16 year | |
TODO: during startup, query instrument for its current time zone | |
since the wavedesc reports instment local time | |
*/ | |
//Yes, this cast is intentional. | |
//It assumes you're on a little endian system using IEEE754 64-bit float, but that applies to everything we support. | |
//cppcheck-suppress invalidPointerCast | |
double fseconds = *reinterpret_cast<const double*>(wavedesc + 296); | |
uint8_t seconds = floor(fseconds); | |
basetime = fseconds - seconds; | |
time_t tnow = time(NULL); | |
struct tm tstruc; | |
#ifdef _WIN32 | |
localtime_s(&tstruc, &tnow); | |
#else | |
localtime_r(&tnow, &tstruc); | |
#endif | |
//Convert the instrument time to a string, then back to a tm | |
//Is there a better way to do this??? | |
//Naively poking "struct tm" fields gives incorrect results (scopehal-apps:#52) | |
//Maybe because tm_yday is inconsistent? | |
char tblock[64] = {0}; | |
snprintf(tblock, | |
sizeof(tblock), | |
"%d-%d-%d %d:%02d:%02d", | |
*reinterpret_cast<uint16_t*>(wavedesc + 308), | |
wavedesc[307], | |
wavedesc[306], | |
wavedesc[305], | |
wavedesc[304], | |
seconds); | |
locale cur_locale; | |
auto& tget = use_facet<time_get<char>>(cur_locale); | |
istringstream stream(tblock); | |
ios::iostate state; | |
char format[] = "%F %T"; | |
tget.get(stream, time_get<char>::iter_type(), stream, state, &tstruc, format, format + strlen(format)); | |
return mktime(&tstruc); | |
} | |
vector<WaveformBase*> SiglentSCPIOscilloscope::ProcessAnalogWaveform(const char* data, | |
size_t datalen, | |
char* wavedesc, | |
uint32_t num_sequences, | |
time_t ttime, | |
double basetime, | |
double* wavetime, | |
int /* ch */) | |
{ | |
vector<WaveformBase*> ret; | |
//Parse the wavedesc headers | |
auto pdesc = wavedesc; | |
//cppcheck-suppress invalidPointerCast | |
float v_gain = *reinterpret_cast<float*>(pdesc + 156); | |
//cppcheck-suppress invalidPointerCast | |
float v_off = *reinterpret_cast<float*>(pdesc + 160); | |
//cppcheck-suppress invalidPointerCast | |
float v_probefactor = *reinterpret_cast<float*>(pdesc + 328); | |
//cppcheck-suppress invalidPointerCast | |
float interval = *reinterpret_cast<float*>(pdesc + 176) * FS_PER_SECOND; | |
//cppcheck-suppress invalidPointerCast | |
double h_off = *reinterpret_cast<double*>(pdesc + 180) * FS_PER_SECOND; //fs from start of waveform to trigger | |
//double h_off_frac = fmodf(h_off, interval); //fractional sample position, in fs | |
double h_off_frac = 0; //((interval*datalen)/2)+h_off; | |
if(h_off_frac < 0) | |
h_off_frac = h_off; //interval + h_off_frac; //double h_unit = *reinterpret_cast<double*>(pdesc + 244); | |
//Raw waveform data | |
size_t num_samples; | |
if(m_highDefinition) | |
num_samples = datalen / 2; | |
else | |
num_samples = datalen; | |
size_t num_per_segment = num_samples / num_sequences; | |
int16_t* wdata = (int16_t*)&data[0]; | |
int8_t* bdata = (int8_t*)&data[0]; | |
// SDS2000X+ and SDS5000X have 30 codes per div. Todo; SDS6000X has 425. | |
// We also need to accomodate probe attenuation here. | |
v_gain = v_gain * v_probefactor / 30; | |
// Vertical offset is also scaled by the probefactor | |
v_off = v_off * v_probefactor; | |
// Update channel voltages and offsets based on what is in this wavedesc | |
// m_channelVoltageRanges[ch] = v_gain * v_probefactor * 30 * 8; | |
// m_channelOffsets[ch] = v_off; | |
// m_triggerOffset = ((interval * datalen) / 2) + h_off; | |
// m_triggerOffsetValid = true; | |
LogTrace("\nV_Gain=%f, V_Off=%f, interval=%f, h_off=%f, h_off_frac=%f, datalen=%ld\n", | |
v_gain, | |
v_off, | |
interval, | |
h_off, | |
h_off_frac, | |
datalen); | |
for(size_t j = 0; j < num_sequences; j++) | |
{ | |
//Set up the capture we're going to store our data into | |
AnalogWaveform* cap = new AnalogWaveform; | |
cap->m_timescale = round(interval); | |
cap->m_triggerPhase = h_off_frac; | |
cap->m_startTimestamp = ttime; | |
cap->m_densePacked = true; | |
//Parse the time | |
if(num_sequences > 1) | |
cap->m_startFemtoseconds = static_cast<int64_t>((basetime + wavetime[j * 2]) * FS_PER_SECOND); | |
else | |
cap->m_startFemtoseconds = static_cast<int64_t>(basetime * FS_PER_SECOND); | |
cap->Resize(num_per_segment); | |
//Convert raw ADC samples to volts | |
if(m_highDefinition) | |
{ | |
Convert16BitSamples((int64_t*)&cap->m_offsets[0], | |
(int64_t*)&cap->m_durations[0], | |
(float*)&cap->m_samples[0], | |
wdata + j * num_per_segment, | |
v_gain, | |
v_off, | |
num_per_segment, | |
0); | |
} | |
else | |
{ | |
Convert8BitSamples((int64_t*)&cap->m_offsets[0], | |
(int64_t*)&cap->m_durations[0], | |
(float*)&cap->m_samples[0], | |
bdata + j * num_per_segment, | |
v_gain, | |
v_off, | |
num_per_segment, | |
0); | |
} | |
ret.push_back(cap); | |
} | |
return ret; | |
} | |
map<int, DigitalWaveform*> SiglentSCPIOscilloscope::ProcessDigitalWaveform(string& data) | |
{ | |
map<int, DigitalWaveform*> ret; | |
// Digital channels not yet implemented | |
return ret; | |
//See what channels are enabled | |
string tmp = data.substr(data.find("SelectedLines=") + 14); | |
tmp = tmp.substr(0, 16); | |
bool enabledChannels[16]; | |
for(int i = 0; i < 16; i++) | |
enabledChannels[i] = (tmp[i] == '1'); | |
//Quick and dirty string searching. We only care about a small fraction of the XML | |
//so no sense bringing in a full parser. | |
tmp = data.substr(data.find("<HorPerStep>") + 12); | |
tmp = tmp.substr(0, tmp.find("</HorPerStep>")); | |
float interval = atof(tmp.c_str()) * FS_PER_SECOND; | |
//LogDebug("Sample interval: %.2f fs\n", interval); | |
tmp = data.substr(data.find("<NumSamples>") + 12); | |
tmp = tmp.substr(0, tmp.find("</NumSamples>")); | |
size_t num_samples = atoi(tmp.c_str()); | |
//LogDebug("Expecting %d samples\n", num_samples); | |
//Extract the raw trigger timestamp (nanoseconds since Jan 1 2000) | |
tmp = data.substr(data.find("<FirstEventTime>") + 16); | |
tmp = tmp.substr(0, tmp.find("</FirstEventTime>")); | |
int64_t timestamp; | |
if(1 != sscanf(tmp.c_str(), "%ld", ×tamp)) | |
return ret; | |
//Get the client's local time. | |
//All we need from this is to know whether DST is active | |
tm now; | |
time_t tnow; | |
time(&tnow); | |
localtime_r(&tnow, &now); | |
//Convert Jan 1 2000 in the client's local time zone (assuming this is the same as instrument time) to Unix time. | |
//Note that the instrument time zone conversion seems to be broken and not handle DST offsets right. | |
//Move the epoch by an hour if we're currently in DST to compensate. | |
tm epoch; | |
epoch.tm_sec = 0; | |
epoch.tm_min = 0; | |
epoch.tm_hour = 0; | |
epoch.tm_mday = 1; | |
epoch.tm_mon = 0; | |
epoch.tm_year = 100; | |
epoch.tm_wday = 6; //Jan 1 2000 was a Saturday | |
epoch.tm_yday = 0; | |
epoch.tm_isdst = now.tm_isdst; | |
time_t epoch_stamp = mktime(&epoch); | |
//Pull out nanoseconds from the timestamp and convert to femtoseconds since that's the scopehal fine time unit | |
const int64_t ns_per_sec = 1000000000; | |
int64_t start_ns = timestamp % ns_per_sec; | |
int64_t start_fs = 1000000 * start_ns; | |
int64_t start_sec = (timestamp - start_ns) / ns_per_sec; | |
time_t start_time = epoch_stamp + start_sec; | |
//Pull out the actual binary data (Base64 coded) | |
tmp = data.substr(data.find("<BinaryData>") + 12); | |
tmp = tmp.substr(0, tmp.find("</BinaryData>")); | |
//Decode the base64 | |
base64_decodestate bstate; | |
base64_init_decodestate(&bstate); | |
unsigned char* block = new unsigned char[tmp.length()]; //base64 is smaller than plaintext, leave room | |
base64_decode_block(tmp.c_str(), tmp.length(), (char*)block, &bstate); | |
//We have each channel's data from start to finish before the next (no interleaving). | |
//TODO: Multithread across waveforms | |
unsigned int icapchan = 0; | |
for(unsigned int i = 0; i < m_digitalChannelCount; i++) | |
{ | |
if(enabledChannels[i]) | |
{ | |
DigitalWaveform* cap = new DigitalWaveform; | |
cap->m_timescale = interval; | |
cap->m_densePacked = true; | |
//Capture timestamp | |
cap->m_startTimestamp = start_time; | |
cap->m_startFemtoseconds = start_fs; | |
//Preallocate memory assuming no deduplication possible | |
cap->Resize(num_samples); | |
//Save the first sample (can't merge with sample -1 because that doesn't exist) | |
size_t base = icapchan * num_samples; | |
size_t k = 0; | |
cap->m_offsets[0] = 0; | |
cap->m_durations[0] = 1; | |
cap->m_samples[0] = block[base]; | |
//Read and de-duplicate the other samples | |
//TODO: can we vectorize this somehow? | |
bool last = block[base]; | |
for(size_t j = 1; j < num_samples; j++) | |
{ | |
bool sample = block[base + j]; | |
//Deduplicate consecutive samples with same value | |
//FIXME: temporary workaround for rendering bugs | |
//if(last == sample) | |
if((last == sample) && ((j + 3) < num_samples)) | |
cap->m_durations[k]++; | |
//Nope, it toggled - store the new value | |
else | |
{ | |
k++; | |
cap->m_offsets[k] = j; | |
cap->m_durations[k] = 1; | |
cap->m_samples[k] = sample; | |
last = sample; | |
} | |
} | |
//Done, shrink any unused space | |
cap->Resize(k); | |
cap->m_offsets.shrink_to_fit(); | |
cap->m_durations.shrink_to_fit(); | |
cap->m_samples.shrink_to_fit(); | |
//See how much space we saved | |
/* | |
LogDebug("%s: %zu samples deduplicated to %zu (%.1f %%)\n", | |
m_digitalChannels[i]->GetDisplayName().c_str(), | |
num_samples, | |
k, | |
(k * 100.0f) / num_samples); | |
*/ | |
//Done, save data and go on to next | |
ret[m_digitalChannels[i]->GetIndex()] = cap; | |
icapchan++; | |
} | |
//No data here for us! | |
else | |
ret[m_digitalChannels[i]->GetIndex()] = NULL; | |
} | |
delete[] block; | |
return ret; | |
} | |
bool SiglentSCPIOscilloscope::AcquireData() | |
{ | |
//State for this acquisition (may be more than one waveform) | |
uint32_t num_sequences = 1; | |
map<int, vector<WaveformBase*>> pending_waveforms; | |
double start = GetTime(); | |
time_t ttime = 0; | |
double basetime = 0; | |
double h_off_frac = 0; | |
vector<vector<WaveformBase*>> waveforms; | |
unsigned char* pdesc = NULL; | |
bool denabled = false; | |
string wavetime; | |
bool enabled[8] = {false}; | |
double* pwtime = NULL; | |
char tmp[128]; | |
//Acquire the data (but don't parse it) | |
lock_guard<recursive_mutex> lock(m_mutex); | |
start = GetTime(); | |
//Get the wavedescs for all channels | |
unsigned int firstEnabledChannel = UINT_MAX; | |
bool any_enabled = true; | |
switch(m_modelid) | |
{ | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS1000: | |
m_sampleRateValid = false; | |
GetSampleRate(); | |
for(unsigned int i = 0; i < m_analogChannelCount; i++) | |
{ | |
if(m_channelsEnabled[i]) | |
{ | |
m_transport->SendCommand("C" + to_string(i + 1) + ":WAVEFORM? DAT2"); | |
// length of data is current memory depth | |
m_analogWaveformDataSize[i] = ReadWaveformBlock(WAVEFORM_SIZE, m_analogWaveformData[i]); | |
// This is the 0x0a0a at the end | |
m_transport->ReadRawData(2, (unsigned char*)tmp); | |
} | |
} | |
//At this point all data has been read so the scope is free to go do | |
//its thing while we crunch the results. Re-arm the trigger if not | |
//in one-shot mode | |
if(!m_triggerOneShot) | |
{ | |
sendOnly("TRIG_MODE SINGLE"); | |
m_triggerArmed = true; | |
} | |
//Process analog waveforms | |
waveforms.resize(m_analogChannelCount); | |
for(unsigned int i = 0; i < m_analogChannelCount; i++) | |
{ | |
std::vector<WaveformBase*> ret; | |
if(m_channelsEnabled[i]) | |
{ | |
AnalogWaveform* cap = new AnalogWaveform; | |
cap->m_timescale = FS_PER_SECOND / m_sampleRate; | |
cap->m_triggerPhase = h_off_frac; | |
cap->m_startTimestamp = ttime; | |
cap->m_densePacked = true; | |
cap->m_startFemtoseconds = static_cast<int64_t>(basetime * FS_PER_SECOND); | |
cap->Resize(m_analogWaveformDataSize[i]); | |
Convert8BitSamples((int64_t*)&cap->m_offsets[0], | |
(int64_t*)&cap->m_durations[0], | |
(float*)&cap->m_samples[0], | |
(int8_t*)m_analogWaveformData[i], | |
m_channelVoltageRanges[i] / (8 * 25), | |
m_channelOffsets[i], | |
m_analogWaveformDataSize[i], | |
0); | |
ret.push_back(cap); | |
} | |
waveforms[i] = ret; | |
} | |
//Save analog waveform data | |
for(unsigned int i = 0; i < m_analogChannelCount; i++) | |
{ | |
if(!m_channelsEnabled[i]) | |
continue; | |
//Done, update the data | |
for(size_t j = 0; j < num_sequences; j++) | |
pending_waveforms[i].push_back(waveforms[i][j]); | |
} | |
break; | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS2000XP: | |
case MODEL_SIGLENT_SDS5000X: | |
if(!ReadWavedescs(m_wavedescs, enabled, firstEnabledChannel, any_enabled)) | |
return false; | |
//Grab the WAVEDESC from the first enabled channel | |
for(unsigned int i = 0; i < m_analogChannelCount; i++) | |
{ | |
if(enabled[i] || (!any_enabled && i == 0)) | |
{ | |
pdesc = (unsigned char*)(&m_wavedescs[i][0]); | |
break; | |
} | |
} | |
//See if any digital channels are enabled | |
if(m_digitalChannelCount > 0) | |
{ | |
m_cacheMutex.lock(); | |
for(size_t i = 0; i < m_digitalChannels.size(); i++) | |
{ | |
if(m_channelsEnabled[m_digitalChannels[i]->GetIndex()]) | |
{ | |
denabled = true; | |
break; | |
} | |
} | |
m_cacheMutex.unlock(); | |
} | |
//Pull sequence count out of the WAVEDESC if we have analog channels active | |
if(pdesc) | |
{ | |
uint32_t trigtime_len = *reinterpret_cast<uint32_t*>(pdesc + 48); | |
if(trigtime_len > 0) | |
num_sequences = trigtime_len / 16; | |
} | |
//No WAVEDESCs, look at digital channels | |
else | |
{ | |
//TODO: support sequence capture of digital channels if the instrument supports this | |
//(need to look into it) | |
if(denabled) | |
num_sequences = 1; | |
//no enabled channels. abort | |
else | |
return false; | |
} | |
if(pdesc) | |
{ | |
// THIS SECTION IS UNTESTED | |
//Figure out when the first trigger happened. | |
//Read the timestamps if we're doing segmented capture | |
ttime = ExtractTimestamp(pdesc, basetime); | |
if(num_sequences > 1) | |
wavetime = m_transport->ReadReply(); | |
pwtime = reinterpret_cast<double*>(&wavetime[16]); //skip 16-byte SCPI header | |
//Read the data from each analog waveform | |
for(unsigned int i = 0; i < m_analogChannelCount; i++) | |
{ | |
m_transport->SendCommand(":WAVEFORM:SOURCE C" + to_string(i + 1) + ";:WAVEFORM:DATA?"); | |
if(enabled[i]) | |
{ | |
m_analogWaveformDataSize[i] = ReadWaveformBlock(WAVEFORM_SIZE, m_analogWaveformData[i]); | |
// This is the 0x0a0a at the end | |
m_transport->ReadRawData(2, (unsigned char*)tmp); | |
} | |
} | |
} | |
//Read the data from the digital waveforms, if enabled | |
if(denabled) | |
{ | |
if(!ReadWaveformBlock(WAVEFORM_SIZE, m_digitalWaveformDataBytes)) | |
{ | |
LogDebug("failed to download digital waveform\n"); | |
return false; | |
} | |
} | |
//At this point all data has been read so the scope is free to go do its thing while we crunch the results. | |
//Re-arm the trigger if not in one-shot mode | |
if(!m_triggerOneShot) | |
{ | |
// lock_guard<recursive_mutex> lock(m_mutex); | |
sendOnly(":TRIGGER:MODE SINGLE"); | |
m_triggerArmed = true; | |
} | |
//Process analog waveforms | |
waveforms.resize(m_analogChannelCount); | |
for(unsigned int i = 0; i < m_analogChannelCount; i++) | |
{ | |
if(enabled[i]) | |
{ | |
waveforms[i] = ProcessAnalogWaveform(&m_analogWaveformData[i][0], | |
m_analogWaveformDataSize[i], | |
&m_wavedescs[i][0], | |
num_sequences, | |
ttime, | |
basetime, | |
pwtime, | |
i); | |
} | |
} | |
//Save analog waveform data | |
for(unsigned int i = 0; i < m_analogChannelCount; i++) | |
{ | |
if(!enabled[i]) | |
continue; | |
//Done, update the data | |
for(size_t j = 0; j < num_sequences; j++) | |
pending_waveforms[i].push_back(waveforms[i][j]); | |
} | |
break; | |
// -------------------------------------------------- | |
default: | |
LogError("Unknown scope type\n"); | |
break; | |
// -------------------------------------------------- | |
} | |
//TODO: proper support for sequenced capture when digital channels are active | |
// if(denabled) | |
// { | |
// //This is a weird XML-y format but I can't find any other way to get it :( | |
// map<int, DigitalWaveform*> digwaves = ProcessDigitalWaveform(m_digitalWaveformData); | |
// //Done, update the data | |
// for(auto it : digwaves) | |
// pending_waveforms[it.first].push_back(it.second); | |
// } | |
//Now that we have all of the pending waveforms, save them in sets across all channels | |
m_pendingWaveformsMutex.lock(); | |
for(size_t i = 0; i < num_sequences; i++) | |
{ | |
SequenceSet s; | |
for(size_t j = 0; j < m_channels.size(); j++) | |
{ | |
if(pending_waveforms.find(j) != pending_waveforms.end()) | |
s[m_channels[j]] = pending_waveforms[j][i]; | |
} | |
m_pendingWaveforms.push_back(s); | |
} | |
m_pendingWaveformsMutex.unlock(); | |
double dt = GetTime() - start; | |
LogTrace("Waveform download and processing took %.3f ms\n", dt * 1000); | |
return true; | |
} | |
void SiglentSCPIOscilloscope::Start() | |
{ | |
lock_guard<recursive_mutex> lock(m_mutex); | |
switch(m_modelid) | |
{ | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS1000: | |
//sendOnly("START"); | |
//sendOnly("MEMORY_SIZE 7K"); | |
sendOnly("STOP"); | |
sendOnly("TRIG_MODE SINGLE"); | |
break; | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS2000XP: | |
case MODEL_SIGLENT_SDS5000X: | |
sendOnly(":TRIGGER:MODE STOP"); | |
sendOnly(":TRIGGER:MODE SINGLE"); //always do single captures, just re-trigger | |
break; | |
// -------------------------------------------------- | |
default: | |
LogError("Unknown scope type\n"); | |
break; | |
// -------------------------------------------------- | |
} | |
m_triggerArmed = true; | |
m_triggerOneShot = false; | |
} | |
void SiglentSCPIOscilloscope::StartSingleTrigger() | |
{ | |
lock_guard<recursive_mutex> lock(m_mutex); | |
//LogDebug("Start single trigger\n"); | |
switch(m_modelid) | |
{ | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS1000: | |
sendOnly("STOP"); | |
sendOnly("TRIG_MODE SINGLE"); | |
break; | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS2000XP: | |
case MODEL_SIGLENT_SDS5000X: | |
sendOnly(":TRIGGER:MODE STOP"); | |
sendOnly(":TRIGGER:MODE SINGLE"); | |
break; | |
// -------------------------------------------------- | |
default: | |
LogError("Unknown scope type\n"); | |
break; | |
// -------------------------------------------------- | |
} | |
m_triggerArmed = true; | |
m_triggerOneShot = true; | |
} | |
void SiglentSCPIOscilloscope::Stop() | |
{ | |
lock_guard<recursive_mutex> lock(m_mutex); | |
switch(m_modelid) | |
{ | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS1000: | |
sendOnly("STOP"); | |
break; | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS2000XP: | |
case MODEL_SIGLENT_SDS5000X: | |
sendOnly(":TRIGGER:MODE STOP"); | |
break; | |
// -------------------------------------------------- | |
default: | |
LogError("Unknown scope type\n"); | |
break; | |
// -------------------------------------------------- | |
} | |
m_triggerArmed = false; | |
m_triggerOneShot = true; | |
//Clear out any pending data (the user doesn't want it, and we don't want stale stuff hanging around) | |
ClearPendingWaveforms(); | |
} | |
void SiglentSCPIOscilloscope::ForceTrigger() | |
{ | |
lock_guard<recursive_mutex> lock(m_mutex); | |
// Don't allow more than one force at a time | |
if(m_triggerForced) | |
return; | |
m_triggerForced = true; | |
switch(m_modelid) | |
{ | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS1000: | |
sendOnly("TRIG_MODE SINGLE"); | |
if(!m_triggerArmed) | |
sendOnly("TRIG_MODE SINGLE"); | |
break; | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS2000XP: | |
case MODEL_SIGLENT_SDS5000X: | |
sendOnly(":TRIGGER:MODE SINGLE"); | |
if(!m_triggerArmed) | |
sendOnly(":TRIGGER:MODE SINGLE"); | |
break; | |
// -------------------------------------------------- | |
default: | |
LogError("Unknown scope type\n"); | |
break; | |
// -------------------------------------------------- | |
} | |
m_triggerArmed = true; | |
this_thread::sleep_for(c_trigger_delay); | |
} | |
float SiglentSCPIOscilloscope::GetChannelOffset(size_t i, size_t /*stream*/) | |
{ | |
//not meaningful for trigger or digital channels | |
if(i > m_analogChannelCount) | |
return 0; | |
{ | |
lock_guard<recursive_mutex> lock(m_cacheMutex); | |
if(m_channelOffsets.find(i) != m_channelOffsets.end()) | |
return m_channelOffsets[i]; | |
} | |
lock_guard<recursive_mutex> lock2(m_mutex); | |
string reply; | |
switch(m_modelid) | |
{ | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS1000: | |
reply = converse("C%ld:OFST?", i + 1); | |
break; | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS2000XP: | |
case MODEL_SIGLENT_SDS5000X: | |
reply = converse(":CHANNEL%ld:OFFSET?", i + 1); | |
break; | |
// -------------------------------------------------- | |
default: | |
LogError("Unknown scope type\n"); | |
break; | |
// -------------------------------------------------- | |
} | |
float offset; | |
sscanf(reply.c_str(), "%f", &offset); | |
lock_guard<recursive_mutex> lock(m_cacheMutex); | |
m_channelOffsets[i] = offset; | |
return offset; | |
} | |
void SiglentSCPIOscilloscope::SetChannelOffset(size_t i, size_t /*stream*/, float offset) | |
{ | |
//not meaningful for trigger or digital channels | |
if(i > m_analogChannelCount) | |
return; | |
{ | |
lock_guard<recursive_mutex> lock2(m_mutex); | |
sendOnly(":CHANNEL%ld:OFFSET %1.2E", i + 1, offset); | |
} | |
lock_guard<recursive_mutex> lock(m_cacheMutex); | |
m_channelOffsets[i] = offset; | |
} | |
float SiglentSCPIOscilloscope::GetChannelVoltageRange(size_t i, size_t /*stream*/) | |
{ | |
//not meaningful for trigger or digital channels | |
if(i > m_analogChannelCount) | |
return 1; | |
{ | |
lock_guard<recursive_mutex> lock(m_cacheMutex); | |
if(m_channelVoltageRanges.find(i) != m_channelVoltageRanges.end()) | |
return m_channelVoltageRanges[i]; | |
} | |
lock_guard<recursive_mutex> lock2(m_mutex); | |
string reply; | |
switch(m_modelid) | |
{ | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS1000: | |
reply = converse("C%d:VOLT_DIV?", i + 1); | |
break; | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS2000XP: | |
case MODEL_SIGLENT_SDS5000X: | |
reply = converse(":CHANNEL%d:SCALE?", i + 1); | |
break; | |
// -------------------------------------------------- | |
default: | |
LogError("Unknown scope type\n"); | |
break; | |
// -------------------------------------------------- | |
} | |
float volts_per_div; | |
sscanf(reply.c_str(), "%f", &volts_per_div); | |
float v = volts_per_div * 8; //plot is 8 divisions high | |
lock_guard<recursive_mutex> lock(m_cacheMutex); | |
m_channelVoltageRanges[i] = v; | |
return v; | |
} | |
void SiglentSCPIOscilloscope::SetChannelVoltageRange(size_t i, size_t /*stream*/, float range) | |
{ | |
lock_guard<recursive_mutex> lock(m_mutex); | |
float vdiv = range / 8; | |
m_channelVoltageRanges[i] = range; | |
switch(m_modelid) | |
{ | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS1000: | |
sendOnly("C%ld:VOLT_DIV %.4f", i + 1, vdiv); | |
break; | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS2000XP: | |
case MODEL_SIGLENT_SDS5000X: | |
sendOnly(":CHANNEL%ld:SCALE %.4f", i + 1, vdiv); | |
break; | |
// -------------------------------------------------- | |
default: | |
LogError("Unknown scope type\n"); | |
break; | |
// -------------------------------------------------- | |
} | |
} | |
vector<uint64_t> SiglentSCPIOscilloscope::GetSampleRatesNonInterleaved() | |
{ | |
vector<uint64_t> ret; | |
switch(m_modelid) | |
{ | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS1000: | |
break; | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS2000XP: | |
case MODEL_SIGLENT_SDS5000X: | |
ret = {10 * 1000, | |
20 * 1000, | |
50 * 1000, | |
100 * 1000, | |
200 * 1000, | |
500 * 1000, | |
1 * 1000 * 1000, | |
2 * 1000 * 1000, | |
5 * 1000 * 1000, | |
10 * 1000 * 1000, | |
20 * 1000 * 1000, | |
50 * 1000 * 1000, | |
100 * 1000 * 1000, | |
200 * 1000 * 1000, | |
500 * 1000 * 1000, | |
1 * 1000 * 1000 * 1000, | |
2 * 1000 * 1000 * 1000}; | |
break; | |
// -------------------------------------------------- | |
default: | |
LogError("Unknown scope type\n"); | |
break; | |
// -------------------------------------------------- | |
} | |
return ret; | |
} | |
vector<uint64_t> SiglentSCPIOscilloscope::GetSampleRatesInterleaved() | |
{ | |
vector<uint64_t> ret = {}; | |
GetSampleRatesNonInterleaved(); | |
return ret; | |
} | |
vector<uint64_t> SiglentSCPIOscilloscope::GetSampleDepthsNonInterleaved() | |
{ | |
vector<uint64_t> ret = {}; | |
switch(m_modelid) | |
{ | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS1000: | |
ret = {7 * 1000, 70 * 1000, 700 * 1000, 7 * 1000 * 1000}; | |
break; | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS2000XP: | |
case MODEL_SIGLENT_SDS5000X: | |
break; | |
// -------------------------------------------------- | |
default: | |
LogError("Unknown scope type\n"); | |
break; | |
// -------------------------------------------------- | |
} | |
return ret; | |
} | |
vector<uint64_t> SiglentSCPIOscilloscope::GetSampleDepthsInterleaved() | |
{ | |
vector<uint64_t> ret = {}; | |
switch(m_modelid) | |
{ | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS1000: | |
ret = {14 * 1000, 140 * 1000, 1400 * 1000, 14 * 1000 * 1000}; | |
break; | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS2000XP: | |
case MODEL_SIGLENT_SDS5000X: | |
break; | |
// -------------------------------------------------- | |
default: | |
LogError("Unknown scope type\n"); | |
break; | |
// -------------------------------------------------- | |
} | |
return ret; | |
} | |
set<SiglentSCPIOscilloscope::InterleaveConflict> SiglentSCPIOscilloscope::GetInterleaveConflicts() | |
{ | |
set<InterleaveConflict> ret; | |
//All scopes normally interleave channels 1/2 and 3/4. | |
//If both channels in either pair is in use, that's a problem. | |
ret.emplace(InterleaveConflict(m_channels[0], m_channels[1])); | |
if(m_analogChannelCount > 2) | |
ret.emplace(InterleaveConflict(m_channels[2], m_channels[3])); | |
return ret; | |
} | |
uint64_t SiglentSCPIOscilloscope::GetTimeDiv() | |
{ | |
double f; | |
//if(!m_sampleRateValid) | |
{ | |
lock_guard<recursive_mutex> lock(m_mutex); | |
string reply; | |
switch(m_modelid) | |
{ | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS1000: | |
reply = converse("TIME_DIV?"); | |
break; | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS2000XP: | |
case MODEL_SIGLENT_SDS5000X: | |
LogError("No code for GetTimeDiv\n"); | |
break; | |
// -------------------------------------------------- | |
default: | |
LogError("Unknown scope type\n"); | |
break; | |
// -------------------------------------------------- | |
} | |
sscanf(reply.c_str(), "%lf", &f); | |
m_timeDiv = static_cast<int64_t>(f); | |
//m_sampleRateValid = true; | |
} | |
return m_timeDiv; | |
} | |
uint64_t SiglentSCPIOscilloscope::GetSampleRate() | |
{ | |
double f; | |
if(!m_sampleRateValid) | |
{ | |
lock_guard<recursive_mutex> lock(m_mutex); | |
string reply; | |
switch(m_modelid) | |
{ | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS1000: | |
reply = converse("SAMPLE_RATE?"); | |
break; | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS2000XP: | |
case MODEL_SIGLENT_SDS5000X: | |
reply = converse(":ACQUIRE:SRATE?"); | |
break; | |
// -------------------------------------------------- | |
default: | |
LogError("Unknown scope type\n"); | |
break; | |
// -------------------------------------------------- | |
} | |
sscanf(reply.c_str(), "%lf", &f); | |
m_sampleRate = static_cast<int64_t>(f); | |
m_sampleRateValid = true; | |
} | |
return m_sampleRate; | |
} | |
uint64_t SiglentSCPIOscilloscope::GetSampleDepth() | |
{ | |
double f; | |
if(!m_memoryDepthValid) | |
{ | |
//:AQUIRE:MDEPTH can sometimes return incorrect values! It returns the *cap* on memory depth, | |
// not the *actual* memory depth....we don't know that until we've collected samples | |
// What you see below is the only observed method that seems to reliably get the *actual* memory depth. | |
lock_guard<recursive_mutex> lock(m_mutex); | |
string reply; | |
switch(m_modelid) | |
{ | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS1000: | |
reply = converse("MEMORY_SIZE?"); | |
break; | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS2000XP: | |
case MODEL_SIGLENT_SDS5000X: | |
reply = converse(":ACQUIRE:MDEPTH?"); | |
break; | |
// -------------------------------------------------- | |
default: | |
LogError("Unknown scope type\n"); | |
break; | |
// -------------------------------------------------- | |
} | |
f = Unit(Unit::UNIT_SAMPLEDEPTH).ParseString(reply); | |
m_memoryDepth = static_cast<int64_t>(f); | |
m_memoryDepthValid = true; | |
} | |
return m_memoryDepth; | |
} | |
void SiglentSCPIOscilloscope::SetSampleDepth(uint64_t depth) | |
{ | |
lock_guard<recursive_mutex> lock(m_mutex); | |
switch(m_modelid) | |
{ | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS1000: | |
switch(depth) | |
{ | |
case 7000: | |
sendOnly("MEMORY_SIZE 7K"); | |
break; | |
case 14000: | |
sendOnly("MEMORY_SIZE 14K"); | |
break; | |
case 70000: | |
sendOnly("MEMORY_SIZE 70K"); | |
break; | |
case 140000: | |
sendOnly("MEMORY_SIZE 140K"); | |
break; | |
case 700000: | |
sendOnly("MEMORY_SIZE 700K"); | |
break; | |
case 1400000: | |
sendOnly("MEMORY_SIZE 1.4M"); | |
break; | |
case 7000000: | |
sendOnly("MEMORY_SIZE 7M"); | |
break; | |
case 14000000: | |
sendOnly("MEMORY_SIZE 14M"); | |
break; | |
default: | |
LogError("Invalid memory depth for channel: %lu\n", depth); | |
} | |
m_sampleRateValid = false; | |
break; | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS2000XP: | |
case MODEL_SIGLENT_SDS5000X: | |
switch(depth) | |
{ | |
case 10000: | |
sendOnly("ACQUIRE:MDEPTH 10k"); | |
break; | |
case 20000: | |
sendOnly("ACQUIRE:MDEPTH 20k"); | |
break; | |
case 100000: | |
sendOnly("ACQUIRE:MDEPTH 100k"); | |
break; | |
case 200000: | |
sendOnly("ACQUIRE:MDEPTH 200k"); | |
break; | |
case 1000000: | |
sendOnly("ACQUIRE:MDEPTH 1M"); | |
break; | |
case 2000000: | |
sendOnly("ACQUIRE:MDEPTH 2M"); | |
break; | |
case 10000000: | |
sendOnly("ACQUIRE:MDEPTH 10M"); | |
break; | |
// We don't yet support memory depths that need to be transferred in chunks | |
case 20000000: | |
//sendOnly("ACQUIRE:MDEPTH 20M"); | |
// break; | |
case 50000000: | |
// sendOnly("ACQUIRE:MDEPTH 50M"); | |
// break; | |
case 100000000: | |
// sendOnly("ACQUIRE:MDEPTH 100M"); | |
// break; | |
case 200000000: | |
// sendOnly("ACQUIRE:MDEPTH 200M"); | |
// break; | |
default: | |
LogError("Invalid memory depth for channel: %lu\n", depth); | |
} | |
break; | |
// -------------------------------------------------- | |
default: | |
LogError("Unknown scope type\n"); | |
break; | |
// -------------------------------------------------- | |
} | |
m_memoryDepthValid = false; | |
} | |
void SiglentSCPIOscilloscope::SetSampleRate(uint64_t rate) | |
{ | |
lock_guard<recursive_mutex> lock(m_mutex); | |
m_sampleRate = rate; | |
m_sampleRateValid = false; | |
m_memoryDepthValid = false; | |
double sampletime = GetSampleDepth() / (double)rate; | |
switch(m_modelid) | |
{ | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS1000: | |
break; | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS2000XP: | |
case MODEL_SIGLENT_SDS5000X: | |
sendOnly(":TIMEBASE:SCALE %1.2E", sampletime / 10); | |
break; | |
// -------------------------------------------------- | |
default: | |
LogError("Unknown scope type\n"); | |
break; | |
// -------------------------------------------------- | |
} | |
m_memoryDepthValid = false; | |
} | |
void SiglentSCPIOscilloscope::EnableTriggerOutput() | |
{ | |
LogWarning("EnableTriggerOutput not implemented\n"); | |
} | |
void SiglentSCPIOscilloscope::SetUseExternalRefclk(bool /*external*/) | |
{ | |
LogWarning("SetUseExternalRefclk not implemented\n"); | |
} | |
void SiglentSCPIOscilloscope::SetTriggerOffset(int64_t offset) | |
{ | |
lock_guard<recursive_mutex> lock(m_mutex); | |
//Siglents standard has the offset being from the midpoint of the capture. | |
//Scopehal has offset from the start. | |
int64_t rate = GetSampleRate(); | |
int64_t halfdepth = GetSampleDepth() / 2; | |
int64_t halfwidth = static_cast<int64_t>(round(FS_PER_SECOND * halfdepth / rate)); | |
switch(m_modelid) | |
{ | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS1000: | |
sendOnly("TRIG_DELAY %1.2E", (halfwidth - offset) * SECONDS_PER_FS); | |
break; | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS2000XP: | |
case MODEL_SIGLENT_SDS5000X: | |
sendOnly(":TIMEBASE:DELAY %1.2E", (halfwidth - offset) * SECONDS_PER_FS); | |
break; | |
// -------------------------------------------------- | |
default: | |
LogError("Unknown scope type\n"); | |
break; | |
// -------------------------------------------------- | |
} | |
//Don't update the cache because the scope is likely to round the offset we ask for. | |
//If we query the instrument later, the cache will be updated then. | |
lock_guard<recursive_mutex> lock2(m_cacheMutex); | |
m_triggerOffsetValid = false; | |
} | |
int64_t SiglentSCPIOscilloscope::GetTriggerOffset() | |
{ | |
//Early out if the value is in cache | |
{ | |
lock_guard<recursive_mutex> lock(m_cacheMutex); | |
if(m_triggerOffsetValid) | |
return m_triggerOffset; | |
} | |
string reply; | |
{ | |
lock_guard<recursive_mutex> lock(m_mutex); | |
switch(m_modelid) | |
{ | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS1000: | |
reply = converse("TRIG_DELAY?"); | |
break; | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS2000XP: | |
case MODEL_SIGLENT_SDS5000X: | |
reply = converse(":TIMEBASE:DELAY?"); | |
break; | |
// -------------------------------------------------- | |
default: | |
LogError("Unknown scope type\n"); | |
break; | |
// -------------------------------------------------- | |
} | |
} | |
lock_guard<recursive_mutex> lock(m_cacheMutex); | |
//Result comes back in scientific notation | |
double sec; | |
sscanf(reply.c_str(), "%le", &sec); | |
m_triggerOffset = static_cast<int64_t>(round(sec * FS_PER_SECOND)); | |
//Convert from midpoint to start point | |
int64_t rate = GetSampleRate(); | |
int64_t halfdepth = GetSampleDepth() / 2; | |
int64_t halfwidth = static_cast<int64_t>(round(FS_PER_SECOND * halfdepth / rate)); | |
m_triggerOffset = halfwidth - m_triggerOffset; | |
m_triggerOffsetValid = true; | |
return m_triggerOffset; | |
} | |
void SiglentSCPIOscilloscope::SetDeskewForChannel(size_t channel, int64_t skew) | |
{ | |
//Cannot deskew digital/trigger channels | |
if(channel >= m_analogChannelCount) | |
return; | |
lock_guard<recursive_mutex> lock(m_mutex); | |
switch(m_modelid) | |
{ | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS1000: | |
sendOnly("C%ld:SKEW %1.2E", channel + 1, skew * SECONDS_PER_FS); | |
break; | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS2000XP: | |
case MODEL_SIGLENT_SDS5000X: | |
sendOnly(":CHANNEL%ld:SKEW %1.2E", channel, skew * SECONDS_PER_FS); | |
break; | |
// -------------------------------------------------- | |
default: | |
LogError("Unknown scope type\n"); | |
break; | |
// -------------------------------------------------- | |
} | |
//Update cache | |
lock_guard<recursive_mutex> lock2(m_cacheMutex); | |
m_channelDeskew[channel] = skew; | |
} | |
int64_t SiglentSCPIOscilloscope::GetDeskewForChannel(size_t channel) | |
{ | |
//Cannot deskew digital/trigger channels | |
if(channel >= m_analogChannelCount) | |
return 0; | |
//Early out if the value is in cache | |
{ | |
lock_guard<recursive_mutex> lock(m_cacheMutex); | |
if(m_channelDeskew.find(channel) != m_channelDeskew.end()) | |
return m_channelDeskew[channel]; | |
} | |
//Read the deskew | |
lock_guard<recursive_mutex> lock(m_mutex); | |
string reply; | |
switch(m_modelid) | |
{ | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS1000: | |
reply = converse("C%ld:SKEW?", channel + 1); | |
break; | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS2000XP: | |
case MODEL_SIGLENT_SDS5000X: | |
reply = converse(":CHANNEL%ld:SKEW?", channel + 1); | |
break; | |
// -------------------------------------------------- | |
default: | |
LogError("Unknown scope type\n"); | |
break; | |
// -------------------------------------------------- | |
} | |
//Value comes back as floating point ps | |
float skew; | |
sscanf(reply.c_str(), "%f", &skew); | |
int64_t skew_ps = round(skew * FS_PER_SECOND); | |
lock_guard<recursive_mutex> lock2(m_cacheMutex); | |
m_channelDeskew[channel] = skew_ps; | |
return skew_ps; | |
} | |
bool SiglentSCPIOscilloscope::IsInterleaving() | |
{ | |
switch(m_modelid) | |
{ | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS1000: | |
// <size>:={7K,70K,700K,7M} for non-interleaved mode. | |
// Non-interleaved means a single channel is active per A/D | |
// converter. Most oscilloscopes feature two channels per A/D converter. | |
// <size>:={14K,140K,1.4M,14M}for interleave mode. | |
// Interleave mode means multiple active channels per A/Dconverter. | |
if((m_channelsEnabled[0] == true) && (m_channelsEnabled[1] == true)) | |
{ | |
// Channel 1 and 2 | |
return false; | |
} | |
else if((m_channelsEnabled[3] == true) && (m_channelsEnabled[4] == true)) | |
{ | |
// Channel 3 and 4 | |
return false; | |
} | |
return true; | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS2000XP: | |
case MODEL_SIGLENT_SDS5000X: | |
LogWarning("IsInterleaving is not implemented\n"); | |
return false; | |
// -------------------------------------------------- | |
default: | |
LogError("Unknown scope type\n"); | |
return false; | |
// -------------------------------------------------- | |
} | |
} | |
bool SiglentSCPIOscilloscope::SetInterleaving(bool /* combine*/) | |
{ | |
LogWarning("SetInterleaving is not implemented\n"); | |
return false; | |
} | |
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
// Analog bank configuration | |
bool SiglentSCPIOscilloscope::IsADCModeConfigurable() | |
{ | |
return false; | |
} | |
vector<string> SiglentSCPIOscilloscope::GetADCModeNames(size_t /*channel*/) | |
{ | |
vector<string> v; | |
LogWarning("GetADCModeNames is not implemented\n"); | |
return v; | |
} | |
size_t SiglentSCPIOscilloscope::GetADCMode(size_t /*channel*/) | |
{ | |
return 0; | |
} | |
void SiglentSCPIOscilloscope::SetADCMode(size_t /*channel*/, size_t /*mode*/) | |
{ | |
return; | |
} | |
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
// Logic analyzer configuration | |
vector<Oscilloscope::DigitalBank> SiglentSCPIOscilloscope::GetDigitalBanks() | |
{ | |
vector<DigitalBank> banks; | |
if(m_hasLA) | |
{ | |
for(size_t n = 0; n < 2; n++) | |
{ | |
DigitalBank bank; | |
for(size_t i = 0; i < 8; i++) | |
bank.push_back(m_digitalChannels[i + n * 8]); | |
banks.push_back(bank); | |
} | |
} | |
return banks; | |
} | |
Oscilloscope::DigitalBank SiglentSCPIOscilloscope::GetDigitalBank(size_t channel) | |
{ | |
DigitalBank ret; | |
if(m_hasLA) | |
{ | |
if(channel <= m_digitalChannels[7]->GetIndex()) | |
{ | |
for(size_t i = 0; i < 8; i++) | |
ret.push_back(m_digitalChannels[i]); | |
} | |
else | |
{ | |
for(size_t i = 0; i < 8; i++) | |
ret.push_back(m_digitalChannels[i + 8]); | |
} | |
} | |
return ret; | |
} | |
bool SiglentSCPIOscilloscope::IsDigitalHysteresisConfigurable() | |
{ | |
return false; | |
} | |
bool SiglentSCPIOscilloscope::IsDigitalThresholdConfigurable() | |
{ | |
return true; | |
} | |
float SiglentSCPIOscilloscope::GetDigitalHysteresis(size_t /*channel*/) | |
{ | |
LogWarning("GetDigitalHysteresis is not implemented\n"); | |
return 0; | |
} | |
float SiglentSCPIOscilloscope::GetDigitalThreshold(size_t channel) | |
{ | |
lock_guard<recursive_mutex> lock(m_mutex); | |
channel -= m_analogChannelCount + 1; | |
string r = converse(":DIGITAL:THRESHOLD%d?", (channel / 8) + 1).c_str(); | |
// Look through the threshold table to see if theres a string match, return it if so | |
uint32_t i = 0; | |
while((c_sds2000xp_threshold_table[i].name) && | |
(strncmp(c_sds2000xp_threshold_table[i].name, r.c_str(), strlen(c_sds2000xp_threshold_table[i].name)))) | |
i++; | |
if(c_sds2000xp_threshold_table[i].name) | |
return c_sds2000xp_threshold_table[i].val; | |
// Didn't match a standard, check for custom | |
if(!strncmp(r.c_str(), c_custom_thresh, strlen(c_custom_thresh))) | |
return strtof(&(r.c_str()[strlen(c_custom_thresh)]), NULL); | |
LogWarning("GetDigitalThreshold unrecognised value [%s]\n", r.c_str()); | |
return 0.0f; | |
} | |
void SiglentSCPIOscilloscope::SetDigitalHysteresis(size_t /*channel*/, float /*level*/) | |
{ | |
LogWarning("SetDigitalHysteresis is not implemented\n"); | |
} | |
void SiglentSCPIOscilloscope::SetDigitalThreshold(size_t channel, float level) | |
{ | |
lock_guard<recursive_mutex> lock(m_mutex); | |
channel -= m_analogChannelCount + 1; | |
// Search through standard thresholds to see if one matches | |
uint32_t i = 0; | |
while(( | |
(c_sds2000xp_threshold_table[i].name) && (fabsf(level - c_sds2000xp_threshold_table[i].val)) > c_thresh_thresh)) | |
i++; | |
if(c_sds2000xp_threshold_table[i].name) | |
sendOnly(":DIGITAL:THRESHOLD%d %s", (channel / 8) + 1, (c_sds2000xp_threshold_table[i].name)); | |
else | |
{ | |
do | |
{ | |
sendOnly(":DIGITAL:THRESHOLD%d CUSTOM,%1.2E", (channel / 8) + 1, level); | |
} while(fabsf((GetDigitalThreshold(channel + m_analogChannelCount + 1) - level)) > 0.1f); | |
} | |
} | |
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
// Triggering | |
void SiglentSCPIOscilloscope::PullTrigger() | |
{ | |
lock_guard<recursive_mutex> lock(m_mutex); | |
std::string reply; | |
switch(m_modelid) | |
{ | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS1000: | |
reply = Trim(converse("TRIG_SELECT?")); | |
// <trig_type>,SR,<source>,HT,<hold_type>,HV,<hold_value1>[,HV2,<hold_value2>] | |
//EDGE,SR,C1,HT,OFF | |
{ | |
vector<string> result; | |
stringstream s_stream(reply); //create string stream from the string | |
while(s_stream.good()) | |
{ | |
string substr; | |
getline(s_stream, substr, ','); //get first string delimited by comma | |
result.push_back(substr); | |
} | |
if(result[0] == "EDGE") | |
{ | |
PullEdgeTrigger(); | |
} | |
else | |
{ | |
LogWarning("Unknown trigger type \"%s\"\n", reply.c_str()); | |
m_trigger = NULL; | |
return; | |
} | |
auto chan = GetChannelByHwName(result[2]); | |
m_trigger->SetInput(0, StreamDescriptor(chan, 0), true); | |
} | |
break; | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS2000XP: | |
case MODEL_SIGLENT_SDS5000X: | |
//Figure out what kind of trigger is active. | |
reply = Trim(converse(":TRIGGER:TYPE?")); | |
if(reply == "DROPout") | |
PullDropoutTrigger(); | |
else if(reply == "EDGE") | |
PullEdgeTrigger(); | |
else if(reply == "RUNT") | |
PullRuntTrigger(); | |
else if(reply == "SLOPe") | |
PullSlewRateTrigger(); | |
else if(reply == "UART") | |
PullUartTrigger(); | |
else if(reply == "INTerval") | |
PullPulseWidthTrigger(); | |
else if(reply == "WINDow") | |
PullWindowTrigger(); | |
// Note that PULSe, PATTern, QUALified, VIDeo, IIC, SPI, LIN, CAN, FLEXray, CANFd & IIS are not yet handled | |
//Unrecognized trigger type | |
else | |
{ | |
LogWarning("Unknown trigger type \"%s\"\n", reply.c_str()); | |
m_trigger = NULL; | |
return; | |
} | |
//Pull the source (same for all types of trigger) | |
PullTriggerSource(m_trigger, reply); | |
//TODO: holdoff | |
break; | |
// -------------------------------------------------- | |
default: | |
LogError("Unknown scope type\n"); | |
break; | |
// -------------------------------------------------- | |
} | |
} | |
/** | |
@brief Reads the source of a trigger from the instrument | |
*/ | |
void SiglentSCPIOscilloscope::PullTriggerSource(Trigger* trig, string triggerModeName) | |
{ | |
string reply = Trim(converse(":TRIGGER:%s:SOURCE?", triggerModeName.c_str())); | |
auto chan = GetChannelByHwName(reply); | |
trig->SetInput(0, StreamDescriptor(chan, 0), true); | |
if(!chan) | |
LogWarning("Unknown trigger source \"%s\"\n", reply.c_str()); | |
} | |
/** | |
@brief Reads settings for a dropout trigger from the instrument | |
*/ | |
void SiglentSCPIOscilloscope::PullDropoutTrigger() | |
{ | |
//Clear out any triggers of the wrong type | |
if((m_trigger != NULL) && (dynamic_cast<DropoutTrigger*>(m_trigger) != NULL)) | |
{ | |
delete m_trigger; | |
m_trigger = NULL; | |
} | |
//Create a new trigger if necessary | |
if(m_trigger == NULL) | |
m_trigger = new DropoutTrigger(this); | |
DropoutTrigger* dt = dynamic_cast<DropoutTrigger*>(m_trigger); | |
//Level | |
dt->SetLevel(stof(converse(":TRIGGER:DROPOUT:LEVEL?"))); | |
//Dropout time | |
Unit fs(Unit::UNIT_FS); | |
dt->SetDropoutTime(fs.ParseString(converse(":TRIGGER:DROPOUT:TIME?"))); | |
//Edge type | |
if(Trim(converse(":TRIGGER:DROPOUT:SLOPE?")) == "RISING") | |
dt->SetType(DropoutTrigger::EDGE_RISING); | |
else | |
dt->SetType(DropoutTrigger::EDGE_FALLING); | |
//Reset type | |
if(Trim(converse(":TRIGGER:DROPOUT:TYPE?")) == "EDGE") | |
dt->SetResetType(DropoutTrigger::RESET_OPPOSITE); | |
else | |
dt->SetResetType(DropoutTrigger::RESET_NONE); | |
} | |
/** | |
@brief Reads settings for an edge trigger from the instrument | |
*/ | |
void SiglentSCPIOscilloscope::PullEdgeTrigger() | |
{ | |
//Clear out any triggers of the wrong type | |
if((m_trigger != NULL) && (dynamic_cast<EdgeTrigger*>(m_trigger) != NULL)) | |
{ | |
delete m_trigger; | |
m_trigger = NULL; | |
} | |
//Create a new trigger if necessary | |
if(m_trigger == NULL) | |
m_trigger = new EdgeTrigger(this); | |
EdgeTrigger* et = dynamic_cast<EdgeTrigger*>(m_trigger); | |
switch(m_modelid) | |
{ | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS1000: | |
// Level | |
{ | |
string level = converse("C1:TRIG_LEVEL?"); | |
et->SetLevel(stof(level)); | |
// Slope | |
GetTriggerSlope(et, Trim(converse("C1:TRIG_SLOPE?"))); | |
// <trig_source>:TRIG_SLOPE <trig_slope> | |
} | |
break; | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS2000XP: | |
case MODEL_SIGLENT_SDS5000X: | |
//Level | |
et->SetLevel(stof(converse(":TRIGGER:EDGE:LEVEL?"))); | |
//TODO: OptimizeForHF (changes hysteresis for fast signals) | |
//Slope | |
GetTriggerSlope(et, Trim(converse(":TRIGGER:EDGE:SLOPE?"))); | |
break; | |
// -------------------------------------------------- | |
default: | |
LogError("Unknown scope type\n"); | |
break; | |
// -------------------------------------------------- | |
} | |
} | |
/** | |
@brief Reads settings for an edge trigger from the instrument | |
*/ | |
void SiglentSCPIOscilloscope::PullPulseWidthTrigger() | |
{ | |
//Clear out any triggers of the wrong type | |
if((m_trigger != NULL) && (dynamic_cast<PulseWidthTrigger*>(m_trigger) != NULL)) | |
{ | |
delete m_trigger; | |
m_trigger = NULL; | |
} | |
//Create a new trigger if necessary | |
if(m_trigger == NULL) | |
m_trigger = new PulseWidthTrigger(this); | |
auto pt = dynamic_cast<PulseWidthTrigger*>(m_trigger); | |
//Level | |
pt->SetLevel(stof(converse(":TRIGGER:INTERVAL:LEVEL?"))); | |
//Condition | |
pt->SetCondition(GetCondition(converse(":TRIGGER:INTERVAL:LIMIT?"))); | |
//Min range | |
Unit fs(Unit::UNIT_FS); | |
pt->SetLowerBound(fs.ParseString(converse(":TRIGGER:INTERVAL:TLOWER?"))); | |
//Max range | |
pt->SetUpperBound(fs.ParseString(converse(":TRIGGER:INTERVAL:TUPPER?"))); | |
//Slope | |
GetTriggerSlope(pt, Trim(converse(":TRIGGER:INTERVAL:SLOPE?"))); | |
} | |
/** | |
@brief Reads settings for a runt-pulse trigger from the instrument | |
*/ | |
void SiglentSCPIOscilloscope::PullRuntTrigger() | |
{ | |
//Clear out any triggers of the wrong type | |
if((m_trigger != NULL) && (dynamic_cast<RuntTrigger*>(m_trigger) != NULL)) | |
{ | |
delete m_trigger; | |
m_trigger = NULL; | |
} | |
//Create a new trigger if necessary | |
if(m_trigger == NULL) | |
m_trigger = new RuntTrigger(this); | |
RuntTrigger* rt = dynamic_cast<RuntTrigger*>(m_trigger); | |
//Lower bound | |
Unit v(Unit::UNIT_VOLTS); | |
rt->SetLowerBound(v.ParseString(converse(":TRIGGER:RUNT:LLEVEL?"))); | |
//Upper bound | |
rt->SetUpperBound(v.ParseString(converse(":TRIGGER:RUNT:HLEVEL?"))); | |
//Lower interval | |
Unit fs(Unit::UNIT_FS); | |
rt->SetLowerInterval(fs.ParseString(converse(":TRIGGER:RUNT:TLOWER?"))); | |
//Upper interval | |
rt->SetUpperInterval(fs.ParseString(converse(":TRIGGER:RUNT:TUPPER?"))); | |
//Slope | |
auto reply = Trim(converse(":TRIGGER:RUNT:POLARITY?")); | |
if(reply == "POSitive") | |
rt->SetSlope(RuntTrigger::EDGE_RISING); | |
else if(reply == "NEGative") | |
rt->SetSlope(RuntTrigger::EDGE_FALLING); | |
//Condition | |
rt->SetCondition(GetCondition(converse(":TRIGGER:RUNT:LIMIT?"))); | |
} | |
/** | |
@brief Reads settings for a slew rate trigger from the instrument | |
*/ | |
void SiglentSCPIOscilloscope::PullSlewRateTrigger() | |
{ | |
//Clear out any triggers of the wrong type | |
if((m_trigger != NULL) && (dynamic_cast<SlewRateTrigger*>(m_trigger) != NULL)) | |
{ | |
delete m_trigger; | |
m_trigger = NULL; | |
} | |
//Create a new trigger if necessary | |
if(m_trigger == NULL) | |
m_trigger = new SlewRateTrigger(this); | |
SlewRateTrigger* st = dynamic_cast<SlewRateTrigger*>(m_trigger); | |
//Lower bound | |
Unit v(Unit::UNIT_VOLTS); | |
st->SetLowerBound(v.ParseString(converse(":TRIGGER:SLOPE:LLEVEL?"))); | |
//Upper bound | |
st->SetUpperBound(v.ParseString(converse(":TRIGGER:SLOPE:HLEVEL?"))); | |
//Lower interval | |
Unit fs(Unit::UNIT_FS); | |
st->SetLowerInterval(fs.ParseString(converse(":TRIGGER:SLOPE:TLOWER?"))); | |
//Upper interval | |
st->SetUpperInterval(fs.ParseString(converse(":TRIGGER:SLOPE:TUPPER?"))); | |
//Slope | |
auto reply = Trim(converse("TRIGGER:SLOPE:SLOPE?")); | |
if(reply == "RISing") | |
st->SetSlope(SlewRateTrigger::EDGE_RISING); | |
else if(reply == "FALLing") | |
st->SetSlope(SlewRateTrigger::EDGE_FALLING); | |
else if(reply == "ALTernate") | |
st->SetSlope(SlewRateTrigger::EDGE_ANY); | |
//Condition | |
st->SetCondition(GetCondition(converse("TRIGGER:SLOPE:LIMIT?"))); | |
} | |
/** | |
@brief Reads settings for a UART trigger from the instrument | |
*/ | |
void SiglentSCPIOscilloscope::PullUartTrigger() | |
{ | |
//Clear out any triggers of the wrong type | |
if((m_trigger != NULL) && (dynamic_cast<UartTrigger*>(m_trigger) != NULL)) | |
{ | |
delete m_trigger; | |
m_trigger = NULL; | |
} | |
//Create a new trigger if necessary | |
if(m_trigger == NULL) | |
m_trigger = new UartTrigger(this); | |
UartTrigger* ut = dynamic_cast<UartTrigger*>(m_trigger); | |
//Bit rate | |
ut->SetBitRate(stoi(converse(":TRIGGER:UART:BAUD?"))); | |
//Level | |
ut->SetLevel(stof(converse(":TRIGGER:UART:LIMIT?"))); | |
//Parity | |
auto reply = Trim(converse(":TRIGGER:UART:PARITY?")); | |
if(reply == "NONE") | |
ut->SetParityType(UartTrigger::PARITY_NONE); | |
else if(reply == "EVEN") | |
ut->SetParityType(UartTrigger::PARITY_EVEN); | |
else if(reply == "ODD") | |
ut->SetParityType(UartTrigger::PARITY_ODD); | |
else if(reply == "MARK") | |
ut->SetParityType(UartTrigger::PARITY_MARK); | |
else if(reply == "SPACe") | |
ut->SetParityType(UartTrigger::PARITY_SPACE); | |
//Operator | |
//bool ignore_p2 = true; | |
// It seems this scope only copes with equivalence | |
ut->SetCondition(Trigger::CONDITION_EQUAL); | |
//Idle polarity | |
reply = Trim(converse(":TRIGGER:UART:IDLE?")); | |
if(reply == "HIGH") | |
ut->SetPolarity(UartTrigger::IDLE_HIGH); | |
else if(reply == "LOW") | |
ut->SetPolarity(UartTrigger::IDLE_LOW); | |
//Stop bits | |
ut->SetStopBits(stof(Trim(converse(":TRIGGER:UART:STOP?")))); | |
//Trigger type | |
reply = Trim(converse(":TRIGGER:UART:CONDITION?")); | |
if(reply == "STARt") | |
ut->SetMatchType(UartTrigger::TYPE_START); | |
else if(reply == "STOP") | |
ut->SetMatchType(UartTrigger::TYPE_STOP); | |
else if(reply == "ERRor") | |
ut->SetMatchType(UartTrigger::TYPE_PARITY_ERR); | |
else | |
ut->SetMatchType(UartTrigger::TYPE_DATA); | |
// Data to match (there is no pattern2 on sds) | |
string p1 = Trim(converse(":TRIGGER:UART:DATA?")); | |
ut->SetPatterns(p1, "", true); | |
} | |
/** | |
@brief Reads settings for a window trigger from the instrument | |
*/ | |
void SiglentSCPIOscilloscope::PullWindowTrigger() | |
{ | |
//Clear out any triggers of the wrong type | |
if((m_trigger != NULL) && (dynamic_cast<WindowTrigger*>(m_trigger) != NULL)) | |
{ | |
delete m_trigger; | |
m_trigger = NULL; | |
} | |
//Create a new trigger if necessary | |
if(m_trigger == NULL) | |
m_trigger = new WindowTrigger(this); | |
WindowTrigger* wt = dynamic_cast<WindowTrigger*>(m_trigger); | |
//Lower bound | |
Unit v(Unit::UNIT_VOLTS); | |
wt->SetLowerBound(v.ParseString(converse(":TRIGGER:WINDOW:LLEVEL?"))); | |
//Upper bound | |
wt->SetUpperBound(v.ParseString(converse(":TRIGGER:WINDOW:HLEVEL?"))); | |
} | |
/** | |
@brief Processes the slope for an edge or edge-derived trigger | |
*/ | |
void SiglentSCPIOscilloscope::GetTriggerSlope(EdgeTrigger* trig, string reply) | |
{ | |
reply = Trim(reply); | |
switch(m_modelid) | |
{ | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS1000: | |
if(reply == "POS") | |
trig->SetType(EdgeTrigger::EDGE_RISING); | |
else if(reply == "NEG") | |
trig->SetType(EdgeTrigger::EDGE_FALLING); | |
else if(reply == "WINDOW") | |
trig->SetType(EdgeTrigger::EDGE_ANY); | |
else | |
LogWarning("SDS1000:Unknown trigger slope %s\n", reply.c_str()); | |
break; | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS2000XP: | |
case MODEL_SIGLENT_SDS5000X: | |
if(reply == "RISing") | |
trig->SetType(EdgeTrigger::EDGE_RISING); | |
else if(reply == "FALLing") | |
trig->SetType(EdgeTrigger::EDGE_FALLING); | |
else if(reply == "ALTernate") | |
trig->SetType(EdgeTrigger::EDGE_ANY); | |
else | |
LogWarning("Unknown trigger slope %s\n", reply.c_str()); | |
break; | |
// -------------------------------------------------- | |
default: | |
LogError("Unknown scope type\n"); | |
break; | |
// -------------------------------------------------- | |
} | |
} | |
/** | |
@brief Parses a trigger condition | |
*/ | |
Trigger::Condition SiglentSCPIOscilloscope::GetCondition(string reply) | |
{ | |
reply = Trim(reply); | |
if(reply == "LESSthan") | |
return Trigger::CONDITION_LESS; | |
else if(reply == "GREATerthan") | |
return Trigger::CONDITION_GREATER; | |
else if(reply == "INNer") | |
return Trigger::CONDITION_BETWEEN; | |
else if(reply == "OUTer") | |
return Trigger::CONDITION_NOT_BETWEEN; | |
//unknown | |
LogWarning("Unknown trigger condition [%s]\n", reply.c_str()); | |
return Trigger::CONDITION_LESS; | |
} | |
void SiglentSCPIOscilloscope::PushTrigger() | |
{ | |
lock_guard<recursive_mutex> lock(m_mutex); | |
auto dt = dynamic_cast<DropoutTrigger*>(m_trigger); | |
auto et = dynamic_cast<EdgeTrigger*>(m_trigger); | |
auto pt = dynamic_cast<PulseWidthTrigger*>(m_trigger); | |
auto rt = dynamic_cast<RuntTrigger*>(m_trigger); | |
auto st = dynamic_cast<SlewRateTrigger*>(m_trigger); | |
auto ut = dynamic_cast<UartTrigger*>(m_trigger); | |
auto wt = dynamic_cast<WindowTrigger*>(m_trigger); | |
if(dt) | |
{ | |
sendOnly(":TRIGGER:TYPE DROPOUT"); | |
sendOnly(":TRIGGER:DROPOUT:SOURCE C%d", m_trigger->GetInput(0).m_channel->GetIndex() + 1); | |
PushDropoutTrigger(dt); | |
} | |
else if(pt) | |
{ | |
sendOnly(":TRIGGER:TYPE INTERVAL"); | |
sendOnly(":TRIGGER:INTERVAL:SOURCE C%d", m_trigger->GetInput(0).m_channel->GetIndex() + 1); | |
PushPulseWidthTrigger(pt); | |
} | |
else if(rt) | |
{ | |
sendOnly(":TRIGGER:TYPE RUNT"); | |
sendOnly(":TRIGGER:RUNT:SOURCE C%d", m_trigger->GetInput(0).m_channel->GetIndex() + 1); | |
PushRuntTrigger(rt); | |
} | |
else if(st) | |
{ | |
sendOnly(":TRIGGER:TYPE SLOPE"); | |
sendOnly(":TRIGGER:SLOPE:SOURCE C%d", m_trigger->GetInput(0).m_channel->GetIndex() + 1); | |
PushSlewRateTrigger(st); | |
} | |
else if(ut) | |
{ | |
sendOnly(":TRIGGER:TYPE UART"); | |
// TODO: Validate these trigger allocations | |
sendOnly(":TRIGGER:UART:RXSOURCE C%d", m_trigger->GetInput(0).m_channel->GetIndex() + 1); | |
sendOnly(":TRIGGER:UART:TXSOURCE C%d", m_trigger->GetInput(1).m_channel->GetIndex() + 1); | |
PushUartTrigger(ut); | |
} | |
else if(wt) | |
{ | |
sendOnly(":TRIGGER:TYPE WINDOW"); | |
sendOnly(":TRIGGER:WINDOW:SOURCE C%d", m_trigger->GetInput(0).m_channel->GetIndex() + 1); | |
PushWindowTrigger(wt); | |
} | |
// TODO: Add in PULSE, VIDEO, PATTERN, QUALITFIED, SPI, IIC, CAN, LIN, FLEXRAY and CANFD Triggers | |
else if(et) //must be last | |
{ | |
sendOnly(":TRIGGER:TYPE EDGE"); | |
sendOnly(":TRIGGER:EDGE:SOURCE C%d", m_trigger->GetInput(0).m_channel->GetIndex() + 1); | |
PushEdgeTrigger(et, "EDGE"); | |
} | |
else | |
LogWarning("Unknown trigger type (not an edge)\n"); | |
} | |
/** | |
@brief Pushes settings for a dropout trigger to the instrument | |
*/ | |
void SiglentSCPIOscilloscope::PushDropoutTrigger(DropoutTrigger* trig) | |
{ | |
PushFloat(":TRIGGER:DROPOUT:LEVEL", trig->GetLevel()); | |
PushFloat(":TRIGGER:DROPOUT:TIME", trig->GetDropoutTime() * SECONDS_PER_FS); | |
sendOnly(":TRIGGER:DROPOUT:SLOPE %s", (trig->GetType() == DropoutTrigger::EDGE_RISING) ? "RISING" : "FALLING"); | |
sendOnly(":TRIGGER:DROPOUT:TYPE %s", (trig->GetResetType() == DropoutTrigger::RESET_OPPOSITE) ? "EDGE" : "STATE"); | |
} | |
/** | |
@brief Pushes settings for an edge trigger to the instrument | |
*/ | |
void SiglentSCPIOscilloscope::PushEdgeTrigger(EdgeTrigger* trig, const std::string trigType) | |
{ | |
//Slope | |
switch(m_modelid) | |
{ | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS1000: | |
//trig | |
switch(trig->GetType()) | |
{ | |
case EdgeTrigger::EDGE_RISING: | |
sendOnly("C1:TRIG_SLOPE POS"); | |
break; | |
case EdgeTrigger::EDGE_FALLING: | |
sendOnly("C1:TRIG_SLOPE NEG"); | |
break; | |
case EdgeTrigger::EDGE_ANY: | |
sendOnly("C1:TRIG_SLOPE_WINDOW"); | |
break; | |
default: | |
LogWarning("Invalid trigger type %d\n", trig->GetType()); | |
break; | |
} | |
//Level | |
sendOnly("C1:TRIG_LEVEL %1.2E", trig->GetLevel()); | |
break; | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS2000XP: | |
case MODEL_SIGLENT_SDS5000X: | |
switch(trig->GetType()) | |
{ | |
case EdgeTrigger::EDGE_RISING: | |
sendOnly(":TRIGGER:%s:SLOPE RISING", trigType.c_str()); | |
break; | |
case EdgeTrigger::EDGE_FALLING: | |
sendOnly(":TRIGGER:%s:SLOPE FALLING", trigType.c_str()); | |
break; | |
case EdgeTrigger::EDGE_ANY: | |
sendOnly(":TRIGGER:%s:SLOPE ALTERNATE", trigType.c_str()); | |
break; | |
default: | |
LogWarning("Invalid trigger type %d\n", trig->GetType()); | |
break; | |
} | |
//Level | |
sendOnly(":TRIGGER:%s:LEVEL %1.2E", trigType.c_str(), trig->GetLevel()); | |
break; | |
// -------------------------------------------------- | |
default: | |
LogError("Unknown scope type\n"); | |
break; | |
// -------------------------------------------------- | |
} | |
} | |
/** | |
@brief Pushes settings for a pulse width trigger to the instrument | |
*/ | |
void SiglentSCPIOscilloscope::PushPulseWidthTrigger(PulseWidthTrigger* trig) | |
{ | |
PushEdgeTrigger(trig, "INTERVAL"); | |
PushCondition(":TRIGGER:INTERVAL", trig->GetCondition()); | |
PushFloat(":TRIGGER:INTERVAL:TUPPER", trig->GetUpperBound() * SECONDS_PER_FS); | |
PushFloat(":TRIGGER:INTERVAL:TLOWER", trig->GetLowerBound() * SECONDS_PER_FS); | |
} | |
/** | |
@brief Pushes settings for a runt trigger to the instrument | |
*/ | |
void SiglentSCPIOscilloscope::PushRuntTrigger(RuntTrigger* trig) | |
{ | |
PushCondition(":TRIGGER:RUNT", trig->GetCondition()); | |
PushFloat(":TRIGGER:RUNT:TUPPER", trig->GetUpperInterval() * SECONDS_PER_FS); | |
PushFloat(":TRIGGER:RUNT:TLOWER", trig->GetLowerInterval() * SECONDS_PER_FS); | |
PushFloat(":TRIGGER:RUNT:LLEVEL", trig->GetLowerBound()); | |
PushFloat(":TRIGGER:RUNT:HLEVEL", trig->GetUpperBound()); | |
sendOnly(":TRIGGER:RUNT:POLARITY %s", (trig->GetSlope() == RuntTrigger::EDGE_RISING) ? "POSITIVE" : "NEGATIVE"); | |
} | |
/** | |
@brief Pushes settings for a slew rate trigger to the instrument | |
*/ | |
void SiglentSCPIOscilloscope::PushSlewRateTrigger(SlewRateTrigger* trig) | |
{ | |
PushCondition(":TRIGGER:SLOPE", trig->GetCondition()); | |
PushFloat(":TRIGGER:SLOPE:TUPPER", trig->GetUpperInterval() * SECONDS_PER_FS); | |
PushFloat(":TRIGGER:SLOPE:TLOWER", trig->GetLowerInterval() * SECONDS_PER_FS); | |
PushFloat(":TRIGGER:SLOPE:HLEVEL", trig->GetUpperBound()); | |
PushFloat(":TRIGGER:SLOPE:LLEVEL", trig->GetLowerBound()); | |
sendOnly(":TRIGGER:SLOPE:SLOPE %s", | |
(trig->GetSlope() == SlewRateTrigger::EDGE_RISING) ? "RISING" : | |
(trig->GetSlope() == SlewRateTrigger::EDGE_FALLING) ? "FALLING" : | |
"ALTERNATE"); | |
} | |
/** | |
@brief Pushes settings for a UART trigger to the instrument | |
*/ | |
void SiglentSCPIOscilloscope::PushUartTrigger(UartTrigger* trig) | |
{ | |
//Special parameter for trigger level | |
PushFloat(":TRIGGER:UART:LIMIT", trig->GetLevel()); | |
//AtPosition | |
//Bit9State | |
PushFloat(":TRIGGER:UART:BAUD", trig->GetBitRate()); | |
sendOnly(":TRIGGER:UART:BITORDER LSB"); | |
//DataBytesLenValue1 | |
//DataBytesLenValue2 | |
//DataCondition | |
//FrameDelimiter | |
//InterframeMinBits | |
//NeedDualLevels | |
//NeededSources | |
sendOnly(":TRIGGER:UART:DLENGTH 8"); | |
switch(trig->GetParityType()) | |
{ | |
case UartTrigger::PARITY_NONE: | |
sendOnly(":TRIGGER:UART:PARITY NONE"); | |
break; | |
case UartTrigger::PARITY_ODD: | |
sendOnly(":TRIGGER:UART:PARITY ODD"); | |
break; | |
case UartTrigger::PARITY_EVEN: | |
sendOnly(":TRIGGER:UART:PARITY EVEN"); | |
break; | |
case UartTrigger::PARITY_MARK: | |
sendOnly(":TRIGGER:UART:PARITY MARK"); | |
break; | |
case UartTrigger::PARITY_SPACE: | |
sendOnly(":TRIGGER:UART:PARITY SPACE"); | |
break; | |
} | |
//Pattern length depends on the current format. | |
//Note that the pattern length is in bytes, not bits, even though patterns are in binary. | |
auto pattern1 = trig->GetPattern1(); | |
sendOnly(":TRIGGER:UART:DLENGTH \"%d\"", (int)pattern1.length() / 8); | |
PushCondition(":TRIGGER:UART", trig->GetCondition()); | |
//Polarity | |
sendOnly(":TRIGGER:UART:IDLE %s", (trig->GetPolarity() == UartTrigger::IDLE_HIGH) ? "HIGH" : "LOW"); | |
auto nstop = trig->GetStopBits(); | |
if(nstop == 1) | |
sendOnly(":TRIGGER:UART:STOP 1"); | |
else if(nstop == 2) | |
sendOnly(":TRIGGER:UART:STOP 2"); | |
else | |
sendOnly(":TRIGGER:UART:STOP 1.5"); | |
//Match type | |
switch(trig->GetMatchType()) | |
{ | |
case UartTrigger::TYPE_START: | |
sendOnly(":TRIGGER:UART:CONDITION START"); | |
break; | |
case UartTrigger::TYPE_STOP: | |
sendOnly(":TRIGGER:UART:CONDITION STOP"); | |
break; | |
case UartTrigger::TYPE_PARITY_ERR: | |
sendOnly(":TRIGGER:UART:CONDITION ERROR"); | |
break; | |
default: | |
case UartTrigger::TYPE_DATA: | |
sendOnly(":TRIGGER:UART:CONDITION DATA"); | |
break; | |
} | |
//UARTCondition | |
//ViewingMode | |
} | |
/** | |
@brief Pushes settings for a window trigger to the instrument | |
*/ | |
void SiglentSCPIOscilloscope::PushWindowTrigger(WindowTrigger* trig) | |
{ | |
PushFloat(":TRIGGER:WINDOW:LLEVEL", trig->GetLowerBound()); | |
PushFloat(":TRIGGER:WINDOW:HLEVEL", trig->GetUpperBound()); | |
} | |
/** | |
@brief Pushes settings for a trigger condition under a .Condition field | |
*/ | |
void SiglentSCPIOscilloscope::PushCondition(const string& path, Trigger::Condition cond) | |
{ | |
switch(cond) | |
{ | |
case Trigger::CONDITION_LESS: | |
sendOnly("%s:LIMIT LESSTHAN", path.c_str()); | |
break; | |
case Trigger::CONDITION_GREATER: | |
sendOnly("%s:LIMIT GREATERTHAN", path.c_str()); | |
break; | |
case Trigger::CONDITION_BETWEEN: | |
sendOnly("%s:LIMIT INNER", path.c_str()); | |
break; | |
case Trigger::CONDITION_NOT_BETWEEN: | |
sendOnly("%s:LIMIT OUTER", path.c_str()); | |
break; | |
//Other values are not legal here, it seems | |
default: | |
break; | |
} | |
} | |
void SiglentSCPIOscilloscope::PushFloat(string path, float f) | |
{ | |
sendOnly("%s %1.2E", path.c_str(), f); | |
} | |
vector<string> SiglentSCPIOscilloscope::GetTriggerTypes() | |
{ | |
vector<string> ret; | |
switch(m_modelid) | |
{ | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS1000: | |
ret.push_back(EdgeTrigger::GetTriggerName()); | |
// TODO add more | |
break; | |
// -------------------------------------------------- | |
case MODEL_SIGLENT_SDS2000XP: | |
case MODEL_SIGLENT_SDS5000X: | |
ret.push_back(DropoutTrigger::GetTriggerName()); | |
ret.push_back(EdgeTrigger::GetTriggerName()); | |
ret.push_back(PulseWidthTrigger::GetTriggerName()); | |
ret.push_back(RuntTrigger::GetTriggerName()); | |
ret.push_back(SlewRateTrigger::GetTriggerName()); | |
if(m_hasUartTrigger) | |
ret.push_back(UartTrigger::GetTriggerName()); | |
ret.push_back(WindowTrigger::GetTriggerName()); | |
break; | |
// -------------------------------------------------- | |
default: | |
LogError("Unknown scope type\n"); | |
break; | |
// -------------------------------------------------- | |
} | |
// TODO: Add in PULSE, VIDEO, PATTERN, QUALITFIED, SPI, IIC, CAN, LIN, FLEXRAY and CANFD Triggers | |
return ret; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment