/* ReceiverFM.ino Bob Larkin 26 April 2020 * No copyright. * * This INO runs the FM detector on input data from the Codec. It * requires an FM modulated signal source at 15 kHz. The Serial out is * the squelch level generated by the squelch noise detector. * * Commented out is a test of introducing a sine wave to the * FM Detector and taking 512 samples of the output. It is * a static test with a fixed frequency for test and so * the output DC value and noise can be tested. Note that the 512 * samples include the startup transient, so the first 300, * or so, points should be ignored in seeing the DC value. * * An option is to change the squelch noise filter (commented out) * and this serves as an example of changing the 10 coefficients. * * Another commented out loop code will print the observed spectrum using * the 1024 point FFT. This shows the NBFM de-emphasis curve. * * Change the value of sine1.frequency to see the DC output change. * See FMReceiver2.ino for testing with real AC modulation. * * As an alternative the input can come from the ADC for "SINE_ADC 0" * * Output is sent to left channel SGTL5000 DAC. */ #include "Audio.h" #include // SINE_ADC 1 for internally generated sine wave. // SINE_ADC 0 to use the SGTL5000 Teensy audio adaptor ADC/DAC #define SINE_ADC 0 #if SINE_ADC AudioSynthGaussian_F32 gwn1; AudioSynthWaveformSine_F32 sine1; AudioMixer4_F32 mix1; RadioFMDetector_F32 fmDet1; AudioOutputI2S_F32 i2sOut; AudioAnalyzeFFT1024_F32 fft1; AudioRecordQueue_F32 queue1; AudioControlSGTL5000 sgtl5000_1; AudioConnection_F32 connectA(gwn1, 0, mix1, 0); AudioConnection_F32 connectB(sine1, 0, mix1, 1); AudioConnection_F32 connectC(mix1, 0, fmDet1, 0); #else // Input from Teensy Audio Adaptor SGTL5000 // Note - With no input, the FM detector output is all noise. This // can be loud, so lower the gain below (like 0.05 gain). AudioInputI2S_F32 i2sIn; RadioFMDetector_F32 fmDet1; AudioAnalyzeFFT1024_F32 fft1; AudioRecordQueue_F32 queue1; AudioOutputI2S_F32 i2sOut; AudioControlSGTL5000 sgtl5000_1; AudioConnection_F32 connectD(i2sIn, 0, fmDet1, 0); // left #endif // Common for both input sources AudioConnection_F32 connect1(fmDet1, 1, i2sOut, 0); // Squelched AudioConnection_F32 connect2(fmDet1, 0, fft1, 0); AudioConnection_F32 connect3(fmDet1, 0, queue1, 0); float dt1[512]; // Place to save output float *pq1, *pd1; uint16_t k; int i; void setup(void) { AudioMemory(5); AudioMemory_F32(100); Serial.begin(300); delay(500); // Any rate is OK Serial.println("Serial Started"); sgtl5000_1.enable(); sgtl5000_1.inputSelect(AUDIO_INPUT_LINEIN); #if SINE_ADC sine1.frequency(15000.0); sine1.amplitude(0.0001f); gwn1.amplitude(0.1f); #endif fft1.setOutputType(FFT_DBFS); fft1.setNAverage(10); // The FM detector has error checking during object construction // when Serial.print is not available. See RadioFMDetector_F32.h: Serial.print("FM Initialization errors: "); Serial.println( fmDet1.returnInitializeFMError() ); // The following enables error checking inside of the "ubdate()" // Output goes to the Serial (USB) Monitor. Normally, this is quiet. fmDet1.showError(1); fmDet1.setSquelchThreshold(0.7f); // Here is an example of designing and using a non-default squelch // noise filter. The INO provides storage for the new coefficients. // static float newFilter[10]; // freq Q ptr to Coeff // setBandpassBiQuad(2000.0f, 3.0f, &newFilter[0]); // setBandpassBiQuad(4000.0f, 3.0f, &newFilter[5]); // fmDet1.setSquelchFilter(newFilter); // Set the volume control (0.0 to 1.0) i2sOut.setGain(0.05f); queue1.begin(); i = 0; k=0; } void loop(void) { float lData[512]; Serial.print("sqLevel "); Serial.println(fmDet1.getSquelchLevel(), 6); delay(500); /* if( fft1.available() ) { float* pd = fft1.getData(); for(int k=0; k<512; k++) lData[k] = pd[k]; for(int k=0; k<512; k++) Serial.println(lData[k],3); Serial.println(" -------"); } */ /* // Collect 512 samples and output to Serial // This "if" will be active for i = 0,1,2,3 if (queue1.available() >= 1) { if( i>=0 && i<4) { pq1 = queue1.readBuffer(); pd1 = &dt1[i*128]; for(k=0; k<128; k++) *pd1++ = *pq1++; queue1.freeBuffer(); if(i++==3) { i=4; // Only collect 4 blocks queue1.end(); // No more data to queue1 } } else { queue1.freeBuffer(); } } // We have 512 data samples. Serial.print them if(i == 4) { #if SINE_ADC Serial.println("For 14,000 Hz sine wave input:"); #endif Serial.println("512 samples of FM Det output, starting t=0"); Serial.println("Time in sec, FM Output, Dev from 15,000 Hz:"); for (k=0; k<512; k++) { Serial.print (0.000022667*(float32_t)k, 6); Serial.print (", "); Serial.print (dt1[k],7); Serial.print (", "); Serial.println (dt1[k]/0.000142421, 2); // Convert to Hz } i = 5; } if(i==5) { i = 6; Serial.print("CPU: Percent Usage, Max: "); Serial.print(AudioProcessorUsage()); Serial.print(", "); Serial.println(AudioProcessorUsageMax()); Serial.print("Int16 Memory Usage, Max: "); Serial.print(AudioMemoryUsage()); Serial.print(", "); Serial.println(AudioMemoryUsageMax()); Serial.print("Float Memory Usage, Max: "); Serial.print(AudioMemoryUsage_F32()); Serial.print(", "); Serial.println(AudioMemoryUsageMax_F32()); Serial.println(); } */ } // Find BiQuad Coefficients for Squelch Noise filter. // Only used if the default -6 dB points of 2680 and 4420 Hz // are not suitable void setBandpassBiQuad(float frequency, float q, float* cf) { double w0 = frequency * (2 * 3.141592654 / 44100); // sampleRate_Hz); double sinW0 = sin(w0); double alpha = sinW0 / ((double)q * 2.0); double cosW0 = cos(w0); double scale = 1.0 / (1.0+alpha); /* b0 */ cf[0] = alpha * scale; /* b1 */ cf[1] = 0; /* b2 */ cf[2] = (-alpha) * scale; /* a1 */ cf[3] = -(-2.0 * cosW0) * scale; /* a2 */ cf[4] = -(1.0 - alpha) * scale; }