Skip to content

jashruth-k-a/MorseCodeX

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Morse Code Signal Monitor

MPCA Mini Project — Embedded Systems Simulation


What This Project Does

This project encodes and decodes Morse code. You type plain text (like HELLO), and the system converts it to Morse, blinks a virtual LED on screen, and draws a signal waveform. You can also go the other way — type dots and dashes and get the decoded text back.

What makes it interesting is how it's built. Instead of one simple Python script doing everything, it's split across four layers that each do one job:

  1. A Python GUI — what you see and interact with
  2. A C program — handles the Morse alphabet and timing math, written in ARM embedded style
  3. A Verilog module — simulates how a real hardware chip would generate the signal using clock cycles
  4. A Python bridge — translates between the C output and the Verilog output so the GUI can display both

All four layers talk to each other by reading and writing plain text files inside a shared/ folder. No networking, no imports across layers — just files.


How We Got Each File

This project was built by four members, each responsible for one layer:

File Who wrote it What they were asked to do
arm/main.c Member 1 Write ARM-style C firmware for encoding and decoding
arm/mem_map.h Member 1 Define memory-mapped register addresses and constants
verilog/morse_core.v Member 2 Build the hardware timing module in Verilog
verilog/testbench.v Member 2 Write a test harness that simulates SOS and dumps a waveform
bridge/sim_bridge.py Member 3 Build the translation layer between C, Verilog, and GUI
gui/morse_gui.py Member 4 Build the Tkinter dashboard with LED animation and waveform
run_all.bat Member 1 One-click setup and test script for Windows

Files and What They Do

Files you work with directly

gui/morse_gui.py        — The dashboard. Run this to start the app.
arm/main.c              — C firmware. Compile this to get the encoder/decoder binary.
arm/mem_map.h           — Header included by main.c. Defines registers and Morse types.
verilog/morse_core.v    — Verilog hardware module. Compiled by Icarus Verilog.
verilog/testbench.v     — Verilog test harness. Feeds SOS into morse_core and checks output.
bridge/sim_bridge.py    — Translation layer. Called by the GUI and usable on its own.
run_all.bat             — Windows batch script. Compiles, runs, and tests everything.

Files created automatically at runtime (inside shared/)

You never write these by hand. They are created when the program runs. This is how each layer leaves "notes" for the next one.

shared/input.txt              ← GUI writes the plain text you typed (e.g. "HELLO")
shared/morse_timing.txt       ← ARM C writes the pulse sequence (e.g. "HIGH 1", "LOW 3")
shared/morse_wave.vcd         ← Verilog writes the waveform dump (standard VCD format)
shared/output.txt             ← Bridge writes the final signal list for the GUI (e.g. "HIGH,100")
shared/vcd_verify.txt         ← Bridge writes "MATCH" or "MISMATCH" after comparing both layers
shared/morse_in.txt           ← GUI writes the dot-dash string when decoding (e.g. "... --- ...")
shared/morse_wave_decoded.txt ← Bridge writes pulse durations for ARM C to decode
shared/decoded.txt            ← ARM C writes the decoded plain text result

How It Works — Step by Step

Encoding (text → LED blink)

Say you type SOS and click Encode & Simulate:

  1. GUI writes SOS to shared/input.txt
  2. GUI runs morse_arm.exe --encode (the compiled C program)
  3. ARM C reads input.txt, looks up each letter in its Morse table, and writes the pulse sequence to shared/morse_timing.txt:
    HIGH 1
    LOW 1
    HIGH 1
    LOW 1
    HIGH 1
    LOW 3
    HIGH 3
    ...
    
  4. GUI runs iverilog and vvp to simulate the Verilog hardware — this produces shared/morse_wave.vcd
  5. The bridge reads both morse_timing.txt and morse_wave.vcd, converts the timing units to milliseconds, and writes shared/output.txt:
    HIGH,100
    LOW,100
    HIGH,100
    ...
    
  6. The bridge also compares the ARM timing against the Verilog waveform and writes MATCH or MISMATCH to shared/vcd_verify.txt
  7. GUI reads output.txt and animates the LED — yellow when HIGH, dark grey when LOW — and draws the waveform

Decoding (dots/dashes → text)

Say you type ... --- ... and click Decode:

  1. GUI writes ... --- ... to shared/morse_in.txt
  2. Bridge converts dots and dashes to pulse durations and writes shared/morse_wave_decoded.txt:
    HIGH 100
    LOW 100
    HIGH 100
    LOW 300
    HIGH 300
    ...
    
  3. GUI runs morse_arm.exe --decode
  4. ARM C reads morse_wave_decoded.txt, measures each HIGH duration (≤150ms = dot, ≥250ms = dash), builds up letters, and writes SOS to shared/decoded.txt
  5. GUI reads decoded.txt and displays the result

The C Firmware (arm/main.c + arm/mem_map.h)

The C code is written in ARM embedded style. The key trick is a compile-time switch in mem_map.h:

#ifdef ARM_TARGET
    // On real hardware: write directly to a memory-mapped address
    #define LED_REG  (*(volatile uint32_t*)0x40000000)
#else
    // On desktop: write to a file instead
    fprintf(morse_timing_file, "HIGH 1\n");
#endif

When you compile with gcc main.c, it runs in desktop simulation mode and writes files. The same code compiled with -DARM_TARGET on a real ARM board would toggle a hardware LED at address 0x40000000.

The Morse lookup table is a simple array of structs covering A–Z and 0–9:

const MorseEntry morse_table[] = {
    {'A', ".-"},  {'B', "-..."}, {'S', "..."},
    {'0', "-----"}, {'1', ".----"}, // ... all 36 entries
};

Command-line flags the binary accepts:

  • --encode → reads shared/input.txt, writes shared/morse_timing.txt
  • --decode → reads shared/morse_wave_decoded.txt, writes shared/decoded.txt
  • --encode-legacy and --decode-legacy → older direct-write mode, kept for compatibility

The Verilog Hardware (verilog/morse_core.v)

This simulates a digital circuit as it would behave on a real chip. It has two modes controlled by the mode input.

Encode mode (mode = 0):

  • Receives one symbol at a time via data_in (0 = dot, 1 = dash, 2 = letter gap, 3 = word gap)
  • Holds led_output HIGH for exactly 100 clock cycles per timing unit
  • Pulses done = 1 for one clock cycle when the symbol is finished transmitting

Decode mode (mode = 1):

  • Monitors signal_in (a stream of HIGH/LOW pulses)
  • Counts how many clock cycles signal_in stays HIGH
  • If the count is ≤ 200 → it's a dot, dot_out fires
  • If the count is ≥ 201 → it's a dash, dash_out fires
  • If signal_in stays LOW for > 200 cycles → letter_done fires
  • If signal_in stays LOW for > 401 cycles → word_done fires

The timing thresholds are parameters at the top of the file, easy to read and change.

The testbench (testbench.v) is a self-contained simulation. It:

  • Feeds S (dot dot dot) then O (dash dash dash) then S again into encode mode
  • Then feeds a full SOS pulse stream into decode mode
  • Counts detected dots, dashes, and letters
  • Prints PASS if the counts match what's expected, FAIL if not
  • Saves the full waveform to shared/morse_wave.vcd

The Bridge (bridge/sim_bridge.py)

The bridge has three jobs:

1. Convert units. ARM C writes timing in units (1, 3, 7). The GUI needs milliseconds. The bridge multiplies: 1 unit = 100ms.

ARM file:   "HIGH 3"   →   output.txt:   "HIGH,300"
ARM file:   "LOW 7"    →   output.txt:   "LOW,700"

2. Parse the VCD waveform. VCD files use picoseconds and a symbol-based format that nothing else understands directly. The bridge finds the led_output signal in the VCD, reads its transitions, and converts them to (state, milliseconds) pairs the GUI can use.

3. Verify the match. After both ARM C and Verilog run, the bridge compares their HIGH pulse durations. If they match within 15% tolerance, it writes MATCH to vcd_verify.txt. Otherwise it writes MISMATCH.

The bridge also exposes plain Python functions (encode_morse, decode_morse) used by the GUI as a fallback when GCC or Icarus Verilog are not installed.


The GUI (gui/morse_gui.py)

Two tabs: Encode and Decode.

Encode tab has:

  • A text input box
  • An "Encode & Simulate" button that runs the full pipeline
  • A circular LED — animates yellow (HIGH) and dark grey (LOW) as each pulse plays
  • A Morse text label showing the dot-dash pattern like ... --- ...
  • A waveform canvas — green bars for HIGH, gaps for LOW, time in ms on the x-axis
  • A VCD verify badge — green MATCH or red MISMATCH

Decode tab has:

  • A Morse input box (type dots and dashes with spaces between letters)
  • A "Decode" button
  • A scrollable log that appends each decoded result

All subprocess calls (compiling, running ARM binary, running Verilog) happen in a background thread so the GUI doesn't freeze. Results are passed back to the GUI thread using root.after(). If the binaries aren't found, the GUI automatically uses the Python functions in sim_bridge.py as a fallback.


Requirements

Tool What it's for How to get it
Python 3.8+ GUI and bridge python.org — standard library only, no pip needed
GCC Compile arm/main.c MinGW-w64 on Windows, or already installed on Linux/macOS
Icarus Verilog Simulate the Verilog files iverilog.icarus.com
GTKWave (optional) View the .vcd waveform file gtkwave.sourceforge.io

Check your tools are working:

python --version
gcc --version
iverilog -v

How to Run

Windows — the easy way

Double-click run_all.bat. It handles everything: creates folders, compiles the C code, runs a test SOS message through the whole pipeline, and prints the result.

Launch the GUI

python gui/morse_gui.py

Compile the C firmware yourself

cd arm
gcc main.c -o morse_arm

On Windows with MinGW:

gcc main.c -o morse_arm.exe

Run the Verilog simulation yourself

cd verilog
iverilog -o morse_sim testbench.v morse_core.v
vvp morse_sim

View the waveform in GTKWave

gtkwave shared/morse_wave.vcd

In GTKWave, add clk, led_output, done, dot_out, and dash_out from the signal list to see what the hardware is doing.

Use the bridge on its own (without the GUI)

python bridge/sim_bridge.py encode "SOS"
python bridge/sim_bridge.py decode "... --- ..."
python bridge/sim_bridge.py test

Timing Reference

Everything is measured in "units". 1 unit = 100ms = 100 Verilog clock cycles.

Signal Duration
Dot HIGH for 1 unit (100ms)
Dash HIGH for 3 units (300ms)
Gap between dots/dashes in the same letter LOW for 1 unit (100ms)
Gap between letters LOW for 3 units (300ms)
Gap between words LOW for 7 units (700ms)

Example — what SOS looks like in shared/morse_timing.txt:

HIGH 1    ← S: dot 1
LOW 1
HIGH 1    ← S: dot 2
LOW 1
HIGH 1    ← S: dot 3
LOW 3     ← letter gap (S is done)
HIGH 3    ← O: dash 1
LOW 1
HIGH 3    ← O: dash 2
LOW 1
HIGH 3    ← O: dash 3
LOW 3     ← letter gap (O is done)
HIGH 1    ← S: dot 1
LOW 1
HIGH 1    ← S: dot 2
LOW 1
HIGH 1    ← S: dot 3
LOW 7     ← word gap (end of word)

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors