Arduino Iambic Keyer and Side Tone Generator

The latest issue of QEX (March/April) came this week with an article about an open-source keyer (pages 25-31).   This article referenced an earlier QEX article from Sept/Oct 2009 using an Arduino.    Since I have a Bencher paddle lying around, but no practice oscillator, I decided to investigate a bit further.   An Iambic keyer needs logic for creating its output, so the Arduino seemed like a shoe-in, especially since they’re widely available, even through Radio Shack (SparkFun, and AdaFruit are other good suppliers).

Finding back-issue articles of QEX turned out to be nigh impossible, so a few quick searches on the ‘net  turned up programs that could be easily adapted.   The first was a tone generation tutorial.   The next was a simple, but functional program that signalled through the LED.  I trimmed the fluff from this program and added tonal output (source code below). Here’s what the basic parts look like:

Keyer components

Plus the paddle (of course).  The two 4.7K resistors on the 1/8″ jack are pull-up resistors and the paddle “shorts” them to ground when contact is made.  Note the 100 ohm resistor in series with the speaker.   Some folks have used speakers from “singing” greeting cards. Wired-up, the completed simple keyer looks like:

wired keyer

The speaker is wired between digital pin 4 and ground.  The paddle wires connect to pins 2 and 5.  The red and black wires to the 1/8″ connector (resistors, barrel) are +5V and ground (respectively).

The code to run is as follows (download the Arduino software here):

///////////////////////////////////////////////////////////////////////////////
//
//  Iambic Morse Code Keyer Sketch
//  Copyright (c) 2009 Steven T. Elliott
//
//  This library is free software; you can redistribute it and/or
//  modify it under the terms of the GNU Lesser General Public
//  License as published by the Free Software Foundation; either
//  version 2.1 of the License, or (at your option) any later version.
//
//  This library is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//  Lesser General Public License for more details:
//
//  Free Software Foundation, Inc., 59 Temple Place, Suite 330,
//  Boston, MA  02111-1307  USA
//
//  http://openqrp.org/?p=343
//
//  "Trimmed" by Bill Bishop - wrb[at]wrbishop.com
//
///////////////////////////////////////////////////////////////////////////////
//
//                         openQRP CPU Pin Definitions
//
///////////////////////////////////////////////////////////////////////////////
//
// Digital Pins
//
int         tonePin  = 4;       // Tone output pin
int         LPin     = 5;       // Left paddle input
int         RPin     = 2;       // Right paddle input
int         ledPin   = 13;      //
//
////////////////////////////////////////////////////////////////////////////////
//
//  keyerControl bit definitions
//
#define     DIT_L      0x01     // Dit latch
#define     DAH_L      0x02     // Dah latch
#define     DIT_PROC   0x04     // Dit is being processed
#define     PDLSWAP    0x08     // 0 for normal, 1 for swap
#define     IAMBICB    0x10     // 0 for Iambic A, 1 for Iambic B
//
////////////////////////////////////////////////////////////////////////////////
//
//  Library Instantiations
//
////////////////////////////////////////////////////////////////////////////////
//
///////////////////////////////////////////////////////////////////////////////
//
//  Global Variables
//

unsigned long       ditTime;                    // No. milliseconds per dit
unsigned char       keyerControl;
unsigned char       keyerState;

#define NOTE_D5  587	// "pitch.h" at http://arduino.cc/en/Tutorial/Tone

///////////////////////////////////////////////////////////////////////////////
//
//  State Machine Defines

enum KSTYPE {IDLE, CHK_DIT, CHK_DAH, KEYED_PREP, KEYED, INTER_ELEMENT };

///////////////////////////////////////////////////////////////////////////////
//
//  System Initialization
//
///////////////////////////////////////////////////////////////////////////////

void setup() {

    // Setup outputs
    pinMode(ledPin, OUTPUT);      // sets the digital pin as output

    // Setup control input pins
    pinMode(LPin, INPUT);        // sets Left Paddle digital pin as input
    pinMode(RPin, INPUT);        // sets Right Paddle digital pin as input
    digitalWrite(ledPin, LOW);   // turn the LED off

    keyerState = IDLE;
    keyerControl = IAMBICB;      // Or 0 for IAMBICA
    loadWPM(15);                 // Fix speed at 15 WPM
}

///////////////////////////////////////////////////////////////////////////////
//
//  Main Work Loop
//
///////////////////////////////////////////////////////////////////////////////

void loop()
{
  static long ktimer;
  int debounce;

  // Basic Iambic Keyer
  // keyerControl contains processing flags and keyer mode bits
  // Supports Iambic A and B
  // State machine based, uses calls to millis() for timing.

  switch (keyerState) {
    case IDLE:
        // Wait for direct or latched paddle press
        if ((digitalRead(LPin) == LOW) ||
                (digitalRead(RPin) == LOW) ||
                    (keyerControl & 0x03)) {
            update_PaddleLatch();
            keyerState = CHK_DIT;
        }
        break;

    case CHK_DIT:
        // See if the dit paddle was pressed
        if (keyerControl & DIT_L) {
            keyerControl |= DIT_PROC;
            ktimer = ditTime;
            keyerState = KEYED_PREP;
        }
        else {
            keyerState = CHK_DAH;
        }
        break;

    case CHK_DAH:
        // See if dah paddle was pressed
        if (keyerControl & DAH_L) {
            ktimer = ditTime*3;
            keyerState = KEYED_PREP;
        }
        else {
            keyerState = IDLE;
        }
        break;

    case KEYED_PREP:
        // Assert key down, start timing, state shared for dit or dah
        digitalWrite(ledPin, HIGH);         // turn the LED on
        tone( tonePin, NOTE_D5 );
        ktimer += millis();                 // set ktimer to interval end time
        keyerControl &= ~(DIT_L + DAH_L);   // clear both paddle latch bits
        keyerState = KEYED;                 // next state
        break;

    case KEYED:
        // Wait for timer to expire
        if (millis() > ktimer) {            // are we at end of key down ?
            digitalWrite(ledPin, LOW);      // turn the LED off
            noTone( tonePin );
            ktimer = millis() + ditTime;    // inter-element time
            keyerState = INTER_ELEMENT;     // next state
        }
        else if (keyerControl & IAMBICB) {
            update_PaddleLatch();           // early paddle latch in Iambic B mode
        }
        break;

    case INTER_ELEMENT:
        // Insert time between dits/dahs
        update_PaddleLatch();               // latch paddle state
        if (millis() > ktimer) {            // are we at end of inter-space ?
            if (keyerControl & DIT_PROC) {             // was it a dit or dah ?
                keyerControl &= ~(DIT_L + DIT_PROC);   // clear two bits
                keyerState = CHK_DAH;                  // dit done, check for dah
            }
            else {
                keyerControl &= ~(DAH_L);              // clear dah latch
                keyerState = IDLE;                     // go idle
            }
        }
        break;
    }
}

///////////////////////////////////////////////////////////////////////////////
//
//    Latch dit and/or dah press
//
//    Called by keyer routine
//
///////////////////////////////////////////////////////////////////////////////

void update_PaddleLatch()
{
    if (digitalRead(RPin) == LOW) {
        keyerControl |= DIT_L;
    }
    if (digitalRead(LPin) == LOW) {
        keyerControl |= DAH_L;
    }
}

///////////////////////////////////////////////////////////////////////////////
//
//    Calculate new time constants based on wpm value
//
///////////////////////////////////////////////////////////////////////////////

void loadWPM (int wpm)
{
    ditTime = 1200/wpm;
}

This keyer program has few options which must be configured at compile-time.   This isn’t always convenient.  So, while gratifying, I still desired more functionality.   But, before I dug in adding features, I did a few more searches and found this absolutely brilliant project:

http://radioartisan.wordpress.com/arduino-cw-keyer/

It has tons of features (including memories) and can be implemented by adding a few more wires and components.   Most of the features are accessible by simply adding a push-button switch, which allows commands to be specified via the paddle. I chose to add a 10K ohm potentiometer, and enable remote speed control by un-commenting the line:

//#define FEATURE_POTENTIOMETER

Which has the keyer looking like:

Adding switching outputs and even a PS2 keyboard are among the many features.   Take some time to look around the site, there are many hidden treasures.   As K3NG points out, enabling many of the features at-once may cause “memory overflow” on the ATMega328P.   It’s been my experience that simply moving to the Arudino Mega 2560, with its larger memory space,  resolves this issue.      Don’t get discouraged waiting for the compile & download to the Arduino; it took about 1 and 1/2 minutes (small sketches take only seconds).

NOTE: that you don’t need to use an entire Arduino module to execute on a project.   Once the project is working, you can pop-out the Atmel chip from the Arduino (usually a ATMega328P) and wire directly up to the pins on a perf or proto-board (or an Evil Mad Scientist Target board).   The chips themselves are only four bucks and some change.   Be sure to buy ones that have the boot loader already installed, or else you’ll need to buy a separate programmer.   See this article for more details and clock parts.

Wrapping up, the project needs boxed & painted, more switches & potentiometers added,  and a power supply.   What did I end up doing?  I put the word out to the local radio artisans, and picked up a used MFJ-492X for a good price.

Additional Links:

Using an Iambic Paddle.
Keyers, Paddles, & More.
Voice & Morse Keyers.

This entry was posted in Amateur Radio. Bookmark the permalink.

20 Responses to Arduino Iambic Keyer and Side Tone Generator

  1. Ray says:

    Thanks for the interesting article. I just built it and I like it better than the “low-cost dot memory keyer” that I built from an article in qst in 1978. I know its a simple setup but it wouldn’t hurt to sketch out the wiring, I was unsure for a while about the V+ to connect to the resistors. I used the 5v output, would the 3v be ok?

  2. Howard says:

    Bill – Thanks for this. It runs fine but I want to add a pot for speed control, and I’m just learning Arduino. I’ve added int sensorPin=0; int sensorValue; sensorValue = analogRead(sensorPin);
    However, your sketch is based on specific e.g. 20 (wpm) so I can’t simply replace the number with sensorValue.
    I don’t know how to convert the analog output from pin 0 to a number like that. Any suggestions? Thanks

    • Bill says:

      Hello Howard,

      Be sure to set your sensorPin to “A0”, not just “0”. A simple tutorial can be found at “http://www.arduino-tutorials.com/arduino-analog-input/”. To change the speed, simply add the line: “loadWPM( (int)(analogRead( sensorPin ) * (20./1023.) + .5) )” — the analog input will vary from 0 to 1023. The “constant” 20./1023. will then scale the analog read value (with rounding) to between 0 and 20 WPM. Change the 20. to be whatever top speed you desire.

      You can put this into your setup() routine if you only care about changing speeds when you start-up/reset the Arduino, or inside the main “loop()” if you want to vary it on-the-fly.

      Obviously, you can optimize when the calculations are being done, and only perform them when the input value changes, but I’ll leave that up to you.

      Best regards,

      ->Bill

  3. Brian says:

    Boy I had a lot of fun with this one… Built it on a Maker Shield and added the speed control through the maker shield pot. Increased max speed to 30wpm and wow! what a blast! Thanks for sharing this.

    Here’s how I added the speed pot: (add somewhere after “Main Work Loop”)

    I’ll be looking for other ways to add to this – Thanks!!

  4. Reg says:

    Thank you for this project. It makes a simple and straightforward keyer for next to no money. I got it working first time, but had to puzzle out the red and black wires – red to resistors and 5Volts and black to ground the socket for attaching the keyer. A bit of thought and it suddenly made sense….. a little circuit diagram would have helped. In the comments left by different people is the way to adjust the speed…. perfect !

    I can play with this and then build it up to the advanced version.

  5. Andy says:

    This is a great project for learning Arduino programming. The two resistors can be eliminated by using the “INPUT_PULLUP” constraint. Works great even a 30 WPM.

  6. Joe says:

    I also have a Bencher paddle waiting to be reactivated.
    I wonder if this project could be operated on battery power. Have you ever measured the power consumption of your Arduino?

    • Bill says:

      Hi Joe,

      I have several battery powered Arduino projects; in a one or two cell LiPo pack with a step up/down voltage regulator from Pololu.com. The LiPo cells require special chargers, and be sure you don’t run them down too far. The energy density of the LiPo cells is quite high (I use them to fly R/C planes too), so I’ve never worried about the current draw of the Arduino. Adafruit.com or Sparkfun.com sell LiPo packs for Arduinos last time I looked.

      Good luck!

      =>Bill

  7. PP5VX (Bone) says:

    I use this very nice code here, for about four years.
    I am a Systems Engineer and love to code for the past 30+ yrs !
    And looked that several people done some mod or another…
    I look 3 mods on Internet, but only one that use “PDLSWAP” (Paddle Swap)
    And no one using “ULTIMATIC” ( …but do this on my own code ! )

    So, if all of you permits, I have some advice below.
    And can sugest it for all of you, that are getting a “white hair” !
    ( Algebra is beatiful to do some “tricks”, but think now on “map” – below )

    1. ( Need ) For Speed ( LOL ):
    => Write this at the very begin of code:
    #define MIN_WPM 13 // Minimum Speed in WPM ( Too low become a mess )
    #define MAX_WPM 50 // Maximum Speed in WPM ( …I love QRQ ! hi )
    => On Analog Read of Speed Pot ( 100 k ), put this line:
    key_speed = map(analogRead(A0), 10, 1000, MIN_WPM, MAX_WPM);

    Notes:
    PSE: Don’t try “stock values”, like “0” to “1023” on ADC reading (a bad choice !)
    If you believe in Santa, don’t believe that “stock values” gives “linear” response !

    2. For Sidetone:
    => Write this ( too ! ), at the very begin of code:
    #define MIN_HZ 400 // Minimum Tone in HZ
    #define MAX_HZ 700 // Maximum Tone in HZ
    => On Analog Read of Tone Pot ( 100k ), put this line:
    key_tone = map(analogRead(A1), 10, 1000, MIN_HZ, MAX_HZ);

    Notes:
    (Again !) don’t try “stock values”, like “0” or “1023” on ADC reading (a bad choice !)
    I can advice too, that is a VERY BAD MISTAKE to put Tone too low or too high
    Between 400 HZ and 700HZ – is a VERY GOOD “real” CW choice !
    Because **no one** in World, use 1000 HZ in CW, on any Band ( get it ? )

    3. For Weight:
    => And as usual, write this at the very begin of code:
    #define MIN_WGT 40 // Minimum Weight in % ( 40% )
    #define MAX_WGT 75 // Maximum Weight in % ( 75% )
    => On Analog Read of Tone Pot ( 100k ), put this line:
    key_weight = map(analogRead(A2), 10, 1000, MIN_WGT, MAX_WGT);

    Notes:
    But to implement Weight, are an exercise for all of you !
    And I have some more “white hair”, because of it ( LOL )

    Enjoy !

    73/DX de PP5VX ( Bone )
    LABRE ( Remido ) – ARRL ( LM ) – AMSAT/NA ( VT: Telemetry Team )

  8. PP5VX (Bone) says:

    Hummm – I bet that some of you even do the “home lesson” !
    So… Put a 100k Pot on A2 ( for Weight ), and try what is below

    1. Define some new variables ( at begin – public ! ):
    byte key_speed = 0; // Keying Speed
    int key_tone = 0; // Keying Tone
    byte key_weight = 0; // Keying Weight

    ( …repeating )
    2. For Weight:
    => As usual, write this at the very begin of code:
    #define MIN_WGT 40 // Minimum Weight in % ( 40% )
    #define MAX_WGT 75 // Maximum Weight in % ( 75% )
    => On Analog Read of Tone Pot ( 100k ), put this line:
    key_weight = map(analogRead(A2), 10, 1000, MIN_WGT, MAX_WGT);

    ** Here is the “trick” – PSE: Pay attention to Mods !
    1. On DIT State:
    case CHK_DIT:
    // See if the dit paddle was pressed
    if (keyerControl & DIT_L) {
    keyerControl |= DIT_PROC;
    ==> ktimer = ditTime; // Dit Time ( DIT = One BIT )
    ……….
    Put this line at “==>”:
    ktimer = ditTime*(key_weight/50.0); // Dit Time+Weight

    2. On DAH State:
    case CHK_DAH:
    // See if dah paddle was pressed
    if (keyerControl & DAH_L) {
    ==> ktimer = ditTime*3; // DAH Time ( DAH = 3 * BIT = 3 BITS )
    ……….
    Put this line at “==>”:
    ktimer = ditTime*(3*(key_weight/50.0)); // Dah Time+Weight

    3. On KEYED State ( …as a BONUS ! ):
    case KEYED:
    // Wait for timer to expire
    if (millis() > ktimer) { // are we at end of key down ?
    digitalWrite(ledPin, LOW); // turn the LED off
    noTone( tonePin );
    ==> ktimer = millis() + ditTime; // inter-element time
    ……….
    Put this line at “==>”:
    ktimer = millis()+ditTime*(2-key_weight/50.0); // InterElement Time+Weight

    Hope that you enjoy my answers, but there are some good mods to do yet !
    But, I left for you as another exercise ! Enjoy again…

    73/DX de PP5VX ( Bone )
    I was hamming for the past 43 yrs ( …and Programming for abt 35 yrs ! )
    My “passionate mode” ? You BET: “CW4EVER” of course !
    And mostly **above** 40 wpm in English, French ou Brazilian Portuguese
    Can you need IOTA SA-027 ? I am VERY QRV from it !
    And a QRS QSO, if is your case… ( hi )

  9. Ed says:

    Hi – I built this using an Arduino Nano with a 10k pot to set the speed. I also used the map() and INPUT_PULLUP suggestions above.

    I’ve also added a function to swap the paddles which works by holding the key you want to be the dot paddle when resetting the board. After that, the setup is read from the Arduino’s EEPROM.

    All worked great except I needed decoupling capacitors on the paddle lines – I used 0.1uF to ground on both paddle pins on the Arduino.

    Ed.

  10. Dave says:

    I have available a WeMos Arduino. Is this project compatible with this board?

    • Bill says:

      It should be, the pin-outs may be different.

      • Dave says:

        Hi Bill, Indeed they were! Took me a while to find a reference from WeMos, that the pinouts on the WeMos board are for the GPIO pins!

        If anyone else uses this device, here is the website to get the right pin numbers to put in the sketch:

        https://wiki.wemos.cc/products:d1:d1_mini

        Pin
        Pin Function ESP-8266 Pin
        TX TXD TXD
        RX RXD RXD
        A0 Analog input, max 3.3V input A0
        D0 IO GPIO16
        D1 IO, SCL GPIO5
        D2 IO, SDA GPIO4
        D3 IO, 10k Pull-up GPIO0
        D4 IO, 10k Pull-up, BUILTIN_LED GPIO2
        D5 IO, SCK GPIO14
        D6 IO, MISO GPIO12
        D7 IO, MOSI GPIO13
        D8 IO, 10k Pull-down, SS GPIO15
        G Ground GND
        5V 5V –
        3V3 3.3V 3.3V
        RST Reset RST

  11. Thank you. I got this working with an Adafruit Circuit Playground Classic (CPC)(https://www.adafruit.com/product/3000).

    It’s nice because there’s an on-board piezo (loud enough for testing) so no external speaker is required. The piezo is on pin 5, so I had to reassign LPin and RPin, but that’s just one keystroke each.

    I tried using internal pullup resistors, but it did not like that, so I put in 10K’s between the power rail on my breadboard and each row where I have on wire to the CPC and one to the jack that my paddles are plugged into.

    I may move this to a PCB, but for now I’m, just happy that I got it working quickly.

    CPC has pads designed for alligator clips, so that eases things, too.

    15wpm is fine for where I am now, but I also created a version using a 100K pot to control speed when I need to, using:
    ditTime = 1200/map(analogRead(potPin),10,1000, MIN_WPM, MAX_WPM);
    (saves a few lines of code)

    There are other options–Adafruit has an express version of the board with more memory and a faster processor that can use a version of Python, and a platform with readily available hardware features (most relevant here is an amplified on-board speaker). When I get to it…

  12. Going further with overkill, I created another version with an Adfruit Feather M0 Express (https://www.adafruit.com/product/3403) on an Adafruit Crickit for Feather (https://www.adafruit.com/product/3343). I used the same code without change, just recompiled for the new board, and added a 4Ohm 3W speaker (driven by Crickit’s on-board amp). Works like a charm and is louder than the prior version’s on-board piezo.

  13. Andy G0SFJ says:

    I have built this and it’s great, thanks! I am learning how to use iambic keyers following tendonitis in my hands that stops me using a straight key.

    I was wondering if it would be possible to add a line to actually key a transmitter. I suppose the transmitter ptt would be a positive GPIO connection and ground? and would need a transistor?

Leave a Reply

Your email address will not be published. Required fields are marked *