Repeaters: Gloversville, NY 146.700 (-) PL: 123.0 | Town of Florida, Amsterdam NY 146.970 (-) PL: 123.0 | Northville, NY 146.835 (-) PL: 123.0

Tiny CW Capacitive Touch Paddle

Capacitive Touch morse code paddle using an ATTiny4 – (126 Bytes)


I (Edgar/KC2UEZ) am a ham radio operator. I like building antennas, experimenting with digital modes, and operating SDRs. Like many no-code operators, after being on the air for a while, I developed an interest and appreciation for Morse Code. I started to learn CW by using I purchased a cheap paddle, but I found the clicking noise a little bit annoying. At this moment, I decided that I wanted to create a noise-free way to send Morse Code.

While I was able to find a few touch paddles (without moving parts) that I could purchase, I ultimately decided to make my own. I saw this project as an opportunity to do some hacking and to learn something new along the way.

While researching capacitive touch online, I came across this Arduino playground post: To get my project underway I modified the code to implement the paddle logic and loaded it into an Arduino. The code reads two input pins. If touch is detected by the micro-controller, the Arduino outputs two signals to toggle the transistors, which simulates a closed circuit, similar to that of a mechanical paddle. The closed circuit enables the radio to create the DIT and DAH tones. The under 1kB binary code worked well and without error. This made me wonder how small I could make this code. After optimizing the code, I managed to shrink it to under 512 bytes.

The project:

The goal of this project was to create a single PCB with an ATTiny4 AVR, a battery, transistors and a 3.5mm connector jack. The paddle is designed to have exposed conductive material in order to read the capacitive touch.

I decided to use Upverter for the schematics and the PCB layout.

After finalizing the layout, I sent the files to OSHpark for fabrication. Three weeks later, I received the PCBs. They were larger than I anticipated, but I wanted at least 1″ x 1″ for the touch area.
The Gerber files used for manufacturing the PCBs are available on the Upverter Project page.
NOTE: Silk screen has a misprint. PB1 and PB0 are reversed.


List of materials:

1 x BC501SM-ND
2 x MMBF170CT-ND
1 x CP-3523SJCT-ND
1 x CR1220 Battery

I purchased the components to make 5 paddles from Digi-Key, which totaled $18.76 (about $3.75 per board). I did not purchase the CR1220 batteries from Digi-Key because they cannot be shipped via USPS and required a more expensive shipping method. Fortunately, I was able to purchase them at my local RadioShack.


The code:

Initially, the DIT_IN and DAH_IN pins are configured as outputs and set to low. In order to read the signal, the pins are re-configured as inputs. After reconfiguration, the internal pull-up resistor is enabled. The code measures the length of time it takes for the pin to switch from 0 to 1. A delay in the change from 0 to 1 reveals that the hands touching the paddle act as a human capacitor, increasing the amount of time it takes for the micro-controller to sense the signal variation. As a result, the code toggles the DIT_OUT or DAH_OUT pin(s), allowing the transistor to route the electrical current to the ground. This allows the radio to interpret the signal as a closed circuit and to generate the proper tone.

Before touch:Pulse Before Touching

After touch:Pulse after touching


Source code:


#include <avr/io.h>

// Define port configuration
#define dit_out PINB2
#define dah_out PINB3
#define dit_in PINB0
#define dah_in PINB1

// Charge time per pin
uint8_t CT(uint8_t pin)
    uint8_t mask = (1 << pin);
    uint8_t i;
    DDRB &= ~mask;	// Set pin as input
    PORTB |= mask;	// Set pin to high
    PUEB |= mask;	// Enable Pull up resistor 

    // See how long it takes to toggle from 0 to 1
    for (i = 0; i < 16; i  ) {
     if (PINB & mask) break;
    PORTB &= ~mask;	// Set pin to low
    DDRB |= mask;	// Set pin as output

    return i;		// Return how long it took

int main(void)
 // Set AVR speed to 8Mhz
 CCP = 0xD8;	            // 0xD8 = 8 Mhz
 CLKPSR = 0x00;             // Sets the clock div factor to 0

 // Set output
 DDRB |= (1 << dit_out);    // Set DIT_out pin as output
 DDRB |= (1 << dah_out);    // Set DAH_out pin as output
 // Loop to check if a side of the paddle is being touch
  if (CT(dit_in) > 4)        // Check for touch  
    PORTB |= (1<< dit_out);  // Set dit_out to HIGH
    PORTB &= ~(1 << dit_out);// Set dit_out to LOW

  if (CT(dah_in) > 4)        // Check for touch
    PORTB |= (1<< dah_out);  // Set dah_out to HIGH
    PORTB &= ~(1 << dah_out);// Set dah_out to LOW

For the implementation I used Atmel Studio 7.0. I program the AVR using a AVRISP MKII programmer selecting the TPI interface.

When programming the ATTiny4, the RSTDISBL fuse must be selected because PB3 is used as one of the outputs.
Note: Setting RSTDISBL will disable re-programming. This can be reversed by applying 12V to pin PB3 while re-programing.

After optimizing the code, I was able to reduce the binary to 126 bytes. I am sure this code could have been written using assembly to achieve better performance and smaller code size, but I am happy with the less than 1/8 of a kB. The touch paddle works well when I use it with a keyer or when directly connected to the radio.

Note: I noticed that I get smaller binary size if –O2 is used instead of –Os (default). I use –O2 and then add the –fipa-cp-clone flag.

The project zip file for Atmel Studio 7 is available here or the link below.


3D Printed Base:

I used the free Autodesk 123D Design program to create a base to rest the fist and keep the paddle from moving. The program is no longer available. Feel free to download the .stl file and print your own, if desired.


How it works:


REV 2.0:

Ideas for future versions of this project:

  • Add on/off switch
  • LEDs
  • Implement keyer, not just paddle
  • ESD/EMC protection
  • Source power from radio
  • Smaller PCB to reduce cost
  • Use assembly instead of C
  • Move to ATTINY10 or other with more flash to implement other features
  • Add programmer header instead of test points
  • Use a different base design

Leave a Comment


This site uses Akismet to reduce spam. Learn how your comment data is processed.