Skip to content

Week 4: Embedded Programming

hero shot
*Morse code input decoder.

Group Assignment

Find the complete group assignment here.

We began with a simple introdution to programming languages, which are most compatible and favorable for embedded programming. Then we continued with the most comman prcoessor families, and as examples looked into a couple of microcontroller units [or MCUs] and microprocessors.

Onik, our local instructor, gave us each an Arduino UNO, an LCD, a breadboard, and jumper wires. He specifically mentioned that we could use any tool we’d like, whether that’s an AI, textbooks, datasheets… anything, just to solve it ourselves. But there was a catch!

alt text

After hours and hours of search, us convincing AI tools that we need no potentiometers, and that the given set of items was enough we gave up. After that we found out that we either needed an LCD screen with an attached potentiometer, or we should have used more jumper wires and rigged the setup with a potentiometer.

alt text alt text

C++

#include <Wire.h>
#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x27, 16, 2);
// sets the I2C address for the columns and rows

void setup() {
  lcd.init();
  // starts the display

  lcd.backLight();
  // turns on the screen light

  lcd.setCursor(0,0);
  // moves cursor to top-left

  lcd.print("Hello, World!");
  // displays the text
}

void loop() {
  // Nothing else needed
}

alt text

XIAO RP2040

For me, all the procesor families seemed to be quite intimidating. ATiny, ARM, STM, AVR… followed by numbers only make it more complicated from the first glance. While I have heard of these before, I never really dedicated time to understand if there’s some grouping or logic behind them. I did not know if these names also included producer names, or just a series of chips… So let’s break down on the example of XIAO RP2040.

So, first off the word “xiao” is the Chinese word for “tiny.” Second off, it is the official product line name created by Seeed Studio.

The XIAO RP2040 has the Raspberry Pi microcontroller powering it. It packs two processor cores, Raspberry Pi’s official naming convention decodes RP2040 as a Raspberry Pi chip with 2 ARM Cortex-M0+ cores, 0 (M0+) processor type, 4 (264 KB) of RAM, and 0 internal flash storage.

alt text

Resistors

Resistors control the flow of electricity in a circuit to prevent components from getting too much power. Without them, sensitive parts like LEDs would pull in too much current and instantly burn out or break.

Because different components need different levels of electrical current, resistors come in various resistance. This resistance is measured in Ohms, denoted by the capital Greek letter omega [Ω]. The resistors have color bands around it, which help us identify its resistance ammount. Refere to the color table below.

alt text *Prompt4.1

Tinkercad

alt text Tinkercad Circuits is like a digital LEGO set for electronics, where you just drag, drop, and snap components together. It completely removes the fear of getting zapped or frying expensive parts because everything lives safely on your screen. If you make a wiring mistake, the worst that happens is a cartoonish graphics with no real-world damage.

Useful fact: Tinkercad is part of AutoDesk, which also owns Fusion 360, and means that it is not limited to circuts only. This also means if you end up using Tinkercad’s CAD software you can transfer the designs onto Fusion 360 for more complex manipulations.

alt text alt text

alt text

The same can be achieve using an Arduino UNO as the power source, or can be plugged onto a breadboard still using the development board as power source. alt text alt text

You can use Tinkercad for coding purposes too, but as I am simulating a simple LED with no specific command, the Ardunio UNO is used solely for the purpose of a power. The same is implemented in the image below, recreating the same setup in real life.

alt text

Arduino

Arduino is an open-source hardware and software company that designs and manufactures programmable circuit boards and developer tools. They act as an all-in-one ecosystem, providing both the physical microcontrollers and the environment to program them.

You might recognize Ardunio by their flagship microcontrollers Arduino UNO, it is actually quite old technology today. Because Arduino is completely open-source, other brands freely use their blueprints to manufacture nearly identical, cheaper clone boards, as well as highly upgraded versions with built-in Wi-Fi and faster processors.

Arduino IDE

alt text Arduino IDE [Integrated Development Environment] is an application, or simply put – a code editor, where you write, compile, and upload the code onto your microprocessor. The IDE primarily uses C++, though it also fully supports standard C, though it uses a simplified custom wrapper around C/C++ to make coding easier [for beginners].

When you first open the application, the IDE greets you with a default code structure:

C++

void setup() {
  // put your setup code here, to run once:
}

void loop() {
  // put your main code here, to run repeatedly:
}
Commands to know:

setup() function runs exactly once when the Arduino powers on or resets. It is used to initialize variables, pin modes, and libraries before the main code starts

loop() function runs continuously in an endless cycle immediately after the setup() function finishes, it contains the main logic of your project, such as reading sensors and controlling outputs

return type tells the computer whether that answer will be a number, a word, or nothing at all, in this case void is the return type – returns nothing


Serial Monitor [Magnifying Glass Icon]: opens a text window that displays data sent from your MPU via the Serial.print() command, you can also type text here to send commands back to the board.

Serial Plotter [Graph Icon]: graphically plots incoming numerical data in real-time,useful for visualizing changing sensor data, like wave patterns or acceleration.

Individual Assignment

Years ago, before I’d even know how to write code, I came across the binary tree representation of the Morse Code. It looked a bit complicated, but once I knew how to traverse through tree nodes the representation was more than logical. The Code itself is comprised of dots and dashes, and by mixing these you get different results.

My name in Morse Code:

H → .... // left > left > left > left
R → .-. // left > right > left
A → .– // left > right
C → -.-. // right > left > right > left
H → .... // left > left > left > left

alt text *Prompt4.2

The logic behind the tree representation is simple – the parent nodes are prioritezed to have the most frequently occuring letters. Naturally, we start with E and T. To get a letter/characters we traverse down the tree as follows: dots to the left, dashes to the right.

The Components

The make a Morse Code decoder you need:

1x Arduino UNO

1x LCD
1x LED

2x 220Ω resistors
1x 10kΩ resistor
1x 100kΩ potentioemeter
1x 0.03Ω button

?x male-to-male jumper wires

The Code

As I have learned coding in university, this was not difficult for me. Nevertheless, as we had learned the basics of procedural programming in Java, which has the same roots as C/C++, hence the syntax was quite understandable. I was only left to prompt the logic and the pin addresses I had used to connect the Ardunio UNO to the rest of the components.

Please refer to the resources section for the complete code file.

Block 1: Library & Pin Setup

C++

#include <LiquidCrystal.h>
const int rs = 12, en = 11, d4 = 5, d5 = 4, d6 = 3, d7 = 2;
const int ledPin = 8;
const int buttonPin = 9;
Imports the LCD screen library and assigns names to the physical Arduino pins — which pin controls the LED, which reads the button, and which ones talk to the screen.

Block 2: Display Tracking Variables

C++

int inputCol = 7;
int outputCol = 8;
String outputText = "";
Tracks where to print next on the LCD. inputCol is for the dots/dashes row, outputCol for the decoded letters row. outputText saves all decoded letters.

Block 3: Timing Variables

C++

unsigned long dotDuration = 0;
unsigned long dashDuration;
unsigned long nextCharDuration;
unsigned long spaceDuration;
unsigned long startTime, pressTime, releaseTime;
Stores time measurements in milliseconds. These are used to tell apart a dot vs. a dash, a letter gap vs. a word gap, all based on how long you press the button.

Block 4: Morse Code Table

C++

struct MorseMap { const char* code; char letter; };
MorseMap morseTable[] = {
  {".-", 'A'}, {"-...", 'B'}, ...
};
An array pairing every Morse pattern, like ".-" with its letter A.

Block 5: Decoder Function

C++

char decodeMorse(String code) {
  for (int i = 0; i < 26; i++) {
    if (code == morseTable[i].code) return morseTable[i].letter;
  }
  return '?';
}
Scans the table above looking for a match. Returns the letter if found, or '?' if the pattern isn't recognized.

Block 6: Calibration in setup()

C++

lcd.print("Press 3 dots.");
for (int i = 0; i < 3; i++) {
  while (digitalRead(buttonPin) == LOW);   // wait for press
  startTime = millis();
  while (digitalRead(buttonPin) == HIGH);  // wait for release
  pressTime = millis() - startTime;
  totalDuration += pressTime;
}
dotDuration = totalDuration / 3;
dashDuration = dotDuration * 3;
Asks you to push the button 3 times and measures your dot speed. Then it calculates: dash is 3× your dot, letter gap is 3× your dot, space is 7× your dot. This makes the decoder adapt to your tempo.

Block 7: Button in loop()

C++

if (digitalRead(buttonPin) == HIGH) {
  startTime = millis();
  while (digitalRead(buttonPin) == HIGH);
  pressTime = millis() - startTime;

  if (pressTime < dashDuration) { lcd.print("."); currentMorse += "."; }
  else                          { lcd.print("-"); currentMorse += "-"; }
  inputCol++;
  releaseTime = millis();
}
Every time you press the button, it times how long you held it. Short press for dot, long press for dash. The symbol is then displayed on the LCD and added to currentMorse.

Block 8: Press gap

C++

unsigned long gapTime = millis() - releaseTime;

if (gapTime > nextCharDuration && gapTime < spaceDuration) {
  // decode the current letter
}
if (gapTime >= spaceDuration) {
  // insert a space (new word)
}
Determines how long you pause between button presses to decide whether you're still typing a letter, finished a letter, or finished a word.

*Prompt4.3

Output

Conclusion

This was my first time ever coming close to embedded programming. Years ago, I tried “learning” electronics on my own, but that ending very quickly, as I was bad at Googling sources.

At first, when we began the group assignment, the local lecture was very interesting and digestable, but when it came down to putting the knowledge into action I did not know what to do.

The very last minute, during our regional review, when I got the code working I regained my hope in learning electronics.

Resources

Source files:

Morse Code Decoder

Prompts

Prompt4.1 Create an illustration of a resistor color scheme. Use a light gray background for the colored section. The text showing the Ohm correspondences should be medium gray, whereas the text next to it using the color code (RGB 157, 157, 157).

Prompt4.2 Create a minimalist, monochrome visual of a binary tree representing the Morse Code alphabet. The design should be neat avoiding any color highlights as it will be on a website with light and dark modes. The tree should be organized by starting with E and T at the top. To find a character, the user will traverse the tree: a dot indicates a move to the left child, while a dash indicates a move to the right.

Prompt4.3 Create an Arduino program that merges a button-controlled LED with a 16x2 LCD display [pins 12, 11, 5, 4, 3, 2] to build a Morse code input decoder. The program should begin with a calibration phase in setup() where the user performs three button presses to define and store an average dot duration using millis(). In the main loop(), the code must measure subsequent button presses to control an LED and classify inputs as either a dot or a dash, where based on the initial calibration three dot durations are equal to a dash.