Skip to content

Instantly share code, notes, and snippets.

@dannas
Created January 20, 2022 20:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dannas/cf01ba5687d3ed4d00baa84661c33f26 to your computer and use it in GitHub Desktop.
Save dannas/cf01ba5687d3ed4d00baa84661c33f26 to your computer and use it in GitHub Desktop.
Mubes changes to SiglentSCPIOscilloscope.cpp
/***********************************************************************************************************************
* *
* 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", &timestamp))
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