Wednesday, May 8, 2013

Direct SSB generation by frequency modulating a PLL

The following code can generate SSB modulation just by controlling a PLL carrier. I have applied this method on the RapsberryPi PLL, and made several contacts on 40m and 20m band with my RaspberryPi.

I will explain the principle in more detail, later. The original idea is depicted here:

The original idea (as seen above) was to implement a complete SSB modulator and demodulator with software in a ATTINY45 micro-controller chip and a AD9834 as DDS. The software samples microphone signal with ADC and then derives the amplitude and phase of the audio signal. The amplitude and phase extraction is derived  from the in-phase microphone signal and its 90 degree phase shifted quadrature signal (created by a Hilbert transform on the in-phase signal). The resulting phase is replicated on the oscillator signal of the DDS, and the amplitude information is used to change the drive-strength (current) of the DDS. The output of the DDS is representing a clean single side-band output SSB near the configured frequency.

To proof this idea is working, I implemented the algorithm on a RaspberryPi. In the RaspberryPi setup there is no DDS, but there is a digital-PLL on the micro-processor SoC which we can use for RF generation. By manipulating the Integer and Fractional dividers, a frequency can be generated from 0 to 250 MHz on GPIO4 pin with a resolution of about 20 Hz at 7 MHz. By alternating the Fractional divider between two nearby values,  the resolution can be further increased to micro-Hz level; for this a 32 bit clock at 32 MHz is used to generate a PWM alike switching pattern between the two Fractional divider values.

The PLL oscillator can be phase modulated by short manipulations of the configured frequency. Increasing the frequency temporarily and then restoring to its original frequency, will shift the phase upwards, while decreasing the frequency temporarily will decrease the phase of the signal.
In this way the phase information for generating a SSB signal can be applied to the RaspberryPi PLL by means of frequency modulation. To do so, first phase differences are calculated for every sample. For each phase deviation (difference) the corresponding frequency change is calculated. This is dependent on the rate that the PLL is changed from frequency; the exact formula is: dev_freq_hz = dev_phase_rad * samp_rate_hz / (2 * pi)

The amplitude signal can be applied by changing the drive-strength of the GPIO4 port, which has 8 levels with a dynamic range of about 13 dB. After some experimenting, amplitude information can be completely rejected, instead the frequency must be placed outside the single side band (e.g. placed to center freq). In this case single side band is generated wihout suppressed carrier, i.e. a constant amplitude envelope is applied (good to prevent RF interference). In this case the audio quality does not seem to suffer from this constant amplitude, and the audio quality becomes even better by applying a little-bit of noise to the microphone input. My impression is that using SSB with constant amplitude increases the readability when the signal is just above the noise.

The RaspberryPi receives the Microphone input via an external USB sound device. To improve the SSB quality, the signal is companded by a A-law compression technique. Three parallel BS170 MOSFETs where directly driven by RaspberryPi GPIO4 output to create about 1Watt of RF. On 40m I could made several SSB contacts through Europe using this setup, receiving stations back by using a nearby online WebSDR receiver. From my location in south-Netherlands following contacts where made:

Apr 27 14:00 40m on4azw/p
Apr 27 14:00 40m pd6king
Apr 27 14:00 40m pa150ba
Apr 27 14:00 40m hb9ag
Apr 27 14:00 40m pd2edr
Apr 28 13:22 40m m0zag
Apr 28 13:22 40m g100rsgb
Apr 28 13:22 40m hb9efx
Apr 28 13:22 40m pa3zax
Apr 28 13:22 40m tm02ref
Apr 28 16:00 20m ea2dt
Apr 29 19:00 40m g3mlo
Apr 30 10:23 7100 pc1king
Apr 30 16:42 7082 pa2cvd
Apr 30 17:00 7092 dl9fcs Gazi, Frankfurt 57
Apr 30 18:30 7074 pa3a Arie, Barendrecht
May  4 11:52 7107 g100c
May  9 15:20 7077 dl/pc1mk Menno, Borkum


Below a fragment of the code that generates SSB signal in the form of amplitude and frequency deviation information, based on the inputed audio signal:

#define ln(x) (log(x)/log(2.718281828459045235f))

static int xv[45];

void filter(int val, int* i, int* q)
{
    int j;

    for (j = 0; j < 44; j++) {
        xv[j] = xv[j+1];
    }
    xv[44] = val;

    *i = xv[22];

    int _q = (xv[1] + xv[3] + xv[5] + xv[7]+xv[7] + 4*xv[9] + 6*xv[11] \
        + 9*xv[13] + 14*xv[15] + 23*xv[17] + 41*xv[19] + 127*xv[21] \
        - (127*xv[23] + 41*xv[25] + 23*xv[27] + 14*xv[29] + 9*xv[31] \
        + 6*xv[33] + 4*xv[35] + xv[37]+xv[37] + xv[39] + xv[41] + xv[43]) ) / 202;

  *q = _q;
}

int arctan2(int y, int x)
{
   int abs_y = abs(y);
   int angle;
   if(x >= 0){
      angle = 45 - 45 * (x - abs_y) / ((x + abs_y)==0?1:(x + abs_y));
   } else {
      angle = 135 - 45 * (x + abs_y) / ((abs_y - x)==0?1:(abs_y - x));
   }
   return (y < 0) ? -angle : angle; // negate if in quad III or IV
}

static float t = 0;
static float prev_f = 0;
static float prev_phase = 0;
static float acc = 0;

void ssb(float in, float fsamp, float* amp, float* df)
{
   int i, q;
   float phase;

   t++;
   filter(in * 128, &i, &q);
   *amp = sqrt( i*i + q*q) / 128.0f; 
   if(*amp > 1.0){
     printf("amp overflow %f\n", *amp);
     *amp = 1.0;
   } 
   phase = M_PI + ((float)arctan2(q,i)) * M_PI/180.0f;
   float dp = phase - prev_phase;
   if(dp < 0) dp = dp + 2*M_PI;
   prev_phase = phase;

   *df = dp*fsamp/(2.0f*M_PI);
}

void main(){
    int samplerate = 8000; // set sample-rate of microphone input here, use 4800 to 8000
    int notvi = 0;   // set no TVI to true if you want constant carrier


    for(;;) {    
        float df, amp;

        int data = //16 bit microphone input here
        ssb((float)data/32767.0, samplerate, &amp, &df);

        float A = 87.7f; // compression parameter
        amp = (fabs(amp) < 1.0f/A) ? A*fabs(amp)/(1.0f+ln(A)) : (1.0f+ln(A*fabs(amp)))/(1.0f+ln(A)); //compand

        int ampval = (int)(round(amp * 8.0f)) - 1;
        int enaval = (ampval < 0) ? 0 : 1;

        if(notvi && ampval < 0){
          enaval = 1; // tx always on
          df = 0;
        }
        if(notvi) ampval = 7; // optional: no amplitude changes

        if(ampval>7) ampval=7;
        if(ampval<0 ampval="0;<!--0--">

       // here, send ampval to 3 bit drive-strength register (if available)
       // here, add or substract df from PLL center frequency (in Hz), adding will make USB, substract. LSB
   }
}



21 comments:

Virgil said...

Interesting!
Please keep on posting on this subject

Roger G3XBM said...

I don't understand a word of it, but I am impressed with what you are doing. Good luck with this work.

73s
Roger G3XBM

Pedro Colla said...

Fascinating work!! The logic seems to be the same used by standard SSB compressors where the voice is handled like a FM signal of medium frequencies AM modulated with a small index with a much lower frequency. Compressors just flattens the AM modulation increasing the average power. The quality of the voice can not be worse than the one of a compressed HF SSB signal. I see no reason why the same principle can not be used to implement a receiver, basically using the same signal (non modulated) as the LO of another RF source and mixing "in software". Very interested to reproduce your system in my bench, any way to access the entire setup? TU Pedro LU7HZ

RNickels said...

More brilliant work, Guido! I have given this some thought after discoveriing Wsprry Pi, but my time has been occupied by other projects.

Will you be adding this to your github soon? I'd love to give it a try. Very nice work!

73, Bob W9RAN

Anonymous said...

SSB generation with constant amplitude sounds particularly nice, as you can make VERY efficient PA stages which do not need to be linear.. just what you need for QRP/SOTA I too would be VERY interested if you have working code posted :-) Hugh G6AIG

Anonymous said...

Please don't publish the complete source code...

this game is too good.

73, lott

Anonymous said...

Please add a mfsk/olivia modem - making the raspberry the perfect data beacon :-)

Trigger said...

Amazing stuff. Would it be possible therefore to create a datamodes rig that handles all the "audio" within the code? I'm really hankering after a SOTA/P psk or datamodes rig. ATB, Tryg EI7CLB

Anonymous said...

will you post the full code?

xe2nbw said...

can you post the code to try?

Anonymous said...

I think there is such a silence because this technique does not work.. sorry. Had a go at coding it myself, then realized you cannot get fundamentally AM sidebands by just FMing a constant carrier, no matter how cleverly.. anyone else had a go?
Hugh G6AIG

ingo0866 said...

Hello, I think that should be a joke. The code snippet I do not understand why only one cutout? No matter, as there are other projects in which one progresses. the small pc can be used as output device. keyword lima sdr from DARC. Note the effort which is operated to build a receiver and a transmitter. There's no PC case with. Just the filter board is a science in itself. Never managed that no one PI.

73, Ingo -DL7UBB-

Anonymous said...

some interesting conversation here: http://forums.qrz.com/archive/index.php/t-405552.html

Anonymous said...

"some interesting conversation here"
but almost all completely rubbish.
I and Q stand for In-phase and Quadrature phase of already AM'ed signals.. not some kind of 'intellegence'. Please read up about Modulation Index, Bessel functions and FM sidebands.. you will quickly realize that AM and FM are worlds apart. If you try the constant-carrier scheme for SSB, you will get wide FM sidebands whatever you do! (just right to put into a 1.5kW amp on 40m) ! Hugh G6AIG

Anonymous said...

It certainly has some potential, there is a strong link between PM and FM, via instantaneous frequency. Frequency is the derivative of phase: http://www.ni.com/white-paper/3361/en/. Have yet to try the code though! What sort of filter have you implemented?

Anonymous said...

I'm very interested to find this code fragment and will certainly go away and have a go at making it work.


I have a contribution of my own, though a much simpler situation. An SSTV proof-of-concept for the Pi. In this case since SSTV is already a sequence of single tones I am just adding (or subtracting for LSB) each successive tone to the carrier frequency. It's SSB, but a very specific instance of it and not half as clever as the code here.

https://github.com/JennyList/LanguageSpy/tree/master/RaspberryPi/rf/sstv

73 de G7CKF

Tarmo said...

Thanks Guido! I have been coding SSB-transmitter with Teensy and AD9850 board, and code you published was very helpful.
Also I tried your original idea with Teensy/AD9850, phase modulation with frequency altering, yes, it works.
AD9850 have also "native phase modulation" so I use it now after all.

http://kissactiongroup.wikispaces.com/SSB_FROM_AD9850

Tarmo, OH6ECF

Anonymous said...

Isn't this SSB generation method similar to the one known as Envelope Removal and Restoration (ERR) ?

It seems to me.

Anonymous said...

The method described here not necessarily applies the restoration part of ERR resulting in a constant amplitude SSB signal. Another difference is that the phase information is applied to the carrier by mean of frequency modulation; this is done via small frequency changes. This is done in such way that only one sideband is generated (in contrast to normal FM where there are lobes on both sides of the carrier). Because the there are no frequency changes for intervals when there is no modulation, a carrier becomes visible at the tuned frequency; temporarily disabling the carrier then will suppress the carrier.
Another interesting discussion about constant amplitude modulation here: http://forums.qrz.com/index.php?threads/constant-amplitude-ssb-might-become-standard.405552/

Anonymous said...

Some further interesting read: https://ham.stackexchange.com/questions/6787/how-does-rpitx-generate-arbitrary-ssb-data-with-a-clock-peripheral

Anonymous said...

Klaus PY2KLA, implemented this approach in a Windows program, see here:

https://groups.io/g/QRP-BR/attachment/94878/0/Gerando%20SSB%20direto.pdf

https://groups.io/g/QRP-BR/topic/gerando_ssb_digitalmente/29628623