Introduction
Getting started with Arduino programming can seem like a daunting task. In addition to learning the ins and outs of writing Arduino code in C, you usually also have to assemble electronic parts using a breadboard. This can feeel overwhelming to an experienced programmer who's trying to take his or her first step into the Arduino world.
Fortunately, Arduino boards (and Arduino clones) have a built-in LED that will let us flash out messages using Morse code. Using the built-in LED, we can get a simple Arduino project up and running without any extra hardware. You can even use Tinkercad's Arduino simulator to do this project without any hardware at all!
One important thing I'll note up front: the code in the article won't be as efficient as possible. It's intended to be simple for novice users to understand and step through. As you read, it's likely you'll think of better or more efficient ways to accomplish the same tasks. And that's great! I strongly encourage you to write your own version of this project that describes other ways of making it work.
What You'll Need
To complete this project, you'll need an Arduino, Arduino Nano, or an Arduino clone. To program your Arduino, you'll need to download and install the free Arduino IDE.
Alternately, you can sign up for an account at Tinkercad and use its built-in Arduino simulator.
Background
Arduino applications can be written using C or C++. We'll be using plain C, as that's what is used by most Arduino projects you'll encounter in the wild.
Don't worry if you're not familiar with C. We'll be writing the code in small pieces that you can enter a line at a time. There will be parts you'll probably want to copy and paste. While there's definitely value in typing out the program's main loop to make sure you understand what's happening, it's okay to copy and paste the character mappings and constant definitions.
The Project
The idea behind this project is simple: we want to use our Arduino's built-in LED to flash out a message of your choice using Morse code. We won't spend a great deal of time here going into exactly what Morse Code is, but to borrow a line from Wikipedia:
Quote:
Morse code is a method of transmitting text information as a series of on-off tones, lights, or clicks that can be directly understood by a skilled listener or observer without special equipment.
In this case, we'll be turning an LED on and off to transmit letters. If you're interested, read the entire Wikipedia article to learn all about Morse code and its fascinating history.
Using Morse code, letters are encoded using a series of dots and dashes. A dot represents and short signal, and a dash represents a long signal. There's no set length of time for dots and dashes. Instead, they are defined relative to a unit of time. The rules are:
- A dot's length is equal to one time unit
- A dash's length is equal to three time units
- The space between parts of a letter is one time unit
- The space between letters is three time units
- The space between words is seven time units
For example, assume we choose 250 milliseconds as our unit of time. In this case, each dot will be 250 milliseconds long, and each dash will be 750ms long. Each part of a letter is separated by a space 250ms, letters will be separated by a space of 750ms, and each worrd will be separated by 1150ms. When we're using a light or LED to send Morse code, the light will be on when we're sending a dot or dash, and off when we're sending a space.
To figure out how to send a message, let's take a look at the internation Morse code alphabet:
(thanks to Wikipedia for the public domain alphabet image)
As we can see, each letter or number is represented by a unique sequence of dots and dashes.
That sounds easy enough! Let's take a look at the code we'll need to write to make this work.
The Code
If you're using physical Arduino hardware, open up the Arduino IDE, and then plug your Arduino into your computer's USB port. You might have to click on the Tools menu and scroll down to Port to set your Arduino's port correctly.
If you're using Tinkercad, load up the Tinkercad dashboard. In the menu at the left side fo the screen, select 'Circuits' and then click on the 'Create New Circuit' button. When the circuit workspace appears, find 'Arduino Uno R3' in the parts list and drag in on to the workspace. Next, click the 'Code' button near the top right of the screen. The code screen will open up in blocks mode, which is not what we want. Click on the dropdown that says 'Blocks' and change it to 'Code'. This will display a code editor that will let us enter our code.
We're now ready to start coding! If there's any code visible in your editor, erase it. We'll be starting from scratch.
First, we'll definite the set of characters we will need to support:
const char* characters = "abcdefghijklmnopqrstuvwxyz0123456789";
Next, we'll add a set of mappings so once we've found the position of the character we want, we can look up its morse code symbols:
const char* mappings[] = {
".-\0",
"-...\0",
"-.-.\0",
"-..\0",
".\0",
"..-.\0",
"--.\0",
"....\0",
"..\0",
".---\0",
"-.-\0",
".-..\0",
"--\0",
"-.\0",
"---\0",
".--.\0",
"--.-\0",
".-.\0",
"...\0",
"-\0",
"..-\0",
"...-\0",
".--\0",
"-..-\0",
"-.--\0",
"--..\0",
"-----\0",
".----\0",
"..---\0",
"...--\0",
"....-\0",
".....\0",
"-....\0",
"--...\0",
"---..\0",
"----.\0",
};
At first glance, there's something you might dislike about this code: finding the index of the character we want the Morse code symbols for will require a O(n) (linear-time) lookup for each character in our message. That's not very efficient! What we'd really like here is a hash table, or std::map from the C++ Standard Library.
The Arduino development environment doesn't provide the C++ Standard Library, though, and writing our own hash table implementation would make this novice level tutorial too complicated. As it turns out, in this simple project, the inefficient linear time lookup doesn't matter. But keep in mind that when you're writing applications for an Arduino or other microcontroller that might be running on battery power, you'll want to give careful thought to your application's algorithms and data structures to ensure you don't use more CPU cycles than you really need.
Also note that we're being careful to null-terminate all of our strings. This is important, because we'll be using strlen
to determine their length, and using strlen
on a string that isn't null-terminated results in undefined behaviour. In our application, this would likely result in the Arduino locking up and needing a reboot.
Next, let's decide what our base time unit will be. This is important, because the length of dots, dashes, and spaces will all depend on what we choose. If we pick a length of time that's too small, the dots and dashes will blink so quickly that they'll be difficult to see. If we pick a length of time that's too large, we might die of old age before we finish flashing the LED to display our message.
Since all of the Arduino pin and delay commands accept time units in milliseconds, it'll make things easier for us if we define our Morse code time units in milliseconds as well. So let's start by defining a single unit of time as 250 milliseconds. That seems reasonable. It's long enough that dots will be clearly visible, but short enough that dashes won't seem to take forever to display. In your code editor, add the following line:
const int TIME_UNIT = 250;
This gives us a time unit upon which all of our other units will be based. Next, based on the Morse code rules we entered above, add the following code:
const int DOT = TIME_UNIT;
const int DASH = 3 * TIME_UNIT;
const int SYMBOL_SPACE = TIME_UNIT;
const int LETTER_SPACE = 3 * TIME_UNIT - SYMBOL_SPACE;
const int WORD_SPACE = 7 * TIME_UNIT - LETTER_SPACE;
Note that when we define the space between letters, we're subtracting the space between symbols. And when we define the space between words, we're subtracting the space between letters. We're doing this because every time we display a symbol (a dot or dash) we're also going to add a space - that is, after every symbol we show with the LED, we're going to leave the LED off for one unit of time.
So there's already one time unit worth of delay baked in to every symbol. To ensure that the space between letters is correct, when we define LETTER_SPACE, we're subtracting the time used for a SYMBOL_SPACE. The same reasoning applies to the relationship between LETTER_SPACE and WORD_SPACE.
Next, let's define the message we'll be turning into Morse code:
const char* message = "codeproject\0";
And that's it for the definitions! Now we're ready to write the code that makes our Morse code flasher work. First, we'll add a setup function that sets pin 13 to output mode. Pin 13 controls the Arduino's built-in LED.
void setup()
{
pinMode(13, OUTPUT);
}
If you're familiar with C, you'll probably be wondering where that OUTPUT constant came from. How can we use it if we haven't defined it anywhere? The answer is that the Arduino development environment automatically imports many useful functions and constants. The setup
function will be automatically called one time when your program first starts up.
Next, we'll add the code that actually flashes the LED:
void loop()
{
int size = strlen(message);
for (int i = 0; i < size; i++)
{
const char* ch = strchr(characters, tolower(message[i]));
if (ch != NULL)
{
int index = (int)(ch - characters);
const char* morseSymbols = mappings[index];
int count = strlen(morseSymbols);
for (int i = 0; i < count; i++)
{
digitalWrite(13, HIGH);
int symbolTime;
char symbol = morseSymbols[i];
if (symbol == '.')
symbolTime = DOT;
else
symbolTime = DASH;
delay(symbolTime);
digitalWrite(13, LOW);
delay(SYMBOL_SPACE);
}
delay(LETTER_SPACE);
}
}
delay(WORD_SPACE);
}
In Arduino programs, the loop
function is called continually. In our program, each run through this function will result in our message being displayed once.
Let's step through the program to see what's going on.
We start by checking the length of our message, and then we begin a loop that steps through the message one character at a time.
We then call strchr
to find the current character within the list of characters we support. If the current character is one that we don't support, strchr
will return null and we'll skip to the next character. Note that we call tolower
on every character we're looking up. tolower
is a C function that, as its name implies, converts upper case characters to lower case. This will let us handle message strings that contain both lower and upper case characters. It's okay to convert everything to lower case because Morse code doesn't distinguish between letter cases.
If strchr
does fien the character, we jump into the block of code contained by the if (ch != NULL)
statement. The first thing we do here is calculate index
, which finds the current character's index within the characters
array. Because the Morse code letters in mappings
are the in same order as the ASCII letters in characters
, we'll be able to use index
to find the Morse code symbols for the current letter we're working on.
If the way index
is being calculated looks a bit strange or confusing, don't worry. We're just doing a bit of pointer arithmetic. I usually really dislike tutorials that say "it works, just trust me!". But in this case, I want to avoid turning a Morse code tutorial into a tutorial on pointers and pointer arithmetic! I'll just ask you to trust me on one thing: there's nothing magic going on here. Even if you don't have a great understanding of pointers right now, you'll be able to learn them. Then, you'll be able to look back on this code you've written and understand it more deeply.
After we've calculated index, we call strlen
, which tells us how long the current Morse code letter is. We'll need to know this so we can loop through it one character at a time and flash the LED with dots and dashes.
Next, we create a char
pointer called morseSymbols
. It just points to the Morse code symbols for the current letter inside the mappings
array. We don't strictly need this pointer. But a few lines from now, having this pointer will let us refer to morseSymbols[i]
instead of mappings[index][i]
. In terms of functionality, they're equivalent, but I strongly prefer code that's easier to understand.
After we create morseSymbols, we use strlen
to assign a count
variable that represents a count of how many individual symbols (dots and dashes) the current Morse letter contains. We then use this count variable to loop through all of the symbols in the morseSymbols
array.
Looking inside the loop, we can see that the first thing we do is call digitalWrite(13, HIGH)
. digitalWrite
is a built in function in the Arduino development environment, and HIGH
is a built-in constant. this function call turns on the Arduino's LED.
Now that the LED is on, we want to wait for a bit of time before we turn it off. Just how long we want to wait depends on whether we're trying to transmit a dot or a dash. So after we turn the LED on, we check whether the current symbol is a dot or a dash. If it's a dot, we call delay(DOT)
. If it's a dash, we call delay(DASH)
. As you'd expect, delay
causes the Arduino to pause execution of the program it is running. Delay takes a single argument: an integer that tells the Arduino how many milliseconds to pause for.
After we've waited the appropriate length of time for our dot or dash, we then call digitalWrite(13, LOW)
. This turns off the LED. Finally, we call delay(SYMBOL_SPACE)
. The net result of these four lines of code in the inner loop is to turn the LED on, wait for either 250ms (DOT
) or 750ms (DASH
) , and then turn the LED off and wait for 250ms (SYMBOL_SPACE
).
After we've flashed the LED to show all of the symbols in a letter, the inner loop completes and we call delay(LETTER_SPACE)
.
Then, after we'll displayed all of the letters, we call delay(WORD_SPACE)
.
Once the loop
function completes, the Arduino calls loop
again and starts the whole process from the beginning. Our Arduino will continue flashing our Morse code message until the end of time, or until we unplug it.
To run this code on your Arduino, upload button in the Arduino IDE. It looks like a green arrow:
The code will upload to your Arduino, and you'll see the SOS morse code flashing.
If you're using Tinkercad, click on the 'Start Simulation' button to start your program.
And...that's it! We've programmed our Arduino to send an SOS signal. It was a fairly simple project, and it only uses a tiny fraction of the Arduino's abilities. It's usually best to start small, though. Now that you've had a taste of Arduino programming, you might enjoy my articles on creating a binary counter using Arduino hardware or Tinkercad.
Next Steps
You might notice that our Morse code flasher has some flaws. It doesn't do anything to handle messages with spaces in them. And although our linear-time lookup is inefficient, we could mitigate this problem by doing the conversion from ASCII to Morse code once, when the program first starts. With there shortcomings in mind, here are a few small additional challenges you could try:
- Modify the program to handle Morse code messages that contain spaces.
- Modify the program to translate the ASCII characters once, at program startup. The
setup
function would be the ideal place to do this, since the Arduino will only call it once. - Implement a hash table in C, or find a pre-made hash table implementation you like. Modify the program to use a hash table to map the ASCII characters to Morse code characters instead of doing an array lookup.
- Acquire a breadboard, a speaker, some resistors, and some jumper wires. Create a simple circuit that connects the speaker to the Arduino, and modify the program to play the Morse code message using the speaker instead of flashing it out via the LED. Or, for even more fun, make your Morse code message play via the LED and the speaker at the same time!