Tuesday, June 2, 2020

Iambic Keyer


This article is a work in progress.  I have not yet posted the complete code below as I am trying to decide if I should make it a single file, leave it as multiple files or provide a link to a public github location.  I am actively updating this post and will remove these comments when the posting is complete.  I appreciate your patience meanwhile.


Back in 2012 I published a project that implemented an iambic keyer/transmitter using the Parallax Propeller.  This was a simple project and worked well though the chipset used to implement it is not mainstream.  The code is still available in the github link posted in that article should you be interested.  This article will describe the architecture and implementation details of a new and expanded version of the concepts originally implemented using the propeller but using a more commonly used micro-controller.

Here, I intend to use a garden variety Arduino based on the lowly 8-bit AVR processors as found in the Uno, Nano and other variants (ATMega328).  My plan is to publish an annotated source code discussion and at the end the full source that can be simply copied and pasted if you are using an Arduino Uno or equivalent.  Porting to a different board should be as simple as remapping I/O pins used for paddles, transmitter keying output, sidetone PWM and CW speed control.  If you need help with this, drop me a note at ko7m at arrl dot net and I will help you out.

The code describe here is implemented as a C++ class named (oddly enough) "keyer".  Since this class will be reading and controlling several GPIO pins, in general it makes little sense to have more than a single instance of the class although you certainly could implement a multi-operator keyer (single arduino controlling multiple transmitters, keyed by multiple operators), it seems unlikely in general that this would be necessary.  I have therefore implemented a singleton static instance of the class within the keyer::getInstance() method that will return a pointer to the singleton class object.  This should be sufficient for 99.9% of the use cases in a typical ham radio station.

File Structure

There is a single ko7mKeyer.ino file containing the normal Arduino sketch setup and loop functions.  Beyond this file, there is an analog conversion bit of code that handles setup of the ADC (analogue to digital conversion) clocks (analog.h and analog.cpp).  There is a timer module that also provides base handlers for pin change interrupts (timer.h and timer.cpp).  The rest of the code is in ko7mKeyer.h and ko7mKeyer.cpp.  Each of these files will show up as separate text tabs in the Arduino IDE.  When you build the project all files will be built as necessary.

Code Walk-through

Here we will visit the code and describe its function section by section.  We pull in keyer definitions from ko7mKeyer.h and declare a pointer to the keyer class.

// ko7mKeyer.ino - Iambic ko7m keyer
// Copyright 2014-2020 - Jeff Whitlatch - ko7m

#include "ko7mKeyer.h"

keyer *pKeyer = NULL;

During the normal Arduino setup, we initialize the serial port for debug output.  We also speed up the ADC by modifying the clock prescaler.  We want analogRead operations to be a quick as possible.  This modification of the clock will significantly reduce the time required to determine the position of the CW speed pot at the cost of a minor amount of noise.  We then set up pin change interrupts on the keyer paddle pins and the debounce timeout timer.  At that point we are ready to grab the keyer class object pointer and initialize the keyer using default settings.

// Application start initialization
void setup()
    Serial.println("ko7m keyer");
    // Change the ADC clock prescaler to speed up ADC conversion.  This will help
    // the performance of the CW speed potentiometer.

    // Common timer and pin change interrupt initialization

    // Retrieve the singleton instance of the keyer object and hang if it cannot be found
    pKeyer = keyer::getInstance();
    if (pKeyer != NULL)
        while(1);       // Hang the processor as there is no keyer object

Now we have the main program loop which keeps track of the period between calls to the keyer check method to move the keyer state machines along.  If the CW speed pot has moved, the new words per minute (WPM) value is returned and displayed on the debug output monitor.

// Main program loop
void loop()
    static unsigned long lastms = 0;
    unsigned long msCounter = millis();
    unsigned long ms = msCounter - lastms;
    static int wpmLast = 0;

    // Pass the number of milliseconds since the last call to control the period between
    // reads of the CW speed pot to a reasonable rate
    int wpm = pKeyer->check(ms);
    if (wpm != wpmLast)
        Serial.print(wpm, DEC);
        Serial.println(" WPM");
        wpmLast = wpm;

    lastms = msCounter;

I will not go into the function of the analog.cpp and analog.h files.  They basically provide the ability to change the analog to digital (ADC) conversion clock prescaler which controls how long analogRead takes to return.  We want it to be fast without introducing too much noise so that checking the CW speed pot every 1/2 second or so does not interfere with the timing of the keyer.  I leave the analysis of these two files to the interested reader.  If you have questions, drop me a note at ko7m at arrl dot net and I will do my best to help.

The timer files provide two services, a 3 millisecond debounce timer and pin change interrupt handlers for the keyer paddles.  It must be remembered that these two files are very specific to the particular Arduino that is in use, in this case the Arduino Uno.

// timer.h - Timer handler

#pragma once

#include <Arduino.h>

class timer
    static timer *getInstance();
    void Init();

Timer.h declares the timer class and Timer.cpp provide the implementation of the class.  The basic idea behind this code is that certain digital I/O pins are set to use pin change interrupts so that anytime the state of these pins change, an interrupt is called to handle the change.  This eliminates polling of the paddles to detect when they are pressed.  Since the paddle contacts can bounce, the interrupt handler will reset a 3 ms timeout anytime any of the pins change.  If the timer itself times out, this means that none of the pins (paddles) has changed for 3 ms.  The handler for the timer timeout event will then update the new debounced pin state.

// timer.cpp - Timer1 handler

#include "timer.h"

// Externals
// Callback functions for pin change and timer interrupt service routines
bool keyerPinChange();
void keyerTimer();

// Create a static singleton instance of this class and return a pointer

// to the instance
timer *timer::getInstance()
    static timer _timerInstance;
    return &_timerInstance;

// Pin change interrupts for Arduino Uno ATMega328
// Digital pin     PC Pin     Vector      Ctl Reg Port PCICR Bit Pin Mask Reg
// D0-D7           PCINT16-23 PCINT2_vect PCIR2   PD   PCIE2     PCMSK2
// D8-D13          PCINT0-5   PCINT0_vect PCIR0   PB   PCIE0     PCMSK0
// D14-D19 (A0-A5) PCINT8-13  PCINT1_vect PCIR1   PC   PCIE1     PCMSK1
// Initialize pin change interrupts for rotary encoder, keyer paddles
// and straight key.  Obviously this code is hardware configuration specific.
// PCMSK0 needs to be set to enable each pin that will use pin change interrupts.
void timer::Init()
    cli();      // Disable interrupts

    // Port B configuration
    // Set pin mask for paddle pins 11 and 12 on Port B
    PCMSK0 |= B00011000;

    // Set pin change interrupt enable for port B (paddles)
    PCICR  |= (1<<PCIE0);

    // Set timer 1 for 3 ms debounce timeout
    TCCR1A = 0;             // set entire TCCR1A register to 0
    TCCR1B = 0;             // same for TCCR1B
    TCNT1  = 0;             // initialize counter value to 0

    // set compare match register 3 ms
    OCR1A = 48000;

    // turn on CTC mode
    TCCR1B |= (1 << WGM12);

    // Set bits for no prescaler
    TCCR1B |= (1 << CS10);

    // enable timer compare interrupt
    TIMSK1 |= (1 << OCIE1A);

    sei();      // Re-enable interrupts

// Pin change interrupt handlers for any GPIO pin that enables pin

// change interrupts.  Calls each handler for each pin or pin group
// Pin change ISR for port B
    // Call handler and reset timer as long as any handler returns true.
    if (keyerPinChange())
        TCNT1  = 0;                 // Zero the timer
        TIMSK1 |= (1 << OCIE1A);    // Re-enable it to start the debounce time over

// Timer ISR - We get here if the debounce timer times out.  Call back the paddle
// and key handlers to process debounced digital inputs.  Disables Timer 1 until

// the next pin change event.
    // Call the handlers

    // Disable Timer
    TIMSK1 &= ~(1 << OCIE1A);

Now that we have debug output and the ability to detect and debounce changes in the keyer paddles, we are ready to take on the main keyer code.  The default class constructor just initializes some of the state variables to sane values.  If you provide a separate I/O pin for a straight key input, it will need be initialized as an input with a pull-up resistor.  If a pull-up is provided in hardeare, you can comment out the line.  A static instance of the class is instantiated in the getInstance method and an accessor function is provided to return a pointer to that class instance.

//  keyer.cpp - Iambic keyer
//  Copyright 2014 - Jeff Whitlatch - ko7m
//  http://morse-rss-news.sourceforge.net/keyerdoc/modeab.pdf
//  http://morse-rss-news.sourceforge.net/keyerdoc/K7QO_Iambic_Paddle.pdf
//  https://ag6qr.net/index.php/2017/01/06/iambic-a-or-b-or-does-it-matter/
//  https://www.qsl.net/pa2ohh/iambic.htm

#include "ko7mKeyer.h"

// Default constructor
    dahState = ditState = 1;
    dahBuffer = ditBuffer = dahMemory = ditMemory = dahIambicBuf = ditIambicBuf=0;
    pinMode(defaultStraightKeyPin, INPUT_PULLUP);

// Create a static singleton instance of this class and return a pointer to the

// instance
keyer *keyer::getInstance()
    static keyer _keyerInstance;
    return &_keyerInstance;

Read an analogue input connected to speed control pot and return the current WPM value.  Ideally the speed control would be a linear pot.  If using a log taper pot, you should wire it so the slowest changing end of the taper is at the end of the speed range that you typically spend your time.  If you spend most of your time at 30 WPM or more, you will want the slowest tuning of the speed at that end of the range.  If on the other hand, you spend most of your time at 15 WPM, you will want the slowest tuning of the speed at the low end of the range.  Use a linear taper pot if at all possible for best results at all speeds.  We also provide set/get accessor methods for things like current WPM speed, sidetone frequency, etc.

// Read the CW speed pot and return the value in words-per-minute
int keyer::readSpeedControl()
    int wpm = defaultWPM;
    int speed = analogRead(speedPin);

    // Handle noisy ADC value at extremes.  cap anything above or below the

    // min/max value
    if (speed < analogMin) speed = analogMin;  
    if (speed > analogMax) speed = analogMax;

    // Remap analogue input range to min/max WPM
    // Speed control min/max should be customized in ko7mKeyer.h to the

    // desired range.

    // Direct mapping of the pot range to the CW speed range
    wpm = map(speed, analogMin, analogMax, minWPM, maxWPM);

    // Linear taper mapping of a log taper pot

    //wpm = multiMap(speed, inVal, outVal, 11);

    return wpm;

// Return current WPM speed value
int keyer::getSpeed()
    return WPM;

// Set new WPM speed value, constrained to the min/max values
void keyer::setSpeed(int newWPM)
    WPM = min( max( newWPM, minWPM ), maxWPM );
    ditTime = 1200 / WPM;

// Returns the current sidetone frequency
int keyer::getSidetoneFreq()
    return sidetoneFreq;

Sidetone is a raw PWM output and should be minimally filtered with an R/C low pass filter. Since the Arduino PWM samples at 32 Khz, Nyquist says that the highest frequency we can produce is 16 Khz.  In reality Ardunino tone range limits are 31 - 65535 Hz.  So putting a low pass filter cutoff around 15 KHz will be more than adequate.

cutoff frequency = 1 / (2 * pi * R * C)

Using a 1000 ohm resistor for R:

15000 = 1/(2000 * pi * C)
C = 1/(30000000 * pi)
C = 1.06103295e-8 farad or 0.0106103295 microfarads.

I suggest using .01 uF with a 1K0 resistor

// Set the generated sidetone frequency.  
void keyer::setSidetoneFreq(int freq)
    sidetoneFreq = freq;

// Swap the keyer paddles from default of dit on the left and dah on the right
void keyer::swapPaddles()
    ditPin ^= dahPin;
    dahPin = ditPin ^ dahPin;
    ditPin ^= dahPin;

Initialize the Arduino pins used by the keyer.  Full control is provide for all pins. speedControl pin must be analogue input.  The rest can be either digital or analogue.  If using pin change interrupts on paddles, you will need to tend to the pin enable mask and for convenience limit the paddles to the same port on the Arduino.  Sidetone pin must support PWM output. If you modify the dit and dah paddle pins, you will need to change the code in keyerPinChange and keyerTimer callback functions at the bottom of this file.  You will also need to tend to the timer::Init method to set the pins change interrupt mask to enable interrupts on the new paddle pins and port in the timer.h and timer.cpp files.  Contact me for assistance if you have issues and need to change which pins are used.

// Initialize the I/O pins used by the keyer
void keyer::initPins(int dit, int dah, int key, int sidetone, int speedControl)
    ditPin = dit;
    dahPin = dah;
    keyPin = key;
    sidetonePin = sidetone;
    speedPin = speedControl;

    // If using external pull-up resistors, change INPUT_PULLUP to INPUT
    pinMode(ditPin, INPUT_PULLUP);
    pinMode(dahPin, INPUT_PULLUP);
    pinMode(speedPin, INPUT);
    pinMode(keyPin, OUTPUT);
    pinMode(sidetonePin, OUTPUT);

    WPM = defaultWPM;
    CPM = 0;
    ditBuffer = dahBuffer = 0;
    keyerMode = keyerModeSave = defaultMode;
    ditTime = 1200 / WPM;
    keyFlags = activeHigh;
    sidetoneFreq = defaultSidetoneFreq;

If during initPins we find that the dah key is stuck low, this will be interpreted to mean that a TS (tip-sleeve) straight key plug has been inserted in the paddle TRS (tip-ring-sleeve) jack.  Since there is no sleeve contact on a TS plug, the effect will be to short the DAH key to ground.  In this event, we will set the keyerMode to keyerModeStraight where the DAH key will be ignored.  We are only checking at init time (power-up) so the straight key would need be plugged in before powering on the keyer device in order for this detection to properly function.

    if (!digitalRead(dahPin))
        if (!digitalRead(dahPin))
            keyerMode = keyerModeSave = keyerModeStraight;

The keyer design has provision for two mechanisms for shifting from receive to transmit and sending a carrier.  A 3.5mm TRS jack is provided for standard iambic paddles along with a simple TR jack for a PTT, transmit key, foot pedal or whatever.  Pulling any of these inputs low will start transmitting. The keyer supports Iambic A, Iambic B, straight key, bug and single paddle modes.

Iambic A and B behave as in the description in the links at the top of this posting.  Straight key mode allows either the dit paddle or a straight key plugged into it or a separate straight key jack to key the transmitter. Bug mode allows the dit paddle to repeat while the dah paddle must be pressed manually for every dah sent.  Single paddle mode is (currently) the same as Iambic A, though in theory no Iambic output can be generated as only the dit or dah paddle switch physically can close at any given time.

TODO: Single paddle and bug mode need to prevent any iambic action while allowing a current symbol to complete.  For example if you are using a squeeze paddle while in either of these modes, the closure of both paddle switches should not be physically possible on a single paddle or bug.  So, we need to simulate that if possible by detecting the mode and the fact that both paddles are closed and allowing the currently playing symbol to complete and then sending the other symbol. If for example while in bug mode, if you hold the dah paddle and then tap the dit paddle, what should happen?  The same thing as on a mechanical bug of course.  Tapping the dit paddle would force the release of the dah paddle and play dits as long as the dit paddle was closed, complete the last dit playing and then go key up.  This should be doable. A single paddle should do likewise, except pressing the opposite paddle should force the original string of symbols to stop and the opposite symbol to play until the original paddle opens and then closes again.

If we are in straight key mode, ignore the dah paddle.  This should allow the straight key to be connected either with a TRS or a TS 3.5mm plug into the paddle TRS jack.  The dit paddle should be wired to the tip and the dah paddle to the ring.  So, if the dah paddle is continually shorted to ground, we will ignore it and not generate any iambic output.

When the straightkey (PTT) input is used, the keyer automatically switches to straight key mode. We restore the keyer mode whenever either paddle is pressed to whatever mode we were in before pressing the separate straight key input.

The following code provides for the ability to check debounced paddle state and generate dit and dah symbols as determined by the current mode and state of the keyer.

// Initialize the Arduino pins used by the keyer.  The default set of pins is used.
void keyer::Init()
    initPins(defaultDitPin, defaultDahPin, defaultKeyPin, defaultSidetonePin, defaultSpeedPin);

// Initialize the Arduino pins used by the keyer.  The default set of pins is used.
void keyer::initPins()
    initPins(defaultDitPin, defaultDahPin, defaultKeyPin, defaultSidetonePin, defaultSpeedPin);

// If the debounced dit paddle is depressed, or if we have buffered a dit, then
// we should generate a dit.
void keyer::checkDitPaddle()
   bool fSendit = false;

    // Save the (inverted) state of the paddle so it will not change while

    // we process it
    if (!ditState)
        ditBuffer = 1;       // This indicates we need to send a dit as that

                             // paddle is pressed

    fSendit = (ditBuffer || ditMemory);

    switch (keyerModeSave)
        case keyerModeStraight:
            if (ditBuffer)

            if (fSendit)
    ditBuffer = 0;

void keyer::checkDahPaddle()
    bool fSendit = false;

    // There is no dah paddle if a straight key is plugged into the paddle jack
    if (keyerModeSave == keyerModeStraight)

    // Save the (inverted) state of the paddle so it will not change while

    // we process it
    if (!dahState)
        dahBuffer = 1;
    fSendit = (dahBuffer || dahMemory);

    switch (keyerModeSave)
        // TODO: This needs a fix
        case keyerModeBug:
            if (dahBuffer)
            if (fSendit)
    dahBuffer = 0;

// This assumes there is a separate straight key or transmit key input
// on the defaultStraightKeyPin pin that will send a carrier as long as

// the input is low
void keyer::checkStraightKey()
    if (!digitalRead(defaultStraightKeyPin))
        ditBuffer = 1;
        keyerMode = keyerModeStraight;

// Check both paddles and the separate straight key input
void keyer::checkPaddles()

// Create dit or dah duration while checking for paddles to change.
// In Iambic modes, we always look for the opposite paddle to the
// element currently being sent.  If no current element is being sent
// we look for both paddles.
void keyer::delayAndWatchKey(int duration)
    if (duration > 0)
        endTime = millis() + duration;
        while (millis() < endTime)
            // Whichever symbol is being sent, watch the other key
                case sendingDit:
                    if (!dahState) dahMemory = 1;
                case sendingDah:
                    if (!ditState) ditMemory = 1;
                    // Intentional fall thru

// Send a dit while checking for paddles to change
void keyer::sendDit()
    beingSent = sendingDit;
    delayAndWatchKey(ditTime);    // Wait 1 dit time while checking paddles
    beingSent = sendingNothing;
    ditMemory = 0;

// Send a dah while checking for paddles to change
void keyer::sendDah()
    beingSent = sendingDah;
    delayAndWatchKey(3 * ditTime); // wait 3 dit timees while checking paddles
    beingSent = sendingNothing;
    dahMemory = 0;

The following methods will provide for keying the I/O pin responsible for keying the transmitter.  Both active high and active low output is possible as desired and indicated by keyFlags.

// Key the transmitter by setting the keyPin value high or low as indicated.
// Additionally, sidetone is generated.  This can be disable by commenting out
// the appropriate digitalWrite or tone function call below.
void keyer::keyDown()
    switch (keyFlags)
        case activeLow:
            digitalWrite(keyPin, LOW);
        case activeHigh:
            digitalWrite(keyPin, HIGH);
    tone(sidetonePin, sidetoneFreq);

// Unkey the transmitter and sidetone
void keyer::keyUp()
    switch (keyFlags)
        case activeLow:
            digitalWrite(keyPin, HIGH);
        case activeHigh:
            digitalWrite(keyPin, LOW);

The keyer check method is to be called from the main loop passing the number of milliseconds since the last call.  This argument is intended to allow timing periodic events by accumulating the amount of time that has passed until periodic intervals are reached.  Currently unused to limit how often we check the status of the CW speed pot for changes.  We check the paddles and send any buffered dits and dahs.  We read the CW speed pot and update the keyer speed anytime it changes and return the current wpm value to the caller.

// Conventional keyer processing from the main loop.  
int keyer::check(uint32_t msCounter)
    int wpm = getSpeed();
    static uint32_t last_time = 0;

    // Check for any change in the paddles

    if (msCounter - last_time >= SPEEDCHECKMS)
        last_time = 0;
        setSpeed(wpm = readSpeedControl());
    last_time += msCounter;

    return wpm;         // Pass back any changes in CW speed

The last set of methods in the keyer are call-back functions from interrupt handlers whenever the keyer paddes are opened or closed or when the debounce timer expires.  This code is very hardware specific.  As written, both dit and dah paddle pins must be on PORTB pins 10 and 11 on the UNO.  The trick here is to minimize interrupt latency, the code is written to assume a paticular port and set of two pins.  If the default pins are changed, the logic below must change to match the new pins and port.  Drop me a note if you have questions

// Callback function for pin change interrupt handler.  Save the last state of the
// paddle I/O pins.  Update the last value from the pin port.  Return true if
either of the pins has changed.
bool keyerPinChange()
    static uint8_t last;
    uint8_t prior;

    prior = last;
    last = (PINB >> 3) & 0x03;

    return (prior != last);

// Timer 1 callback handler for keyer paddles.  If this timer interrupt fires, then
// there have been no changes in either keyer paddle for 3 ms. Update the in-memory
// state variables to represent the position of both paddles.
void keyerTimer()
    keyer *pKeyer = keyer::getInstance();
    uint8_t newCode = (PINB >> 3) & 0x3;
    uint8_t oldCode = ((pKeyer->dahState << 1) | (pKeyer->ditState));

    if (newCode != oldCode)

        // ditState and dahState will represent debounced paddle state in real time
        pKeyer->ditState = (newCode >> 0) & 0x01;  
        pKeyer->dahState = (newCode >> 1) & 0x01;

The keyer header file defines the various I/O pins used and the keyer class.

// ko7mKeyer.h - Morse code iambic keyer library
// Copyright 2014 - Jeff Whitlatch - ko7m

#pragma once

#include <Arduino.h>
#include "analog.h"
#include "timer.h"

// Enumerations
enum { keyerModeA, keyerModeB, keyerModeSinglePaddle, keyerModeBug, keyerModeStraight, keyerModeFence };
enum { activeHigh, activeLow };
enum { sendingNothing, sendingDit, sendingDah };

// Constants
const int defaultDitPin         = 11;
const int defaultDahPin         = 12;
const int defaultSidetonePin    = 10;
const int defaultKeyPin         = A1;
const int defaultSpeedPin       = A2;
const int defaultStraightKeyPin = PD4;

const int defaultSidetoneFreq = 700;

const int defaultWPM         = 35;
const uint8_t maxWPM         = 60;
const uint8_t minWPM         = 10;

const int cwOffset           = 700;     // 700 Hz default CW offset

const int defaultMode        = keyerModeA;

const int analogMin          = 10;
const int analogMax          = 1010;

const int stuckKeyDelay      = 500;     // dahKey stuck for 1/2 second at boot, switch to straight key

const uint32_t SPEEDCHECKMS  = 500;     // Check speed changes every 1/2 second

class keyer
        keyer(int dit, int dah, int key, int sidetone, int speedControl);
        static keyer *getInstance();

        void Init();
        void initPins();
        void initPins(int dit, int dah, int key, int sidetone, int speedControl);

        int  check(uint32_t msCounter);
        int  readSpeedControl();
        int  getSpeed();
        void setSpeed(int newWPM);
        int  getSidetoneFreq();
        void setSidetoneFreq(int newFreq);
        void swapPaddles();
        void checkPaddles();
        void sendBuffer();

        int keyerModeSave;

        uint8_t ditState;           // The actual status of the paddle I/O pins in real time, debounced
        uint8_t dahState;
        uint8_t ditBuffer;          // Buffer of paddle state during processing
        uint8_t dahBuffer;
        uint8_t ditMemory;          // Single symbol buffers
        uint8_t dahMemory;
        uint8_t ditIambicBuf;       // Iambic B symbol buffers
        uint8_t dahIambicBuf;

        int WPM;
        int CPM;

        int keyerMode;
        int ditTime;

        int keyFlags;
        int beingSent;

        int ditPin;
        int dahPin;
        int keyPin;
        int sidetonePin;
        int speedPin;

        int sidetoneFreq;

        uint32_t endTime;

        void checkDitPaddle();
        void checkDahPaddle();
        void checkStraightKey();
        void delayAndWatchKey(int duration);
        void sendDit();
        void sendDah();
        void keyDown();
        void keyUp();

        // CW speed potentiometer interpolation table
        // The speed pot is a log taper device.  Here we create an interpolation table to remap
        // the output to be more linear.  The following table is used to build the interpolation
        // table used below.

        //Position  Resistance   ADC    WPM
        //   0.00%    0.00%        0     10
        //  10.00%    2.00%       20     13
        //  20.00%    4.00%       41     16
        //  30.00%    6.00%       61     19
        //  40.00%    8.00%       82     22
        //  50.00%   10.00%      102     25
        //  60.00%   20.00%      205     28
        //  70.00%   35.00%      358     31
        //  80.00%   55.00%      563     34
        //  90.00%   82.00%      839     37
        //  100.00% 100.00%     1023     40

        // Input values (ADC values)
        int inVal[11] = { 0, 10, 20, 41, 82, 102, 205, 358, 563, 829, 1023 };

        // Output values (WPM values)
        int outVal[11] = { 10, 13, 16, 19, 22, 25, 28, 31, 34, 37, 40 };

Sunday, May 31, 2020

Alzheimers Poem

Do not ask me to remember
Don't try to make me understand
Let me rest and know you're with me
Kiss my cheek and hold my hand

I'm confused beyond your concept
I'm sad and sick and lost
All I know is that I need you
To be with me at all cost

Do not lose your patience with me
Do not scold or curse or cry
I can't help the way I'm acting
Can't be different though I try

Just remember that I need you
That the best of me is gone
Please don't fail to stand beside me
Love me 'til my life is done

by Owen Darnell

Saturday, January 25, 2020

Something to Ponder

The paradox of our time in history is that we have taller buildings but shorter tempers, wider freeways, but narrower viewpoints. We spend more, but have less, we buy more, but enjoy less. We have bigger houses and smaller families, more conveniences, but less time. We have more degrees but less sense, more knowledge, but less judgment, more experts, yet more problems, more medicine, but less wellness.

We drink too much, smoke too much, spend too recklessly, laugh too little, drive too fast, get too angry, stay up too late, get up too tired, read too little, watch TV too much, and pray too seldom.

We have multiplied our possessions, but reduced our values. We talk too much, love too seldom, and hate too often.

We've learned how to make a living, but not a life. We've added years to life not life to years. We've been all the way to the moon and back, but have trouble crossing the street to meet a new neighbor. We conquered outer space but not inner space. We've done larger things, but not better things.

We've cleaned up the air, but polluted the soul. We've conquered the atom, but not our prejudice. We write more, but learn less. We plan more, but accomplish less. We've learned to rush, but not to wait. We build more computers to hold more information, to produce more copies than ever, but we communicate less and less.

These are the times of fast foods and slow digestion, big men and small character, steep profits and shallow relationships. These are the days of two incomes but more divorce, fancier houses, but broken homes. These are days of quick trips, disposable diapers, throwaway morality, one night stands, overweight bodies, and pills that do everything from cheer, to quiet, to kill. It is a time when there is much in the showroom window and nothing in the stockroom. A time when technology can bring this letter to you, and a time when you can choose either to share this insight, or to just hit delete.

Remember to spend some time with your loved ones, because they are not going to be around forever.

Remember, say a kind word to someone who looks up to you in awe, because that little person soon will grow up and leave your side.

Remember, to give a warm hug to the one next to you, because that is the only treasure you can give with your heart and it doesn't cost a cent.

Remember, to say, 'I love you' to your partner and your loved ones, but most of all mean it. A kiss and an embrace will mend hurt when it comes from deep inside of you.

Remember to hold hands and cherish the moment for someday that person will not be there again.

Give time to love, give time to speak! And give time to share the precious thoughts in your mind.

And always remember, life is not measured by the number of breaths we take, but by those moments that take our breath away.

George Carlin - 1937-2008

Wednesday, January 1, 2020

Happy New Year

Happy New Year everyone.  Make it a good one.  Don't forget to stop and smell the roses and spend time on who and what is important to you.  Resist the urge to take anyone or anything for granted.  Give a little and leave those you touch better than you found them.  Above all, forgive those who have wronged you and don't let hate win.  Happy New Year.

Tuesday, December 25, 2018

Merry Christmas!

Politically correct or not, let me take this opportunity to wish everyone a Merry Christmas and a Happy New Year.

Afrikaans: Geseënde Kersfees
Afrikander: Een Plesierige Kerfees
African/ Eritrean/ Tigrinja: Rehus-Beal-Ledeats
Albanian: Gezur Krislinjden
Arabic: Milad Majid
Argentine: Feliz Navidad
Armenian: Shenoraavor Nor Dari yev Pari Gaghand
Azeri: Tezze Iliniz Yahsi Olsun
Bahasa Malaysia: Selamat Hari Natal
Basque: Zorionak eta Urte Berri On!
Bengali: Shuvo Naba Barsha
Bohemian: Vesele Vanoce
Bosnian: (BOSANSKI) Cestit Bozic i Sretna Nova godina
Brazilian: Feliz Natal
Breton: Nedeleg laouen na bloavezh mat
Bulgarian: Tchestita Koleda; Tchestito Rojdestvo Hristovo
Catalan: Bon Nadal i un Bon Any Nou!
Chile: Feliz Navidad
Chinese: (Cantonese) Gun Tso Sun Tan'Gung Haw Sun
Chinese: (Mandarin) Sheng Dan Kuai Le
Choctaw: Yukpa, Nitak Hollo Chito
Columbia: Feliz Navidad y Próspero Año Nuevo
Cornish: Nadelik looan na looan blethen noweth
Corsian: Pace e salute
Crazanian: Rot Yikji Dol La Roo
Cree: Mitho Makosi Kesikansi
Croatian: Sretan Bozic
Czech: Prejeme Vam Vesele Vanoce a stastny Novy Rok
Danish: Glædelig Jul
Duri: Christmas-e- Shoma Mobarak
Dutch: Vrolijk Kerstfeest en een Gelukkig Nieuwjaar! or Zalig Kerstfeast
English: Merry Christmas
Eskimo: (inupik) Jutdlime pivdluarit ukiortame pivdluaritlo!
Esperanto: Gajan Kristnaskon
Estonian: Rõõmsaid Jõulupühi
Ethiopian: (Amharic) Melkin Yelidet Beaal
Faeroese: Gledhilig jol og eydnurikt nyggjar!
Farsi: Cristmas-e-shoma mobarak bashad
Finnish: Hyvaa joulua
Flemish: Zalig Kerstfeest en Gelukkig nieuw jaar
French: Joyeux Noel
Frisian: Noflike Krystdagen en in protte Lok en Seine yn it Nije Jier!
Galician: Bo Nada
Gaelic: Nollaig chridheil agus Bliadhna mhath ùr!
German: Fröhliche Weihnachten
Greek: Kala Christouyenna!
Haiti: (Creole) Jwaye Nowel or to Jesus Edo Bri'cho o Rish D'Shato Brichto
Hausa: Barka da Kirsimatikuma Barka da Sabuwar Shekara!
Hawaiian: Mele Kalikimaka
Hebrew: Mo'adim Lesimkha. Chena tova
Hindi: Shub Naya Baras (good New Year not Merry Christmas)
Hungarian: Boldog Karácsonyt
Icelandic: Gledileg Jol
Indonesian: Selamat Hari Natal
Iraqi: Idah Saidan Wa Sanah Jadidah
Irish: Nollaig Shona Dhuit, or Nodlaig mhaith chugnat
Iroquois: Ojenyunyat Sungwiyadeson honungradon nagwutut. Ojenyunyat osrasay.
Italian: Buone Feste Natalizie
Japanese: Shinnen omedeto. Kurisumasu Omedeto
Jiberish: Mithag Crithagsigathmithags
Korean: Sung Tan Chuk Ha
Kurdish: Serî sallî nwê pîroz
Lao: souksan van Christmas
Latin: Natale hilare et Annum Faustum!
Latvian: Prieci'gus Ziemsve'tkus un Laimi'gu Jauno Gadu!
Lausitzian: Wjesole hody a strowe nowe leto
Lettish: Priecigus Ziemassvetkus
Lithuanian: Linksmu Kaledu
Low Saxon: Heughliche Winachten un 'n moi Nijaar
Luxembourgish: Schèine Chreschtdaag an e gudde Rutsch
Macedonian: Sreken Bozhik
Maltese: IL-Milied It-tajjeb
Manx: Nollick ghennal as blein vie noa
Maori: Meri Kirihimete
Marathi: Shub Naya Varsh (good New Year not Merry Christmas)
Navajo: Merry Keshmish
Norwegian: God Jul, or Gledelig Jul
Occitan: Pulit nadal e bona annado
Papiamento: Bon Pasco 
Papua New Guinea: Bikpela hamamas blong dispela Krismas na Nupela yia i go long yu
Pennsylvania German: En frehlicher Grischtdaag un en hallich Nei Yaahr!
Peru: Feliz Navidad y un Venturoso Año Nuevo
Philippines: Maligayang Pasko!
Polish: Wesolych Swiat Bozego Narodzenia or Boze Narodzenie
Portuguese: Feliz Natal
Pushto: Christmas Aao Ne-way Kaal Mo Mobarak Sha
Rapa-Nui (Easter Island): Mata-Ki-Te-Rangi. Te-Pito-O-Te-Henua
Rhetian: Bellas festas da nadal e bun onn
Romanche: (sursilvan dialect): Legreivlas fiastas da Nadal e bien niev onn!
Rumanian: Sarbatori vesele or Craciun fericit
Russian: Pozdrevlyayu s prazdnikom Rozhdestva is Novim Godom
Sami: Buorrit Juovllat
Samoan: La Maunia Le Kilisimasi Ma Le Tausaga Fou
Sardinian: Bonu nadale e prosperu annu nou
Scots Gaelic: Nollaig Chridheil dhuibh
Serbian: Hristos se rodi.
Singhalese: Subha nath thalak Vewa. Subha Aluth Awrudhak Vewa
Slovak: Vesele Vianoce. A stastlivy Novy Rok
Slovene: Vesele Bozicne Praznike Srecno Novo Leto or Vesel Bozic in srecno Novo leto
Spanish: Feliz Navidad
Swedish: God Jul and (Och) Ett Gott Nytt År
Switzerland (Swiss-German): Schöni Wienachte
Tagalog: Maligayamg Pasko. Masaganang Bagong Taon
Tamil: (Tamizh) Nathar Puthu Varuda Valthukkal (good New Year not Merry Christmas)
Trukeese: (Micronesian) Neekiriisimas annim oo iyer seefe feyiyeech!
Thai: Sawadee Pee Mai or souksan wan Christmas
Turkish: Noeliniz Ve Yeni Yiliniz Kutlu Olsun
Ukrainian: Z Rizdvom Khrystovym or S rozhdestvom Kristovym
Urdu: Naya Saal Mubarak Ho (good New Year not Merry Christmas)
Vietnamese: Chuc Mung Giang Sinh
Welsh: Nadolig Llawen
Yoruba: E ku odun, e ku iye'dun!