As we have been discussing in another thread, I have been working on Weaver modulation for SDR (Software Defined Radio) using the so-called Weaver Method. I am happy to release the first of what will be a set of Audio Library based objects that can be incorporated into Teensy based SDR projects. The full code can be downloaded from github here (together with a copy of my tutorial on the Weaver method), but for illustration purposes I include it here:
The AudioWeaverModem object can act either as a SSB modulator or demodulator, simply by reversing the order of two oscillators. In this example we do both: we take an audio stream and create a SSB signal (upper or lower sideband) at a frequency within the Nyquist rate (22.05 kHz), then demodulate it back to audio again. See my tutorial for a description of how it works.
The details of this implementation are:
Filter - FIR, F_c = 1500 Hz, Parks-McClellan, length 70, designed using Iowa Hills FIR package. You can simply change this if you wish.
Filtering is done using the arm_math function arm_fir_fast_q15().
I have been streaming music and speech continuously for the past few days, and I'm surprised at the quality. It is, of course, limited to about 3kHz bandwidth by the filter - but that's the whole idea !
FIR filters are slow. The audio block processing time on a Teensy 3.2 is just on 500 usecs, and most is taken up by the two length 70 filters.
I also have an fully-operational 16-bit IIR based system with 8th order bi-quad Chebyshev II filters. It runs considerably faster, but my implementation using arm_math is suffering from the classic IIR limit cycle oscillation, which causes a -48dB 1500Hz tone in the output. It's very faint but also annoying. I'm looking at 32-bits for when I get my Teensy 3.6 up and running.
I also have the quadrature input demodulator version AudioWeaverDemodQSD for QSD (Quadrature Sampling Detector) SDR receivers. I'll be loading it on github probably later today, BUT the situation is not quite as rosy as I paint in the tutorial. It is very sensitive to dc offsets and gain imbalances on the inputs. I'd be delighted to have somebody try it, but let's discuss it...
Code:
//-------------------------------------------------------------------------------------------
// Test sketch for the "AudioWeaverModem" Teensy Audio object for Weaver methhod SSB
// modulation/demodulation.
//
// Uses two instances of AudioWeaverModem objects:
// 1) the first configured as a modulator to take an audio signal and convert to an SSB signal
// 2) the second configured as a demodulator to convert the SSB back to audio.
// Plays audio through the I2S input and outputs on the Teensy Audio shield
//
// Author: Derek Rowell
// Date: 2/27/2017
//
//-------------------------------------------------------------------------------------------
#include <Audio.h>
#include "AudioWeaverModem.h"
#include <Wire.h>
#include <SPI.h>
#include <SerialFlash.h>
#define LPF_cutoff 1500.0
//------------------------------------------------------
AudioInputI2S AudioInput;
AudioMixer4 InGain;
AudioWeaverModem Demodulator;
AudioWeaverModem Modulator;
AudioMixer4 OutGain;
AudioOutputI2S Output;
//-------------------------------------------------------
AudioConnection c1(AudioInput,0, InGain,0);
AudioConnection c2(InGain,0, Modulator,0);
AudioConnection c3(Modulator,0, Demodulator,0);
AudioConnection c4(Demodulator,0, OutGain,0);
AudioConnection c5(OutGain,0, Output,0);
AudioControlSGTL5000 audioShield;
//-------------------------------------------------------
void setup() {
double TuningFrequency; // Weaver tuning frequency
double SSBFrequency;
//---------------- Enter parameters here:
SSBFrequency = 12000.; // SSB signal frequency
boolean USB = true; // Select upper or lower sideband
//----------------
if (USB){
TuningFrequency = SSBFrequency + LPF_cutoff; // Set up for USB
Modulator.USB();
Demodulator.USB();
} else {
TuningFrequency = SSBFrequency - LPF_cutoff; // Set up for LSB
Modulator.LSB();
Demodulator.LSB();
}
audioShield.enable();
AudioMemory(20);
audioShield.volume(0.7);
InGain.gain(0., 2.0); // Input gain (adjust to suit audio source)
OutGain.gain(0., 5.0); // Input gain (adjust to suit audio source)
Modulator.Oscillator1_freq(LPF_cutoff); // Weaver modulator oscillator frequencies
Modulator.Oscillator2_freq(TuningFrequency);
Demodulator.Oscillator1_freq(TuningFrequency); // Weaver demodulator oscillator frequencies
Demodulator.Oscillator2_freq(LPF_cutoff);
}
//----------------------------------------------------------------------------
void loop() {
}
Code:
//---------------------------------------------------------------------------------
// File: AudioWeaverModem.cpp
// A ''Teensy'' Audio Object function for Weaver SSB demodulation
//
// Note: This version uses FIR filtering. An IIR version is being prepared.
//
// Author: Derek Rowell
//
// Date: Feb. 19, 2017
//
// Copyright (c) 2017, Derek Rowell
//
// 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:
// 1) The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// 2) THIS 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
// AUTHOR 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.
//---------------------------------------------------------------------------------
#include "AudioWeaverModem.h"
extern "C" {extern const int16_t AudioWaveformSine[257];}
int16_t Oscillator(uint16_t Phase){
uint16_t index; // Index into sine table
uint16_t delta; // Remainder for interpolation,
int16_t val1, val2;
index = Phase>>8; // index into lookup table
delta = Phase&0xFF; // remainder
val1 = AudioWaveformSine[index];
val2 = AudioWaveformSine[index+1]; // table has 257 entries
return val1 + (((val2 - val1)*delta)>>8); // linear interpolation
}
void AudioWeaverModem::update(void) {
int16_t oscOutI, oscOutQ;
int16_t TempI1[AUDIO_BLOCK_SAMPLES], TempQ1[AUDIO_BLOCK_SAMPLES];
int16_t TempI2[AUDIO_BLOCK_SAMPLES], TempQ2[AUDIO_BLOCK_SAMPLES];
audio_block_t *blockIn, *blockOut, *blockDebug;
//
blockIn = receiveReadOnly(0);
if (!blockIn) return;
blockOut = allocate(); // create new output block
blockDebug = allocate(); // create new output block
if (!blockOut) return;
//
//--- First multiplication stage
for (uint16_t i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
blockDebug->data[i] = blockIn->data[i];
TempI1[i] = (blockIn->data[i]*Oscillator(sinPhase1))>>15;
TempQ1[i] = (blockIn->data[i]*Oscillator(cosPhase1))>>15;
sinPhase1 += phaseInc1;
cosPhase1 += phaseInc1;
}
//--- Low-pass filter both blocks
arm_fir_fast_q15(&filterI, TempI1, TempI2, AUDIO_BLOCK_SAMPLES);
arm_fir_fast_q15(&filterQ, TempQ1, TempQ2, AUDIO_BLOCK_SAMPLES);
//
//-- Second multiplication stage
for (uint16_t i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
// blockDebug->data[i] = TempQ2[i];
oscOutI = Oscillator(sinPhase2);
oscOutQ = Oscillator(cosPhase2);
//
//--- Sum or difference to produce the output.
if (lowSB) blockOut->data[i] = ((TempI2[i]*oscOutI)>>15) - ((TempQ2[i]*oscOutQ)>>15);
else blockOut->data[i] = ((TempI2[i]*oscOutI)>>15) + ((TempQ2[i]*oscOutQ)>>15);
sinPhase2 += phaseInc2;
cosPhase2 += phaseInc2;
}
//--- End of processing...
transmit(blockOut, 0);
transmit(blockDebug, 1);
release(blockDebug);
release(blockOut);
release(blockIn);
return;
}
Code:
//---------------------------------------------------------------------------------
// File: AudioWeaverModem.h
// Helper file for ''Teensy'' Audio Object AudioWeaverModem for Weaver SSB
// modulation/demodulation.
//
// Note: This version uses FIR filtering. An IIR version is being prepared.
//
// Author: Derek Rowell
//
// Date: Feb. 19, 2017
//
// Copyright (c) 2017, Derek Rowell
//
// 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:
// 1) The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// 2) THIS 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
// AUTHOR 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.
//---------------------------------------------------------------------------------
//
#ifndef weaver_modem_h_
#define weaver_modem_h_
#include "Arduino.h"
#include "AudioStream.h"
#include "arm_math.h"
#define FIR_MAX_COEFFS 200
class AudioWeaverModem : public AudioStream {
public:
AudioWeaverModem() : AudioStream(1,inputQueueArray) {
sinPhase1 = 0; // Initial phase for oscillator 1
cosPhase1 = 16384; // Corresponds to phase shift of pi/2 for cosine
sinPhase2 = 0; // Initial phase for oscillator 2
cosPhase2 = 16384; // Corresponds to phase shift of pi/2 for cosine
arm_fir_init_q15(&filterI, n_coeffs, coeffs, FIRstateI, AUDIO_BLOCK_SAMPLES);
arm_fir_init_q15(&filterQ, n_coeffs, coeffs, FIRstateQ, AUDIO_BLOCK_SAMPLES);
}
//--------
virtual void update(void);
//--- Set internal oscillator 1 frequency
void Oscillator1_freq(float oscFreq1) {
if (oscFreq1 < 0.0) oscFreq1 = 0.0;
else if (oscFreq1 > AUDIO_SAMPLE_RATE_EXACT/2) oscFreq1 = AUDIO_SAMPLE_RATE_EXACT/2;
phaseInc1 = uint16_t(oscFreq1*(65536.0/AUDIO_SAMPLE_RATE_EXACT));
}
//--- Set internal oscillator 2 frequency
void Oscillator2_freq(float oscFreq2) {
if (oscFreq2 < 0.0) oscFreq2 = 0.0;
else if (oscFreq2 > AUDIO_SAMPLE_RATE_EXACT/2) oscFreq2 = AUDIO_SAMPLE_RATE_EXACT/2;
phaseInc2 = uint16_t(oscFreq2*(65536.0/AUDIO_SAMPLE_RATE_EXACT));
}
//--- Set to LSB mode
void LSB(void){lowSB = true;}
//--- Set USB mode
void USB(void){lowSB = false;}
//
private:
audio_block_t *inputQueueArray[1];
uint16_t sinPhase1;
uint16_t cosPhase1;
uint16_t phaseInc1;
uint16_t sinPhase2;
uint16_t cosPhase2;
uint16_t phaseInc2;
boolean lowSB = true;
arm_fir_instance_q15 filterI;
arm_fir_instance_q15 filterQ;
q15_t FIRstateI[AUDIO_BLOCK_SAMPLES + FIR_MAX_COEFFS];
q15_t FIRstateQ[AUDIO_BLOCK_SAMPLES + FIR_MAX_COEFFS];
// Lowpass Filter coefficients:
int16_t n_coeffs = 70;
int16_t coeffs[70] = {
67, 43, 52, 59, 61, 58, 47, 27, -2, -40,
-87, -141, -199, -257, -311, -355, -383, -391, -372, -323,
-238, -117, 40, 233, 457, 706, 973, 1249, 1523, 1784,
2022, 2226, 2388, 2500, 2558, 2558, 2500, 2388, 2226, 2022,
1784, 1523, 1249, 973, 706, 457, 233, 40, -117, -238,
-323, -372, -391, -383, -355, -311, -257, -199, -141, -87,
-40, -2, 27, 47, 58, 61, 59, 52, 43, 67
};
};
#endif
The AudioWeaverModem object can act either as a SSB modulator or demodulator, simply by reversing the order of two oscillators. In this example we do both: we take an audio stream and create a SSB signal (upper or lower sideband) at a frequency within the Nyquist rate (22.05 kHz), then demodulate it back to audio again. See my tutorial for a description of how it works.
The details of this implementation are:
Filter - FIR, F_c = 1500 Hz, Parks-McClellan, length 70, designed using Iowa Hills FIR package. You can simply change this if you wish.
Filtering is done using the arm_math function arm_fir_fast_q15().
I have been streaming music and speech continuously for the past few days, and I'm surprised at the quality. It is, of course, limited to about 3kHz bandwidth by the filter - but that's the whole idea !
FIR filters are slow. The audio block processing time on a Teensy 3.2 is just on 500 usecs, and most is taken up by the two length 70 filters.
I also have an fully-operational 16-bit IIR based system with 8th order bi-quad Chebyshev II filters. It runs considerably faster, but my implementation using arm_math is suffering from the classic IIR limit cycle oscillation, which causes a -48dB 1500Hz tone in the output. It's very faint but also annoying. I'm looking at 32-bits for when I get my Teensy 3.6 up and running.
I also have the quadrature input demodulator version AudioWeaverDemodQSD for QSD (Quadrature Sampling Detector) SDR receivers. I'll be loading it on github probably later today, BUT the situation is not quite as rosy as I paint in the tutorial. It is very sensitive to dc offsets and gain imbalances on the inputs. I'd be delighted to have somebody try it, but let's discuss it...