Try to learn something about everything, and everything about somethingThomas Huxley “Darwin's bulldog” (1824-1895)

This is an old revision of the document!


Arduino Projects

60m Transceiver

The 60m transceiver has the following features

  • Channelized operation
  • LCD display
    • Frequency
    • Mode (SSB / CW)
    • Modulation Source (Mic/Key/Data)
    • RX / TX status
  • 9MHz IF with Xtal filter
  • SSB & CW set via mode switch
  • Approx 3W PEP
  • Digital mode input/output and PTT via 9-pin D
  • SSB Mic input via 8-pin round “Icom” connector
  • Modulation Source (Mic or Data) chosen via select switch
  • Scan start switch - scan a preset group of channels - cycles continuously, no “squelch”

Arduino Versions

There are several different firmware versions….

RX Only, with CW / SSB, and scan

gm4slv_60m_channels_cwscan_noptt_new_calibration_130523.ino

#include <rgb_lcd.h>
#include <si5351.h>
 
char version[] = "v0.1";
 
const long channel_array[] = 
{
5258500,5260000,5261000,5262000,5263000, 5276000,
5278500,5279000,5287200,5290000,5298000,
5301000,5304000,5317000,5320000,5333000,
5334000,5335000,5354000,5355000,5362000,
5363000,5364700,5366500,5371500,5378000,
5379000,5395000,5398500,5403500,
5345000,5245000,5450000,5505000,5680000,
5195000,5616000,5649000
} ;
 
const long scan_array[] = 
{
 5260000,5261000,
 5262000,5263000,
 5366500,5354000,
 5355000,
} ;
 
 
const int mems = sizeof(channel_array)/sizeof(long);
 
const int scan_mems = sizeof(scan_array)/sizeof(long);
 
// the last "no_scan" number channels of
// channel_array are not to be scanned. 
// Put the channels not to be scanned last 
// in the array and update no_scan to reflect. 
 
const int no_scan = 0; 
 
const long bandStart = 5000000;    
const long bandEnd = 6000000;
const long txcio = 9001200;
 
const long cwoffset = 600;
const long filteroffset = 500;
 
volatile int channel = 01;
 
volatile long freq = channel_array[channel];  //      
volatile long oldfreq = 0;
 
 
 
 
 
volatile long currentfreq = 0;
volatile int updatedisplay = 0;
 
// Rotary encoder pins and other inputs
static const int rotAPin = 2;
static const int rotBPin = 3;
static const int pushSwPin = 4;
static const int mictxrxPin = 5;
static const int txRLA1Pin = 7;
static const int txRLA2Pin = 6;
static const int datatxrxPin = 8;
static const int modRLAPin = 9;
static const int modeSWPin = 11;
static const int scanSWPin = 10;
 
volatile int scanning = 0;
volatile int old_scanning = 0;
 
volatile int tx = 0;
volatile int oldtxrx = 0;
 
volatile int mod = 0;
volatile int oldmod = 0;
 
volatile int channel_freq = 0; // push-button to toggle tune method
volatile int old_channel_freq = 0;
 
volatile long scan_freq = scan_array[channel];
volatile long old_scan_freq = 0;
 
volatile int mode = 1; // 1 = USB/LSB, 0 = CW 
volatile int oldmode = 0;
 
// Rotary encoder variables, used by interrupt routines
volatile int rotState = 0;
volatile int rotAval = 1;
volatile int rotBval = 1;
 
 
int digit1 = 0;
int digit2 = 0;
int digit3 = 0;
int digit4 = 0;   
int digit5 = 0;
int digit6 = 0;
int digit7 = 0;
 
// Instantiate the Objects
rgb_lcd lcd;
Si5351 si5351;
 
 
 
byte customChar[8] = {
  0b11111,
  0b11011,
  0b11001,
  0b00000,
  0b11001,
  0b11011,
  0b11111,
  0b11111
};
 
 
 
void setup()
{
 
 
  // Set up frequency and radix switches
  pinMode(rotAPin, INPUT_PULLUP);
  pinMode(rotBPin, INPUT_PULLUP);
  pinMode(pushSwPin, INPUT_PULLUP);
 
  pinMode(mictxrxPin, INPUT_PULLUP);
  pinMode(datatxrxPin, INPUT_PULLUP);
 
  pinMode(modeSWPin, INPUT_PULLUP);
  pinMode(scanSWPin, INPUT_PULLUP);
  pinMode(txRLA1Pin, OUTPUT);
  pinMode(txRLA2Pin, OUTPUT);
  pinMode(modRLAPin, OUTPUT);
 
  digitalWrite(txRLA1Pin, LOW);
  digitalWrite(txRLA2Pin, LOW);
  digitalWrite(modRLAPin, LOW);
 
  // Set up interrupt pins
  attachInterrupt(digitalPinToInterrupt(rotAPin), ISRrotAChange, CHANGE);
  attachInterrupt(digitalPinToInterrupt(rotBPin), ISRrotBChange, CHANGE);
 
  // Initialize the display
  lcd.begin(16, 2);
 
lcd.createChar(0, customChar); // create a new custom character
 
  lcd.cursor();
 
  // Initialize the Si5351
 
  //si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0, -31200);         // 
  //si5351.drive_strength(SI5351_CLK0, SI5351_DRIVE_8MA);   // 2 mA for HB mixers
  //si5351.drive_strength(SI5351_CLK2, SI5351_DRIVE_8MA);   // 2 mA for HB mixers
 
  si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0, 0);
  si5351.set_correction(-34976, SI5351_PLL_INPUT_XO);
  si5351.set_pll(SI5351_PLL_FIXED, SI5351_PLLA);
  si5351.drive_strength(SI5351_CLK0, SI5351_DRIVE_8MA);
  si5351.drive_strength(SI5351_CLK2, SI5351_DRIVE_8MA);
 
 
  // Update display and send start frequency
  UpdateDisplay();
  SendFrequency();
}
 
 
void loop()
{
 
  CheckScanSwitch();
 
  //CheckTXSwitch();
 
  //CheckModSwitch();
 
  CheckModeSwitch();
 
  // Check to see if the freq has changed
  currentfreq = getfreq();                      // Interrupt safe method to get the current frequency
 
  if (currentfreq != oldfreq)
  {
 
    oldfreq = currentfreq;
 
    UpdateDisplay();
    SendFrequency();
 
  }
 
  // Check the rotary encoder (radix) swith
  if (digitalRead(pushSwPin) == LOW)           // Read the rotary encoder switch
  {
    delay(500);
    if (digitalRead(pushSwPin) == LOW)
    {
 
    if (channel_freq == 1)
    {
      channel_freq = 0;
    }
    else if (channel_freq == 0)
    {
      channel_freq = 1;
    }
    //delay(50);
 
    UpdateDisplay();
 
    }
  }
 
} // end of main loop
 
 
long getfreq()
{
  long temp_freq;
  cli();
  temp_freq = freq;
  sei();
  return temp_freq;
}
 
void CheckScanSwitch()
{
 if (digitalRead(scanSWPin) == 0)
 
    {
 
      scanning  = 1;
      scan_freq = 0;
      channel = channel + 1;
 
      if (channel > scan_mems - (no_scan + 1))
            {
              channel = 0;
            }
 
      freq = scan_array[channel];
 
      UpdateDisplay();
      SendFrequency();
      delay(500);
    }
    else
    {
 
 
      if (scanning != old_scanning)
      {
        scanning = 0;
        UpdateDisplay();
      }
      old_scanning = scanning;
    }
 
}
 
//void CheckModSwitch()
//{
//  if (digitalRead(modSWPin) == 0)
//      mod = 0;                             // 0 = Phone
//  else if (digitalRead(modSWPin) == 1)
//      mod = 1;                                 // 1=Data
 
 
//  if (mod != oldmod)
//  {
//    if ( mod == 0)
//      {
//        digitalWrite(modRLAPin,LOW);
//      }
//    else
//      {
//        digitalWrite(modRLAPin,HIGH);
//      } 
//    UpdateDisplay();
//    SendFrequency();
//    oldmod = mod;
//  }
//}
 
 
void CheckModeSwitch()
{
  if (digitalRead(modeSWPin) == 0)
      mode = 0;                             // 0 = CW
  else if (digitalRead(modeSWPin) == 1)
      mode = 1;                                 // 1=SSB
 
 
  if (mode != oldmode)
  {
    if ( mode == 0)
      {
 
      }
    else
      {
 
      } 
    UpdateDisplay();
    SendFrequency();
    oldmode = mode;
  }
}
 
 
 
void CheckTXSwitch()
{
  if (scanning == 0)
  {
  if (digitalRead(mictxrxPin) == 0)
 
  {
    tx = 1;                             // 1=TX
    mod = 0;
 
   }  else if (digitalRead(datatxrxPin) == 0)
 
  {  tx = 1;
     mod = 1;
 
  }  else if (digitalRead(mictxrxPin) == 1)
 
  {  tx = 0;                                 // 0=Phone
 
  } else if (digitalRead(datatxrxPin) == 1)
 
 {   tx = 0;
 
 }
 
  if (tx != oldtxrx)
  {
    if ( tx == 1)
      {
        if ( mod == 0 )
        {
          digitalWrite(modRLAPin,LOW);
        }
        else if ( mod == 1 )
        {
          digitalWrite(modRLAPin,HIGH);
        }
        digitalWrite(txRLA1Pin,HIGH);
        digitalWrite(txRLA2Pin,HIGH);
      }
    else
      {
        digitalWrite(txRLA1Pin,LOW);
        digitalWrite(txRLA2Pin,LOW);   
      }
    UpdateDisplay();
    SendFrequency();
    oldtxrx = tx;
  }
}
}
 
// Interrupt routines
void ISRrotAChange()
{
  if (digitalRead(rotAPin))
  {
    rotAval = 1;
    UpdateRot();
  }
  else
  {
    rotAval = 0;
    UpdateRot();
  }
}
 
 
void ISRrotBChange()
{
  if (digitalRead(rotBPin))
  {
    rotBval = 1;
    UpdateRot();
  }
  else
  {
    rotBval = 0;
    UpdateRot();
  }
}
 
 
// Determine which way the rotary encoder is rotating and action as required
void UpdateRot()
{
  switch (rotState)
  {
    case 0:                                         // Idle state, look for direction
      if (!rotBval)
        rotState = 1;                               // CW 1
      if (!rotAval)
        rotState = 11;                              // CCW 1
      break;
 
    case 1:                                         // CW, wait for A low while B is low
      if (!rotBval)
      {
        if (!rotAval)
        {
          // either increment the radix or freq
          if (channel_freq == 1)
          {
            updatedisplay = 1;
            if (mode == 1)
            {
            freq = freq + 500;
            }
            else
            {
              freq = freq + 100;
            }
            if (freq > bandEnd)
            {
              freq = bandEnd;
            }
 
          }
          else
          {
            channel = channel + 1;
 
            if (channel > mems -1)
            {
              channel = 0;
            }
            freq = channel_array[channel]; 
          }
          rotState = 2;                             // CW 2
        }
      }
      else if (rotAval)
        rotState = 0;                               // It was just a glitch on B, go back to start
      break;
 
    case 2:                                         // CW, wait for B high
      if (rotBval)
        rotState = 3;                               // CW 3
      break;
 
    case 3:                                         // CW, wait for A high
      if (rotAval)
        rotState = 0;                               // back to idle (detent) state
      break;
 
    case 11:                                        // CCW, wait for B low while A is low
      if (!rotAval)
      {
        if (!rotBval)
        {
          if ( channel_freq == 1 )
          {
            updatedisplay = 1;
 
            if (mode == 1)
            {
            freq = freq - 500;
            }
            else
            {
              freq = freq - 100;
            }
 
            if (freq < bandStart)
            {
              freq = bandStart;
            }
          }
          else
          {
            channel = channel - 1;
 
            if (channel < 0)
             {
              channel = mems - 1;
             }
            freq = channel_array[channel];
 
          }
          rotState = 12;                            // CCW 2
        }
      }
      else if (rotBval)
        rotState = 0;                               // It was just a glitch on A, go back to start
      break;
 
    case 12:                                        // CCW, wait for A high
      if (rotAval)
        rotState = 13;                              // CCW 3
      break;
 
    case 13:                                        // CCW, wait for B high
      if (rotBval)
        rotState = 0;                               // back to idle (detent) state
      break;
  }
}
 
 
void UpdateDisplay()
{
 
 
    digit1 = (freq%10);
    digit2 = ((freq/10)%10);
    digit3 = ((freq/100)%10);
    digit4 = ((freq/1000)%10);   
    digit5 = ((freq/10000)%10);
    digit6 = ((freq/100000)%10);
    digit7 = ((freq/1000000)%10);
 
 
  lcd.setCursor(0,0);
  lcd.print(digit7);
  lcd.setCursor(1,0);
  lcd.print(",");
  lcd.setCursor(2,0);
  lcd.print(digit6);
  lcd.setCursor(3,0);
  lcd.print(digit5);
  lcd.setCursor(4,0);
  lcd.print(digit4);
  lcd.setCursor(5,0);
  lcd.print(".");
  lcd.setCursor(6,0);
  lcd.print(digit3);
  lcd.setCursor(7,0);
  lcd.print(digit2);
 
  lcd.setCursor(8,0);
  lcd.print("kHz");
 
 
 
 
  lcd.setCursor(0,1);
  lcd.print("        ");
 
 
 
  lcd.setCursor(channel,1); 
 
 
if (scanning == 1)
{
   // lcd.print("|");
 
  lcd.write((byte)0);  // print the custom char at (2, 0)
}
 
 /*
  //if (channel < 10)
  //{
  //lcd.print("0");
  //}
  //lcd.print(channel);
 
 lcd.setCursor(0,1);
 if (freq == 5287200 )
 {
  //lcd.setCursor(3,1);
  //lcd.print("    ");
  //lcd.setCursor(3,1);
  lcd.print("WSPR1");
 }
 
 if (freq == 5290000 )
 {
  //lcd.setCursor(3,1);
  //lcd.print("    ");
  //lcd.setCursor(3,1);
  lcd.print("ORK");
 }
 
 if (freq == 5364700 )
 {
  //lcd.setCursor(3,1);
  //lcd.print("    ");
  //lcd.setCursor(3,1);
  lcd.print("WSPR2");
 }
 
 if (freq == 5450000 )
 {
  //lcd.setCursor(3,1);
  //lcd.print("    ");
  //lcd.setCursor(3,1);
  lcd.print("RAF");
 }
 
 if (freq == 5505000 )
 {
  //lcd.setCursor(3,1);
  //lcd.print("    ");
  //lcd.setCursor(3,1);
  lcd.print("EIP");
 }
 
if (freq == 5317000 )
 {
  //lcd.setCursor(3,1);
  //lcd.print("    ");
  //lcd.setCursor(3,1);
  lcd.print("VMARS");
 }
 
if (freq == 5366500 )
 {
  //lcd.setCursor(3,1);
  //lcd.print("    ");
  //lcd.setCursor(3,1);
  lcd.print("FK");
 }
 
if (freq == 5258500 )
 {
  //lcd.setCursor(3,1);
  //lcd.print("    ");
  //lcd.setCursor(3,1);
  lcd.print("FA");
 }
 
if (freq == 5278500 )
 {
  //lcd.setCursor(3,1);
  //lcd.print("    ");
  //lcd.setCursor(3,1);
  lcd.print("FB");
 }
 
if (freq == 5288500 )
 {
  //lcd.setCursor(3,1);
  //lcd.print("    ");
  //lcd.setCursor(3,1);
  lcd.print("FC");
 }
 
if (freq == 5371500 )
 {
  //lcd.setCursor(3,1);
  //lcd.print("    ");
  //lcd.setCursor(3,1);
  lcd.print("FL");
 }
 
if (freq == 5398500 )
 {
  //lcd.setCursor(3,1);
  //lcd.print("    ");
  //lcd.setCursor(3,1);
  lcd.print("FE");
 }
 
 if (freq == 5403500 )
 {
  //lcd.setCursor(3,1);
  //lcd.print("    ");
  //lcd.setCursor(3,1);
  lcd.print("FM");
 }
 
if (freq == 5195000 )
 {
  //lcd.setCursor(3,1);
  //lcd.print("    ");
  //lcd.setCursor(3,1);
  lcd.print("DRA5");
 }
 
if (freq == 5262000 )
 {
  //lcd.setCursor(3,1);
  //lcd.print("    ");
  //lcd.setCursor(3,1);
  lcd.print("QRP CW");
 }
 
 
if ((freq >= 5259000) && (freq < 5262000) )
 {
  //lcd.setCursor(3,1);
  //lcd.print("    ");
  //lcd.setCursor(3,1);
  lcd.print("CW");
 }
 
 
 
*/
 
 
 
 
 lcd.setCursor(14,0);
 
if (tx == 0)
   { lcd.print("RX");
 
   } else if (tx == 1)
   {
    lcd.print("TX");
  }
 
  lcd.setCursor(8,1);
  if (mode == 1)
  {
    lcd.print("SSB");
  }
  if (mode == 0)
  {
    lcd.print(" CW");
  }
 
  lcd.setCursor(12,1);
  if ( mode == 0 )
  {
    lcd.print(" Key");
    //lcd.print(version);
  }
  else
 
  {
    if (mod == 0)
  {
    lcd.print(" Mic");
    //lcd.print(version);
  }
  else
  {
   lcd.print("Data");
   //lcd.print(version);
  }
}
if (channel_freq == 1)
{
  if (mode == 1)
  {
    lcd.setCursor(6,0);
  }
  else if ( mode == 0 )
  {
    lcd.setCursor(7,0);
  }
  lcd.blink();
}
else
{
  lcd.noBlink();
}
} // end of updatedisplay()
 
 
void SendFrequency()
 
 
 
 
{
 
if (mode == 1 ) // SSB
 
  {
    si5351.set_freq(((txcio + freq) * 100ULL), SI5351_CLK0);     // VFO
    si5351.set_freq((txcio * 100ULL), SI5351_CLK2);              // BFO
 
  }
 
else if (mode == 0) // CW
 
  if ( tx == 0 )
 
  {
 
    si5351.set_freq(((txcio + freq - cwoffset - filteroffset) * 100ULL), SI5351_CLK0);    // VFO
    si5351.set_freq(((txcio  - filteroffset) * 100ULL), SI5351_CLK2);                     // BFO
 
  }
 
  else if (tx == 1 )
 
  {
 
    si5351.set_freq(((txcio + freq - cwoffset - filteroffset) * 100ULL), SI5351_CLK0);    // VFO
    si5351.set_freq(((txcio - cwoffset - filteroffset) * 100ULL), SI5351_CLK2);           // BFO
 
  }
 
} // end of sendfrequency()

Further Information

Page created : 11/07/26 10:09 BST

Page updated : 11/07/26 10:22 BST