Repository navigation
Morse
Morse code (CW): a tone which is switched on and off. Each character is a sequence of short (dots) and long (dashes) tones. Two alphabets are supported:
- International Morse code (ITU-R M.1677, the default): the code of amateur radio, aviation and shipping. A dash is 3 units, the spaces between the elements, characters and words are 1, 3 and 7 units.
-
American Morse code (the railroad code of the landline telegraph): different codes, shorter dashes (2 units), the long dashes of
L(4 units) and0(5 units), and a space of 2 units within the charactersC,O,R,Y,Zand&. The word space is 6 units.
The speed is given in words per minute (wpm) with the standard word PARIS: a unit (the length of a dot) is 1.2 / wpm seconds, e.g. 60 ms at 20 wpm.
Advantages
- The universal mode of amateur radio: it can be received by ear and by other CW decoders (International Morse code).
- Very narrow band: the receiver only listens to about 30 Hz around the tone at 20 wpm.
- The receiver follows the speed of the sender and corrects the length of the tones.
- Needs very little CPU and memory.
Disadvantages
- Slow: 20 wpm are about 2 characters per second.
- Only upper case letters, digits and some punctuation. American Morse code has fewer characters.
- No error detection: noise turns into wrong characters.
- The frequency of the tone must be known: the receiver accepts about ±15 Hz at 20 wpm (less at lower speeds).
| Mode | Alphabet | Speed |
|---|---|---|
morse, cw
|
International | 20 wpm |
morse-N, cwN (e.g. morse-25, cw12) |
International | N wpm (1 - 100) |
morse-american |
American | 20 wpm |
morse-american-N (e.g. morse-american-15) |
American | N wpm |
I2SStream i2s;
MorseStream cw(i2s, RXTX_MODE);
void setup() {
MorseConfig cfg = cw.defaultConfig(); // International, 20 wpm
cfg.wpm = 15;
cfg.sample_rate = 8000;
...
cw.begin(cfg);
}
void loop() {
cw.print("CQ CQ DE N0CALL K");
cw.flush();
while (cw.available()) Serial.write(cw.read());
}echo "CQ CQ DE N0CALL K" | tinymodem --tx -f cq.wav morse-25
tinymodem --rx -f cq.wav morse-25
echo "WHAT HATH GOD WROUGHT" | tinymodem --tx morse-american-15MorseConfig, MorseEncoder, MorseDecoder, MorseStream, MorseStreamRTOS and MorseCode (the code tables and the timing) are the Morse classes. The transmitter accepts upper and lower case; a space (or a character which is not in the alphabet) is a word space. In International Morse code, a line break is sent as the prosign AA (.-.-) and received as line break. The receiver provides upper case characters and ? for unknown codes. On the command line, -R, -v, -M (tone frequency), -q, --rx-one and --Xrxnoise are supported.
MorseConfig field |
Default | |
|---|---|---|
alphabet |
INTERNATIONAL |
INTERNATIONAL or AMERICAN
|
wpm |
20 | Speed in words per minute: the transmitter's speed and the start value of the receiver |
frequency |
700 | Frequency of the tone (Hz) |
volume |
1.0 | Transmit amplitude |
ramp_ms |
5 | Transmitter: rise and fall time of the tone (avoids clicks) |
adaptive_speed |
true | Receiver: follow the speed of the sender |
min_amplitude |
0.01 | Receiver: minimum level of the tone |
rx_timeout_units |
20 | Receiver: a transmission ends after this silence (in units) |
rx_one |
false | Receiver: stop after the first transmission |
channel |
0 | Receiver: input channel |
sample_rate, channels, bits_per_sample
|
48000, 1, 16 | Audio format |
The transmitter switches a sine tone on and off with a raised cosine ramp. The timing is calculated exactly over the whole transmission, so there are no rounding errors.
The receiver mixes the signal with the tone frequency and sums it over half a unit, every 2 ms. The tone is on when the level is above the middle between the signal level and the noise floor (with hysteresis). A new tone must also be 5 times above the average noise level, so pure noise doesn't produce characters. Changes shorter than 0.3 units are ignored. The lengths of the tones and spaces in units give the dots and dashes and the spaces between the elements, characters and words; the decision limits are in the middle between the nominal lengths of the alphabet. The length of a unit follows the dots and dashes of the sender. The rise and fall time and the measurement shorten the tones and lengthen the spaces by the same amount: the receiver measures this with the dots and the following element spaces (both 1 unit) and corrects it.
Measured with the unit tests and the command line tool at 8000 Hz:
| International | American | |
|---|---|---|
| Speeds | 5 - 50 wpm | 5 - 50 wpm |
| Sender's speed with the receiver at 20 wpm | 12 - 32 wpm | 15 - 28 wpm |
| Noise without errors (20 wpm) | 2.0 (5 wpm: 2.0, 40 wpm: 1.0) | 1.5 |
| Frequency offset (20 wpm) | ±15 Hz | not measured |
Noise without a signal doesn't produce characters. The decoder runs about 11000 times faster than real time on a PC, 130 times on the ESP32-S3, 320 times on the ESP32-P4, 95 times on the RP2350 and 14 times on the RP2040 (without floating point unit) (see Performance). It needs 2 KB at 8000 Hz and 11 KB at 48000 Hz (20 wpm): the memory grows with the unit length and the sample rate.