Commit 9bcef88b8d30d970f3a144b63f2f13832fb718e7

Authored by oliverhaag
1 parent b935f4e0

Frequency detection using autocorrelation and spectrums in csv export

openhantek/ChangeLog
... ... @@ -97,3 +97,8 @@
97 97 * Some bugfixes for the DSO-5200
98 98 * Simplified trigger point calculation and correct handling for fast rate mode
99 99 * Recalculating pretrigger position after samplerate changes
  100 +
  101 +2010-09-29 Oliver Haag <oliver.haag@gmail.com>
  102 +* Added spectrums to exported csv files
  103 +* Using autocorrelation for frequency detection now
  104 +* Some undefined value bugfixes
... ...
openhantek/src/dataanalyzer.cpp
... ... @@ -184,55 +184,10 @@ void DataAnalyzer::run() {
184 184 // Lower priority for spectrum calculation
185 185 this->setPriority(QThread::LowPriority);
186 186  
187   - // Calculate peak-to-peak voltage and frequency
188   - for(int channel = 0; channel < this->analyzedData.count(); channel++) {
189   - if(this->settings->scope.voltage[channel].used && this->analyzedData[channel]->samples.voltage.sample) {
190   - bool aboveTrigger = this->analyzedData[channel]->samples.voltage.sample[0] > this->settings->scope.voltage[channel].trigger;
191   - double minimalVoltage, maximalVoltage;
192   - unsigned int firstTrigger = 0, lastTrigger = 0;
193   - int triggerCount = -1;
194   - this->analyzedData[channel]->amplitude = 0;
195   - minimalVoltage = maximalVoltage = this->analyzedData[channel]->samples.voltage.sample[0];
196   -
197   - for(unsigned int position = 0; position < this->analyzedData[channel]->samples.voltage.count; position++) {
198   - // Check trigger condition
199   - if(aboveTrigger != (this->analyzedData[channel]->samples.voltage.sample[position] > this->settings->scope.voltage[channel].trigger)) {
200   - aboveTrigger = this->analyzedData[channel]->samples.voltage.sample[position] > this->settings->scope.voltage[channel].trigger;
201   - // We measure the time between two trigger slopes
202   - if(aboveTrigger == (this->settings->scope.trigger.slope == Dso::SLOPE_POSITIVE)) {
203   - triggerCount++;
204   - if(triggerCount == 0)
205   - firstTrigger = position;
206   - else
207   - this->analyzedData[channel]->amplitude += maximalVoltage - minimalVoltage;
208   - minimalVoltage = this->analyzedData[channel]->samples.voltage.sample[position];
209   - maximalVoltage = this->analyzedData[channel]->samples.voltage.sample[position];
210   - lastTrigger = position;
211   - }
212   - }
213   - else { // Get minimal and maximal voltage
214   - if(this->analyzedData[channel]->samples.voltage.sample[position] < minimalVoltage)
215   - minimalVoltage = this->analyzedData[channel]->samples.voltage.sample[position];
216   - else if(this->analyzedData[channel]->samples.voltage.sample[position] > maximalVoltage)
217   - maximalVoltage = this->analyzedData[channel]->samples.voltage.sample[position];
218   - }
219   - }
220   -
221   - // Calculate values
222   - if(triggerCount >= 0) {
223   - this->analyzedData[channel]->amplitude /= triggerCount;
224   - this->analyzedData[channel]->frequency = (double) triggerCount / (lastTrigger - firstTrigger) / this->analyzedData[channel]->samples.voltage.interval;
225   - }
226   - else {
227   - this->analyzedData[channel]->amplitude = maximalVoltage - minimalVoltage;
228   - this->analyzedData[channel]->frequency = 0;
229   - }
230   - }
231   - }
232 187  
233   - // Calculate spectrums
  188 + // Calculate frequencies, peak-to-peak voltages and spectrums
234 189 for(int channel = 0; channel < this->analyzedData.count(); channel++) {
235   - if(this->settings->scope.spectrum[channel].used && this->analyzedData[channel]->samples.voltage.sample) {
  190 + if(this->analyzedData[channel]->samples.voltage.sample) {
236 191 // Calculate new window
237 192 if(this->lastWindow != this->settings->scope.spectrumWindow || this->lastBufferSize != this->analyzedData[channel]->samples.voltage.count) {
238 193 if(this->lastBufferSize != this->analyzedData[channel]->samples.voltage.count) {
... ... @@ -325,9 +280,12 @@ void DataAnalyzer::run() {
325 280 // Set sampling interval
326 281 this->analyzedData[channel]->samples.spectrum.interval = 1.0 / this->analyzedData[channel]->samples.voltage.interval / this->analyzedData[channel]->samples.voltage.count;
327 282  
  283 + // Number of real/complex samples
  284 + unsigned int dftLength = this->analyzedData[channel]->samples.voltage.count / 2;
  285 +
328 286 // Reallocate memory for samples if the sample count has changed
329   - if(this->analyzedData[channel]->samples.spectrum.count != this->analyzedData[channel]->samples.voltage.count / 2) {
330   - this->analyzedData[channel]->samples.spectrum.count = this->analyzedData[channel]->samples.voltage.count / 2;
  287 + if(this->analyzedData[channel]->samples.spectrum.count != dftLength) {
  288 + this->analyzedData[channel]->samples.spectrum.count = dftLength;
331 289 if(this->analyzedData[channel]->samples.spectrum.sample)
332 290 delete[] this->analyzedData[channel]->samples.spectrum.sample;
333 291 this->analyzedData[channel]->samples.spectrum.sample = new double[this->analyzedData[channel]->samples.voltage.count];
... ... @@ -339,23 +297,98 @@ void DataAnalyzer::run() {
339 297 windowedValues[position] = this->window[position] * this->analyzedData[channel]->samples.voltage.sample[position];
340 298  
341 299 // Do discrete real to half-complex transformation
342   - // TODO: Reuse plan and use FFTW_MEASURE to get fastest algorithm
  300 + /// \todo Check if buffer size is multiple of 2
  301 + /// \todo Reuse plan and use FFTW_MEASURE to get fastest algorithm
343 302 fftw_plan fftPlan = fftw_plan_r2r_1d(this->analyzedData[channel]->samples.voltage.count, windowedValues, this->analyzedData[channel]->samples.spectrum.sample, FFTW_R2HC, FFTW_ESTIMATE);
344 303 fftw_execute(fftPlan);
345 304 fftw_destroy_plan(fftPlan);
346 305  
347   - // Deallocate sample buffer
348   - delete[] windowedValues;
  306 + // Do an autocorrelation to get the frequency of the signal
  307 + double *conjugateComplex = windowedValues; // Reuse the windowedValues buffer
349 308  
350   - // Convert values into dB (Relative to the reference level)
351   - double offset = 60 - this->settings->scope.spectrumReference - 20 * log10(sqrt(this->analyzedData[channel]->samples.spectrum.count));
352   - double offsetLimit = this->settings->scope.spectrumLimit - this->settings->scope.spectrumReference;
353   - for(unsigned int position = 0; position < this->analyzedData[channel]->samples.spectrum.count; position++) {
354   - this->analyzedData[channel]->samples.spectrum.sample[position] = 20 * log10(fabs(this->analyzedData[channel]->samples.spectrum.sample[position])) + offset;
355   -
356   - // Check if this value has to be limited
357   - if(offsetLimit > this->analyzedData[channel]->samples.spectrum.sample[position])
358   - this->analyzedData[channel]->samples.spectrum.sample[position] = offsetLimit;
  309 + // Real values
  310 + unsigned int position;
  311 + double correctionFactor = 1.0 / dftLength / dftLength;
  312 + conjugateComplex[0] = (this->analyzedData[channel]->samples.spectrum.sample[0] * this->analyzedData[channel]->samples.spectrum.sample[0]) * correctionFactor;
  313 + for(position = 1; position < dftLength; position++)
  314 + conjugateComplex[position] = (this->analyzedData[channel]->samples.spectrum.sample[position] * this->analyzedData[channel]->samples.spectrum.sample[position] + this->analyzedData[channel]->samples.spectrum.sample[this->analyzedData[channel]->samples.voltage.count - position] * this->analyzedData[channel]->samples.spectrum.sample[this->analyzedData[channel]->samples.voltage.count - position]) * correctionFactor;
  315 + // Complex values, all zero for autocorrelation
  316 + conjugateComplex[dftLength] = (this->analyzedData[channel]->samples.spectrum.sample[dftLength] * this->analyzedData[channel]->samples.spectrum.sample[dftLength]) * correctionFactor;
  317 + for(position++; position < this->analyzedData[channel]->samples.voltage.count; position++)
  318 + conjugateComplex[position] = 0;
  319 +
  320 + // Do half-complex to real inverse transformation
  321 + double *correlation = new double[this->analyzedData[channel]->samples.voltage.count];
  322 + fftPlan = fftw_plan_r2r_1d(this->analyzedData[channel]->samples.voltage.count, conjugateComplex, correlation, FFTW_HC2R, FFTW_ESTIMATE);
  323 + fftw_execute(fftPlan);
  324 + fftw_destroy_plan(fftPlan);
  325 + delete[] conjugateComplex;
  326 +
  327 + // Calculate peak-to-peak voltage
  328 + double minimalVoltage, maximalVoltage;
  329 + minimalVoltage = maximalVoltage = this->analyzedData[channel]->samples.voltage.sample[0];
  330 +
  331 + for(unsigned int position = 1; position < this->analyzedData[channel]->samples.voltage.count; position++) {
  332 + if(this->analyzedData[channel]->samples.voltage.sample[position] < minimalVoltage)
  333 + minimalVoltage = this->analyzedData[channel]->samples.voltage.sample[position];
  334 + else if(this->analyzedData[channel]->samples.voltage.sample[position] > maximalVoltage)
  335 + maximalVoltage = this->analyzedData[channel]->samples.voltage.sample[position];
  336 + }
  337 +
  338 + this->analyzedData[channel]->amplitude = maximalVoltage - minimalVoltage;
  339 +
  340 + // Get the frequency from the correlation results
  341 + double correlationLimit = pow(sqrt(maximalVoltage - minimalVoltage) / 2, 4);
  342 + bool newPeak = false; // Ignore correlation without offset (position = 0)
  343 + double bestPeak = 0, lastPeak = 0;
  344 + unsigned int bestPeakPosition = 0, currentPeakPosition = 0;
  345 +
  346 + for(unsigned int position = 1; position < this->analyzedData[channel]->samples.voltage.count; position++) {
  347 + if(correlation[position] < correlationLimit) {
  348 + // Check if there was a good peak before
  349 + if(currentPeakPosition) {
  350 + // Is this really a better correlation and not just a secondary peak of the first one?
  351 + if(lastPeak > bestPeak * 1.2) {
  352 + bestPeak = lastPeak;
  353 + bestPeakPosition = currentPeakPosition;
  354 + }
  355 + currentPeakPosition = 0;
  356 + }
  357 + newPeak = true;
  358 + }
  359 + else if((currentPeakPosition || newPeak) && correlation[position] > lastPeak) {
  360 + // We want this peak, store it
  361 + lastPeak = correlation[position];
  362 + currentPeakPosition = position;
  363 + newPeak = false;
  364 + }
  365 + }
  366 + delete[] correlation;
  367 +
  368 + // Check if there's a possible peak available that wasn't finished
  369 + if(currentPeakPosition && currentPeakPosition < this->analyzedData[channel]->samples.voltage.count - 1 && lastPeak > bestPeak * 1.2) {
  370 + bestPeak = lastPeak;
  371 + bestPeakPosition = currentPeakPosition;
  372 + }
  373 +
  374 + // Calculate the frequency in Hz
  375 + if(bestPeakPosition)
  376 + this->analyzedData[channel]->frequency = 1.0 / (this->analyzedData[channel]->samples.voltage.interval * bestPeakPosition);
  377 + else
  378 + this->analyzedData[channel]->frequency = 0;
  379 +
  380 + // Finally calculate the real spectrum if we want it
  381 + if(this->settings->scope.spectrum[channel].used) {
  382 + // Convert values into dB (Relative to the reference level)
  383 + double offset = 60 - this->settings->scope.spectrumReference - 20 * log10(dftLength);
  384 + double offsetLimit = this->settings->scope.spectrumLimit - this->settings->scope.spectrumReference;
  385 + for(unsigned int position = 0; position < this->analyzedData[channel]->samples.spectrum.count; position++) {
  386 + this->analyzedData[channel]->samples.spectrum.sample[position] = 20 * log10(fabs(this->analyzedData[channel]->samples.spectrum.sample[position])) + offset;
  387 +
  388 + // Check if this value has to be limited
  389 + if(offsetLimit > this->analyzedData[channel]->samples.spectrum.sample[position])
  390 + this->analyzedData[channel]->samples.spectrum.sample[position] = offsetLimit;
  391 + }
359 392 }
360 393 }
361 394 else if(this->analyzedData[channel]->samples.spectrum.sample) {
... ...
openhantek/src/exporter.cpp
... ... @@ -384,6 +384,18 @@ bool Exporter::doExport() {
384 384 // Finally a newline
385 385 csvStream << '\n';
386 386 }
  387 +
  388 + if(this->settings->scope.spectrum[channel].used) {
  389 + // Start with channel name and the sample interval
  390 + csvStream << "\"" << this->settings->scope.spectrum[channel].name << "\"," << this->dataAnalyzer->data(channel)->samples.spectrum.interval;
  391 +
  392 + // And now all magnitudes in dB
  393 + for(unsigned int position = 0; position < this->dataAnalyzer->data(channel)->samples.spectrum.count; position++)
  394 + csvStream << "," << this->dataAnalyzer->data(channel)->samples.spectrum.sample[position];
  395 +
  396 + // Finally a newline
  397 + csvStream << '\n';
  398 + }
387 399 }
388 400  
389 401 csvFile.close();
... ...
openhantek/src/hantek/control.cpp
... ... @@ -745,7 +745,7 @@ namespace Hantek {
745 745 scalerId = gainId % 3;
746 746 this->sampleRange[channel] = 0xff;
747 747 break;
748   - case 1:
  748 + default:
749 749 /// \todo Use calibration data to get the DSO-5200 sample ranges
750 750 if(gainId == GAIN_10MV) {
751 751 scalerId = 1;
... ... @@ -909,7 +909,7 @@ namespace Hantek {
909 909 maximum = 0xfd;
910 910 break;
911 911  
912   - case 1:
  912 + default:
913 913 // The range is the same as used for the offsets for 10 bit models
914 914 minimum = ((unsigned short int) *((unsigned char *) &(this->channelLevels[channel][this->gain[channel]][OFFSET_START])) << 8) + *((unsigned char *) &(this->channelLevels[channel][this->gain[channel]][OFFSET_START]) + 1);
915 915 maximum = ((unsigned short int) *((unsigned char *) &(this->channelLevels[channel][this->gain[channel]][OFFSET_START])) << 8) + *((unsigned char *) &(this->channelLevels[channel][this->gain[channel]][OFFSET_END]) + 1);
... ...
openhantek/src/hantek/device.cpp
... ... @@ -165,10 +165,11 @@ namespace Hantek {
165 165 libusb_close(this->handle);
166 166  
167 167 ssize_t deviceCount = libusb_get_device_list(this->context, &deviceList);
168   - if (deviceCount < 0)
  168 + if(deviceCount < 0)
169 169 return tr("Failed to get device list: %3").arg(Helper::libUsbErrorString(errorCode));
170 170  
171 171 // Iterate through all usb devices
  172 + this->model = MODEL_UNKNOWN;
172 173 for(ssize_t deviceIterator = 0; deviceIterator < deviceCount; deviceIterator++) {
173 174 device = deviceList[deviceIterator];
174 175 // Get device descriptor
... ...
openhantek/src/settings.cpp
... ... @@ -56,8 +56,8 @@ DsoSettings::DsoSettings() {
56 56 this->scope.trigger.special = false;
57 57 // General
58 58 this->scope.physicalChannels = 0;
59   - this->scope.spectrumLimit = 0.0;
60   - this->scope.spectrumReference = 20.0;
  59 + this->scope.spectrumLimit = -20.0;
  60 + this->scope.spectrumReference = 0.0;
61 61 this->scope.spectrumWindow = Dso::WINDOW_HANN;
62 62  
63 63  
... ...