From f6e578b82c24735f6cc3f88ec79e51b22e417110 Mon Sep 17 00:00:00 2001 From: boblark Date: Sat, 3 Apr 2021 09:20:33 -0700 Subject: [PATCH] Added alternate 2-channel input to IQ mixers --- RadioIQMixer_F32.cpp | 53 ++++++++++++++++++++++++++++++++------------ RadioIQMixer_F32.h | 51 +++++++++++++++++++++++++++--------------- 2 files changed, 72 insertions(+), 32 deletions(-) diff --git a/RadioIQMixer_F32.cpp b/RadioIQMixer_F32.cpp index e6e677d..260572d 100644 --- a/RadioIQMixer_F32.cpp +++ b/RadioIQMixer_F32.cpp @@ -11,9 +11,13 @@ * up or down, and a pair of filters on i and q determine which is allow * to pass to the output. * + * April 2021 - Alternatively, there can be two inputs to 0 (left) and 1 + * (right) feeding the two mixers separately. This covers all transmit + * and receive situations. + * * The sin/cos LO is from synth_sin_cos_f32.cpp See that for details. * - * There are two then two outputs. + * Inputs are either real or I-Q per bool twoChannel. Rev Apr 2021 * * MIT License, Use at your own risk. */ @@ -23,29 +27,42 @@ #include "sinTable512_f32.h" void RadioIQMixer_F32::update(void) { - audio_block_f32_t *blockIn, *blockOut_i=NULL, *blockOut_q=NULL; + audio_block_f32_t *blockIn0, *blockIn1, *blockOut_i=NULL, *blockOut_q=NULL; uint16_t index, i; float32_t a, b, deltaPhase, phaseC; - // Get first input, i, that will be filtered - blockIn = AudioStream_F32::receiveWritable_f32(0); - if (!blockIn) { - if(errorPrintIQM) Serial.println("IQMIXER-ERR: No input memory"); + // Get input block // <data[i] = blockIn->data[i] * (a + 0.001953125*(b-a)*deltaPhase); + blockOut_i->data[i] = blockIn0->data[i] * (a + 0.001953125*(b-a)*deltaPhase); /* Repeat for cosine by adding 90 degrees phase */ index = (index + 128) & 0x01ff; @@ -69,7 +86,10 @@ void RadioIQMixer_F32::update(void) { a = sinTable512_f32[index]; b = sinTable512_f32[index+1]; /* deltaPhase will be the same as used for sin */ - blockOut_q->data[i] = blockIn->data[i]*(a + 0.001953125*(b-a)*deltaPhase); + if(twoChannel) + blockOut_q->data[i] = blockIn1->data[i]*(a + 0.001953125*(b-a)*deltaPhase); + else + blockOut_q->data[i] = blockIn0->data[i]*(a + 0.001953125*(b-a)*deltaPhase); } } else { // Do a more flexible update, i.e., not doSimple @@ -83,7 +103,7 @@ void RadioIQMixer_F32::update(void) { b = sinTable512_f32[index+1]; // We now have a sine value, so multiply with the input data and save // Linear interpolate sine and multiply with the input and amplitude (about 1.0) - blockOut_i->data[i] = amplitude_pk * blockIn->data[i] * (a + 0.001953125*(b-a)*deltaPhase); + blockOut_i->data[i] = amplitude_pk * blockIn0->data[i] * (a + 0.001953125*(b-a)*deltaPhase); /* Shift forward phaseS_C and get cos. First, the calculation of index of the table */ phaseC = phaseS + phaseS_C; @@ -94,10 +114,15 @@ void RadioIQMixer_F32::update(void) { a = sinTable512_f32[index]; b = sinTable512_f32[index+1]; // Same as sin, but leave amplitude of LO at +/- 1.0 - blockOut_q->data[i] = blockIn->data[i] * (a + 0.001953125*(b-a)*deltaPhase); + if(twoChannel) + blockOut_q->data[i] = blockIn1->data[i]*(a + 0.001953125*(b-a)*deltaPhase); + else + blockOut_q->data[i] = blockIn0->data[i]*(a + 0.001953125*(b-a)*deltaPhase); } } - AudioStream_F32::release(blockIn); // Done with this + AudioStream_F32::release(blockIn0); // Done with this + if(twoChannel) + AudioStream_F32::release(blockIn1); //transmit the data AudioStream_F32::transmit(blockOut_i, 0); // send the I outputs AudioStream_F32::release(blockOut_i); diff --git a/RadioIQMixer_F32.h b/RadioIQMixer_F32.h index d92a35d..e0e702a 100644 --- a/RadioIQMixer_F32.h +++ b/RadioIQMixer_F32.h @@ -1,20 +1,25 @@ /* - * RadioIQMixer_F32 + * RadioIQMixer_F32.h + * * 8 April 2020 Bob Larkin * With much credit to: * Chip Audette (OpenAudio) Feb 2017 * and of course, to PJRC for the Teensy and Teensy Audio Library + * + * This quadrature mixer block is suitable for both transmit and receive. * - * A basic building block is a pair of mixers fed in parallel with the + * A basic building block is a pair of mixers with the * LO going to the mixers at the same frequency, but differing in phase * by 90 degrees. This provides two outputs I and Q that are offset in * frequency but also 90 degrees apart in phase. The LO are included * in the block, but there are no post-mixing filters. * * The frequency is set by .frequency(float freq_Hz) - * Particularly for use in transmitting, there is provision for varying + * There is provision for varying * the phase between the sine and cosine oscillators. Technically this is no * longer sin and cos, but that is what real hardware needs. +* +* The amplitudeC(a) allows balancing of I and Q channels. * * The output levels are 0.5 times the input level. * @@ -25,15 +30,18 @@ * Outputs: 0 is I 1 is Q * * Functions, available during operation: - * void frequency(float32_t fr) Sets BFO frequency Hz + * void frequency(float32_t fr) Sets LO frequency Hz * void iqmPhaseS(float32_t ps) Sets Phase of Sine in radians * void phaseS_C_r(float32_t pc) Sets relative phase of Cosine in radians, approximately pi/2 - * void amplitudeC(float32_t a) Sets relative amplitude of Sine, approximately 1.0 + * void amplitudeC(float32_t a) Sets relative amplitude of I output * void useSimple(bool s) Faster if 1, but no phase/amplitude adjustment * void setSampleRate_Hz(float32_t fs_Hz) Allows dynamic sample rate change for this function + * void useTwoChannel(bool 2Ch) Uses 2 input cannels, I & Q, if true. Apr 2021 * * Time: T3.6 For an update of a 128 sample block, doSimple=1, 46 microseconds * T4.0 For an update of a 128 sample block, doSimple=1, 20 microseconds + * + * Rev Apr2021 Allowed for 2-channel I-Q input. Defaults to 1 Channel. "real." */ #ifndef _radioIQMixer_f32_h @@ -48,15 +56,15 @@ class RadioIQMixer_F32 : public AudioStream_F32 { //GUI: shortName: IQMixer public: // Option of AudioSettings_F32 change to block size or sample rate: - RadioIQMixer_F32(void) : AudioStream_F32(1, inputQueueArray_f32) { + RadioIQMixer_F32(void) : AudioStream_F32(2, inputQueueArray_f32) { // Defaults } - RadioIQMixer_F32(const AudioSettings_F32 &settings) : AudioStream_F32(1, inputQueueArray_f32) { + RadioIQMixer_F32(const AudioSettings_F32 &settings) : AudioStream_F32(2, inputQueueArray_f32) { setSampleRate_Hz(settings.sample_rate_Hz); block_size = settings.audio_block_samples; } - void frequency(float32_t fr) { // Frequency in Hz + void frequency(float32_t fr) { // LO Frequency in Hz freq = fr; if (freq < 0.0f) freq = 0.0f; else if (freq > sample_rate_Hz/2.0f) freq = sample_rate_Hz/2.0f; @@ -64,8 +72,9 @@ public: } /* Externally, phase comes in the range (0,2*M_PI) keeping with C math functions - * Internally, the full circle is represented as (0.0, 512.0). This is - * convenient for finding the entry to the sine table. + * For convenience internally, the full circle is represented as (0.0, 512.0). + * This function allows multiple mixers to be phase coordinated (stop + * interrupts when setting). */ void iqmPhaseS(float32_t a) { while (a < 0.0f) a += MF_TWOPI; @@ -78,6 +87,7 @@ public: // phaseS_C_r is the number of radians that the cosine output leads the // sine output. The default is M_PI_2 = pi/2 = 1.57079633 radians, // corresponding to 90.00 degrees cosine leading sine. + // This is used to correct hardware phase unbalance void iqmPhaseS_C(float32_t a) { while (a < 0.0f) a += MF_TWOPI; while (a > MF_TWOPI) a -= MF_TWOPI; @@ -87,14 +97,21 @@ public: return; } - // The amplitude, a, is the peak, as in zero-to-peak. This produces outputs - // ranging from -a to +a. Both outputs are the same amplitude. - void iqmAmplitude(float32_t a) { - amplitude_pk = a; + // Sets the gain g for the I channel. + // The Q channel is always 1.0. This is used to correct hardware + // amplitude unbalance. + void iqmAmplitude(float32_t g) { + amplitude_pk = g; doSimple = false; return; } + // Channel 0 (left) is real for single input, or is I for + // complex input. With twoChannel===true channel 1 is Q. + void useTwoChannel(bool _2Ch) { + twoChannel = _2Ch; + } + // Speed up calculations by setting phaseS_C=90deg, amplitude=1 void useSimple(bool s) { doSimple = s; @@ -119,7 +136,7 @@ public: virtual void update(void); private: - audio_block_f32_t *inputQueueArray_f32[1]; + audio_block_f32_t *inputQueueArray_f32[2]; float32_t freq = 1000.0f; float32_t phaseS = 0.0f; float32_t phaseS_C = 128.00; // 512.00 is 360 degrees @@ -128,10 +145,8 @@ private: float32_t phaseIncrement = 512.00f * freq /sample_rate_Hz; uint16_t block_size = AUDIO_BLOCK_SAMPLES; uint16_t errorPrintIQM = 0; // Normally off - // if only freq() is used, the complexities of phase, phaseS_C, - // and amplitude are not used, speeding up the sin and cos: bool doSimple = true; - + bool twoChannel = false; // Activates 2 channels for I-Q input }; #endif