From 69a298f51d9ec8a05c5fff1f0e61bfec7b43c4b7 Mon Sep 17 00:00:00 2001 From: boblark Date: Mon, 13 Feb 2023 19:23:03 -0800 Subject: [PATCH] Add CESSB for zero-IF radio transmitters, new --- OpenAudio_ArduinoLibrary.h | 1 + examples/CESSB_ZeroIF/CESSB_ZeroIF.ino | 218 ++++++++++++ examples/CESSB_ZeroIF/hilbert251A_Z_.h | 253 ++++++++++++++ radioCESSB_Z_transmit_F32.cpp | 255 ++++++++++++++ radioCESSB_Z_transmit_F32.h | 444 +++++++++++++++++++++++++ 5 files changed, 1171 insertions(+) create mode 100644 examples/CESSB_ZeroIF/CESSB_ZeroIF.ino create mode 100644 examples/CESSB_ZeroIF/hilbert251A_Z_.h create mode 100644 radioCESSB_Z_transmit_F32.cpp create mode 100644 radioCESSB_Z_transmit_F32.h diff --git a/OpenAudio_ArduinoLibrary.h b/OpenAudio_ArduinoLibrary.h index 7e33988..243f544 100644 --- a/OpenAudio_ArduinoLibrary.h +++ b/OpenAudio_ArduinoLibrary.h @@ -57,6 +57,7 @@ #include "AudioFilterEqualizer_F32.h" #include "AudioFilterFIRGeneral_F32.h" #include "radioCESSBtransmit_F32.h" +#include "radioCESSB_Z_transmit_F32.h" #include "RadioFMDetector_F32.h" #include "radioBFSKmodulator_F32.h" #include "radioFT8Modulator_F32.h" diff --git a/examples/CESSB_ZeroIF/CESSB_ZeroIF.ino b/examples/CESSB_ZeroIF/CESSB_ZeroIF.ino new file mode 100644 index 0000000..648771a --- /dev/null +++ b/examples/CESSB_ZeroIF/CESSB_ZeroIF.ino @@ -0,0 +1,218 @@ +// CESSB_ZeroIF.ino +// This tests the Controlled Envelope Single Sideband generator version +// that produces a zero-IF signal 0to 3kHz or 0 to -3 kHz. +// Uses radioCESSB_Z_transmit_F32.h and .cpp. See the .h file for +// more information and references. +// Tests with voice from SD Card file and a 1 second 750 Hz tone burst. +// +// The SD card may connect to different pins, depending on the +// hardware you are using. Configure the SD card +// pins to match your hardware. It is set for T4.x Rev D PJRC +// Teensy Audio Adaptor card here. +// +// Your microSD card must have the WAV file loaded to it: +// W9GR48.WAV +// These are at +// https://github.com/chipaudette/OpenAudio_ArduinoLibrary/blob/master/utility/ +// +// This example code is in the public domain. + +#include +#include +#include +#include +#include "OpenAudio_ArduinoLibrary.h" + +// T3.x supported sample rates: 2000, 8000, 11025, 16000, 22050, 24000, 32000, 44100, 44117, 48000, +// 88200, 88235 (44117*2), 95680, 96000, 176400, 176470, 192000 +// T4.x supports any sample rate the codec will handle. +const float sample_rate_Hz = 48000.0f; +const int audio_block_samples = 128; // Always 128 +AudioSettings_F32 audio_settings(sample_rate_Hz, audio_block_samples); + +AudioSynthWaveformSine_F32 sine1(audio_settings); +AudioSDPlayer_F32 playWav1(audio_settings); +AudioMixer4_F32 mixer4_0; +radioCESSB_Z_transmit_F32 cessb1(audio_settings); +RadioIQMixer_F32 iqMixer1(audio_settings); +AudioMixer4_F32 mixer4_2; +AudioFilter90Deg_F32 filter90deg1(audio_settings); +RadioIQMixer_F32 iqMixer2(audio_settings); +AudioMixer4_F32 mixer4_1; +AudioOutputI2S_F32 audioOutput(audio_settings); +AudioAnalyzeFFT1024_F32 fft1; + +AudioConnection_F32 patchCord0(playWav1, 0, mixer4_0, 0); +AudioConnection_F32 patchCordb(sine1, 0, mixer4_0, 1); +AudioConnection_F32 patchCordc(mixer4_0, 0, cessb1, 0); +AudioConnection_F32 patchCord1(cessb1, 0, iqMixer1, 0); +AudioConnection_F32 patchCord2(cessb1, 1, iqMixer1, 1); +AudioConnection_F32 patchCord9(iqMixer1, 0, mixer4_2, 0); +AudioConnection_F32 patchCord10(iqMixer1,1, mixer4_2, 1); + +// mixer4_2 is transmitter SSB output, iqMixer2 is receiver input +AudioConnection_F32 patchCord14(mixer4_2,0, iqMixer2, 0); +AudioConnection_F32 patchCord3(iqMixer2, 0, filter90deg1, 0); +AudioConnection_F32 patchCord4(iqMixer2, 1, filter90deg1, 1); +AudioConnection_F32 patchCord7(filter90deg1, 0, mixer4_1, 0); +AudioConnection_F32 patchCord8(filter90deg1, 1, mixer4_1, 1); +AudioConnection_F32 patchCord11(mixer4_1, 0, audioOutput, 0); +AudioConnection_F32 patchCord12(mixer4_1, 0, audioOutput, 1); +AudioConnection_F32 patchCord13(mixer4_2, 0, fft1, 0); + +AudioControlSGTL5000 sgtl5000_1; + +// Use these with the Teensy 4.x Rev D Audio Shield (NOT for T3.x) +#define SDCARD_CS_PIN_Z 10 +#define SDCARD_MOSI_PIN_Z 11 +#define SDCARD_SCK_PIN_Z 13 + +// Filter for AudioFilter90Deg_F32 hilbert1, only for receiving the CESSB +#include "hilbert251A_Z_.h" + +// wavData is a global struct, definined in AudioSDPlayer_F32.h +// This provides information about the current WAV file to this .INO +struct wavData* pCurrentWavData; +// And data about the CESSB +struct levelsZ* pLevelData; +uint32_t writeOne = 0; +uint32_t cntFFT = 0; +uint32_t ttt; // For timing test audio + +uint32_t tp; + +void setup() { // ********** SETUP ********** + Serial.begin(9600); delay(1000); + Serial.println("*** Test CESSB Zero-IF from SD Card Voice Sample ***"); + + AudioMemory_F32(70, audio_settings); + pCurrentWavData = playWav1.getCurrentWavData(); + sgtl5000_1.enable(); + delay(500); + SPI.setMOSI(SDCARD_MOSI_PIN_Z); + SPI.setSCK(SDCARD_SCK_PIN_Z); + Serial.print("SD.begin() returns "); Serial.println(SD.begin(SDCARD_CS_PIN_Z)); + +// sine0.frequency(468.75f); // 2-tone generators +// sine0.amplitude(0.707107); + sine1.frequency(750.0f); + sine1.amplitude(0.707107); + + // Build the CESSB SSB transmitter + // The WAV file has carefully controlled 0.707 peaks. We bring these to 1.000 + mixer4_0.gain(0, 1.41421356f); // Play WAV file on + mixer4_0.gain(2, 0.0); // Sine Wave 1 off + + cessb1.setSampleRate_Hz(48000.0f); + // Set input, correction, and output gains + float32_t Pre_CESSB_Gain = 1.5f; // Use to set amount of clipping, 1.0 to 2.0f, 3 is excessive + cessb1.setGains(Pre_CESSB_Gain, 1.4f, 1.0f); + cessb1.setSideband(false); + pLevelData = cessb1.getLevels(0); // Gets pointer to struct + + // Generate SSB at 15 kHz from zero-IF signal of CESSB generator + iqMixer1.useTwoChannel(true); + iqMixer1.frequency(15000); + mixer4_2.gain(1, 1.0f); // 1.0f for LSB, -1.0f for USB + + // Need a receiver for the SSB transmitter to let us hear the results. + iqMixer2.frequency(15000.0f); + iqMixer2.useTwoChannel(false); + filter90deg1.begin(hilbert251A, 251); + mixer4_1.gain(0, -1.0f); // LSB, + for USB + audioOutput.setGain(0.02); // <<< Output volume control + fft1.setOutputType(FFT_DBFS); + fft1.windowFunction(AudioWindowBlackmanHarris1024); + fft1.setNAverage(16); + ttt = millis(); // Time test audio + tp=millis(); + } + +void playFile(const char *filename) { + if(playWav1.isPlaying()) + return; + Serial.println(""); + Serial.print("Playing file: "); + Serial.println(filename); + playWav1.play(filename); // Start playing the file. + // A brief delay for the library read WAV info + delay(25); + Serial.print("WAV file format = "); Serial.println(pCurrentWavData->audio_format); + Serial.print("WAV number channels = "); Serial.println(pCurrentWavData->num_channels); + Serial.print("WAV File Sample Rate = "); Serial.println(pCurrentWavData->sample_rate); + Serial.print("Number of bits per Sample = "); Serial.println(pCurrentWavData->bits); + Serial.print("File length, seconds = "); + Serial.println(0.001f*(float32_t)playWav1.lengthMillis(), 3); +} + +void loop() { + uint32_t tt=millis() - ttt; + if(tt < 2000) + { + // Thanks to W9GR for the test file, W9GR48.WAV. This is intended for testing + // the CESSB radio transmission system. + playFile("W9GR48.WAV"); + mixer4_0.gain(0, 1.41421356f); // Play WAV file on + mixer4_0.gain(1, 0.0f); // Sine Wave 1 off + } + else if(tt > 12300 && tt<13300) + { + mixer4_0.gain(1, 0.0f); // Play WAV file off + // The following puts a 1-sec 750 Hz, full amplitude tone into the input + // .707 on the generator and 1.414 here make the peak sine wave 1.000 at the CESSB input + mixer4_0.gain(1, 1.41421356f); // Sine Wave 1 on, 750 Hz + } + else if(tt >= 13300) + { + mixer4_0.gain(0, 1.41421356f); // Play WAV file on + mixer4_0.gain(1, 0.0f); // Sine Wave 1 off + ttt = millis(); // Start again + } + delay(1); + +// Un-comment the following to print out the spectrum +/* + if(fft1.available() && ++cntFFT>100 && cntFFT<102) + { + for(int kk=0; kk<512; kk++) + { + Serial.print(46.875f*(float32_t)kk); Serial.print(","); + Serial.println(fft1.read(kk)); + } + } + */ + + if(cessb1.levelDataCount() > 300) // Typically 300 to 3000 + { + cessb1.getLevels(1); // Cause write of data to struct & reset + + // Detailed Report + Serial.print(10.0f*log10f(pLevelData->pwr0)); + Serial.print(" In Ave Pwr Out "); + Serial.println(10.0f*log10f(pLevelData->pwr1)); + Serial.print(20.0f*log10f(pLevelData->peak0)); + Serial.print(" In Peak Out "); + Serial.println(20.0f*log10f(pLevelData->peak1)); + Serial.print(pLevelData->peak0, 6); + Serial.print(" In Peak Volts Out "); + Serial.println(pLevelData->peak1, 6); + Serial.print("Enhancement = "); + float32_t enhance = (10.0f*log10f(pLevelData->pwr1) - 20.0f*log10f(pLevelData->peak1)) - + (10.0f*log10f(pLevelData->pwr0) - 20.0f*log10f(pLevelData->peak0)); + if(enhance<1.0f) enhance = 1.0f; + Serial.print(enhance); Serial.println(" dB"); + +/* + // CSV Report suitable for entering to spread sheet + // InAve, InPk, OutAve, OutPk, EnhancementdB + Serial.print(pLevelData->pwr0, 5); Serial.print(","); + Serial.print(pLevelData->peak0, 5); Serial.print(","); + Serial.print(pLevelData->pwr1, 5); Serial.print(","); + Serial.print(pLevelData->peak1, 5); Serial.print(","); + float32_t enhance = (10.0f*log10f(pLevelData->pwr1) - 20.0f*log10f(pLevelData->peak1)) - + (10.0f*log10f(pLevelData->pwr0) - 20.0f*log10f(pLevelData->peak0)); + if(enhance<1.0f) enhance = 1.0f; + Serial.println(enhance); + */ + } + } diff --git a/examples/CESSB_ZeroIF/hilbert251A_Z_.h b/examples/CESSB_ZeroIF/hilbert251A_Z_.h new file mode 100644 index 0000000..745db86 --- /dev/null +++ b/examples/CESSB_ZeroIF/hilbert251A_Z_.h @@ -0,0 +1,253 @@ +// Following is 251 term Hilbert FIR filter +float32_t hilbert251A[]={ +0.0000003255, +0.0000000000, +0.0000030702, +0.0000000000, +0.0000089286, +0.0000000000, +0.0000183061, +0.0000000000, +0.0000316287, +0.0000000000, +0.0000493436, +0.0000000000, +0.0000719193, +0.0000000000, +0.0000998451, +0.0000000000, +0.0001336320, +0.0000000000, +0.0001738120, +0.0000000000, +0.0002209393, +0.0000000000, +0.0002755899, +0.0000000000, +0.0003383625, +0.0000000000, +0.0004098790, +0.0000000000, +0.0004907853, +0.0000000000, +0.0005817525, +0.0000000000, +0.0006834782, +0.0000000000, +0.0007966881, +0.0000000000, +0.0009221383, +0.0000000000, +0.0010606178, +0.0000000000, +0.0012129515, +0.0000000000, +0.0013800041, +0.0000000000, +0.0015626848, +0.0000000000, +0.0017619529, +0.0000000000, +0.0019788241, +0.0000000000, +0.0022143787, +0.0000000000, +0.0024697715, +0.0000000000, +0.0027462425, +0.0000000000, +0.0030451312, +0.0000000000, +0.0033678928, +0.0000000000, +0.0037161183, +0.0000000000, +0.0040915578, +0.0000000000, +0.0044961498, +0.0000000000, +0.0049320558, +0.0000000000, +0.0054017033, +0.0000000000, +0.0059078375, +0.0000000000, +0.0064535860, +0.0000000000, +0.0070425380, +0.0000000000, +0.0076788436, +0.0000000000, +0.0083673390, +0.0000000000, +0.0091137048, +0.0000000000, +0.0099246683, +0.0000000000, +0.0108082660, +0.0000000000, +0.0117741868, +0.0000000000, +0.0128342256, +0.0000000000, +0.0140028938, +0.0000000000, +0.0152982506, +0.0000000000, +0.0167430570, +0.0000000000, +0.0183664064, +0.0000000000, +0.0202060801, +0.0000000000, +0.0223120327, +0.0000000000, +0.0247516963, +0.0000000000, +0.0276183140, +0.0000000000, +0.0310445375, +0.0000000000, +0.0352256211, +0.0000000000, +0.0404611696, +0.0000000000, +0.0472354231, +0.0000000000, +0.0563851215, +0.0000000000, +0.0694911881, +0.0000000000, +0.0899418673, +0.0000000000, +0.1265473875, +0.0000000000, +0.2116132716, +0.0000000000, +0.6358933477, +0.0000000000, +-0.6358933478, +0.0000000000, +-0.2116132717, +0.0000000000, +-0.1265473876, +0.0000000000, +-0.0899418674, +0.0000000000, +-0.0694911882, +0.0000000000, +-0.0563851216, +0.0000000000, +-0.0472354232, +0.0000000000, +-0.0404611697, +0.0000000000, +-0.0352256212, +0.0000000000, +-0.0310445376, +0.0000000000, +-0.0276183141, +0.0000000000, +-0.0247516964, +0.0000000000, +-0.0223120328, +0.0000000000, +-0.0202060802, +0.0000000000, +-0.0183664065, +0.0000000000, +-0.0167430571, +0.0000000000, +-0.0152982507, +0.0000000000, +-0.0140028939, +0.0000000000, +-0.0128342257, +0.0000000000, +-0.0117741869, +0.0000000000, +-0.0108082661, +0.0000000000, +-0.0099246684, +0.0000000000, +-0.0091137049, +0.0000000000, +-0.0083673391, +0.0000000000, +-0.0076788437, +0.0000000000, +-0.0070425381, +0.0000000000, +-0.0064535861, +0.0000000000, +-0.0059078376, +0.0000000000, +-0.0054017034, +0.0000000000, +-0.0049320559, +0.0000000000, +-0.0044961499, +0.0000000000, +-0.0040915579, +0.0000000000, +-0.0037161184, +0.0000000000, +-0.0033678929, +0.0000000000, +-0.0030451313, +0.0000000000, +-0.0027462426, +0.0000000000, +-0.0024697716, +0.0000000000, +-0.0022143788, +0.0000000000, +-0.0019788242, +0.0000000000, +-0.0017619530, +0.0000000000, +-0.0015626849, +0.0000000000, +-0.0013800042, +0.0000000000, +-0.0012129516, +0.0000000000, +-0.0010606179, +0.0000000000, +-0.0009221384, +0.0000000000, +-0.0007966882, +0.0000000000, +-0.0006834783, +0.0000000000, +-0.0005817526, +0.0000000000, +-0.0004907854, +0.0000000000, +-0.0004098791, +0.0000000000, +-0.0003383626, +0.0000000000, +-0.0002755900, +0.0000000000, +-0.0002209394, +0.0000000000, +-0.0001738121, +0.0000000000, +-0.0001336321, +0.0000000000, +-0.0000998452, +0.0000000000, +-0.0000719194, +0.0000000000, +-0.0000493437, +0.0000000000, +-0.0000316288, +0.0000000000, +-0.0000183062, +0.0000000000, +-0.0000089287, +0.0000000000, +-0.0000030703, +0.0000000000, +-0.0000003256}; diff --git a/radioCESSB_Z_transmit_F32.cpp b/radioCESSB_Z_transmit_F32.cpp new file mode 100644 index 0000000..c5304e9 --- /dev/null +++ b/radioCESSB_Z_transmit_F32.cpp @@ -0,0 +1,255 @@ +/* + * radioCESSB_Z_transmit_F32.cpp + * This version of CESSB is intended for Zero-IF hardware. + * + * Bob Larkin, in support of the library: + * Chip Audette, OpenAudio, Dec 2022 + * + * MIT License, Use at your own risk. + * + * See radioCESSB_Z_transmit_F32.h for technical info. + * + */ +// NOTE: 96 ksps sample rate not yet implemented +#include "radioCESSB_Z_transmit_F32.h" + +void radioCESSB_Z_transmit_F32::update(void) { + audio_block_f32_t *blockIn, *blockOutI, *blockOutQ; + + // Temporary storage. At an audio sample rate of 96 ksps, the used + // space will be half of the declared space. + float32_t HilbertIn[32]; + float32_t workingDataI[128]; + float32_t workingDataQ[128]; + float32_t delayedDataI[64]; // Allows batching of 64 data points + float32_t delayedDataQ[64]; + float32_t diffI[64]; + float32_t diffQ[64]; + + if(sampleRate!=SAMPLE_RATE_44_50 && sampleRate!=SAMPLE_RATE_88_100) + return; + + // Get all needed resources, or return if not available. + blockIn = AudioStream_F32::receiveReadOnly_f32(); + if (!blockIn) + { return; } + blockOutI = AudioStream_F32::allocate_f32(); // a block for I output + if (!blockOutI) + { + AudioStream_F32::release(blockIn); + return; + } + blockOutQ = AudioStream_F32::allocate_f32(); // and for Q + if (!blockOutQ) + { + AudioStream_F32::release(blockOutI); + AudioStream_F32::release(blockIn); + return; + } + // The audio input peak levels for start of CESSB are -1.0, 1.0 + // when gainIn==1.0. + +/* // A +/- pulse to test timing of various delays + // PULSE TEST for diagnostics only + for(int kk=0; kk<128; kk++) + { + uint16_t y=(ny & 1023); + // pulse max at is just starting to clip + + if (y>=100 && y<115) blockIn->data[kk] = 4.189f; + else if(y>=115 && y<130) blockIn->data[kk] = -4.189f; + else blockIn->data[kk] = 0.0f; + ny++; + // Serial.println(blockIn->data[kk]); + } + */ + // uint32_t ttt=micros(); + + // Decimate 48 ksps to 12 ksps, 128 to 32 samples + // or 96 ksps to 12 ksps, 128 to 16 samples + arm_fir_decimate_f32(&decimateInst, &(blockIn->data[0]), + &HilbertIn[0], 128); + // We now have nW=32 (for 48 ksps) or 16 (for 96 ksps) samples to process + + // Apply the Hilbert transform FIR. + arm_fir_f32(&firInstHilbertI, &HilbertIn[0], &workingDataI[0], nW); + + /* ======= Sidebar: Circular 2^n length delay arrays ======== + * + * The length of the array, N, + * must be a power of 2. For example N=2^6 = 64. The minimum + * delay possible is the trivial case of 0 up to N-1. + * As in C, let i be the index of the N array elements which + * would range from 0 to N-1. If p is an integer, that is a power + * of 2 also, with p >= n, it can serve as an index to the + * delay array by "ANDing" it with (N-1). That is, + * i = p & (N-1). It can be convenient if the largest + * possible value of the integer p, plus 1, is an integer multiple + * of the arrray size N, as then the rollover of p will not cause + * a jump in i. For instance, if p is an uint8_t with a maximum + * value of pmax=255, (pmax+1)/N = (255+1)/64 = 4, which is an + * integer. This combination will have no problems from rollover + * of p. + * + * The new data point is entered at index p & (N - 1). To + * achieve a delay of d, the output of the delay array is taken + * at index ((p-d) & (N-1)). The index is then incremented by 1. + * + * There are three delay lines of this construction below, starting + * with delayHilbertQ + * ========================================================== */ + + // Circular delay line for signal to align data with Hilbert FIR output + // nW (32 for 48ksps) points into and out of the delay array + for(uint16_t i=0; i maxMag0) + maxMag0 = vWD; // Peak envelope + countPower0++; + } + + // Interpolate by 2 up to 24 ksps rate + for(int k=0; k 1.0f) // This the clipping, scaled to 1.0, desired max + { + workingDataI[kk] /= mag; + workingDataQ[kk] /= mag; + } + } + + // clipperIn needs spectrum control, so LP filter it. + // Both BW of the signal and the sample rate have been doubled. + arm_fir_f32(&firInstClipperI, workingDataI, workingDataI, nC); + arm_fir_f32(&firInstClipperQ, workingDataQ, workingDataQ, nC); + // Ready to compensate for filter overshoots + for (int k=0; k eMax) // Data point just entered + eMax = osEnv[(indexOsEnv) & 0X03]; + if(osEnv[(indexOsEnv-1) & 0X03] > eMax) // Entered one before + eMax = osEnv[(indexOsEnv-1) & 0X03]; + if(osEnv[(indexOsEnv-2) & 0X03] > eMax) // Entered one before that + eMax = osEnv[(indexOsEnv-2) & 0X03]; + if(eMax < 1.0f) + eMax = 1.0f; // Below clipping region + indexOsEnv++; + + // Clip the signal to 1.0. -2 allows 1 look ahead on signal. + float32_t eCorrectedI = osDelayI[(indexOsDelay - 2) & 0X3F] / eMax; + float32_t eCorrectedQ = osDelayQ[(indexOsDelay - 2) & 0X3F] / eMax; + // Filtering is linear, so we only need to filter the difference between + // the signal and the clipper output. This needs less filtering, as the + // difference is many dB below the signal to begin with. Hershberger 2014 + diffI[k] = osDelayI[(indexOsDelay - 2) & 0X3F] - eCorrectedI; + diffQ[k] = osDelayQ[(indexOsDelay - 2) & 0X3F] - eCorrectedQ; + } // End, for k=0 to 63 + + // Filter the differences, osFilter has 123 taps and 61 delay + arm_fir_f32(&firInstOShootI, diffI, diffI, nC); + arm_fir_f32(&firInstOShootQ, diffQ, diffQ, nC); + + // Do the overshoot compensation + for(int k=0; k maxMag1) + maxMag1 = vWD; // Peak envelope + countPower1++; + } + + // Finally interpolate to 48 or 96 ksps. Data is in workingDataI[k] + // and is 64 samples for audio 48 ksps. + for(int k=0; kdata[0], 128); + arm_fir_f32(&firInstInterpolate2Q, workingDataQ, &blockOutQ->data[0], 128); + // Voltage gain from blockIn->data to here for small sine wave is 1.0 + + AudioStream_F32::transmit(blockOutI, 0); // send the outputs + AudioStream_F32::transmit(blockOutQ, 1); + AudioStream_F32::release(blockIn); // Release the blocks + AudioStream_F32::release(blockOutI); + AudioStream_F32::release(blockOutQ); + + jjj++; //For test printing + // Serial.println(micros() - ttt); +} // end update() diff --git a/radioCESSB_Z_transmit_F32.h b/radioCESSB_Z_transmit_F32.h new file mode 100644 index 0000000..672dfd4 --- /dev/null +++ b/radioCESSB_Z_transmit_F32.h @@ -0,0 +1,444 @@ +/* + * radioCESSB_Z_transmit_F32.h + * + * This is a modification of the CESSB algorithm to output SSB at zero carrier + * instead of 1350 Hz as the Weaver modulation produces. This allows + * transmission with zero-IF radios where the finite carrier balance + * of the hardware mixers produces the mid-band tone.The basic change + * is to use the phasing method in place of the Weaver method. However, + * all filters needed to be changed and are at the bottom of this file. + * The 12 and 24 ksps sample rates of the radioCESSB_transmit_F32 class + * are continued here as they were more than adequate for the Weaver method. + * The sine/cosine oscillator is not needed here and has been removed. + * + * + * 18 Jan 2023 (c) copyright Bob Larkin + * But with With much credit to: + * Chip Audette (OpenAudio) + * and of course, to PJRC for the Teensy and Teensy Audio Library + * + * The development of the Controlled Envelope Single Side Band (CESSB) + * was done by Dave Hershberger, W9GR. Many thanks to Dave. + * The following description is mostly taken + * from Frank, DD4WH and is on line at the GNU Radio site, ref: + * https://github-wiki-see.page/m/df8oe/UHSDR/wiki/Controlled-Envelope-Single-Sideband-CESSB + * and has been revised by Bob L. to reflect the phasing method change. + * + * Controlled Envelope Single Sideband is an invention by Dave Hershberger + * W9GR with the aim to "allow your rig to output more average power while + * keeping peak envelope power PEP the same". The increase in perceived + * loudness can be up to 4dB without any audible increase in distortion + * and without making you sound "processed" (Hershberger 2014, 2016b). + * + * The principle to achieve this is relatively simple. The process + * involves only audio baseband processing which can be done digitally in + * software without the need for modifications in the hardware or messing + * with the RF output of your rig. + * + * Controlled Envelope Single Sideband can be produced using three + * processing blocks making up a complete CESSB system: + * 1. An SSB modulator. This is implemented as the phasing method to allow + * minimum (12 kHz) decimated sample rate with the output of I & Q + * signals (a complex SSB signal). + * 2. A baseband envelope clipper. This takes the modulus of the I & Q + * signals (also called the magnitude), which is sqrt(I * I + Q * Q) + * and divides the I & Q signals by the modulus, IF the signal is + * larger than 1.0. If not, the signal remains untouched. After + * clipping, the signal is lowpass filtered with a linear phase FIR + * low pass filter with a stopband frequency of 3.0kHz + * 3. An overshoot controller . This does something similar as the + * envelope clipper: Again, the modulus is calculated (but now on + * the basis of the current and two preceding and two subsequent + * samples). If the signals modulus is larger than 1 (clipping), + * the signals I and Q are divided by the maximum of 1 or of + * (1.9 * signal). That means the clipping is overcompensated by 1.9 + * [the phasing method seems to perform best with 1.4*signal] + * which leads to a much better suppression of the overshoots from + * the first two stages. Finally, the resulting signal is again + * lowpass-filtered with a linear phase FIR filter with stopband + * frequency of 3.0khz + * + * It is important that the sample rate is high enough so that the higher + * frequency components of the output of the modulator, clipper and + * overshoot controller do not alias back into the desired signal. Also + * all the filters should be linear phase filters (FIR, not IIR). + * + * This CESSB system can reduce the overshoot of the SSB modulator from + * 61% to 1.3%, meaning about 2.5 times higher perceived SSB output power + * (Hershberger 2014). + * + * References: + * 1-Hershberger, D.L. (2014): Controlled Envelope Single Sideband. QEX + * November/December 2014 pp3-13. + * http://www.arrl.org/files/file/QEX_Next_Issue/2014/Nov-Dec_2014/Hershberger_QEX_11_14.pdf + * 2-Hershberger, D.L. (2016a): External Processing for Controlled + * Envelope Single Sideband. - QEX January/February 2016 pp9-12. + * http://www.arrl.org/files/file/QEX_Next_Issue/2016/January_February_2016/Hershberger_QEX_1_16.pdf + * 3-Hershberger, D.L. (2016b): Understanding Controlled Envelope Single + * Sideband. - QST February 2016 pp30-36. + * 4-Forum discussion on CESSB on the Flex-Radio forum, + * https://community.flexradio.com/discussion/6432965/cessb-questions + * + * Status: Experimental + * + * Inputs: 0 is voice audio input + * Outputs: 0 is I 1 is Q + * + * Functions, available during operation: + * void frequency(float32_t fr) Sets LO frequency Hz + * + * void setSampleRate_Hz(float32_t fs_Hz) Allows dynamic sample rate change for this function + * + * struct levels* getLevels(int what) { + * what = 0 returns a pointer to struct levels before data is ready + * what = 1 returns a pointer to struct levels + * + * uint32_t levelDataCount() return countPower0 + * + * void setGains(float32_t gIn, float32_t gCompensate, float32_t gOut) + * + * Time: T3.6 For an update of a 128 sample block, estimated 700 microseconds + * T4.0 For an update of a 128 sample block, measured 211 microseconds + * These times are for a 48 ksps rate. + * + * NOTE: Do NOT follow this block with any non-linear phase filtering, + * such as IIR. Minimize any linear-phase filtering such as FIR. + * Such activities enhance the overshoots and defeat the purpose of CESSB. + */ + +#ifndef _radioCESSB_Z_transmit_f32_h +#define _radioCESSB_Z_transmit_f32_h + +#include "Arduino.h" +#include "AudioStream_F32.h" +#include "arm_math.h" +#include "mathDSP_F32.h" + +#define SAMPLE_RATE_0 0 +#define SAMPLE_RATE_44_50 1 +#define SAMPLE_RATE_88_100 2 + +#ifndef M_PI +#define M_PI 3.141592653589793f +#endif + +#ifndef M_PI_2 +#define M_PI_2 1.570796326794897f +#endif + +#ifndef M_TWOPI +#define M_TWOPI (M_PI * 2.0f) +#endif + + // For the average power and peak voltage readings, global +struct levelsZ { + float32_t pwr0; + float32_t peak0; + float32_t pwr1; + float32_t peak1; + uint32_t countP; // Number of averaged samples for pwr0. + }; + +class radioCESSB_Z_transmit_F32 : public AudioStream_F32 { +//GUI: inputs:1, outputs:2 //this line used for automatic generation of GUI node +//GUI: shortName:CESSBTransmit //this line used for automatic generation of GUI node +public: + radioCESSB_Z_transmit_F32(void) : + AudioStream_F32(1, inputQueueArray_f32) + { + setSampleRate_Hz(AUDIO_SAMPLE_RATE); + //uses default AUDIO_SAMPLE_RATE from AudioStream.h + //setBlockLength(128); Always default 128 + } + + radioCESSB_Z_transmit_F32(const AudioSettings_F32 &settings) : + AudioStream_F32(1, inputQueueArray_f32) + { + setSampleRate_Hz(settings.sample_rate_Hz); + //setBlockLength(128); Always default 128 + } + + // Sample rate starts at default 44.1 ksps. That will work. Filters + // are designed for 48 and 96 ksps, however. This is a *required* + // function at setup(). + void setSampleRate_Hz(const float fs_Hz) { + sample_rate_Hz = fs_Hz; + if(sample_rate_Hz>44000.0f && sample_rate_Hz<50100.0f) + { + // Design point is 48 ksps + sampleRate = SAMPLE_RATE_44_50; + nW = 32; + nC = 64; + countLevelMax = 37; // About 0.1 sec for 48 ksps + inverseMaxCount = 1.0f/(float32_t)countLevelMax; + + arm_fir_decimate_init_f32(&decimateInst, 65, 4, + (float32_t*)decimateFilter48, &pStateDecimate[0], 128); + + arm_fir_init_f32(&firInstHilbertI, 201, (float32_t*)hilbert201_130Hz12000Hz, + &pStateHilbertI[0], nW); + + arm_fir_init_f32(&firInstInterpolate1I, 23, (float32_t*)interpolateFilter1, + &pStateInterpolate1I[0], nC); + arm_fir_init_f32(&firInstInterpolate1Q, 23, (float32_t*)interpolateFilter1, + &pStateInterpolate1Q[0], nC); + + arm_fir_init_f32(&firInstClipperI, 123, (float32_t*)clipperOut, + &pStateClipperI[0], nC); + arm_fir_init_f32(&firInstClipperQ, 123, (float32_t*)clipperOut, + &pStateClipperQ[0], nC); + + arm_fir_init_f32(&firInstOShootI, 123, (float32_t*)clipperOut, + &pStateOShootI[0], nC); + arm_fir_init_f32(&firInstOShootQ, 123, (float32_t*)clipperOut, + &pStateOShootQ[0], nC); + + arm_fir_init_f32(&firInstInterpolate2I, 23, (float32_t*)interpolateFilter1, + &pStateInterpolate2I[0], nC); + arm_fir_init_f32(&firInstInterpolate2Q, 23, (float32_t*)interpolateFilter1, + &pStateInterpolate2Q[0], nC); + } + else if(sample_rate_Hz>88000.0f && sample_rate_Hz<100100.0f) + { + // GET THINGS WORKING AT SAMPLE_RATE_44_50 FIRST AND THEN FIX UP 96 ksps + // Design point is 96 ksps +/* sampleRate = SAMPLE_RATE_88_100; //<<<<<<<<<<<<<<<<<<<<<