OOSMOS Morse Code Keyer Example
1. Introduction
2. State Chart
3. Keyer Source Code
4. Keyer on Arduino
5. Keyer on PIC32
6. Keyer on ChipKit
7. Keyer on MSP430
8. Keyer on Windows

1. Introduction

This example is an implementation of a Morse code keyer, useful to Ham Radio CW operators using Iambic paddles (see Figure 1) to send Morse code.
CW (Morse code) is formed with DIT and DAH sounds (or, if you prefer, dots and dashes). A Dit is a short tone and a Dah is a long tone. An iambic key has two paddles. One paddle generates the Dah sound and the other the Dit sound. (In the U.S., the Dah paddle is usually the right-hand paddle.) With the assistance of a microcontroller and software like in this example, an operator, using a combination of taps and holds of these paddles, can form perfectly spaced Morse code characters.
For example, the letter B (DahDitDitDit in Morse code) is formed by tapping the Dah paddle immediately followed by a press-and-hold of the Dit paddle. Dits are repeatedly generated by the software until the operator releases the Dit paddle. Thus, with only two paddle depressions, we can generate a perfectly formed B.
Figure 1. Iambic Paddles
Here is a video of an operator using iambic keyer paddles.
This keyer example uses the generalized keyer class in the Classes directory and is documented here.

2. State Chart

This is the state chart for the keyer class.
Keyer Class State Chart

3. Keyer Source Code

This is the code for the keyer class. The code generated from the state chart is inside the insertion markers.
1
22
23
24
25
26
27
28
29
30
31
32
33
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
204
205
206
207
208
209
210
211
219
220
221
222
223
224
225
226
227
228
229
[GPLv2]
 
#include "oosmos.h"
#include "keyer.h"
#include "pin.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
 
struct keyerTag
{
//>>>DECL
[Code Generated from State Chart]
//<<<DECL
 
pin * m_pDahPin;
pin * m_pDitPin;
pin * m_pSpeakerPin;
 
uint32_t m_DitTimeMS;
uint32_t m_DahTimeMS;
uint32_t m_SpaceTimeMS;
 
bool m_DitWasPressed;
bool m_DahWasPressed;
};
 
static void CheckDahIsPressedPoll(keyer * pKeyer)
{
if (!pKeyer->m_DahWasPressed && pinIsOn(pKeyer->m_pDahPin)) {
pKeyer->m_DahWasPressed = true;
}
}
 
static void CheckDitIsPressedPoll(keyer * pKeyer)
{
if (!pKeyer->m_DitWasPressed && pinIsOn(pKeyer->m_pDitPin)) {
pKeyer->m_DitWasPressed = true;
}
}
 
static bool DahWasPressed(const keyer * pKeyer)
{
return pKeyer->m_DahWasPressed;
}
 
static bool DitWasPressed(const keyer * pKeyer)
{
return pKeyer->m_DitWasPressed;
}
 
static bool IsDitPressed(const keyer * pKeyer)
{
return pinIsOn(pKeyer->m_pDitPin);
}
 
static bool IsDahPressed(const keyer * pKeyer)
{
return pinIsOn(pKeyer->m_pDahPin);
}
 
static void DitThread(const keyer * pKeyer, oosmos_sState * pState)
{
oosmos_ThreadBegin();
pinOn(pKeyer->m_pSpeakerPin);
oosmos_ThreadDelayMS(pKeyer->m_DitTimeMS);
pinOff(pKeyer->m_pSpeakerPin);
 
oosmos_ThreadDelayMS(pKeyer->m_SpaceTimeMS);
oosmos_ThreadEnd();
}
 
static void DahThread(const keyer * pKeyer, oosmos_sState * pState)
{
oosmos_ThreadBegin();
pinOn(pKeyer->m_pSpeakerPin);
oosmos_ThreadDelayMS(pKeyer->m_DahTimeMS);
pinOff(pKeyer->m_pSpeakerPin);
 
oosmos_ThreadDelayMS(pKeyer->m_SpaceTimeMS);
oosmos_ThreadEnd();
}
 
//>>>CODE
[Code Generated from State Chart]
//<<<CODE
 
extern keyer * keyerNew(pin * pDahPin, pin * pDitPin, pin * pSpeakerPin, unsigned WPM)
{
oosmos_Allocate(pKeyer, keyer, 1, NULL);
 
//>>>INIT
[Code Generated from State Chart]
//<<<INIT
 
pKeyer->m_pDahPin = pDahPin;
pKeyer->m_pDitPin = pDitPin;
pKeyer->m_pSpeakerPin = pSpeakerPin;
pKeyer->m_DitTimeMS = 1200 / WPM;
pKeyer->m_DahTimeMS = pKeyer->m_DitTimeMS * 3;
pKeyer->m_SpaceTimeMS = pKeyer->m_DitTimeMS;
 
return pKeyer;
}
Class keyer.c

4. Keyer on Arduino

Using our portable keyer object, we will now write the small bit of code specific to the Arduino. This example uses the Arduino Uno, but all Arduinos (that we know of) will run this example.

4.1 Keyer on Arduino - Wiring Diagram

Figure 2 is a Fritzing wiring diagram of the components. We have 3.5mm jack to accommodate a stereo plug for the keyer. The Dit paddle goes to pin 6 and the Dah paddle to pin 7. The buzzer is hooked to pin 13, which, on the Uno, is the pin that is hooked to the built-in, on-board LED, so when the speaker sounds, the LED also lights up.
The Arduino has built-in pull up resistors capability but we don't use it. Because not all boards have this capability, we use external resistors so that we can make a single breadboard that works across all devices.
Figure 2. Arduino Wiring Diagram

4.2 Keyer on Arduino - Code

This is the main program for an Arduino target where we implement the Arduino-standard setup and loop functions. In setup, we create three pin objects; two for the digital inputs for the Dit and Dah paddles as well as one for the "speaker" output. (This does not really drive a speaker, rather a piezoelectric buzzer.)
Once the pin objects are created, we pass them to the keyer constructor. Because the keyer object creates a State Machine object that automatically registers the object with OOSMOS, we can then call oosmos_RunStateMachines in the loop function and the keyer state machine will be run repeatedly.
1
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
[GPLv2]
 
#include "oosmos.h"
#include "pin.h"
#include "keyer.h"
#include "prt.h"
 
extern void setup()
{
pin * pDitPin = pinNew(6, pinIn, pinActiveLow);
pin * pDahPin = pinNew(7, pinIn, pinActiveLow);
pin * pSpeakerPin = pinNew(13, pinOut, pinActiveHigh);
 
const int WPM = 5;
 
keyerNew(pDahPin, pDitPin, pSpeakerPin, WPM);
}
 
extern void loop()
{
oosmos_RunStateMachines();
}
Keyer on Arduino - KeyerExample.ino

5. Keyer on PIC32

Using the portable keyer object, we can write the small bit of code specific to the PIC32 Starter Kit. The PIC32 architecture has a different way of addressing pins than does, say, an Arduino, but the pin class abstraction hides these details such that the same keyer object can be portable to all microcontrollers.
Note that the PIC32 needs to have some specialized #pragma directives to properly setup the chip. We also have to tell the run-time environment that the clock speed is 80 MHz (see line 33 of main.c).
We use the PIC32 Starter Kit board for this demo along with the I/O Expansion Board headers J10 to gain access the processor's pins. (See figure 3.)
Figure 3. PIC32 Starter Kit with I/O Expansion Board
The documentation related to which pins
Figure 4. I/O Expansion Board, Header J10
Figure 5. PIC32 Wiring Diagram
The PIC32 does not have a setup and loop structure like the Arduino-family does. Instead, we simply implement the main function and call oosmos_RunStateMachines in a loop once all the pin and keyer objects have been created.
There are a variety of PIC32 Starter Kits. When you open up any of our PIC32 projects with MPLAB X, you may need to change the processor type to get a clean compile.
1
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
[GPLv2]
 
#include "oosmos.h"
#include "pin.h"
#include "keyer.h"
#include <stdbool.h>
 
#pragma config FPLLMUL = MUL_20, FPLLIDIV = DIV_2, FPLLODIV = DIV_1, FWDTEN = OFF
#pragma config POSCMOD = HS, FNOSC = PRIPLL, FPBDIV = DIV_1
 
extern int main(void)
{
oosmos_ClockSpeedInMHz(80);
 
pin * pDahPin = pinNew(IOPORT_C, BIT_1, pinIn, pinActiveLow);
pin * pDitPin = pinNew(IOPORT_C, BIT_2, pinIn, pinActiveLow);
pin * pSpeakerPin = pinNew(IOPORT_C, BIT_3, pinOut, pinActiveHigh);
 
const unsigned WPM = 15;
 
keyerNew(pDahPin, pDitPin, pSpeakerPin, WPM);
 
for (;;) {
oosmos_RunStateMachines();
}
}
Keyer on PIC32 - main.c

6. Keyer on ChipKit

Using the portable keyer object, we can write the small bit of code specific to the ChipKit. Because the ChipKit is Arduino pin compatible, we can use the same pin descriptions as the Arduino example.
Figure 6. ChipKit Wiring Diagram
1
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
[GPLv2]
 
#include "oosmos.h"
#include "pin.h"
#include "keyer.h"
#include "prt.h"
 
extern void setup()
{
pin * pDitPin = pinNew(6, pinIn, pinActiveLow);
pin * pDahPin = pinNew(7, pinIn, pinActiveLow);
pin * pSpeakerPin = pinNew(13, pinOut, pinActiveHigh);
 
const int WPM = 5;
 
keyerNew(pDahPin, pDitPin, pSpeakerPin, WPM);
}
 
extern void loop()
{
oosmos_RunStateMachines();
}
Keyer on ChipKit - KeyerExample.ino

7. Keyer on MSP430

Using the portable keyer object, we can write the small bit of code specific to an MSP430F5529 LaunchPad board.
Fritzing does not have a part for the MSP430F5529 LaunchPad board, so we created a reasonable representation of the board's headers in order to show the connections.
MSP430 Wiring Diagram
The pin designations for the MSP430F5529 LaunchPad board are not the same as Arduino Uno pins. (See lines 29-31.)
1
22
23
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
[GPLv2]
 
#includes...
 
extern void setup()
{
pin * pDitPin = pinNew(P1_2, pinIn, pinActiveLow);
pin * pDahPin = pinNew(P1_3, pinIn, pinActiveLow);
pin * pSpeakerPin = pinNew(P1_4, pinOut, pinActiveHigh);
 
const int WPM = 15;
 
keyerNew(pDahPin, pDitPin, pSpeakerPin, WPM);
}
 
extern void loop()
{
oosmos_RunStateMachines();
}
Snippet 1. Keyer on MSP430 - KeyerExample.ino

8. Keyer on Windows

Using the portable keyer object and the multi-platform pin class, we can support a Windows version of the Keyer class, mostly for educational purposes.
1
22
23
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
[GPLv2]
 
#includes...
 
extern int main(void)
{
printf(". (period) - dit\n");
printf("/ (slash) - dah\n");
printf("\n");
 
pin * pDitPin = pinNew('.', pinActiveHigh);
pin * pDahPin = pinNew('/', pinActiveHigh);
pin * pSpeakerPin = pinNew(' ', pinActiveHigh);
 
keyerNew(pDahPin, pDitPin, pSpeakerPin, 20);
 
for (;;) {
oosmos_RunStateMachines();
oosmos_DelayMS(1);
}
}
Keyer on Windows - main.c
Copyright © 2014-2025  OOSMOS, LLC