👉 Version française disponible ici : README_FR.MD
This project implements a hardware and software Morse decoder based on an ATmega328P microcontroller, allowing:
- input of dots and dashes using an external Morse key,
- precise measurement of press and silence durations,
- conversion of the Morse sequence into a binary representation,
- lookup of the corresponding character in a constant array of structures stored in FLASH memory (PROGMEM),
- real-time display of the decoded text on a 20x4 I2C LCD,
- dynamic adjustment of the Morse sending speed.
The project is intentionally designed to be low-level, compact, and educational, with no dynamic memory allocation, no interrupts, and a controlled use of AVR memory resources.
- 1. General Overview
- 2. Hardware
- 3. Schematic and PCB
- 4. Software Operating Principle
- 5. Morse Timing Management
- 6. Morse Acquisition
- 7. Morse Code Storage in FLASH Memory
- 8. Decoding: Binary Conversion and PROGMEM Reading
- 9. LCD Display
- 10. simpleBouton Library
- 11. Acknowledgements
The principle relies exclusively on time measurements:
- A short press on the Morse key corresponds to a dot.
- A long press corresponds to a dash.
- Dots and dashes are stored in binary form:
1= dot0= dash
- A sufficiently long pause triggers the decoding of a character.
- An even longer pause triggers the display of a space.
The project uses:
- no interrupts,
- no hardware timers,
- no dynamic memory allocation.
All logic is based on millis().
- 5 V power supply
- Input via DC jack
- Series diode 1N4007 for reverse polarity protection
- ATmega328P-PU
- 16 MHz crystal
- Capacitors:
- 2 × 22 pF (oscillator)
- 2 × 100 nF (VCC / AVCC decoupling)
- RESET resistor: 10 kΩ to VCC
- I2C interface (PCF8574)
- Address used:
0x27 - Power supply: 5 V
Connections:
- SDA → PC4 (Arduino A4)
- SCL → PC5 (Arduino A5)
LiquidCrystal_I2C lcd(0x27, 20, 4);
The Morse key is connected via a PJ-307C jack
Documentation: https://www.lcsc.com/product-detail/C16684.html
Usage is strictly equivalent to a push button:
- Tip → PD2 (Arduino D2)
- Sleeve → GND
Pin PD2 is configured as INPUT_PULLUP.
Behavior:
- idle →
HIGH - pressed →
LOW
- PLUS button → D6 (PD6)
- MINUS button → D7 (PD7)
Wiring:
- button between pin and GND
- internal pull-up enabled
- Output D3 (PD3)
- Direct drive
- Active only while the Morse key is pressed
| Function | Arduino | ATmega328P |
|---|---|---|
| Morse Key | D2 | PD2 |
| Buzzer | D3 | PD3 |
| LCD SDA | A4 | PC4 |
| LCD SCL | A5 | PC5 |
| PLUS Button | D6 | PD6 |
| MINUS Button | D7 | PD7 |
The code is structured in two main parts:
- Real-time acquisition (inside
loop()) - Deferred decoding (after detecting a silence)
Dots and dashes are accumulated until a significant pause is detected.
The base time unit is the dot duration:
uint32_t seuilPointTrait = 150;Applied rules:
- dot = 1 unit
- dash = 3 units
- inter-letter gap = 3 units
- inter-word gap = 9 units
The threshold can be adjusted using the PLUS/MINUS buttons.
Symbols are stored in:
static uint8_t codeMorseRecu[6];
static uint8_t y = 0;1 = dot
0 = dash
Each key release validates one symbol.
The Morse code is stored in a constant array of C++ structures, placed in program memory (FLASH) using PROGMEM.
It is a static array accessed sequentially, perfectly suited to AVR constraints.
struct struct_code {
uint16_t caractere : 7;
uint16_t longueur : 3;
uint16_t code : 6;
};Each structure occupies 16 bits, optimizing memory usage.
The sequence {1,0,0,1} is converted into a binary integer using successive shifts:
resultat = (resultat << 1) | tableau[i];Character lookup relies on:
- the binary value,
- and the length, which is essential to avoid ambiguities
(e.g.I = 11andD = 011both yield decimal3).
Structures are copied one by one from FLASH to RAM using memcpy_P().
- Line 0: tempo threshold display
- Lines 1 to 3: decoded text
The voirCaractere() function handles:
- cursor advancement,
- line wrapping,
- clean screen reset.
The project relies on the simpleBouton library for:
- software debouncing,
- reliable edge detection,
- press and silence duration measurement.
Key functions:
vientDEtreRelache()dureeEnfonce()estRelacheDepuisPlusDe(ms)estStableDepuisPlusDe(ms)
The code uses three simpleBouton inputs:
simpleBouton bouton(2);: main input (Morse key)simpleBouton boutonPlus(6);simpleBouton boutonMoins(7);
The simpleBouton library configures pins as INPUT_PULLUP.
Wiring consequence:
- The Morse key must connect the pin to GND when pressed.
This library enables a clear and robust Morse logic.
- The Arduino community for high-quality technical exchanges:
https://forum.arduino.cc/t/morse-key-bouton-poussoir/1336519 - Bricoleau for the
simpleBoutonlibrary - ChatGPT for assistance with project structuring, pedagogy, and documentation
This project uses the simpleBouton library, developed by Bricoleau.
The library is included in this repository without any modification, solely to facilitate compilation, understanding, and faithful reproduction of the project.
It is fundamental to the software architecture, as it forms the core of the Morse key temporal management mechanism.
In particular, it provides:
- reliable detection of transitions (press / release),
- measurement of press and silence durations,
- management of stable button states,
all without using hardware interrupts or hardware timers,
relying exclusively on millis() and a software state machine.
The entire Morse logic of the project is built on these primitives, for example:
bouton.vientDEtreRelache();
bouton.estRelacheDepuisPlusDe(...);
bouton.estStableDepuisPlusDe(...);
bouton.dureeEnfonce();All rights related to this library remain the property of its author.
- The
code/directory contains the main Arduino sketch (.ino) as well as detailed documentation explaining its internal operation. - The
simpleBouton/directory contains the third-party simpleBouton library (included without modification), used for reliable button handling and press duration measurement.
To compile the project in the Arduino IDE, the simpleBouton library
must be placed in the libraries/ directory of the Arduino environment,
or integrated locally according to the user’s workflow.

