Specific Area Message Encoding


or SAME, uses Audio Frequency Shift Keying to encode data for transmission over the audio channels of commercial broadcast radio and television, and on the National Weather Service radio system.

There are already some examples of using 8 bit AVR microcontrollers in this application. These examples require the assistance of external active circuitry.

This article describes a 'bare bones' method of demodulating SAME transmissions, using a the audio output of a consumer receiver, an avr, and a few passive parts that are probably in your junkbox. Though it will be targeted to the 'ubiquitous' 16 Mhz atmega328, it should be easily adaptable to any crystal clocked avr with ADC and enough memory. 'Enough' memory may be less than you think.. the example provided here uses less than 1500 bytes of FLASH and less than 100 bytes of RAM.

You can obtain the specifications for SAME here (256 Kb PDF), and you should take the time now to read and understand it, but the salient points are
An alert consists of an informational 'sentence', repeated three times with 1 second of silence between. The informational sentences are usually followed by the warning tone and a voice message. Alerts are terminated with three 'End Of Message' sentences, again separated by 1 second of silence.

Each sentence begins with a 16 byte 'preamble'. The preamble byte is binary 10101011 (AB in hex), a value good for promoting synchronization.

The preamble is followed by the 'header', up to 268 bytes long. These bytes represent 7 bit ASCII characters, thus the high bit is always clear. Characters are transmitted low bit first.

The first 4 characters of a 'header' signify the 'type' of sentence. Informational sentences begin with 'ZCZC', followed by several fields of information , which are separated by the characters '-' or '+'. The other type of sentence is the 'End of Message', which is simply the 4 characters 'NNNN'.

Let's get Physical
To get started lets first discuss the interface between receiver and avr. We will assume that your receiver has a 'line out' available (the kind with 0.9 volt peak to peak output). Lacking that, a headphone out will work, though you may have to fiddle with the volume. If you are comfortable opening it up, you can even tap off the speaker leads.

The salient part to consider is a DC blocking capacitor, often called 'coupling' capacitor. The blocking cap will pass audio from the receiver to an ADC input. This needs to exhibit a small reactance at our frequencies of interest, compared to the receivers output impedance. Given that the typical line out impedance ranges from 100 to 600 ohms, something in the range of 1 uF or larger will work nicely (Xc = ~53 ohms). This cap must be a non-polarized type, electrolytics won't work well.

Our attention next turns to the low-pass, 'anti-aliasing' filter. While it may seem a bit controversial, we have found that in this application you probably do not need one. The SAME transmission is quite 'clean', phase-continous, and strongly modulated. During the transmission, there simply aren't many spectral components present, other than the tones we are interested in.

Line output peak to peak voltage is only 0.9 volts, and the typical SAME modulation is 80%, so we will normally see peak to peak voltages about 0.72 volts. This does not match well with a 5 volt ADC reference, so we will use the internal 1.1 volt band-gap reference.

The ADC input must be biased to 1/2 the reference voltage. This can be accomplished by connecting a 100K ohm resister from the Aref pin to the ADC input, and another 100K ohm resistor from the input to ground. It doesn't hurt to match the resistors, but the scheme we will use is not very sensitive to DC offsets.

To complete the interface, a filter cap should be connected from Aref to ground. The value is not very critical, 10 to 100 nF ceramic or film will work well.

For reference, our own interfaces used during development were obtained from our own junkbox. A 0.39 uF mylar film blocking cap, a pair of 100K 5% resistors for bias, and a 100 nF ceramic cap for Vref bypass. We assembled them on pieces of perf board, with 6 inch longs leads of 22 gauge stranded wire. Two leads were were used to connect to the audio source. Three leads were stripped back, tinned, and used to plug in to the headers of some old Arduino Duemilanoves of the atmega328P persuasion.

Before we go any further, now is the time to locate your copy of the atmega328p data sheet, and get the ready to compile C file to refer to as we go along.

We will now flesh out the overall program structure.




Of course, the devil is in the details. Before we begin to write code, we need to discuss those bandpass filters. We will utilize a special, stripped down form of DFT known as the Goertzel Algorithm. Where the DFT computes the spectral energy for a large number of 'bins' spanning a range of frequencies, the Goertzel computes the energy of one specific bin. As a result it is much more efficient than the DFT, for applications concerned with only a few bins.

The basic operation of such a filter is that a certain number of samples are passed through the filter function, which accumulates 'spectral energy' corresponding to the frequency of interest. After that certain number of samples, we check the magnitude of the accumulated energy to see whether the frequency was present or not.

There are a variety of ways to implement a Goertzel filter. The normal Goertzel derives both real and imaginary results, providing phase and frequency information. We are interested here only in magnitude, so we use a form of Goertzel which is optimized for magnitude.

The steps to derive such a filter for any particular frequency are as follows...
Choose a sampling rate
We know from sampling theory that the sampling rate must be at least twice the highest frequency component contained in the signal. With the Goertzel, it is very beneficial to choose a sampling rate which is an integer multiple of the frequency of interest, if possible. Since we are dealing with two frequencies, our sampling rate will ideally be an integer multiple of both. The least common multiple is 6250 Hz, which is 4 times the logic zero frequency, and three times the logic one frequency.

Choose a block size
As mentioned, we are using the Goertzel as a band-pass filter. Given that the SAME signal is pretty clean, and that we are not too sure about F_CPU, we want our bandwidth to as large as it can be, without letting the two filters pass-bands over-lap. This is accomplished by choosing a bandwidth equal to the difference between the two frequencies, which is 520.83 Hz.

The block size of a Goertzel filter is related to the sampling rate and the bandwidth...
 block_size = sample_rate / bandwidth

That gives us a Goertzel block size of 12.

Compute a coefficient
A DFT must compute a number of transcendental operations many, many times. One of the advantages of the Goertzel filter is that we can arrange to compute a coefficient once, before applying the filter algorithm. The coefficient is computed as follows...

 k = size * frequency / rate
 w = 2 * pi / size * k
 coefficient = 2 * cos(w)

We get 0 as the logic zero coefficient, and -1 as the logic one coefficient. These are integer values, which is very good, as we can dispense with floating point math in the filter loop. These also means we can simplify the filters, by removing some operations and turning others into simple negation.

Construct the filter function
Now that we have the coefficients, we can begin to flesh out our filter functions. The basic form of the filter function is...

 Q0 = coeff * Q1 - Q2 + sample
 Q2 = Q1
 Q1 = Q0

Note that Q1 and Q2 act as 'integrators', accumulating past results.

After passing a block of samples through the filter, we check the energy stored with the magnitude function...

 magnitude2 = Q12 + Q22 - Q1 * Q2 * coeff

Based on the magnitude, we can judge how strongly the target frequency was represented in the block of samples. Before beginning the next block, we clear the two accumulators Q1 and Q2. If we did not, they would simply keep on accumulating energy, growing larger without bound (well, at least until they overflowed).

Since our coefficients are zero and -1, we can simplify our filter functions somewhat...

 Low Tone Filter
 Q0 = - QL2 + sample
 QL2 = QL1
 QL1 = Q0

 Low Tone Magnitude
 low_magnitude2 = QL12 + QL22



 High Tone Filter
 Q0 = -QH1 - QH2 + sample
 QH2 = QH1
 QH1 = Q0

 High Tone Magnitude
 high_magnitude2 = QH12 + QH22 + QH1*QH2

So let's translate this into C right now...

/* ======================================================================================
    Goertzel Band-Pass Filter functions
*/
inline  void LowTone_Filter(int) __attribute__((always_inline));
inline  void HighTone_Filter(int) __attribute__((always_inline));
inline long LowTone_Magnitude(void) __attribute__((always_inline));
inline long HighTone_Magnitude(void) __attribute__((always_inline));

int QL1, QL2;
inline void LowTone_Filter(int sample) {
  int Q0;
  Q0 = -QL2 + sample;
  QL2 = QL1;
  QL1 = Q0;
}

int QH1, QH2;
inline void HighTone_Filter(int sample) {
  int Q0;

  Q0 = -QH1  - QH2 + sample;
  QH2 = QH1;
  QH1 = Q0;
}

inline long LowTone_Magnitude(void) {
  return (long)QL1 * (long)QL1 + (long)QL2 * (long)QL2;
}

inline long HighTone_Magnitude(void) {
  return (long)QH1 * (long)QH1 + (long)QH2 * (long)QH2 + (long)QH1 * (long)QH2;
}


Note that we declared these functions 'inline', and told the compiler that we want them to aggressively be inlined. We are writing the code this way to make it easier to grasp, we really do not want the overhead of function calls.

With the Goertzel functions in place, we need next to generate samples at the desired rate of 6250 Hz. Dividing F_CPU by 6250 we get 2560. TIMER/COUNTER 1 is well suited for this. Operating it in CTC mode and setting TOP to 2559 does the trick. An additional benefit... if we use CTC mode 12, the timer/counter can 'automagically' start an ADC conversion in hardware, eliminating a potential source of jitter.

So we will configure TIMER/COUNTER 1 to generate our sampling rate. Also, we need to configure the ADC itself. And, we need some way of 'exporting' SAME data... in this example we will just send it out across the USART serial line.

It would be nice to collect our configuration code all into one function, so let's do that. Let's call it 'setup()' ( now, where did that come from?).

/* ======================================================================================
    SETUP
    Configure modules we do need, power down those we don't.
*/

void setup(void) {

  cli();

  /*
     TIMER1 generates the 6250 Hz sampling rate
     CTC mode 12, no prescaling
  */
  TCCR1A = 0;
  TCCR1B = _BV(WGM12) | _BV(WGM13) | _BV(CS10);
  ICR1 = 2559;


  /*
    ADC
    Internal 1.1 volt AVcc reference, channel 0
    Auto-trigger from TIMER1
  */
  DIDR0 = _BV(ADC0D);
  ADMUX = _BV(REFS1) | _BV(REFS0);
  ADCSRB = _BV(ADTS0) | _BV(ADTS1) | _BV(ADTS2);
  ADCSRA = ADCSRA |= _BV(ADPS2) | _BV(ADPS0) | _BV(ADATE) | _BV(ADEN) | _BV(ADSC);


  /*
      USART exports SAME characters
      9600 N 8 1
  */
#define BAUD 9600
#include 
  UBRR0H = UBRRH_VALUE;
  UBRR0L = UBRRL_VALUE;
#if USE_2X
  UCSRA0 |= (1 << U2X);
#endif
  UCSR0B = _BV(TXEN0);
  UCSR0C = _BV(UCSZ00) | _BV(UCSZ01);


  /*
      MISC
      Disable unused modules
      Set sleep mode to 'idle'
  */
  PRR = _BV(PRTWI) | _BV(PRSPI) | _BV(PRTIM0) | _BV(PRTIM2);
  set_sleep_mode(0);


  /*
      Enable interrupts, and we are ready
  */
  ADCSRA |= _BV(ADIE);
  sei();

} /* end 'Setup'


So at this point, we have ADC samples arriving at the required rate, we have a serial link ready to transmit, we have powered down anything we will not be needing, and we have filter functions ready for samples. Now we need to think about bit synchronization.