[McHUG] CW Decoder for Arduino

n3sb at qis.net n3sb at qis.net
Sat Oct 11 20:56:05 EDT 2008


Hi Folks;

The code below is my very first attempt at decoding CW. It works, but needs
refinement. It's attached below for anyone who wants to play with it.

73; Steve, N3SB

-------------------------------------------------------------------

// Program morse1 written October 11, 2008 N3SB
//
// This program is the first attempt to receive CW on an Arduino (ATMega168
based)
// The program depends on a 1000 Hz interrupt routine, which is used to read the
status of the "key" line.
// From there the length of the CW elements is measured, and the elements are
translated to ASCII characters.

// #include <avr/interrupt.h>  
// #include <avr/io.h>  
#include <avr/pgmspace.h>

// Macro and other defines
#define KEY_UP 1                // Logic level expected when key is up
#define KEY_DOWN 0              // Logic level expected when key is down
#define INIT_TIMER_COUNT 6  
#define RESET_TIMER2 TCNT2 = INIT_TIMER_COUNT  
#define UNKNOWN Serial.print("_")

// Variable defines
unsigned int int_counter = 0;
unsigned int key_len = 0;
unsigned char edge = 0;
unsigned char key_up_edge = KEY_UP * 15;      // set 4 LSBs to the value of
KEY_UP
unsigned char key_down_edge = KEY_DOWN * 15;  // set 4 LSBs to the value of
KEY_DOWN
unsigned char key_status = KEY_UP;
unsigned char dot_len = 100;                   // start with a 20 ms dot length
unsigned int cw_char = 0;                     // holds the character we are
receiving
unsigned int char_length = 0;                 // counts the number of elements
in the character
unsigned char space = 0;                      // flag for tracking the output of
spaces
unsigned int q;

// Pin Defines
int ledpin = 13;               // Arduino pin used by the LED
int keypin = 2;                // Arduino pin used for CW input

// Aruino runs at 16 Mhz, so we have 1000 Overflows per second...  
// 1 / ( (16000000 / 64) / 256) = 1 / 1000  
ISR(TIMER2_OVF_vect) {  
  RESET_TIMER2;  
  if (int_counter < 1000) {      // Keep int_counter from overflowing
    int_counter++;  
  }
  edge = (edge << 1) | digitalRead(keypin);  // Shift in the status of the key
pin
  // hack
  // digitalWrite(ledpin, digitalRead(keypin));               // turn off LED

}  // end of timer interrupt routine

void setup() {
  pinMode(keypin, INPUT);        // configure key pin for input
// hack to enable the internal pull-up on the keypin
  PORTD = PORTD | 4;
  pinMode(ledpin, OUTPUT);       // configure LED pin for output
  Serial.begin(9600);
  Serial.println("Program MorseRX1 - 11 October 2008 - N3SB");
  Serial.println("Initializing 1 ms timer interrupt");  

  //Timer2 Settings: Timer Prescaler /64,   
  TCCR2B |= (1<<CS22);                     // turn on CS22 bit
  TCCR2B &= ~((1<<CS21) | (1<<CS20));      // turn off CS21 and CS20 bits   

  // Use normal mode  
  TCCR2A &= ~((1<<WGM21) | (1<<WGM20));    // turn off WGM21 and WGM20 bits 
  TCCR2B &= ~(1<<WGM22);                   // turn off WGM22

    // Use internal clock - external clock not used in Arduino  
  ASSR |= (0<<AS2);  

  //Timer2 Overflow Interrupt Enable  
  TIMSK2 |= (1<<TOIE2) | (0<<OCIE2A);      //Timer2 Overflow Interrupt Enable  

  RESET_TIMER2;                 
  digitalWrite(ledpin, LOW);               // turn off LED

  sei();  
}    // end of Arduino setup structure  

void loop() {  
  if (key_status == KEY_UP)
  {
    // look for a stable transition to the KEY_DOWN state
    if ( (edge & 15) == key_down_edge ) // Key status has transitioned to DOWN
    {
      key_status = KEY_DOWN;            // Set new key status
      int_counter = 0;                  // clear int counter
      // Serial.println("KEY_DOWN Edge detected");    // hack
    }
    else
    {
      // In this state the key is up. We now time how long it's up to determine
inter-element, inter-character, and inter-word spacing
      // Inter-element is one dot long
      // Inter-character is three dots long
      // Inter-word is seven dots long
      // The code below currently only looks for inter-character and inter-word
spacings
      digitalWrite(ledpin, LOW);               // turn off LED
      // hack - the threshold should be dot_len * 2, but 3 works much better for
some reason.
      if (int_counter > (unsigned int)(dot_len * 3) )  // if the inter-element
spacing is greater than our dot length * 2 then its a character separator.
      {
        // Now we get to process a character - but only if one has been
received!
        if (char_length > 0)
        {
          process_character( );
          cw_char = 0;
          char_length = 0;
          space = 0;
        }
      }
      if (int_counter >= (dot_len * 6) )    // inter-word spacing has been
detected - or a long pause
      {
         // print a space - but only one!
         if (space == 0)
         {
           Serial.print(" ");
           space = 1;
         }
      }
    }
  }
  else                                  // Key status is not KEY_UP (it's down)
  {
    // look for a stable transition to the KEY_UP state
    if ( (edge & 15) == key_up_edge )   // Key status has transitioned to UP
    {
      key_status = KEY_UP;             // Set new key status
      key_len = int_counter;            // Save length of time key was down
      int_counter = 0;                  // clear int counter
      // Serial.println("KEY_UP Edge detected");    // hack

      // Let's now see how long the key was down - was it a dot or a dash?
      if (key_len < (dot_len << 1) )    // threshold for dot - dash evaluation
is dot length * 2
      {
        // Guessing it's a dot, or something shorter....
        if (key_len > (dot_len >> 1) )  // a valid dot has to be at least half
as long as our dot length
        {
         // It's a good dot! 
          cw_char = cw_char << 1;       // shift the character storage left (and
save a 0 for the dot)
          char_length++;                // increment the character length
counter
        }
      }
      else
      {
        // Guessing it's a dash, or something longer....
        if (key_len < (dot_len << 3) )   // a vaild dash has to be no longer
than our dot length * 8
        {
         // It's a good dash!
          cw_char = (cw_char << 1) | 1;  // shift the character storage left
(and save a 1 for the dash)
          char_length++;                 // increment the character length
counter
        }
      }
    }    // end of key up edge detection

// In this state the key is down. We currently don't do anything more than wait
for the key to go back up!
   digitalWrite(ledpin, HIGH);               // turn on LED

  }
}  // end of Arduino loop structure

void process_character(void)
{
  // This function uses the global variable cw_char and char_length to translate
a bit pattern into its ASCII equivalent
  // The pattern is represented by bits stored in cw_char. 0 represents a dot
and 1 represents a dash.
  // The LSB of cw_char has the last element received. char_length contains the
number of elements.
  // cw_char is a 16 bit variable, which allows for the translation of
extra-long characters. Some folks run CQ together, for example.
  // The conversion process could have been done with a look-up table, but this
approach allows us to do some other things based
  // on what character was received.
  
  //                            0123456789
  
/*
*/
  
  switch (char_length)     // evaluate the bit patter based on the number of
elements
  {
    case 1:                // one element characters - E T
      switch (cw_char)
      {
        case 0:
          Serial.print("E");
          break;
        case 1:
          Serial.print("T");
          break;
        default:
          UNKNOWN;         // this condition should never occur
      }
      break;
      
    case 2:                // two element characters I A N M
      switch (cw_char)
      {
        case 0:
          Serial.print("I");
          break;
        case 1:
          Serial.print("A");
          break;
        case 2:
          Serial.print("N");
          break;
        case 3:
          Serial.print("M");
          break;
        default:
          UNKNOWN;         // this condition should never occur
      }
      break;
      
    case 3:                // three element characters S U R W D K G O
      switch (cw_char)
      {
        case 0:
          Serial.print("S");
          break;
        case 1:
          Serial.print("U");
          break;
        case 2:
          Serial.print("R");
          break;
        case 3:
          Serial.print("W");
          break;
        case 4:
          Serial.print("D");
          break;
        case 5:
          Serial.print("K");
          break;
        case 6:
          Serial.print("G");
          break;
        case 7:
          Serial.print("O");
          break;
        default:
          UNKNOWN;         // this condition should never occur
      }
      break;
    
    case 4:                // four element characters H V F _ L _ P J B X C Y Z
Q _ _
      switch (cw_char)
      {
        case 0:
          Serial.print("H");
          break;
        case 1:
          Serial.print("V");
          break;
        case 2:
          Serial.print("F");
          break;
        case 3:
          UNKNOWN;
          break;
        case 4:
          Serial.print("L");
          break;
        case 5:
          UNKNOWN;
          break;
        case 6:
          Serial.print("P");
          break;
        case 7:
          Serial.print("J");
          break;
        case 8:
          Serial.print("B");
          break;
        case 9:
          Serial.print("X");
          break;
        case 10:
          Serial.print("C");
          break;
        case 11:
          Serial.print("Y");
          break;
        case 12:
          Serial.print("Z");
          break;
        case 13:
          Serial.print("Q");
          break;
        case 14:
          UNKNOWN;
          break;
        case 15:
          UNKNOWN;
          break;
        default:
          UNKNOWN;         // We have tested for all possible combinations. This
condition should never occur
      }

      break;
      
    case 5:                // five element characters 5 4 3 2 as ar 1 6 bt / kn
7 8 9 0 (and a bunch of invalid characters in between)
      switch (cw_char)
      {
        case 0:
          Serial.print("5");
          break;
        case 1:
          Serial.print("4");
          break;
        case 3:
          Serial.print("3");
          break;
        case 7:
          Serial.print("2");
          break;
        case 8:
          Serial.print("as");
          break;
        case 10:
          Serial.print("ar");
          break;
        case 15:
          Serial.print("1");
          break;
        case 16:
          Serial.print("6");
          break;
        case 17:
          Serial.print("bt");
          break;
        case 18:
          Serial.print("/");
          break;
        case 22:
          Serial.print("kn");
          break;
        case 24:
          Serial.print("7");
          break;
        case 28:
          Serial.print("8");
          break;
        case 30:
          Serial.print("9");
          break;
        case 31:
          Serial.print("0");
          break;
        default:
          UNKNOWN;         // A number of patterns have not been specifically
tested. This condition can occur.
      }
      break;
      
    case 6:                // six element characters (punctuation marks) ?
period @ comma  
      switch (cw_char)
      {
        case 12:
          Serial.print("?");
          break;
        case 21:
          Serial.print(".");
          break;
        case 26:
          Serial.print("@");
          break;
        case 51:
          Serial.print(",");
          break;
        default:
          UNKNOWN;         // Most of the patterns have not been specifically
tested. This condition can occur.
      }
      break;
      
    case 7:
      break;
    
    default:
     UNKNOWN;      // unknown or undefined character. 
  }
  
}





More information about the McHUG mailing list