How to Connect a CW Key or Switch to a PC

James C. Ahlstrom
April, 2020

This paper considers several ways to connect a switch to a PC. Since PCs do not have IO ports and no longer come with serial or parallel ports, we will use a USB to serial port adapter. Later we will use an external microcontroller. This is motivated by the need to connect Morse code keys, paddles and electronic keyers to amateur radio equipment. In particular we want to connect a key to a PC and use it to send CW using a remote HermesLite2 which is connected to the PC by Ethernet. The HermesLite2 is a software defined radio transceiver that is optimized for low cost.

In case you are unfamiliar with amateur radio, a key is a switch, a paddle is two switches and a keyer is one switch that can send very fast switch changes. In all cases, the exact timing of switch closures must be maintained. At a fast but reasonable CW rate of 60 words per minute, the length of a "dit" is 20 milliseconds. We will present measurements of the switch speed possible on Windows and Linux PCs.

Serial Port Electrical Specifications

The serial port logic levels are -25 to -3 volts for False and 3 to 25 volts for True. But serial ports are often wired assuming that an open circuit or zero volts counts as False. Here is the pinout of the usual DB9. There are pins for transmitted data, received data and ground. There are two pins DTR and RTS that are set by the PC, and four pins that are read by the PC. We will be using DTR as a source of power, and the PC must set this pin. We will use CTS and DSR as switch inputs to the PC. There is no specification for how much power DTR can provide nor for how much current CTS needs. On my USB to serial adapter, unloaded DTR was 7.1 volts, connecting DTR to CTS through a 2K2 resistor dropped this to 5.9 volts. The CTS was drawing 0.8 ma. Connecting DTR to ground through a 2K2 resistor dropped the voltage to 5.7 volts.

Connections Using a Serial Port Adapter

filter board
Figure 1

Figure 1 is the schematic of the usual connections for a key or paddles. Note that DTR is being connected to CTS, DSR or both. Since none of these are at ground, it will only work for floating switches. It will not work with a keyer since one side of the keyer is grounded.

filter board
Figure 2

Figure 2 is the schematic of a modification with one side of the switches grounded. This requires more current from DTR depending on the value of the resistors. The logic levels are also reversed, and key up (open) is now logic level True. The diode may be needed to protect electronic switches from negative voltage.

filter board
Figure 3

Figure 3 is the same as Figure 2, except that there is an input for a sidetone oscillator. Grounding the keyer input grounds both the serial port and the sidetone. The serial port and sidetone are isolated with diodes.

Using Hex Inverter
Figure 4

Figure 4 uses a hex inverter to enable larger resistors to be used with a weak DTR. It adds hysteresis to the switch inputs. It also changes the logic levels back to False for open, and adds a transistor open collector output to read the RTS signal from the PC. The maximum voltage for the inverter is 18 volts, so be sure your adapter DTR is lower than this. I tested this circuit but it may not offer any real advantages over the circuits above.

Using Microcontroller
Figure 5

Figure 5 uses an Arduino MKR Zero. The microcontroller (MCU) reads the key presses and sends a four byte message to the PC with the key state and a time stamp. The PC saves the messages and plays back the key presses according to the time stamp. This removes the need for real-time processing in the PC. The MCU code for this is trivial, and almost any MCU can be used. But MKR Zero has a real DAC and so it can also generate a sidetone with zero delay.

Measure Serial Port Speed

I measured the switching speed using Figure 2 above, except that only CTS was connected and I did not use the diode. I used a keyer as the switch source with a time between switches of 28 milliseconds and a corresponding speed of 43 words per minute. The measurements were made on a PC with an Asus motherboard, an Intel i5-6600T processor, and 8 Gigs of memory. The PC dual boots Windows 10 and Ubuntu 18 LTS. The program to read CTS was running with its own thread within my SDR program Quisk, and had this structure in pseudocode:

while thread is active
    Read the CTS_bit from the serial port
        Write the CTS bit to the RTS bit
    Get the time of day from the computer clock
    If the CTS bit changed
        Set delta to the current time minus the time of last change
        Subtract the expected time of 28 milliseconds from delta
        if delta is greater than +/- 2 milliseconds (this is the tolerance)
            Print a message with delta
    Sleep for one millisecond

I used a two channel oscilloscope to monitor the keyer and RTS. The delay between the keyer change and the RTS change is some indication of the speed of the serial port control lines. On Linux, the delay varied from 0.2 to 0.6 milliseconds. On Windows the delay varied up to 20 milliseconds.

We would like to know how fast the PC is reading the key change. Assuming that reading the computer clock is fast, we should expect a key change every 28 milliseconds. I ran the code above and noted the delays "delta" from the messages. I measured delta on a quiet machine, and also during a load generated by opening a complicated web page. On Linux, delta varied about +/- 2 milliseconds. Over a span of 16 minutes, it varied from -4 to +3. During the web page stress test, it varied from -4 to +6.

On Windows I had to increase the tolerance from 2 to 15 milliseconds to avoid being flooded with messages. Then I regularly got delta of -18 to +20. During the web page test, delta was -18 to +288. I tried to change the Windows code to use SetCommMask() and WaitCommEvent() instead of polling. This change failed with error code 0x6d on my USB serial port even though it worked on a virtual serial port. Perhaps it is not available for USB serial.

Assuming a 10% variation in CW timing is acceptable, and using a typical 4 millisecond variation from Linux, we can expect good performance down to a dit length of 40 milliseconds or 30 words per minute. Performance on Windows is currently unacceptable, but I am looking for ways to improve it.

Measure CW Performance

The measurement of delta is interesting, but we still have to key the hardware. All work so far has been done on Linux. A software defined radio is constantly reading samples and the mic input, and performing digital signal processing. That makes it troublesome to read the CTS changes on the serial port. For the circuits of Figures 2, 3 and 4 I started another thread to poll the serial port at one millisecond intervals. When a key change occurs, I read the system clock and add the time and key state to a queue. When the PC gets around to sending Tx samples, I send DC values for key down, and zeros for key up. Tx samples are sent according to the timestamp. These Tx samples are treated the same as SSB for Tx purposes, and follow the usual route to the transmitter. Since they are DC, they are not offset from the Tx frequency. Since they have a timestamp, real time processing in the PC is not necessary. The HermesLite2 also has a CWX feature to key the transmitter, but I could not find an advantage in using it, and the current design works on other hardware. For the oscilloscope traces below, the time between key changes is 28 milliseconds. I am showing the key signal for reference, but there is much latency between key press and CW output. The PC was unloaded except for the SDR program Quisk.

Oscilloscope CTS
Figure 7
Oscilloscope CTS
Figure 6

Figures 6 and 7 show the key input and the HermesLite2 keyed RF output when using the circuit of Figure 4 on Linux. Using the circuits in Figures 2 and 3 would show the same result. Visual inspection showed little jitter in the pulse timing. Performance was better than expected.

For the circuit of Figure 5 the MCU polls the keyer signal, reads its clock and sends 4-byte messages to the serial port. The messages consist of a time stamp, the key state and some error checking. This scheme depends on the PC not losing any serial port bytes, but does not depend on real time processing. The messages are sent to the transmitter as above. There is no additional thread to read the serial port; I am using the sound thread. Visual inspection showed no jitter. The MCU was also generating a sidetone at the time.

Oscilloscope CTS
Figure 9
Oscilloscope CTS
Figure 8

Figures 8 and 9 show the key input and the HermesLite2 keyed RF output when using the circuit of Figure 5 on Linux.


Sidetone is an audible tone sounded while the key is down. An operator depends on this tone when sending CW, especially when using a "bug" or paddles. There must be no delay between key down/up and sidetone. This is difficult for a PC because it really does require real time processing. The sidetone generation in Quisk is much too slow to be useful. By ear, the circuit of Figure 5 had a perfect sidetone as expected. A normal keyed audio oscillator may not work because of startup time. A good solution is a free running oscillator keyed with an analog switch and used in the circuit of Figure 3. However, most keyers will have a builtin sidetone, and an additional sidetone may not be necessary.

Oscilloscope CTS
Figure 10

Figure 10 shows the key signal and the sidetone output from the circuit of Figure 5. The rise time was set to 4 milliseconds. The sidetone has zero delay. The waveform shows artifacts from the DDS clock. The sidetone could be low pass filtered, but it sounds good as is.

Further Work

Alan Hopper, the author of Spark SDR, is working on a solution using MIDI. A PC understands that MIDI is time sensitive and will endeavor to generate sound promptly. The audio can be used as a sidetone, and can also be sent to the hardware with its timing intact. He should be publishing test results soon.

I need to clean up my code, and work on a Windows version. Windows often uses callbacks, and if I can find these and make them work perhaps I can speed up the timing of CTS. Of course, the circuit of Figure 5 should work perfectly now.