View on GitHub

pxt-morse

Morse Code Decoding

morse=github:bsiever/pxt-morse
clicks=github:bsiever/microbit-pxt-clicks

Morse Code: Keying-in, Decoding, and Encoding

This extension can decode and encode dots/dashes of Morse Code as well as manage the detection of keying-in of Morse code.

The term “dit” is sometimes used rather than dot. And “dah” rather than dash.

Keying #morse-keying

“Keying” refers to keying in the dots, dashes, and the important silences that occur. The keying blocks, [morse.keyDown()] and [morse.keyUp()], are used to indicate when a key is pressed and released. Key presses and releases will automatically be decoded, first to dots and dashes and then, following an appropriate “silence”, a code letter or number if it’s a valid code (the sequence of dots and dashes will be provided for invalid codes).

Standard Morse code timing expects:

This extension uses four timing values to decode key presses:

If the duration of time between [morse.keyDown()] and [morse.keyUp()] is:

If, following a [morse.keyUp()], there is no [morse.keyDown()] for more than [morse.maxBetweenSymbolTime()], any successful dots and dashes are treated as a letter and [morse.onCodeSelected()] is executed.
If, following a [morse.keyUp()], there is no [morse.keyDown()] for more than [morse.maxBetweenLetterTime()], any successful dots/dashes are treated as a letter and [morse.onCodeSelected()] is executed. The code will be a space (" ") to indicate the gap between words.

By default the timing is comparable to a dit that lasts about 160ms, but requiring an extra long gap between words:

~hint

Keying with the built-in buttons may be easier if the Button Clicks extension’s on button down and on button up blocks are used.

~

Key Down #morse-keydown

morse.keyDown() : void

The Morse code key has been pressed.

Key Up #morse-keyup

morse.keyUp() : void

The Morse code key has been released.

Set Dot and Dash Times / Timing #morse-setmaxdurationdotdash

morse.setMaxDurationDotDash(dotTime: number, dashTime: number) {

Set the maximum time (in milliseconds) of dots and dashes.

Get the Max Dot Time #morse-maxdottime

morse.maxDotTime()

Provides the current maximum dot time.

Get the Max Dot Time #morse-maxdashtime

morse.maxDashTime()

Provides the current maximum dash time.

Set Silence Between Symbols Letters Times / Timing #morse-setMaxSilenceBetweenSymbolsLetters

morse.setMaxSilenceBetweenSymbolsLetters(symbolTime: number, letterTime: number) 

Set the maximum time (in millisecondes) of slience allowed between symbols (dots/dashes) and letters of a word.

Get the Maximum Between Symbol Time #morse-maxbetweensymboltime

morse.maxBetweenSymbolTime()

Provides the current maximum time allowed between symbols (dots and dashes) before considering the sequence of dots/dashes completed. When this time is exceeded any preceeding dots and dashes are decoded and [morse.onCodeSelected()] is executed.

Get the Maximum Between Letter Time #morse-maxbetweenlettertime

morse.maxBetweenLetterTime()

Provides the current maximum time before considering the preceeding letters to be completed That is, before being considered a space between words or end of transmissions. When this time is exceeded [morse.onCodeSelected()] is executed and the code will be a space (" ").

Reset Key timing #morse-resettiming

morse.resetTiming() : void

Reset the timing of keying. This ignores any current key up/down activities.

A reset may be needed if timing parameters are changed while in the midst of keying in a symbol. Resetting decoding may also be needed.

Identifying when individual symbols (dots, dashes, silences) are Keyed In #morse-onnewsymbol

morse.onNewSymbol(handler: (symbol: string) => void)

The symbol will indicate the which symbol has been detected/entered:

Decoding #morse-decoding

Decoding refers to decoding a sequence of dots, dashes, and silences into letters based on Morse code.

Dot #morse-dot

morse.dot() : void

Register that a complete “dot” has happened.

Dash #morse-dash

morse.dash() : void

Register that a complete “dash” has happened.

Silence #morse-silence

morse.silence(kind?: morse.Silence) : void

Register that a silence between the key being pressed (down) has happened:

~alert

Silences are needed

A morse.Silence.InterLetter or morse.Silence.InterWord is required to detect a letter.

~

Reset Decoding #morse-resetdecoding

morse.resetDecoding() : void

Reset dash/dot processing. That is, start at the beginning as though nothing had been keyed in.

On Code Selected #morse-oncodeselected

morse.onCodeSelected(handler: (code: string, sequence: string) => void) 

A code has been selected (following a morse.Silence.InterLetter or a morse.Silence.InterWord). A valid code will be represented with a valid Morse character. An invalid Morse code will be indicated with a code that is a question mark (?). sequence will be the sequence of dots and dashes in the code. If there’s an end-of-word silence ([morse.silence(morse.Silence.InterWord)]), the code will be a space (" ") and the sequence will be empty.

Note that several codes are unused by traditional Morse code. In these cases the code will be ? and the sequence will indicate the sequence of dots and dashes. This extension will track the first seven (7) dots/dashes. Any more than 7 will be ignored.

Unused codes include:

Peek at the current Code #morse-peekcode

morse.peekCode()

Provide the code described by the currently entered dots and dashes. Note that the current code is not compelte until a suitable silence has occurred.

For example, if a single dot was entered, [morse.peekCode()] would return an E, which is represented by a single dot. If a second dot is entered before any sufficient “silence”, it would then return a O, which is represented by two dots. Etc.

Peek at the current sequence #morse-peeksequence

morse.peekSequence()

Provide the sequence of dots and dashes that is currently entered but not yet complete (there hasn’t yet been a sufficient silence).

A maximum of seven symbols are stored. Any more than seven will be ignored.

Encoding #morse-encoding

Encoding refers to converting letters and spaces to Morse code.

Encoding text as Morse Code #morse-encode

morse.encode(characters: string) : string 

The given string will be converted to a represntation of Morse code using dots (.), dashes (-), spaces indicating gaps between the symbols for a letter, and underscores indicating the gaps between words.

For example, [morse.encode("SOS SOS")] would return "... --- ..._... --- ...".

Examples

Morse Code Trainer

This example can help you learn the “code” part of Morse code without the key timing.

For example, the code for the letter U is “..-“. To practice entering a “U” you would press button A twice, then button B, then A+B. The screen should show the “U”.

input.onButtonPressed(Button.A, function () {
    morse.dot()
    basic.showLeds(`
        . . . . .
        . . . . .
        . . # . .
        . . . . .
        . . . . .
        `)
    basic.pause(200)
    basic.clearScreen()
})
morse.onCodeSelected(function (code, sequence) {
    basic.showString("" + (code))
})

input.onButtonPressed(Button.AB, function () {
    basic.showIcon(IconNames.Yes)
    morse.silence(morse.Silence.InterLetter)
})
input.onButtonPressed(Button.B, function () {
    morse.dash()
    basic.showLeds(`
        . . . . .
        . . . . .
        # # # # #
        . . . . .
        . . . . .
        `)
    basic.pause(200)
    basic.clearScreen()
})

Key Timing Trainer

This example allows you to practice the timing of key presses/releases for Morse code:

Start practicing by trying to prefect the dot and dash times of individual letters:

~alert

The example uses a special form of showString to ensure it’s shown fast enough to keep up with Morse code entry. This version of “Show String” isn’t available as a block, so it is placed in a function that can be used from blocks.

~

~alert

This example requires the Button clicks extension to detect when button A is pressed ([morse.keyDown()]) and released ([morse.keyUp()]).

~

// Show a string "now" without a delay / scrolling
function showStringNow (theString: string) {
    basic.showString(theString, 0)
}
morse.onCodeSelected(function (code, sequence) {
    // Make silences visible.
    if (code == " ") {
        code = "_"
    }
    serial.writeLine("Code: " + code)
    showStringNow(code)
})
buttonClicks.onButtonUp(buttonClicks.AorB.A, function () {
    morse.keyUp()
})
// Show dot/dash
morse.onNewSymbol(function (newSymbol) {
    serial.writeLine(newSymbol)
    showStringNow(newSymbol)
})
buttonClicks.onButtonDown(buttonClicks.AorB.A, function () {
    morse.keyDown()
})
input.onButtonPressed(Button.B, function () {
    morse.resetTiming()
    morse.resetDecoding()
})
morse.setMaxDurationDotDash(
200,
1000
)
morse.setMaxSilenceBetweenSymbolsLetters(
500,
2000
)

Morse Code Keying Trainer

This example can be used to practice keying in Morse code. It will show the code letter for the current sequence of dots/dashes that were entered with button A. It will “flash” when the code is completed/accepted (that is, after a long enough silence to indicate the end of the letter).

For example, after a single dot is entered it will show “E” until either: 1) Another dot/dash occurs or 2) Enough time has elapsed to consider the current letter to be done. If a second “dot” is keyed in before the time is up (now at “..”), it will display an “I”. Etc.

// Show a string "now" without a delay / scrolling
function showStringNow (theString: string) {
    basic.showString(theString, 0)
}
morse.onCodeSelected(function (code, sequence) {
    showStringNow(code)
    game.addScore(1)
})
buttonClicks.onButtonUp(buttonClicks.AorB.A, function () {
    morse.keyUp()
})
morse.onNewSymbol(function (newSymbol) {
    if (newSymbol == "-" || newSymbol == ".") {
        showStringNow(morse.peekCode())
    }
})
buttonClicks.onButtonDown(buttonClicks.AorB.A, function () {
    morse.keyDown()
})
morse.setMaxDurationDotDash(
200,
1000
)
morse.setMaxSilenceBetweenSymbolsLetters(
500,
2000
)

~alert

The example uses a special form of showString to ensure it’s shown fast enough to keep up with Morse code entry. This version of “Show String” isn’t available as a block, so it is placed in a function that can be used from blocks.

~

~alert

This example requires the Button clicks extension to detect when button A is pressed ([morse.keyDown()]) and released ([morse.keyUp()]).

~

Challenges

  1. Use radio extensions to recieve Morse code from remote “keys”.
  2. Combine this with the Bluetooth HID extension (micro:bit v2 only) to create a Morse Code keybaord!
  3. Create a program that uses tones to play the dots/dashes as they are being keyed in.

Extras

Here’s a fun “tone trainer” to practice decoding Morse code yourself (v2 micro:bit only): Tone Trainer w/ Voice. You can modify the codes in the start to practice just a few Morse codes characters at a time.

Acknowledgements

This was inspired by the work of “grandpaBond” on the Micro:bit Developer Slack Forum, who created this fantastic example to help kids learn Morse Code: https://makecode.microbit.org/24561-13529-14704-94719.

Icon based on Font Awesome icon 0xF141 SVG.

Misc.

I develop micro:bit extensions in my spare time to support activities I’m enthusiastic about, like summer camps and science curricula. You are welcome to become a sponsor of my micro:bit work (one time or recurring payments), which helps offset equipment costs: here. Any support at all is greatly appreciated!

Supported targets

for PXT/microbit