/* * 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 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) * 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, unless adjusted by * gainOut(g). * * Status: Tested in doSimple==1 * Tested in FineFreqShift_OA.ino, T3.6 and T4.0 * * Inputs: 0 is signal * Outputs: 0 is I 1 is Q * * Functions, available during operation: * 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 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 * void setGainOut(float32_t gainO) Sets gain after mixers. Often a value of 2.0 makes the * block lossless. For both doSimple and not doSimple. * * Time: T3.6 For an update of a 128 sample block, doSimple=true, 46 microseconds * T4.0 For an update of a 128 sample block, doSimple=true, 20 microseconds * * Rev Apr2021 Allowed for 2-channel I-Q input. Defaults to 1 Channel. "real." * Rev 30Jan23 Corrected setSampleRate_Hz(sr) to do so! RSL * Rev 2 Feb 2023 Added gainOut, with or without doSimple. RSL */ #ifndef _radioIQMixer_f32_h #define _radioIQMixer_f32_h #include "AudioStream_F32.h" #include "arm_math.h" #include "mathDSP_F32.h" class RadioIQMixer_F32 : public AudioStream_F32 { //GUI: inputs:2, outputs:2 //this line used for automatic generation of GUI node //GUI: shortName: IQMixer public: // Option of AudioSettings_F32 change to block size or sample rate: RadioIQMixer_F32(void) : AudioStream_F32(2, inputQueueArray_f32) { // Defaults } 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) { // 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; phaseIncrement = 512.0f * freq / sample_rate_Hz; } /* Externally, phase comes in the range (0,2*M_PI) keeping with C math functions * 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; while (a > MF_TWOPI) a -= MF_TWOPI; phaseS = 512.0f * a / MF_TWOPI; doSimple = false; return; } // 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; // Internally a full circle is 512.00 of phase phaseS_C = 512.0f * a / MF_TWOPI; doSimple = false; return; } // 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; if(doSimple) { phaseS_C = 128.0f; amplitude_pk = 1.0f; } return; } void setGainOut(float32_t _gainO) { // Rev 2023 gainOut = _gainO; } void setSampleRate_Hz(float32_t fs_Hz) { sample_rate_Hz = fs_Hz; // Added 30Jan23 RSL // Check freq range if (freq > sample_rate_Hz/2.0f) freq = sample_rate_Hz/2.f; // update phase increment for new frequency phaseIncrement = 512.0f * freq / sample_rate_Hz; } void showError(uint16_t e) { // Serial.print errors in update() errorPrintIQM = e; } virtual void update(void); private: 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 float32_t amplitude_pk = 1.0f; float32_t sample_rate_Hz = AUDIO_SAMPLE_RATE_EXACT; float32_t phaseIncrement = 512.00f * freq /sample_rate_Hz; float32_t gainOut = 1.0f; uint16_t block_size = AUDIO_BLOCK_SAMPLES; uint16_t errorPrintIQM = 0; // Normally off bool doSimple = true; bool twoChannel = false; // Activates 2 channels for I-Q input }; #endif