diff --git a/scopehal/RigolOscilloscope.cpp b/scopehal/RigolOscilloscope.cpp index 045b8212..de4878e8 100644 --- a/scopehal/RigolOscilloscope.cpp +++ b/scopehal/RigolOscilloscope.cpp @@ -38,8 +38,41 @@ #include #endif +#pragma GCC diagnostic push +#pragma GCC diagnostic error "-Wswitch" +// #pragma GCC diagnostic warning "-Wswitch-enum" + + using namespace std; +template +static optional parseNumeric(string const &input, const string &fmt) { + T output {}; + if (sscanf(input.c_str(), fmt.c_str(), &output) < 1) + return nullopt; + return output; +} + +static auto parseDouble(string const &input, const string &fmt = "%lf") +{ + return parseNumeric(input, fmt); +} + +static auto parseU64(string const &input, const string &fmt = "%" PRIu64) +{ + return parseNumeric(input, fmt); +} + +//TODO: this would make sens to move to some utility library +template +struct CallOnEOC { + T onExit; + CallOnEOC(T onExit_) : onExit(onExit_) {} + ~CallOnEOC() { + onExit(); + } +}; + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //Construction / destruction @@ -52,151 +85,18 @@ RigolOscilloscope::RigolOscilloscope(SCPITransport* transport) , m_liveMode(false) , m_highDefinition(false) { - //Last digit of the model number is the number of channels - if(1 == sscanf(m_model.c_str(), "DS%d", &m_modelNumber)) - { - if(m_model.size() >= 7 && (m_model[6] == 'D' || m_model[6] == 'E')) - m_protocol = DS_OLD; - else - m_protocol = DS; - } - else if(1 == sscanf(m_model.c_str(), "MSO%d", &m_modelNumber)) - { - m_protocol = MSO5; - // Hacky workaround since :SYST:OPT:STAT doesn't work properly on some scopes - // Only enable chan 1 - m_transport->SendCommandQueued("CHAN1:DISP 1\n"); - m_transport->SendCommandQueued("CHAN2:DISP 0\n"); - if(m_modelNumber % 10 > 2) - { - m_transport->SendCommandQueued("CHAN3:DISP 0\n"); - m_transport->SendCommandQueued("CHAN4:DISP 0\n"); - } - // Set in run mode to be able to set memory depth - m_transport->SendCommandQueued("RUN\n"); - - m_transport->SendCommandQueued("ACQ:MDEP 200M\n"); - auto reply = Trim(m_transport->SendCommandQueuedWithReply("ACQ:MDEP?\n")); - m_opt200M = reply == "2.0000E+08" ? - true : - false; // Yes, it actually returns a stringified float, manual says "scientific notation" - - // Reset memory depth - m_transport->SendCommandQueued("ACQ:MDEP 1M\n"); - string originalBandwidthLimit = m_transport->SendCommandQueuedWithReply("CHAN1:BWL?"); - - // Figure out its actual bandwidth since :SYST:OPT:STAT is practically useless - m_transport->SendCommandQueued("CHAN1:BWL 200M\n"); - reply = Trim(m_transport->SendCommandQueuedWithReply("CHAN1:BWL?\n")); - - // A bit of a tree, maybe write more beautiful code - if(reply == "200M") - m_bandwidth = 350; - else - { - m_transport->SendCommandQueued("CHAN1:BWL 100M\n"); - reply = Trim(m_transport->SendCommandQueuedWithReply("CHAN1:BWL?\n")); - if(reply == "100M") - m_bandwidth = 200; - else - { - if(m_modelNumber % 1000 - m_modelNumber % 10 == 100) - m_bandwidth = 100; - else - m_bandwidth = 70; - } - } - - m_transport->SendCommandQueued("CHAN1:BWL " + originalBandwidthLimit); - } - else if(1 == sscanf(m_model.c_str(), "DHO%d", &m_modelNumber) && (m_modelNumber < 5000)) - { // Model numbers are : - // - DHO802 (70MHz), DHO804 (70Mhz), DHO812 (100MHz),DHO814 (100MHz) - // - DHO914/DHO914S (125MHz), DHO924/DHO924S (250MHz) - // - DHO1072 (70MHz), DHO1074 (70MHz), DHO1102 (100MHz), DHO1104 (100MHz), DHO1202 (200MHz), DHO1204 (200MHz) - // - DHO4204 (200MHz), DHO4404 (400 MHz), DHO4804 (800MHz) - m_protocol = DHO; - // Those are 12 bits (HD) models => default to high definition mode - // This can be overriden by driver 8bits setting - m_highDefinition = true; - - int model_multiplicator = 100; - int model_modulo = 100; - if(m_modelNumber > 1000) - { // DHO1000 and 4000 - model_multiplicator = 10; - model_modulo = 1000; - } - else if(m_modelNumber > 900) - { // special handling of DHO900 series - model_multiplicator = 125; - } - m_bandwidth = m_modelNumber % model_modulo / 10 * model_multiplicator; - if(m_bandwidth == 0) m_bandwidth = 70; // Fallback for DHO80x models - m_opt200M = false; // does not exist in 800/900 series - m_lowSrate = false; - - if (m_modelNumber > 4000 && m_modelNumber < 5000) { - m_maxMdepth = 250*1000*1000; - m_maxSrate = 4*1000*1000*1000U; - /* probe for bandwidth upgrades and memory upgrades on DHO4000 series */ - auto reply = Trim(m_transport->SendCommandQueuedWithReply(":SYST:OPT:STAT? RLU\n")); - if (reply == "1") - m_maxMdepth = 500*1000*1000; - - reply = Trim(m_transport->SendCommandQueuedWithReply(":SYST:OPT:STAT? BW2T4\n")); - if (reply == "1") - m_bandwidth = 400; - - reply = Trim(m_transport->SendCommandQueuedWithReply(":SYST:OPT:STAT? BW2T8\n")); - if (reply == "1") - m_bandwidth = 800; - - reply = Trim(m_transport->SendCommandQueuedWithReply(":SYST:OPT:STAT? BW4T8\n")); - if (reply == "1") - m_bandwidth = 800; - } - else if (m_modelNumber > 1000 && m_modelNumber < 2000) { - m_maxMdepth = 50*1000*1000; - m_maxSrate = 2*1000*1000*1000; - /* probe for bandwidth upgrades and memory upgrades on DHO1000 series */ - auto reply = Trim(m_transport->SendCommandQueuedWithReply(":SYST:OPT:STAT? RLU\n")); - if (reply == "1") - m_maxMdepth = 100*1000*1000; - - reply = Trim(m_transport->SendCommandQueuedWithReply(":SYST:OPT:STAT? BW7T10\n")); - if (reply == "1") - m_bandwidth = 100; - - reply = Trim(m_transport->SendCommandQueuedWithReply(":SYST:OPT:STAT? BW7T20\n")); - if (reply == "1") - m_bandwidth = 200; - - reply = Trim(m_transport->SendCommandQueuedWithReply(":SYST:OPT:STAT? BW10T20\n")); - if (reply == "1") - m_bandwidth = 200; - } - else - { // DHO800/900 (DHO800 also have 50M memory since firmware v00.01.03.00.04 2024/07/11) - m_maxMdepth = 50*1000*1000; - m_maxSrate = 1.25*1000*1000*1000; - m_lowSrate = true; - } - } - else + DecodeDeviceSeries(); + if(m_series == Series::UNKNOWN) { - LogError("Bad model number\n"); + LogError("device series not recognized nor supported\n"); return; } + LogTrace("RigolOscilloscope: series: %d\n", int(m_series)); - // Maybe fix this in a similar manner to bandwidth - int nchans = m_modelNumber % 10; + AnalyzeDeviceCapabilities(); - if((m_protocol != MSO5) && (m_protocol != DHO)) - m_bandwidth = m_modelNumber % 1000 - nchans; - - for(int i = 0; i < nchans; i++) + for(auto i = 0U; i < m_analogChannelCount; i++) { //Hardware name of the channel string chname = string("CHAN") + to_string(i + 1); @@ -228,31 +128,139 @@ RigolOscilloscope::RigolOscilloscope(SCPITransport* transport) m_channels.push_back(chan); chan->SetDefaultDisplayName(); } - m_analogChannelCount = nchans; - - //Add the external trigger input - m_extTrigChannel = new OscilloscopeChannel( - this, "EX", "", Unit(Unit::UNIT_FS), Unit(Unit::UNIT_VOLTS), Stream::STREAM_TYPE_TRIGGER, m_channels.size()); - m_channels.push_back(m_extTrigChannel); - m_extTrigChannel->SetDefaultDisplayName(); - //Configure acquisition modes - if(m_protocol == DS_OLD) - m_transport->SendCommandQueued(":WAV:POIN:MODE RAW"); - else + if (m_digitalChannelCount != 0) { - m_transport->SendCommandQueued(string(":WAV:FORM ") + (m_highDefinition ? "WORD" : "BYTE")); - m_transport->SendCommandQueued(":WAV:MODE RAW"); + m_digitalBanks.push_back({}); + for(auto i = 0U; i < m_digitalChannelCount; i++) + { + //Hardware name of the channel + string chname = string("D") + to_string(i); + + //Color the channels based on Rigol's standard color sequence (yellow-cyan-red-blue) + string color = "#66ff00"; + + //Create the channel + auto chan = new OscilloscopeChannel( + this, chname, color, Unit(Unit::UNIT_FS), Unit(Unit::UNIT_VOLTS), Stream::STREAM_TYPE_DIGITAL, m_analogChannelCount + i); + m_channels.push_back(chan); + if (m_digitalBanks.back().size() == DIGITAL_BANK_SIZE) + m_digitalBanks.push_back({}); + m_digitalBanks.back().push_back(chan); + chan->SetDefaultDisplayName(); + } } - if(m_protocol == MSO5 || m_protocol == DS_OLD || m_protocol == DHO) + // Add the external trigger input + auto const has_ext_trigger = [&]() -> bool + { + switch (m_series) + { + case Series::DHO800: + // from DHO800/900 prog. manual: "EXT is only available for DHO812 and DHO802" + // assuming it is based on analog channel count == 2 ... + return m_analogChannelCount == 2; + + case Series::DS1000: + case Series::MSODS1000Z: + case Series::DHO1000: + case Series::DHO4000: + case Series::DHO900: + return true; + + case Series::MSO5000: + case Series::UNKNOWN: + return false; + } + return false; + }(); + if (has_ext_trigger) { - for(size_t i = 0; i < m_analogChannelCount; i++) - m_transport->SendCommandQueued(":" + m_channels[i]->GetHwname() + ":VERN ON"); + m_extTrigChannel = new OscilloscopeChannel( + this, "External", "#FFFFFF", Unit(Unit::UNIT_FS), Unit(Unit::UNIT_VOLTS), Stream::STREAM_TYPE_TRIGGER, m_channels.size()); + m_channels.push_back(m_extTrigChannel); + m_extTrigChannel->SetDefaultDisplayName(); } - if(m_protocol == MSO5 || m_protocol == DS || m_protocol == DHO) - m_transport->SendCommandQueued(":TIM:VERN ON"); - FlushConfigCache(); + // Add AC line external trigger input + switch (m_series) + { + case Series::MSODS1000Z: + case Series::MSO5000: + case Series::DHO1000: + case Series::DHO4000: + case Series::DS1000: + m_aclTrigChannel = new OscilloscopeChannel( + this, "ACL", "#FF0000", Unit(Unit::UNIT_FS), Unit(Unit::UNIT_VOLTS), Stream::STREAM_TYPE_TRIGGER, m_channels.size()); + m_channels.push_back(m_aclTrigChannel); + m_aclTrigChannel->SetDisplayName("ACLine"); + m_aclTrigChannel->SetDefaultDisplayName(); + break; + + case Series::DHO800: + case Series::DHO900: + case Series::UNKNOWN: + break; + } + + //Configure acquisition modes + switch (m_series) { + case Series::DS1000: + // m_transport->SendCommandQueued(":WAV:POIN:MODE RAW"); + // this command is not listed anywhere in the docs + break; + + case Series::MSODS1000Z: + case Series::MSO5000: + case Series::DHO1000: + case Series::DHO4000: + case Series::DHO800: + case Series::DHO900: + m_transport->SendCommandQueued(string(":WAV:FORM ") + (m_highDefinition ? "WORD" : "BYTE")); + m_transport->SendCommandQueued(":WAV:MODE RAW"); + break; + + case Series::UNKNOWN: + break; + } + + // disable auto roll mode on DHOs + switch (m_series) { + case Series::DHO1000: + case Series::DHO4000: + case Series::DHO800: + case Series::DHO900: + m_transport->SendCommandQueued(":TIM:ROLL 0"); + break; + + case Series::DS1000: + case Series::MSODS1000Z: + case Series::MSO5000: + case Series::UNKNOWN: + break; + } + + + for(size_t i = 0; i < m_analogChannelCount; i++) + m_transport->SendCommandQueued(":" + m_channels[i]->GetHwname() + ":VERN ON"); + + switch (m_series) { + case Series::MSODS1000Z: + case Series::MSO5000: + case Series::DHO1000: + case Series::DHO4000: + case Series::DHO800: + case Series::DHO900: + m_transport->SendCommandQueued(":TIM:VERN ON"); + break; + + case Series::DS1000: + case Series::UNKNOWN: + break; + } + + FlushConfigCache(); + + UpdateDynamicCapabilities(); //make sure all setup commands finish before we proceed m_transport->FlushCommandQueue(); } @@ -277,6 +285,585 @@ uint32_t RigolOscilloscope::GetInstrumentTypesForChannel(size_t /*i*/) const //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //Device interface functions +void RigolOscilloscope::DecodeDeviceSeries() +{ + LogTrace("Decoding device series\n"); + // Called once from the ctor so we don't lock any mutex as there is only one reference to this object + // and no concurrent access is possible at this time. + + // Last digit of the model number is the number of channels + m_series = [&]() -> Series + { + // scope name is always, no numeric prefix, followed by numeric model number optionally followed by alfanumeric suffix + auto cursor = m_model.begin(); + { + // extract + m_modelNew.prefix.clear(); + m_modelNew.prefix.resize(10); // preallocate space + int length; + if (1 != sscanf(cursor.base(), "%4[^0-9]%n", m_modelNew.prefix.data(), &length)) + { + LogError("could not parse scope series prefix, got more than maximum expected length 3\n"); + return Series::UNKNOWN; + } + m_modelNew.prefix.resize(length); + LogTrace("parsed model prefix %s\n", m_modelNew.prefix.c_str()); + cursor += length; + } + + { + // parse model number + int length; + if (1 != sscanf(cursor.base(), "%5u%n", &m_modelNew.number, &length)) + { + LogError("could not parse scope model number\n"); + return Series::UNKNOWN; + } + LogTrace("parsed model numer %d\n", m_modelNew.number); + cursor += length; + } + + // extract suffix - just a remainder after model number + m_modelNew.suffix.assign(cursor, m_model.end()); + + // decode into device family + if(m_modelNew.prefix == "DS" or m_modelNew.prefix == "MSO") + { + switch(m_modelNew.number / 1000) + { + case 1: + if(m_modelNew.suffix.size() < 1) + break; + if(m_modelNew.suffix[0] == 'D' || m_modelNew.suffix[0] == 'E') + return Series::DS1000; + else if(m_modelNew.suffix[0] == 'Z') + return Series::MSODS1000Z; + break; + case 5: return Series::MSO5000; + default: break; + } + } + else if (m_modelNew.prefix == "DHO") + { + if (m_modelNew.number < 1000) + { + switch (m_modelNew.number / 100) + { + case 8: return Series::DHO800; + case 9: return Series::DHO900; + default: break; + } + } + else + { + switch (m_modelNew.number / 1000) + { + case 1: return Series::DHO1000; + case 4: return Series::DHO4000; + default: break; + } + } + } + LogError("model %s was not recognized\n", m_model.c_str()); + return Series::UNKNOWN; + }(); +} + +void RigolOscilloscope::AnalyzeDeviceCapabilities() { + // Called once from the ctor so we don't lock any mutex as there is only one reference to this object + // and no concurrent access is possible at this time. + LogTrace("Analyzing scope capabilities\n"); + + // Last digit of the model number is the number of channels + switch(m_series) { + + case Series::DS1000: + m_analogChannelCount = m_modelNew.number % 10; + m_bandwidth = m_modelNew.number % 1000 - m_analogChannelCount; + //TODO: there are DS1000D devices with LA + break; + + case Series::MSODS1000Z: + { + m_analogChannelCount = m_modelNew.number % 10; + m_bandwidth = m_modelNew.number % 1000 - m_analogChannelCount; + + m_digitalChannelCount = m_modelNew.prefix == "MSO" ? 16 : 0; + + { + // Probe 24M memory depth option. + // Hacky workaround since DS1000Z does not have a way how to query installed options + // Only enable chan 1 + // TODO: what about LA channels on MSOs? + m_transport->SendCommandQueued("CHAN1:DISP 1"); + m_transport->SendCommandQueued("CHAN2:DISP 0"); + if(m_analogChannelCount > 2) + { + m_transport->SendCommandQueued("CHAN3:DISP 0"); + m_transport->SendCommandQueued("CHAN4:DISP 0"); + } + + auto original_state = Trim(m_transport->SendCommandQueuedWithReply(":TRIG:STAT?")); + // Set in run mode to be able to set memory depth + m_transport->SendCommandQueued("RUN"); + auto originalMdepth = Trim(m_transport->SendCommandQueuedWithReply("ACQ:MDEP?")); + m_transport->SendCommandQueued("ACQ:MDEP 24000000"); + m_opt24M = Trim(m_transport->SendCommandQueuedWithReply("ACQ:MDEP?")) == "24000000"; + if (m_opt24M) + LogTrace("DS1000Z: 24 Mpts memory option detected\n"); + + // Reset memory depth to original value + m_transport->SendCommandQueued("ACQ:MDEP " + originalMdepth); + if (original_state == "STOP") + m_transport->SendCommandQueued("STOP"); + } + + break; + } + + case Series::MSO5000: + { + m_analogChannelCount = m_modelNew.number % 10; + m_digitalChannelCount = 16; + + // Hacky workarounds since :SYST:OPT:STAT doesn't work properly on some scopes (especially patched FWs) + // Such scopes reply with 0 to all option querios even though they have all active. + + if (m_analogChannelCount < 4) + do // lambda would be cleaner, but current implementation of logging does not support trace logs from lambdas + { + lock_guard lock(m_transport->GetMutex()); // this sequence may not be interrupted by others + // MSO5072 has 4 HW channels, but 2 are available only as an option ("4CH") + // As previously mentioned, :SYST:OPT:STAT?