Wednesday, July 24, 2013

Morse code with an Arduino

In the hobby of amateur radio is a hobby called Radiosport. It is a competitive activity that combines RDF (radio direction finding) and orienteering, or land navigation. At the heart of this activity are a set of hidden transmitters that periodically transmits a coded message in Morse code. The goal is to locate all the transmitters in the minimum time. I have created some software for the Arduino, that with a gated oscillator and a hand-held radio, will make a good hidden transmitter for RDF activities like Radiosport or fox hunts.

The code for Arduino is called “Processing”. You can think of it as ‘C’, as the back end is a CPP compiler. The Arduino requires two functions to be implemented, and calls them when the device is powered on. The first function, ‘setup’ is called once soon after the device comes up, and then another function, ‘loop’ is called again and again forever, until the device is powered down.

 

I start with a few global variables. You’ll see this pattern many times in Arduino code.


/* Version 0.1 Support a-z and 0-9 and space */
/* Version 0.2 Support .,?!{}&:;=+-_"$@'/ */
int ledPin = 13;
int PTT = 12;


PTT means push-to-talk, a line to tell the transmitter to…transmit. The ledPin would connected to gate the output of an audio frequency oscillator. The output of that gated oscillator would go to the mic line of the transmitter.




int lengthDot = 200;
int lengthDash = 3 * lengthDot;
int CycleTime = 15000; // every 15 seconds


The length of a dot is 200 milliseconds, while the length of a dash is three times that. This transmitter transmits every 15 seconds.



I use a structure that contains a key, the number of symbols (sum of dots and dashes) and the code itself encoded from right to left. Dots are 0, while dashes are 1.




typedef struct {
char info;
byte length;
byte code;
} tagMorseLetter;


I create a table with 54 entries




int entries = 54;
tagMorseLetter morseCode[] = {
//code is shifted from the left
{'a',2,0b00000010}, // .-
{'b',4,0b00000001}, // -...
{'c',4,0b00000101}, // -.-.
{'d',3,0b00000001}, // -..
{'e',1,0b00000000}, // .
{'f',4,0b00000100}, // ..-.
{'g',3,0b00000011}, // --.
{'h',4,0b00000000}, // ....
{'i',2,0b00000000}, // ..
{'j',4,0b00001110}, // .---
{'k',3,0b00000101}, // -.-
{'l',4,0b00000010}, // .-..
{'m',2,0b00000011}, // --
{'n',2,0b00000001}, // -.
{'o',3,0b00000111}, // ---
{'p',4,0b00000110}, // .--.
{'q',4,0b00001011}, // --.-
{'r',3,0b00000010}, // .-.
{'s',3,0b00000000}, // ...
{'t',1,0b00000001}, // -
{'u',3,0b00000100}, // ..-
{'v',4,0b00001000}, // ...-
{'w',3,0b00000110}, // .--
{'x',4,0b00001001}, // -..-
{'y',4,0b00001101}, // -.--
{'z',4,0b00000011}, // --..
{'1',5,0b00011110}, // .----
{'2',5,0b00011100}, // ..---
{'3',5,0b00011000}, // ...--
{'4',5,0b00010000}, // ....-
{'5',5,0b00000000}, // .....
{'6',5,0b00000001}, // -....
{'7',5,0b00000011}, // --...
{'8',5,0b00000111}, // ---..
{'9',5,0b00001111}, // ----.
{'0',5,0b00011111}, // ----- Stay away from the codes below here
{'.',6,0b00101010}, // .-.-.- _No_ hams know them
{',',6,0b00110011}, // --..--
{'?',6,0b00001100}, // ..--..
{'!',6,0b00110101}, // -.-.--
{'{',5,0b00001101}, // -.--.
{'}',6,0b00101101}, // -.--.-
{'&',5,0b00000010}, // .-...
{':',6,0b00000111}, // ---...
{';',6,0b00010101}, // -.-.-.
{'=',5,0b00010001}, // -...-
{'+',5,0b00001010}, // .-.-.
{'-',6,0b00100001}, // -....-
{'_',6,0b00101100}, // ..--.-
{'"',6,0b00010010}, // .-..-.
{'$',7,0b01001000}, // ...-..-
{'@',6,0b00010110}, // .--.-.
{'\'',6,0b00011110}, // .----.
{'/',5,0b00001001} // -..-.
};


The meat of the code. The variable ‘current’ is the index of current item in the message as it's being transmitted.




int current = 0;

void setup() {
pinMode(ledPin, OUTPUT);
pinMode(PTT, OUTPUT);
}
void loop() {
int timeSpent = DoTask(ledPin,"your callsign here");
int restOfTime = CycleTime - timeSpent;
if (restOfTime > 0) delay(restOfTime);
}


Because I want to transmit synchronously, I need to know how much time each function in my code takes (give or take). I can sum all the times, and know when to start my next transmission. When I send a message, that function tells me how long it took. Of course that requires that each call to send a character or a space returns the time that took, which of course requires that the call to send a dot or a dash also returns how long it took. It's turtles all the way down. And finally, I wrap it in a DoTask function.




int DoTask(int pin, char* sentence) {
return sendSentence(pin, sentence);
}



int sendSentence(int pin, char sentence[])
{
int result = 0;
boolean between = false;
digitalWrite(PTT, HIGH);
char* p = sentence;
while (0 != *p) {
if (between) result += InterLetter(pin);
if (*p == ' ') result += Space(pin);
else {
int entry = FindCode(*p); // unknown time
if (-1 != entry) {
result += sendLetter(pin,morseCode[entry].length,morseCode[entry].code);
between = true;
}
}
p++;
}
digitalWrite(PTT, LOW);
return result;
}


I use a mask to get the LSB of the current code. I shift it out, bit-by-bit, and depending if the result is a 0 or 1, I transmit a dot or a dash. Note that the symbols are coded right to left, so I must shift them out left to right. Of course I accumulate the time spent transmitting the symbols. Between each symbol is a gap, so I account for that also.




int sendLetter(int pin, byte length, byte code)
{
int result = 0;
boolean between = false;
for(byte i = 0; i<length; i++) {
byte lsb = code & 0b00000001;
if (between) result += Gap(pin);
if (lsb == 0b00000001) result += Dash(pin);
if (lsb == 0b00000000) result += Dot(pin);
code = code>>1;
between = true;
}
digitalWrite(pin, LOW);
return result;
}
int Dash(int pin) {
digitalWrite(pin, HIGH);
delay(lengthDash);
return lengthDash;
}
int Dot(int pin)
{
digitalWrite(pin, HIGH);
delay(lengthDot);
return lengthDot;
}
int Gap(int pin)
{
digitalWrite(pin, LOW);
delay(lengthDot);
return lengthDot;
}
/* The space between letters is the time of 3 dots */
int InterLetter(int pin)
{
digitalWrite(pin, LOW);
int totalDelay = lengthDot + lengthDot + lengthDot;
delay(totalDelay);
return totalDelay;
}
/* A space character is the time of 7 dots */
int Space(int pin)
{
digitalWrite(pin, LOW);
int totalDelay = lengthDot + lengthDot +
lengthDot + lengthDot +
lengthDot + lengthDot + lengthDot;
delay(totalDelay);
return totalDelay;
}


Now normally I'd run a binary search here, but this was just easier, and it works. In a future version, I’ll improve this.




int FindCode(char c) {
int found = -1;
for(int i = 0; i< entries; i++) {
if (morseCode[i].info == c) {
found = i;
break;
}
}
return found;
}

No comments:

Post a Comment