PlainDSP (Part 2)

Part 1234

The next lines of code illustrate how PlainDSP simplifies the combination of data acquisition and data analysis. You no longer need to worry about the vectors of data. PlainDSP creates dynamically  and transparently the required vectors. On the other hand you must keep in mind that they exist and take 8 times the number of samples… So that no more that 128 samples shall be used when PlainDSP is running on an arduino UNO.

The first part of the code contains the instruction for including PlainDDS library and the creation of the DSP object. Then come the data acquisition parameters. Note that PlainDSP allows you to selectively deactivate the unused timers in order to improve the sampling rate stability, along with the activation of the noise canceler function. The sampling rate is now limited to 80kHz, which is the price to pay for the simplification of use of the library. On the other hand, this limit is far above the 44.1kHz sampling rate required for achieving CD sound quality

#include <PlainDSP.h>
/* Create objects */
PlainDSP DSP; /* Create DSP object */
/* Acquisition parameters */
const uint16_t _samples = 128; /* Max value depends upon available memory space */
const float _samplingFrequency = 2000.0; /* From 0.125 Hz to 80 kHz */
const uint16_t _adcChannel = 0; /* From 0 to 5 on ATmega328 powered Arduinos */
const uint16_t _refVoltage = DSP_REF_VOL_DEFAULT; /* VCC: 5V */
const uint8_t _options = (DSP_OPT_DIS_TIM_0 | DSP_OPT_DIS_TIM_2 | DSP_OPT_NOI_CANCELLER);
const float _targetPeakFrequency = 10000.0;
/* Local scale constants */
#define SCL_INDEX 0x00
#define SCL_TIME 0x01
#define SCL_FREQUENCY 0x02

The second part deals with the set up section of the code. This is where the data acquisition parameters are set: adc channel index (0 to 5 for arduino UNO), reference voltage, sampling rate frequency (0.125Hz to 80kHz), samples (1 to 128 for arduino UNO) and noise reduction options.

void setup()
{
	/* Initialize serial comm port */
	Serial.begin(115200); 
	/* Set data acquisition parameters and get memory location of data */
	DSP.SetAcquisitionEngine(_adcChannel, _refVoltage, _samplingFrequency, _samples, _options);	
};

The third part deals with the loop section of the code. This is where the data acquisition and data analysis take place. Acquiring data is as simple as GetScanData(). Once executed, data can be read using the ReadData() function. Again, no need to care about where and how data is strored. In the example, some additional functions are used prior to applying FFT: ResetOffset() which nulls the offset of a periodic (e.g. sinusoidal) signal. The Rescale() function converts the ordinate scale so that 1024 counts translate to 5V. Use the  PrintVector() function in order to visualize the signal on completion of the successive steps. The ClearVector() function must be used to erase the content of the vector which contains the imaginary data. Failing to do so may lead to unexpected results.

Note: This vector is not automatically cleared in order to allow the reversal use of FFT, in the context of denoising function.

Use the ComputeFFT()  function with appropriate argument, depending upon the the nature of the original signal. Finally execute the Complex to real function in order to get the frequency domain data, so as to say, the frequency spectrum.

Both MajorPeak() and TargetPeak() which perform automatic peak picking and peak quantification (apex location, interpolated peak apex location, peak height). Once again, you do not have to worry about the location of data

 

void loop() 
{
	/* Acquire data */
	DSP.GetScanData();
	/* Null offset */
	DSP.ResetOffset();
	/* Rescale signal */
	DSP.Rescale((5.0 / 1024.0), 0);
	/* Print raw data */
	PrintVector(SCL_TIME); /* Comment line if not needed */
	/* Weigh data */
	DSP.Windowing(DSP_WIN_TYP_HANN, DSP_FORWARD);	
	PrintVector(SCL_TIME); /* Comment line if not needed */
	/* Compute FFT */
	DSP.ClearVector(DSP_IMG_DATA);
	DSP.ComputeFFT(DSP_FORWARD);	
	/* Print real and imag data */
	PrintVector(SCL_INDEX); /* Comment line if not needed */
	/* Compute magnitudes */
	DSP.ComplexToReal(DSP_SCL_TYP_AMPLITUDE);
	/* Print frequency spectrum */
	PrintVector(SCL_FREQUENCY); /* Comment line if not needed */
	/* Find major peak */
	struct strPeakProperties majorPeak;
	DSP.MajorPeak(_targetPeakFrequency - (_targetPeakFrequency * .5), _targetPeakFrequency + (_targetPeakFrequency * .5), &majorPeak);
	Serial.print("Major peak ");
	Serial.print("bin: ");
	Serial.print(majorPeak.bin);	
	Serial.print(", feq.: ");
	Serial.print(majorPeak.position, 6);	
	Serial.print(", height: ");
	Serial.print(majorPeak.height, 6);	
	Serial.println();
	/* Find target peak */
	struct strPeakProperties targetPeak;
	DSP.TargetPeak(_targetPeakFrequency, (_targetPeakFrequency * 0.1), &targetPeak);
	Serial.print("Target peak ");
	Serial.print("bin: ");
	Serial.print(targetPeak.bin);	
	Serial.print(", feq.: ");
	Serial.print(targetPeak.position, 6);	
	Serial.print(", height: ");
	Serial.print(targetPeak.height, 6);	
	Serial.println();
	/* Mark event */
	BlinkLed(1);
	while(true); /* Run Once */
	// delay(3000); /* Repeat after delay */
};

Ultimately, the PrintVector() function allows the export of abscissa and ordinate data taken from the time domain and frequency domain.

void PrintVector(uint8_t scaleType)
{	
	uint16_t bufferSize = _samples;
	if (scaleType == SCL_FREQUENCY) {
		bufferSize >>= 1;
	}
	for (uint16_t i = 0; i < bufferSize; i++) {
		float abscissa;
		/* Print abscissa value */
		switch (scaleType) {
		case SCL_INDEX:
			abscissa = float(i);
			break;
		case SCL_TIME:
			abscissa = (i / _samplingFrequency);
			break;
		case SCL_FREQUENCY:
			abscissa = ((i * _samplingFrequency) / _samples);
			break;
		}
		Serial.print(abscissa, 6);
		Serial.print('\t');
		/* Print ordinate */
		Serial.print(DSP.ReadData(i), 6);
		Serial.println();
	}
	Serial.println();
};

 

Leave a Reply

You must be logged in to post a comment.