[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