Go Down

Topic: Decode NOAA Weather Radio SAME codes (Read 19410 times) previous topic - next topic

starduster73

How hard would it be to create an Arduino-based system that would detect and decode NOAA Weather Radio SAME codes for display or logging? Basically, I would like to have a device that would detect the alerts the National Weather Service broadcasts for severe weather (i.e, Severe Thunderstorm Warning, Winter Storm Warning, Tornado Warning, Required Weekly Test too, perhaps) and decode the information encoded in the "bursts" pushed out before the 1050Hz audible tone (or on TV/radio/commercial media, the familiar "EBS-style" buzz) to display before the voice comes on to read the text of the alert to human listeners. Maybe at the same time I could do some kind of switching for a siren or other mock-up of an audible alarm (even if it's just, i.e. a piezo buzzer). It would work a lot like the commercially available "weatherradio" devices that you can program to listen for warnings and alert you of them. I imagine the wire going to the speaker output of a radio tuned to the local NWS frequency could be tied to the pin of an Arduino (or a similar device) and the header of the alert (i.e., the start of the data) could be "listened for", and once the controller picked up on it, it could read the rest of the data and decode it (the info is supposed to be sent out three times in succession before the 1050hz tone for checking/validation purposes). Thanks in advance for any insight or advice.

kduin

Hey.
I'm from germany, so your weather warning system is not familiar to me.

But if the NOAA provides an RSS-Feed (for example) you could read the feed with an ethernet shield every minute and play the tone if a new feed arrives?

PaulS

http://www.nws.noaa.gov/nwr/same.htm
Quote
Using NWR SAME
After buying an NWR SAME receiver, you must program your county, parish or independent city or marine area into the radio. Do NOT program your radio for louder or clearer station not designated as a SAME channel. You will not receive alert. Your NWR will then alert you only of weather and other emergencies for the county(s)/ area(s) programmed. NWR receivers without the SAME capability alert for emergencies anywhere within the coverage area of the NWR transmitter, typically several counties, even though the emergency could be well away from the listener.

    When an NWS office broadcasts a warning, watch or non-weather emergency, it also broadcasts a digital SAME code that may be heard as a very brief static burst, depending on the characteristics of the receiver. This SAME code contains the type of message, county(s) affected, and message expiration time.
    A programmed NWR SAME receiver will turn on for that message, with the listener hearing the 1050 Hz warning alarm tone as an attention signal, followed by the broadcast message.
    At the end of the broadcast message, listeners will hear a brief digital end-of-message static burst followed by a resumption of the NWR broadcast cycle.


I'm not sure how you intend to use an Arduino with this system. The Arduino is not a radio.
The art of getting good answers lies in asking good questions.

el_supremo

The SAME code is transmitted as an AFSK signal at a rather low baud rate using a somewhat non-standard format (e.g. it is not 8-n-1). To read it you would have to be able to do some digital signal processing of the incoming audio and the Arduino is not really suitable for this. Alternatively, you could try to find a tone-decoding chip to do the dirty work for you (i.e. convert the tones to a bitstream) and then Arduino could be used to decode the bits.

Pete
Don't send me technical questions via Private Message.

starduster73

I would not be using the Arduino as the radio receiver...it would be tied to the output of an appropriate receiver and decode the signal coming from it. el_supremo's suggestion seems to be the best way to go about it, although in theory it should be possible to tap the line to the speaker with such a chip and process the electrical signals directly rather than try to decode the data from audio generated by a speaker, correct? If the local WX office provides an RSS feed, that would be an even better method, although I'm not sure they do, at least in my area. I will look into this further and see what I find. Thanks.

kg4wsv

It is possible to decode directly with the arduino, although it will not be a reliable as methods using a modem or tone decoder IC.  The method typically used is known as zero crossing detection, and you basically look at the audio sine wave coming in and see when it hits 0V.  Google "whereAVR" to see one example of doing packet radio decoding (1200 baud AFSK) with an ATmega8.

One IC to provide tone decoding is the XR2211.  It has been around a while, and there are examples of ham packet decoders using this IC.

I've got a CMX865A that is supposed to do arbitrary tone decoding, but I haven't had time to play with it much - that project keeps getting put on the back burner.

-j

HerrProfessor

I'm also interested in finding out more about decoding the FSK from the SAME stream.  I've got an Arduino Uno to use for this task, but I'm having a hard time getting my head around the code part (measuring the frequency and translating that into a hi or low bit).  Would I need to use an interrupt to detect when the waveform crosses the 0V threshold, then start a timer until the next crossing?  I guess I just need someone to spell it out for me - I tend to overlook the obvious at times.

Once I am able to decode the FSK stream, I know I can handle parsing out the messages since I understand the format quite well.

I recently found an XR2211 in my parts bin and was thinking about using that as opposed to straight audio in to the Uno, but I'd like to see if I can get the audio decoded first since it's become a real challenge.

tsc

kf2qd

Decoding an FSK Signal -

Basically FSK is a serial signal sent using 2 frequencies. What you need is a narrow pass filter to detect either of these frequencies and give you a one when it is detected. you then decode the pattern of ones and zeros to rebuild the bytes and assemble the bytes into your message.

Some purists might insist that you need 2 frequency decodes, but you can make the assumption that when one of the frequencies is not there the other is. SO - assume the zeros are there and look for the ones.

The weather and other EAS alerts consist of a message containing the alert information and codes for the areas for which the alert applies. They can be rather broad or rather specific.

HerrProfessor

Thanks for the reply.  I understand looking for the 1's and assuming all others are 0, but where I'm getting confused is in creating the byte.  Let's say that the char 'A' is coming in from the stream.  It would arrive as 10000010 binary (65 decimal, 01000001 binary) based on the filter doing its job.  If I decode the period correctly (edge to edge) and determine that the frequency equates to a 1, then I can stuff this into the byte that I'm creating.  But if it's a 0, or several 0's in a row (or 1's for that matter), I don't have a transition during those entries so my byte would not be correct.

Would I need to generate a clock at 520.83 Hz in order to synchronize the input?  Or, could I use the two interrupt pins on the Uno to determine the 1's and 0's and stuff the bits into the byte?

I appreciate your response!  I really want to learn how this works because I'm coming up with ideas for several other similar type projects.

tsc

el_supremo

Yes, you're right, you would need to clock the bits in. It wouldn't matter if there were a sequence of two or more zeros or two or more ones, you'd still need to know where each bit starts and/or ends. If the XR2211 (or other) chip can decode the AFSK you could put the digital output on a Rx pin and see if the Arduino can handle the bit rate and the weird format - I don't know if it can handle either.
If you try to handle it yourself, a timer at the bit rate is probably not a good idea because it might "tick" right on the edge between each bit and your program would then not necessarily make the correct decision. One way to handle this is to run the clock at, say, four times the actual bit rate and then for each consecutive group of four bits, the majority value wins. e.g. 1000 would mean a zero, because the first bit probably sampled the end of the previous bit.

Pete
Don't send me technical questions via Private Message.

kf2qd

Start &Stop bits help here. Start Bits Especially. You are receiving zeros, and then you get a one. The one bit is there to synchronize the reciever to the incoming serial bit stream can properly feed the bits into a shift register. So - you would be looking for a 1, when it is recieved and timed out,  you would wait for 1/2 a bit time and then sample 5, 7 or 8, (depends on protocol) bit times and then you should get a zero bit or 2 depending on the protocol as stop bits.

kg4wsv

In addition to start and stop bits, the preamble will help find where the bytes start.

The XR2211 is a tone decoder, not an FSK decoder.  It only detects a single tone, but as others pointed out you can use the presence of a single tone to do the job.  Lots of packet radio gear has done this over the years.

-j

HerrProfessor

Sorry for the long delay - I've had a chance to think about the code and write something up that seems to me would do the job.  I'm waiting on a couple of parts to arrive so I can finish the XR2211 circuit, so I haven't been able to test this out yet.  Here is what I've come up with:

Code: [Select]

/*
  NWR S.A.M.E. Decoder 

  Specifications for this format can be found here:
 
  http://www.nws.noaa.gov/nwr/same.pdf
 
  Created 13 Jan. 2012
  Modified 19 Jan. 2012
*/

//  this is the sample rate (384 us)...
const long sampleRate = 384000000;
const int ledPin = 13;        //  pin 13 used by on board LED...
const int dataIn = 8;         //  pin that accepts input from decoder...
boolean inputState = false;   //  stores the value read at the digital pin...
boolean boolWinner = false;   //  used to store boolean value winner...
long sampleTime = 0;          //  used to store display time for LCD panel...
int sampleCount = 0;          //  used to keep track of samples taken...
int bitCounter = 0;           //  used to keep track of bits entered...
int boolHigh = 0;             //  used to track HI bits...
int boolLow = 0;              //  used to track LO bits...
char byteData;                //  used to stuff each bit until 8 bits reached...
boolean zczcHeader = false;   //  flag indicating header captured...
String zczcCaptured = "";     //  stores characters captured...

void setup()
{
  //  Pin 8 connected to output of XR2211...
  pinMode(dataIn, INPUT);
  //  Pin 13 has an LED connected...
  pinMode(ledPin, OUTPUT);
  //  turn the LED off
  digitalWrite(ledPin, LOW);
  //  set com port baud rate...
  Serial.begin(9600);
}

void loop()
{
  //  1.  set up a sampling rate of 5 times the BAUD rate:
  //      BAUD Rate is 520.83 Hz
  //      x5 = 2604.15 Hz
  //      1 / 2604.15 = 384 uS
  // 
  //  2.  therefore, this loop will read the digital pin
  //      every 384 uS and count each time up to 5, then
  //      the program will do the following:
  //
  //      a.  once 5 samples has been taken, we determine
  //          the majority winner (1 or 0)
  //      b.  take that bit, place it in the char variable
  //          by using shift left (<<)
  //      c.  reset the sample counter to 0
  //      d.  increment the bit counter (++)
  //      e.  when we have 8 bits, reset the counter, then
  //          test for the ZCZC header
  //      f.  if found, set the header found flag and begin
  //          processing out all incoming chars
  //      g.  if not found, continue until ZCZC header has
  //          been found

  if((micros() - sampleTime) > sampleRate)
  {
    //  read the digital input pin...
    inputState = digitalRead(dataIn);
    //  increment the sample counter...
    sampleCount++;
   
    //  add the counts...
    if(inputState)
    {
      //  this is a 1...
      boolHigh++;
    }
    else
    {
      //  this is a 0...
      boolLow++;     
    }
   
    //  only when we have 5 samples!
    if(sampleCount > 4)
    {
      //  must reset sample count...
      sampleCount = 0;
     
      //  find out the winner...
      if(boolHigh > boolLow)
      {
        //  reset the counters...
        boolHigh = 0;
        boolLow = 0;
        //  high value wins...
        boolWinner = true;
        //  enter bit shift left by 1...
        byteData = boolWinner << 1;
        bitCounter++;
      }
      else
      {
        //  reset the counters...
        boolHigh = 0;
        boolLow = 0;
        //  low value wins...
        boolWinner = false;
        //  enter bit shift left by 1...
        byteData = boolWinner << 1;
        bitCounter++;
      }
    }
   
    //  if bitCounter is 8, we have a byte, so
    //  check for proper char expected and
    //  remember to reset the variable...
    if(bitCounter > 7)
    {
      //  reset the varaible...
      bitCounter = 0;
     
      if(!zczcHeader)
      {
        if(byteData == 'Z')
        {
          //  sent to serial port...
          Serial.print(byteData);
          //  add this char to the string...
          zczcCaptured += byteData;
        }
        else if(byteData == 'C')
        {
          //  sent to serial port...
          Serial.print(byteData);
          //  add this char to the string...
          zczcCaptured += byteData;
        }
        else
        {
          //  this is not a header, so clear the string...
          zczcCaptured = "";
        }
       
        //  test to see if we got the header...
        if(zczcCaptured == "ZCZC")
        {
          //  set the flag to indicate header captured...
          zczcHeader = true;
        }
        else
        {
          zczcHeader = false;
        }
      }
      else
      {
        //  we already found the header,
        //  so just send char to the
        //  serial port...
        Serial.print(byteData);
      }
    }

    //  get the time stamp...
    sampleTime = micros();
  }
}


I know this is probably not the best or most efficient, but once I see how this works I can always clean up a bit.

Since the specification states that there are no start or stop bits, I figured that the best way to deal with the data was to sample as el_supremo and others have mentioned.  I know the there is a preamble (16 bytes of 10101011) before the header (ZCZC), but I'm thinking that I might be able to ignore that since ZCZC is unique to the messages.  There is a link to the NOAA SAME Specification in the code.

Does this look like I'm headed in the right direction?

Thanks again for your assistance!
tsc

HerrProfessor

#13
Jan 20, 2012, 08:11 pm Last Edit: Jan 20, 2012, 08:17 pm by HerrProfessor Reason: 1
Ok, I got the parts in yesterday and built the circuit using the XR2211.  Checked the output on the scope with a sample audio feed recorded from an actual warning the other day.  I'm seeing the pulses run across the scope in synch with the FSK data from the sound file.

Made some changes to the source.  Here's the new sketch:

Code: [Select]

/*
 NWR S.A.M.E. Decoder  

 Specifications for this format can be found here:
 
 http://www.nws.noaa.gov/nwr/same.pdf
 
 Created 13 Jan. 2012
 Modified 20 Jan. 2012
*/

const long sampleRate = 384;  //  this is the sample rate (384 us)...
const int ledPin = 13;        //  pin 13 used by on board LED...
const int dataIn = 8;         //  pin that accepts input from decoder...
boolean inputState = false;   //  stores the value read at the digital pin...
boolean boolWinner = false;   //  used to store boolean value winner...
long sampleTime = 0;          //  used to store display time for LCD panel...
int sampleCount = 0;          //  used to keep track of samples taken...
int bitCounter = 0;           //  used to keep track of bits entered...
int boolHigh = 0;             //  used to track HI bits...
int boolLow = 0;              //  used to track LO bits...
unsigned char byteData;       //  used to stuff each bit until 8 bits reached...
boolean zczcHeader = false;   //  flag indicating header captured...
String zczcCaptured = "";     //  stores characters captured...
const char charOne = '1';
const char charZero = '0';

void setup()
{
 //  Pin 8 connected to output of XR2211...
 pinMode(dataIn, INPUT);
 //  Pin 13 has an LED connected...
 pinMode(ledPin, OUTPUT);
 //  turn the LED off
 digitalWrite(ledPin, LOW);
 //  set com port baud rate...
 Serial.begin(9600);
 Serial.println("Initialized...");
}

void loop()
{
 //  1.  set up a sampling rate of 5 times the BAUD rate:
 //      BAUD Rate is 520.83 Hz
 //      x5 = 2604.15 Hz
 //      1 / 2604.15 = 384 uS
 //  
 //  2.  therefore, this loop will read the digital pin
 //      every 384 uS and count each time up to 5, then
 //      the program will do the following:
 //
 //      a.  once 5 samples has been taken, we determine
 //          the majority winner (1 or 0)
 //      b.  take that bit, place it in the char variable
 //          by using shift left (<<)
 //      c.  reset the sample counter to 0
 //      d.  increment the bit counter (++)
 //      e.  when we have 8 bits, reset the counter, then
 //          test for the ZCZC header
 //      f.  if found, set the header found flag and begin
 //          processing out all incoming chars
 //      g.  if not found, continue until ZCZC header has
 //          been found

 if((micros() - sampleTime) > sampleRate)
 {
   //  read the digital input pin...
   inputState = digitalRead(dataIn);
   //  increment the sample counter...
   sampleCount++;
   //  add the counts...
   if(inputState == 1)
   {
     //  this is a 1...
     boolHigh++;
   }
   else
   {
     //  this is a 0...
     boolLow++;      
   }
   //  only when we have 5 samples!
   if(sampleCount > 4)
   {
     //  must reset sample count...
     sampleCount = 0;
     
     //  find out the winner...
     if(boolHigh > boolLow)
     {
       //  reset the counters...
       boolHigh = 0;
       boolLow = 0;
       //  high value wins...
       boolWinner = true;
       //  enter bit shift left by 1...
       byteData = charOne << 1;
       bitCounter++;
     }
     else
     {
       //  reset the counters...
       boolHigh = 0;
       boolLow = 0;
       //  low value wins...
       boolWinner = false;
       //  enter bit shift left by 1...
       byteData = charZero << 1;
       bitCounter++;
     }
   }
   
   //  if bitCounter is 8, we have a byte, so
   //  check for proper char expected and
   //  remember to reset the variable...
   if(bitCounter > 7)
   {
     //  reset the varaible...
     bitCounter = 0;
     
     if(!zczcHeader)
     {
       if(~byteData == 'Z')
       {
         //  send to serial port...
         Serial.print(~byteData);
         //  add this char to the string (and remember to invert!)...
         zczcCaptured += ~byteData;
       }
       else if(~byteData == 'C')
       {
         //  send to serial port...
         Serial.print(~byteData);
         //  add this char to the string (and remember to invert!)...
         zczcCaptured += ~byteData;
       }
       else
       {
         //  this is not a header, so clear the string...
         zczcCaptured = "";
       }
       
       //  test to see if we got the header...
       if(zczcCaptured == "ZCZC")
       {
         //  set the flag to indicate header captured...
         zczcHeader = true;
       }
       else
       {
         zczcHeader = false;
       }
     }
     else
     {
       //  we already found the header,
       //  so just send char to the
       //  serial port (and remember
       //  to invert!)...
       Serial.print(~byteData);
     }
   }

   //  get the time stamp...
   sampleTime = micros();
 }
}


First - I wasn't thinking I guess, but I had to set the variable 'sampleRate' to 384 (384 uS).  Second, since I'm shifting each bit into the char variable, I would need to invert this once complete.  Anywhere that the variable 'byteData' appears, it should be inverted:

Code: [Select]

~byteData


So I started the Uno and fed the audio file to the circuit.  I would hope to see the following decoded:

Code: [Select]

//  Severe Thunderstorm Warning - Benton Co. MS - 1 Hour Duration -
//  Expires 17th Day of Year At 17:40 UTC - From KMEG (Memphis)
ZCZC-WXR-SVR-028009+0100-0171740-KMEG/NWS-____
ZCZC-WXR-SVR-028009+0100-0171740-KMEG/NWS-____


Instead, I have nothing.  I've put some Serial.println() statements and I've been able to verify that all counters are parsing though like they should.  I've verified that the signal on pin 8 does change state if I move the input wire between Vcc and GND.

Any ideas as to what is wrong with the code?

Thanks!
tsc

rdsman

My first question:

Do you have a pulse train at pin 8, when you are playing your sample file?


Go Up