Decoding Morse Code

A video posted by Dan Royer (@imakerobots) on

Morse code is an ancient tongue spoken by the hooded figures who worship a terrifying obsidian pillar in the sunken city of – oh, wait. Sorry! Wrong blog.

We’ve briefly looked over producing Morse code and making music. What if we’re receiving Morse code now? Today, we’ll be teaching the Arduino to translate Morse code back into English and use a microphone at the same time.

What you’ll need:

  • Arduino Uno
  • Wires, male on one end and female on the other
  • USB Cable
  • Microphone
  • PC/Laptop connection

The circuit

A photo posted by Dan Royer (@imakerobots) on

Let’s take a brief look at the circuit we’ll be playing with:

Arduino (Morse Code with Arduino and Micro) png

We’re also going to want to connect the whole thing into our computer so we can upload code. Remember, all the code can be found href=’http://github.com/marginallyclever/arduinostarterkit’ target=’_blank’>here. Today, we’ll be looking at the folder called “arduinoListenToMorse”.

Volume Graph

First step we’re going to take is a checking step – we’re going to make sure our microphone works exactly how it’s supposed to work. I’ll make a graph of the volume picked up by the microphone for you to see.

#define THRESHOLD (0)
#define MAX_SAMPLES (5)

int top=0;

int samples[MAX_SAMPLES];
int si=0;
int mi=0;
int total=0;

char *graph[11] = {
"----------",
"*---------",
"**--------",
"***-------",
"****------",
"*****-----",
"******----",
"*******---",
"********--",
"*********-",
"**********",
};

void setup() {
Serial.begin(57600);

for(int i=0;i<MAX_SAMPLES;++i) {
samples[i]=0;
}
}

void loop() {
int volume=analogRead(0);
                                // average the samples
total -= samples[si];
samples[si] = volume;
total += samples[si];
if( mi < MAX_SAMPLES ) mi++;
si = (si+1) % MAX_SAMPLES;
int average = total / mi;

                                // remember the loudest sound, for scaling the graph
if( top < average ) top = average;
                                // scale the volume from threshold to top, inclusive.
int x = 10.0 * (float)(average-THRESHOLD)/(float)(top-THRESHOLD);
if(x<0) x=0;
if(x>10) x=10;
                                // print it out
Serial.print(millis());
Serial.print('\t');
Serial.print(top);
Serial.print('\t');
Serial.println(graph[x]);

delay(25);
}

The graph comes out like this:

25	42	**********
51	42	**********
76	42	**********
102	42	**********
128	42	**********
153	42	**********
179	42	**********
204	42	**********
230	42	**********
256	42	**********
281	42	**********
307	42	**********
332	42	**********
358	42	**********
384	42	**********
409	42	**********
435	42	**********
460	42	**********
487	42	**********
513	42	**********
538	42	**********
564	42	**********
589	42	**********
615	42	**********
641	42	**********
666	42	**********
692	42	**********
717	42	**********
743	42	**********
769	42	**********
794	42	**********
820	42	**********
845	42	**********
871	42	**********
897	42	**********
922	42	**********
948	42	**********
973	42	**********
1000	42	**********
1026	42	**********
1051	42	**********
1077	42	**********
1102	42	**********
1128	42	**********
1154	42	**********
1179	42	**********
1205	42	**********
1230	42	**********
1257	42	**********
1283	42	**********
1308	42	**********
1334	42	**********
1359	42	**********
1385	42	**********
1411	42	**********
1436	42	**********
1462	42	**********
1487	42	**********
1514	42	**********
1540	42	**********
1565	42	**********
1591	42	**********
1616	42	**********
1642	42	**********
1668	42	**********
1693	42	**********
1719	42	**********
1744	42	**********
1770	42	**********
1797	42	**********
1822	42	**********
1848	42	**********
1873	42	**********
1899	42	**********
1925	42	**********
1950	42	**********
1976	42	**********
2001	42	**********
2027	42	**********
2054	42	**********
2079	42	**********
2105	42	**********
2130	42	**********
2156	42	**********
2182	42	**********
2207	42	**********
2233	42	**********
2258	42	**********
2284	42	**********
2311	42	**********
2336	42	**********
2362	42	**********
2387	42	**********
2413	42	**********
2439	42	**********
2464	42	**********
2490	42	**********
2515	42	**********
2541	42	**********
2568	42	**********
2593	42	**********
2619	42	**********
2644	42	**********
2670	42	**********
2696	42	**********
2721	42	**********
2747	42	**********
2772	42	**********
2798	42	**********
2825	42	**********
2850	42	**********
2876	42	**********
2902	42	**********
2927	42	**********
2953	42	**********
2978	42	**********
3004	42	**********
3030	42	**********
3055	42	**********
3081	42	**********
3107	42	**********
3133	42	**********
3159	42	**********
3184	42	**********
3210	42	**********
3235	42	**********
3261	42	**********
3287	42	**********
3312	42	**********
3338	42	**********
3364	42	**********
3390	42	**********
3416	42	**********
3441	42	**********
3467	43	**********
3492	43	**********
3518	43	**********
3544	43	**********
3569	43	**********
3595	43	**********
3621	43	**********
3647	43	**********
3673	43	**********
3698	43	**********
3724	43	**********
3749	43	**********
3775	43	**********
3801	43	**********
3826	43	**********

If we take a look at it, it’s obvious to see that the volume in a quiet room is ~42. I’m going to set the threshold volume to 44 and sing something into the microphone.

4006	208	----------
4032	208	----------
4059	208	----------
4084	208	----------
4110	208	----------
4135	208	----------
4161	208	----------
4187	208	----------
4213	208	----------
4239	208	----------
4264	208	----------
4290	208	----------
4316	208	----------
4341	208	----------
4367	208	*---------
4393	208	*---------
4419	208	*---------
4445	208	*---------
4470	208	----------
4496	208	----------
4521	208	----------
4547	208	----------
4574	208	----------
4599	208	----------
4625	208	**--------
4651	208	**--------
4676	208	***-------
4702	208	***-------
4728	208	***-------
4754	208	*---------
4780	208	*---------
4805	208	----------
4831	208	----------
4856	208	----------
4882	208	----------
4909	208	----------
4934	208	----------
4960	208	----------
4985	208	----------
5011	208	----------
5037	208	----------
5063	208	----------
5089	208	----------
5114	208	----------
5140	208	----------
5166	208	----------
5191	208	----------
5217	208	----------
5243	208	----------
5269	208	----------
5295	208	*****-----
5320	208	******----
5346	208	******----
5371	208	******----
5398	208	********--
5424	208	***-------
5449	208	******----
5475	208	*******---
5500	208	*******---
5526	208	******----
5552	208	******----
5578	208	*---------
5604	208	*---------
5629	208	*---------
5655	208	----------
5681	208	----------
5706	208	----------
5732	208	----------
5758	208	**--------
5784	208	**--------
5810	208	**--------
5835	208	**--------
5861	208	**--------
5886	208	----------
5913	208	----------
5939	208	----------
5964	208	----------
5990	208	----------
6016	208	----------
6041	208	----------
6067	208	----------
6093	208	----------
6119	208	----------
6145	208	----------
6170	208	----------
6196	208	----------
6221	208	----------
6248	208	----------
6274	208	----------
6299	208	----------
6325	208	***-------
6350	208	***-------
6376	208	***-------
6402	208	***-------
6428	208	***-------
6454	208	----------
6479	208	----------
6505	208	----------
6531	208	----------
6556	208	----------
6582	208	----------
6608	208	----------
6634	208	----------
6660	208	----------
6685	208	----------
6711	208	----------
6736	208	----------
6762	208	----------
6789	208	----------
6814	208	----------
6840	208	----------
6865	208	----------
6891	208	----------
6917	208	----------
6943	208	----------
6969	208	----------
6994	208	----------
7020	208	----------
7046	208	----------
7071	208	----------
7097	208	----------
7123	208	----------
7149	208	----------
7175	208	----------
7200	208	----------
7226	208	----------
7251	208	----------
7277	208	----------
7304	208	----------
7329	208	----------
7355	208	----------
7380	208	----------
7406	208	----------
7432	208	----------
7458	208	----------
7484	208	----------
7510	208	*---------
7535	208	*****-----
7561	208	*****-----
7586	208	*****-----
7612	208	*****-----
7639	208	****------
7664	208	**--------
7690	208	**--------
7715	208	**--------
7741	208	**--------
7767	208	**--------
7793	208	----------
7819	208	***-------
7844	208	**--------
7870	212	**********
7896	220	**********
7921	392	**********
7947	412	**********
7973	412	*********-
7999	412	******----
8025	412	******----
8050	412	*---------
8076	412	----------
8101	412	----------
8128	412	----------
8154	412	----------
8179	412	----------
8205	412	***-------
8230	412	***-------
8256	412	**--------
8282	412	****------
8308	412	****------
8334	412	***-------
8359	412	***-------
8385	412	***-------
8411	412	----------
8436	412	*---------
8462	412	----------
8488	412	**--------
8514	412	**--------
8540	412	**--------
8565	412	***-------
8591	412	****------
8616	412	*****-----
8643	412	*****-----
8669	412	*****-----
8694	412	****------
8720	412	***-------
8745	412	----------
8771	412	----------
8797	412	----------
8823	412	----------
8849	412	----------
8875	412	*---------
8900	412	*---------
8926	412	*---------
8951	412	**--------
8978	412	**--------
9004	412	*---------
9029	412	*---------
9055	412	*---------
9080	412	----------
9106	412	----------
9132	412	----------
9158	412	----------
9184	412	----------
9209	412	----------
9235	412	----------
9261	412	----------
9286	412	----------
9313	412	***-------
9338	412	***-------
9364	412	***-------
9390	412	***-------
9415	412	***-------
9441	412	----------
9466	412	----------
9493	412	----------
9519	412	----------
9544	412	----------
9570	412	----------
9595	412	----------
9621	412	**--------
9648	412	***-------
9673	412	***-------
9699	412	**--------
9724	412	**--------
9750	412	**--------
9776	412	**--------
9801	412	***-------
9828	412	***-------
9853	412	***-------
9879	412	*---------
9905	412	*---------
9930	412	----------

Great! I can see the volume.

Okay, so after I sang into the microphone, we got a bunch of volumes with stars and dashes, right? Yours will probably look different, but we get the same meaning behind it – which is that we can see the volume. Louder? More stars. Quieter? Less.

In my first version, the volume was high-low-high-low over and over again. The Arduino is so fast that it’s listening to the silence between pulses that our ears can’t even detect. Picture this – how do you tell the Arduino to “listen for the lengths of a beep” if the Arduino thinks “each and every beep is actually a lot of super short pulses bunched together”?

Let’s figure out stuff regarding the volume, first. To get around this I tried to smooth the input by averaging several samples in a row. We’re going to hit File > Examples > Analog > Smoothing. It makes nice “waves” in the graph that has no interruptions.

Off and On

Okay, let’s build a bit more into the code. I’d like the Arduino to report when there is and isn’t a beep. So, I’m going to wrap the output every 100 characters so that the Serial Monitor is still readable as the data is coming in.

                        // use a microphone to listen for morse code, then decypher the 
                        // message.
#define THRESHOLD (44)
#define MAX_SAMPLES (5)

int top=0;

int samples[MAX_SAMPLES];
int si=0;
int mi=0;
int total=0;

int c=0;

void setup() {
Serial.begin(57600);

for(int i=0;i<MAX_SAMPLES;++i) {
samples[i]=0;
}
}

void loop() {
int volume=analogRead(0);

total -= samples[si];
samples[si] = volume;
total += samples[si];
if( mi < MAX_SAMPLES ) mi++;
si = (si+1) % MAX_SAMPLES;

int average = total / mi;

if( top < average ) top = average;

int x = 10.0 * (float)(average-THRESHOLD)/(float)(top-THRESHOLD);
if(x<0) x=0;
if(x>10) x=10;

if(x>1) Serial.print('*');
else Serial.print(' ');

c++;
if(c>100) {
                     // wrap
c=0;
Serial.println();
}

delay(5);
}

When I play the Morse code into the microphone, this is what the output gave:

*****************************************************************************************************
*****************************************************************************************************
*****************************************************************************************************
**************************************************************           ************ *         *****
**********
                         // these three short beeps are when the sending Arduino reset, because 
                         // the speaker is on pin 13.
                         // there is a brief silence and then the message begins.                                                                                                                                            *********************************************************
**                                                       *********************                  *****
*************** *                ********************                  ***********************       
                                                 ********************                                
                                                                                                   **
**********************************************************                 *********************     
            ************************************************************                 ************
*********                                                       *************************************
***********************                  *********************************************************   
                 **********************************************************                          
                               ******************                    ******************              
       *********************************************************                                     
                  *************************************************************                ******
***************                                                       *******************************
******************************                 ********************                  ****************
******************************************                   *********************                   
                                   *********************                 *********************       
                                                  *********************                  ************
**********************************************                *********************                  
********************                                                                                 
                                                     ************************************************
**********                   **********************************************************              
   ************************************************************                                      
                *********************                    ********************                  ******
****************************************************                 ********************            
                                                                                                     
                     *********************                 ******************************************
****************                   *********************                                             
         *********************                 ***********************                               
                         *********************************************************                 **
*********************                  *********************************************************     
            *********************                                                          **********
************************************************                 ********************                
  **********************************************************                                         
                *********************                 *********************                 *********
************                                                                                         
                                             ********************                  ******************
**                  *********************                 ********************                       
                                ***********************                ******************************
******************************                                                      *****************
****                  ********************                    *********************                  
                                                                                                     
           ************************************************************                  ************
********                  ********************                                                       
***********************                                                       ***********************
*************************************               ********************* *                  ********
*************************************************                 *********************              
                                            ********************                  *******************
*                                                       *********************************************
***************                  ********************                  *********************         
                                              ********************                                   
                    ************************************************************                 ****
*****************                  ********************                                              
            ********************                 ****************************************************
********                  ********************                  *************************************
********************                 ***********************                *************************
***********************************

Okay, wow. A load of stuff that I can’t understand. What’s important to note here is that there are distinct sections of short and long. Let’s ‘clean it up in a text editor.

I copied everything into Sublime Text 2 and immediately took off the starting noise – the large block of stars before the message actually begins. I used regular expressions to find and delete all “\n” characters, then replaced all []+ with “\n”. We end up with a much more organized (albeit still unreadable) text:

***********************************************************
*********************
********************
*
********************
***********************
********************
************************************************************
*********************
************************************************************
*********************
************************************************************
*********************************************************
**********************************************************
******************
******************
*********************************************************
*************************************************************
*********************
*************************************************************
********************
**********************************************************
*********************
*********************
*********************
*********************
**********************************************************
*********************
********************
**********************************************************
**********************************************************
************************************************************
*********************
********************
**********************************************************
********************
*********************
**********************************************************
*********************
*********************
***********************
*********************************************************
***********************
*********************************************************
*********************
**********************************************************
********************
**********************************************************
*********************
*********************
*********************
********************
********************
*********************
********************
***********************
************************************************************
*********************
********************
*********************
************************************************************
********************
********************
***********************
************************************************************
*********************
*
*********************************************************
*********************
********************
********************
************************************************************
********************
*********************
********************
************************************************************
*********************
********************
********************
************************************************************
********************
*********************************************************
***********************
************************************************************

Check out our code again. We see that the delay between each stars is 5ms. Taking a look at our text, we see that the longer lines are about 60 stars – 300ms. A shorter line is about 20-22 stars. Let’s say 100ms. This pretty much matches what I’d expect according to the standard for Morse code, and it also tells me how fast the transmitter is transmitting.

There appear to be some odd outliers, such as 1 star lines, so let’s ignore all lines that are less than, say, four stars. So, I delete all 4 star or lower lines, then used regular expressions to replace [*]{17} with “~”. I’m going to delete all the rest of the [*]s. Then, I’m going to replace all “~~~” with “-”. Replace remaining “~”s with “.”. Delete the rest of the “\n” characters.

Okay! We’ve managed to condense the long text into the following:

-…..-.-.—..–.-.-….-..—..-..-…-.-.-.-……..-…-…-.-…-…-…-.-.-

Dashes and dots. Morse code! Now, we have to figure out where the letters and words are – or more specifically, where each one starts and ends.

Automaton

Let’s go through the whole manual process and make it automatic. Programming!

#define THRESHOLD (44)
#define MAX_SAMPLES (5)

#define BAUD (100.0)
#define WAIT (5.0)
#define AVG_SHORT (BAUD*1.0/WAIT)
#define AVG_LONG (BAUD*3.0/WAIT)
#define AVG_NEWWORD (BAUD*7.0/WAIT)
#define MINIMUM (AVG_SHORT/4.0)

int top=0;

int samples[MAX_SAMPLES];
int si=0;
int mi=0;
int total=0;

int c=0;
int is_on=0;

void setup() {
Serial.begin(57600);

for(int i=0;i<MAX_SAMPLES;++i) {
samples[i]=0;
}
}

void loop() {
findOnOffEvent();
}

void findOnOffEvent() {
int volume=analogRead(0);

total -= samples[si];
samples[si] = volume;
total += samples[si];
if( mi < MAX_SAMPLES ) mi++;
si = (si+1) % MAX_SAMPLES;
int average = total / mi;

if( top < average ) top = average;

int x = 10.0 * (float)(average-THRESHOLD)/(float)(top-THRESHOLD);
if(x<0) x=0;
if(x>10) x=10;

if(x>1) {
                               // noise!
if(is_on==0) {
                               // noise has just started.
if( c > MINIMUM ) {
                               // Was the silence a new word or a new letter?
if( c > (AVG_NEWWORD+AVG_SHORT)/2.0 ) {
Serial.println();
} else if( c > (AVG_LONG+AVG_SHORT)/2.0 ) {
Serial.print(' ');
}
}
                              // remember noise started
is_on=1;
c=0;
}
} else {
                              // silence!
if(is_on==1) {
                              // silence is new
if( c > MINIMUM ) {
                              // Was the noise a long or a short?
if( c > (AVG_LONG + AVG_SHORT)/2.0 ) {
Serial.print('-');
} else {
Serial.print('.');
}
}
                              // remember silence started
is_on=0;
c=0;
}
}

c++;

delay(WAIT);
}

This code splits everything up, so we get this output when we play it:

- .... .
-.-. --- ..- -. -.-. .. .-..
--- ..-.
.-. .. -.-. -.- ...
.... .- ...
-.. . -.-. .. -.. . -.. .-.-.-

Check it by hand! Do we get the same thing after we decode it?

Translator

Finally, I think I have everything in place and I can turn dashes and dots back into english letters.

#define THRESHOLD (44)
#define MAX_SAMPLES (5)

#define BAUD (100.0)
#define WAIT (5.0)
#define AVG_LONG (BAUD*3.0/WAIT)
#define AVG_SHORT (BAUD*1.0/WAIT)
#define AVG_NEWWORD (BAUD*7.0/WAIT)
#define MINIMUM (AVG_SHORT/4.0)

#define MAX_PATTERN (64)

#define NUM_CODES (54)
                      // 0 10 20 30 40 50
                      // 0123456789012345678901234567890123456789012345678901234
static const char *letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.,?'!/()&:;=+-_\"$@";
                      // each code represents a character.
                      // 1 represents a dot and 0 represents a dash.
                      // the order of these codes matches the order of the characters in 
                      // *letters.
static const char *codes[NUM_CODES] = {
"10",                 // A, codes[0]
"0111",               // B, codes[1]
"0101",               // C
"011",                // D
"1",                  // E
"1101",               // F
"001",                // G
"1111",               // H
"11",                 // I
"1000",               // J
"010",                // K
"1011",               // L
"00",                 // M
"01",                 // N
"000",                // O
"1001",               // P
"0010",               // Q
"101",                // R
"111",                // S
"0",                  // T
"110",                // U
"1110",               // V
"100",                // w
"0110",               // x
"0100",               // y
"0011",               // z
"00000",              // 0
"10000",              // 1
"11000",              // 2
"11100",              // 3
"11110",              // 4
"11111",              // 5
"01111",              // 6
"00111",              // 7
"00011",              // 8
"00001",              // 9
"101010",             // .
"001100",             // ,
"110011",             // ?
"100001",             // '
"010100",             // !
"01101",              // /
"01001",              // (
"010010",             // )
"10111",              // &
"000111",             // :
"010101",             // ;
"01110",              // =
"10101",              // +
"01110",              // -
"110010",             // _
"101101",             // "
"1110110",            // $
"100101",             // @, codes[54]
};

int top=0;

int samples[MAX_SAMPLES];
int si=0;
int mi=0;
int total=0;

int c=0;
int is_on=0;

char pattern[MAX_PATTERN];
int pi=0;

void setup() {
Serial.begin(57600);

for(int i=0;i<MAX_SAMPLES;++i) {
samples[i]=0;
}
for(int i=0;i<MAX_PATTERN;++i) {
pattern[i]=0;
}
}

void loop() {
int volume=analogRead(0);

total -= samples[si];
samples[si] = volume;
total += samples[si];
if( mi < MAX_SAMPLES ) mi++;
si = (si+1) % MAX_SAMPLES;
int average = total / mi;

if( top < average ) top = average;

int x = 10.0 * (float)(average-THRESHOLD)/(float)(top-THRESHOLD);
if(x<0) x=0;
if(x>10) x=10;

if(x>1) {
                                      // noise!
if(is_on==0) {
                                      // noise has just started.
if( c > MINIMUM ) {
                                      // Was the silence a new word or a new letter?
if( c > (AVG_NEWWORD+AVG_SHORT)/2.0 ) {
pattern[pi]=0;
findLetter();
                                      // new word, extra \n
Serial.println();
                                      // start counting - and . all over again.
pi=0;
} else if( c > (AVG_LONG+AVG_SHORT)/2.0 ) {
pattern[pi]=0;
findLetter();
                                      // start counting - and . all over again.
pi=0;
}

}
                                      // remember noise started
is_on=1;
c=0;
}
} else {
                                      // silence!
if(is_on==1) {
                                      // silence is new
if( c > MINIMUM ) {
                                      // Was the noise a long or a short?
if( c > (AVG_LONG + AVG_SHORT)/2.0 ) {
                                      // long
Serial.print('-');
pattern[pi++]='0';
} else {
                                      // short
Serial.print('.');
pattern[pi++]='1';
}
}
                                      // remember silence started
is_on=0;
c=0;
}
}

c++;

delay(WAIT);
}

                          // pattern contains the received longs and shorts,
                          // saved as 1s and 0s. Find the matching code in the list
                          // then find the matching printable character.
                          // print '?' if nothing is found
void findLetter() {
int i,j;
                          // go through all the codes
for(i=0;i<NUM_CODES;i++) {
                          // check if code[i] matches pattern exactly.
if(strlen(pattern) == strlen(codes[i]) && strcmp(pattern,codes[i])==0) {
                          // match!
Serial.print(' ');
Serial.println(letters[i]);
return;
}
}
Serial.print('?');
}

We’ve got everything split up, so let’s take a look at the final output.

-       T
....    H
.       E

-.-.    C
---     O
..-     U
-.      N
-.-.    C
..      I
.-..    L

---     O
..-.    F

.-.     R
..      I
-.-.    C
-.-     K
...     S

....    H
.-      A
...     S

-..     D
.       E
-.-.    C
..      I
-..     D
.       E
-..     D  
.-.-.-  .

I’m really enjoyed the first season of Rick & Morty. I hope the rest are at least as strange.

Questions

  • Want to seriously challenge yourself? use a large sample rate and no smoothing to find the frequency of each beep. Build an electric tuning fork for a friend’s piano!

Only registered users can comment.

  1. I have a question, the distance between lines on existing graphvolume for how many seconds / ms ?,
    I see the code
    In Sketch 1…
    “delay (25);
    } [/ Code]”
    In Sketch 2…
    “delay (5);
    } [/ Code]”
    In Sketch 3…
    “delay (WAIT);
    } [/ Code]”
    “if it could set the distance between lines on the graph through the code?