Arduino Fox Controller

26 January 2017

In the January 2017 QST "Letters From Our Members" section, there was a story about a Ham supported charity event that was being disrupted by a stuck microphone tying up three linked repeaters. Using skills acquired through Fox Hunting, a Ham was able to find the stuck microphone in a nearby vehicle.

That story inspired me to create an Arduino controlled Fox Hunting transmitter. The Fox consists of an Arduino Nano, an old Icom HT, a battery pack and a one gallon re-purposed alcohol can. Just about any type of Arduino could be used including the Arduino Uno, but I had a Nano on hand.

Some features:
  • Transmissions can be remotely started and stopped with another HT sending DTMF tones (Touch Tones). Because the DTMF decoder is all software, you have to wait between transmissions to send commands. Requiring no extra components is a plus though.
  • Final project uses an Arduino Nano costing about $4.
  • Only a few resistors and capacitors are required to interface the HT.
  • Only one power supply required. If your power supply is over 9 volts, use an Arduino Uno because the voltage regulator on the Uno can't handle voltages over 12 volts. You could optionally build a "pre-regulator" for the Nano. You can optionally power the Nano using a USB power pack plugged into the Nano USB port.
  • The Arduino can be programmed to send musical notes in addition to Morse Code. The following sketch includes the first few notes of a song revered by all true Southern Hams.
Here's a little demonstration of the Fox Transmitter:


Some photos of the project:


Because the Arduino puts out a Square Wave, I made a 3 stage R-C filter to turn it into something like a Sine Wave. The resulting tone sounds a bit smoother than the harsh Square Wave. The Icom IC-2SAT HT uses a single pin for both microphone input and PTT, so in this case, I used a digital pin on the Arduino for PTT and when the pin is Low, it pulls a 2.2K resistor to Ground. I fed the audio from the R-C filter to the same pin through a .2 micro-farad capacitor. Every type of HT uses a different scheme for PTT and microphone input, so do some research for your particular type of radio.


R1-3 = 3.3k
R4 = 47k
R5 = 2.2k
C1-4 = 200 nf
R6 = 1k
R7-8 = 100k
C5 = 100 nf

I recently created a printed circuit board and enclosure for this project. All the Eagle files for the PCB and STL files for the enclosure are included in the following file:


Here's a 40mm x 60mm single sided PCB I created with Eagle CAD. 


I added a jumper (the only trace on the top side of the board) to allow you to connect PTT only to the 4 pin TRRS connector and the terminal block. If your radio has a separate PTT line, run a jumper where you see the upper trace and leave off resistor R5.

The board is laid out to connect a straight TRRS cable from the controller to a Yaesu FT60R. You can leave off this connector and connect your cable to the 4 pin terminal block or directly to the PCB if desired.

The Arduino is plugged into a header to allow some components under the Arduino and also to allow easy removal and replacement of the Arduino.

Here are some Amazon links if you need any of the connectors:

5.5mmx2.1mm 3Pins PCB Mounting Female DC Power Jack - https://www.amazon.com/gp/product/B00MJVIFS2/

Black 4 Pin 3.5mm Stereo Jack Socket PCB Mount Connector - https://www.amazon.com/gp/product/B00CQMGBJE/

2.54mm Breakaway PCB Board 40Pin Male and Female Header Connector for Arduino Shield - https://www.amazon.com/dp/B01MQ48T2V/

2 Pole 5mm Pitch PCB Mount Screw Terminal Block - https://amazon.com/gp/product/B01M7X75IC



Adjust the volume on the HT so that it's sufficient for the Arduino to decode the DTMF tones, not so much that it over drives the Analog input. An oscilloscope is handy for this, but a little experimentation will yield the same result. With my Icom HT, the volume is at approximately one third.

The six 1.2 volt NiMH batteries in the battery pack provide a nominal 7.2 volts, about 8.4 volts when the batteries are fresh. The battery pack powers the Arduino and HT. Initial testing shows that my old batteries will power the Fox for over 2 hours transmitting an intermittent beacon at 1/2 watt.

You could also power the circuit with a USB type power pack (5 volts) plugged into the USB connector of the Arduino. 

I cobbled together some code from various places on the Internet and created the following Arduino Sketch.

You'll also need the associated header file: pitches.h, DTMF.h and DTMF.cpp
You can download a ZIP file here: FoxSketch.zip. It includes the INO file, DTMF Library folder and pitches.h.
Copy pitches.h to the same folder as the sketch. Copy the DTMF folder to your Arduino Libraries folder.

Here's the sketch:

// Arduino controlled Fox Hunting Xmtr and / or Beacon 
// Code cobbled together from various sources
// Pin 6 is the square wave output for the Morse Code
// Pin 7 is Push to Talk pin (low = PTT)

#include <DTMF.h>
#include "pitches.h"

int sensorPin = A0;  //The following block used by DTMF
float n = 128.0;
float sampling_rate = 8926.0;
DTMF dtmf = DTMF(n, sampling_rate);
float d_mags[8];
char thischar;

int xmit = 0;  // if xmit = 0, don't transmit
int pause = 220;  // pause between transmissions, 22 = 1 second (approx)

// notes in the melody:
int melody[] = {
  NOTE_G4, NOTE_E4, NOTE_C4, NOTE_C4, NOTE_C4, NOTE_D4, NOTE_E4, NOTE_F4, NOTE_G4, NOTE_G4, NOTE_G4, NOTE_E4
};
// note durations: 4 = quarter note, 8 = eighth note, etc.:
int noteDurations[] = {
  8, 8, 4, 4, 8, 8, 8, 8, 4, 4, 4, 4
};

#define PTT  7
#define BUZZER_PIN 6  // send music and cw out this pin
#define BUZZER_FREQUENCY 700  // cw pitch
char beaconstring[] = "CQ WT4Y/FOX";
char idstring[] = "DE WT4Y";
int randNumber;

char chartemp[4] ;
char incomingByte = 0;
//int morseOutput = 4; //Pin for receiving Morse output - attach LED to this pin
int interval = 60;   //length of time for one dot - basic unit of measurement;  one interval occurs between each dot or dash; dash is equivalent to 3 dots; 7 dots between words

String alphabet = "abcdefghijklmnopqrstuvwxyz1234567890/ ";
String ALPHABET = alphabet;

String morseCode[] = { 
    ".-", // A
    "-...", // B
    "-.-.", // C
    "-..", // D
    ".", // E
    "..-.", // F
    "--.", // G
    "....", // H
    "..", // I
    ".---", // J
    "-.-", // K
    ".-..", // L
    "--", // M
    "-.", // N
    "---",  // O
    ".--.", // P
    "--.-", // Q
    ".-.", // R
    "...",  // S
    "-", // T
    "..-", // U
    "...-", // V
    ".--", // W
    "-..-", // X
    "-.--", // Y
    "--..", // Z
    ".----", // 1
    "..---", // 2
    "...--", // 3
    "....-", // 4
    ".....", // 5
    "-....", // 6
    "--...", // 7
    "---..", // 8
    "----.", // 9
    "-----", // 0
    "-..-.", // forward slash
    " "   //space character
};

void setup() {
  
  pinMode(PTT, OUTPUT);
  digitalWrite(PTT, HIGH);
  Serial.begin(9600);
//  pinMode(morseOutput, OUTPUT);
  
  ALPHABET.toUpperCase();
  randomSeed(analogRead(0));  // in case random delays between 
                              // transmissions are used
}

void loop() {

for (int x = 0; x < pause; x ++)  // while waiting between transmissions, look for DTMF
{
//  Serial.println(x);
  dtmf.sample(sensorPin);
  dtmf.detect(d_mags, 506);
  thischar = dtmf.button(d_mags, 1800.);
  
  if (thischar) {  // decide what to do if DTMF tone is received
    switch (thischar) {
      case 49:  // the number 1
        xmit = 1;  // set the flag to enable transmissions
        break;
      case 53:  //number 5
        digitalWrite(PTT, LOW);
        delay(2000);
        sendLetter('h');
        sendLetter('i');
        digitalWrite(PTT, HIGH);
        delay(2000);
        break;
      case 54:  //number 6
        digitalWrite(PTT, LOW);
        delay(2000);
        for (int i = 0; i < sizeof(idstring); i++){
          sendLetter(idstring[i]);
        }
        digitalWrite(PTT, HIGH);
        delay(2000);
        break;
      case 55:  //number 7
        digitalWrite(PTT, LOW);
        delay(2000);
        sendLetter('7');
        sendLetter('3');
        digitalWrite(PTT, HIGH);
        delay(2000);
        break;
      default:  // any other number, turn off transmissions
        xmit = 0;  // set the flag to disable transmissions
      break;
    }
  }
  delay(1);
}

if (xmit == 1)
  {
  digitalWrite(PTT, LOW);
  delay(2000); // delay 2 seconds after PTT to account for race condition

// play a little melody
  for (int thisNote = 0; thisNote < 12; thisNote++) {
    // to calculate the note duration, take one second
    // divided by the note type.
    //e.g. quarter note = 1000 / 4, eighth note = 1000/8, etc.
    int noteDuration = 1000 / noteDurations[thisNote];
    tone(BUZZER_PIN, melody[thisNote], noteDuration);
    // to distinguish the notes, set a minimum time between them.
    // the note's duration + 30% seems to work well:
    int pauseBetweenNotes = noteDuration * 1.30;
    delay(pauseBetweenNotes);
    // stop the tone playing:
    noTone(BUZZER_PIN);
  }

  delay(1000);  //one second between melody and beaconstring

  for (int i = 0; i < sizeof(beaconstring); i++){
    sendLetter(beaconstring[i]);
    }

  delay(500);
  digitalWrite(PTT, HIGH);  // drop the PTT and wait a while
//  randNumber = random(10000, 20000);  // remove comments and add comments to delay(5000)
//  Serial.print(randNumber);           // to use random delays
//  Serial.println();
//  delay(randNumber);
//  delay(5000);
  }
}

// End of Loop()
// Functions follow

void sendLetter (char c) {
   int i;
   for (i = 0; i < alphabet.length(); i = i + 1) {
       if (alphabet[i] == c || ALPHABET[i] == c) {
           //Serial.print(c);
           sendMorseCode(morseCode[i]);
           return;
       }
   }
   if (c == '\n')
      Serial.println();
}

void sendMorseCode (String tokens) {
   int i;
   for (i = 0; i < tokens.length(); i = i + 1) {
       switch (tokens[i]) {
           case '-':
               sendDash();
               break;
           case '.':
               sendDot();
               break;
           case ' ':
               sendEndOfWord();
               break;
       }
   }
   morseOutputOff(2);
//   Serial.print(" ");
}

void sendEndOfWord() {
   morseOutputOff(4);
//   Serial.print("  ");
}

//basic functions - Morse code concepts  
void sendDot() {
   tone(BUZZER_PIN, BUZZER_FREQUENCY);
   morseOutputOn(1);
   noTone(BUZZER_PIN);
   morseOutputOff(1);
//   Serial.print(".");
}
void sendDash() {
   tone(BUZZER_PIN, BUZZER_FREQUENCY);
   morseOutputOn(3);
   noTone(BUZZER_PIN);
   morseOutputOff(1);
//   Serial.print("-");
}

//Low level functions - how the actions are accomplished
// n = number of intervals 
// interval is a fixed length of time determined at start, for example 200 milliseconds
void morseOutputOn (int n) {
//   example: morseOutputOn(1) means turn output on and keep it on for 1 interval 
//            morseOutputOn(3) means turn output on and keep it on for 3 intervals 
//   
//   digitalWrite(morseOutput, HIGH);
   delay(n * interval);
}

void morseOutputOff (int n) {
//   digitalWrite(morseOutput, LOW);
   delay(n * interval);
}