Back to Software

Morse Code USB Keyboard

7 Mar 2015
Progress: Complete

I've seen people do this using an Arduino with USB libraries... talk about overkill. Every computer has the ability to do this without any external processing, which I interpret as, without any additional expense. You can send on/off signals by touching the DTR/DSR lines together (or, the RTS/CTS lines together) of an ordinary serial port. I believe this is quite a well known trick.

A USB-to-serial cable can be had for under £2. Assuming you already have a Morse key, that's all the hardware you need. If you don't want to sacrifice the cable, you can indulge in a 9-pin plug to connect the two together. DTR/DSR are pins 4 and 6, RTS/CTS are pins 7 and 8.

A morse code straight key, wired directly into a USB serial adapter
To use it as a keyboard, I wrote a little Python script.

import serial
import pyaudio
import numpy
import ctypes
user32 = ctypes.windll.user32


key = serial.Serial(9)  # COM port

ditlength=12            # determines character speed
spacelength=60          # ~5*ditlength, longer for farnsworth pause

a=1.99                  # ~700Hz, a = 2*cos(2*pi* Pitch / 44100)


# A-Z 0-9 .,/?=+
lookup={
    ".-":65,"-...":66,"-.-.":67,"-..":68,".":69,"..-.":70,"--.":71,"....":72,"..":73,
    ".---":74,"-.-":75,".-..":76,"--":77,"-.":78,"---":79,".--.":80,"--.-":81,".-.":82,
    "...":83,"-":84,"..-":85,"...-":86,".--":87,"-..-":88,"-.--":89,"--..":90,".----":49,
    "..---":50,"...--":51,"....-":52,".....":53,"-....":54,"--...":55,"---..":56,"----.":57,
    "-----":48,".-.-.-":190,"--..--":188,"-..-.":191,"..--..":191+256,"-...-":187,".-.-.":187+256,
    "........":8
    }

def typechar(keycode):
    if keycode>255:
        user32.keybd_event(16,0,0,0) # hold shift
        user32.keybd_event(keycode-256,0,0,0)
        user32.keybd_event(keycode-256,0,2,0)
        user32.keybd_event(16,0,2,0)
    else:
        user32.keybd_event(keycode,0,0,0)
        user32.keybd_event(keycode,0,2,0)


def process(in_data, frame_count, time_info, status):
    global s1, s2, a, vol, down, up, stack

    v=key.getDSR()
    if v:
        down+=1
        up=0
    elif down:
        stack+=("-" if down>ditlength else ".")
        down=0
    else:
        up+=1
        if up==1+ditlength:
            if stack in lookup: typechar(lookup[stack])
            if len(stack)>7: up+=spacelength
            stack=''
        if up==spacelength:
            typechar(32)

    v=float(v)
    data=[]
    
    for i in range(frame_count):
        if v!=vol:
            if i<30: vol+= (v-vol)/20
            else: vol=v
        output = a * s1 - s2; 
        s2 = s1;
        s1 = output;
        data.append(numpy.uint8(vol*output*128))
    return (bytes(data), pyaudio.paContinue)


s1=s2=a*0.2
vol=0
down=0
up=1+spacelength
stack=''

pyaudio.PyAudio().open(
    format=pyaudio.paInt8,
    channels=1,
    rate=44100,
    frames_per_buffer=512,
    output=True,
    stream_callback=process
    ).start_stream()

input()


Pretty straightforward really. It uses my favourite recurrence relation to make a sine wave, and adds a little lowpass/envelope to the audio to prevent clicking. Those speed parameters work well for typing at around 15 to 20wpm. They are in units of buffersize divided by samplerate, so if you change either you'll need to readjust your speeds. The character set is quite limited but nothing's stopping you adding to it or even inventing your own codes for other keys (see keycode references). Typing the error prosign (eight dots) acts as a backspace.

I suspect that controlling every aspect of a computer via Morse code is something that's been done before. There may even be a standard for it. I, however, just wanted a fun way to practice.


Update See Morse Code USB Keyboard Mk II.