Farnsworth CW Auto Caller
An Arduino CW 'recorder' that plays back iambic paddle presses at your character speed and spacing

Description

Automatic CW callers only send at a set speed with predetermined character spacing. It’s helpful to have your auto caller send at the same speed and spacing you do which can be a handy way to broadcast your receiving capabilities and not have people replying with such short character spacing. Simply flip the switch, use the paddles to “record” your call, flip the switch back to “passthru” mode, and then press the button to have the same paddle presses with the same timing sent to the radio for transmit.

The Code

//  Farnsworth Auto CQ Caller for iambic paddles

#define  leftButtonPinOut   8
#define  rightButtonPinOut  7
#define  leftButtonPinIn    2
#define  rightButtonPinIn   4
#define  led               13
#define  recordSwitch      12
#define  playbackButton    11

int leftButtonState;
int rightButtonState;
int recordSwitchState;
int playbackButtonState;
int arrayPointer = 0;
long time;
long startTime;
int lastLeftButtonState;
int lastRightButtonState;
int lastPlaybackButtonState;
long playTime;
long prevTimePress;
boolean hasRecorded = false;
boolean recordMemCleared = false;
boolean memExceeded = false;
int interrupt;


struct paddleTime 
  {
    long timePress;
    int  leftButtonRecState:1;
    int  rightButtonRecState:1;
  };
paddleTime keyTimes[120];            // array to hold key-up/key-down times   120 max at this time


void setup ()
  {
    pinMode(leftButtonPinIn, INPUT);
    pinMode(rightButtonPinIn, INPUT);
    pinMode(rightButtonPinOut, OUTPUT);
    pinMode(leftButtonPinOut, OUTPUT);
    pinMode(recordSwitch, INPUT);
    pinMode(playbackButton, INPUT);
    digitalWrite(leftButtonPinIn, HIGH);
    digitalWrite(rightButtonPinIn, HIGH);
    digitalWrite(recordSwitch, HIGH);
    digitalWrite(playbackButton, HIGH);
    pinMode(led, OUTPUT);
  }
  
void loop()
  {
    recordSwitchState = digitalRead(recordSwitch);
    if (recordSwitchState == LOW)                                        // recording
      {
        delay(20);  // debounce for rusty switches
        if (recordMemCleared == false)                                       // clear out the memory for a new recording
          {
            paddleTime paddleMark = {0,0,0};
            for (arrayPointer=0; arrayPointer < 120; arrayPointer++)
              {
                paddleMark.timePress = -1;
                paddleMark.leftButtonRecState = HIGH;
                paddleMark.rightButtonRecState = HIGH;
                keyTimes[arrayPointer]=paddleMark;
              }
            recordMemCleared = true;
            memExceeded = false;
            hasRecorded = true;
            arrayPointer = 0;
          }
        leftButtonState = digitalRead(leftButtonPinIn);
        rightButtonState = digitalRead(rightButtonPinIn);
        digitalWrite(led, HIGH);
        paddleTime paddleMark = {0,0,0};
       
        
        if (arrayPointer > 120)   // Arduino's tiny memory exceeded!
          {
            digitalWrite(leftButtonPinOut, LOW);      // these two write lows prevent the paddles from being stuck down in the event of jitter
            digitalWrite(rightButtonPinOut, LOW);
            digitalWrite (led, HIGH);
            delay(300);
            digitalWrite (led, LOW);
            delay(600); 
            memExceeded = true;
          }
        else
          {
            
            if (leftButtonState != lastLeftButtonState || rightButtonState != lastRightButtonState)
              {
                digitalWrite(leftButtonPinOut, !leftButtonState);
                digitalWrite(rightButtonPinOut, !rightButtonState);
                time = millis();
                paddleMark.timePress = time;
                paddleMark.leftButtonRecState = leftButtonState;
                paddleMark.rightButtonRecState = rightButtonState;
                lastLeftButtonState = leftButtonState;
                lastRightButtonState = rightButtonState;
                keyTimes[arrayPointer]=paddleMark;
                Serial.print(paddleMark.leftButtonRecState);
                Serial.println(paddleMark.rightButtonRecState);
                delay(20);
                arrayPointer++;
              }
        
          }          
      }                                            // end recording phase
      
    
    if (recordSwitchState == HIGH)                // not recording, passthrough mode
      {
        recordMemCleared = false;
        arrayPointer = 0;
        digitalWrite(led, LOW);
        leftButtonState = digitalRead(leftButtonPinIn);
        rightButtonState = digitalRead(rightButtonPinIn);
        playbackButtonState = digitalRead(playbackButton);
        
        if (playbackButtonState == LOW && lastPlaybackButtonState == HIGH && hasRecorded == true && memExceeded == false)          // playback mode
          {
            interrupt = 0;
            Serial.println("playing back");
            digitalWrite(led, HIGH);
            
            paddleTime paddleMark = {0,0,0};
            paddleMark = keyTimes[0];
            startTime = paddleMark.timePress;

            for (arrayPointer = 0; arrayPointer < 120; arrayPointer++)
              {
                leftButtonState = digitalRead(leftButtonPinIn);
                rightButtonState = digitalRead(rightButtonPinIn);
           
                if (leftButtonState == LOW || rightButtonState == LOW)    // interrupted by paddle press
                  {
                    interrupt = 1;
                  }
                    
                if (paddleMark.timePress == -1 || interrupt == 1)        // out of bounds or interrupted
                  {
                     // do nothing
                  }
                else
                  {
                    paddleMark = keyTimes[arrayPointer];
                    if (arrayPointer > 1) // was 1 -- something is wrong here, as it keeps the initial delay on the first run but not subsequent recordings
                      {   
                        playTime =(paddleMark.timePress - prevTimePress);
                        if (playTime > 0)
                          {
                            delay(playTime);
                          }
                      }
                      if (paddleMark.leftButtonRecState == -1)
                        {
                          digitalWrite(leftButtonPinOut, LOW);
                        }
                      else
                        {
                          digitalWrite(leftButtonPinOut, HIGH);
                        }
                      if (paddleMark.rightButtonRecState == -1)
                        {
                          digitalWrite(rightButtonPinOut, LOW);
                        }
                      else
                        {
                          digitalWrite(rightButtonPinOut, HIGH);
                        }
                    prevTimePress = paddleMark.timePress;
                  }     
              }
            
            delay(200);  // debounce
            lastPlaybackButtonState = playbackButtonState;
          }
        if (playbackButtonState == HIGH && lastPlaybackButtonState == LOW)          // nothing
          {
            lastPlaybackButtonState = playbackButtonState;
          }
        digitalWrite (leftButtonPinOut, !leftButtonState);
        digitalWrite (rightButtonPinOut, !rightButtonState);
      }
  }

Last modified on 2015-01-01