diff --git a/openhantek/CMakeLists.txt b/openhantek/CMakeLists.txt index 1c90d18..eb984b3 100644 --- a/openhantek/CMakeLists.txt +++ b/openhantek/CMakeLists.txt @@ -10,7 +10,7 @@ set(CMAKE_AUTORCC ON) # include directories set(CMAKE_INCLUDE_CURRENT_DIR ON) -include_directories(src/ src/dso src/hantek) +include_directories(src/ src/hantek src/analyse src/widgets src/docks src/configdialog) # collect sources and other files file(GLOB_RECURSE SRC "src/*.cpp") diff --git a/openhantek/src/analyse/dataanalyzer.cpp b/openhantek/src/analyse/dataanalyzer.cpp new file mode 100644 index 0000000..099c441 --- /dev/null +++ b/openhantek/src/analyse/dataanalyzer.cpp @@ -0,0 +1,426 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// OpenHantek +// dataanalyzer.cpp +// +// Copyright (C) 2010 Oliver Haag +// oliver.haag@gmail.com +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along with +// this program. If not, see . +// +//////////////////////////////////////////////////////////////////////////////// + +#define _USE_MATH_DEFINES +#include + +#include +#include +#include + +#include + +#include "dataanalyzer.h" + +#include "glscope.h" +#include "utils/printutils.h" +#include "settings.h" + +std::unique_ptr DataAnalyzer::convertData(const DSOsamples* data, const DsoSettingsScope *scope) { + QReadLocker locker(&data->lock); + + unsigned int channelCount = (unsigned int)scope->voltage.size(); + + std::unique_ptr result = std::unique_ptr(new DataAnalyzerResult(channelCount)); + + for (unsigned int channel = 0; channel < channelCount; ++channel) { + DataChannel *const channelData = result->modifyData(channel); + + bool gotDataForChannel = channel < scope->physicalChannels && + channel < (unsigned int)data->data.size() && + !data->data.at(channel).empty(); + bool isMathChannel = channel >= scope->physicalChannels && + (scope->voltage[channel].used || + scope->spectrum[channel].used) && + result->channelCount() >= 2 && + !result->data(0)->voltage.sample.empty() && + !result->data(1)->voltage.sample.empty(); + + if ( !gotDataForChannel && !isMathChannel) { + // Clear unused channels + channelData->voltage.sample.clear(); + result->modifyData(scope->physicalChannels)->voltage.interval = 0; + continue; + } + + // Set sampling interval + const double interval = 1.0 / data->samplerate; + if (interval != channelData->voltage.interval) { + channelData->voltage.interval = interval; + if (data->append) // Clear roll buffer if the samplerate changed + channelData->voltage.sample.clear(); + } + + unsigned int size; + if (channel < scope->physicalChannels) { + size = data->data.at(channel).size(); + if (data->append) + size += channelData->voltage.sample.size(); + result->challengeMaxSamples(size); + } else + size = result->getMaxSamples(); + + // Physical channels + if (channel < scope->physicalChannels) { + // Copy the buffer of the oscilloscope into the sample buffer + if (data->append) + channelData->voltage.sample.insert( + channelData->voltage.sample.end(), + data->data.at(channel).begin(), + data->data.at(channel).end()); + else + channelData->voltage.sample = data->data.at(channel); + } else { // Math channel + // Resize the sample vector + channelData->voltage.sample.resize(size); + // Set sampling interval + result->modifyData(scope->physicalChannels)->voltage.interval = + result->data(0)->voltage.interval; + + // Resize the sample vector + result->modifyData(scope->physicalChannels)-> + voltage.sample.resize(qMin(result->data(0)->voltage.sample.size(), + result->data(1)->voltage.sample.size())); + + // Calculate values and write them into the sample buffer + std::vector::const_iterator ch1Iterator = + result->data(0)->voltage.sample.begin(); + std::vector::const_iterator ch2Iterator = + result->data(1)->voltage.sample.begin(); + std::vector &resultData = + result->modifyData(scope->physicalChannels)->voltage.sample; + for (std::vector::iterator resultIterator = resultData.begin(); + resultIterator != resultData.end(); ++resultIterator) { + switch (scope->voltage[scope->physicalChannels].misc) { + case Dso::MATHMODE_1ADD2: + *resultIterator = *ch1Iterator + *ch2Iterator; + break; + case Dso::MATHMODE_1SUB2: + *resultIterator = *ch1Iterator - *ch2Iterator; + break; + case Dso::MATHMODE_2SUB1: + *resultIterator = *ch2Iterator - *ch1Iterator; + break; + } + ++ch1Iterator; + ++ch2Iterator; + } + } + } + return result; +} + +/// \brief Analyzes the data from the dso. +void DataAnalyzer::applySettings(DsoSettingsScope *scope) +{ + this->scope=scope; +} + +void DataAnalyzer::setSourceData(const DSOsamples *data) +{ + sourceData = data; +} + +std::unique_ptr DataAnalyzer::getNextResult() +{ + return std::move(lastResult); +} + +void DataAnalyzer::samplesAvailable() +{ + if (sourceData == nullptr) return; + std::unique_ptr result = convertData(sourceData, scope); + spectrumAnalysis(result.get(), lastWindow, lastRecordLength, scope); + lastResult = std::move(result); + emit analyzed(); + +} + +void DataAnalyzer::spectrumAnalysis(DataAnalyzerResult* result, + Dso::WindowFunction& lastWindow, unsigned int lastRecordLength, + const DsoSettingsScope *scope) { + // Calculate frequencies, peak-to-peak voltages and spectrums + for (unsigned int channel = 0; channel < result->channelCount(); ++channel) { + DataChannel *const channelData = result->modifyData(channel); + + if (!channelData->voltage.sample.empty()) { + // Calculate new window + unsigned int sampleCount = channelData->voltage.sample.size(); + if (lastWindow != scope->spectrumWindow || + lastRecordLength != sampleCount) { + if (lastRecordLength != sampleCount) { + lastRecordLength = sampleCount; + + if (result->window) + fftw_free(result->window); + result->window = + (double *)fftw_malloc(sizeof(double) * lastRecordLength); + } + + unsigned int windowEnd = lastRecordLength - 1; + lastWindow = scope->spectrumWindow; + + switch (scope->spectrumWindow) { + case Dso::WINDOW_HAMMING: + for (unsigned int windowPosition = 0; + windowPosition < lastRecordLength; ++windowPosition) + *(result->window + windowPosition) = + 0.54 - 0.46 * cos(2.0 * M_PI * windowPosition / windowEnd); + break; + case Dso::WINDOW_HANN: + for (unsigned int windowPosition = 0; + windowPosition < lastRecordLength; ++windowPosition) + *(result->window + windowPosition) = + 0.5 * (1.0 - cos(2.0 * M_PI * windowPosition / windowEnd)); + break; + case Dso::WINDOW_COSINE: + for (unsigned int windowPosition = 0; + windowPosition < lastRecordLength; ++windowPosition) + *(result->window + windowPosition) = + sin(M_PI * windowPosition / windowEnd); + break; + case Dso::WINDOW_LANCZOS: + for (unsigned int windowPosition = 0; + windowPosition < lastRecordLength; ++windowPosition) { + double sincParameter = + (2.0 * windowPosition / windowEnd - 1.0) * M_PI; + if (sincParameter == 0) + *(result->window + windowPosition) = 1; + else + *(result->window + windowPosition) = + sin(sincParameter) / sincParameter; + } + break; + case Dso::WINDOW_BARTLETT: + for (unsigned int windowPosition = 0; + windowPosition < lastRecordLength; ++windowPosition) + *(result->window + windowPosition) = + 2.0 / windowEnd * + (windowEnd / 2 - + std::abs((double)(windowPosition - windowEnd / 2.0))); + break; + case Dso::WINDOW_TRIANGULAR: + for (unsigned int windowPosition = 0; + windowPosition < lastRecordLength; ++windowPosition) + *(result->window + windowPosition) = + 2.0 / lastRecordLength * + (lastRecordLength / 2 - + std::abs((double)(windowPosition - windowEnd / 2.0))); + break; + case Dso::WINDOW_GAUSS: { + double sigma = 0.4; + for (unsigned int windowPosition = 0; + windowPosition < lastRecordLength; ++windowPosition) + *(result->window + windowPosition) = + exp(-0.5 * pow(((windowPosition - windowEnd / 2) / + (sigma * windowEnd / 2)), + 2)); + } break; + case Dso::WINDOW_BARTLETTHANN: + for (unsigned int windowPosition = 0; + windowPosition < lastRecordLength; ++windowPosition) + *(result->window + windowPosition) = + 0.62 - + 0.48 * std::abs((double)(windowPosition / windowEnd - 0.5)) - + 0.38 * cos(2.0 * M_PI * windowPosition / windowEnd); + break; + case Dso::WINDOW_BLACKMAN: { + double alpha = 0.16; + for (unsigned int windowPosition = 0; + windowPosition < lastRecordLength; ++windowPosition) + *(result->window + windowPosition) = + (1 - alpha) / 2 - + 0.5 * cos(2.0 * M_PI * windowPosition / windowEnd) + + alpha / 2 * cos(4.0 * M_PI * windowPosition / windowEnd); + } break; + // case WINDOW_KAISER: + // TODO + // double alpha = 3.0; + // for(unsigned int windowPosition = 0; windowPosition < + // lastRecordLength; ++windowPosition) + //*(result->window + windowPosition) = ; + // break; + case Dso::WINDOW_NUTTALL: + for (unsigned int windowPosition = 0; + windowPosition < lastRecordLength; ++windowPosition) + *(result->window + windowPosition) = + 0.355768 - + 0.487396 * cos(2 * M_PI * windowPosition / windowEnd) + + 0.144232 * cos(4 * M_PI * windowPosition / windowEnd) - + 0.012604 * cos(6 * M_PI * windowPosition / windowEnd); + break; + case Dso::WINDOW_BLACKMANHARRIS: + for (unsigned int windowPosition = 0; + windowPosition < lastRecordLength; ++windowPosition) + *(result->window + windowPosition) = + 0.35875 - 0.48829 * cos(2 * M_PI * windowPosition / windowEnd) + + 0.14128 * cos(4 * M_PI * windowPosition / windowEnd) - + 0.01168 * cos(6 * M_PI * windowPosition / windowEnd); + break; + case Dso::WINDOW_BLACKMANNUTTALL: + for (unsigned int windowPosition = 0; + windowPosition < lastRecordLength; ++windowPosition) + *(result->window + windowPosition) = + 0.3635819 - + 0.4891775 * cos(2 * M_PI * windowPosition / windowEnd) + + 0.1365995 * cos(4 * M_PI * windowPosition / windowEnd) - + 0.0106411 * cos(6 * M_PI * windowPosition / windowEnd); + break; + case Dso::WINDOW_FLATTOP: + for (unsigned int windowPosition = 0; + windowPosition < lastRecordLength; ++windowPosition) + *(result->window + windowPosition) = + 1.0 - 1.93 * cos(2 * M_PI * windowPosition / windowEnd) + + 1.29 * cos(4 * M_PI * windowPosition / windowEnd) - + 0.388 * cos(6 * M_PI * windowPosition / windowEnd) + + 0.032 * cos(8 * M_PI * windowPosition / windowEnd); + break; + default: // Dso::WINDOW_RECTANGULAR + for (unsigned int windowPosition = 0; + windowPosition < lastRecordLength; ++windowPosition) + *(result->window + windowPosition) = 1.0; + } + } + + // Set sampling interval + channelData->spectrum.interval = + 1.0 / channelData->voltage.interval / sampleCount; + + // Number of real/complex samples + unsigned int dftLength = sampleCount / 2; + + // Reallocate memory for samples if the sample count has changed + channelData->spectrum.sample.resize(sampleCount); + + // Create sample buffer and apply window + double *windowedValues = new double[sampleCount]; + for (unsigned int position = 0; position < sampleCount; ++position) + windowedValues[position] = + result->window[position] * + channelData->voltage.sample[position]; + + // Do discrete real to half-complex transformation + /// \todo Check if record length is multiple of 2 + /// \todo Reuse plan and use FFTW_MEASURE to get fastest algorithm + fftw_plan fftPlan = + fftw_plan_r2r_1d(sampleCount, windowedValues, + &channelData->spectrum.sample.front(), + FFTW_R2HC, FFTW_ESTIMATE); + fftw_execute(fftPlan); + fftw_destroy_plan(fftPlan); + + // Do an autocorrelation to get the frequency of the signal + double *conjugateComplex = + windowedValues; // Reuse the windowedValues buffer + + // Real values + unsigned int position; + double correctionFactor = 1.0 / dftLength / dftLength; + conjugateComplex[0] = (channelData->spectrum.sample[0] * + channelData->spectrum.sample[0]) * + correctionFactor; + for (position = 1; position < dftLength; ++position) + conjugateComplex[position] = + (channelData->spectrum.sample[position] * + channelData->spectrum.sample[position] + + channelData->spectrum.sample[sampleCount - position] * + channelData->spectrum.sample[sampleCount - position]) * + correctionFactor; + // Complex values, all zero for autocorrelation + conjugateComplex[dftLength] = + (channelData->spectrum.sample[dftLength] * + channelData->spectrum.sample[dftLength]) * + correctionFactor; + for (++position; position < sampleCount; ++position) + conjugateComplex[position] = 0; + + // Do half-complex to real inverse transformation + double *correlation = new double[sampleCount]; + fftPlan = fftw_plan_r2r_1d(sampleCount, conjugateComplex, correlation, + FFTW_HC2R, FFTW_ESTIMATE); + fftw_execute(fftPlan); + fftw_destroy_plan(fftPlan); + delete[] conjugateComplex; + + // Calculate peak-to-peak voltage + double minimalVoltage, maximalVoltage; + minimalVoltage = maximalVoltage = channelData->voltage.sample[0]; + + for (unsigned int position = 1; position < sampleCount; ++position) { + if (channelData->voltage.sample[position] < minimalVoltage) + minimalVoltage = channelData->voltage.sample[position]; + else if (channelData->voltage.sample[position] > maximalVoltage) + maximalVoltage = channelData->voltage.sample[position]; + } + + channelData->amplitude = maximalVoltage - minimalVoltage; + + // Get the frequency from the correlation results + double minimumCorrelation = correlation[0]; + double peakCorrelation = 0; + unsigned int peakPosition = 0; + + for (unsigned int position = 1; position < sampleCount / 2; ++position) { + if (correlation[position] > peakCorrelation && + correlation[position] > minimumCorrelation * 2) { + peakCorrelation = correlation[position]; + peakPosition = position; + } else if (correlation[position] < minimumCorrelation) + minimumCorrelation = correlation[position]; + } + delete[] correlation; + + // Calculate the frequency in Hz + if (peakPosition) + channelData->frequency = + 1.0 / (channelData->voltage.interval * peakPosition); + else + channelData->frequency = 0; + + // Finally calculate the real spectrum if we want it + if (scope->spectrum[channel].used) { + // Convert values into dB (Relative to the reference level) + double offset = 60 - scope->spectrumReference - + 20 * log10(dftLength); + double offsetLimit = scope->spectrumLimit - + scope->spectrumReference; + for (std::vector::iterator spectrumIterator = + channelData->spectrum.sample.begin(); + spectrumIterator != channelData->spectrum.sample.end(); + ++spectrumIterator) { + double value = 20 * log10(fabs(*spectrumIterator)) + offset; + + // Check if this value has to be limited + if (offsetLimit > value) + value = offsetLimit; + + *spectrumIterator = value; + } + } + } else if (!channelData->spectrum.sample.empty()) { + // Clear unused channels + channelData->spectrum.interval = 0; + channelData->spectrum.sample.clear(); + } + } +} diff --git a/openhantek/src/analyse/dataanalyzer.h b/openhantek/src/analyse/dataanalyzer.h new file mode 100644 index 0000000..95cea4e --- /dev/null +++ b/openhantek/src/analyse/dataanalyzer.h @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#pragma once + +#include + +#include +#include +#include + +#include "definitions.h" +#include "utils/printutils.h" +#include "dataanalyzerresult.h" +#include "dsosamples.h" + +struct DsoSettingsScope; + +//////////////////////////////////////////////////////////////////////////////// +/// \class DataAnalyzer dataanalyzer.h +/// \brief Analyzes the data from the dso. +/// Calculates the spectrum and various data about the signal and saves the +/// time-/frequencysteps between two values. +class DataAnalyzer : public QObject { + Q_OBJECT + +public: + void applySettings(DsoSettingsScope* scope); + void setSourceData(const DSOsamples* data); + std::unique_ptr getNextResult(); + /** + * Call this if the source data changed. + */ + void samplesAvailable(); +private: + static std::unique_ptr convertData(const DSOsamples *data, const DsoSettingsScope *scope); + static void spectrumAnalysis(DataAnalyzerResult* result, + Dso::WindowFunction &lastWindow, + unsigned int lastRecordLength, + const DsoSettingsScope* scope); +private: + DsoSettingsScope* scope; + unsigned int lastRecordLength=0; ///< The record length of the previously analyzed data + Dso::WindowFunction lastWindow=(Dso::WindowFunction)-1; ///< The previously used dft window function + const DSOsamples *sourceData=nullptr; + std::unique_ptr lastResult; +signals: + void analyzed(); +}; diff --git a/openhantek/src/analyse/dataanalyzerresult.cpp b/openhantek/src/analyse/dataanalyzerresult.cpp new file mode 100644 index 0000000..8c1af5c --- /dev/null +++ b/openhantek/src/analyse/dataanalyzerresult.cpp @@ -0,0 +1,44 @@ +#include "dataanalyzerresult.h" +#include + +DataAnalyzerResult::DataAnalyzerResult(unsigned int channelCount) +{ + analyzedData.resize(channelCount); +} + +/// \brief Returns the analyzed data. +/// \param channel Channel, whose data should be returned. +/// \return Analyzed data as AnalyzedData struct. +const DataChannel *DataAnalyzerResult::data(int channel) const { + if (channel >= (int)this->analyzedData.size()) + return 0; + + return &this->analyzedData[(size_t)channel]; +} + +DataChannel *DataAnalyzerResult::modifyData(int channel) { + if (channel >= (int)this->analyzedData.size()) + throw new std::runtime_error("If you modfiy the DataAnalyzerResult, you need to set the channels first!"); + + return &this->analyzedData[(size_t)channel]; +} + +/// \brief Returns the sample count of the analyzed data. +/// \return The maximum sample count of the last analyzed data. +unsigned int DataAnalyzerResult::sampleCount() const { return this->maxSamples; } + +unsigned int DataAnalyzerResult::channelCount() const +{ + return analyzedData.size(); +} + +void DataAnalyzerResult::challengeMaxSamples(unsigned int newMaxSamples) +{ + if (newMaxSamples > this->maxSamples) + this->maxSamples = newMaxSamples; +} + +unsigned int DataAnalyzerResult::getMaxSamples() const +{ + return maxSamples; +} diff --git a/openhantek/src/analyse/dataanalyzerresult.h b/openhantek/src/analyse/dataanalyzerresult.h new file mode 100644 index 0000000..639d362 --- /dev/null +++ b/openhantek/src/analyse/dataanalyzerresult.h @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#pragma once + +#include + +//////////////////////////////////////////////////////////////////////////////// +/// \struct SampleValues dataanalyzer.h +/// \brief Struct for a array of sample values. +struct SampleValues { + std::vector sample; ///< Vector holding the sampling data + double interval=0.0; ///< The interval between two sample values +}; + +//////////////////////////////////////////////////////////////////////////////// +/// \struct AnalyzedData dataanalyzer.h +/// \brief Struct for the analyzed data. +struct DataChannel { + SampleValues voltage; ///< The time-domain voltage levels (V) + SampleValues spectrum; ///< The frequency-domain power levels (dB) + double amplitude = 0.0; ///< The amplitude of the signal + double frequency = 0.0; ///< The frequency of the signal +}; + +class DataAnalyzerResult +{ +public: + DataAnalyzerResult(unsigned int channelCount); + const DataChannel *data(int channel) const; + DataChannel *modifyData(int channel); + unsigned int sampleCount() const; + unsigned int channelCount() const; + double *window = nullptr; ///< The array for the dft window factors + + /** + * Applies a new maximum samples value, if the given value is higher than the already stored one + * @param newMaxSamples Maximum samples value + */ + void challengeMaxSamples(unsigned int newMaxSamples); + unsigned int getMaxSamples() const; +private: + std::vector analyzedData; ///< The analyzed data for each channel + unsigned int maxSamples = 0; ///< The maximum record length of the analyzed data +}; diff --git a/openhantek/src/dataanalyzer.cpp b/openhantek/src/dataanalyzer.cpp deleted file mode 100644 index e9894d3..0000000 --- a/openhantek/src/dataanalyzer.cpp +++ /dev/null @@ -1,483 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// -// OpenHantek -// dataanalyzer.cpp -// -// Copyright (C) 2010 Oliver Haag -// oliver.haag@gmail.com -// -// This program is free software: you can redistribute it and/or modify it -// under the terms of the GNU General Public License as published by the Free -// Software Foundation, either version 3 of the License, or (at your option) -// any later version. -// -// This program is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -// more details. -// -// You should have received a copy of the GNU General Public License along with -// this program. If not, see . -// -//////////////////////////////////////////////////////////////////////////////// - -#define _USE_MATH_DEFINES -#include - -#include -#include - -#include - -#include "dataanalyzer.h" - -#include "glscope.h" -#include "utils/printutils.h" -#include "settings.h" - -//////////////////////////////////////////////////////////////////////////////// -// struct SampleValues -/// \brief Initializes the members to their default values. -SampleValues::SampleValues() { this->interval = 0.0; } - -//////////////////////////////////////////////////////////////////////////////// -// struct AnalyzedData -/// \brief Initializes the members to their default values. -AnalyzedData::AnalyzedData() { - this->amplitude = 0.0; - this->frequency = 0.0; -} - -/// \brief Returns the analyzed data. -/// \param channel Channel, whose data should be returned. -/// \return Analyzed data as AnalyzedData struct. -AnalyzedData const *DataAnalyzer::data(unsigned int channel) const { - if (channel >= this->analyzedData.size()) - return 0; - - return &this->analyzedData[channel]; -} - -/// \brief Returns the sample count of the analyzed data. -/// \return The maximum sample count of the last analyzed data. -unsigned int DataAnalyzer::sampleCount() { return this->maxSamples; } - -QMutex *DataAnalyzer::mutex() const { return this->analyzedDataMutex; } - -void DataAnalyzer::transferData() { - QMutexLocker locker(waitingDataMutex); - QMutexLocker locker2(analyzedDataMutex); - - unsigned int maxSamples = 0; - unsigned int channelCount = - (unsigned int)scope->voltage.size(); - - // Adapt the number of channels for analyzed data - this->analyzedData.resize(channelCount); - - for (unsigned int channel = 0; channel < channelCount; ++channel) { - AnalyzedData *const channelData = &this->analyzedData[channel]; - - if ( // Check... - ( // ...if we got data for this channel... - channel < scope->physicalChannels && - channel < (unsigned int)this->waitingData->size() && - !this->waitingData->at(channel).empty()) || - ( // ...or if it's a math channel that can be calculated - channel >= scope->physicalChannels && - (scope->voltage[channel].used || - scope->spectrum[channel].used) && - this->analyzedData.size() >= 2 && - !this->analyzedData[0].samples.voltage.sample.empty() && - !this->analyzedData[1].samples.voltage.sample.empty())) { - // Set sampling interval - const double interval = 1.0 / this->waitingDataSamplerate; - if (interval != channelData->samples.voltage.interval) { - channelData->samples.voltage.interval = interval; - if (this->waitingDataAppend) // Clear roll buffer if the samplerate - // changed - channelData->samples.voltage.sample.clear(); - } - - unsigned int size; - if (channel < scope->physicalChannels) { - size = this->waitingData->at(channel).size(); - if (this->waitingDataAppend) - size += channelData->samples.voltage.sample.size(); - if (size > maxSamples) - maxSamples = size; - } else - size = maxSamples; - - // Physical channels - if (channel < scope->physicalChannels) { - // Copy the buffer of the oscilloscope into the sample buffer - if (this->waitingDataAppend) - channelData->samples.voltage.sample.insert( - channelData->samples.voltage.sample.end(), - this->waitingData->at(channel).begin(), - this->waitingData->at(channel).end()); - else - channelData->samples.voltage.sample = this->waitingData->at(channel); - } - // Math channel - else { - // Resize the sample vector - channelData->samples.voltage.sample.resize(size); - // Set sampling interval - this->analyzedData[scope->physicalChannels] - .samples.voltage.interval = - this->analyzedData[0].samples.voltage.interval; - - // Resize the sample vector - this->analyzedData[scope->physicalChannels] - .samples.voltage.sample.resize( - qMin(this->analyzedData[0].samples.voltage.sample.size(), - this->analyzedData[1].samples.voltage.sample.size())); - - // Calculate values and write them into the sample buffer - std::vector::const_iterator ch1Iterator = - this->analyzedData[0].samples.voltage.sample.begin(); - std::vector::const_iterator ch2Iterator = - this->analyzedData[1].samples.voltage.sample.begin(); - std::vector &resultData = - this->analyzedData[scope->physicalChannels] - .samples.voltage.sample; - for (std::vector::iterator resultIterator = resultData.begin(); - resultIterator != resultData.end(); ++resultIterator) { - switch (scope->voltage[scope->physicalChannels].misc) { - case Dso::MATHMODE_1ADD2: - *resultIterator = *ch1Iterator + *ch2Iterator; - break; - case Dso::MATHMODE_1SUB2: - *resultIterator = *ch1Iterator - *ch2Iterator; - break; - case Dso::MATHMODE_2SUB1: - *resultIterator = *ch2Iterator - *ch1Iterator; - break; - } - ++ch1Iterator; - ++ch2Iterator; - } - } - } else { - // Clear unused channels - channelData->samples.voltage.sample.clear(); - this->analyzedData[scope->physicalChannels] - .samples.voltage.interval = 0; - } - } -} - -/// \brief Analyzes the data from the dso. -void DataAnalyzer::run(DsoSettingsOptions* options,DsoSettingsScope* scope,DsoSettingsView* view) { - this->options=options; - this->scope=scope; - this->view=view; - - transferData(); - - // Lower priority for spectrum calculation - this->setPriority(QThread::LowPriority); - - QMutexLocker locker(analyzedDataMutex); - - // Calculate frequencies, peak-to-peak voltages and spectrums - for (unsigned int channel = 0; channel < this->analyzedData.size(); - ++channel) { - AnalyzedData *const channelData = &this->analyzedData[channel]; - - if (!channelData->samples.voltage.sample.empty()) { - // Calculate new window - unsigned int sampleCount = channelData->samples.voltage.sample.size(); - if (this->lastWindow != scope->spectrumWindow || - this->lastRecordLength != sampleCount) { - if (this->lastRecordLength != sampleCount) { - this->lastRecordLength = sampleCount; - - if (this->window) - fftw_free(this->window); - this->window = - (double *)fftw_malloc(sizeof(double) * this->lastRecordLength); - } - - unsigned int windowEnd = this->lastRecordLength - 1; - this->lastWindow = scope->spectrumWindow; - - switch (scope->spectrumWindow) { - case Dso::WINDOW_HAMMING: - for (unsigned int windowPosition = 0; - windowPosition < this->lastRecordLength; ++windowPosition) - *(this->window + windowPosition) = - 0.54 - 0.46 * cos(2.0 * M_PI * windowPosition / windowEnd); - break; - case Dso::WINDOW_HANN: - for (unsigned int windowPosition = 0; - windowPosition < this->lastRecordLength; ++windowPosition) - *(this->window + windowPosition) = - 0.5 * (1.0 - cos(2.0 * M_PI * windowPosition / windowEnd)); - break; - case Dso::WINDOW_COSINE: - for (unsigned int windowPosition = 0; - windowPosition < this->lastRecordLength; ++windowPosition) - *(this->window + windowPosition) = - sin(M_PI * windowPosition / windowEnd); - break; - case Dso::WINDOW_LANCZOS: - for (unsigned int windowPosition = 0; - windowPosition < this->lastRecordLength; ++windowPosition) { - double sincParameter = - (2.0 * windowPosition / windowEnd - 1.0) * M_PI; - if (sincParameter == 0) - *(this->window + windowPosition) = 1; - else - *(this->window + windowPosition) = - sin(sincParameter) / sincParameter; - } - break; - case Dso::WINDOW_BARTLETT: - for (unsigned int windowPosition = 0; - windowPosition < this->lastRecordLength; ++windowPosition) - *(this->window + windowPosition) = - 2.0 / windowEnd * - (windowEnd / 2 - - std::abs((double)(windowPosition - windowEnd / 2.0))); - break; - case Dso::WINDOW_TRIANGULAR: - for (unsigned int windowPosition = 0; - windowPosition < this->lastRecordLength; ++windowPosition) - *(this->window + windowPosition) = - 2.0 / this->lastRecordLength * - (this->lastRecordLength / 2 - - std::abs((double)(windowPosition - windowEnd / 2.0))); - break; - case Dso::WINDOW_GAUSS: { - double sigma = 0.4; - for (unsigned int windowPosition = 0; - windowPosition < this->lastRecordLength; ++windowPosition) - *(this->window + windowPosition) = - exp(-0.5 * pow(((windowPosition - windowEnd / 2) / - (sigma * windowEnd / 2)), - 2)); - } break; - case Dso::WINDOW_BARTLETTHANN: - for (unsigned int windowPosition = 0; - windowPosition < this->lastRecordLength; ++windowPosition) - *(this->window + windowPosition) = - 0.62 - - 0.48 * std::abs((double)(windowPosition / windowEnd - 0.5)) - - 0.38 * cos(2.0 * M_PI * windowPosition / windowEnd); - break; - case Dso::WINDOW_BLACKMAN: { - double alpha = 0.16; - for (unsigned int windowPosition = 0; - windowPosition < this->lastRecordLength; ++windowPosition) - *(this->window + windowPosition) = - (1 - alpha) / 2 - - 0.5 * cos(2.0 * M_PI * windowPosition / windowEnd) + - alpha / 2 * cos(4.0 * M_PI * windowPosition / windowEnd); - } break; - // case WINDOW_KAISER: - // TODO - // double alpha = 3.0; - // for(unsigned int windowPosition = 0; windowPosition < - // this->lastRecordLength; ++windowPosition) - //*(this->window + windowPosition) = ; - // break; - case Dso::WINDOW_NUTTALL: - for (unsigned int windowPosition = 0; - windowPosition < this->lastRecordLength; ++windowPosition) - *(this->window + windowPosition) = - 0.355768 - - 0.487396 * cos(2 * M_PI * windowPosition / windowEnd) + - 0.144232 * cos(4 * M_PI * windowPosition / windowEnd) - - 0.012604 * cos(6 * M_PI * windowPosition / windowEnd); - break; - case Dso::WINDOW_BLACKMANHARRIS: - for (unsigned int windowPosition = 0; - windowPosition < this->lastRecordLength; ++windowPosition) - *(this->window + windowPosition) = - 0.35875 - 0.48829 * cos(2 * M_PI * windowPosition / windowEnd) + - 0.14128 * cos(4 * M_PI * windowPosition / windowEnd) - - 0.01168 * cos(6 * M_PI * windowPosition / windowEnd); - break; - case Dso::WINDOW_BLACKMANNUTTALL: - for (unsigned int windowPosition = 0; - windowPosition < this->lastRecordLength; ++windowPosition) - *(this->window + windowPosition) = - 0.3635819 - - 0.4891775 * cos(2 * M_PI * windowPosition / windowEnd) + - 0.1365995 * cos(4 * M_PI * windowPosition / windowEnd) - - 0.0106411 * cos(6 * M_PI * windowPosition / windowEnd); - break; - case Dso::WINDOW_FLATTOP: - for (unsigned int windowPosition = 0; - windowPosition < this->lastRecordLength; ++windowPosition) - *(this->window + windowPosition) = - 1.0 - 1.93 * cos(2 * M_PI * windowPosition / windowEnd) + - 1.29 * cos(4 * M_PI * windowPosition / windowEnd) - - 0.388 * cos(6 * M_PI * windowPosition / windowEnd) + - 0.032 * cos(8 * M_PI * windowPosition / windowEnd); - break; - default: // Dso::WINDOW_RECTANGULAR - for (unsigned int windowPosition = 0; - windowPosition < this->lastRecordLength; ++windowPosition) - *(this->window + windowPosition) = 1.0; - } - } - - // Set sampling interval - channelData->samples.spectrum.interval = - 1.0 / channelData->samples.voltage.interval / sampleCount; - - // Number of real/complex samples - unsigned int dftLength = sampleCount / 2; - - // Reallocate memory for samples if the sample count has changed - channelData->samples.spectrum.sample.resize(sampleCount); - - // Create sample buffer and apply window - double *windowedValues = new double[sampleCount]; - for (unsigned int position = 0; position < sampleCount; ++position) - windowedValues[position] = - this->window[position] * - channelData->samples.voltage.sample[position]; - - // Do discrete real to half-complex transformation - /// \todo Check if record length is multiple of 2 - /// \todo Reuse plan and use FFTW_MEASURE to get fastest algorithm - fftw_plan fftPlan = - fftw_plan_r2r_1d(sampleCount, windowedValues, - &channelData->samples.spectrum.sample.front(), - FFTW_R2HC, FFTW_ESTIMATE); - fftw_execute(fftPlan); - fftw_destroy_plan(fftPlan); - - // Do an autocorrelation to get the frequency of the signal - double *conjugateComplex = - windowedValues; // Reuse the windowedValues buffer - - // Real values - unsigned int position; - double correctionFactor = 1.0 / dftLength / dftLength; - conjugateComplex[0] = (channelData->samples.spectrum.sample[0] * - channelData->samples.spectrum.sample[0]) * - correctionFactor; - for (position = 1; position < dftLength; ++position) - conjugateComplex[position] = - (channelData->samples.spectrum.sample[position] * - channelData->samples.spectrum.sample[position] + - channelData->samples.spectrum.sample[sampleCount - position] * - channelData->samples.spectrum.sample[sampleCount - position]) * - correctionFactor; - // Complex values, all zero for autocorrelation - conjugateComplex[dftLength] = - (channelData->samples.spectrum.sample[dftLength] * - channelData->samples.spectrum.sample[dftLength]) * - correctionFactor; - for (++position; position < sampleCount; ++position) - conjugateComplex[position] = 0; - - // Do half-complex to real inverse transformation - double *correlation = new double[sampleCount]; - fftPlan = fftw_plan_r2r_1d(sampleCount, conjugateComplex, correlation, - FFTW_HC2R, FFTW_ESTIMATE); - fftw_execute(fftPlan); - fftw_destroy_plan(fftPlan); - delete[] conjugateComplex; - - // Calculate peak-to-peak voltage - double minimalVoltage, maximalVoltage; - minimalVoltage = maximalVoltage = channelData->samples.voltage.sample[0]; - - for (unsigned int position = 1; position < sampleCount; ++position) { - if (channelData->samples.voltage.sample[position] < minimalVoltage) - minimalVoltage = channelData->samples.voltage.sample[position]; - else if (channelData->samples.voltage.sample[position] > maximalVoltage) - maximalVoltage = channelData->samples.voltage.sample[position]; - } - - channelData->amplitude = maximalVoltage - minimalVoltage; - - // Get the frequency from the correlation results - double minimumCorrelation = correlation[0]; - double peakCorrelation = 0; - unsigned int peakPosition = 0; - - for (unsigned int position = 1; position < sampleCount / 2; ++position) { - if (correlation[position] > peakCorrelation && - correlation[position] > minimumCorrelation * 2) { - peakCorrelation = correlation[position]; - peakPosition = position; - } else if (correlation[position] < minimumCorrelation) - minimumCorrelation = correlation[position]; - } - delete[] correlation; - - // Calculate the frequency in Hz - if (peakPosition) - channelData->frequency = - 1.0 / (channelData->samples.voltage.interval * peakPosition); - else - channelData->frequency = 0; - - // Finally calculate the real spectrum if we want it - if (scope->spectrum[channel].used) { - // Convert values into dB (Relative to the reference level) - double offset = 60 - scope->spectrumReference - - 20 * log10(dftLength); - double offsetLimit = scope->spectrumLimit - - scope->spectrumReference; - for (std::vector::iterator spectrumIterator = - channelData->samples.spectrum.sample.begin(); - spectrumIterator != channelData->samples.spectrum.sample.end(); - ++spectrumIterator) { - double value = 20 * log10(fabs(*spectrumIterator)) + offset; - - // Check if this value has to be limited - if (offsetLimit > value) - value = offsetLimit; - - *spectrumIterator = value; - } - } - } else if (!channelData->samples.spectrum.sample.empty()) { - // Clear unused channels - channelData->samples.spectrum.interval = 0; - channelData->samples.spectrum.sample.clear(); - } - } - - this->maxSamples = maxSamples; - emit analyzed(maxSamples); -} - -/// \brief Starts the analyzing of new input data. -/// \param data The data arrays with the input data. -/// \param size The sizes of the data arrays. -/// \param samplerate The samplerate for all input data. -/// \param append The data will be appended to the previously analyzed data -/// (Roll mode). -/// \param mutex The mutex for all input data. -void DataAnalyzer::analyze(const std::vector> *data, - double samplerate, bool append, QMutex *mutex) { - // Previous analysis still running, drop the new data - if (this->isRunning()) { -#ifdef DEBUG - timestampDebug("Analyzer overload, dropping packets!"); -#endif - return; - } - - // The thread will analyze it, just save the pointers - this->waitingData = data; - this->waitingDataAppend = append; - this->waitingDataMutex = mutex; - this->waitingDataSamplerate = samplerate; - this->start(); -#ifdef DEBUG - static unsigned long id = 0; - ++id; - timestampDebug(QString("Analyzed packet %1").arg(id)); -#endif -} diff --git a/openhantek/src/dataanalyzer.h b/openhantek/src/dataanalyzer.h deleted file mode 100644 index 701b3f2..0000000 --- a/openhantek/src/dataanalyzer.h +++ /dev/null @@ -1,113 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// -// OpenHantek -/// \file dataanalyzer.h -/// \brief Declares the DataAnalyzer class. -// -// Copyright (C) 2010 Oliver Haag -// oliver.haag@gmail.com -// -// This program is free software: you can redistribute it and/or modify it -// under the terms of the GNU General Public License as published by the Free -// Software Foundation, either version 3 of the License, or (at your option) -// any later version. -// -// This program is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -// more details. -// -// You should have received a copy of the GNU General Public License along with -// this program. If not, see . -// -//////////////////////////////////////////////////////////////////////////////// - -#ifndef DATAANALYZER_H -#define DATAANALYZER_H - -#include - -#include -#include - -#include "definitions.h" -#include "utils/printutils.h" - -struct DsoSettingsOptions; -struct DsoSettingsScope; -struct DsoSettingsView; -//////////////////////////////////////////////////////////////////////////////// -/// \struct SampleValues dataanalyzer.h -/// \brief Struct for a array of sample values. -struct SampleValues { - std::vector sample; ///< Vector holding the sampling data - double interval; ///< The interval between two sample values - - SampleValues(); -}; - -//////////////////////////////////////////////////////////////////////////////// -/// \struct SampleData dataanalyzer.h -/// \brief Struct for the sample value arrays. -struct SampleData { - SampleValues voltage; ///< The time-domain voltage levels (V) - SampleValues spectrum; ///< The frequency-domain power levels (dB) -}; - -//////////////////////////////////////////////////////////////////////////////// -/// \struct AnalyzedData dataanalyzer.h -/// \brief Struct for the analyzed data. -struct AnalyzedData { - SampleData samples; ///< Voltage and spectrum values - double amplitude; ///< The amplitude of the signal - double frequency; ///< The frequency of the signal - - AnalyzedData(); -}; - -//////////////////////////////////////////////////////////////////////////////// -/// \class DataAnalyzer dataanalyzer.h -/// \brief Analyzes the data from the dso. -/// Calculates the spectrum and various data about the signal and saves the -/// time-/frequencysteps between two values. -class DataAnalyzer : public QThread { - Q_OBJECT - -public: - const AnalyzedData *data(unsigned int channel) const; - unsigned int sampleCount(); - /// \brief Returns the mutex for the data. - /// \return Mutex for the analyzed data. - QMutex *mutex() const; - -protected: - void run(DsoSettingsOptions* options,DsoSettingsScope* scope,DsoSettingsView* view); - void transferData(); - - DsoSettingsOptions* options; ///< General options of the program - DsoSettingsScope* scope; ///< All oscilloscope related settings - DsoSettingsView* view; ///< All view related settings - - std::vector analyzedData; ///< The analyzed data for each channel - QMutex *analyzedDataMutex= new QMutex(); ///< A mutex for the analyzed data of all channels - - unsigned int lastRecordLength=0; ///< The record length of the previously analyzed data - unsigned int maxSamples=0; ///< The maximum record length of the analyzed data - Dso::WindowFunction lastWindow=(Dso::WindowFunction)-1; ///< The previously used dft window function - double *window=nullptr; ///< The array for the dft window factors - - const std::vector> *waitingData; ///< Pointer to input data from device - double waitingDataSamplerate=0.0; ///< The samplerate of the input data - bool waitingDataAppend; ///< true, if waiting data should be appended - QMutex *waitingDataMutex=nullptr; ///< A mutex for the input data - -public slots: - void analyze(const std::vector> *data, double samplerate, - bool append, QMutex *mutex); - -signals: - void analyzed(unsigned long samples); ///< The data with that much samples has - ///been analyzed -}; - -#endif diff --git a/openhantek/src/dsowidget.cpp b/openhantek/src/dsowidget.cpp index cd28612..d993d31 100644 --- a/openhantek/src/dsowidget.cpp +++ b/openhantek/src/dsowidget.cpp @@ -1,25 +1,4 @@ -//////////////////////////////////////////////////////////////////////////////// -// -// OpenHantek -// dsowidget.cpp -// -// Copyright (C) 2010 Oliver Haag -// oliver.haag@gmail.com -// -// This program is free software: you can redistribute it and/or modify it -// under the terms of the GNU General Public License as published by the Free -// Software Foundation, either version 3 of the License, or (at your option) -// any later version. -// -// This program is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -// more details. -// -// You should have received a copy of the GNU General Public License along with -// this program. If not, see . -// -//////////////////////////////////////////////////////////////////////////////// +// SPDX-License-Identifier: GPL-2.0+ #include @@ -33,437 +12,413 @@ #include "hantek/definitions.h" #include "utils/printutils.h" #include "utils/dsoStrings.h" -#include "dataanalyzer.h" #include "exporter.h" #include "glscope.h" -#include "levelslider.h" +#include "widgets/levelslider.h" #include "settings.h" -//////////////////////////////////////////////////////////////////////////////// -// class DsoWidget -/// \brief Initializes the components of the oszilloscope-screen. -/// \param settings The settings object containing the oscilloscope settings. -/// \param dataAnalyzer The data analyzer that should be used as data source. -/// \param parent The parent widget. -/// \param flags Flags for the window manager. -DsoWidget::DsoWidget(DsoSettings *settings, DataAnalyzer *dataAnalyzer, - QWidget *parent, Qt::WindowFlags flags) - : QWidget(parent, flags) { - this->settings = settings; - this->dataAnalyzer = dataAnalyzer; - - // Palette for this widget - QPalette palette; - palette.setColor(QPalette::Background, - this->settings->view.color.screen.background); - palette.setColor(QPalette::WindowText, - this->settings->view.color.screen.text); - - // The OpenGL accelerated scope widgets - this->generator = new GlGenerator(this->settings, this); - this->generator->setDataAnalyzer(this->dataAnalyzer); - this->mainScope = new GlScope(this->settings); - this->mainScope->setGenerator(this->generator); - this->zoomScope = new GlScope(this->settings); - this->zoomScope->setGenerator(this->generator); - this->zoomScope->setZoomMode(true); - -#ifdef OS_DARWIN - // Workaround for https://bugreports.qt-project.org/browse/QTBUG-8580 - this->mainScope->hide(); - this->mainScope->show(); -#endif - - // The offset sliders for all possible channels - this->offsetSlider = new LevelSlider(Qt::RightArrow); - for (int channel = 0; channel < this->settings->scope.voltage.count(); - ++channel) { - this->offsetSlider->addSlider(this->settings->scope.voltage[channel].name, - channel); - this->offsetSlider->setColor( - channel, this->settings->view.color.screen.voltage[channel]); - this->offsetSlider->setLimits(channel, -DIVS_VOLTAGE / 2, DIVS_VOLTAGE / 2); - this->offsetSlider->setStep(channel, 0.2); - this->offsetSlider->setValue(channel, - this->settings->scope.voltage[channel].offset); - this->offsetSlider->setVisible(channel, - this->settings->scope.voltage[channel].used); - } - for (int channel = 0; channel < this->settings->scope.voltage.count(); - ++channel) { - this->offsetSlider->addSlider(this->settings->scope.spectrum[channel].name, - this->settings->scope.voltage.count() + +DsoWidget::DsoWidget(DsoSettings *settings, QWidget *parent, Qt::WindowFlags flags) + : QWidget(parent, flags), settings(settings), + generator(new GlGenerator(&settings->scope, &settings->view)), + mainScope(new GlScope(settings, generator)), + zoomScope(new GlScope(settings, generator)) { + + // Palette for this widget + QPalette palette; + palette.setColor(QPalette::Background, this->settings->view.color.screen.background); + palette.setColor(QPalette::WindowText, this->settings->view.color.screen.text); + + // The OpenGL accelerated scope widgets + this->zoomScope->setZoomMode(true); + + // The offset sliders for all possible channels + this->offsetSlider = new LevelSlider(Qt::RightArrow); + for (int channel = 0; channel < this->settings->scope.voltage.count(); + ++channel) { + this->offsetSlider->addSlider(this->settings->scope.voltage[channel].name, channel); - this->offsetSlider->setColor( - this->settings->scope.voltage.count() + channel, - this->settings->view.color.screen.spectrum[channel]); - this->offsetSlider->setLimits(this->settings->scope.voltage.count() + + this->offsetSlider->setColor( + channel, this->settings->view.color.screen.voltage[channel]); + this->offsetSlider->setLimits(channel, -DIVS_VOLTAGE / 2, DIVS_VOLTAGE / 2); + this->offsetSlider->setStep(channel, 0.2); + this->offsetSlider->setValue(channel, + this->settings->scope.voltage[channel].offset); + this->offsetSlider->setVisible(channel, + this->settings->scope.voltage[channel].used); + } + for (int channel = 0; channel < this->settings->scope.voltage.count(); + ++channel) { + this->offsetSlider->addSlider(this->settings->scope.spectrum[channel].name, + this->settings->scope.voltage.count() + + channel); + this->offsetSlider->setColor( + this->settings->scope.voltage.count() + channel, + this->settings->view.color.screen.spectrum[channel]); + this->offsetSlider->setLimits(this->settings->scope.voltage.count() + channel, - -DIVS_VOLTAGE / 2, DIVS_VOLTAGE / 2); - this->offsetSlider->setStep(this->settings->scope.voltage.count() + channel, - 0.2); - this->offsetSlider->setValue( - this->settings->scope.voltage.count() + channel, - this->settings->scope.spectrum[channel].offset); - this->offsetSlider->setVisible( - this->settings->scope.voltage.count() + channel, - this->settings->scope.spectrum[channel].used); - } - - // The triggerPosition slider - this->triggerPositionSlider = new LevelSlider(Qt::DownArrow); - this->triggerPositionSlider->addSlider(); - this->triggerPositionSlider->setLimits(0, 0.0, 1.0); - this->triggerPositionSlider->setStep(0, 0.2 / DIVS_TIME); - this->triggerPositionSlider->setValue(0, - this->settings->scope.trigger.position); - this->triggerPositionSlider->setVisible(0, true); - - // The sliders for the trigger levels - this->triggerLevelSlider = new LevelSlider(Qt::LeftArrow); - for (int channel = 0; channel < (int)this->settings->scope.physicalChannels; - ++channel) { - this->triggerLevelSlider->addSlider(channel); - this->triggerLevelSlider->setColor( - channel, - (!this->settings->scope.trigger.special && - channel == (int)this->settings->scope.trigger.source) - ? this->settings->view.color.screen.voltage[channel] - : this->settings->view.color.screen.voltage[channel].darker()); - this->adaptTriggerLevelSlider(channel); - this->triggerLevelSlider->setValue( - channel, this->settings->scope.voltage[channel].trigger); - this->triggerLevelSlider->setVisible( - channel, this->settings->scope.voltage[channel].used); - } - - // The marker slider - this->markerSlider = new LevelSlider(Qt::UpArrow); - for (int marker = 0; marker < MARKER_COUNT; ++marker) { - this->markerSlider->addSlider(QString::number(marker + 1), marker); - this->markerSlider->setLimits(marker, -DIVS_TIME / 2, DIVS_TIME / 2); - this->markerSlider->setStep(marker, 0.2); - this->markerSlider->setValue( - marker, this->settings->scope.horizontal.marker[marker]); - this->markerSlider->setVisible(marker, true); - this->settings->scope.horizontal.marker_visible[marker] = true; - } - - // The table for the settings - this->settingsTriggerLabel = new QLabel(); - this->settingsTriggerLabel->setMinimumWidth(160); - this->settingsRecordLengthLabel = new QLabel(); - this->settingsRecordLengthLabel->setAlignment(Qt::AlignRight); - this->settingsRecordLengthLabel->setPalette(palette); - this->settingsSamplerateLabel = new QLabel(); - this->settingsSamplerateLabel->setAlignment(Qt::AlignRight); - this->settingsSamplerateLabel->setPalette(palette); - this->settingsTimebaseLabel = new QLabel(); - this->settingsTimebaseLabel->setAlignment(Qt::AlignRight); - this->settingsTimebaseLabel->setPalette(palette); - this->settingsFrequencybaseLabel = new QLabel(); - this->settingsFrequencybaseLabel->setAlignment(Qt::AlignRight); - this->settingsFrequencybaseLabel->setPalette(palette); - this->settingsLayout = new QHBoxLayout(); - this->settingsLayout->addWidget(this->settingsTriggerLabel); - this->settingsLayout->addWidget(this->settingsRecordLengthLabel, 1); - this->settingsLayout->addWidget(this->settingsSamplerateLabel, 1); - this->settingsLayout->addWidget(this->settingsTimebaseLabel, 1); - this->settingsLayout->addWidget(this->settingsFrequencybaseLabel, 1); - - // The table for the marker details - this->markerInfoLabel = new QLabel(); - this->markerInfoLabel->setMinimumWidth(160); - this->markerInfoLabel->setPalette(palette); - this->markerTimeLabel = new QLabel(); - this->markerTimeLabel->setAlignment(Qt::AlignRight); - this->markerTimeLabel->setPalette(palette); - this->markerFrequencyLabel = new QLabel(); - this->markerFrequencyLabel->setAlignment(Qt::AlignRight); - this->markerFrequencyLabel->setPalette(palette); - this->markerTimebaseLabel = new QLabel(); - this->markerTimebaseLabel->setAlignment(Qt::AlignRight); - this->markerTimebaseLabel->setPalette(palette); - this->markerFrequencybaseLabel = new QLabel(); - this->markerFrequencybaseLabel->setAlignment(Qt::AlignRight); - this->markerFrequencybaseLabel->setPalette(palette); - this->markerLayout = new QHBoxLayout(); - this->markerLayout->addWidget(this->markerInfoLabel); - this->markerLayout->addWidget(this->markerTimeLabel, 1); - this->markerLayout->addWidget(this->markerFrequencyLabel, 1); - this->markerLayout->addWidget(this->markerTimebaseLabel, 1); - this->markerLayout->addWidget(this->markerFrequencybaseLabel, 1); - - // The table for the measurements - QPalette tablePalette = palette; - this->measurementLayout = new QGridLayout(); - this->measurementLayout->setColumnMinimumWidth(0, 64); - this->measurementLayout->setColumnMinimumWidth(1, 32); - this->measurementLayout->setColumnStretch(2, 2); - this->measurementLayout->setColumnStretch(3, 2); - this->measurementLayout->setColumnStretch(4, 3); - this->measurementLayout->setColumnStretch(5, 3); - for (int channel = 0; channel < this->settings->scope.voltage.count(); - ++channel) { - tablePalette.setColor(QPalette::WindowText, - this->settings->view.color.screen.voltage[channel]); - this->measurementNameLabel.append( - new QLabel(this->settings->scope.voltage[channel].name)); - this->measurementNameLabel[channel]->setPalette(tablePalette); - this->measurementMiscLabel.append(new QLabel()); - this->measurementMiscLabel[channel]->setPalette(tablePalette); - this->measurementGainLabel.append(new QLabel()); - this->measurementGainLabel[channel]->setAlignment(Qt::AlignRight); - this->measurementGainLabel[channel]->setPalette(tablePalette); - tablePalette.setColor(QPalette::WindowText, - this->settings->view.color.screen.spectrum[channel]); - this->measurementMagnitudeLabel.append(new QLabel()); - this->measurementMagnitudeLabel[channel]->setAlignment(Qt::AlignRight); - this->measurementMagnitudeLabel[channel]->setPalette(tablePalette); - this->measurementAmplitudeLabel.append(new QLabel()); - this->measurementAmplitudeLabel[channel]->setAlignment(Qt::AlignRight); - this->measurementAmplitudeLabel[channel]->setPalette(palette); - this->measurementFrequencyLabel.append(new QLabel()); - this->measurementFrequencyLabel[channel]->setAlignment(Qt::AlignRight); - this->measurementFrequencyLabel[channel]->setPalette(palette); - this->setMeasurementVisible(channel, - this->settings->scope.voltage[channel].used); - this->measurementLayout->addWidget(this->measurementNameLabel[channel], - channel, 0); - this->measurementLayout->addWidget(this->measurementMiscLabel[channel], - channel, 1); - this->measurementLayout->addWidget(this->measurementGainLabel[channel], - channel, 2); - this->measurementLayout->addWidget(this->measurementMagnitudeLabel[channel], - channel, 3); - this->measurementLayout->addWidget(this->measurementAmplitudeLabel[channel], - channel, 4); - this->measurementLayout->addWidget(this->measurementFrequencyLabel[channel], - channel, 5); - if ((unsigned int)channel < this->settings->scope.physicalChannels) - this->updateVoltageCoupling(channel); - else - this->updateMathMode(); - this->updateVoltageDetails(channel); - this->updateSpectrumDetails(channel); - } - - // The layout for the widgets - this->mainLayout = new QGridLayout(); - this->mainLayout->setColumnStretch(2, 1); // Scopes increase their size - this->mainLayout->setRowStretch(3, 1); - // Bars around the scope, needed because the slider-drawing-area is outside - // the scope at min/max - this->mainLayout->setColumnMinimumWidth( - 1, this->triggerPositionSlider->preMargin()); - this->mainLayout->setColumnMinimumWidth( - 3, this->triggerPositionSlider->postMargin()); - this->mainLayout->setRowMinimumHeight(2, this->offsetSlider->preMargin()); - this->mainLayout->setRowMinimumHeight(4, this->offsetSlider->postMargin()); - this->mainLayout->setRowMinimumHeight(6, 4); - this->mainLayout->setRowMinimumHeight(8, 4); - this->mainLayout->setRowMinimumHeight(10, 8); - this->mainLayout->setSpacing(0); - this->mainLayout->addLayout(this->settingsLayout, 0, 0, 1, 5); - this->mainLayout->addWidget(this->mainScope, 3, 2); - this->mainLayout->addWidget(this->offsetSlider, 2, 0, 3, 2, Qt::AlignRight); - this->mainLayout->addWidget(this->triggerPositionSlider, 1, 1, 2, 3, - Qt::AlignBottom); - this->mainLayout->addWidget(this->triggerLevelSlider, 2, 3, 3, 2, - Qt::AlignLeft); - this->mainLayout->addWidget(this->markerSlider, 4, 1, 2, 3, Qt::AlignTop); - this->mainLayout->addLayout(this->markerLayout, 7, 0, 1, 5); - this->mainLayout->addWidget(this->zoomScope, 9, 2); - this->mainLayout->addLayout(this->measurementLayout, 11, 0, 1, 5); - - // Apply settings and update measured values - this->updateTriggerDetails(); - this->updateRecordLength(this->settings->scope.horizontal.recordLength); - this->updateFrequencybase(this->settings->scope.horizontal.frequencybase); - this->updateSamplerate(this->settings->scope.horizontal.samplerate); - this->updateTimebase(this->settings->scope.horizontal.timebase); - this->updateZoom(this->settings->view.zoom); - - // The widget itself - this->setPalette(palette); - this->setBackgroundRole(QPalette::Background); - this->setAutoFillBackground(true); - this->setLayout(this->mainLayout); - - // Connect change-signals of sliders - this->connect(this->offsetSlider, SIGNAL(valueChanged(int, double)), this, - SLOT(updateOffset(int, double))); - this->connect(this->triggerPositionSlider, SIGNAL(valueChanged(int, double)), - this, SLOT(updateTriggerPosition(int, double))); - this->connect(this->triggerLevelSlider, SIGNAL(valueChanged(int, double)), - this, SLOT(updateTriggerLevel(int, double))); - this->connect(this->markerSlider, SIGNAL(valueChanged(int, double)), this, - SLOT(updateMarker(int, double))); - this->connect(this->markerSlider, SIGNAL(valueChanged(int, double)), - this->mainScope, SLOT(update())); - this->connect(this->markerSlider, SIGNAL(valueChanged(int, double)), - this->zoomScope, SLOT(update())); - - // Connect other signals - this->connect(this->dataAnalyzer, SIGNAL(analyzed(unsigned long)), this, - SLOT(dataAnalyzed())); - this->connect(this->dataAnalyzer, SIGNAL(analyzed(unsigned long)), this, - SLOT(updateRecordLength(unsigned long))); + -DIVS_VOLTAGE / 2, DIVS_VOLTAGE / 2); + this->offsetSlider->setStep(this->settings->scope.voltage.count() + channel, + 0.2); + this->offsetSlider->setValue( + this->settings->scope.voltage.count() + channel, + this->settings->scope.spectrum[channel].offset); + this->offsetSlider->setVisible( + this->settings->scope.voltage.count() + channel, + this->settings->scope.spectrum[channel].used); + } + + // The triggerPosition slider + this->triggerPositionSlider = new LevelSlider(Qt::DownArrow); + this->triggerPositionSlider->addSlider(); + this->triggerPositionSlider->setLimits(0, 0.0, 1.0); + this->triggerPositionSlider->setStep(0, 0.2 / DIVS_TIME); + this->triggerPositionSlider->setValue(0, + this->settings->scope.trigger.position); + this->triggerPositionSlider->setVisible(0, true); + + // The sliders for the trigger levels + this->triggerLevelSlider = new LevelSlider(Qt::LeftArrow); + for (int channel = 0; channel < (int)this->settings->scope.physicalChannels; + ++channel) { + this->triggerLevelSlider->addSlider(channel); + this->triggerLevelSlider->setColor( + channel, + (!this->settings->scope.trigger.special && + channel == (int)this->settings->scope.trigger.source) + ? this->settings->view.color.screen.voltage[channel] + : this->settings->view.color.screen.voltage[channel].darker()); + this->adaptTriggerLevelSlider(channel); + this->triggerLevelSlider->setValue( + channel, this->settings->scope.voltage[channel].trigger); + this->triggerLevelSlider->setVisible( + channel, this->settings->scope.voltage[channel].used); + } + + // The marker slider + this->markerSlider = new LevelSlider(Qt::UpArrow); + for (int marker = 0; marker < MARKER_COUNT; ++marker) { + this->markerSlider->addSlider(QString::number(marker + 1), marker); + this->markerSlider->setLimits(marker, -DIVS_TIME / 2, DIVS_TIME / 2); + this->markerSlider->setStep(marker, 0.2); + this->markerSlider->setValue( + marker, this->settings->scope.horizontal.marker[marker]); + this->markerSlider->setVisible(marker, true); + this->settings->scope.horizontal.marker_visible[marker] = true; + } + + // The table for the settings + this->settingsTriggerLabel = new QLabel(); + this->settingsTriggerLabel->setMinimumWidth(160); + this->settingsRecordLengthLabel = new QLabel(); + this->settingsRecordLengthLabel->setAlignment(Qt::AlignRight); + this->settingsRecordLengthLabel->setPalette(palette); + this->settingsSamplerateLabel = new QLabel(); + this->settingsSamplerateLabel->setAlignment(Qt::AlignRight); + this->settingsSamplerateLabel->setPalette(palette); + this->settingsTimebaseLabel = new QLabel(); + this->settingsTimebaseLabel->setAlignment(Qt::AlignRight); + this->settingsTimebaseLabel->setPalette(palette); + this->settingsFrequencybaseLabel = new QLabel(); + this->settingsFrequencybaseLabel->setAlignment(Qt::AlignRight); + this->settingsFrequencybaseLabel->setPalette(palette); + this->settingsLayout = new QHBoxLayout(); + this->settingsLayout->addWidget(this->settingsTriggerLabel); + this->settingsLayout->addWidget(this->settingsRecordLengthLabel, 1); + this->settingsLayout->addWidget(this->settingsSamplerateLabel, 1); + this->settingsLayout->addWidget(this->settingsTimebaseLabel, 1); + this->settingsLayout->addWidget(this->settingsFrequencybaseLabel, 1); + + // The table for the marker details + this->markerInfoLabel = new QLabel(); + this->markerInfoLabel->setMinimumWidth(160); + this->markerInfoLabel->setPalette(palette); + this->markerTimeLabel = new QLabel(); + this->markerTimeLabel->setAlignment(Qt::AlignRight); + this->markerTimeLabel->setPalette(palette); + this->markerFrequencyLabel = new QLabel(); + this->markerFrequencyLabel->setAlignment(Qt::AlignRight); + this->markerFrequencyLabel->setPalette(palette); + this->markerTimebaseLabel = new QLabel(); + this->markerTimebaseLabel->setAlignment(Qt::AlignRight); + this->markerTimebaseLabel->setPalette(palette); + this->markerFrequencybaseLabel = new QLabel(); + this->markerFrequencybaseLabel->setAlignment(Qt::AlignRight); + this->markerFrequencybaseLabel->setPalette(palette); + this->markerLayout = new QHBoxLayout(); + this->markerLayout->addWidget(this->markerInfoLabel); + this->markerLayout->addWidget(this->markerTimeLabel, 1); + this->markerLayout->addWidget(this->markerFrequencyLabel, 1); + this->markerLayout->addWidget(this->markerTimebaseLabel, 1); + this->markerLayout->addWidget(this->markerFrequencybaseLabel, 1); + + // The table for the measurements + QPalette tablePalette = palette; + this->measurementLayout = new QGridLayout(); + this->measurementLayout->setColumnMinimumWidth(0, 64); + this->measurementLayout->setColumnMinimumWidth(1, 32); + this->measurementLayout->setColumnStretch(2, 2); + this->measurementLayout->setColumnStretch(3, 2); + this->measurementLayout->setColumnStretch(4, 3); + this->measurementLayout->setColumnStretch(5, 3); + for (int channel = 0; channel < this->settings->scope.voltage.count(); + ++channel) { + tablePalette.setColor(QPalette::WindowText, + this->settings->view.color.screen.voltage[channel]); + this->measurementNameLabel.append( + new QLabel(this->settings->scope.voltage[channel].name)); + this->measurementNameLabel[channel]->setPalette(tablePalette); + this->measurementMiscLabel.append(new QLabel()); + this->measurementMiscLabel[channel]->setPalette(tablePalette); + this->measurementGainLabel.append(new QLabel()); + this->measurementGainLabel[channel]->setAlignment(Qt::AlignRight); + this->measurementGainLabel[channel]->setPalette(tablePalette); + tablePalette.setColor(QPalette::WindowText, + this->settings->view.color.screen.spectrum[channel]); + this->measurementMagnitudeLabel.append(new QLabel()); + this->measurementMagnitudeLabel[channel]->setAlignment(Qt::AlignRight); + this->measurementMagnitudeLabel[channel]->setPalette(tablePalette); + this->measurementAmplitudeLabel.append(new QLabel()); + this->measurementAmplitudeLabel[channel]->setAlignment(Qt::AlignRight); + this->measurementAmplitudeLabel[channel]->setPalette(palette); + this->measurementFrequencyLabel.append(new QLabel()); + this->measurementFrequencyLabel[channel]->setAlignment(Qt::AlignRight); + this->measurementFrequencyLabel[channel]->setPalette(palette); + this->setMeasurementVisible(channel, + this->settings->scope.voltage[channel].used); + this->measurementLayout->addWidget(this->measurementNameLabel[channel], + channel, 0); + this->measurementLayout->addWidget(this->measurementMiscLabel[channel], + channel, 1); + this->measurementLayout->addWidget(this->measurementGainLabel[channel], + channel, 2); + this->measurementLayout->addWidget(this->measurementMagnitudeLabel[channel], + channel, 3); + this->measurementLayout->addWidget(this->measurementAmplitudeLabel[channel], + channel, 4); + this->measurementLayout->addWidget(this->measurementFrequencyLabel[channel], + channel, 5); + if ((unsigned int)channel < this->settings->scope.physicalChannels) + this->updateVoltageCoupling(channel); + else + this->updateMathMode(); + this->updateVoltageDetails(channel); + this->updateSpectrumDetails(channel); + } + + // The layout for the widgets + this->mainLayout = new QGridLayout(); + this->mainLayout->setColumnStretch(2, 1); // Scopes increase their size + this->mainLayout->setRowStretch(3, 1); + // Bars around the scope, needed because the slider-drawing-area is outside + // the scope at min/max + this->mainLayout->setColumnMinimumWidth( + 1, this->triggerPositionSlider->preMargin()); + this->mainLayout->setColumnMinimumWidth( + 3, this->triggerPositionSlider->postMargin()); + this->mainLayout->setRowMinimumHeight(2, this->offsetSlider->preMargin()); + this->mainLayout->setRowMinimumHeight(4, this->offsetSlider->postMargin()); + this->mainLayout->setRowMinimumHeight(6, 4); + this->mainLayout->setRowMinimumHeight(8, 4); + this->mainLayout->setRowMinimumHeight(10, 8); + this->mainLayout->setSpacing(0); + this->mainLayout->addLayout(this->settingsLayout, 0, 0, 1, 5); + this->mainLayout->addWidget(this->mainScope, 3, 2); + this->mainLayout->addWidget(this->offsetSlider, 2, 0, 3, 2, Qt::AlignRight); + this->mainLayout->addWidget(this->triggerPositionSlider, 1, 1, 2, 3, + Qt::AlignBottom); + this->mainLayout->addWidget(this->triggerLevelSlider, 2, 3, 3, 2, + Qt::AlignLeft); + this->mainLayout->addWidget(this->markerSlider, 4, 1, 2, 3, Qt::AlignTop); + this->mainLayout->addLayout(this->markerLayout, 7, 0, 1, 5); + this->mainLayout->addWidget(this->zoomScope, 9, 2); + this->mainLayout->addLayout(this->measurementLayout, 11, 0, 1, 5); + + // Apply settings and update measured values + this->updateTriggerDetails(); + this->updateRecordLength(this->settings->scope.horizontal.recordLength); + this->updateFrequencybase(this->settings->scope.horizontal.frequencybase); + this->updateSamplerate(this->settings->scope.horizontal.samplerate); + this->updateTimebase(this->settings->scope.horizontal.timebase); + this->updateZoom(this->settings->view.zoom); + + // The widget itself + this->setPalette(palette); + this->setBackgroundRole(QPalette::Background); + this->setAutoFillBackground(true); + this->setLayout(this->mainLayout); + + // Connect change-signals of sliders + this->connect(this->offsetSlider, SIGNAL(valueChanged(int, double)), this, + SLOT(updateOffset(int, double))); + this->connect(this->triggerPositionSlider, SIGNAL(valueChanged(int, double)), + this, SLOT(updateTriggerPosition(int, double))); + this->connect(this->triggerLevelSlider, SIGNAL(valueChanged(int, double)), + this, SLOT(updateTriggerLevel(int, double))); + this->connect(this->markerSlider, SIGNAL(valueChanged(int, double)), this, + SLOT(updateMarker(int, double))); + this->connect(this->markerSlider, SIGNAL(valueChanged(int, double)), + this->mainScope, SLOT(update())); + this->connect(this->markerSlider, SIGNAL(valueChanged(int, double)), + this->zoomScope, SLOT(update())); } -/// \brief Stops the oscilloscope thread and the timer. -DsoWidget::~DsoWidget() {} +void DsoWidget::showNewData(std::unique_ptr data) +{ + if (!data) return; + this->data = std::move(data); + emit doShowNewData(); +} /// \brief Set the trigger level sliders minimum and maximum to the new values. void DsoWidget::adaptTriggerLevelSlider(unsigned int channel) { - this->triggerLevelSlider->setLimits( - channel, - (-DIVS_VOLTAGE / 2 - this->settings->scope.voltage[channel].offset) * - this->settings->scope.voltage[channel].gain, - (DIVS_VOLTAGE / 2 - this->settings->scope.voltage[channel].offset) * - this->settings->scope.voltage[channel].gain); - this->triggerLevelSlider->setStep( - channel, this->settings->scope.voltage[channel].gain * 0.2); + this->triggerLevelSlider->setLimits( + channel, + (-DIVS_VOLTAGE / 2 - this->settings->scope.voltage[channel].offset) * + this->settings->scope.voltage[channel].gain, + (DIVS_VOLTAGE / 2 - this->settings->scope.voltage[channel].offset) * + this->settings->scope.voltage[channel].gain); + this->triggerLevelSlider->setStep( + channel, this->settings->scope.voltage[channel].gain * 0.2); } /// \brief Show/Hide a line of the measurement table. void DsoWidget::setMeasurementVisible(unsigned int channel, bool visible) { - this->measurementNameLabel[channel]->setVisible(visible); - this->measurementMiscLabel[channel]->setVisible(visible); - this->measurementGainLabel[channel]->setVisible(visible); - this->measurementMagnitudeLabel[channel]->setVisible(visible); - this->measurementAmplitudeLabel[channel]->setVisible(visible); - this->measurementFrequencyLabel[channel]->setVisible(visible); - if (!visible) { - this->measurementGainLabel[channel]->setText(QString()); - this->measurementMagnitudeLabel[channel]->setText(QString()); - this->measurementAmplitudeLabel[channel]->setText(QString()); - this->measurementFrequencyLabel[channel]->setText(QString()); - } + this->measurementNameLabel[channel]->setVisible(visible); + this->measurementMiscLabel[channel]->setVisible(visible); + this->measurementGainLabel[channel]->setVisible(visible); + this->measurementMagnitudeLabel[channel]->setVisible(visible); + this->measurementAmplitudeLabel[channel]->setVisible(visible); + this->measurementFrequencyLabel[channel]->setVisible(visible); + if (!visible) { + this->measurementGainLabel[channel]->setText(QString()); + this->measurementMagnitudeLabel[channel]->setText(QString()); + this->measurementAmplitudeLabel[channel]->setText(QString()); + this->measurementFrequencyLabel[channel]->setText(QString()); + } } /// \brief Update the label about the marker measurements void DsoWidget::updateMarkerDetails() { - double divs = fabs(this->settings->scope.horizontal.marker[1] - - this->settings->scope.horizontal.marker[0]); - double time = divs * this->settings->scope.horizontal.timebase; - - if (this->settings->view.zoom) { - this->markerInfoLabel->setText( - tr("Zoom x%L1").arg(DIVS_TIME / divs, -1, 'g', 3)); - this->markerTimebaseLabel->setText( - valueToString(time / DIVS_TIME, UNIT_SECONDS, 3) + - tr("/div")); - this->markerFrequencybaseLabel->setText( - valueToString( - divs * this->settings->scope.horizontal.frequencybase / DIVS_TIME, - UNIT_HERTZ, 4) + - tr("/div")); - } - this->markerTimeLabel->setText( - valueToString(time, UNIT_SECONDS, 4)); - this->markerFrequencyLabel->setText( - valueToString(1.0 / time, UNIT_HERTZ, 4)); + double divs = fabs(this->settings->scope.horizontal.marker[1] - + this->settings->scope.horizontal.marker[0]); + double time = divs * this->settings->scope.horizontal.timebase; + + if (this->settings->view.zoom) { + this->markerInfoLabel->setText( + tr("Zoom x%L1").arg(DIVS_TIME / divs, -1, 'g', 3)); + this->markerTimebaseLabel->setText( + valueToString(time / DIVS_TIME, UNIT_SECONDS, 3) + + tr("/div")); + this->markerFrequencybaseLabel->setText( + valueToString( + divs * this->settings->scope.horizontal.frequencybase / DIVS_TIME, + UNIT_HERTZ, 4) + + tr("/div")); + } + this->markerTimeLabel->setText( + valueToString(time, UNIT_SECONDS, 4)); + this->markerFrequencyLabel->setText( + valueToString(1.0 / time, UNIT_HERTZ, 4)); } /// \brief Update the label about the trigger settings void DsoWidget::updateSpectrumDetails(unsigned int channel) { - this->setMeasurementVisible(channel, - this->settings->scope.voltage[channel].used || - this->settings->scope.spectrum[channel].used); + this->setMeasurementVisible(channel, + this->settings->scope.voltage[channel].used || + this->settings->scope.spectrum[channel].used); - if (this->settings->scope.spectrum[channel].used) - this->measurementMagnitudeLabel[channel]->setText( - valueToString(this->settings->scope.spectrum[channel].magnitude, + if (this->settings->scope.spectrum[channel].used) + this->measurementMagnitudeLabel[channel]->setText( + valueToString(this->settings->scope.spectrum[channel].magnitude, UNIT_DECIBEL, 3) + - tr("/div")); - else - this->measurementMagnitudeLabel[channel]->setText(QString()); + tr("/div")); + else + this->measurementMagnitudeLabel[channel]->setText(QString()); } /// \brief Update the label about the trigger settings void DsoWidget::updateTriggerDetails() { - // Update the trigger details - QPalette tablePalette = this->palette(); - tablePalette.setColor(QPalette::WindowText, - this->settings->view.color.screen - .voltage[this->settings->scope.trigger.source]); - this->settingsTriggerLabel->setPalette(tablePalette); - QString levelString = valueToString( - this->settings->scope.voltage[this->settings->scope.trigger.source] - .trigger, - UNIT_VOLTS, 3); - QString pretriggerString = - tr("%L1%").arg((int)(this->settings->scope.trigger.position * 100 + 0.5)); - this->settingsTriggerLabel->setText( - tr("%1 %2 %3 %4") - .arg(this->settings->scope - .voltage[this->settings->scope.trigger.source] - .name, - Dso::slopeString(this->settings->scope.trigger.slope), - levelString, pretriggerString)); - - /// \todo This won't work for special trigger sources + // Update the trigger details + QPalette tablePalette = this->palette(); + tablePalette.setColor(QPalette::WindowText, + this->settings->view.color.screen + .voltage[this->settings->scope.trigger.source]); + this->settingsTriggerLabel->setPalette(tablePalette); + QString levelString = valueToString( + this->settings->scope.voltage[this->settings->scope.trigger.source] + .trigger, + UNIT_VOLTS, 3); + QString pretriggerString = + tr("%L1%").arg((int)(this->settings->scope.trigger.position * 100 + 0.5)); + this->settingsTriggerLabel->setText( + tr("%1 %2 %3 %4") + .arg(this->settings->scope + .voltage[this->settings->scope.trigger.source] + .name, + Dso::slopeString(this->settings->scope.trigger.slope), + levelString, pretriggerString)); + + /// \todo This won't work for special trigger sources } /// \brief Update the label about the trigger settings void DsoWidget::updateVoltageDetails(unsigned int channel) { - if (channel >= (unsigned int)this->settings->scope.voltage.count()) - return; + if (channel >= (unsigned int)this->settings->scope.voltage.count()) + return; - this->setMeasurementVisible(channel, - this->settings->scope.voltage[channel].used || - this->settings->scope.spectrum[channel].used); + this->setMeasurementVisible(channel, + this->settings->scope.voltage[channel].used || + this->settings->scope.spectrum[channel].used); - if (this->settings->scope.voltage[channel].used) - this->measurementGainLabel[channel]->setText( - valueToString(this->settings->scope.voltage[channel].gain, + if (this->settings->scope.voltage[channel].used) + this->measurementGainLabel[channel]->setText( + valueToString(this->settings->scope.voltage[channel].gain, UNIT_VOLTS, 3) + - tr("/div")); - else - this->measurementGainLabel[channel]->setText(QString()); + tr("/div")); + else + this->measurementGainLabel[channel]->setText(QString()); } /// \brief Handles frequencybaseChanged signal from the horizontal dock. /// \param frequencybase The frequencybase used for displaying the trace. void DsoWidget::updateFrequencybase(double frequencybase) { - this->settingsFrequencybaseLabel->setText( - valueToString(frequencybase, UNIT_HERTZ, 4) + tr("/div")); + this->settingsFrequencybaseLabel->setText( + valueToString(frequencybase, UNIT_HERTZ, 4) + tr("/div")); } /// \brief Updates the samplerate field after changing the samplerate. /// \param samplerate The samplerate set in the oscilloscope. void DsoWidget::updateSamplerate(double samplerate) { - this->settingsSamplerateLabel->setText( - valueToString(samplerate, UNIT_SAMPLES, 4) + tr("/s")); + this->settingsSamplerateLabel->setText( + valueToString(samplerate, UNIT_SAMPLES, 4) + tr("/s")); } /// \brief Handles timebaseChanged signal from the horizontal dock. /// \param timebase The timebase used for displaying the trace. void DsoWidget::updateTimebase(double timebase) { - this->settingsTimebaseLabel->setText( - valueToString(timebase, UNIT_SECONDS, 4) + tr("/div")); + this->settingsTimebaseLabel->setText( + valueToString(timebase, UNIT_SECONDS, 4) + tr("/div")); - this->updateMarkerDetails(); + this->updateMarkerDetails(); } /// \brief Handles magnitudeChanged signal from the spectrum dock. /// \param channel The channel whose magnitude was changed. void DsoWidget::updateSpectrumMagnitude(unsigned int channel) { - this->updateSpectrumDetails(channel); + this->updateSpectrumDetails(channel); } /// \brief Handles usedChanged signal from the spectrum dock. /// \param channel The channel whose used-state was changed. /// \param used The new used-state for the channel. void DsoWidget::updateSpectrumUsed(unsigned int channel, bool used) { - if (channel >= (unsigned int)this->settings->scope.voltage.count()) - return; + if (channel >= (unsigned int)this->settings->scope.voltage.count()) + return; - this->offsetSlider->setVisible( - this->settings->scope.voltage.count() + channel, used); + this->offsetSlider->setVisible( + this->settings->scope.voltage.count() + channel, used); - this->updateSpectrumDetails(channel); + this->updateSpectrumDetails(channel); } /// \brief Handles modeChanged signal from the trigger dock. @@ -474,163 +429,150 @@ void DsoWidget::updateTriggerSlope() { this->updateTriggerDetails(); } /// \brief Handles sourceChanged signal from the trigger dock. void DsoWidget::updateTriggerSource() { - // Change the colors of the trigger sliders - if (this->settings->scope.trigger.special || - this->settings->scope.trigger.source >= - this->settings->scope.physicalChannels) - this->triggerPositionSlider->setColor( - 0, this->settings->view.color.screen.border); - else - this->triggerPositionSlider->setColor( - 0, this->settings->view.color.screen - .voltage[this->settings->scope.trigger.source]); - - for (int channel = 0; channel < (int)this->settings->scope.physicalChannels; - ++channel) - this->triggerLevelSlider->setColor( - channel, - (!this->settings->scope.trigger.special && - channel == (int)this->settings->scope.trigger.source) - ? this->settings->view.color.screen.voltage[channel] - : this->settings->view.color.screen.voltage[channel].darker()); - - this->updateTriggerDetails(); + // Change the colors of the trigger sliders + if (this->settings->scope.trigger.special || + this->settings->scope.trigger.source >= + this->settings->scope.physicalChannels) + this->triggerPositionSlider->setColor( + 0, this->settings->view.color.screen.border); + else + this->triggerPositionSlider->setColor( + 0, this->settings->view.color.screen + .voltage[this->settings->scope.trigger.source]); + + for (int channel = 0; channel < (int)this->settings->scope.physicalChannels; + ++channel) + this->triggerLevelSlider->setColor( + channel, + (!this->settings->scope.trigger.special && + channel == (int)this->settings->scope.trigger.source) + ? this->settings->view.color.screen.voltage[channel] + : this->settings->view.color.screen.voltage[channel].darker()); + + this->updateTriggerDetails(); } /// \brief Handles couplingChanged signal from the voltage dock. /// \param channel The channel whose coupling was changed. void DsoWidget::updateVoltageCoupling(unsigned int channel) { - if (channel >= (unsigned int)this->settings->scope.voltage.count()) - return; + if (channel >= (unsigned int)this->settings->scope.voltage.count()) + return; - this->measurementMiscLabel[channel]->setText(Dso::couplingString( - (Dso::Coupling)this->settings->scope.voltage[channel].misc)); + this->measurementMiscLabel[channel]->setText(Dso::couplingString( + (Dso::Coupling)this->settings->scope.voltage[channel].misc)); } /// \brief Handles modeChanged signal from the voltage dock. void DsoWidget::updateMathMode() { - this->measurementMiscLabel[this->settings->scope.physicalChannels]->setText( - Dso::mathModeString((Dso::MathMode)this->settings->scope - .voltage[this->settings->scope.physicalChannels] - .misc)); + this->measurementMiscLabel[this->settings->scope.physicalChannels]->setText( + Dso::mathModeString((Dso::MathMode)this->settings->scope + .voltage[this->settings->scope.physicalChannels] + .misc)); } /// \brief Handles gainChanged signal from the voltage dock. /// \param channel The channel whose gain was changed. void DsoWidget::updateVoltageGain(unsigned int channel) { - if (channel >= (unsigned int)this->settings->scope.voltage.count()) - return; + if (channel >= (unsigned int)this->settings->scope.voltage.count()) + return; - if (channel < this->settings->scope.physicalChannels) - this->adaptTriggerLevelSlider(channel); + if (channel < this->settings->scope.physicalChannels) + this->adaptTriggerLevelSlider(channel); - this->updateVoltageDetails(channel); + this->updateVoltageDetails(channel); } /// \brief Handles usedChanged signal from the voltage dock. /// \param channel The channel whose used-state was changed. /// \param used The new used-state for the channel. void DsoWidget::updateVoltageUsed(unsigned int channel, bool used) { - if (channel >= (unsigned int)this->settings->scope.voltage.count()) - return; + if (channel >= (unsigned int)this->settings->scope.voltage.count()) + return; - this->offsetSlider->setVisible(channel, used); - this->triggerLevelSlider->setVisible(channel, used); - this->setMeasurementVisible(channel, - this->settings->scope.voltage[channel].used); + this->offsetSlider->setVisible(channel, used); + this->triggerLevelSlider->setVisible(channel, used); + this->setMeasurementVisible(channel, + this->settings->scope.voltage[channel].used); - this->updateVoltageDetails(channel); + this->updateVoltageDetails(channel); } /// \brief Change the record length. void DsoWidget::updateRecordLength(unsigned long size) { - this->settingsRecordLengthLabel->setText( - valueToString(size, UNIT_SAMPLES, 4)); + this->settingsRecordLengthLabel->setText( + valueToString(size, UNIT_SAMPLES, 4)); } /// \brief Export the oscilloscope screen to a file. /// \return true if the document was exported successfully. -bool DsoWidget::exportAs() { - QStringList filters; - filters << tr("Portable Document Format (*.pdf)") - << tr("Image (*.png *.xpm *.jpg)") - << tr("Comma-Separated Values (*.csv)"); - - QFileDialog fileDialog(static_cast(this->parent()), - tr("Export file..."), QString(), filters.join(";;")); - fileDialog.setFileMode(QFileDialog::AnyFile); - fileDialog.setAcceptMode(QFileDialog::AcceptSave); - if (fileDialog.exec() != QDialog::Accepted) - return false; - - Exporter exporter(this->settings, this->dataAnalyzer, - static_cast(this->parent())); - exporter.setFilename(fileDialog.selectedFiles().first()); - exporter.setFormat((ExportFormat)( - EXPORT_FORMAT_PDF + filters.indexOf(fileDialog.selectedNameFilter()))); - - return exporter.doExport(); +void DsoWidget::exportAs() { + exportNextFrame.reset(Exporter::createSaveToFileExporter(settings)); } /// \brief Print the oscilloscope screen. /// \return true if the document was sent to the printer successfully. -bool DsoWidget::print() { - Exporter exporter(this->settings, this->dataAnalyzer, - static_cast(this->parent())); - exporter.setFormat(EXPORT_FORMAT_PRINTER); - - return exporter.doExport(); +void DsoWidget::print() { + exportNextFrame.reset(Exporter::createPrintExporter(settings)); } /// \brief Stop the oscilloscope. void DsoWidget::updateZoom(bool enabled) { - this->mainLayout->setRowStretch(9, enabled ? 1 : 0); - this->zoomScope->setVisible(enabled); - - // Show time-/frequencybase and zoom factor if the magnified scope is shown - this->markerLayout->setStretch(3, enabled ? 1 : 0); - this->markerTimebaseLabel->setVisible(enabled); - this->markerLayout->setStretch(4, enabled ? 1 : 0); - this->markerFrequencybaseLabel->setVisible(enabled); - if (enabled) - this->updateMarkerDetails(); - else - this->markerInfoLabel->setText(tr("Marker 1/2")); + this->mainLayout->setRowStretch(9, enabled ? 1 : 0); + this->zoomScope->setVisible(enabled); + + // Show time-/frequencybase and zoom factor if the magnified scope is shown + this->markerLayout->setStretch(3, enabled ? 1 : 0); + this->markerTimebaseLabel->setVisible(enabled); + this->markerLayout->setStretch(4, enabled ? 1 : 0); + this->markerFrequencybaseLabel->setVisible(enabled); + if (enabled) + this->updateMarkerDetails(); + else + this->markerInfoLabel->setText(tr("Marker 1/2")); - this->repaint(); + this->repaint(); } /// \brief Prints analyzed data. -void DsoWidget::dataAnalyzed() { - for (int channel = 0; channel < this->settings->scope.voltage.count(); - ++channel) { - if (this->settings->scope.voltage[channel].used && - this->dataAnalyzer->data(channel)) { - // Amplitude string representation (4 significant digits) - this->measurementAmplitudeLabel[channel]->setText(valueToString( - this->dataAnalyzer->data(channel)->amplitude, UNIT_VOLTS, 4)); - // Frequency string representation (5 significant digits) - this->measurementFrequencyLabel[channel]->setText(valueToString( - this->dataAnalyzer->data(channel)->frequency, UNIT_HERTZ, 5)); +void DsoWidget::doShowNewData() { + if (exportNextFrame){ + exportNextFrame->exportSamples(data.get()); + exportNextFrame.reset(nullptr); + } + + generator->generateGraphs(data.get()); + + updateRecordLength(data.get()->getMaxSamples()); + + for (int channel = 0; channel < this->settings->scope.voltage.count(); + ++channel) { + if (this->settings->scope.voltage[channel].used && + data.get()->data(channel)) { + // Amplitude string representation (4 significant digits) + this->measurementAmplitudeLabel[channel]->setText(valueToString( + data.get()->data(channel)->amplitude, UNIT_VOLTS, 4)); + // Frequency string representation (5 significant digits) + this->measurementFrequencyLabel[channel]->setText(valueToString( + data.get()->data(channel)->frequency, UNIT_HERTZ, 5)); + } } - } } /// \brief Handles valueChanged signal from the offset sliders. /// \param channel The channel whose offset was changed. /// \param value The new offset for the channel. void DsoWidget::updateOffset(int channel, double value) { - if (channel < this->settings->scope.voltage.count()) { - this->settings->scope.voltage[channel].offset = value; + if (channel < this->settings->scope.voltage.count()) { + this->settings->scope.voltage[channel].offset = value; - if (channel < (int)this->settings->scope.physicalChannels) - this->adaptTriggerLevelSlider(channel); - } else if (channel < this->settings->scope.voltage.count() * 2) - this->settings->scope - .spectrum[channel - this->settings->scope.voltage.count()] - .offset = value; + if (channel < (int)this->settings->scope.physicalChannels) + this->adaptTriggerLevelSlider(channel); + } else if (channel < this->settings->scope.voltage.count() * 2) + this->settings->scope + .spectrum[channel - this->settings->scope.voltage.count()] + .offset = value; - emit offsetChanged(channel, value); + emit offsetChanged(channel, value); } /// \brief Handles valueChanged signal from the triggerPosition slider. @@ -638,35 +580,35 @@ void DsoWidget::updateOffset(int channel, double value) { /// \param value The new triggerPosition in seconds relative to the first /// sample. void DsoWidget::updateTriggerPosition(int index, double value) { - if (index != 0) - return; + if (index != 0) + return; - this->settings->scope.trigger.position = value; + this->settings->scope.trigger.position = value; - this->updateTriggerDetails(); + this->updateTriggerDetails(); - emit triggerPositionChanged( - value * this->settings->scope.horizontal.timebase * DIVS_TIME); + emit triggerPositionChanged( + value * this->settings->scope.horizontal.timebase * DIVS_TIME); } /// \brief Handles valueChanged signal from the trigger level slider. /// \param channel The index of the slider. /// \param value The new trigger level. void DsoWidget::updateTriggerLevel(int channel, double value) { - this->settings->scope.voltage[channel].trigger = value; + this->settings->scope.voltage[channel].trigger = value; - this->updateTriggerDetails(); + this->updateTriggerDetails(); - emit triggerLevelChanged(channel, value); + emit triggerLevelChanged(channel, value); } /// \brief Handles valueChanged signal from the marker slider. /// \param marker The index of the slider. /// \param value The new marker position. void DsoWidget::updateMarker(int marker, double value) { - this->settings->scope.horizontal.marker[marker] = value; + this->settings->scope.horizontal.marker[marker] = value; - this->updateMarkerDetails(); + this->updateMarkerDetails(); - emit markerChanged(marker, value); + emit markerChanged(marker, value); } diff --git a/openhantek/src/dsowidget.h b/openhantek/src/dsowidget.h index 6a2ffe4..35be142 100644 --- a/openhantek/src/dsowidget.h +++ b/openhantek/src/dsowidget.h @@ -1,148 +1,126 @@ -//////////////////////////////////////////////////////////////////////////////// -// -// OpenHantek -/// \file dsowidget.h -/// \brief Declares the DsoWidget class. -// -// Copyright (C) 2010 Oliver Haag -// oliver.haag@gmail.com -// -// This program is free software: you can redistribute it and/or modify it -// under the terms of the GNU General Public License as published by the Free -// Software Foundation, either version 3 of the License, or (at your option) -// any later version. -// -// This program is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -// more details. -// -// You should have received a copy of the GNU General Public License along with -// this program. If not, see . -// -//////////////////////////////////////////////////////////////////////////////// - -#ifndef DSOWIDGET_H -#define DSOWIDGET_H +// SPDX-License-Identifier: GPL-2.0+ + +#pragma once #include +#include #include "dockwindows.h" #include "glscope.h" #include "levelslider.h" +#include "exporter.h" class DataAnalyzer; class DsoSettings; class QGridLayout; -//////////////////////////////////////////////////////////////////////////////// -/// \class DsoWidget dsowidget.h +/// \class DsoWidget /// \brief The widget for the oszilloscope-screen /// This widget contains the scopes and all level sliders. class DsoWidget : public QWidget { - Q_OBJECT + Q_OBJECT public: - DsoWidget(DsoSettings *settings, DataAnalyzer *dataAnalyzer, - QWidget *parent = 0, Qt::WindowFlags flags = 0); - ~DsoWidget(); - + /// \brief Initializes the components of the oszilloscope-screen. + /// \param settings The settings object containing the oscilloscope settings. + /// \param dataAnalyzer The data analyzer that should be used as data source. + /// \param parent The parent widget. + /// \param flags Flags for the window manager. + DsoWidget(DsoSettings *settings, QWidget *parent = 0, Qt::WindowFlags flags = 0); + void showNewData(std::unique_ptr data); protected: - void adaptTriggerLevelSlider(unsigned int channel); - void setMeasurementVisible(unsigned int channel, bool visible); - void updateMarkerDetails(); - void updateSpectrumDetails(unsigned int channel); - void updateTriggerDetails(); - void updateVoltageDetails(unsigned int channel); - - QGridLayout *mainLayout; ///< The main layout for this widget - GlGenerator *generator; ///< The generator for the OpenGL vertex arrays - GlScope *mainScope; ///< The main scope screen - GlScope *zoomScope; ///< The optional magnified scope screen - LevelSlider *offsetSlider; ///< The sliders for the graph offsets - LevelSlider *triggerPositionSlider; ///< The slider for the pretrigger - LevelSlider *triggerLevelSlider; ///< The sliders for the trigger level - LevelSlider *markerSlider; ///< The sliders for the markers - - QHBoxLayout *settingsLayout; ///< The table for the settings info - QLabel *settingsTriggerLabel; ///< The trigger details - QLabel *settingsRecordLengthLabel; ///< The record length - QLabel *settingsSamplerateLabel; ///< The samplerate - QLabel *settingsTimebaseLabel; ///< The timebase of the main scope - QLabel *settingsFrequencybaseLabel; ///< The frequencybase of the main scope - - QHBoxLayout *markerLayout; ///< The table for the marker details - QLabel *markerInfoLabel; ///< The info about the zoom factor - QLabel *markerTimeLabel; ///< The time period between the markers - QLabel *markerFrequencyLabel; ///< The frequency for the time period - QLabel *markerTimebaseLabel; ///< The timebase for the zoomed scope - QLabel *markerFrequencybaseLabel; ///< The frequencybase for the zoomed scope - - QGridLayout *measurementLayout; ///< The table for the signal details - QList measurementNameLabel; ///< The name of the channel - QList measurementGainLabel; ///< The gain for the voltage (V/div) - QList - measurementMagnitudeLabel; ///< The magnitude for the spectrum (dB/div) - QList measurementMiscLabel; ///< Coupling or math mode - QList measurementAmplitudeLabel; ///< Amplitude of the signal (V) - QList measurementFrequencyLabel; ///< Frequency of the signal (Hz) - - DsoSettings *settings; ///< The settings provided by the main window - - DataAnalyzer *dataAnalyzer; ///< The data source provided by the main window - + void adaptTriggerLevelSlider(unsigned int channel); + void setMeasurementVisible(unsigned int channel, bool visible); + void updateMarkerDetails(); + void updateSpectrumDetails(unsigned int channel); + void updateTriggerDetails(); + void updateVoltageDetails(unsigned int channel); + + QGridLayout *mainLayout; ///< The main layout for this widget + LevelSlider *offsetSlider; ///< The sliders for the graph offsets + LevelSlider *triggerPositionSlider; ///< The slider for the pretrigger + LevelSlider *triggerLevelSlider; ///< The sliders for the trigger level + LevelSlider *markerSlider; ///< The sliders for the markers + + QHBoxLayout *settingsLayout; ///< The table for the settings info + QLabel *settingsTriggerLabel; ///< The trigger details + QLabel *settingsRecordLengthLabel; ///< The record length + QLabel *settingsSamplerateLabel; ///< The samplerate + QLabel *settingsTimebaseLabel; ///< The timebase of the main scope + QLabel *settingsFrequencybaseLabel; ///< The frequencybase of the main scope + + QHBoxLayout *markerLayout; ///< The table for the marker details + QLabel *markerInfoLabel; ///< The info about the zoom factor + QLabel *markerTimeLabel; ///< The time period between the markers + QLabel *markerFrequencyLabel; ///< The frequency for the time period + QLabel *markerTimebaseLabel; ///< The timebase for the zoomed scope + QLabel *markerFrequencybaseLabel; ///< The frequencybase for the zoomed scope + + QGridLayout *measurementLayout; ///< The table for the signal details + QList measurementNameLabel; ///< The name of the channel + QList measurementGainLabel; ///< The gain for the voltage (V/div) + QList + measurementMagnitudeLabel; ///< The magnitude for the spectrum (dB/div) + QList measurementMiscLabel; ///< Coupling or math mode + QList measurementAmplitudeLabel; ///< Amplitude of the signal (V) + QList measurementFrequencyLabel; ///< Frequency of the signal (Hz) + + DsoSettings *settings; ///< The settings provided by the main window + GlGenerator *generator; ///< The generator for the OpenGL vertex arrays + GlScope *mainScope; ///< The main scope screen + GlScope *zoomScope; ///< The optional magnified scope screen + std::unique_ptr exportNextFrame; + std::unique_ptr data; public slots: - // Horizontal axis - // void horizontalFormatChanged(HorizontalFormat format); - void updateFrequencybase(double frequencybase); - void updateSamplerate(double samplerate); - void updateTimebase(double timebase); - - // Trigger - void updateTriggerMode(); - void updateTriggerSlope(); - void updateTriggerSource(); - - // Spectrum - void updateSpectrumMagnitude(unsigned int channel); - void updateSpectrumUsed(unsigned int channel, bool used); - - // Vertical axis - void updateVoltageCoupling(unsigned int channel); - void updateMathMode(); - void updateVoltageGain(unsigned int channel); - void updateVoltageUsed(unsigned int channel, bool used); - - // Menus - void updateRecordLength(unsigned long size); - - // Export - bool exportAs(); - bool print(); - - // Scope control - void updateZoom(bool enabled); - - // Data analyzer - void dataAnalyzed(); - -protected slots: - // Sliders - void updateOffset(int channel, double value); - void updateTriggerPosition(int index, double value); - void updateTriggerLevel(int channel, double value); - void updateMarker(int marker, double value); + // Horizontal axis + // void horizontalFormatChanged(HorizontalFormat format); + void updateFrequencybase(double frequencybase); + void updateSamplerate(double samplerate); + void updateTimebase(double timebase); + + // Trigger + void updateTriggerMode(); + void updateTriggerSlope(); + void updateTriggerSource(); + + // Spectrum + void updateSpectrumMagnitude(unsigned int channel); + void updateSpectrumUsed(unsigned int channel, bool used); + + // Vertical axis + void updateVoltageCoupling(unsigned int channel); + void updateMathMode(); + void updateVoltageGain(unsigned int channel); + void updateVoltageUsed(unsigned int channel, bool used); + + // Menus + void updateRecordLength(unsigned long size); + + // Export + void exportAs(); + void print(); + + // Scope control + void updateZoom(bool enabled); + + // Data analyzer + void doShowNewData(); + +private slots: + // Sliders + void updateOffset(int channel, double value); + void updateTriggerPosition(int index, double value); + void updateTriggerLevel(int channel, double value); + void updateMarker(int marker, double value); signals: - // Sliders - void offsetChanged(unsigned int channel, - double value); ///< A graph offset has been changed - void - triggerPositionChanged(double value); ///< The pretrigger has been changed - void triggerLevelChanged(unsigned int channel, - double value); ///< A trigger level has been changed - void markerChanged(unsigned int marker, - double value); ///< A marker position has been changed + // Sliders + void offsetChanged(unsigned int channel, + double value); ///< A graph offset has been changed + void + triggerPositionChanged(double value); ///< The pretrigger has been changed + void triggerLevelChanged(unsigned int channel, + double value); ///< A trigger level has been changed + void markerChanged(unsigned int marker, + double value); ///< A marker position has been changed }; - -#endif diff --git a/openhantek/src/glgenerator.cpp b/openhantek/src/glgenerator.cpp index bf21061..ce1b8ef 100644 --- a/openhantek/src/glgenerator.cpp +++ b/openhantek/src/glgenerator.cpp @@ -1,25 +1,4 @@ -//////////////////////////////////////////////////////////////////////////////// -// -// OpenHantek -// glgenerator.cpp -// -// Copyright (C) 2010 Oliver Haag -// oliver.haag@gmail.com -// -// This program is free software: you can redistribute it and/or modify it -// under the terms of the GNU General Public License as published by the Free -// Software Foundation, either version 3 of the License, or (at your option) -// any later version. -// -// This program is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -// more details. -// -// You should have received a copy of the GNU General Public License along with -// this program. If not, see . -// -//////////////////////////////////////////////////////////////////////////////// +// SPDX-License-Identifier: GPL-2.0+ #include @@ -28,400 +7,353 @@ #include "dataanalyzer.h" #include "settings.h" -//////////////////////////////////////////////////////////////////////////////// -// class GlGenerator -/// \brief Initializes the scope widget. -/// \param settings The target settings object. -/// \param parent The parent widget. -GlGenerator::GlGenerator(DsoSettings *settings, QObject *parent) - : QObject(parent) { - this->settings = settings; +GlGenerator::GlGenerator(DsoSettingsScope *scope, DsoSettingsView *view) : settings(scope), view(view) { + // Grid + vaGrid[0].resize(((DIVS_TIME * DIVS_SUB - 2) * (DIVS_VOLTAGE - 2) + + (DIVS_VOLTAGE * DIVS_SUB - 2) * (DIVS_TIME - 2) - + ((DIVS_TIME - 2) * (DIVS_VOLTAGE - 2))) * + 2); + std::vector::iterator glIterator = vaGrid[0].begin(); + // Draw vertical lines + for (int div = 1; div < DIVS_TIME / 2; ++div) { + for (int dot = 1; dot < DIVS_VOLTAGE / 2 * DIVS_SUB; ++dot) { + float dotPosition = (float)dot / DIVS_SUB; + *(glIterator++) = -div; + *(glIterator++) = -dotPosition; + *(glIterator++) = -div; + *(glIterator++) = dotPosition; + *(glIterator++) = div; + *(glIterator++) = -dotPosition; + *(glIterator++) = div; + *(glIterator++) = dotPosition; + } + } + // Draw horizontal lines + for (int div = 1; div < DIVS_VOLTAGE / 2; ++div) { + for (int dot = 1; dot < DIVS_TIME / 2 * DIVS_SUB; ++dot) { + if (dot % DIVS_SUB == 0) + continue; // Already done by vertical lines + float dotPosition = (float)dot / DIVS_SUB; + *(glIterator++) = -dotPosition; + *(glIterator++) = -div; + *(glIterator++) = dotPosition; + *(glIterator++) = -div; + *(glIterator++) = -dotPosition; + *(glIterator++) = div; + *(glIterator++) = dotPosition; + *(glIterator++) = div; + } + } - this->dataAnalyzer = 0; - this->digitalPhosphorDepth = 0; + // Axes + vaGrid[1].resize( + (2 + (DIVS_TIME * DIVS_SUB - 2) + (DIVS_VOLTAGE * DIVS_SUB - 2)) * 4); + glIterator = vaGrid[1].begin(); + // Horizontal axis + *(glIterator++) = -DIVS_TIME / 2; + *(glIterator++) = 0; + *(glIterator++) = DIVS_TIME / 2; + *(glIterator++) = 0; + // Vertical axis + *(glIterator++) = 0; + *(glIterator++) = -DIVS_VOLTAGE / 2; + *(glIterator++) = 0; + *(glIterator++) = DIVS_VOLTAGE / 2; + // Subdiv lines on horizontal axis + for (int line = 1; line < DIVS_TIME / 2 * DIVS_SUB; ++line) { + float linePosition = (float)line / DIVS_SUB; + *(glIterator++) = linePosition; + *(glIterator++) = -0.05; + *(glIterator++) = linePosition; + *(glIterator++) = 0.05; + *(glIterator++) = -linePosition; + *(glIterator++) = -0.05; + *(glIterator++) = -linePosition; + *(glIterator++) = 0.05; + } + // Subdiv lines on vertical axis + for (int line = 1; line < DIVS_VOLTAGE / 2 * DIVS_SUB; ++line) { + float linePosition = (float)line / DIVS_SUB; + *(glIterator++) = -0.05; + *(glIterator++) = linePosition; + *(glIterator++) = 0.05; + *(glIterator++) = linePosition; + *(glIterator++) = -0.05; + *(glIterator++) = -linePosition; + *(glIterator++) = 0.05; + *(glIterator++) = -linePosition; + } - this->generateGrid(); + // Border + vaGrid[2].resize(4 * 2); + glIterator = vaGrid[2].begin(); + *(glIterator++) = -DIVS_TIME / 2; + *(glIterator++) = -DIVS_VOLTAGE / 2; + *(glIterator++) = DIVS_TIME / 2; + *(glIterator++) = -DIVS_VOLTAGE / 2; + *(glIterator++) = DIVS_TIME / 2; + *(glIterator++) = DIVS_VOLTAGE / 2; + *(glIterator++) = -DIVS_TIME / 2; + *(glIterator++) = DIVS_VOLTAGE / 2; } -/// \brief Deletes OpenGL objects. -GlGenerator::~GlGenerator() { - /// \todo Clean up vaChannel +const std::vector &GlGenerator::channel(int mode, int channel, int index) const { + return vaChannel[mode][channel][index]; } -/// \brief Set the data analyzer whose data will be drawn. -/// \param dataAnalyzer Pointer to the DataAnalyzer class. -void GlGenerator::setDataAnalyzer(DataAnalyzer *dataAnalyzer) { - if (this->dataAnalyzer) - disconnect(this->dataAnalyzer, SIGNAL(finished()), this, - SLOT(generateGraphs())); - this->dataAnalyzer = dataAnalyzer; - connect(this->dataAnalyzer, SIGNAL(finished()), this, SLOT(generateGraphs())); +const std::vector &GlGenerator::grid(int a) const { + return vaGrid[a]; } -/// \brief Prepare arrays for drawing the data we get from the data analyzer. -void GlGenerator::generateGraphs() { - if (!this->dataAnalyzer) - return; - - // Adapt the number of graphs - for (int mode = Dso::CHANNELMODE_VOLTAGE; mode < Dso::CHANNELMODE_COUNT; - ++mode) - this->vaChannel[mode].resize(this->settings->scope.voltage.count()); - - // Set digital phosphor depth to one if we don't use it - if (this->settings->view.digitalPhosphor) - this->digitalPhosphorDepth = this->settings->view.digitalPhosphorDepth; - else - this->digitalPhosphorDepth = 1; - - // Handle all digital phosphor related list manipulations - for (int mode = Dso::CHANNELMODE_VOLTAGE; mode < Dso::CHANNELMODE_COUNT; - ++mode) { - for (unsigned int channel = 0; channel < this->vaChannel[mode].size(); - ++channel) { - // Move the last list element to the front - this->vaChannel[mode][channel].push_front(std::vector()); - - // Resize lists for vector array to fit the digital phosphor depth - this->vaChannel[mode][channel].resize(this->digitalPhosphorDepth); - } - } - - QMutexLocker locker(this->dataAnalyzer->mutex()); - - unsigned int preTrigSamples = 0; - unsigned int postTrigSamples = 0; - switch (this->settings->scope.horizontal.format) { - case Dso::GRAPHFORMAT_TY: { - unsigned int swTriggerStart = 0; - // check trigger point for software trigger - if (this->settings->scope.trigger.mode == Dso::TRIGGERMODE_SOFTWARE && - this->settings->scope.trigger.source <= 1) { - int channel = this->settings->scope.trigger.source; - if (this->settings->scope.voltage[channel].used && - this->dataAnalyzer->data(channel) && - !this->dataAnalyzer->data(channel)->samples.voltage.sample.empty()) { - double value; - double level = this->settings->scope.voltage[channel].trigger; - unsigned int sampleCount = - this->dataAnalyzer->data(channel)->samples.voltage.sample.size(); - double timeDisplay = this->settings->scope.horizontal.timebase * 10; - double samplesDisplay = - timeDisplay * this->settings->scope.horizontal.samplerate; - if (samplesDisplay >= sampleCount) { -// For sure not enough samples to adjust for jitter. -// Following options exist: -// 1: Decrease sample rate -// 2: Change trigger mode to auto -// 3: Ignore samples -// For now #3 is chosen -#ifdef DEBUG - timestampDebug(QString("Too few samples to make a steady " - "picture. Decrease sample rate")); -#endif - return; +bool GlGenerator::isReady() const +{ + return ready; +} + + +void GlGenerator::generateGraphs(const DataAnalyzerResult* result) { + + int digitalPhosphorDepth = view->digitalPhosphorDepth; + + // Handle all digital phosphor related list manipulations + for (int mode = Dso::CHANNELMODE_VOLTAGE; mode < Dso::CHANNELMODE_COUNT; ++mode) { + // Adapt the number of graphs + vaChannel[mode].resize(settings->voltage.count()); + + for (unsigned int channel = 0; channel < vaChannel[mode].size(); ++channel) { + // Move the last list element to the front + vaChannel[mode][channel].push_front(std::vector()); + + // Resize lists for vector array to fit the digital phosphor depth + vaChannel[mode][channel].resize(digitalPhosphorDepth); } - preTrigSamples = - (this->settings->scope.trigger.position * samplesDisplay); - postTrigSamples = sampleCount - (samplesDisplay - preTrigSamples); - // std::vector::const_iterator dataIterator = - // this->dataAnalyzer->data(channel)->samples.voltage.sample.begin(); - - if (this->settings->scope.trigger.slope == Dso::SLOPE_POSITIVE) { - double prev = INT_MAX; - for (unsigned int i = preTrigSamples; i < postTrigSamples; i++) { - value = - this->dataAnalyzer->data(channel)->samples.voltage.sample[i]; - if (value > level && prev <= level) { - int rising = 0; - for (unsigned int k = i + 1; k < i + 11 && k < sampleCount; k++) { - if (this->dataAnalyzer->data(channel) - ->samples.voltage.sample[k] >= value) { - rising++; + } + + ready = true; + + unsigned int preTrigSamples = 0; + unsigned int postTrigSamples = 0; + switch (settings->horizontal.format) { + case Dso::GRAPHFORMAT_TY: { + unsigned int swTriggerStart = 0; + // check trigger point for software trigger + if (settings->trigger.mode == Dso::TRIGGERMODE_SOFTWARE && + settings->trigger.source <= 1) { + unsigned int channel = settings->trigger.source; + if (settings->voltage[channel].used && + result->data(channel) && + !result->data(channel)->voltage.sample.empty()) { + double value; + double level = settings->voltage[channel].trigger; + unsigned int sampleCount = + result->data(channel)->voltage.sample.size(); + double timeDisplay = settings->horizontal.timebase * 10; + double samplesDisplay = + timeDisplay * settings->horizontal.samplerate; + if (samplesDisplay >= sampleCount) { + // For sure not enough samples to adjust for jitter. + // Following options exist: + // 1: Decrease sample rate + // 2: Change trigger mode to auto + // 3: Ignore samples + // For now #3 is chosen + timestampDebug(QString("Too few samples to make a steady " + "picture. Decrease sample rate")); + return; } - } - if (rising > 7) { - swTriggerStart = i; - break; - } - } - prev = value; - } - } else if (this->settings->scope.trigger.slope == Dso::SLOPE_NEGATIVE) { - double prev = INT_MIN; - for (unsigned int i = preTrigSamples; i < postTrigSamples; i++) { - value = - this->dataAnalyzer->data(channel)->samples.voltage.sample[i]; - if (value < level && prev >= level) { - int falling = 0; - for (unsigned int k = i + 1; k < i + 11 && k < sampleCount; k++) { - if (this->dataAnalyzer->data(channel) - ->samples.voltage.sample[k] < value) { - falling++; + preTrigSamples = (settings->trigger.position * samplesDisplay); + postTrigSamples = sampleCount - (samplesDisplay - preTrigSamples); + + if (settings->trigger.slope == Dso::SLOPE_POSITIVE) { + double prev = INT_MAX; + for (unsigned int i = preTrigSamples; i < postTrigSamples; i++) { + value = result->data(channel)->voltage.sample[i]; + if (value > level && prev <= level) { + int rising = 0; + for (unsigned int k = i + 1; k < i + 11 && k < sampleCount; k++) { + if (result->data(channel) + ->voltage.sample[k] >= value) { + rising++; + } + } + if (rising > 7) { + swTriggerStart = i; + break; + } + } + prev = value; + } + } else if (settings->trigger.slope == Dso::SLOPE_NEGATIVE) { + double prev = INT_MIN; + for (unsigned int i = preTrigSamples; i < postTrigSamples; i++) { + value = result->data(channel)->voltage.sample[i]; + if (value < level && prev >= level) { + int falling = 0; + for (unsigned int k = i + 1; k < i + 11 && k < sampleCount; k++) { + if (result->data(channel)->voltage.sample[k] < value) { + falling++; + } + } + if (falling > 7) { + swTriggerStart = i; + break; + } + } + prev = value; + } } - } - if (falling > 7) { - swTriggerStart = i; - break; - } } - prev = value; - } + if (swTriggerStart == 0) { + timestampDebug(QString("Trigger not asserted. Data ignored")); + return; + } } - } - if (swTriggerStart == 0) { -#ifdef DEBUG - timestampDebug(QString("Trigger not asserted. Data ignored")); -#endif - return; - } - } - // Add graphs for channels - for (int mode = Dso::CHANNELMODE_VOLTAGE; mode < Dso::CHANNELMODE_COUNT; - ++mode) { - for (int channel = 0; channel < this->settings->scope.voltage.size(); - ++channel) { - // Check if this channel is used and available at the data analyzer - if (((mode == Dso::CHANNELMODE_VOLTAGE) - ? this->settings->scope.voltage[channel].used - : this->settings->scope.spectrum[channel].used) && - this->dataAnalyzer->data(channel) && - !this->dataAnalyzer->data(channel) - ->samples.voltage.sample.empty()) { - // Check if the sample count has changed - unsigned int sampleCount = (mode == Dso::CHANNELMODE_VOLTAGE) - ? this->dataAnalyzer->data(channel) - ->samples.voltage.sample.size() - : this->dataAnalyzer->data(channel) - ->samples.spectrum.sample.size(); - if (mode == Dso::CHANNELMODE_VOLTAGE) - sampleCount -= (swTriggerStart - preTrigSamples); - unsigned int neededSize = sampleCount * 2; + // Add graphs for channels + for (int mode = Dso::CHANNELMODE_VOLTAGE; mode < Dso::CHANNELMODE_COUNT; ++mode) { + for (int channel = 0; channel < (int)settings->voltage.size(); ++channel) { + // Check if this channel is used and available at the data analyzer + if (((mode == Dso::CHANNELMODE_VOLTAGE) + ? settings->voltage[channel].used + : settings->spectrum[channel].used) && + result->data(channel) && + !result->data(channel) + ->voltage.sample.empty()) { + // Check if the sample count has changed + size_t sampleCount = (mode == Dso::CHANNELMODE_VOLTAGE) + ? result->data(channel)->voltage.sample.size() + : result->data(channel)->spectrum.sample.size(); + if (mode == Dso::CHANNELMODE_VOLTAGE) + sampleCount -= (swTriggerStart - preTrigSamples); + size_t neededSize = sampleCount * 2; #if 0 - for(unsigned int index = 0; index < this->digitalPhosphorDepth; ++index) { - if(this->vaChannel[mode][channel][index].size() != neededSize) - this->vaChannel[mode][channel][index].clear(); // Something was changed, drop old traces - } + for(unsigned int index = 0; index < digitalPhosphorDepth; ++index) { + if(vaChannel[mode][channel][index].size() != neededSize) + vaChannel[mode][channel][index].clear(); // Something was changed, drop old traces + } #endif - // Set size directly to avoid reallocations - this->vaChannel[mode][channel].front().resize(neededSize); - - // Iterator to data for direct access - std::vector::iterator glIterator = - this->vaChannel[mode][channel].front().begin(); - - // What's the horizontal distance between sampling points? - double horizontalFactor; - if (mode == Dso::CHANNELMODE_VOLTAGE) - horizontalFactor = - this->dataAnalyzer->data(channel)->samples.voltage.interval / - this->settings->scope.horizontal.timebase; - else - horizontalFactor = - this->dataAnalyzer->data(channel)->samples.spectrum.interval / - this->settings->scope.horizontal.frequencybase; - - // Fill vector array - if (mode == Dso::CHANNELMODE_VOLTAGE) { - std::vector::const_iterator dataIterator = - this->dataAnalyzer->data(channel) - ->samples.voltage.sample.begin(); - const double gain = this->settings->scope.voltage[channel].gain; - const double offset = this->settings->scope.voltage[channel].offset; - - std::advance(dataIterator, swTriggerStart - preTrigSamples); - - for (unsigned int position = 0; position < sampleCount; - ++position) { - *(glIterator++) = position * horizontalFactor - DIVS_TIME / 2; - *(glIterator++) = *(dataIterator++) / gain + offset; - } - } else { - std::vector::const_iterator dataIterator = - this->dataAnalyzer->data(channel) - ->samples.spectrum.sample.begin(); - const double magnitude = - this->settings->scope.spectrum[channel].magnitude; - const double offset = - this->settings->scope.spectrum[channel].offset; - - for (unsigned int position = 0; position < sampleCount; - ++position) { - *(glIterator++) = position * horizontalFactor - DIVS_TIME / 2; - *(glIterator++) = *(dataIterator++) / magnitude + offset; + // Set size directly to avoid reallocations + vaChannel[mode][(size_t)channel].front().resize(neededSize); + + // Iterator to data for direct access + std::vector::iterator glIterator = + vaChannel[mode][(size_t)channel].front().begin(); + + // What's the horizontal distance between sampling points? + double horizontalFactor; + if (mode == Dso::CHANNELMODE_VOLTAGE) + horizontalFactor = + result->data(channel)->voltage.interval / + settings->horizontal.timebase; + else + horizontalFactor = + result->data(channel)->spectrum.interval / + settings->horizontal.frequencybase; + + // Fill vector array + if (mode == Dso::CHANNELMODE_VOLTAGE) { + std::vector::const_iterator dataIterator = + result->data(channel) + ->voltage.sample.begin(); + const double gain = settings->voltage[channel].gain; + const double offset = settings->voltage[channel].offset; + + std::advance(dataIterator, swTriggerStart - preTrigSamples); + + for (unsigned int position = 0; position < sampleCount; + ++position) { + *(glIterator++) = position * horizontalFactor - DIVS_TIME / 2; + *(glIterator++) = *(dataIterator++) / gain + offset; + } + } else { + std::vector::const_iterator dataIterator = + result->data(channel) + ->spectrum.sample.begin(); + const double magnitude = + settings->spectrum[channel].magnitude; + const double offset = + settings->spectrum[channel].offset; + + for (unsigned int position = 0; position < sampleCount; + ++position) { + *(glIterator++) = position * horizontalFactor - DIVS_TIME / 2; + *(glIterator++) = *(dataIterator++) / magnitude + offset; + } + } + } else { + // Delete all vector arrays + for (unsigned index = 0; index < (unsigned)digitalPhosphorDepth; ++index) + vaChannel[mode][channel][index].clear(); + } } - } - } else { - // Delete all vector arrays - for (unsigned int index = 0; index < this->digitalPhosphorDepth; - ++index) - this->vaChannel[mode][channel][index].clear(); - } - } - } - } break; - - case Dso::GRAPHFORMAT_XY: - for (int channel = 0; channel < this->settings->scope.voltage.size(); - ++channel) { - // For even channel numbers check if this channel is used and this and the - // following channel are available at the data analyzer - if (channel % 2 == 0 && - channel + 1 < this->settings->scope.voltage.size() && - this->settings->scope.voltage[channel].used && - this->dataAnalyzer->data(channel) && - !this->dataAnalyzer->data(channel)->samples.voltage.sample.empty() && - this->dataAnalyzer->data(channel + 1) && - !this->dataAnalyzer->data(channel + 1) - ->samples.voltage.sample.empty()) { - // Check if the sample count has changed - const unsigned int sampleCount = qMin( - this->dataAnalyzer->data(channel)->samples.voltage.sample.size(), - this->dataAnalyzer->data(channel + 1) - ->samples.voltage.sample.size()); - const unsigned int neededSize = sampleCount * 2; - for (unsigned int index = 0; index < this->digitalPhosphorDepth; - ++index) { - if (this->vaChannel[Dso::CHANNELMODE_VOLTAGE][channel][index] - .size() != neededSize) - this->vaChannel[Dso::CHANNELMODE_VOLTAGE][channel][index] - .clear(); // Something was changed, drop old traces - } - - // Set size directly to avoid reallocations - this->vaChannel[Dso::CHANNELMODE_VOLTAGE][channel].front().resize( - neededSize); - - // Iterator to data for direct access - std::vector::iterator glIterator = - this->vaChannel[Dso::CHANNELMODE_VOLTAGE][channel].front().begin(); - - // Fill vector array - unsigned int xChannel = channel; - unsigned int yChannel = channel + 1; - std::vector::const_iterator xIterator = - this->dataAnalyzer->data(xChannel)->samples.voltage.sample.begin(); - std::vector::const_iterator yIterator = - this->dataAnalyzer->data(yChannel)->samples.voltage.sample.begin(); - const double xGain = this->settings->scope.voltage[xChannel].gain; - const double yGain = this->settings->scope.voltage[yChannel].gain; - const double xOffset = this->settings->scope.voltage[xChannel].offset; - const double yOffset = this->settings->scope.voltage[yChannel].offset; - - for (unsigned int position = 0; position < sampleCount; ++position) { - *(glIterator++) = *(xIterator++) / xGain + xOffset; - *(glIterator++) = *(yIterator++) / yGain + yOffset; } - } else { - // Delete all vector arrays - for (unsigned int index = 0; index < this->digitalPhosphorDepth; - ++index) - this->vaChannel[Dso::CHANNELMODE_VOLTAGE][channel][index].clear(); - } - - // Delete all spectrum graphs - for (unsigned int index = 0; index < this->digitalPhosphorDepth; ++index) - this->vaChannel[Dso::CHANNELMODE_SPECTRUM][channel][index].clear(); - } - break; + } break; + + case Dso::GRAPHFORMAT_XY: + for (int channel = 0; channel < settings->voltage.size(); + ++channel) { + // For even channel numbers check if this channel is used and this and the + // following channel are available at the data analyzer + if (channel % 2 == 0 && + channel + 1 < settings->voltage.size() && + settings->voltage[channel].used && + result->data(channel) && + !result->data(channel)->voltage.sample.empty() && + result->data(channel + 1) && + !result->data(channel + 1) + ->voltage.sample.empty()) { + // Check if the sample count has changed + const unsigned sampleCount = qMin( + result->data(channel)->voltage.sample.size(), + result->data(channel + 1) + ->voltage.sample.size()); + const unsigned neededSize = sampleCount * 2; + for (unsigned index = 0; index < (unsigned)digitalPhosphorDepth; ++index) { + if (vaChannel[Dso::CHANNELMODE_VOLTAGE][(size_t)channel][index].size() != neededSize) + vaChannel[Dso::CHANNELMODE_VOLTAGE][(size_t)channel][index].clear(); // Something was changed, drop old traces + } - default: - break; - } + // Set size directly to avoid reallocations + vaChannel[Dso::CHANNELMODE_VOLTAGE][(size_t)channel].front().resize( + neededSize); + + // Iterator to data for direct access + std::vector::iterator glIterator = + vaChannel[Dso::CHANNELMODE_VOLTAGE][channel].front().begin(); + + // Fill vector array + unsigned int xChannel = channel; + unsigned int yChannel = channel + 1; + std::vector::const_iterator xIterator = + result->data(xChannel)->voltage.sample.begin(); + std::vector::const_iterator yIterator = + result->data(yChannel)->voltage.sample.begin(); + const double xGain = settings->voltage[xChannel].gain; + const double yGain = settings->voltage[yChannel].gain; + const double xOffset = settings->voltage[xChannel].offset; + const double yOffset = settings->voltage[yChannel].offset; + + for (unsigned int position = 0; position < sampleCount; ++position) { + *(glIterator++) = *(xIterator++) / xGain + xOffset; + *(glIterator++) = *(yIterator++) / yGain + yOffset; + } + } else { + // Delete all vector arrays + for (unsigned int index = 0; index < (unsigned)digitalPhosphorDepth; ++index) + vaChannel[Dso::CHANNELMODE_VOLTAGE][(size_t)channel][index].clear(); + } - emit graphsGenerated(); -} + // Delete all spectrum graphs + for (unsigned int index = 0; index < (unsigned)digitalPhosphorDepth; ++index) + vaChannel[Dso::CHANNELMODE_SPECTRUM][(size_t)channel][index].clear(); + } + break; -/// \brief Create the needed OpenGL vertex arrays for the grid. -void GlGenerator::generateGrid() { - // Grid - this->vaGrid[0].resize(((DIVS_TIME * DIVS_SUB - 2) * (DIVS_VOLTAGE - 2) + - (DIVS_VOLTAGE * DIVS_SUB - 2) * (DIVS_TIME - 2) - - ((DIVS_TIME - 2) * (DIVS_VOLTAGE - 2))) * - 2); - std::vector::iterator glIterator = this->vaGrid[0].begin(); - // Draw vertical lines - for (int div = 1; div < DIVS_TIME / 2; ++div) { - for (int dot = 1; dot < DIVS_VOLTAGE / 2 * DIVS_SUB; ++dot) { - float dotPosition = (float)dot / DIVS_SUB; - *(glIterator++) = -div; - *(glIterator++) = -dotPosition; - *(glIterator++) = -div; - *(glIterator++) = dotPosition; - *(glIterator++) = div; - *(glIterator++) = -dotPosition; - *(glIterator++) = div; - *(glIterator++) = dotPosition; + default: + break; } - } - // Draw horizontal lines - for (int div = 1; div < DIVS_VOLTAGE / 2; ++div) { - for (int dot = 1; dot < DIVS_TIME / 2 * DIVS_SUB; ++dot) { - if (dot % DIVS_SUB == 0) - continue; // Already done by vertical lines - float dotPosition = (float)dot / DIVS_SUB; - *(glIterator++) = -dotPosition; - *(glIterator++) = -div; - *(glIterator++) = dotPosition; - *(glIterator++) = -div; - *(glIterator++) = -dotPosition; - *(glIterator++) = div; - *(glIterator++) = dotPosition; - *(glIterator++) = div; - } - } - - // Axes - this->vaGrid[1].resize( - (2 + (DIVS_TIME * DIVS_SUB - 2) + (DIVS_VOLTAGE * DIVS_SUB - 2)) * 4); - glIterator = this->vaGrid[1].begin(); - // Horizontal axis - *(glIterator++) = -DIVS_TIME / 2; - *(glIterator++) = 0; - *(glIterator++) = DIVS_TIME / 2; - *(glIterator++) = 0; - // Vertical axis - *(glIterator++) = 0; - *(glIterator++) = -DIVS_VOLTAGE / 2; - *(glIterator++) = 0; - *(glIterator++) = DIVS_VOLTAGE / 2; - // Subdiv lines on horizontal axis - for (int line = 1; line < DIVS_TIME / 2 * DIVS_SUB; ++line) { - float linePosition = (float)line / DIVS_SUB; - *(glIterator++) = linePosition; - *(glIterator++) = -0.05; - *(glIterator++) = linePosition; - *(glIterator++) = 0.05; - *(glIterator++) = -linePosition; - *(glIterator++) = -0.05; - *(glIterator++) = -linePosition; - *(glIterator++) = 0.05; - } - // Subdiv lines on vertical axis - for (int line = 1; line < DIVS_VOLTAGE / 2 * DIVS_SUB; ++line) { - float linePosition = (float)line / DIVS_SUB; - *(glIterator++) = -0.05; - *(glIterator++) = linePosition; - *(glIterator++) = 0.05; - *(glIterator++) = linePosition; - *(glIterator++) = -0.05; - *(glIterator++) = -linePosition; - *(glIterator++) = 0.05; - *(glIterator++) = -linePosition; - } - - // Border - this->vaGrid[2].resize(4 * 2); - glIterator = this->vaGrid[2].begin(); - *(glIterator++) = -DIVS_TIME / 2; - *(glIterator++) = -DIVS_VOLTAGE / 2; - *(glIterator++) = DIVS_TIME / 2; - *(glIterator++) = -DIVS_VOLTAGE / 2; - *(glIterator++) = DIVS_TIME / 2; - *(glIterator++) = DIVS_VOLTAGE / 2; - *(glIterator++) = -DIVS_TIME / 2; - *(glIterator++) = DIVS_VOLTAGE / 2; + + emit graphsGenerated(); } diff --git a/openhantek/src/glgenerator.h b/openhantek/src/glgenerator.h index 6470c6c..8cea0db 100644 --- a/openhantek/src/glgenerator.h +++ b/openhantek/src/glgenerator.h @@ -1,79 +1,45 @@ -//////////////////////////////////////////////////////////////////////////////// -// -// OpenHantek -/// \file glgenerator.h -/// \brief Declares the GlScope class. -// -// Copyright (C) 2008, 2009 Oleg Khudyakov -// prcoder@potrebitel.ru -// Copyright (C) 2010 Oliver Haag -// oliver.haag@gmail.com -// -// This program is free software: you can redistribute it and/or modify it -// under the terms of the GNU General Public License as published by the Free -// Software Foundation, either version 3 of the License, or (at your option) -// any later version. -// -// This program is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -// more details. -// -// You should have received a copy of the GNU General Public License along with -// this program. If not, see . -// -//////////////////////////////////////////////////////////////////////////////// +// SPDX-License-Identifier: GPL-2.0+ -#ifndef GLGENERATOR_H -#define GLGENERATOR_H +#pragma once #include #include #include -#include "definitions.h" +#include "scopesettings.h" +#include "viewsettings.h" +#include "dataanalyzerresult.h" #define DIVS_TIME 10.0 ///< Number of horizontal screen divs #define DIVS_VOLTAGE 8.0 ///< Number of vertical screen divs #define DIVS_SUB 5 ///< Number of sub-divisions per div -class DataAnalyzer; -class DsoSettings; class GlScope; //////////////////////////////////////////////////////////////////////////////// -/// \class GlGenerator glgenerator.h +/// \class GlGenerator /// \brief Generates the vertex arrays for the GlScope classes. class GlGenerator : public QObject { - Q_OBJECT - - friend class GlScope; + Q_OBJECT public: - GlGenerator(DsoSettings *settings, QObject *parent = 0); - ~GlGenerator(); - - void setDataAnalyzer(DataAnalyzer *dataAnalyzer); - -protected: - void generateGrid(); - + /// \brief Initializes the scope widget. + /// \param settings The target settings object. + /// \param parent The parent widget. + GlGenerator(DsoSettingsScope *scope, DsoSettingsView* view); + void generateGraphs(const DataAnalyzerResult *result); + const std::vector& channel(int mode, int channel, int index) const; + const std::vector& grid(int a) const; + bool isReady() const; private: - DataAnalyzer *dataAnalyzer; - DsoSettings *settings; - - std::vector>> - vaChannel[Dso::CHANNELMODE_COUNT]; - std::vector vaGrid[3]; - - unsigned int digitalPhosphorDepth; - -public slots: - void generateGraphs(); - + DsoSettingsScope *settings; + DsoSettingsView *view; + std::vector>> vaChannel[Dso::CHANNELMODE_COUNT]; + std::vector vaGrid[3]; + bool ready = false; signals: - void graphsGenerated(); ///< The graphs are ready to be drawn + void graphsGenerated(); ///< The graphs are ready to be drawn }; -#endif + diff --git a/openhantek/src/glscope.cpp b/openhantek/src/glscope.cpp index 47e5189..f2732f9 100644 --- a/openhantek/src/glscope.cpp +++ b/openhantek/src/glscope.cpp @@ -1,27 +1,4 @@ -//////////////////////////////////////////////////////////////////////////////// -// -// OpenHantek -// glscope.cpp -// -// Copyright (C) 2008, 2009 Oleg Khudyakov -// prcoder@potrebitel.ru -// Copyright (C) 2010 Oliver Haag -// oliver.haag@gmail.com -// -// This program is free software: you can redistribute it and/or modify it -// under the terms of the GNU General Public License as published by the Free -// Software Foundation, either version 3 of the License, or (at your option) -// any later version. -// -// This program is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -// more details. -// -// You should have received a copy of the GNU General Public License along with -// this program. If not, see . -// -//////////////////////////////////////////////////////////////////////////////// +// SPDX-License-Identifier: GPL-2.0+ #include @@ -33,250 +10,212 @@ #include "glgenerator.h" #include "settings.h" -//////////////////////////////////////////////////////////////////////////////// -// class GlScope -/// \brief Initializes the scope widget. -/// \param settings The settings that should be used. -/// \param parent The parent widget. -GlScope::GlScope(DsoSettings *settings, QWidget *parent) : GL_WIDGET_CLASS(parent) { - this->settings = settings; - - this->generator = 0; - this->zoomed = false; +GlScope::GlScope(DsoSettings *settings, const GlGenerator *generator, QWidget *parent) : GL_WIDGET_CLASS(parent), + settings(settings), generator(generator) { + connect(generator, &GlGenerator::graphsGenerated, [this]() {update();}); } -/// \brief Deletes OpenGL objects. -GlScope::~GlScope() {} - /// \brief Initializes OpenGL output. void GlScope::initializeGL() { - glDisable(GL_DEPTH_TEST); - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDisable(GL_DEPTH_TEST); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glPointSize(1); + glPointSize(1); - QColor bg = settings->view.color.screen.background; - glClearColor(bg.redF(), bg.greenF(), bg.blueF(), bg.alphaF()); + QColor bg = settings->view.color.screen.background; + glClearColor(bg.redF(), bg.greenF(), bg.blueF(), bg.alphaF()); - glShadeModel(GL_SMOOTH /*GL_FLAT*/); - glLineStipple(1, 0x3333); + glShadeModel(GL_SMOOTH /*GL_FLAT*/); + glLineStipple(1, 0x3333); - glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_VERTEX_ARRAY); } /// \brief Draw the graphs and the grid. void GlScope::paintGL() { - if (!this->isVisible()) - return; + if (!this->isVisible() || !this->generator->isReady()) + return; + + // Clear OpenGL buffer and configure settings + glClear(GL_COLOR_BUFFER_BIT); + glLineWidth(1); + + if (this->settings->view.digitalPhosphorDepth > 0) { + drawGraph(); + } + + if (!this->zoomed) { + // Draw vertical lines at marker positions + glEnable(GL_LINE_STIPPLE); + QColor trColor = settings->view.color.screen.markers; + glColor4f(trColor.redF(), trColor.greenF(), trColor.blueF(), trColor.alphaF()); + + for (int marker = 0; marker < MARKER_COUNT; ++marker) { + if (!this->settings->scope.horizontal.marker_visible[marker]) + continue; + if (this->vaMarker[marker].size() != 4) { + this->vaMarker[marker].resize(2 * 2); + this->vaMarker[marker][1] = -DIVS_VOLTAGE; + this->vaMarker[marker][3] = DIVS_VOLTAGE; + } - // Clear OpenGL buffer and configure settings - glClear(GL_COLOR_BUFFER_BIT); - glLineWidth(1); + this->vaMarker[marker][0] = + this->settings->scope.horizontal.marker[marker]; + this->vaMarker[marker][2] = + this->settings->scope.horizontal.marker[marker]; - // Draw the graphs - if (this->generator && this->generator->digitalPhosphorDepth > 0) { + glVertexPointer(2, GL_FLOAT, 0, &this->vaMarker[marker].front()); + glDrawArrays(GL_LINES, 0, this->vaMarker[marker].size() / 2); + } + + glDisable(GL_LINE_STIPPLE); + } + + // Draw grid + this->drawGrid(); +} + +/// \brief Resize the widget. +/// \param width The new width of the widget. +/// \param height The new height of the widget. +void GlScope::resizeGL(int width, int height) { + glViewport(0, 0, (GLint)width, (GLint)height); + + glMatrixMode(GL_PROJECTION); + + // Set axes to div-scale and apply correction for exact pixelization + glLoadIdentity(); + GLdouble pixelizationWidthCorrection = + (width > 0) ? (GLdouble)width / (width - 1) : 1; + GLdouble pixelizationHeightCorrection = + (height > 0) ? (GLdouble)height / (height - 1) : 1; + glOrtho(-(DIVS_TIME / 2) * pixelizationWidthCorrection, + (DIVS_TIME / 2) * pixelizationWidthCorrection, + -(DIVS_VOLTAGE / 2) * pixelizationHeightCorrection, + (DIVS_VOLTAGE / 2) * pixelizationHeightCorrection, -1.0, 1.0); + + glMatrixMode(GL_MODELVIEW); +} + +/// \brief Set the zoom mode for this GlScope. +/// \param zoomed true magnifies the area between the markers. +void GlScope::setZoomMode(bool zoomed) { this->zoomed = zoomed; } + +/// \brief Draw the grid. +void GlScope::drawGrid() { + glDisable(GL_POINT_SMOOTH); + glDisable(GL_LINE_SMOOTH); + + QColor color; + // Grid + color = this->settings->view.color.screen.grid; + glColor4f(color.redF(), color.greenF(), color.blueF(), color.alphaF()); + glVertexPointer(2, GL_FLOAT, 0, &generator->grid(0).front()); + glDrawArrays(GL_POINTS, 0, generator->grid(0).size() / 2); + // Axes + color = settings->view.color.screen.axes; + glColor4f(color.redF(), color.greenF(), color.blueF(), color.alphaF()); + glVertexPointer(2, GL_FLOAT, 0, &generator->grid(1).front()); + glDrawArrays(GL_LINES, 0, generator->grid(1).size() / 2); + // Border + color = settings->view.color.screen.border; + glColor4f(color.redF(), color.greenF(), color.blueF(), color.alphaF()); + glVertexPointer(2, GL_FLOAT, 0, &generator->grid(2).front()); + glDrawArrays(GL_LINE_LOOP, 0, generator->grid(2).size() / 2); +} + +void GlScope::drawGraphDepth(int mode, int channel, int index) +{ + if (generator->channel(mode, channel, index).empty()) return; + QColor trColor; + if (mode == Dso::CHANNELMODE_VOLTAGE) + trColor = settings->view.color.screen.voltage[channel] + .darker(fadingFactor[index]); + else + trColor = settings->view.color.screen.spectrum[channel] + .darker(fadingFactor[index]); + glColor4f(trColor.redF(), trColor.greenF(), trColor.blueF(), + trColor.alphaF()); + glVertexPointer( + 2, GL_FLOAT, 0, + &generator->channel(mode, channel, index).front()); + glDrawArrays( + (this->settings->view.interpolation == Dso::INTERPOLATION_OFF) ? GL_POINTS : GL_LINE_STRIP, + 0, generator->channel(mode, channel, index).size() / + 2); +} + +void GlScope::drawGraph() +{ if (this->settings->view.antialiasing) { - glEnable(GL_POINT_SMOOTH); - glEnable(GL_LINE_SMOOTH); - glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); + glEnable(GL_POINT_SMOOTH); + glEnable(GL_LINE_SMOOTH); + glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); } // Apply zoom settings via matrix transformation if (this->zoomed) { - glPushMatrix(); - glScalef(DIVS_TIME / fabs(this->settings->scope.horizontal.marker[1] - - this->settings->scope.horizontal.marker[0]), - 1.0, 1.0); - glTranslatef(-(this->settings->scope.horizontal.marker[0] + + glPushMatrix(); + glScalef(DIVS_TIME / fabs(this->settings->scope.horizontal.marker[1] - + this->settings->scope.horizontal.marker[0]), + 1.0, 1.0); + glTranslatef(-(this->settings->scope.horizontal.marker[0] + this->settings->scope.horizontal.marker[1]) / - 2, - 0.0, 0.0); + 2, + 0.0, 0.0); } // Values we need for the fading of the digital phosphor - double *fadingFactor = new double[this->generator->digitalPhosphorDepth]; - fadingFactor[0] = 100; - double fadingRatio = pow(10.0, 2.0 / this->generator->digitalPhosphorDepth); - for (unsigned int index = 1; index < this->generator->digitalPhosphorDepth; - ++index) - fadingFactor[index] = fadingFactor[index - 1] * fadingRatio; + if ((int)fadingFactor.size() != this->settings->view.digitalPhosphorDepth) { + fadingFactor.resize((size_t)this->settings->view.digitalPhosphorDepth); + fadingFactor[0] = 100; + double fadingRatio = pow(10.0, 2.0 / this->settings->view.digitalPhosphorDepth); + for (unsigned index = 1; index < this->settings->view.digitalPhosphorDepth; ++index) + fadingFactor[index] = fadingFactor[index - 1] * fadingRatio; + } switch (this->settings->scope.horizontal.format) { case Dso::GRAPHFORMAT_TY: - // Real and virtual channels - for (int mode = Dso::CHANNELMODE_VOLTAGE; mode < Dso::CHANNELMODE_COUNT; - ++mode) { - for (int channel = 0; channel < this->settings->scope.voltage.count(); - ++channel) { - if ((mode == Dso::CHANNELMODE_VOLTAGE) - ? this->settings->scope.voltage[channel].used - : this->settings->scope.spectrum[channel].used) { - // Draw graph for all available depths - for (int index = this->generator->digitalPhosphorDepth - 1; - index >= 0; index--) { - if (!this->generator->vaChannel[mode][channel][index].empty()) { - QColor trColor; - if (mode == Dso::CHANNELMODE_VOLTAGE) - trColor = settings->view.color.screen.voltage[channel] - .darker(fadingFactor[index]); - else - trColor = settings->view.color.screen.spectrum[channel] - .darker(fadingFactor[index]); - glColor4f(trColor.redF(), trColor.greenF(), trColor.blueF(), - trColor.alphaF()); - glVertexPointer( - 2, GL_FLOAT, 0, - &this->generator->vaChannel[mode][channel][index].front()); - glDrawArrays( - (this->settings->view.interpolation == - Dso::INTERPOLATION_OFF) - ? GL_POINTS - : GL_LINE_STRIP, - 0, this->generator->vaChannel[mode][channel][index].size() / - 2); - } + // Real and virtual channels + for (int mode = Dso::CHANNELMODE_VOLTAGE; mode < Dso::CHANNELMODE_COUNT; ++mode) { + for (int channel = 0; channel < this->settings->scope.voltage.count(); ++channel) { + if (!channelUsed(mode, channel)) + continue; + + // Draw graph for all available depths + for (int index = this->settings->view.digitalPhosphorDepth - 1; index >= 0; index--) { + drawGraphDepth(mode, channel, index); + } } - } } - } - break; + break; case Dso::GRAPHFORMAT_XY: - // Real and virtual channels - for (int channel = 0; channel < this->settings->scope.voltage.count() - 1; - channel += 2) { - if (this->settings->scope.voltage[channel].used) { - // Draw graph for all available depths - for (int index = this->generator->digitalPhosphorDepth - 1; - index >= 0; index--) { - if (!this->generator - ->vaChannel[Dso::CHANNELMODE_VOLTAGE][channel][index] - .empty()) { - QColor trColor = settings->view.color.screen.voltage[channel] - .darker(fadingFactor[index]); - glColor4f(trColor.redF(), trColor.greenF(), trColor.blueF(), - trColor.alphaF()); - glVertexPointer( - 2, GL_FLOAT, 0, - &this->generator - ->vaChannel[Dso::CHANNELMODE_VOLTAGE][channel][index] - .front()); - glDrawArrays( - (this->settings->view.interpolation == Dso::INTERPOLATION_OFF) - ? GL_POINTS - : GL_LINE_STRIP, - 0, - this->generator - ->vaChannel[Dso::CHANNELMODE_VOLTAGE][channel][index] - .size() / - 2); + // Real and virtual channels + for (int channel = 0; channel < this->settings->scope.voltage.count() - 1; channel += 2) { + if (this->settings->scope.voltage[channel].used) { + for (int index = this->settings->view.digitalPhosphorDepth - 1; index >= 0; index--) { + drawGraphDepth(Dso::CHANNELMODE_VOLTAGE, channel, index); + } } - } } - } - break; + break; default: - break; + break; } - delete[] fadingFactor; - glDisable(GL_POINT_SMOOTH); glDisable(GL_LINE_SMOOTH); if (this->zoomed) - glPopMatrix(); - } - - if (!this->zoomed) { - // Draw vertical lines at marker positions - glEnable(GL_LINE_STIPPLE); - QColor trColor = settings->view.color.screen.markers; - glColor4f(trColor.redF(), trColor.greenF(), trColor.blueF(), - trColor.alphaF()); - - for (int marker = 0; marker < MARKER_COUNT; ++marker) { - if (!this->settings->scope.horizontal.marker_visible[marker]) - continue; - if (this->vaMarker[marker].size() != 4) { - this->vaMarker[marker].resize(2 * 2); - this->vaMarker[marker][1] = -DIVS_VOLTAGE; - this->vaMarker[marker][3] = DIVS_VOLTAGE; - } - - this->vaMarker[marker][0] = - this->settings->scope.horizontal.marker[marker]; - this->vaMarker[marker][2] = - this->settings->scope.horizontal.marker[marker]; - - glVertexPointer(2, GL_FLOAT, 0, &this->vaMarker[marker].front()); - glDrawArrays(GL_LINES, 0, this->vaMarker[marker].size() / 2); - } - - glDisable(GL_LINE_STIPPLE); - } - - // Draw grid - this->drawGrid(); -} - -/// \brief Resize the widget. -/// \param width The new width of the widget. -/// \param height The new height of the widget. -void GlScope::resizeGL(int width, int height) { - glViewport(0, 0, (GLint)width, (GLint)height); - - glMatrixMode(GL_PROJECTION); - - // Set axes to div-scale and apply correction for exact pixelization - glLoadIdentity(); - GLdouble pixelizationWidthCorrection = - (width > 0) ? (GLdouble)width / (width - 1) : 1; - GLdouble pixelizationHeightCorrection = - (height > 0) ? (GLdouble)height / (height - 1) : 1; - glOrtho(-(DIVS_TIME / 2) * pixelizationWidthCorrection, - (DIVS_TIME / 2) * pixelizationWidthCorrection, - -(DIVS_VOLTAGE / 2) * pixelizationHeightCorrection, - (DIVS_VOLTAGE / 2) * pixelizationHeightCorrection, -1.0, 1.0); - - glMatrixMode(GL_MODELVIEW); -} - -/// \brief Set the generator that provides the vertex arrays. -/// \param generator Pointer to the GlGenerator class. -void GlScope::setGenerator(GlGenerator *generator) { - if (this->generator) - disconnect(this->generator, SIGNAL(graphsGenerated()), this, - SLOT(update())); - this->generator = generator; - connect(this->generator, SIGNAL(graphsGenerated()), this, SLOT(update())); + glPopMatrix(); } -/// \brief Set the zoom mode for this GlScope. -/// \param zoomed true magnifies the area between the markers. -void GlScope::setZoomMode(bool zoomed) { this->zoomed = zoomed; } - -/// \brief Draw the grid. -void GlScope::drawGrid() { - glDisable(GL_POINT_SMOOTH); - glDisable(GL_LINE_SMOOTH); - - QColor color; - // Grid - color = this->settings->view.color.screen.grid; - glColor4f(color.redF(), color.greenF(), color.blueF(), color.alphaF()); - glVertexPointer(2, GL_FLOAT, 0, &this->generator->vaGrid[0].front()); - glDrawArrays(GL_POINTS, 0, this->generator->vaGrid[0].size() / 2); - // Axes - color = settings->view.color.screen.axes; - glColor4f(color.redF(), color.greenF(), color.blueF(), color.alphaF()); - glVertexPointer(2, GL_FLOAT, 0, &this->generator->vaGrid[1].front()); - glDrawArrays(GL_LINES, 0, this->generator->vaGrid[1].size() / 2); - // Border - color = settings->view.color.screen.border; - glColor4f(color.redF(), color.greenF(), color.blueF(), color.alphaF()); - glVertexPointer(2, GL_FLOAT, 0, &this->generator->vaGrid[2].front()); - glDrawArrays(GL_LINE_LOOP, 0, this->generator->vaGrid[2].size() / 2); +bool GlScope::channelUsed(int mode, int channel) +{ + return (mode == Dso::CHANNELMODE_VOLTAGE) + ? settings->scope.voltage[channel].used + : settings->scope.spectrum[channel].used; } diff --git a/openhantek/src/glscope.h b/openhantek/src/glscope.h index 466999e..e82c8e5 100644 --- a/openhantek/src/glscope.h +++ b/openhantek/src/glscope.h @@ -1,33 +1,10 @@ -//////////////////////////////////////////////////////////////////////////////// -// -// OpenHantek -/// \file glscope.h -/// \brief Declares the GlScope class. -// -// Copyright (C) 2008, 2009 Oleg Khudyakov -// prcoder@potrebitel.ru -// Copyright (C) 2010 Oliver Haag -// oliver.haag@gmail.com -// -// This program is free software: you can redistribute it and/or modify it -// under the terms of the GNU General Public License as published by the Free -// Software Foundation, either version 3 of the License, or (at your option) -// any later version. -// -// This program is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -// more details. -// -// You should have received a copy of the GNU General Public License along with -// this program. If not, see . -// -//////////////////////////////////////////////////////////////////////////////// +// SPDX-License-Identifier: GPL-2.0+ -#ifndef GLSCOPE_H -#define GLSCOPE_H +#pragma once +#include #include +#include #if (QT_VERSION >= QT_VERSION_CHECK(5, 4, 0)) #include using GL_WIDGET_CLASS = QOpenGLWidget; @@ -46,28 +23,30 @@ class DsoSettings; /// \class GlScope glscope.h /// \brief OpenGL accelerated widget that displays the oscilloscope screen. class GlScope : public GL_WIDGET_CLASS { - Q_OBJECT + Q_OBJECT public: - GlScope(DsoSettings *settings, QWidget *parent = 0); - ~GlScope(); + /// \brief Initializes the scope widget. + /// \param settings The settings that should be used. + /// \param parent The parent widget. + GlScope(DsoSettings *settings, const GlGenerator *generator, QWidget *parent = 0); - void setGenerator(GlGenerator *generator); - void setZoomMode(bool zoomed); + void setZoomMode(bool zoomed); protected: - void initializeGL(); - void paintGL(); - void resizeGL(int width, int height); - - void drawGrid(); - + void initializeGL() override; + void paintGL() override; + void resizeGL(int width, int height) override; + + void drawGrid(); + void drawGraphDepth(int mode, int channel, int index); + void drawGraph(); + bool channelUsed(int mode, int channel); private: - GlGenerator *generator; - DsoSettings *settings; + DsoSettings *settings; + const GlGenerator *generator; + std::vector fadingFactor; - std::vector vaMarker[2]; - bool zoomed; + std::vector vaMarker[2]; + bool zoomed = false; }; - -#endif diff --git a/openhantek/src/hantek/dsosamples.h b/openhantek/src/hantek/dsosamples.h new file mode 100644 index 0000000..8bb75a3 --- /dev/null +++ b/openhantek/src/hantek/dsosamples.h @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#pragma once + +#include +#include +#include +#include + +struct DSOsamples { + std::vector> data; ///< Pointer to input data from device + double samplerate = 0.0; ///< The samplerate of the input data + bool append = false; ///< true, if waiting data should be appended + mutable QReadWriteLock lock; +}; diff --git a/openhantek/src/hantek/hantekdsocontrol.cpp b/openhantek/src/hantek/hantekdsocontrol.cpp index 49c5d09..241821c 100644 --- a/openhantek/src/hantek/hantekdsocontrol.cpp +++ b/openhantek/src/hantek/hantekdsocontrol.cpp @@ -40,6 +40,11 @@ const USBDevice *HantekDsoControl::getDevice() const return device; } +const DSOsamples &HantekDsoControl::getLastSamples() +{ + return result; +} + /// \brief Initializes the command buffers and lists. /// \param parent The parent widget. HantekDsoControl::HantekDsoControl(USBDevice* device) : device(device) { @@ -73,24 +78,24 @@ HantekDsoControl::HantekDsoControl(USBDevice* device) : device(device) { } // Set settings to default values - this->settings.samplerate.limits = &(this->specification.samplerate.single); - this->settings.samplerate.downsampler = 1; - this->settings.samplerate.current = 1e8; - this->settings.trigger.position = 0; - this->settings.trigger.point = 0; - this->settings.trigger.mode = Dso::TRIGGERMODE_NORMAL; - this->settings.trigger.slope = Dso::SLOPE_POSITIVE; - this->settings.trigger.special = false; - this->settings.trigger.source = 0; + settings.samplerate.limits = &(this->specification.samplerate.single); + settings.samplerate.downsampler = 1; + settings.samplerate.current = 1e8; + settings.trigger.position = 0; + settings.trigger.point = 0; + settings.trigger.mode = Dso::TRIGGERMODE_NORMAL; + settings.trigger.slope = Dso::SLOPE_POSITIVE; + settings.trigger.special = false; + settings.trigger.source = 0; for (unsigned int channel = 0; channel < HANTEK_CHANNELS; ++channel) { - this->settings.trigger.level[channel] = 0.0; - this->settings.voltage[channel].gain = 0; - this->settings.voltage[channel].offset = 0.0; - this->settings.voltage[channel].offsetReal = 0.0; - this->settings.voltage[channel].used = false; + settings.trigger.level[channel] = 0.0; + settings.voltage[channel].gain = 0; + settings.voltage[channel].offset = 0.0; + settings.voltage[channel].offsetReal = 0.0; + settings.voltage[channel].used = false; } - this->settings.recordLengthId = 1; - this->settings.usedChannels = 0; + settings.recordLengthId = 1; + settings.usedChannels = 0; // Special trigger sources this->specialTriggerSources << tr("EXT") << tr("EXT/10"); @@ -117,7 +122,7 @@ HantekDsoControl::HantekDsoControl(USBDevice* device) : device(device) { this->lastTriggerMode = (Dso::TriggerMode)-1; // Sample buffers - this->samples.resize(HANTEK_CHANNELS); + result.data.resize(HANTEK_CHANNELS); this->previousSampleCount = 0; @@ -390,9 +395,9 @@ HantekDsoControl::HantekDsoControl(USBDevice* device) : device(device) { this->specification.sampleSize = 8; break; } - this->settings.recordLengthId = 1; - this->settings.samplerate.limits = &(this->specification.samplerate.single); - this->settings.samplerate.downsampler = 1; + settings.recordLengthId = 1; + settings.samplerate.limits = &(this->specification.samplerate.single); + settings.samplerate.downsampler = 1; this->previousSampleCount = 0; // Get channel level data @@ -408,16 +413,16 @@ HantekDsoControl::HantekDsoControl(USBDevice* device) : device(device) { // Emit signals for initial settings emit availableRecordLengthsChanged( - this->settings.samplerate.limits->recordLengths); + settings.samplerate.limits->recordLengths); updateSamplerateLimits(); - emit recordLengthChanged(this->settings.samplerate.limits - ->recordLengths[this->settings.recordLengthId]); - if (this->settings.samplerate.limits - ->recordLengths[this->settings.recordLengthId] != UINT_MAX) - emit recordTimeChanged((double)this->settings.samplerate.limits - ->recordLengths[this->settings.recordLengthId] / - this->settings.samplerate.current); - emit samplerateChanged(this->settings.samplerate.current); + emit recordLengthChanged(settings.samplerate.limits + ->recordLengths[settings.recordLengthId]); + if (settings.samplerate.limits + ->recordLengths[settings.recordLengthId] != UINT_MAX) + emit recordTimeChanged((double)settings.samplerate.limits + ->recordLengths[settings.recordLengthId] / + settings.samplerate.current); + emit samplerateChanged(settings.samplerate.current); if (this->device->getUniqueModelID() == MODEL_DSO6022BE) { QList sampleSteps; @@ -445,7 +450,7 @@ unsigned int HantekDsoControl::getChannelCount() { return HANTEK_CHANNELS; } /// \brief Get available record lengths for this oscilloscope. /// \return The number of physical channels, empty list for continuous. QList *HantekDsoControl::getAvailableRecordLengths() { - return &this->settings.samplerate.limits->recordLengths; + return &settings.samplerate.limits->recordLengths; } /// \brief Get minimum samplerate for this oscilloscope. @@ -459,7 +464,7 @@ double HantekDsoControl::getMinSamplerate() { /// \return The maximum samplerate for the current configuration in S/s. double HantekDsoControl::getMaxSamplerate() { ControlSamplerateLimits *limits = - (this->settings.usedChannels <= 1) + (settings.usedChannels <= 1) ? &this->specification.samplerate.multi : &this->specification.samplerate.single; return limits->max; @@ -469,18 +474,18 @@ double HantekDsoControl::getMaxSamplerate() { void HantekDsoControl::updateInterval() { // Check the current oscilloscope state everytime 25% of the time the buffer // should be refilled - if (this->settings.samplerate.limits - ->recordLengths[this->settings.recordLengthId] == UINT_MAX) + if (settings.samplerate.limits + ->recordLengths[settings.recordLengthId] == UINT_MAX) cycleTime = (int)((double)this->device->getPacketSize() / - ((this->settings.samplerate.limits == + ((settings.samplerate.limits == &this->specification.samplerate.multi) ? 1 : HANTEK_CHANNELS) / - this->settings.samplerate.current * 250); + settings.samplerate.current * 250); else - cycleTime = (int)((double)this->settings.samplerate.limits - ->recordLengths[this->settings.recordLengthId] / - this->settings.samplerate.current * 250); + cycleTime = (int)((double)settings.samplerate.limits + ->recordLengths[settings.recordLengthId] / + settings.samplerate.current * 250); // Not more often than every 10 ms though but at least once every second cycleTime = qBound(10, cycleTime, 1000); @@ -519,7 +524,7 @@ int HantekDsoControl::getCaptureState() { if (errorCode < 0) return errorCode; - this->settings.trigger.point = + settings.trigger.point = this->calculateTriggerPoint(response.getTriggerPoint()); return (int)response.getCaptureState(); @@ -571,192 +576,194 @@ int HantekDsoControl::getSamples(bool process) { return errorCode; // Process the data only if we want it - if (process) { - // How much data did we really receive? - dataLength = errorCode; - if (this->specification.sampleSize > 8) - totalSampleCount = dataLength / 2; - else - totalSampleCount = dataLength; + if (!process) + return LIBUSB_SUCCESS; - QMutexLocker locker(&samplesMutex); + // How much data did we really receive? + dataLength = errorCode; + if (this->specification.sampleSize > 8) + totalSampleCount = dataLength / 2; + else + totalSampleCount = dataLength; + + // Convert channel data + if (fastRate) { + QWriteLocker locker(&result.lock); + result.samplerate = settings.samplerate.current; + result.append = settings.samplerate.limits->recordLengths[settings.recordLengthId] == UINT_MAX; + + // Fast rate mode, one channel is using all buffers + sampleCount = totalSampleCount; + int channel = 0; + for (; channel < HANTEK_CHANNELS; ++channel) { + if (settings.voltage[channel].used) + break; + } - // Convert channel data - if (fastRate) { - // Fast rate mode, one channel is using all buffers - sampleCount = totalSampleCount; - int channel = 0; - for (; channel < HANTEK_CHANNELS; ++channel) { - if (this->settings.voltage[channel].used) - break; - } + // Clear unused channels + for (int channelCounter = 0; channelCounter < HANTEK_CHANNELS; + ++channelCounter) + if (channelCounter != channel) { - // Clear unused channels - for (int channelCounter = 0; channelCounter < HANTEK_CHANNELS; - ++channelCounter) - if (channelCounter != channel) { + result.data[channelCounter].clear(); + } - this->samples[channelCounter].clear(); + if (channel < HANTEK_CHANNELS) { + // Resize sample vector + result.data[channel].resize(sampleCount); + + // Convert data from the oscilloscope and write it into the sample + // buffer + unsigned int bufferPosition = settings.trigger.point * 2; + if (this->specification.sampleSize > 8) { + // Additional most significant bits after the normal data + unsigned int extraBitsPosition; // Track the position of the extra + // bits in the additional byte + unsigned int extraBitsSize = + this->specification.sampleSize - 8; // Number of extra bits + unsigned short int extraBitsMask = + (0x00ff << extraBitsSize) & + 0xff00; // Mask for extra bits extraction + + for (unsigned int realPosition = 0; realPosition < sampleCount; + ++realPosition, ++bufferPosition) { + if (bufferPosition >= sampleCount) + bufferPosition %= sampleCount; + + extraBitsPosition = bufferPosition % HANTEK_CHANNELS; + + result.data[channel][realPosition] = + ((double)((unsigned short int)data[bufferPosition] + + (((unsigned short int) + data[sampleCount + bufferPosition - + extraBitsPosition] + << (8 - + (HANTEK_CHANNELS - 1 - extraBitsPosition) * + extraBitsSize)) & + extraBitsMask)) / + this->specification + .voltageLimit[channel] + [settings.voltage[channel].gain] - + settings.voltage[channel].offsetReal) * + this->specification + .gainSteps[settings.voltage[channel].gain]; } - - if (channel < HANTEK_CHANNELS) { + } else { + for (unsigned int realPosition = 0; realPosition < sampleCount; + ++realPosition, ++bufferPosition) { + if (bufferPosition >= sampleCount) + bufferPosition %= sampleCount; + + double dataBuf = (double)((int)data[bufferPosition]); + result.data[channel][realPosition] = + (dataBuf / + this->specification + .voltageLimit[channel] + [settings.voltage[channel].gain] - + settings.voltage[channel].offsetReal) * + this->specification + .gainSteps[settings.voltage[channel].gain]; + } + } + } + } else { + QWriteLocker locker(&result.lock); + result.samplerate = settings.samplerate.current; + result.append = settings.samplerate.limits->recordLengths[settings.recordLengthId] == UINT_MAX; + + // Normal mode, channels are using their separate buffers + sampleCount = totalSampleCount / HANTEK_CHANNELS; + // if device is 6022BE, drop heading & trailing samples + if (this->device->getUniqueModelID() == MODEL_DSO6022BE) + sampleCount -= (DROP_DSO6022_HEAD + DROP_DSO6022_TAIL); + for (int channel = 0; channel < HANTEK_CHANNELS; ++channel) { + if (settings.voltage[channel].used) { // Resize sample vector - this->samples[channel].resize(sampleCount); + if (result.data[channel].size() < sampleCount) { + result.data[channel].resize(sampleCount); + } // Convert data from the oscilloscope and write it into the sample // buffer - unsigned int bufferPosition = this->settings.trigger.point * 2; + unsigned int bufferPosition = settings.trigger.point * 2; if (this->specification.sampleSize > 8) { // Additional most significant bits after the normal data - unsigned int extraBitsPosition; // Track the position of the extra - // bits in the additional byte unsigned int extraBitsSize = this->specification.sampleSize - 8; // Number of extra bits unsigned short int extraBitsMask = (0x00ff << extraBitsSize) & 0xff00; // Mask for extra bits extraction + unsigned int extraBitsIndex = + 8 - + channel * 2; // Bit position offset for extra bits extraction for (unsigned int realPosition = 0; realPosition < sampleCount; - ++realPosition, ++bufferPosition) { - if (bufferPosition >= sampleCount) - bufferPosition %= sampleCount; - - extraBitsPosition = bufferPosition % HANTEK_CHANNELS; - - this->samples[channel][realPosition] = - ((double)((unsigned short int)data[bufferPosition] + - (((unsigned short int) - data[sampleCount + bufferPosition - - extraBitsPosition] - << (8 - - (HANTEK_CHANNELS - 1 - extraBitsPosition) * - extraBitsSize)) & - extraBitsMask)) / + ++realPosition, bufferPosition += HANTEK_CHANNELS) { + if (bufferPosition >= totalSampleCount) + bufferPosition %= totalSampleCount; + + result.data[channel][realPosition] = + ((double)((unsigned short int) + data[bufferPosition + HANTEK_CHANNELS - 1 - + channel] + + (((unsigned short int) + data[totalSampleCount + bufferPosition] + << extraBitsIndex) & + extraBitsMask)) / this->specification .voltageLimit[channel] - [this->settings.voltage[channel].gain] - - this->settings.voltage[channel].offsetReal) * + [settings.voltage[channel].gain] - + settings.voltage[channel].offsetReal) * this->specification - .gainSteps[this->settings.voltage[channel].gain]; + .gainSteps[settings.voltage[channel].gain]; } } else { + if (this->device->getUniqueModelID() == MODEL_DSO6022BE) { + bufferPosition += channel; + // if device is 6022BE, offset DROP_DSO6022_HEAD incrementally + bufferPosition += DROP_DSO6022_HEAD * 2; + } else + bufferPosition += HANTEK_CHANNELS - 1 - channel; + for (unsigned int realPosition = 0; realPosition < sampleCount; - ++realPosition, ++bufferPosition) { - if (bufferPosition >= sampleCount) - bufferPosition %= sampleCount; - - double dataBuf = (double)((int)data[bufferPosition]); - this->samples[channel][realPosition] = - (dataBuf / - this->specification - .voltageLimit[channel] - [this->settings.voltage[channel].gain] - - this->settings.voltage[channel].offsetReal) * - this->specification - .gainSteps[this->settings.voltage[channel].gain]; - } - } - } - } else { - // Normal mode, channels are using their separate buffers - sampleCount = totalSampleCount / HANTEK_CHANNELS; - // if device is 6022BE, drop heading & trailing samples - if (this->device->getUniqueModelID() == MODEL_DSO6022BE) - sampleCount -= (DROP_DSO6022_HEAD + DROP_DSO6022_TAIL); - for (int channel = 0; channel < HANTEK_CHANNELS; ++channel) { - if (this->settings.voltage[channel].used) { - // Resize sample vector - if (samples[channel].size() < sampleCount) { - this->samples[channel].resize(sampleCount); - } + ++realPosition, bufferPosition += HANTEK_CHANNELS) { + if (bufferPosition >= totalSampleCount) + bufferPosition %= totalSampleCount; - // Convert data from the oscilloscope and write it into the sample - // buffer - unsigned int bufferPosition = this->settings.trigger.point * 2; - if (this->specification.sampleSize > 8) { - // Additional most significant bits after the normal data - unsigned int extraBitsSize = - this->specification.sampleSize - 8; // Number of extra bits - unsigned short int extraBitsMask = - (0x00ff << extraBitsSize) & - 0xff00; // Mask for extra bits extraction - unsigned int extraBitsIndex = - 8 - - channel * 2; // Bit position offset for extra bits extraction - - for (unsigned int realPosition = 0; realPosition < sampleCount; - ++realPosition, bufferPosition += HANTEK_CHANNELS) { - if (bufferPosition >= totalSampleCount) - bufferPosition %= totalSampleCount; - - this->samples[channel][realPosition] = - ((double)((unsigned short int) - data[bufferPosition + HANTEK_CHANNELS - 1 - - channel] + - (((unsigned short int) - data[totalSampleCount + bufferPosition] - << extraBitsIndex) & - extraBitsMask)) / + if (this->device->getUniqueModelID() == MODEL_DSO6022BE) { + double dataBuf = (double)((int)(data[bufferPosition] - 0x83)); + result.data[channel][realPosition] = + (dataBuf / + this->specification + .voltageLimit[channel] + [settings.voltage[channel].gain]) * this->specification - .voltageLimit[channel] - [this->settings.voltage[channel].gain] - - this->settings.voltage[channel].offsetReal) * + .gainSteps[settings.voltage[channel].gain]; + } else { + double dataBuf = (double)((int)(data[bufferPosition])); + result.data[channel][realPosition] = + (dataBuf / + this->specification.voltageLimit + [channel][settings.voltage[channel].gain] - + settings.voltage[channel].offsetReal) * this->specification - .gainSteps[this->settings.voltage[channel].gain]; - } - } else { - if (this->device->getUniqueModelID() == MODEL_DSO6022BE) { - bufferPosition += channel; - // if device is 6022BE, offset DROP_DSO6022_HEAD incrementally - bufferPosition += DROP_DSO6022_HEAD * 2; - } else - bufferPosition += HANTEK_CHANNELS - 1 - channel; - - for (unsigned int realPosition = 0; realPosition < sampleCount; - ++realPosition, bufferPosition += HANTEK_CHANNELS) { - if (bufferPosition >= totalSampleCount) - bufferPosition %= totalSampleCount; - - if (this->device->getUniqueModelID() == MODEL_DSO6022BE) { - double dataBuf = (double)((int)(data[bufferPosition] - 0x83)); - this->samples[channel][realPosition] = - (dataBuf / - this->specification - .voltageLimit[channel] - [this->settings.voltage[channel].gain]) * - this->specification - .gainSteps[this->settings.voltage[channel].gain]; - } else { - double dataBuf = (double)((int)(data[bufferPosition])); - this->samples[channel][realPosition] = - (dataBuf / - this->specification.voltageLimit - [channel][this->settings.voltage[channel].gain] - - this->settings.voltage[channel].offsetReal) * - this->specification - .gainSteps[this->settings.voltage[channel].gain]; - } + .gainSteps[settings.voltage[channel].gain]; } } - } else { - // Clear unused channels - this->samples[channel].clear(); } + } else { + // Clear unused channels + result.data[channel].clear(); } } - -#ifdef DEBUG - static unsigned int id = 0; - ++id; - timestampDebug(QString("Received packet %1").arg(id)); -#endif - emit samplesAvailable( - &(this->samples), this->settings.samplerate.current, - this->settings.samplerate.limits - ->recordLengths[this->settings.recordLengthId] == UINT_MAX, - &(this->samplesMutex)); } + static unsigned int id = 0; + ++id; + timestampDebug(QString("Received packet %1").arg(id)); + + emit samplesAvailable(); + return errorCode; } @@ -787,18 +794,18 @@ double HantekDsoControl::getBestSamplerate(double samplerate, bool fastRate, // Get downsampling factor that would provide the requested rate double bestDownsampler = (double)limits->base / - this->specification.bufferDividers[this->settings.recordLengthId] / + this->specification.bufferDividers[settings.recordLengthId] / samplerate; // Base samplerate sufficient, or is the maximum better? if (bestDownsampler < 1.0 && (samplerate <= limits->max / this->specification - .bufferDividers[this->settings.recordLengthId] || + .bufferDividers[settings.recordLengthId] || !maximum)) { bestDownsampler = 0.0; bestSamplerate = limits->max / - this->specification.bufferDividers[this->settings.recordLengthId]; + this->specification.bufferDividers[settings.recordLengthId]; } else { switch (this->specification.command.bulk.setSamplerate) { case BULK_SETTRIGGERANDSAMPLERATE: @@ -866,7 +873,7 @@ double HantekDsoControl::getBestSamplerate(double samplerate, bool fastRate, bestSamplerate = limits->base / bestDownsampler / - this->specification.bufferDividers[this->settings.recordLengthId]; + this->specification.bufferDividers[settings.recordLengthId]; } if (downsampler) @@ -878,11 +885,8 @@ double HantekDsoControl::getBestSamplerate(double samplerate, bool fastRate, /// \param fastRate Is set to the state of the fast rate mode when provided. /// \return The total number of samples the scope should return. unsigned int HantekDsoControl::getSampleCount(bool *fastRate) { - unsigned int totalSampleCount = - this->settings.samplerate.limits - ->recordLengths[this->settings.recordLengthId]; - bool fastRateEnabled = - this->settings.samplerate.limits == &this->specification.samplerate.multi; + unsigned int totalSampleCount = settings.samplerate.limits->recordLengths[settings.recordLengthId]; + bool fastRateEnabled = settings.samplerate.limits == &this->specification.samplerate.multi; if (totalSampleCount == UINT_MAX) { // Roll mode @@ -905,7 +909,7 @@ unsigned int HantekDsoControl::getSampleCount(bool *fastRate) { /// \return The record length that has been set, 0 on error. unsigned int HantekDsoControl::updateRecordLength(unsigned int index) { if (index >= - (unsigned int)this->settings.samplerate.limits->recordLengths.size()) + (unsigned int)settings.samplerate.limits->recordLengths.size()) return 0; switch (this->specification.command.bulk.setRecordLength) { @@ -947,9 +951,9 @@ unsigned int HantekDsoControl::updateRecordLength(unsigned int index) { // Check if the divider has changed and adapt samplerate limits accordingly bool bDividerChanged = this->specification.bufferDividers[index] != - this->specification.bufferDividers[this->settings.recordLengthId]; + this->specification.bufferDividers[settings.recordLengthId]; - this->settings.recordLengthId = index; + settings.recordLengthId = index; if (bDividerChanged) { this->updateSamplerateLimits(); @@ -958,7 +962,7 @@ unsigned int HantekDsoControl::updateRecordLength(unsigned int index) { this->restoreTargets(); } - return this->settings.samplerate.limits->recordLengths[index]; + return settings.samplerate.limits->recordLengths[index]; } /// \brief Sets the samplerate based on the parameters calculated by @@ -1068,49 +1072,49 @@ unsigned int HantekDsoControl::updateSamplerate(unsigned int downsampler, } // Update settings - bool fastRateChanged = fastRate != (this->settings.samplerate.limits == + bool fastRateChanged = fastRate != (settings.samplerate.limits == &this->specification.samplerate.multi); if (fastRateChanged) { - this->settings.samplerate.limits = limits; + settings.samplerate.limits = limits; } - this->settings.samplerate.downsampler = downsampler; + settings.samplerate.downsampler = downsampler; if (downsampler) - this->settings.samplerate.current = - this->settings.samplerate.limits->base / - this->specification.bufferDividers[this->settings.recordLengthId] / + settings.samplerate.current = + settings.samplerate.limits->base / + this->specification.bufferDividers[settings.recordLengthId] / downsampler; else - this->settings.samplerate.current = - this->settings.samplerate.limits->max / - this->specification.bufferDividers[this->settings.recordLengthId]; + settings.samplerate.current = + settings.samplerate.limits->max / + this->specification.bufferDividers[settings.recordLengthId]; // Update dependencies - this->setPretriggerPosition(this->settings.trigger.position); + this->setPretriggerPosition(settings.trigger.position); // Emit signals for changed settings if (fastRateChanged) { emit availableRecordLengthsChanged( - this->settings.samplerate.limits->recordLengths); + settings.samplerate.limits->recordLengths); emit recordLengthChanged( - this->settings.samplerate.limits - ->recordLengths[this->settings.recordLengthId]); + settings.samplerate.limits + ->recordLengths[settings.recordLengthId]); } // Check for Roll mode - if (this->settings.samplerate.limits - ->recordLengths[this->settings.recordLengthId] != UINT_MAX) - emit recordTimeChanged((double)this->settings.samplerate.limits - ->recordLengths[this->settings.recordLengthId] / - this->settings.samplerate.current); - emit samplerateChanged(this->settings.samplerate.current); + if (settings.samplerate.limits + ->recordLengths[settings.recordLengthId] != UINT_MAX) + emit recordTimeChanged((double)settings.samplerate.limits + ->recordLengths[settings.recordLengthId] / + settings.samplerate.current); + emit samplerateChanged(settings.samplerate.current); return downsampler; } /// \brief Restore the samplerate/timebase targets after divider updates. void HantekDsoControl::restoreTargets() { - if (this->settings.samplerate.target.samplerateSet) + if (settings.samplerate.target.samplerateSet) this->setSamplerate(); else this->setRecordTime(); @@ -1121,15 +1125,15 @@ void HantekDsoControl::updateSamplerateLimits() { // Works only if the minimum samplerate for normal mode is lower than for fast // rate mode, which is the case for all models ControlSamplerateLimits *limits = - (this->settings.usedChannels <= 1) + (settings.usedChannels <= 1) ? &this->specification.samplerate.multi : &this->specification.samplerate.single; emit samplerateLimitsChanged( (double)this->specification.samplerate.single.base / this->specification.samplerate.single.maxDownsampler / - this->specification.bufferDividers[this->settings.recordLengthId], + this->specification.bufferDividers[settings.recordLengthId], limits->max / - this->specification.bufferDividers[this->settings.recordLengthId]); + this->specification.bufferDividers[settings.recordLengthId]); } /// \brief Sets the size of the oscilloscopes sample buffer. @@ -1143,12 +1147,12 @@ unsigned int HantekDsoControl::setRecordLength(unsigned int index) { return 0; this->restoreTargets(); - this->setPretriggerPosition(this->settings.trigger.position); + this->setPretriggerPosition(settings.trigger.position); - emit recordLengthChanged(this->settings.samplerate.limits - ->recordLengths[this->settings.recordLengthId]); - return this->settings.samplerate.limits - ->recordLengths[this->settings.recordLengthId]; + emit recordLengthChanged(settings.samplerate.limits + ->recordLengths[settings.recordLengthId]); + return settings.samplerate.limits + ->recordLengths[settings.recordLengthId]; } /// \brief Sets the samplerate of the oscilloscope. @@ -1160,20 +1164,20 @@ double HantekDsoControl::setSamplerate(double samplerate) { return 0.0; if (samplerate == 0.0) { - samplerate = this->settings.samplerate.target.samplerate; + samplerate = settings.samplerate.target.samplerate; } else { - this->settings.samplerate.target.samplerate = samplerate; - this->settings.samplerate.target.samplerateSet = true; + settings.samplerate.target.samplerate = samplerate; + settings.samplerate.target.samplerateSet = true; } if (this->device->getUniqueModelID() != MODEL_DSO6022BE) { // When possible, enable fast rate if it is required to reach the requested // samplerate bool fastRate = - (this->settings.usedChannels <= 1) && + (settings.usedChannels <= 1) && (samplerate > this->specification.samplerate.single.max / - this->specification.bufferDividers[this->settings.recordLengthId]); + this->specification.bufferDividers[settings.recordLengthId]); // What is the nearest, at least as high samplerate the scope can provide? unsigned int downsampler = 0; @@ -1196,18 +1200,18 @@ double HantekDsoControl::setSamplerate(double samplerate) { static_cast(this->control[CONTROLINDEX_SETTIMEDIV]) ->setDiv(this->specification.sampleDiv[sampleId]); this->controlPending[CONTROLINDEX_SETTIMEDIV] = true; - this->settings.samplerate.current = samplerate; + settings.samplerate.current = samplerate; // Provide margin for SW trigger unsigned int sampleMargin = 2000; // Check for Roll mode - if (this->settings.samplerate.limits - ->recordLengths[this->settings.recordLengthId] != UINT_MAX) + if (settings.samplerate.limits + ->recordLengths[settings.recordLengthId] != UINT_MAX) emit recordTimeChanged( - (double)(this->settings.samplerate.limits - ->recordLengths[this->settings.recordLengthId] - sampleMargin) / - this->settings.samplerate.current); - emit samplerateChanged(this->settings.samplerate.current); + (double)(settings.samplerate.limits + ->recordLengths[settings.recordLengthId] - sampleMargin) / + settings.samplerate.current); + emit samplerateChanged(settings.samplerate.current); return samplerate; } @@ -1222,26 +1226,26 @@ double HantekDsoControl::setRecordTime(double duration) { return 0.0; if (duration == 0.0) { - duration = this->settings.samplerate.target.duration; + duration = settings.samplerate.target.duration; } else { - this->settings.samplerate.target.duration = duration; - this->settings.samplerate.target.samplerateSet = false; + settings.samplerate.target.duration = duration; + settings.samplerate.target.samplerateSet = false; } if (this->device->getUniqueModelID() != MODEL_DSO6022BE) { // Calculate the maximum samplerate that would still provide the requested // duration double maxSamplerate = (double)this->specification.samplerate.single - .recordLengths[this->settings.recordLengthId] / + .recordLengths[settings.recordLengthId] / duration; // When possible, enable fast rate if the record time can't be set that low // to improve resolution bool fastRate = - (this->settings.usedChannels <= 1) && + (settings.usedChannels <= 1) && (maxSamplerate >= this->specification.samplerate.multi.base / - this->specification.bufferDividers[this->settings.recordLengthId]); + this->specification.bufferDividers[settings.recordLengthId]); // What is the nearest, at most as high samplerate the scope can provide? unsigned int downsampler = 0; @@ -1252,8 +1256,8 @@ double HantekDsoControl::setRecordTime(double duration) { if (this->updateSamplerate(downsampler, fastRate) == UINT_MAX) return 0.0; else { - return (double)this->settings.samplerate.limits - ->recordLengths[this->settings.recordLengthId] / + return (double)settings.samplerate.limits + ->recordLengths[settings.recordLengthId] / bestSamplerate; } } else { @@ -1278,11 +1282,11 @@ double HantekDsoControl::setRecordTime(double duration) { static_cast(this->control[CONTROLINDEX_SETTIMEDIV]) ->setDiv(this->specification.sampleDiv[sampleId]); this->controlPending[CONTROLINDEX_SETTIMEDIV] = true; - this->settings.samplerate.current = + settings.samplerate.current = this->specification.sampleSteps[sampleId]; - emit samplerateChanged(this->settings.samplerate.current); - return this->settings.samplerate.current; + emit samplerateChanged(settings.samplerate.current); + return settings.samplerate.current; } } @@ -1298,19 +1302,19 @@ int HantekDsoControl::setChannelUsed(unsigned int channel, bool used) { return Dso::ERROR_PARAMETER; // Update settings - this->settings.voltage[channel].used = used; + settings.voltage[channel].used = used; unsigned int channelCount = 0; for (int channelCounter = 0; channelCounter < HANTEK_CHANNELS; ++channelCounter) { - if (this->settings.voltage[channelCounter].used) + if (settings.voltage[channelCounter].used) ++channelCount; } // Calculate the UsedChannels field for the command unsigned char usedChannels = USED_CH1; - if (this->settings.voltage[1].used) { - if (this->settings.voltage[0].used) { + if (settings.voltage[1].used) { + if (settings.voltage[0].used) { usedChannels = USED_CH1CH2; } else { // DSO-2250 uses a different value for channel 2 @@ -1352,8 +1356,8 @@ int HantekDsoControl::setChannelUsed(unsigned int channel, bool used) { // Check if fast rate mode availability changed bool fastRateChanged = - (this->settings.usedChannels <= 1) != (channelCount <= 1); - this->settings.usedChannels = channelCount; + (settings.usedChannels <= 1) != (channelCount <= 1); + settings.usedChannels = channelCount; if (fastRateChanged) this->updateSamplerateLimits(); @@ -1430,9 +1434,9 @@ double HantekDsoControl::setGain(unsigned int channel, double gain) { this->controlPending[CONTROLINDEX_SETRELAYS] = true; } - this->settings.voltage[channel].gain = gainId; + settings.voltage[channel].gain = gainId; - this->setOffset(channel, this->settings.voltage[channel].offset); + this->setOffset(channel, settings.voltage[channel].offset); return this->specification.gainSteps[gainId]; } @@ -1453,23 +1457,23 @@ double HantekDsoControl::setOffset(unsigned int channel, double offset) { unsigned short int minimum = ((unsigned short int)*((unsigned char *)&( this->specification - .offsetLimit[channel][this->settings.voltage[channel].gain] + .offsetLimit[channel][settings.voltage[channel].gain] [OFFSET_START])) << 8) + *((unsigned char *)&( this->specification - .offsetLimit[channel][this->settings.voltage[channel].gain] + .offsetLimit[channel][settings.voltage[channel].gain] [OFFSET_START]) + 1); unsigned short int maximum = ((unsigned short int)*((unsigned char *)&( this->specification - .offsetLimit[channel][this->settings.voltage[channel].gain] + .offsetLimit[channel][settings.voltage[channel].gain] [OFFSET_END])) << 8) + *((unsigned char *)&( this->specification - .offsetLimit[channel][this->settings.voltage[channel].gain] + .offsetLimit[channel][settings.voltage[channel].gain] [OFFSET_END]) + 1); unsigned short int offsetValue = offset * (maximum - minimum) + minimum + 0.5; @@ -1484,10 +1488,10 @@ double HantekDsoControl::setOffset(unsigned int channel, double offset) { this->controlPending[CONTROLINDEX_SETOFFSET] = true; } - this->settings.voltage[channel].offset = offset; - this->settings.voltage[channel].offsetReal = offsetReal; + settings.voltage[channel].offset = offset; + settings.voltage[channel].offsetReal = offsetReal; - this->setTriggerLevel(channel, this->settings.trigger.level[channel]); + this->setTriggerLevel(channel, settings.trigger.level[channel]); return offsetReal; } @@ -1501,7 +1505,7 @@ int HantekDsoControl::setTriggerMode(Dso::TriggerMode mode) { if (mode < Dso::TRIGGERMODE_AUTO || mode >= Dso::TRIGGERMODE_COUNT) return Dso::ERROR_PARAMETER; - this->settings.trigger.mode = mode; + settings.trigger.mode = mode; return Dso::ERROR_NONE; } @@ -1551,8 +1555,8 @@ int HantekDsoControl::setTriggerSource(bool special, unsigned int id) { ->setTrigger(special); this->controlPending[CONTROLINDEX_SETRELAYS] = true; - this->settings.trigger.special = special; - this->settings.trigger.source = id; + settings.trigger.special = special; + settings.trigger.source = id; // Apply trigger level of the new source if (special) { @@ -1561,7 +1565,7 @@ int HantekDsoControl::setTriggerSource(bool special, unsigned int id) { ->setTrigger(0x7f); this->controlPending[CONTROLINDEX_SETOFFSET] = true; } else - this->setTriggerLevel(id, this->settings.trigger.level[id]); + this->setTriggerLevel(id, settings.trigger.level[id]); return Dso::ERROR_NONE; } @@ -1589,23 +1593,23 @@ double HantekDsoControl::setTriggerLevel(unsigned int channel, double level) { minimum = ((unsigned short int)*((unsigned char *)&( this->specification - .offsetLimit[channel][this->settings.voltage[channel].gain] + .offsetLimit[channel][settings.voltage[channel].gain] [OFFSET_START])) << 8) + *((unsigned char *)&( this->specification - .offsetLimit[channel][this->settings.voltage[channel].gain] + .offsetLimit[channel][settings.voltage[channel].gain] [OFFSET_START]) + 1); maximum = ((unsigned short int)*((unsigned char *)&( this->specification - .offsetLimit[channel][this->settings.voltage[channel].gain] + .offsetLimit[channel][settings.voltage[channel].gain] [OFFSET_END])) << 8) + *((unsigned char *)&( this->specification - .offsetLimit[channel][this->settings.voltage[channel].gain] + .offsetLimit[channel][settings.voltage[channel].gain] [OFFSET_END]) + 1); break; @@ -1620,18 +1624,18 @@ double HantekDsoControl::setTriggerLevel(unsigned int channel, double level) { // Never get out of the limits unsigned short int levelValue = qBound( (long int)minimum, - (long int)((this->settings.voltage[channel].offsetReal + + (long int)((settings.voltage[channel].offsetReal + level / this->specification - .gainSteps[this->settings.voltage[channel].gain]) * + .gainSteps[settings.voltage[channel].gain]) * (maximum - minimum) + 0.5) + minimum, (long int)maximum); // Check if the set channel is the trigger source - if (!this->settings.trigger.special && - channel == this->settings.trigger.source && + if (!settings.trigger.special && + channel == settings.trigger.source && this->device->getUniqueModelID() != MODEL_DSO6022BE) { // SetOffset control command for trigger level static_cast(this->control[CONTROLINDEX_SETOFFSET]) @@ -1641,10 +1645,10 @@ double HantekDsoControl::setTriggerLevel(unsigned int channel, double level) { /// \todo Get alternating trigger in here - this->settings.trigger.level[channel] = level; + settings.trigger.level[channel] = level; return (double)((levelValue - minimum) / (maximum - minimum) - - this->settings.voltage[channel].offsetReal) * - this->specification.gainSteps[this->settings.voltage[channel].gain]; + settings.voltage[channel].offsetReal) * + this->specification.gainSteps[settings.voltage[channel].gain]; } /// \brief Set the trigger slope. @@ -1686,7 +1690,7 @@ int HantekDsoControl::setTriggerSlope(Dso::Slope slope) { return Dso::ERROR_UNSUPPORTED; } - this->settings.trigger.slope = slope; + settings.trigger.slope = slope; return Dso::ERROR_NONE; } @@ -1703,13 +1707,13 @@ double HantekDsoControl::setPretriggerPosition(double position) { return -2; // All trigger positions are measured in samples - unsigned int positionSamples = position * this->settings.samplerate.current; + unsigned int positionSamples = position * settings.samplerate.current; unsigned int recordLength = - this->settings.samplerate.limits - ->recordLengths[this->settings.recordLengthId]; + settings.samplerate.limits + ->recordLengths[settings.recordLengthId]; bool rollMode = recordLength == UINT_MAX; // Fast rate mode uses both channels - if (this->settings.samplerate.limits == &this->specification.samplerate.multi) + if (settings.samplerate.limits == &this->specification.samplerate.multi) positionSamples /= HANTEK_CHANNELS; switch (this->specification.command.bulk.setPretrigger) { @@ -1758,8 +1762,8 @@ double HantekDsoControl::setPretriggerPosition(double position) { return Dso::ERROR_UNSUPPORTED; } - this->settings.trigger.position = position; - return (double)positionSamples / this->settings.samplerate.current; + settings.trigger.position = position; + return (double)positionSamples / settings.samplerate.current; } #ifdef DEBUG @@ -1888,8 +1892,8 @@ void HantekDsoControl::run() { } // State machine for the device communication - if (this->settings.samplerate.limits - ->recordLengths[this->settings.recordLengthId] == UINT_MAX) { + if (settings.samplerate.limits + ->recordLengths[settings.recordLengthId] == UINT_MAX) { // Roll mode this->captureState = CAPTURE_WAITING; bool toNextState = true; @@ -1964,7 +1968,7 @@ void HantekDsoControl::run() { #endif // Check if we're in single trigger mode - if (this->settings.trigger.mode == Dso::TRIGGERMODE_SINGLE && + if (settings.trigger.mode == Dso::TRIGGERMODE_SINGLE && this->_samplingStarted) this->stopSampling(); @@ -2016,7 +2020,7 @@ void HantekDsoControl::run() { #endif // Check if we're in single trigger mode - if (this->settings.trigger.mode == Dso::TRIGGERMODE_SINGLE && + if (settings.trigger.mode == Dso::TRIGGERMODE_SINGLE && this->_samplingStarted) this->stopSampling(); @@ -2032,12 +2036,12 @@ void HantekDsoControl::run() { this->previousSampleCount = this->getSampleCount(); if (this->_samplingStarted && - this->lastTriggerMode == this->settings.trigger.mode) { + this->lastTriggerMode == settings.trigger.mode) { ++this->cycleCounter; if (this->cycleCounter == this->startCycle && - this->settings.samplerate.limits - ->recordLengths[this->settings.recordLengthId] != + settings.samplerate.limits + ->recordLengths[settings.recordLengthId] != UINT_MAX) { // Buffer refilled completely since start of sampling, enable the // trigger now @@ -2054,7 +2058,7 @@ void HantekDsoControl::run() { timestampDebug("Enabling trigger"); #endif } else if (this->cycleCounter >= 8 + this->startCycle && - this->settings.trigger.mode == Dso::TRIGGERMODE_AUTO) { + settings.trigger.mode == Dso::TRIGGERMODE_AUTO) { // Force triggering errorCode = this->device->bulkCommand(this->command[BULK_FORCETRIGGER]); @@ -2090,8 +2094,8 @@ void HantekDsoControl::run() { this->_samplingStarted = true; this->cycleCounter = 0; - this->startCycle = this->settings.trigger.position * 1000 / cycleTime + 1; - this->lastTriggerMode = this->settings.trigger.mode; + this->startCycle = settings.trigger.position * 1000 / cycleTime + 1; + this->lastTriggerMode = settings.trigger.mode; break; case CAPTURE_SAMPLING: diff --git a/openhantek/src/hantek/hantekdsocontrol.h b/openhantek/src/hantek/hantekdsocontrol.h index ee111aa..73b7523 100644 --- a/openhantek/src/hantek/hantekdsocontrol.h +++ b/openhantek/src/hantek/hantekdsocontrol.h @@ -7,6 +7,7 @@ #include "bulkStructs.h" #include "stateStructs.h" #include "utils/printutils.h" +#include "dsosamples.h" #include @@ -17,14 +18,12 @@ class USBDevice; -////////////////////////////////////////////////////////////////////////////// -/// \class Control hantek/control.h /// \brief The DsoControl abstraction layer for %Hantek USB DSOs. class HantekDsoControl : public QObject { Q_OBJECT public: - /** + /** * Creates a dsoControl object. The actual event loop / timer is not started. * You can optionally create a thread and move the created object to the thread. * You need to call updateInterval() to start the timer. @@ -44,15 +43,13 @@ public: const QStringList *getSpecialTriggerSources(); const USBDevice* getDevice() const; + const DSOsamples& getLastSamples(); signals: void samplingStarted(); ///< The oscilloscope started sampling/waiting for trigger void samplingStopped(); ///< The oscilloscope stopped sampling/waiting for trigger - void statusMessage(const QString &message, - int timeout); ///< Status message about the oscilloscope - void samplesAvailable(const std::vector> *data, - double samplerate, bool append, - QMutex *mutex); ///< New sample data is available + void statusMessage(const QString &message, int timeout); ///< Status message about the oscilloscope + void samplesAvailable(); ///< New sample data is available void availableRecordLengthsChanged(const QList &recordLengths); ///< The available record lengths, empty list for continuous void samplerateLimitsChanged(double minimum, double maximum); ///< The minimum or maximum samplerate has changed @@ -93,10 +90,8 @@ protected: Hantek::ControlSettings settings; ///< The current settings of the device // Results - std::vector> samples; ///< Sample data vectors sent to the data analyzer - unsigned int previousSampleCount; ///< The expected total number of samples at - ///the last check before sampling started - QMutex samplesMutex; ///< Mutex for the sample data + DSOsamples result; + unsigned int previousSampleCount; ///< The expected total number of samples at the last check before sampling started // State of the communication thread int captureState = Hantek::CAPTURE_WAITING; diff --git a/openhantek/src/main.cpp b/openhantek/src/main.cpp index 517b648..da9edb4 100644 --- a/openhantek/src/main.cpp +++ b/openhantek/src/main.cpp @@ -119,25 +119,38 @@ int main(int argc, char *argv[]) { //////// Create DSO control object and move it to a separate thread //////// QThread dsoControlThread; - std::shared_ptr dsoControl(new HantekDsoControl(device.get())); - dsoControl->moveToThread(&dsoControlThread); - QObject::connect(&dsoControlThread,&QThread::started,dsoControl.get(),&HantekDsoControl::run); - QObject::connect(dsoControl.get(), &HantekDsoControl::communicationError, QCoreApplication::instance(), &QCoreApplication::quit); - QObject::connect(device.get(), &USBDevice::deviceDisconnected, QCoreApplication::instance(), &QCoreApplication::quit); + dsoControlThread.setObjectName("dsoControlThread"); + HantekDsoControl dsoControl(device.get()); + dsoControl.moveToThread(&dsoControlThread); + QObject::connect(&dsoControlThread,&QThread::started, &dsoControl,&HantekDsoControl::run); + QObject::connect(&dsoControl, &HantekDsoControl::communicationError, + QCoreApplication::instance(), &QCoreApplication::quit); + QObject::connect(device.get(), &USBDevice::deviceDisconnected, + QCoreApplication::instance(), &QCoreApplication::quit); //////// Create data analyser object //////// - std::shared_ptr dataAnalyser(new DataAnalyzer()); + QThread dataAnalyzerThread; + dataAnalyzerThread.setObjectName("dataAnalyzerThread"); + DataAnalyzer dataAnalyser; + dataAnalyser.setSourceData(&dsoControl.getLastSamples()); + dataAnalyser.moveToThread(&dataAnalyzerThread); + QObject::connect(&dsoControl, &HantekDsoControl::samplesAvailable, + &dataAnalyser, &DataAnalyzer::samplesAvailable); //////// Create main window //////// - OpenHantekMainWindow *openHantekMainWindow = new OpenHantekMainWindow(dsoControl, dataAnalyser); + OpenHantekMainWindow *openHantekMainWindow = new OpenHantekMainWindow(&dsoControl, &dataAnalyser); openHantekMainWindow->show(); //////// Start DSO thread and go into GUI main loop + dataAnalyzerThread.start(); dsoControlThread.start(); int res = openHantekApplication.exec(); //////// Clean up //////// dsoControlThread.quit(); dsoControlThread.wait(10000); + + dataAnalyzerThread.quit(); + dataAnalyzerThread.wait(10000); return res; } diff --git a/openhantek/src/mainwindow.cpp b/openhantek/src/mainwindow.cpp index a84ec1d..ad8e2c3 100644 --- a/openhantek/src/mainwindow.cpp +++ b/openhantek/src/mainwindow.cpp @@ -28,7 +28,7 @@ /// \brief Initializes the gui elements of the main window. /// \param parent The parent widget. /// \param flags Flags for the window manager. -OpenHantekMainWindow::OpenHantekMainWindow(std::shared_ptr dsoControl, std::shared_ptr dataAnalyzer) +OpenHantekMainWindow::OpenHantekMainWindow(HantekDsoControl *dsoControl, DataAnalyzer *dataAnalyzer) :dsoControl(dsoControl),dataAnalyzer(dataAnalyzer) { // Window title @@ -45,7 +45,11 @@ OpenHantekMainWindow::OpenHantekMainWindow(std::shared_ptr dso createDockWindows(); // Central oszilloscope widget - dsoWidget = new DsoWidget(settings, dataAnalyzer.get()); + dataAnalyzer->applySettings(&settings->scope); + dsoWidget = new DsoWidget(settings); + connect(dataAnalyzer, &DataAnalyzer::analyzed, [this]() { + dsoWidget->showNewData(this->dataAnalyzer->getNextResult()); + }); setCentralWidget(dsoWidget); // Subroutines for window elements @@ -260,91 +264,85 @@ void OpenHantekMainWindow::createDockWindows() { /// \brief Connect general signals and device management signals. void OpenHantekMainWindow::connectSignals() { // Connect general signals - connect(this, SIGNAL(settingsChanged()), this, SLOT(applySettings())); + connect(this, &OpenHantekMainWindow::settingsChanged, this, &OpenHantekMainWindow::applySettings); // connect(dsoWidget, SIGNAL(stopped()), this, SLOT(stopped())); - connect(dsoControl.get(), SIGNAL(statusMessage(QString, int)), - statusBar(), SLOT(showMessage(QString, int))); - connect(dsoControl.get(), - SIGNAL(samplesAvailable(const std::vector> *, - double, bool, QMutex *)), - dataAnalyzer.get(), - SLOT(analyze(const std::vector> *, double, bool, - QMutex *))); + connect(dsoControl, &HantekDsoControl::statusMessage, + statusBar(), &QStatusBar::showMessage); // Connect signals to DSO controller and widget - connect(horizontalDock, SIGNAL(samplerateChanged(double)), this, - SLOT(samplerateSelected())); - connect(horizontalDock, SIGNAL(timebaseChanged(double)), this, - SLOT(timebaseSelected())); - connect(horizontalDock, SIGNAL(frequencybaseChanged(double)), - dsoWidget, SLOT(updateFrequencybase(double))); - connect(horizontalDock, SIGNAL(recordLengthChanged(unsigned long)), - this, SLOT(recordLengthSelected(unsigned long))); + connect(horizontalDock, &HorizontalDock::samplerateChanged, this, + &OpenHantekMainWindow::samplerateSelected); + connect(horizontalDock, &HorizontalDock::timebaseChanged, this, + &OpenHantekMainWindow::timebaseSelected); + connect(horizontalDock, &HorizontalDock::frequencybaseChanged, + dsoWidget, &DsoWidget::updateFrequencybase); + connect(horizontalDock, &HorizontalDock::recordLengthChanged, + this, &OpenHantekMainWindow::recordLengthSelected); // connect(horizontalDock, SIGNAL(formatChanged(HorizontalFormat)), // dsoWidget, SLOT(horizontalFormatChanged(HorizontalFormat))); - connect(triggerDock, SIGNAL(modeChanged(Dso::TriggerMode)), - dsoControl.get(), SLOT(setTriggerMode(Dso::TriggerMode))); - connect(triggerDock, SIGNAL(modeChanged(Dso::TriggerMode)), - dsoWidget, SLOT(updateTriggerMode())); - connect(triggerDock, SIGNAL(sourceChanged(bool, unsigned int)), - dsoControl.get(), SLOT(setTriggerSource(bool, unsigned int))); - connect(triggerDock, SIGNAL(sourceChanged(bool, unsigned int)), - dsoWidget, SLOT(updateTriggerSource())); - connect(triggerDock, SIGNAL(slopeChanged(Dso::Slope)), dsoControl.get(), - SLOT(setTriggerSlope(Dso::Slope))); - connect(triggerDock, SIGNAL(slopeChanged(Dso::Slope)), dsoWidget, - SLOT(updateTriggerSlope())); - connect(dsoWidget, SIGNAL(triggerPositionChanged(double)), - dsoControl.get(), SLOT(setPretriggerPosition(double))); - connect(dsoWidget, SIGNAL(triggerLevelChanged(unsigned int, double)), - dsoControl.get(), SLOT(setTriggerLevel(unsigned int, double))); - - connect(voltageDock, SIGNAL(usedChanged(unsigned int, bool)), this, - SLOT(updateUsed(unsigned int))); - connect(voltageDock, SIGNAL(usedChanged(unsigned int, bool)), - dsoWidget, SLOT(updateVoltageUsed(unsigned int, bool))); + connect(triggerDock, &TriggerDock::modeChanged, + dsoControl, &HantekDsoControl::setTriggerMode); + connect(triggerDock, &TriggerDock::modeChanged, + dsoWidget, &DsoWidget::updateTriggerMode); + connect(triggerDock, &TriggerDock::sourceChanged, + dsoControl, &HantekDsoControl::setTriggerSource); + connect(triggerDock, &TriggerDock::sourceChanged, + dsoWidget, &DsoWidget::updateTriggerSource); + connect(triggerDock, &TriggerDock::slopeChanged, dsoControl, + &HantekDsoControl::setTriggerSlope); + connect(triggerDock, &TriggerDock::slopeChanged, dsoWidget, + &DsoWidget::updateTriggerSlope); + connect(dsoWidget, &DsoWidget::triggerPositionChanged, + dsoControl, &HantekDsoControl::setPretriggerPosition); + connect(dsoWidget, &DsoWidget::triggerLevelChanged, + dsoControl, &HantekDsoControl::setTriggerLevel); + + connect(voltageDock, &VoltageDock::usedChanged, this, + &OpenHantekMainWindow::updateUsed); + connect(voltageDock, &VoltageDock::usedChanged, + dsoWidget, &DsoWidget::updateVoltageUsed); connect(voltageDock, - SIGNAL(couplingChanged(unsigned int, Dso::Coupling)), - dsoControl.get(), SLOT(setCoupling(unsigned int, Dso::Coupling))); + &VoltageDock::couplingChanged, + dsoControl, &HantekDsoControl::setCoupling); connect(voltageDock, - SIGNAL(couplingChanged(unsigned int, Dso::Coupling)), dsoWidget, - SLOT(updateVoltageCoupling(unsigned int))); - connect(voltageDock, SIGNAL(modeChanged(Dso::MathMode)), - dsoWidget, SLOT(updateMathMode())); - connect(voltageDock, SIGNAL(gainChanged(unsigned int, double)), this, - SLOT(updateVoltageGain(unsigned int))); - connect(voltageDock, SIGNAL(gainChanged(unsigned int, double)), - dsoWidget, SLOT(updateVoltageGain(unsigned int))); - connect(dsoWidget, SIGNAL(offsetChanged(unsigned int, double)), this, - SLOT(updateOffset(unsigned int))); - - connect(spectrumDock, SIGNAL(usedChanged(unsigned int, bool)), this, - SLOT(updateUsed(unsigned int))); - connect(spectrumDock, SIGNAL(usedChanged(unsigned int, bool)), - dsoWidget, SLOT(updateSpectrumUsed(unsigned int, bool))); - connect(spectrumDock, SIGNAL(magnitudeChanged(unsigned int, double)), - dsoWidget, SLOT(updateSpectrumMagnitude(unsigned int))); + &VoltageDock::couplingChanged, dsoWidget, + &DsoWidget::updateVoltageCoupling); + connect(voltageDock, &VoltageDock::modeChanged, + dsoWidget, &DsoWidget::updateMathMode); + connect(voltageDock, &VoltageDock::gainChanged, this, + &OpenHantekMainWindow::updateVoltageGain); + connect(voltageDock, &VoltageDock::gainChanged, + dsoWidget, &DsoWidget::updateVoltageGain); + connect(dsoWidget, &DsoWidget::offsetChanged, this, + &OpenHantekMainWindow::updateOffset); + + connect(spectrumDock, &SpectrumDock::usedChanged, this, + &OpenHantekMainWindow::updateUsed); + connect(spectrumDock, &SpectrumDock::usedChanged, + dsoWidget, &DsoWidget::updateSpectrumUsed); + connect(spectrumDock, &SpectrumDock::magnitudeChanged, + dsoWidget, &DsoWidget::updateSpectrumMagnitude); // Started/stopped signals from oscilloscope - connect(dsoControl.get(), SIGNAL(samplingStarted()), this, SLOT(started())); - connect(dsoControl.get(), SIGNAL(samplingStopped()), this, SLOT(stopped())); + connect(dsoControl, &HantekDsoControl::samplingStarted, this, &OpenHantekMainWindow::started); + connect(dsoControl, &HantekDsoControl::samplingStopped, this, &OpenHantekMainWindow::stopped); // connect(dsoControl, SIGNAL(recordLengthChanged(unsigned long)), this, // SLOT(recordLengthChanged())); - connect(dsoControl.get(), SIGNAL(recordTimeChanged(double)), this, - SLOT(recordTimeChanged(double))); - connect(dsoControl.get(), SIGNAL(samplerateChanged(double)), this, - SLOT(samplerateChanged(double))); + connect(dsoControl, &HantekDsoControl::recordTimeChanged, this, + &OpenHantekMainWindow::recordTimeChanged); + connect(dsoControl, &HantekDsoControl::samplerateChanged, this, + &OpenHantekMainWindow::samplerateChanged); - connect(dsoControl.get(), - SIGNAL(availableRecordLengthsChanged(QList)), + connect(dsoControl, + &HantekDsoControl::availableRecordLengthsChanged, horizontalDock, - SLOT(availableRecordLengthsChanged(QList))); - connect(dsoControl.get(), SIGNAL(samplerateLimitsChanged(double, double)), - horizontalDock, SLOT(samplerateLimitsChanged(double, double))); - connect(dsoControl.get(), SIGNAL(samplerateSet(int, QList)), - horizontalDock, SLOT(samplerateSet(int, QList))); + &HorizontalDock::availableRecordLengthsChanged); + connect(dsoControl, &HantekDsoControl::samplerateLimitsChanged, + horizontalDock, &HorizontalDock::samplerateLimitsChanged); + connect(dsoControl, &HantekDsoControl::samplerateSet, + horizontalDock, &HorizontalDock::samplerateSet); } /// \brief Initialize the device with the current settings. @@ -444,10 +442,10 @@ void OpenHantekMainWindow::started() { startStopAction->setIcon(QIcon(":actions/stop.png")); startStopAction->setStatusTip(tr("Stop the oscilloscope")); - disconnect(startStopAction, SIGNAL(triggered()), dsoControl.get(), - SLOT(startSampling())); - connect(startStopAction, SIGNAL(triggered()), dsoControl.get(), - SLOT(stopSampling())); + disconnect(startStopAction, &QAction::triggered, dsoControl, + &HantekDsoControl::startSampling); + connect(startStopAction, &QAction::triggered, dsoControl, + &HantekDsoControl::stopSampling); } /// \brief The oscilloscope stopped sampling. @@ -456,10 +454,10 @@ void OpenHantekMainWindow::stopped() { startStopAction->setIcon(QIcon(":actions/start.png")); startStopAction->setStatusTip(tr("Start the oscilloscope")); - disconnect(startStopAction, SIGNAL(triggered()), dsoControl.get(), - SLOT(stopSampling())); - connect(startStopAction, SIGNAL(triggered()), dsoControl.get(), - SLOT(startSampling())); + disconnect(startStopAction, &QAction::triggered, dsoControl, + &HantekDsoControl::stopSampling); + connect(startStopAction, &QAction::triggered, dsoControl, + &HantekDsoControl::startSampling); } /// \brief Configure the oscilloscope. diff --git a/openhantek/src/mainwindow.h b/openhantek/src/mainwindow.h index cddb1da..a4e8736 100644 --- a/openhantek/src/mainwindow.h +++ b/openhantek/src/mainwindow.h @@ -27,7 +27,7 @@ class OpenHantekMainWindow : public QMainWindow { Q_OBJECT public: - OpenHantekMainWindow(std::shared_ptr dsoControl, std::shared_ptr dataAnalyser); + OpenHantekMainWindow(HantekDsoControl* dsoControl, DataAnalyzer* dataAnalyser); protected: void closeEvent(QCloseEvent *event); @@ -87,8 +87,8 @@ private: #endif // Data handling classes - std::shared_ptr dsoControl; - std::shared_ptr dataAnalyzer; + HantekDsoControl* dsoControl; + DataAnalyzer* dataAnalyzer; // Other variables QString currentFile; diff --git a/openhantek/src/utils/printutils.h b/openhantek/src/utils/printutils.h index 0dc4abb..061d11b 100644 --- a/openhantek/src/utils/printutils.h +++ b/openhantek/src/utils/printutils.h @@ -54,6 +54,10 @@ unsigned int hexParse(const QString dump, unsigned char *data, unsigned int leng /// \brief Print debug information with timestamp. /// \param text Text that will be output via qDebug. +#ifdef DEBUG inline void timestampDebug(QString text) { qDebug("%s: %s", QTime::currentTime().toString("hh:mm:ss.zzz").toLatin1().constData(), text.toLatin1().constData()); } +#else +#define timestampDebug(ARG) +#endif