Added alternate 2-channel input to IQ mixers

pull/6/merge
boblark 3 years ago
parent bd5f30854b
commit f6e578b82c
  1. 53
      RadioIQMixer_F32.cpp
  2. 51
      RadioIQMixer_F32.h

@ -11,9 +11,13 @@
* up or down, and a pair of filters on i and q determine which is allow * up or down, and a pair of filters on i and q determine which is allow
* to pass to the output. * 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. * 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. * MIT License, Use at your own risk.
*/ */
@ -23,29 +27,42 @@
#include "sinTable512_f32.h" #include "sinTable512_f32.h"
void RadioIQMixer_F32::update(void) { 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; uint16_t index, i;
float32_t a, b, deltaPhase, phaseC; float32_t a, b, deltaPhase, phaseC;
// Get first input, i, that will be filtered // Get input block // <<Writable??
blockIn = AudioStream_F32::receiveWritable_f32(0); blockIn0 = AudioStream_F32::receiveWritable_f32(0);
if (!blockIn) { if (!blockIn0)
if(errorPrintIQM) Serial.println("IQMIXER-ERR: No input memory");
return; return;
if(twoChannel) {
blockIn1 = AudioStream_F32::receiveWritable_f32(1);
if (!blockIn1) {
AudioStream_F32::release(blockIn0);
if(errorPrintIQM) Serial.println("IQMIXER-ERR: No 1 input memory");
return;
}
} }
// Try to get a pair of blocks for the IQ output // Try to get a pair of blocks for the IQ output
blockOut_i = AudioStream_F32::allocate_f32(); blockOut_i = AudioStream_F32::allocate_f32();
if (!blockOut_i){ // Didn't have any if (!blockOut_i){ // Didn't have any
if(errorPrintIQM) Serial.println("IQMIXER-ERR: No I output memory"); if(errorPrintIQM) Serial.println("IQMIXER-ERR: No I output memory");
AudioStream_F32::release(blockIn); AudioStream_F32::release(blockIn0);
if(twoChannel)
AudioStream_F32::release(blockIn1);
return; return;
} }
blockOut_q = AudioStream_F32::allocate_f32(); blockOut_q = AudioStream_F32::allocate_f32();
if (!blockOut_q){ if (!blockOut_q){
if(errorPrintIQM) Serial.println("IQMIXER-ERR: No Q output memory"); if(errorPrintIQM)
AudioStream_F32::release(blockIn); Serial.println("IQMIXER-ERR: No Q output memory");
AudioStream_F32::release(blockIn0);
AudioStream_F32::release(blockOut_i); AudioStream_F32::release(blockOut_i);
if(twoChannel)
AudioStream_F32::release(blockIn1);
return; return;
} }
@ -61,7 +78,7 @@ void RadioIQMixer_F32::update(void) {
a = sinTable512_f32[index]; a = sinTable512_f32[index];
b = sinTable512_f32[index+1]; b = sinTable512_f32[index+1];
// Linear interpolation and multiplying (DBMixer) with input // Linear interpolation and multiplying (DBMixer) with input
blockOut_i->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 */ /* Repeat for cosine by adding 90 degrees phase */
index = (index + 128) & 0x01ff; index = (index + 128) & 0x01ff;
@ -69,7 +86,10 @@ void RadioIQMixer_F32::update(void) {
a = sinTable512_f32[index]; a = sinTable512_f32[index];
b = sinTable512_f32[index+1]; b = sinTable512_f32[index+1];
/* deltaPhase will be the same as used for sin */ /* 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 else { // Do a more flexible update, i.e., not doSimple
@ -83,7 +103,7 @@ void RadioIQMixer_F32::update(void) {
b = sinTable512_f32[index+1]; b = sinTable512_f32[index+1];
// We now have a sine value, so multiply with the input data and save // 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) // 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 */ /* Shift forward phaseS_C and get cos. First, the calculation of index of the table */
phaseC = phaseS + phaseS_C; phaseC = phaseS + phaseS_C;
@ -94,10 +114,15 @@ void RadioIQMixer_F32::update(void) {
a = sinTable512_f32[index]; a = sinTable512_f32[index];
b = sinTable512_f32[index+1]; b = sinTable512_f32[index+1];
// Same as sin, but leave amplitude of LO at +/- 1.0 // 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 //transmit the data
AudioStream_F32::transmit(blockOut_i, 0); // send the I outputs AudioStream_F32::transmit(blockOut_i, 0); // send the I outputs
AudioStream_F32::release(blockOut_i); AudioStream_F32::release(blockOut_i);

@ -1,20 +1,25 @@
/* /*
* RadioIQMixer_F32 * RadioIQMixer_F32.h
*
* 8 April 2020 Bob Larkin * 8 April 2020 Bob Larkin
* With much credit to: * With much credit to:
* Chip Audette (OpenAudio) Feb 2017 * Chip Audette (OpenAudio) Feb 2017
* and of course, to PJRC for the Teensy and Teensy Audio Library * 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 * 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 * 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 * frequency but also 90 degrees apart in phase. The LO are included
* in the block, but there are no post-mixing filters. * in the block, but there are no post-mixing filters.
* *
* The frequency is set by .frequency(float freq_Hz) * 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 * the phase between the sine and cosine oscillators. Technically this is no
* longer sin and cos, but that is what real hardware needs. * 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. * The output levels are 0.5 times the input level.
* *
@ -25,15 +30,18 @@
* Outputs: 0 is I 1 is Q * Outputs: 0 is I 1 is Q
* *
* Functions, available during operation: * 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 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 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 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 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 * 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 * 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 #ifndef _radioIQMixer_f32_h
@ -48,15 +56,15 @@ class RadioIQMixer_F32 : public AudioStream_F32 {
//GUI: shortName: IQMixer //GUI: shortName: IQMixer
public: public:
// Option of AudioSettings_F32 change to block size or sample rate: // 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 // 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); setSampleRate_Hz(settings.sample_rate_Hz);
block_size = settings.audio_block_samples; 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; freq = fr;
if (freq < 0.0f) freq = 0.0f; if (freq < 0.0f) freq = 0.0f;
else if (freq > sample_rate_Hz/2.0f) freq = sample_rate_Hz/2.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 /* 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 * For convenience internally, the full circle is represented as (0.0, 512.0).
* convenient for finding the entry to the sine table. * This function allows multiple mixers to be phase coordinated (stop
* interrupts when setting).
*/ */
void iqmPhaseS(float32_t a) { void iqmPhaseS(float32_t a) {
while (a < 0.0f) a += MF_TWOPI; 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 // 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, // sine output. The default is M_PI_2 = pi/2 = 1.57079633 radians,
// corresponding to 90.00 degrees cosine leading sine. // corresponding to 90.00 degrees cosine leading sine.
// This is used to correct hardware phase unbalance
void iqmPhaseS_C(float32_t a) { void iqmPhaseS_C(float32_t a) {
while (a < 0.0f) a += MF_TWOPI; while (a < 0.0f) a += MF_TWOPI;
while (a > MF_TWOPI) a -= MF_TWOPI; while (a > MF_TWOPI) a -= MF_TWOPI;
@ -87,14 +97,21 @@ public:
return; return;
} }
// The amplitude, a, is the peak, as in zero-to-peak. This produces outputs // Sets the gain g for the I channel.
// ranging from -a to +a. Both outputs are the same amplitude. // The Q channel is always 1.0. This is used to correct hardware
void iqmAmplitude(float32_t a) { // amplitude unbalance.
amplitude_pk = a; void iqmAmplitude(float32_t g) {
amplitude_pk = g;
doSimple = false; doSimple = false;
return; 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 // Speed up calculations by setting phaseS_C=90deg, amplitude=1
void useSimple(bool s) { void useSimple(bool s) {
doSimple = s; doSimple = s;
@ -119,7 +136,7 @@ public:
virtual void update(void); virtual void update(void);
private: private:
audio_block_f32_t *inputQueueArray_f32[1]; audio_block_f32_t *inputQueueArray_f32[2];
float32_t freq = 1000.0f; float32_t freq = 1000.0f;
float32_t phaseS = 0.0f; float32_t phaseS = 0.0f;
float32_t phaseS_C = 128.00; // 512.00 is 360 degrees 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; float32_t phaseIncrement = 512.00f * freq /sample_rate_Hz;
uint16_t block_size = AUDIO_BLOCK_SAMPLES; uint16_t block_size = AUDIO_BLOCK_SAMPLES;
uint16_t errorPrintIQM = 0; // Normally off 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 doSimple = true;
bool twoChannel = false; // Activates 2 channels for I-Q input
}; };
#endif #endif

Loading…
Cancel
Save