Audio Library Object for Weaver (Third Method) SSB Modulation/Demodulation

Status
Not open for further replies.

DerekR

Well-known member
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:
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...
 

Attachments

  • WeaverDocument.pdf
    481.8 KB · Views: 1,048
Hi DerekR,

Question: On which Teensy do you run this software? I want to use it for an I/Q modulator on 2400MHz. Are the I and Q signals available on the audio board?
I hope you can help!
Rob
 
I'm not sure I understand. Are you saying you want to produce an SSB signal at 2400 MHz? Is it a voice signal? The document I attached in the post above tells you haw to do this, but of course it cannot be done in the Teensy alone! All the Teensy can do is generate a signal in its audio bandwidth (0-22.05kHz) , and you will need a stable precision quadrature oscillator/up-converter/summer for the final 2400MHz output (which won't be an easy task). As the document shows, you don't need quadrature audio input in this case because your audio/voice input is converted to quadrature in the first mixing stage. You can use the Teensy Audio Board as the microphone input.

Yes, I am actively using the Weaver system on the Teensy - but as a demodulator. When the notification of your post came in, I was in the middle of writing the documentation for my complete SDR in a single Audio library block, which I hope to post in a week or so. It contains the Weaver system for demodulating SSB, but also contains impulsive noise blanking, demodulation (with appropriate pre-filters) for SSB, CW, AM/SAM, automatic notch/peaking filters, AGC, and a wide array of audio filters. It takes a pair of quadrature inputs from the Teensy Audio Board I2S output and processes them to produce the mono audio output. It is in-effect a dual conversion receiver with an intermediate-frequency (IF) centered on approx. 7 kHz. You have to provide quadrature inputs with a band-width of +/- 22.05kHz from an external front-end.

I use the T3.6 exclusively these days, with all the signal chain processing done in 32-bit floating point.
 
Last edited:
Hi DerekR,

Thanks for reply!
Sorry I wasn't very clear in my question.
I'm familiar with the Teensy + audio board because I build the Teensy SDR designed by Frank DD4WH.

This is the case: I try building a voice SSB Tx for 2400 MHz (Es'Hailsat) using the HMC1097 as I/Q modulator. For that chip I need a differential I and Q signal. I have experimented with various applications and after searching the internet I found your post.
At the moment I'm experimenting with the design of G4JNT using a dsPIC, it's working but the latency is not so good for voice SSB.
So I'm looking for a Teensy solution as you described in the first paragraph of your post. Because I am not an experienced programmer I am looking for a ready-made design.

I hope I'm Q5 now... ;-)
Rob PA0RWE
 
Hi Rob,
That's very interesting. I have not tried a SSB modulator (because I gave up my ham license (ZL2ACF) at the age of 17 in 1960), although I've been thinking about about possibly taking the exam here in the USA just so I could experiment. Also I was not aware of the HMC1097 even though I try to keep in touch with Analog Devices products.

I would be interested in putting together a couple of Teensy based audio IQ generators for you. I have all the basic components already written and they just need to be glued together as a single Audio Library object. Should only take an hour or so...

Here's my proposal: the block will accept mic input through the SGTL5000 codec on the Teensy Audio Board, and return the quadrature audio through the L/R line-out lines on the Audio Board. As it is a single block, the latency should be 2.9 msecs - is that acceptable?

To start with I suggest we create a Hilbert transform based (phase-shifter) quadrature generator because it will produce true quadrature audio output more easily. I have the FIR filters already designed. (The Weaver method leaves the audio folded about dc after its LPF)

I am presuming you have a T3.6 available with an Audio Board? I will also assume you have installed an update arm_math library (although that's not essential...)

(edit) I'll include 8th-order elliptic IIR band-pass (300 - 3000Hz) filters on the input and the two outputs as well.
 
Last edited:
Hi Derek (is that your real name.?..),

OK you where a ham as well, great! I have got my license in 1975....

The HMC1097 is originally from Hittite (before AD takes over Hittite). But AD has the same chip, ADL5375.

You don't need to hurry because I first have to buy the Teensy and Audio board. But it seems fun and interesting to me to experiment with your software.
I'm using the Arduino IDE for development, I hope this is OK.

The HMC1097 requires differential I and Q signals and these inputs needs a bias of max 0.5V. That might be a problem to implement.
Latency is no problem. I listened with SDR# and I noticed the latency there, it was not coming from my SSB setup HI!

When I order the Teensy today, maybe on Friday I can start.

73's
Rob
 
The Hilbert version is all done and working! I'll be posting it either later this evening or tomorrow ;)
The block execution time is 390 usecs with the 3 IIR filters active, and 190 usecs if they are disabled (ie just the Hilbert transformer active).

Now I should warn you that the Teensy 3.6 has a serious bug on I2S input through the audio board where at random on program upload and sometimes on power-up the two channels will be out of sync by one sample. This causes havoc with any quadrature processing. I don't know if the same problem exists on I2S output. I'll take a look...
 
Files are uploaded...

Hi Rob,
I have uploaded the first-pass at the IQ generator for you. The zip folder contains five files:
1) - The test sketch AudioIQgenerator.ino
2) - The Audio object: AudioIQgenerator.h and AudioIQgenerator.cpp
3) - A simple but useful Audio object AudioSerialPlotter.h and AudioSerialPlotter.cpp

The AudioSerialPlotter allows you to plot your data from within the block, so you don't need a scope to see what is happening. Simply open the SerialPlotter from the Tools tab.

The quadrature generator uses a length 257 Hilbert transformer designed in MATLAB. The actual FIR filtering is based on my own code that recognizes that the coefficients of the Hilbert filter are odd symmetric about the midpoint, and that the odd coefficients are zero, which reduces the number of multiplies by a factor of 4. Hilbert filters are tricky beasts. In general they fall apart at low frequencies. I've included 8th order elliptic band-pass filters to chop the low frequencies (below 300Hz) before and after the Hilbert filter.

I think this should get you started, we can talk about tweaks later.

Re the differential outputs and bias voltage I think the best thing would be a little op-amp circuit, and do both things at once.

Derek
 

Attachments

  • AudioIQgenerator.zip
    7 KB · Views: 187
The files are uploaded to the forum in a zip file. At the bottom of the post there is a grey box. if you click on it, the zip should be automatically downloaded to your computer. On my computer (Windows 10) it ends up in my "downloads" folder.You will then have to extract the 5 files.
The demo just uses a sine wave as the input so that you can observe the phase shift at different frequencies. I assume you can handle setting up the audio board for mic input...

Let me know if it does not work because you don't have the updated version of arm_math installed. I can rewrite it for the version that ships with Teensyduino.

I'm also looking at ways of improving the performance at low frequencies.
 
That's great! I've been trying to think how to generate an accurate phase difference measurement. One idea I'm thinking about is to take the FFT of the two signals, compute the phase of each line in the spectrum, and take the difference... I don't know how accurate that would be.

Incidentally, I've ordered a dual core ESP32 board (adafruit Feather Huzzah ESP32) with wifi and Bluetooth, which should arrive tomorrow. My goal is to learn freeRTOS, and multicore programming, BUT I read in the tech manual that the 2400Mhz transmitter can be internally modulated by quadrature inputs. That's all I know, I've no idea how to do it, but I immediately thought of your project.
 
Status
Not open for further replies.
Back
Top