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...
- We keep a FIFO buffer of the last N samples (12, in this case)
- Each new sample added to the FIFO 'pushes' out the oldest sample
- With each new sample, we run the entire FIFO through the bandpass filters
- We then take the magnitudes, with each new sample
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.