
I find the history of Morse code fascinating. I admire those that are able to send and interpret Morse code messages at high speed.
Some time ago I saw a YouTube video about a Morse Code decoder that was based on a micro controller. I didn’t see the schematic or the code but I was very impressed that it was possible with relatively few components. I didn’t know how it was achieved but the puzzle of the design continued to percolate away in the back of my mind.
A Morse code decoder has a lot of elements that interest me. It covers radio history, codes, electronics and programming. Attempting to design one was an opportunity to learn about detecting data patterns, try out some case build ideas I had as well as learn a bit more about Morse Code.
I had an idea that it may be possible using an interrupt routine and using it to look at the time between triggered interrupts. I set myself a challenge to design one. Soon I would be hearing messages by spies in exotic locations, possibly in an attic, wearing trench coats and fedoras tapping out their secret messages to their HQ, or more likely not.
I’ll say straight up that I am not a developer or have electronics training. This is just a hobby and I have not written a lot of code for the Arduino. This design is unlikely to be the best out there. It’s just a result of the result of the challenge I set myself. I’m just documenting the project for fun and maybe inspire others to design their own projects.
Goal
In designing the decoder I wanted to achieve:
- The decoder to use a microphone as the input. Any Morse it hears is displayed as plain text on a display.
- Convert text from a clear source with little noise that is sent at speeds in excess of 20 wpm
- Use an Arduino Nano as the microcontroller, not because it is the best, but because it is the only one that I am familiar with and I don’t have a desire to learn others at the moment.
- Other parts should be common components or modules and not many of them.
- Use a 20 x 4 LCD for simplicity. I really like these displays even though they are a bit old school. In my opinion, the glowing backlight seems to have the right aesthetic for the project.
- Display to show
- Top line: The length of the latest received dot and the speed set and speed received
- Middle two lines: Plain text of the latest received message
- Bottom line: The latest received dots and dashes
- Manual mode allows user to set speed using a potentiometer
- Auto mode that automatically tracks sending speed.
- Potentiometer to set the input sensitivity
- An LED that lights when the microphone module activates, that is it is receiving a dot or dash. There is one already on the microphone module I used.
- An LED that illuminates when the processor believes it is receiving a dot or dash. I started with D13 as it was already on the module.
- Build into a wooden box made from old timber I had. Part of the build was trying to come up with a simple to build and interesting project enclosures.
- Paint the front and back panels black.
The project was partly about trying out some ideas with the case as well as the actual unit.
While it could do with a bit more development and tweaking, it is now at a point that it works reasonably well, at least where the audio doesn’t have too much background static and hiss. It decodes the Morse of this recreation of the messages from the Titanic on this YouTube video fairly reliably.
The prototype
I started with a simple Morse key that I made from a piece of scrap timber, bending a piece of metal strapping from around the packing of some large item we had purchased, a few screws and an old knob. Added a pulldown resistor and capacitor to reduce bouncing. I connected it to an LCD module and an Arduino Nano. After quite a bit of coding, well lots more thinking than coding, I was able to get it display a single character on the screen with input from the Morse key. Sending Morse using appropriate timing, even slowly is much harder than I expected. With that working I was encouraged and then added a standard audio module and gave up on the key. It was some time before I got any success with that, which brings me to an unexpected feature of the audio module.
The digital output of the audio module wasn’t what I expected
The audio module has two output pins; an audio output and a digital output. I was expecting the digital output would change when the audio tone of a beep was detected and change back when the tone ended. That’s not quite what I found. I have one of those really cheap data analysers that plug into my laptop. This is what I saw when I connected it up.

There are lots of ups and downs… a real lot. Perhaps it is changing at the frequency of the tone playing or at least at a ratio of the tone. If so that opens up further questions and ideas for exploration in the future. Meanwhile how do I get rid of those? I also had a look on the oscilloscope.

I tried adding a smoothing capacitor as they have in power supplies. I found 22uF worked but sometimes affected the first mark in a word resulting in the wrong character being displayed. What about a low pass filter? I don’t know much about them and I’ve never made one before. I found several sites that calculate the values of a resistor and capacitor for a low pass filter, but what frequency should I be aiming for? In the end I experimented with the output connected to an oscilloscope and Morse playing into the mic. The result is based on one of a few different capacitors I had and by tweaking a variable resistor. The waveform on the oscilloscope looks far from square, in fact it looks really bad.

It looks better on the data analyzer.

The text on the display Morse decoder was surprisingly accurate.
Prosigns. What are prosigns?
I was not aware of prosigns, but soon came across them on the Wikipedia Morse Code page. Prosigns are characters that have a value that is has a text equivalent that is more than one character. The most well known one outside of Morse operators is probably SOS. It is not designed to be sent as three different characters, rather as one long character, that is …—… The spacing between the S and O bits is the same spacing as the within the S and O bits. It seemed like a good idea to support SOS and if I was going to do that I may as well cover as many prosigns as possible. SOS also has the most marks (dots and dashes) of any I found, so the sketch supports up to 9 bit characters.
Wait, some characters can be a prosign or another character?
Yes they can. It looks like there have been different standards used that has resulted in some Morse patterns set to different characters or prosigns, for example -.–. can be KN or (
So far I have not been able to work out which is the best to use so I’ve taken the easy way out and display both. In this example <KN/(> is displayed.
Display update speed was important
When dots can be as short as 20-30 milliseconds the time to update the display becomes significant. Well the speed of the whole sketch probably becomes significant. I tried to improve the speed in the rest of the sketch as best I could, but I’m sure it could be improved further. Updating the display was definitely slow. I decided to try two methods to speed it up.
The first was to remove the I2C backpack from the display and connect it up directly to the Arduino. This meant more wiring and required another pot for the contrast and resistor for the backlight, but it was not that difficult. The display updated in about half the time. A good result.
The other idea was to try a different library. After reading this forum post I decided to give the hd44780 library a go. I was expecting significant code changes to get it to work, but instead I only needed to change a few lines at the start of the sketch. The result was another three times faster. That’s about 6 times faster with both changes. Not only that but the display seemed clearer. I suspect there is less clearing and overwriting text. This should make up for some of my less efficient code J
How do you calculate speed? Surely there is a single standard
To assist in setting the morse speed to ensure the timing is correct I’ve used the dot length in milliseconds to convert to words per minute. I based the conversion on the PARIS
Number of dot lengths per second = 1000 / dot length in ms
W = 1200 / T (ms)
Where: T is the unit time, or dot duration in milliseconds, W is the speed in wpm
Examples: 150ms = 8wpm, 100ms = 12wpm, 50ms = 24wpm, 20ms = 60wpm
It sounded simple enough but while there is standard timing, e.g. gaps between marks should be one dot length and dashes should be three dot lengths but that’s not always what is being sent. So the dot length may not reliably indicate how many words would actually be sent in a minute. I don’t know that it matters, but don’t know that it doesn’t.
A few notes about the code
The code is available in my Github repository https://github.com/garrysblog/Arduino-Morse-Code-Decoder
What could be improved?
Its structure could be improved. I am trying to write well structured code, but I’m not there yet.
It does not support some characters with homoglyphic diacritical marks, for example ä.
The automatic setting could be improved. If follows along with changing speeds ok, but it sometimes adds spaces when it shouldn’t, it also has trouble if it quickly changes for example when it switches between to senders that are using very different speeds.
The speed potentiometer can be set on the border of different speeds and the display flickers between two different speed numbers. Perhaps it would be better with a rotary encoder with the speed could be saved in EPROM memory so the setting is the same next time it is turned on. However, I do like the simplicity and usability of a potentiometer.
Auto speed sensing and noise handling also could be improved.
The hardware could be improved too. That method of filtering the incoming audio is not ideal.
Operation – twiddling the knobs
This is how I operate it:
- Place the microphone in front of the speaker sounding the Morse
- Adjust the input sensitivity so that the audio LED flashes in time with the sound of the Morse
- For manual operation, set the function to Manual
- Look at bottom line of the display and adjust the speed pot so that it is displaying both dots and dashes in the pattern I hear
- Look at the received and set speed numbers on the top line of the display and adjust so the set number is similar to the received number
- Look at the text being received. If necessary, tweak the speed setting to get spacing correct.
- For auto mode, simply set the function to Auto.
Switching to auto sets the speed to the current set speed and then tries to track the received speed by averaging the length of recent received dots. While it works ok, manual mode holds the speed to a more stable setting.
The case
The case has built from some old timber. Originally destined for wooden crates it had been sitting in a shed since the 1960s or 70s.



The electronics
There is a description of the wiring in the code, but I intend to also create a diagram that I will upload with the files in the Github repository.
Parts list
- KY-037 Audio module to Detects sound. Digital output pin goes low when the sound level exceeds set threshold.
- Arduino Nano
- 20×4 LCD display. I like green best, but most YouTubers seem to prefer blue.
- 10K linear pot to adjust contrast of the display.
- 150 – 270 ohm resistor to limit the current of the display backlight.
- 1 x LED to replace the one on the audio module. This was only needed to make the status visible on the front panel of the case.
- 1 x LED to display the status of the Arduino. The built-in LED on the Arduino can be used instead of this LED and resistor below.
- 1 x resistor to limit current to the LED above.
- Pot to adjust the audio threshold. The Audio module has a pot built onto the module and that can be used for testing. It is fiddly to use so adding one in parallel makes it much easier to use.
- 10K linear pot to adjust the speed of the incoming transmission.
- 6.8 uF tantalum capacitor for low pass audio filter.
- 270 ohm resistor for low pass audio filter.
- SPST switch for power on/off. Not used if powering by USB.
- SPDT switch to switch between manual and auto mode.
- I started with an I2C backpack on the LCD but it was not able to handle faster Morse. If a backpack is used the contrast pot and backlight resistor are not needed.
The circuit


Audio module
The Audio module I have does not have any markings but it appears to be a KY-037. I found a circuit diagram for it here http://arduinolearning.com/code/ky038-microphone-module-and-arduino-example.php. Other microphone modules may also work.
The audio sensitivity trimmer pot
The KY-037 has a 100K potentiometer to adjust the threshold where the output triggers from high (nothing detected) to low (sound detected). I found this potentiometer to be very difficult to adjust. I removed the potentiometer and replaced it with an 10K pot and 22K resistor in series. That made it far easier to use and placed the threshold setting at about the midway point. It’s tricky to remove the built-in pot. If I was doing it again I would try soldering a 10k pot and another resistor (maybe 39K) in series with the onboard pot as that would be much easier to wire up.
Audio module LED
There is an LED on the audio module that lights when the output goes low (receive). This is very useful for both testing and adjusting the sensitivity while in operation. When I installed it in the case, I removed the existing LED on the audio module and replaced it with an ordinary 5mm red LED mounted on the front panel.
Filtering the output
The best results I have got is by connecting a resistor between the audio out and Arduino input and a capacitor from the input to ground. The input to the Arduino looks a bit ugly on the oscilloscope but was the best for operation that I tried.
The received status LED
I added this LED as a way to diagnose issues, but found it to be very useful in operation. This LED lights when the interrupt is triggered and the code believes there is a dot/dash being sent. Under ideal conditions, both LEDs should be flashing at the same time and both only when there is sound from the speaker being monitored. It’s a good indicator that something is not set or working correctly.
The speed pot
I used a standard 10K linear pot. This is only used for manual mode.
Should you build it?
Well that’s up to you, but I’ll repeat what I said early on. Building projects is just a hobby for me. I’m not a professional or have professional experience. Keep that in mind. At the very least I hope the project gives you inspiration.
Hi Gerry,
Very nice wright up and build but I’m getting an error
( ‘include’ does not name a type) when trying to compile the sketch. I want to use the hd44780 library and my LCD doesn’t have a backpack. I’m sure that I haven’t uncommented the code correctly. I’ve included the reverent section of the code and the error. Garry would you please take a look at it?
Thank you!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
// ********************************** LCD library and setup *********************************
/*
// I2C LCD libraries and setup
#include // Wire for I2C communication to the LCD
#include // LCD I2C library for LCD functions
const byte I2C_ADDR = 0x3F; // Hexadecimal address of the LCD unit
LiquidCrystal_I2C lcd(I2C_ADDR, 20, 4); // Set the LCD address to 0x27 for a 20 chars and 4 line display
*/
/*
// LCD without I2C, using LiquidCrystal library
#include
// initialize the library with the numbers of the interface pins
const byte LCD_COLS = 20;
const byte LCD_ROWS = 4;
const byte rs = 7, en = 12, d4 = 11, d5 = 10, d6 = 9, d7 = 8;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
*/
// LCD without I2C using HD44780 library
include
include // Arduino pin i/o class header
const byte LCD_COLS = 20;
const byte LCD_ROWS = 4;
const int rs = 7, en = 12, d4 = 11, d5 = 10, d6 = 9, d7 = 8;
hd44780_pinIO lcd(rs, en, d4, d5, d6, d7);
// zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
// ******** Initialise LCD – Use method for your library and connection ********
// For HD44780 library (with a backpack?) **************
//lcd.begin(LCD_COLS, LCD_ROWS);
// Initialise the LCD and turn on backlight for LiquidCrystal library
// lcd.begin(); // Use with I2C backpack
// lcd.backlight(); // Use with I2C backpack
// For LiquidCrystal and HD44780 libraries without a backpack
lcd.begin(LCD_COLS, LCD_ROWS); // Use when not using a backpack
// zzzzzzzzzzzzzzzzzzzzzzzzzzzzz End of LCD initialisation zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
MorseCodeDecoder:106:1: error: ‘include’ does not name a type
include
^~~~~~~
MorseCodeDecoder:111:1: error: ‘hd44780_pinIO’ does not name a type
hd44780_pinIO lcd(rs, en, d4, d5, d6, d7);
^~~~~~~~~~~~~
C:\Users\xxx\Desktop\HAM\Arduino CW ID\Arduino-Morse-Code-Decoder-master\Arduino-Morse-Code-Decoder-master\MorseCodeDecoder\MorseCodeDecoder.ino: In function ‘void setup()’:
MorseCodeDecoder:287:3: error: ‘lcd’ was not declared in this scope
lcd.begin(LCD_COLS, LCD_ROWS); // Use when not using a backpack
^~~
MorseCodeDecoder:287:13: error: ‘LCD_COLS’ was not declared in this scope
lcd.begin(LCD_COLS, LCD_ROWS); // Use when not using a backpack
^~~~~~~~
C:\Users\xxx\Desktop\HAM\Arduino CW ID\Arduino-Morse-Code-Decoder-master\Arduino-Morse-Code-Decoder-master\MorseCodeDecoder\MorseCodeDecoder.ino:287:13: note: suggested alternative: ‘LCD_ROWS’
lcd.begin(LCD_COLS, LCD_ROWS); // Use when not using a backpack
^~~~~~~~
LCD_ROWS
C:\Users\xxx\Desktop\HAM\Arduino CW ID\Arduino-Morse-Code-Decoder-master\Arduino-Morse-Code-Decoder-master\MorseCodeDecoder\MorseCodeDecoder.ino: In function ‘void welcomeScreen()’:
MorseCodeDecoder:521:3: error: ‘lcd’ was not declared in this scope
lcd.home();
^~~
C:\Users\xxx\Desktop\HAM\Arduino CW ID\Arduino-Morse-Code-Decoder-master\Arduino-Morse-Code-Decoder-master\MorseCodeDecoder\MorseCodeDecoder.ino: In function ‘void displaySetSpeed(int)’:
MorseCodeDecoder:550:3: error: ‘lcd’ was not declared in this scope
lcd.setCursor(0, 0); // Set cursor to top left
^~~
C:\Users\xxx\Desktop\HAM\Arduino CW ID\Arduino-Morse-Code-Decoder-master\Arduino-Morse-Code-Decoder-master\MorseCodeDecoder\MorseCodeDecoder.ino: In function ‘void displayActualSpeed(int)’:
MorseCodeDecoder:578:3: error: ‘lcd’ was not declared in this scope
lcd.setCursor(16, 0); // Set cursor on top row
^~~
C:\Users\xxx\Desktop\HAM\Arduino CW ID\Arduino-Morse-Code-Decoder-master\Arduino-Morse-Code-Decoder-master\MorseCodeDecoder\MorseCodeDecoder.ino: In function ‘void updateTextDisplay(byte)’:
MorseCodeDecoder:610:7: error: ‘lcd’ was not declared in this scope
lcd.setCursor(0, 1);
^~~
MorseCodeDecoder:622:5: error: ‘lcd’ was not declared in this scope
lcd.setCursor(lastCharLoc, 2);
^~~
C:\Users\xxx\Desktop\HAM\Arduino CW ID\Arduino-Morse-Code-Decoder-master\Arduino-Morse-Code-Decoder-master\MorseCodeDecoder\MorseCodeDecoder.ino: In function ‘void displayMark(char)’:
MorseCodeDecoder:650:5: error: ‘lcd’ was not declared in this scope
lcd.setCursor(d, 3); // Set cursor to correct place on the last line
^~~
exit status 1
‘include’ does not name a type
LikeLike
Hi Peter, thanks for your interest in the project. I have to confess I’m not sure what is causing that error. However, if you are using an LCD without a backpack and the HD44870 library it should work without changing any of the code. That’s exactly the configuration I’m using and the default setup in the sketch.
Just to rule out a couple of other things I’ve tried recompiling and uploading using different methods. All seem to work ok. I’m using the standard Arduino IDE. Currently version 1.8.13. When I originally wrote the sketch it would have been an older version, not sure what one. If you are using the Arduino IDE we can probably rule that out.
I also tried ruling out possible changes with the HD44870 library. Originally I downloaded the library from GitHub https://github.com/duinoWitchery/hd44780 and placed it in my Arduino library folder. I used v1.1.0. I just did the same with the same with the latest version 1.3.2 and it was ok. I also tried installing the library using Library Manager, selecting HDD44870 by Bill Perry Version 1.3.2 and that worked to.
These have all been good things for me to check as it confirms for me that it works the same with current versions of the IDE and library.
I recommend checking that you have the correct library selected and grabbing the Morse Decoder code again from GitHub and trying to compile it as is and see if you get any errors.
LikeLike
Hi Garry, I built your project, because I like some of the features (especially the display of the recognized morse code in the bottom line of the display). I would like to let people try to enter morse code with a simple morse key, so I am not using the microphone board. I thought it could be done by activating an internal pullup resistor using “pinMode(INPUT_PIN, INPUT_PULLUP);”, but while the Arduino detects a “key down” event (pin2 connected with ground), it does not always seem to detect “key up”, although the routine morse_ISR() should detect all changes (up/down). Can you give me any advice about how to modify the code ?
LikeLike
Hi Frank. Thanks for your interest. When I started working on the project I started with just a simple home made Morse key I made and it wasn’t until after that that I swapped to the microphone. Having said that I don’t know Morse so my tests were very slow and there have been a lot of code changes since then. I wonder if what you are seeing is bounce? When I set up the key I had a 100nF capacitor across the key contacts and a 10K resistor between the key and the input pin for debouncing. You could try adding those to start with and see how that goes.
The code is also attempting to be smart and ignore really fast and really slow changes. With a key you could try removing those. The easiest way to remove the short one is to change minDuration to 0 on line 364. Alternatively if you are having bouncing with the key you could try increasing it.
The maximum length of a dash is set on line 367 with the maxDashDuration variable. You could try increasing that value.
I’m interested to hear how you go.
LikeLike
Hi Garry, thanks a lot. I played with different settings of minDuration, but noticed, that debouncing the morse key is required. But when I increased maxDashDuration (25 * dotDuration), I got a much better decoding. So the standard settings expect a very clean code, which I cannot enter, because I am also not familiar with morse. But with a more relaxed setting it is working now.
In the future I would like to let visitors on a museum ship try to write some characters on the display (“interactive museum”). I was surprised to find out, that there is still interest in this first “digital data transmission” and that visitors have fun trying it. So I would like to give them such an opportunity – especially with your software, which displays both characters and dots and dashes simultaneously. If you let me know your email address (mine is frpl at gmx dot de), I might be able to send you pictures later this month.
Thanks again, Frank
LikeLike