Binary Tree Morse Decoder

by lingib in Circuits > Arduino

11593 Views, 113 Favorites, 0 Comments

Binary Tree Morse Decoder

cover.JPG
Binary Tree Morse Decoder

This instructable explains how to decode Morse Code using an Arduino Uno R3.

The decoder, which automatically adjusts to the send speed, is capable of decoding morse up to at least 80 words per minute.

The incoming code is displayed as text on your Arduino Serial Monitor (or TFT screen if fitted)

A tone oscillator has been included should you wish to practice sending morse.

The decoder features:

  • a 320 x 240 TFT display module [1]
  • a Goertzel digital bandpass filter for separating unwanted signals.
  • a “Binary Morse Tree” for decoding the signal
  • auto-speed tracking
  • an audible output when practicing morse
  • both incoming and outgoing text are displayed.

The following characters and symbols are recognised:

  • [A..Z]
  • [0..9]
  • [. , ? ' ! / ( ) & : ; = + - _ " @]

The estimated cost of the morse decoder shield, less the TFT display, is $25. [1]

Images

  • The cover photo shows a fully assembled unit
  • The video shows the decoder working

Notes

[1]

Parts List

The following parts were obtained from https://www.aliexpress.com/

  • 1 only prototype shield for Arduino UNO R3, 2.54mm Pitch

The following parts were obtained locally:

  • 1 only LM358 dual opamp
  • 1 only LED green
  • 1 only LED clip
  • 1 only electret microphone capsule
  • 1 only normally-open push-button
  • 1 only 8-pin DIP socket
  • 2 only 330 ohm resistors
  • 2 only 2K2 resistors
  • 5 only 10K ohm resistors
  • 2 only 56K ohm resistors
  • 2 only 1uF capacitor
  • 1 only 10uF capacitor

The following parts are optional:

  • 1 only 2.2 Inch TFT SPI LCD Display Module 240*320 ILI9341 with SD Card Slot for Arduino Raspberry Pi 51/AVR/STM32/ARM/PIC [1]
  • Morse key / push-button
  • 1 only BC548 NPN transistor
  • 1 only 1 inch speaker
  • 1 only 33K ohm resistor
  • 1 only 3.5mm mono plug (for morse key)
  • 1 only 3.5mm mono socket (for morse key)
  • 3 only 9mm M3 tapped nylon spacers
  • 1 only 130 x 68 x 44mm ABS plastic box
  • 5 only 2-pin right-angle connectors

The estimated cost of the morse decoder shield, less the optional TFT display, is $25. [1]

Notes

[1]

The parts list for the optional 320 x 240 TFT display module is listed in my instructable https://www.instructables.com/id/Arduino-TFT-Grap...

[2]

A morse key or sturdy push-button is required if you wish to use the sender.

Circuit Diagram

MorseCodeDecoder.jpg
TFT_Shield .jpg
shield2.JPG
shield3.JPG

Images

  • Photo 1 shows the circuit diagram for the morse decoder. The 330 ohm resistor in series with the morse key limits the D4 output current in the event of an accidental short to ground ... increasing its value decreases the audio output from the speaker. For this reason I have not added it to the shield but attached it directly to the morse-key jack for ease of adjustment.
  • Photo 3 shows the completed shield attached to an Arduino. No other components are required if the text is to be viewed on your Arduino “Serial Monitor”.
  • Photo 4 shows the decoder partially boxed. A hole has been cut in the lid for viewing the display. The speaker and microphone have been hot-glued to the case. Drill some speaker-holes in the lid before mounting the speaker. The center socket on the lid is for an extension microphone ... without this the decoder must be placed close to the speaker which is not always possible.
  • Photo 5 shows the TFT screen. Black electrical tape has been attached to the display edges ... this tape prevents light leakage and masks any misalignment between the display and the opening in the lid.

Important

[1]

Arduinos with a large USB connector require a layer of electrical tape between the USB connector and the Arduino shield. Accidental shorts are possible without the tape as the clearance is small. The tape is not required for Arduinos that have small connectors.

Theory

Morse Tree.jpg
Morse Tree 1.JPG
Morse Tree 2.JPG

Each morse code letter comprises a series of short and long duration tones called “dots” and “dashes”.

  • a dot (.) is 1 unit in length
  • a dash (_) is 3 units in length
  • the space between letter elements is 1 unit
  • the space between letters is 3 units
  • the space between words is 7 units

We can determine whether the incoming tone is dot or a dash by comparing its duration with a reference tone of 2 units in length.

  • a dot is less than 2 units
  • a dash is greater than 2 units

There are two distinctly different methods for decoding the incoming pattern of dots and dashes:

  • linear search
  • binary tree (also known as a dichotomic search)

Linear Search

One common method is to create an array of characters and their matching morse patterns. For example each of the following characters would be saved as:

  • A . _
  • B _ . . .
  • C _ . _ .
  • 0 _ _ _ _ _
  • 1 . _ _ _ _
  • 2 . . _ _ _

Each letter requires 6 cells ... 1 for the letter itself and 5 for the (.)’s and (_)’s. In order to do this we need a letters[36][6] character array with a total of 216 cells. Unused cells are normally filled with a zero or a blank.

To decode the incoming dots and dashes we must compare the dot/dash pattern of each incoming letter with our reference character patterns.

While this method works, it is extremely slow.

Say we have 26 letters (‘A’, ..’ Z’) and the digits (‘0’, ... ‘9’) stored in an array, then we must perform 36 searches, each with up to 5 sub-searches, which is a total of 36*5=180 searches to decode the numeral ‘9’.

Binary Tree

A binary search is far quicker as no searches are required.

Unlike the linear search, which requires both the character and the morse patterns to be stored, the binary tree only stores the characters which means that the array size is smaller.

I have split my binary tree (photo1) into two halves (photos 2 and 3) to make it more readable.

To find a character we move a pointer left each time we hear a dot and move the pointer right every time we hear a dash. After each move we halve the pointer distance for the next move ... hence the name binary tree.

To decode the letter ‘9’ ( dash, dash, dash, dash, dot) requires 5 moves ... 4 to the right, and 1 to the left which leaves the pointer directly over the ‘9’.

Five moves is significantly faster than 180 searches !!!!!

The binary character array is also smaller ... 26 letters and 10 numerals only requires a 64 x 1 line array. I’ve chosen to create a 128 character array so that I can decode punctuation.

Design Notes

FFT_no_signal.jpg
FFT_750Hz.jpg
7680_800_48_step.JPG
7680_800_48_sweep.JPG
Goertzel_numbers.jpg
microphone.JPG
speaker_220_ohms.JPG

Morse is difficult to decode in the presence of interfering signals. The unwanted signals must be rejected ... this requires some sort of filter.

There are many possibilities:

  1. Phase-locked loops
  2. Inductor-capacitor filters
  3. Resistor-capacitor active filters
  4. Digital signal processing such as Fast Fourier Transform, or the Goertzel filter.

Methods 1,2,3 require external components which are bulky.

Method 4 requires no external components ... the frequencies are detected using mathematical algorithms.

Fast Fourier Transform (FFT)

One method of detecting the presence of a tone in a complex waveform is to use the Fast Fourier Transform

Photo 1 shows how FFT (Fast Fourier Transform) divides the audio spectrum into “bins”.

Photo 2 shows how the FFT “bins” respond to a signal ... in this case 800Hz. If a second signal of say 1500Hz was present we would see two responses ... one at 800Hz and another at 1500Hz.

In theory a morse code decoder can be be made by monitoring the output level of a particular FFT frequency bin ... a large number represents the presence of a dot or dash ... a small number represents no signal.

Such a morse code decoder could be made by monitoring “bin 6” in photo 2 but there are a number of things wrong with this approach:

  • we only want one frequency bin ... the rest are wasted calculations
  • the frequency bins may not appear exactly on the frequency of interest
  • it’s relatively slow (20mS per Arduino loop()

Another method is to use a Goertzel filter.

Goertzel Filter

The Goertzel filter is similar to FFT but only has a single frequency bin.

Photo3 shows the frequency response of a Goertzel filter to discrete audio steps.

Photo 4 is a sweep of the same filter over the same frequency range.

I decided to “go” with the Goertzel algorithm as:

  • The Arduino loop() time using the Goertzel algorithm was 14mS (milliseconds) versus 20mS (milliseconds) for an FFT solution using the Arduino “fix_FFT” library.
  • It is easy to set the center frequency of a Goertzel bandpass filter.
  • The bandwidth is approximately 190Hz.

Photo 5 shows the numeric output from a 900Hz Goertzel filter when a tone is detected. I have set my tone threshold to a value of 4000 ... values above 4000 indicate a tone.

In theory you just need to tune your filter to a comfortable listening frequency. Unfortunately the audio output from my 1 inch monitoring speaker drops rapidly below 900Hz. To avoid any issues I’m using a filter frequency of 950Hz. The necessary formulas for calculating alternate filter frequencies are found in my code header.

Decoding

Decoding the dots and dashes is not as easy as it first looks.

Perfect morse is defined as:

  • dot = 1 unit
  • spaces inside letter = 1 unit
  • dash = 3 units
  • space between letters = 3 units
  • space between words = 7 units

To decode perfect morse we simply need a reference tone duration of 2 units

  • dot < 2 units
  • element space < 2 units
  • dash > 2 units
  • letter _space > 2 units
  • word_space > 6 units (i.e 3 x reference units)

This works for machine morse but in the “real world” :

  • the sending speed varies
  • the duration of each dots varies
  • the duration of each dash varies
  • the letters E,I,S,H,5 only contain dots which average to the dot duration
  • the letters T,M,O,0 only contain dashes which average to the dash duration
  • word gaps may not arrive
  • fading creates errors from which the decoder must recover.
  • corrupt signals due to interference

Letters containing only dots and dashes is partially solved if:

  • we estimate the reference duration until we have received a valid dot and a valid dash. I use 200 milliseconds which is valid if the send speed is between 6 WPM (words per minute) and 17 WPM. You may need to increase this value if you are learning morse. A speed table is included in the software.

Speed variations are solved if:

  • we perform a rolling average on each dot and each dash and
  • recalculate the reference duration after each symbol is received

Word gaps and word gaps not arriving are solved if we:

  • remember the time of the last trailing-edge (tone to no-tone) transition,
  • restart the algorithm after each letter,
  • calculate the elapsed time while waiting for the next leading-edge (no-tone to tone) transition and
  • insert a space if 6 time units have been exceeded.

Morse Oscillator

I initially tried some Piezo buzzers but found:

  • the frequency was fixed
  • the output frequency was too high for prolonged listening
  • the piezos tended to drift out of the Goertzel passband

I then tried driving an acoustic transducer with a 750Hz squarewave but found it had a resonance that filtered out the 1st and 3rd harmonics. Photo 6 shows the microphone amplifier output to a 750Hz square-wave ... we are seeing the 5th harmonic !!!

I then resorted to a using a small speaker. Photo 7 shows the microphone output to a 750Hz squarewave that was sent to a small speaker ... this time we are seeing the fundamental ... not the 5th harmonic. The Goertzel filter ignores any harmonics.

Notes

[1]

https://en.wikipedia.org/wiki/Goertzel_algorithm

https://www.embedded.com/the-goertzel-algorithm/

Software

blackman_window.jpg
100_3000_Goertzel.JPG
100_3000_Goertzel_with_blackman_window.JPG

Installation

  • Download the attached file MorseCodeDecoder.ino [1]
  • Copy the contents of this file to a new Arduino sketch
  • Save the sketch as "MorseCodeDecoder" (without the quotes)
  • Compile and upload the sketch to your Arduino

Software Update
23 July 2020

The following features have been added to the attached file "MorseCodeDecoder6.ino"

  • an "Exact Blackman" window [2]
  • a "Noise_blanker"

Adjustment:

  • increase your receiver audio level until the LED starts to flicker then back off
  • now tune your receiver until the LED flashes in step with the the incoming morse
  • the Noise_blanker has been set to ignore noise bursts up to 8mS (one loop time)
  • the Noise threshold can be adjusted by setting Debug=true and watching your Serial Plotter

Note

[1]

Set your Arduino Serial Monitor to 115200 bauds if you wish too view the text.

[2]

  • Photo 1 ... Exact Blackman window
  • Photo 2 ... Goertzel filter without Exact Blackman window
  • Photo 3 ,,, Goertzel filter with Exact Blackman window applied

Operation

Decoder

Place the unit next to your speaker when listening to morse.

  • The electret microphone capsule picks up the morse signal from your speaker.
  • The output of the electret microphone is then amplified 647 times (56dB) before being passed to the Arduino for processing.
  • A Goertzel digital bandpass filter extracts the morse signal from the noise.
  • Decoding is done using a binary tree.
  • The decoder output is displayed as text on a 320 x 240 pixel TFT display. It is also sent to your Arduino “Serial Monitor” if you don’t wish to use a display.

Morse Sender

A morse sender has also been included. This allows you to practice sending morse and works as follows:

  • A constant audible tone is generated on Arduino pin 4.
  • We hear this tone via the decoder’s loud-speaker whenever we press the morse-key.
  • The tone is set to the same frequency as the Goertzel filter which fools the decoder into thinking its listening to real morse ... whatever you send will appear as printed text on the display.

Your sending will improve as the decoder picks up common errors such as:

  • too much space between symbols. (example: Q pinted as MA)
  • too much space beween letters (example: NOW printed as NO W)
  • incorrect code

Summary

Decoder

This instructable describes how to make a morse decoder that converts morse code to printed text.

  • The decoder is capable of decoding morse up to al least 80 WPM (words per minute)
  • The decoder automatically tracks variations in received send-speed.
  • The text is displayed on your Serial Monitor (or on a 320 x 240 TFT display module if fitted) [1]

Sender

A morse sender has also been included

  • The sender helps you improve the quality of your morse sending.
  • The decoder confirms that what you have sent is correct

Cost of parts

The estimated cost of the morse decoder shield, less the optional TFT display, is $25.

  Click here   to view my other instructables.