diff --git a/doc/img/Spectrum_Measurement_3dBBandwidth.png b/doc/img/Spectrum_Measurement_3dBBandwidth.png new file mode 100644 index 000000000..93d19423c Binary files /dev/null and b/doc/img/Spectrum_Measurement_3dBBandwidth.png differ diff --git a/doc/img/Spectrum_Measurement_AdjChannelPower.png b/doc/img/Spectrum_Measurement_AdjChannelPower.png index d55489a04..96913166c 100644 Binary files a/doc/img/Spectrum_Measurement_AdjChannelPower.png and b/doc/img/Spectrum_Measurement_AdjChannelPower.png differ diff --git a/doc/img/Spectrum_Measurement_ChannelPower.png b/doc/img/Spectrum_Measurement_ChannelPower.png index bbf837d08..9c523d68d 100644 Binary files a/doc/img/Spectrum_Measurement_ChannelPower.png and b/doc/img/Spectrum_Measurement_ChannelPower.png differ diff --git a/doc/img/Spectrum_Measurement_OccupiedBandwidth.png b/doc/img/Spectrum_Measurement_OccupiedBandwidth.png new file mode 100644 index 000000000..e1a27c19e Binary files /dev/null and b/doc/img/Spectrum_Measurement_OccupiedBandwidth.png differ diff --git a/doc/img/Spectrum_Measurement_SNR.png b/doc/img/Spectrum_Measurement_SNR.png index 41e0e3a3d..b82651b80 100644 Binary files a/doc/img/Spectrum_Measurement_SNR.png and b/doc/img/Spectrum_Measurement_SNR.png differ diff --git a/doc/img/Spectrum_Measurement_dialog_OccupiedBandwidth.png b/doc/img/Spectrum_Measurement_dialog_OccupiedBandwidth.png new file mode 100644 index 000000000..36cbb963a Binary files /dev/null and b/doc/img/Spectrum_Measurement_dialog_OccupiedBandwidth.png differ diff --git a/sdrbase/dsp/spectrumsettings.h b/sdrbase/dsp/spectrumsettings.h index 3be5836f3..8d2829fec 100644 --- a/sdrbase/dsp/spectrumsettings.h +++ b/sdrbase/dsp/spectrumsettings.h @@ -76,6 +76,8 @@ public: MeasurementPeaks, MeasurementChannelPower, MeasurementAdjacentChannelPower, + MeasurementOccupiedBandwidth, + Measurement3dBBandwidth, MeasurementSNR }; diff --git a/sdrgui/gui/glspectrumview.cpp b/sdrgui/gui/glspectrumview.cpp index 0f0937cad..7f0b01456 100644 --- a/sdrgui/gui/glspectrumview.cpp +++ b/sdrgui/gui/glspectrumview.cpp @@ -1751,6 +1751,12 @@ void GLSpectrumView::paintGL() case SpectrumSettings::MeasurementAdjacentChannelPower: measureAdjacentChannelPower(); break; + case SpectrumSettings::MeasurementOccupiedBandwidth: + measureOccupiedBandwidth(); + break; + case SpectrumSettings::Measurement3dBBandwidth: + measure3dBBandwidth(); + break; case SpectrumSettings::MeasurementSNR: measureSNR(); measureSFDR(); @@ -2223,6 +2229,98 @@ void GLSpectrumView::measureAdjacentChannelPower() } } +// Measure bandwidth that has 99% of power +void GLSpectrumView::measureOccupiedBandwidth() +{ + float hzPerBin = m_sampleRate / (float) m_fftSize; + int bins = m_measurementBandwidth / hzPerBin; + int start = frequencyToBin(m_centerFrequency + m_measurementCenterFrequencyOffset); + float totalPower, power = 0.0f; + int step = 0; + int width = 0; + int idx = start; + float gain = m_useCalibration ? m_calibrationGain : 1.0f; + float shift = m_useCalibration ? m_calibrationShiftdB : 0.0f; + + totalPower = CalcDb::powerFromdB(calcChannelPower(m_centerFrequency + m_measurementCenterFrequencyOffset, m_measurementBandwidth)); + do + { + if ((idx >= 0) && (idx < m_nbBins)) + { + if (m_linear) { + power += m_currentSpectrum[idx] * gain; + } else { + power += CalcDb::powerFromdB(m_currentSpectrum[idx]) + shift; + } + width++; + } + + step++; + if ((step & 1) == 1) { + idx -= step; + } else { + idx += step; + } + } + while (((power / totalPower) < 0.99f) && (step < m_nbBins)); + + float occupiedBandwidth = width * hzPerBin; + if (m_measurements) { + m_measurements->setOccupiedBandwidth(occupiedBandwidth); + } + if (m_measurementHighlight) + { + drawBandwidthMarkers(m_centerFrequency + m_measurementCenterFrequencyOffset, m_measurementBandwidth, m_measurementDarkMarkerColor); + drawBandwidthMarkers(m_centerFrequency + m_measurementCenterFrequencyOffset, occupiedBandwidth, m_measurementLightMarkerColor); + } +} + +// Measure bandwidth -3dB from peak +void GLSpectrumView::measure3dBBandwidth() +{ + // Find max peak and it's power in dB + int peakBin = findPeakBin(m_currentSpectrum); + float peakPower = m_linear ? CalcDb::dbPower(m_currentSpectrum[peakBin]) : m_currentSpectrum[peakBin]; + + // Search right until 3dB from peak + int rightBin = peakBin; + for (int i = peakBin + 1; i < m_nbBins; i++) + { + float power = m_linear ? CalcDb::dbPower(m_currentSpectrum[i]) : m_currentSpectrum[i]; + if (peakPower - power > 3.0f) + { + rightBin = i - 1; + break; + } + } + + // Search left until 3dB from peak + int leftBin = peakBin; + for (int i = peakBin - 1; i >= 0; i--) + { + float power = m_linear ? CalcDb::dbPower(m_currentSpectrum[i]) : m_currentSpectrum[i]; + if (peakPower - power > 3.0f) + { + leftBin = i + 1; + break; + } + } + + // Calcualte bandwidth + float hzPerBin = m_sampleRate / (float) m_fftSize; + int bins = rightBin - leftBin - 1; + float bandwidth = bins * hzPerBin; + int centerBin = leftBin + (rightBin - leftBin) / 2; + float centerFrequency = binToFrequency(centerBin); + + if (m_measurements) { + m_measurements->set3dBBandwidth(bandwidth); + } + if (m_measurementHighlight) { + drawBandwidthMarkers(centerFrequency, bandwidth, m_measurementLightMarkerColor); + } +} + const QVector4D GLSpectrumView::m_measurementLightMarkerColor = QVector4D(0.6f, 0.6f, 0.6f, 0.2f); const QVector4D GLSpectrumView::m_measurementDarkMarkerColor = QVector4D(0.6f, 0.6f, 0.6f, 0.15f); @@ -2485,17 +2583,20 @@ float GLSpectrumView::calcChannelPower(int64_t centerFrequency, int channelBandw int end = start + bins; float power = 0.0; + start = std::max(start, 0); + end = std::min(end, m_nbBins); + if (m_linear) { float gain = m_useCalibration ? m_calibrationGain : 1.0f; - for (int i = start; i <= end; i++) { + for (int i = start; i < end; i++) { power += m_currentSpectrum[i] * gain; } } else { float shift = m_useCalibration ? m_calibrationShiftdB : 0.0f; - for (int i = start; i <= end; i++) { + for (int i = start; i < end; i++) { power += CalcDb::powerFromdB(m_currentSpectrum[i]) + shift; } } diff --git a/sdrgui/gui/glspectrumview.h b/sdrgui/gui/glspectrumview.h index e5dcd64b5..234b220bc 100644 --- a/sdrgui/gui/glspectrumview.h +++ b/sdrgui/gui/glspectrumview.h @@ -431,6 +431,8 @@ private: void measurePeaks(); void measureChannelPower(); void measureAdjacentChannelPower(); + void measureOccupiedBandwidth(); + void measure3dBBandwidth(); void measureSNR(); void measureSFDR(); float calcChannelPower(int64_t centerFrequency, int channelBandwidth) const; diff --git a/sdrgui/gui/spectrummeasurements.cpp b/sdrgui/gui/spectrummeasurements.cpp index b90d42d59..c217b0d8b 100644 --- a/sdrgui/gui/spectrummeasurements.cpp +++ b/sdrgui/gui/spectrummeasurements.cpp @@ -347,6 +347,22 @@ void SpectrumMeasurements::createAdjacentChannelPowerTable() createMeasurementsTable(rows, units); } +void SpectrumMeasurements::createOccupiedBandwidthTable() +{ + QStringList rows = {"Occupied B/W"}; + QStringList units = {" Hz"}; + + createMeasurementsTable(rows, units); +} + +void SpectrumMeasurements::create3dBBandwidthTable() +{ + QStringList rows = {"3dB B/W"}; + QStringList units = {" Hz"}; + + createMeasurementsTable(rows, units); +} + void SpectrumMeasurements::createSNRTable() { QStringList rows = {"SNR", "SNFR", "THD", "THD+N", "SINAD", "SFDR",}; @@ -512,6 +528,16 @@ void SpectrumMeasurements::setMeasurementParams(SpectrumSettings::Measurement me createAdjacentChannelPowerTable(); layout()->addWidget(m_table); break; + case SpectrumSettings::MeasurementOccupiedBandwidth: + reset(); + createOccupiedBandwidthTable(); + layout()->addWidget(m_table); + break; + case SpectrumSettings::Measurement3dBBandwidth: + reset(); + create3dBBandwidthTable(); + layout()->addWidget(m_table); + break; case SpectrumSettings::MeasurementSNR: reset(); createSNRTable(); @@ -648,6 +674,16 @@ void SpectrumMeasurements::setAdjacentChannelPower(float left, float leftACPR, f updateMeasurement(4, rightACPR); } +void SpectrumMeasurements::setOccupiedBandwidth(float occupiedBandwidth) +{ + updateMeasurement(0, occupiedBandwidth); +} + +void SpectrumMeasurements::set3dBBandwidth(float bandwidth) +{ + updateMeasurement(0, bandwidth); +} + void SpectrumMeasurements::setPeak(int peak, int64_t frequency, float power) { if (peak < m_peakTable->rowCount()) diff --git a/sdrgui/gui/spectrummeasurements.h b/sdrgui/gui/spectrummeasurements.h index ab14999b6..f202a0aab 100644 --- a/sdrgui/gui/spectrummeasurements.h +++ b/sdrgui/gui/spectrummeasurements.h @@ -96,6 +96,8 @@ public: void setSFDR(float sfdr); void setChannelPower(float power); void setAdjacentChannelPower(float left, float leftACPR, float center, float right, float rightACPR); + void setOccupiedBandwidth(float occupiedBandwidth); + void set3dBBandwidth(float bandwidth); void setPeak(int peak, int64_t frequency, float power); void reset(); @@ -105,6 +107,8 @@ private: void createTableMenus(); void createChannelPowerTable(); void createAdjacentChannelPowerTable(); + void createOccupiedBandwidthTable(); + void create3dBBandwidthTable(); void createSNRTable(); void tableContextMenu(QPoint pos); void peakTableContextMenu(QPoint pos); diff --git a/sdrgui/gui/spectrummeasurements.md b/sdrgui/gui/spectrummeasurements.md index 1fe3f2f0d..2b75012fa 100644 --- a/sdrgui/gui/spectrummeasurements.md +++ b/sdrgui/gui/spectrummeasurements.md @@ -20,6 +20,8 @@ Shows the n largest peaks in magnitude - **Results precision**: controls the number of decimal places displayed on the power readings in dB. This control is common to all measurement types - **Peaks**: controls the number of peaks +![Spectrum Measurements - peaks](../../doc/img/Spectrum_Measurement_Peak.png) +

Channel Power

Channel power measures the total power within a user-defined bandwidth. Channel shift is adjustable with the dialog. @@ -41,27 +43,60 @@ The adjacent channel power measurement measures the power in a channel of user-d ![Spectrum Measurements dialog - adjacentchannel power](../../doc/img/Spectrum_Measurement_dialog_AdjChannelPower.png) - **Center frequency offset**: channels offset from the center in Hz - - **Channel bandwidth**: bandwidth of the in-channel in Hz - - **Channel spacing**: In-channel to adjacent channel centers spacing in Hz + - **Channel bandwidth**: bandwidth of the center channel in Hz + - **Channel spacing**: Center channel to adjacent channel centers spacing in Hz - **Adjacent channel bandwidth**: Adjacent channels bandwidth in Hz Spectrum display with the highlight artifact on: -![Spectrum Measurements - adjacentchannel power](../../doc/img/Spectrum_Measurement_AdjChannelPower.png) +![Spectrum Measurements - adjacent channel power](../../doc/img/Spectrum_Measurement_AdjChannelPower.png) + +The results table displays: + + - **Left power**: power in the left channel in dB + - **Left ACPR**: difference in power between the left channel and the center channel in dB + - **Center power**: power in the center channel in dB + - **Right ACPR**: difference in power between the right channel and the center channel in dB + - **Right power**: power in the right channel in dB + +

Occupied Bandwidth

+ +The occupied bandwidth measurement measures the bandwidth that contains 99% of the total power. + +![Spectrum Measurements dialog - occupied bandwidth](../../doc/img/Spectrum_Measurement_dialog_OccupiedBandwidth.png) + + - **Center frequency offset**: channel offset from the center in Hz + - **Channel bandwidth**: bandwidth of the channel in Hz, over which the total power should be measured + +Spectrum display with the highlight artifact on: + +![Spectrum Measurements - occupied bandwidth](../../doc/img/Spectrum_Measurement_OccupiedBandwidth.png) + +

3dB Bandwidth

+ +The 3dB bandwidth measurement measures the bandwidth of a signal, calculated as the frequency difference between where the power falls by 3dB either side of the maximum peak. + +![Spectrum Measurements - 3dB bandwidth](../../doc/img/Spectrum_Measurement_3dBBandwidth.png)

SNR

+The SNR measurement simultaneously calculates SNR, SNFR, THD, THD+N, SINAD and SFDR. + ![Spectrum Measurements dialog - SNR](../../doc/img/Spectrum_Measurement_dialog_SNR.png) - - **Harmonics**: controls the number of harmonics for THD, THD+N or SINAD measurements (see next) + - **Harmonics**: controls the number of harmonics for SNR, THD or THD+N measurements (see next) + +Spectrum display with the highlight artifact on: + +![Spectrum Measurements - SNR](../../doc/img/Spectrum_Measurement_SNR.png) + +The fundamental, harmonics and SFDR are highlighted.

SNR: Signal to Noise Ratio

The SNR measurement estimates a signal-to-noise ratio. The fundamental signal is the largest peak (i.e. FFT bin with highest magnitude). The bandwidth of the signal is assumed to be the width of the largest peak, which includes adjacent bins with a monotonically decreasing magnitude. Noise is summed over the full bandwidth (i.e all FFT bins), with the fundamental and user-specified number of harmonics being replaced with the noise median from outside of these regions. The noise median is also subtracted from the signal, before the SNR is calculated. -![Spectrum Measurements - SNR](../../doc/img/Spectrum_Measurement_SNR.png) - -

SFNR: Signal to Noise Floor Ratio

+

SNFR: Signal to Noise Floor Ratio

The SNFR measurement estimates a signal-to-noise-floor ratio. This is similar to the SNR, except that the noise used in the ratio, is only the median noise value calculated from the noise outside of the fundamental and harmonics, summed over the bandwidth of the signal. One way to think of this, is that it is the SNR if all noise outside of the signal's bandwidth was filtered. @@ -81,5 +116,12 @@ SINAD is measured as per SNR, but the result is the ratio of the fundamental to SFDR is a measurement of the difference in power from the largest peak (the fundamental) to the second largest peak (the strongest spurious signal). -![Spectrum Measurements - SFDR](../../doc/img/Spectrum_Measurement_SFDR.png) +

Specifications

+The measurements table has a Spec column that allows entry of user-defined specifications for the Current, Mean, Min and Max values to be checked against. +For example, you might choose to enter >50 in the SFDR row, to check that SFDR is greater than 50dB. +Each time the specification fails to be met, the value in the Fails column is incremented. +Values out of specification are highlighted in red. +The Spec column supports the following operators: <, <=, >, >= and =. + +![Spectrum Measurements - specifications](../../doc/img/Spectrum_Measurement_Specifications.png) diff --git a/sdrgui/gui/spectrummeasurementsdialog.cpp b/sdrgui/gui/spectrummeasurementsdialog.cpp index ef5480db7..341d4568e 100644 --- a/sdrgui/gui/spectrummeasurementsdialog.cpp +++ b/sdrgui/gui/spectrummeasurementsdialog.cpp @@ -80,7 +80,8 @@ void SpectrumMeasurementsDialog::displaySettings() ui->resetMeasurements->setVisible(reset && show); bool bw = (m_settings->m_measurement == SpectrumSettings::MeasurementChannelPower) - || (m_settings->m_measurement == SpectrumSettings::MeasurementAdjacentChannelPower); + || (m_settings->m_measurement == SpectrumSettings::MeasurementAdjacentChannelPower) + || (m_settings->m_measurement == SpectrumSettings::MeasurementOccupiedBandwidth); ui->centerFrequencyOffsetLabel->setVisible(bw && show); ui->centerFrequencyOffset->setVisible(bw && show); ui->bandwidthLabel->setVisible(bw && show); diff --git a/sdrgui/gui/spectrummeasurementsdialog.ui b/sdrgui/gui/spectrummeasurementsdialog.ui index 2fc2d24f2..cd328c69d 100644 --- a/sdrgui/gui/spectrummeasurementsdialog.ui +++ b/sdrgui/gui/spectrummeasurementsdialog.ui @@ -155,6 +155,16 @@ Adjacent channel power + + + Occupied b/w + + + + + 3dB bandwidth + + SNR