Where do we begin?


In the simple Goertzel, we process 12 samples, and then compare the magnitudes to make a bit decision. Lets code that and see how it looks

ISR(ADC_vect) {
  char block_index;
  int sample;

  sample = ADC - 512;
  LowTone_Filter( sample );
  HighTone_Filter( sample );
  block_index++;
  if(block_index > 11) block_index = 0;
  if(block_index == 0){
    char bit = HighTone_Magnitude() > LowTone_Magnitude();
    QL1 = QL2 = QH1 = QH2 = 0;

    ...
    ...

  }
}

To be most effective, the Goertzel block must begin at the same moment as a transmitted bit period. The Goertzel can tolerate some mis-alignment, but ideally our block_index will be at zero when the transmitted 'bit cell' beings.

If we had some means of detecting bit transitions, we could simply set block index to zero at the moment of transition and... voila!

So how can we detect transitions? Well, the code above already has most of the components we need... it is a bit detector, after all. But it's 'coarse', it only resolves down to 1.92 millisecond. We just need a bit detector with better resolution.


The Goertzel filter can be run in two 'modes'. The traditional mode is to accumulate a block of samples, and then check the magnitude, just as we've shown in the above source code.

There is another mode, which we shall carefully name 'continous' mode to distinguish it from the 'sliding' mode.

Though the literature often confuses the two, the Sliding form is a different animal with a different purpose in life.

In the block mode, once we run a sample through the filters we are done with it. In continous mode...

In our particular application, this increases our bit detectors resolution by 12x...

ISR(ADC_vect) {

  /*
      Clear the Input Compare Flag
  */
  TIFR1 |= _BV(ICF1);


  /*
      Grab and store current sample
  */
  static unsigned char sample_index = 0;
  static int samples[12];

  samples[sample_index++] = ADC - 512;
  if (sample_index == 12) sample_index = 0;


  /*
      Process all 12 samples through the band-pass filters
  */
  unsigned char i, n;

  QL1 = QL2 = QH1 = QH2 = 0;
  n = sample_index;

  for (i = 0; i < 12; i++) {
    LowTone_Filter( samples[n]);
    HighTone_Filter( samples[n]);
    n++;
    if (n == 12 ) n = 0;
  }


  /*
        Compare filter magnitudes
  */
  char bit = HighTone_Magnitude() > LowTone_Magnitude();


  /*
      Transition Detection
  */
  static char last_bit;
  static char transition;
  static unsigned char block_index;

  if (bit != last_bit) transition = block_index;
  last_bit = bit;

    ...
    ...

  }
}
Having detected a transition, we now can set block_index to zero, right? Uhhmm... no. Not a good idea.

Unfortunately, jitter and noise can cause 'false transitions'. If we are too aggressive about it, we may wind up constantly resetting block_index, and never reach 11, never shifting a bit in and never assembling a character...

What we will do, instead, is give block_index a little 'nudge' in the right direction. Given enough nudges, our Goertzel block will eventually line up with transmitted bit periods. The SAME preamble is the ideal place to 'lock on' to the transmitted bit stream, with over 90 rapid bit transitions. The body of the message is not so ideal for this purpose, as it will contain runs of several bits without any transition.

So, we will set things up to synchronize during the preamble, and if we see the 'ZCZC' Start of Message we will consider ourselves locked in, preventing further adjustment...
  /*
    12 samples make a bit ---------------------------------------------------------------
   */
  if (block_index == 11) {

    /*
        Bit Sync
    */
    static char sync_flag;

    if (sync_flag == 0) {
      if (transition > 7) {
        block_index++;
      }
      else {
        if (transition > 2) block_index--;
      }
    }

    transition = 0;

Note that we do not make an adjustment if the transition comes at index 0, 1, or 2. The Goertzel filter, in this application, is 'good enough' with 5 or 6 samples. By setting up this 'do not care' zone, we prevent needless adjustments that may be more harmful than helpful. Natural 'jitter' will tend to keep transitions occurring at index 1.

With our demodulator synchronized to the transmitted bit stream, it's now time to synchronize with the byte stream, and finish up by exporting any SAME data we find.