/* * Process_DSP_R.ino * Basically the Hill code with changes for Teensy floating point * OpenAudio_ArduinoLibrary. * Bob Larkin W7PUA, September 2022. * */ /* Thank you to Charlie Hill, W5BAA, https://github.com/Rotron/Pocket-FT8 * for the conversion to Teensy operation, as well as * to Kārlis Goba, YL3JG, https://github.com/kgoba/ft8_lib. * Thanks to all the contributors to the Joe Taylor WSJT project. * See "The FT4 and FT8 Communication Protocols," Steve Franks, K9AN, * Bill Somerville, G4WJS and Joe Taylor, K1JT, QEX July/August 2020 * pp 7-17 as well as https://www.physics.princeton.edu/pulsar/K1JT */ /* ***** MIT License *** Copyright (C) 2021, Charles Hill Copyright (C) 2022, Bob Larkin on changes for F32 library Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* NOTE - The frequency range used here is the same as used by others. * This is about bin 128 to 768, or 400 Hz to 2400 Hz. * Stations do operate outside this range. It would be easy to * increase the range here. The library function radioFT8Demodulator_F32 is filtered * to pass all frequencies up to, at least 2800 Hz. */ // Following are used inside extract_power() float32_t fft_buffer[2048]; float fftOutput[2048]; float window[2048]; // Change to 1024 by symmetry <<<<<<<<<<<<<<<<<<< arm_rfft_fast_instance_f32 Sfft; float32_t powerSum = 0.0f; // Use these for snr estimate float32_t runningSum = 0.0f; float32_t powerMax = 0.0f; float32_t runningMax = 0.0f; float32_t noiseBuffer[8]; // Circular storage uint16_t noiseBufferWrite = 0; // Array index bool noiseMeasured = false; // <<<<<=128 && i<768) // Omit the first 400 Hz and last 800 Hz powerSum += fftOutput[i]; if(fftOutput[i] > powerMax) powerMax = fftOutput[i]; // Next, 20*log10() (not 10*) is to the make 8-bit resolution 0.5 dB. // The floats range from nothing to 40*log10(1024)=120 for a // pure sine wave. For FT8, we never encounter this. To keep // the sine wave answer below 256 would use an upward // offset of 256-120=136. This totally prevents overload! // Borrow fft_buffer for a moment: fft_buffer[i] = 136.0f + 20.0f*log10f( 0.0000001f + fftOutput[i] ); } fft_buffer[0] = 0.000001; // Fake DC term /* Noise needs to be estimated to determine snr. Two cases: * runningMax/runningSum < 100 This is weak signal case for which * the runningSum must be used alone. * runningMax/runningSum > 100 Here the 2 second quiet period can * can be found and running Sum used * when runningMax/runningSum is high. */ runningSum = 0.80f*runningSum + 0.20f*powerSum; // Tracks changes in pwr runningMax = 0.99f*runningMax + 0.01f*powerMax; // Slow decay // Put the sum intocircular buffer noiseBuffer[ 0X0007 & noiseBufferWrite++ ] = 0.00156f*runningSum; for(int kk=0; kk<8; kk++) y[kk] = (float32_t)noiseBuffer[ 0X0007 & (kk + noiseBufferWrite) ]; //fitCurve (int order, int nPoints, float32_t py[], int nCoeffs, float32_t *coeffs) fitCurve(2, 8, y, 3, noiseCoeff); float32_t y9 = noiseCoeff[2] + 9.0f*noiseCoeff[1] + 81.0f*noiseCoeff[0]; if(runningMax > 100.0f*0.00156f*runningSum && y9 > 2.0f*noiseCoeff[2] && !noiseMeasured) { // This measurement occurs once every 15 sec, but may be just before // or just after decode. Either way, the "latest" noise estimate is used. noiseMeasured = true; // Reset after decode() noisePowerEstimateH = 0.2f*(y[0]+y[1]+y[2]+y[3]+y[4]); noisePwrDBIntH = (int16_t)(10.0f*log10f(noisePowerEstimateH)); noisePeakAveRatio = runningMax/(0.00156*runningSum); #ifdef DEBUG_N Serial.println("Noise measurement between transmit time periods:"); Serial.print(" rSum, rMax= "); Serial.print(0.00156*runningSum, 5); Serial.print(" "); Serial.print(runningMax, 5); Serial.print(" Ratio= "); Serial.print(noisePeakAveRatio, 3); Serial.print(" Int noise= "); Serial.println(noisePwrDBIntH); // dB increments #endif } // Loop over two frequency bin offsets. This first picks up 367 even // numbered fft_buffer[] followed by 367 odd numbered bins. This is // a frequency shift of 3.125 Hz. With windowing, the bandwidth // of each FFT output is about 6 Hz, close to a match for the // 0.16 sec transmission time. /* First pass: j on (0, 367) j*2+freq_sub on (0, 734) (even) * Secnd pass: j on (0, 367) j*2+freq_sub on (1, 735) (odd) */ for (int freq_sub=0; freq_sub<2; ++freq_sub) { for (int j=0; j<368; ++j) { export_fft_power[offset] = (uint8_t)fft_buffer[j*2 + freq_sub]; ++offset; } } } // End extract_power() // =============================================================== // CURVE FIT /* curveFitting - Library for fitting curves to given points using Least Squares method, with Cramer's rule used to solve the linear equation. Created by Rowan Easter-Robinson, August 23, 2018. Released into the public domain. Converted to float32_t, made specific to FT8 case Bob L Oct 2022 */ void cpyArray(float32_t *src, float32_t*dest, int n){ for (int i = 0; i < n*n; i++){ dest[i] = src[i]; } } void subCol(float32_t *mat, float32_t* sub, uint8_t coln, uint8_t n){ if (coln >= n) return; for (int i = 0; i < n; i++){ mat[(i*n)+coln] = sub[i]; } } /*Determinant algorithm taken from // https://codeforwin.org/2015/08/c-program-to-find-determinant-of-matrix.html */ int trianglize(float32_t **m, int n) { int sign = 1; for (int i = 0; i < n; i++) { int max = 0; for (int row = i; row < n; row++) if (fabs(m[row][i]) > fabs(m[max][i])) max = row; if (max) { sign = -sign; float32_t *tmp = m[i]; m[i] = m[max], m[max] = tmp; } if (!m[i][i]) return 0; for (int row = i + 1; row < n; row++) { float32_t r = m[row][i] / m[i][i]; if (!r) continue; for (int col = i; col < n; col ++) m[row][col] -= m[i][col] * r; } } return sign; } float32_t det(float32_t *in, int n) { float32_t *m[n]; m[0] = in; for (int i = 1; i < n; i++) m[i] = m[i - 1] + n; int sign = trianglize(m, n); if (!sign) return 0; float32_t p = 1; for (int i = 0; i < n; i++) p *= m[i][i]; return p * sign; } /*End of Determinant algorithm*/ //Raise x to power float32_t curveFitPower(float32_t base, int exponent){ if (exponent == 0){ return 1; } else { float32_t val = base; for (int i = 1; i < exponent; i++){ val = val * base; } return val; } } #define MAX_ORDER 4 int fitCurve (int order, int nPoints, float32_t py[], int nCoeffs, float32_t *coeffs) { int i, j; float32_t T[MAX_ORDER] = {0}; //Values to generate RHS of linear equation float32_t S[MAX_ORDER*2+1] = {0}; //Values for LHS and RHS of linear equation float32_t denom; //denominator for Cramer's rule, determinant of LHS linear equation float32_t x, y; float32_t px[nPoints]; //Generate X values, from 0 to n for (i=0; i