[McHUG] Arduino CW Receive Program 12 October version

n3sb at qis.net n3sb at qis.net
Sun Oct 12 20:26:57 EDT 2008


Hi Folks;

The 12-October version is attached below. There are a lot of improvements!
CW Speed tracking is implemented, and it works quite well. I've been using a
memory keyer to generate signals for the Arduino. I've recorded a couple test
messages to facilitate testing. I can change the speed control during playback
from 10 WPM to 50 WPM, and the decoded CW output doesn't miss a beat! If the CW
speed changes instantaneously from 10 to 50, or from 50 to 10, the output will
have 3-4 characters at the most before it's on track again. Using the same
memory keyer, I tested the program to about 60 WPM. My memory keyer ran out of
steam, but the Arduino was still capable of copying CW twice as fast by my
guess. 

My auto-speed algorithm may fall apart when it's subjected to real-world
conditions - static, noise, etc. That's something I hope to test soon.

I've changed the code a bit to better support decoded CW output to an LED or
other device. (The program below still outputs decoded CW to the RS-232 port,
but it's now possible to change just a couple lines of code to send it
elsewhere.) Debug messages are still hard-coded to output to the RS-232 port.

I've also implemented a simple function that looks for two "trigger messages"
and either turns on or turns off the standard Arduino LED. It should be easy to
modify this function to add other trigger messages and to do other things when
the messages are received. Can you guess what trigger messages I used?

73; Steve, N3SB

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


// Program morse1 written October 12, 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>
#include <string.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 strcpy(rcvdchar,"_")
 
// 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 int dot_len = 100;                   // start with a 20 ms dot length
unsigned int temp_dot_len = dot_len;          // temporary storage
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 rcvd_dot = 0;                    // temporary storage for received
dot length
unsigned int rcvd_dash = 0;                   // temporary storage for received
dash length
unsigned char adjust = 0;                     // flag to permit adjustment of CW
speed
unsigned char faster = 0;                     // flag to recommend adjusting
speed up
unsigned char slower = 0;                     // flag to recommend adjusting
speed down
unsigned char print_speed = 0;                // flag to cause the CW speed to
be printed
char rcvdchar[3];                             // storage for current received
character(s)
unsigned int charpos;                         // pointer for working with
strings
char trigger1[] = ".N3SB LED ON.";            // trigger message to turn LED on
char trigger2[] = ".N3SB LED OFF.";           // trigger message to turn LED
off
unsigned int trig1ptr = 0;
unsigned int trig2ptr = 0;
unsigned int trig1len;
unsigned int trig2len;

// 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 < 4000) 
  {                            // Keep int_counter from overflowing
    int_counter++;  
  }
  if (int_counter == 3999)
  {
    print_speed = 1;
  }
  edge = (edge << 1) | digitalRead(keypin);  // Shift in the status of the key
pin

}  // end of timer interrupt routine

void setup() {
  pinMode(keypin, INPUT);        // configure key pin for input
  digitalWrite(keypin, HIGH);    // turn on the weak pull-up on the designated
input pin
  pinMode(ledpin, OUTPUT);       // configure LED pin for output
  digitalWrite(ledpin, LOW);     // turn off LED
  trig1len = strlen(trigger1);
  trig2len = strlen(trigger2);
  Serial.begin(9600);
  Serial.println(" ");  
  Serial.println("Program MorseRX1 - 12 October 2008 - N3SB");
  Serial.println(" ");  

  //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;                 

  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
      temp_dot_len = int_counter;
      int_counter = 0;                  // clear int counter
      if ( (char_length > 0) && (adjust == 0) )              // this routine
adjusts the receive speed based on the spaces between elements
      {
        if (temp_dot_len > dot_len)
          dot_len = dot_len + (dot_len >> 2);      // decrease speed by 25%
        else
          dot_len = dot_len - (dot_len >> 2);      // increase speed by 25%
      }
    }
    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
      if (int_counter > (dot_len << 1) )  // 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( );           // Note - word spaces are NOT
processed here! 
          // print character(s) received
          charpos = 0;
          while (rcvdchar[charpos] != 0)
            Serial.print(rcvdchar[charpos++]);
          testmsg();
          cw_char = 0;
          char_length = 0;
          space = 0;
          if (adjust == 1)
          {
            // adjust the dot_len variable based on received characters
            dot_len = (rcvd_dot + rcvd_dash/3)/2;
            faster = 0;
            slower = 0;
          }
          if (slower > 3)        // need to decrease speed (increase dot_len)
          {
            dot_len = dot_len + (dot_len >> 2);  // decrease speed by 25%
          }
        }
      }
      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;
           strcpy(rcvdchar, " ");
           testmsg();
         }
      }
      if ( (char_length > 8) && (cw_char == 0) )   // getting nothing but dots
-- don't wait for a letter break - turn up the speed
      {
        dot_len = dot_len - (dot_len >> 2);  // decrease speed by 25%
        char_length = 0;
      }
      if (print_speed == 1)
      {
        Serial.print(" dot_len=");
        Serial.print(dot_len, DEC);
        Serial.print("ms ");
        print_speed = 0;
      }
    }
  }
  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

      // 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 >> 2) )  // a valid dot has to be at least a
quarter 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
          rcvd_dot = key_len;
        }
      }
      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
          rcvd_dash = key_len;
        }
      }
    }    // 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.
  // If the character received has a mix of dots and dashes, then the program
will use the received dot and dash lenghts
  // to adjust the dot_len variable, which effectively allows the program to
track the received CW speed. 
  
  adjust = 0;              // default is don't adjust dot_len
  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:
          strcpy(rcvdchar, "E");
          break;
        case 1:
          strcpy(rcvdchar, "T");
          slower++;
          break;
        default:
          UNKNOWN;         // this condition should never occur
      }
      break;
      
    case 2:                // two element characters I A N M
      switch (cw_char)
      {
        case 0:
          strcpy(rcvdchar, "I");
          break;
        case 1:
          strcpy(rcvdchar, "A");
          adjust = 1;
          break;
        case 2:
          strcpy(rcvdchar, "N");
          adjust = 1;
          break;
        case 3:
          strcpy(rcvdchar, "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:
          strcpy(rcvdchar, "S");
          break;
        case 1:
          strcpy(rcvdchar, "U");
          adjust = 1;
          break;
        case 2:
          strcpy(rcvdchar, "R");
          adjust = 1;
          break;
        case 3:
          strcpy(rcvdchar, "W");
          adjust = 1;
          break;
        case 4:
          strcpy(rcvdchar, "D");
          adjust = 1;
          break;
        case 5:
          strcpy(rcvdchar, "K");
          adjust = 1;
          break;
        case 6:
          strcpy(rcvdchar, "G");
          adjust = 1;
          break;
        case 7:
          strcpy(rcvdchar, "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:
          strcpy(rcvdchar, "H");
          break;
        case 1:
          strcpy(rcvdchar, "V");
          adjust = 1;
          break;
        case 2:
          strcpy(rcvdchar, "F");
          adjust = 1;
          break;
        case 3:
          UNKNOWN;
          break;
        case 4:
          strcpy(rcvdchar, "L");
          adjust = 1;
          break;
        case 5:
          UNKNOWN;
          break;
        case 6:
          strcpy(rcvdchar, "P");
          adjust = 1;
          break;
        case 7:
          strcpy(rcvdchar, "J");
          adjust = 1;
          break;
        case 8:
          strcpy(rcvdchar, "B");
          adjust = 1;
          break;
        case 9:
          strcpy(rcvdchar, "X");
          adjust = 1;
          break;
        case 10:
          strcpy(rcvdchar, "C");
          adjust = 1;
          break;
        case 11:
          strcpy(rcvdchar, "Y");
          adjust = 1;
          break;
        case 12:
          strcpy(rcvdchar, "Z");
          adjust = 1;
          break;
        case 13:
          strcpy(rcvdchar, "Q");
          adjust = 1;
          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:
          strcpy(rcvdchar, "5");
          break;
        case 1:
          strcpy(rcvdchar, "4");
          adjust = 1;
          break;
        case 3:
          strcpy(rcvdchar, "3");
          adjust = 1;
          break;
        case 7:
          strcpy(rcvdchar, "2");
          adjust = 1;
          break;
        case 8:
          strcpy(rcvdchar, "as");
          adjust = 1;
          break;
        case 10:
          strcpy(rcvdchar, "ar");
          adjust = 1;
          break;
        case 15:
          strcpy(rcvdchar, "1");
          adjust = 1;
          break;
        case 16:
          strcpy(rcvdchar, "6");
          adjust = 1;
          break;
        case 17:
          strcpy(rcvdchar, "bt");
          adjust = 1;
          break;
        case 18:
          strcpy(rcvdchar, "/");
          adjust = 1;
          break;
        case 22:
          strcpy(rcvdchar, "kn");
          adjust = 1;
          break;
        case 24:
          strcpy(rcvdchar, "7");
          adjust = 1;
          break;
        case 28:
          strcpy(rcvdchar, "8");
          adjust = 1;
          break;
        case 30:
          strcpy(rcvdchar, "9");
          adjust = 1;
          break;
        case 31:
          strcpy(rcvdchar, "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 5:
          strcpy(rcvdchar, "sk");
          adjust = 1;
          break;
        case 12:
          strcpy(rcvdchar, "?");
          adjust = 1;
          break;
        case 18:
          strcpy(rcvdchar, "\"");
          adjust = 1;
          break;
        case 21:
          strcpy(rcvdchar, ".");
          adjust = 1;
          break;
        case 26:
          strcpy(rcvdchar, "@");
          adjust = 1;
          break;
        case 30:
          strcpy(rcvdchar, "'");
          adjust = 1;
          break;
        case 33:
          strcpy(rcvdchar, "-");
          adjust = 1;
          break;
        case 43:
          strcpy(rcvdchar, "!");
          adjust = 1;
          break;
        case 51:
          strcpy(rcvdchar, ",");
          adjust = 1;
          break;
        default:
          if (cw_char == 0)      // got 6 dots - speed needs to be turned up
          {
            faster++;
          }
          UNKNOWN;         // Most of the patterns have not been specifically
tested. This condition can occur.
      }
      break;
      
    case 7:
      switch (cw_char)
      {
        case 69:
          strcpy(rcvdchar, "bk");
          adjust = 1;
          break;
        default:
          if (cw_char == 0)      // got 6 dots - speed needs to be turned up
          {
            faster++;
          }
          UNKNOWN;         // Most of the patterns have not been specifically
tested. This condition can occur.
      }

      if (cw_char == 0)      // got 7 dots - speed needs to be turned up
      {
        faster++;
        UNKNOWN;
      }
      break;

    case 8:
      switch (cw_char)
      {
        case 164:
          strcpy(rcvdchar, "cl");
          break;
        case 173:
          strcpy(rcvdchar, "CQ");    // A strung-together CQ
          break;
        default:
          if (cw_char == 0)      // got 8 dots - speed needs to be turned up
          {
            faster++;
          }
        UNKNOWN;         // Most of the patterns have not been specifically
tested. This condition can occur.
      }
      break;
    
    default:
      if (cw_char == 0)      // got all dots - speed needs to be turned up
      {
        faster++;
      }
     UNKNOWN;      // unknown or undefined character. 
  }
}        // end of process_character

void testmsg(void)
// This function works with rcvdchar[] and the trigger messages trigger1[] and
trigger2[], and trig1ptr, trig2ptr, trig1len, trig2len to see if there's a
match
{
  if (trigger1[trig1ptr] == rcvdchar[0])
  {
    trig1ptr++;
      if (trig1ptr == trig1len)
      {
          digitalWrite(ledpin, HIGH);     // turn on LED
          trig1ptr = 0;
      }
  }
  else
  {
    trig1ptr = 0;
  }  
  
  if (trigger2[trig2ptr] == rcvdchar[0])
  {
    trig2ptr++;
      if (trig2ptr == trig2len)
      {
          digitalWrite(ledpin, LOW);     // turn off LED
          trig2ptr = 0;
      }
  }
  else
  {
    trig2ptr = 0;
  }  
 
  
  
}        // end of testmsg



More information about the McHUG mailing list