A Digitally Controlled Analog VFO.

 

 

To My Dear Friend Bill, N2CQR.

 

This page is dedicated to you, Bill, who dearly loves Analog VFO's. In 2018 N2CQR designed a project around an HRO dial mechanism. It was quite the project but alas Bill discovered that one of the primary virtues of the HRO dial, repeatability, was in fact not the case. Bill has now resolved this issue with some hardware changes; but it is a system long in the tooth.

 

Being an ardent lover of the Arduino and its many capabilities, I asked myself why couldn't we make an HRO substitute using a stepper motor. A bit of time on the Inet and some N6QW hacking enabled me to make a first prototype which shows great promise.

Firstly I bought five stepper motors and the control boards for about $12 on Amazon. A tour of You Tube revealed many application/tutorial videos. But one in particular from www.brainy-bits.com was particularly good. The code is pretty simple and I modified the code to slow the rotation speed and the size of the step for each step. In the video you also see my add of an LCD display which later on as you will see adds a whole new dimension to my approach. I have shown the code below.

BUT BUT you must also include the LiquidCrystal-I2C .h and .cpp files in the sketch and in the same folder. Also you must use the wiring diagram shown in his website. I was a bit confused by what is in his code and the actual pictorial wiring diagram. Wire per the pictorial and I did use an Optical Encoder versus my typical mechanical ones.

Yes I know I will get a crap load of email about how you connect up the LCD. I used the I2C backpack adapter which affixes to the back of the LCD and only needs four wires from the Arduino Uno (or other Arduino) which includes: +5 Volts, Ground, SDA from Pin A4 and SCL from Pin A5. Now not all LCD's and backpacks use the same I2C address so in the code there are three. Typically the 0x27 works but you may have to try 0x3F and even 0x20. If you are lost at this point, put down the iron and go watch Fox News!

/* The change here in the original code to slow down the speed and also the 600 to 32 really slow
* down the movement This also adds an LCD to show data.
* 2/24/2019
*
*
*
*
*/

#include "LiquidCrystal_I2C.h"

#include "Stepper.h"
#define STEPS 32 // Number of steps for one revolution of Internal shaft
// 2048 steps for one revolution of External shaft

volatile boolean TurnDetected; // need volatile for Interrupts
volatile boolean rotationdirection; // CW or CCW rotation

const int PinCLK=2; // Generating interrupts using CLK signal
const int PinDT=3; // Reading DT signal
const int PinSW=4; // Reading Push Button switch

int RotaryPosition=0; // To store Stepper Motor Position

int PrevPosition; // Previous Rotary position Value to check accuracy
int StepsToTake; // How much to move Stepper

LiquidCrystal_I2C lcd(0x27,16,2); // or 0x3F 0r 0x20

// Setup of proper sequencing for Motor Driver Pins
// In1, In2, In3, In4 in the sequence 1-3-2-4
Stepper small_stepper(STEPS, 8, 10, 9, 11);

// Interrupt routine runs if CLK goes from HIGH to LOW
void isr () {
delay(4); // delay for Debouncing
if (digitalRead(PinCLK))
rotationdirection= digitalRead(PinDT);
else
rotationdirection= !digitalRead(PinDT);
TurnDetected = true;
}

void setup () {

lcd.begin(); // initialize with the I2C addr 0x27
lcd.clear();
delay(100);
lcd.backlight();


pinMode(PinCLK,INPUT);
pinMode(PinDT,INPUT);
pinMode(PinSW,INPUT);
digitalWrite(PinSW, HIGH); // Pull-Up resistor for switch
attachInterrupt (0,isr,FALLING); // interrupt 0 always connected to pin 2 on Arduino UNO
}

void loop () {
//*************************LCD Data goes here***********************
lcd.setCursor(2,0); // Putting some data on the LCD
lcd.print(RotaryPosition);
delay(4);
lcd.print(" ");// needed to overwrite the display
lcd.setCursor(6,0);
lcd.print(StepsToTake);
delay(4);
lcd.print(" ");// needed to overwrite the display
lcd.setCursor(10,0);
lcd.print("Step=1");
lcd.setCursor(0,1);
lcd.print("N6QW Digi/Al VFO");
//*************************************
small_stepper.setSpeed(32); //Max seems to be 700 Changed from 600
if (!(digitalRead(PinSW))) { // check if button is pressed
if (RotaryPosition == 0) { // check if button was already pressed
} else {
small_stepper.step(-(RotaryPosition*1)); //changed from 50 changed to 5
RotaryPosition=0; // Reset position to ZERO
}
}

// Runs if rotation was detected
if (TurnDetected) {
PrevPosition = RotaryPosition; // Save previous position in variable
if (rotationdirection) {
RotaryPosition=RotaryPosition-1;} // decrase Position by 1
else {
RotaryPosition=RotaryPosition+1;} // increase Position by 1

TurnDetected = false; // do NOT repeat IF loop until new rotation detected

// Which direction to move Stepper motor
if ((PrevPosition + 1) == RotaryPosition) { // Move motor CW
StepsToTake=1; //changed from 50
small_stepper.step(StepsToTake);
}

if ((RotaryPosition + 1) == PrevPosition) { // Move motor CCW
StepsToTake=-1; //changed from 50
small_stepper.step(StepsToTake);
}
}
}

 

Next is the code for the automatic zeroing of the capacitor on power down. The LiquidCrystal_I2C .h and .cpp files are included files and must be in the same folder as the sletch.

 

/* The change here in the original code to 4 to slow down the speed and also the 600 to 32 really slow
* down the movement This also adds an LCD to show data.
* 2/24/2019
* This adds the motor start stop switch.
*
*
*
*
*/

#include "LiquidCrystal_I2C.h"

#include "Stepper.h"
#define STEPS 32 // Number of steps for one revolution of Internal shaft
// 2048 steps for one revolution of External shaft

volatile boolean TurnDetected; // need volatile for Interrupts
volatile boolean rotationdirection; // CW or CCW rotation

const int PinCLK=2; // Generating interrupts using CLK signal
const int PinDT=3; // Reading DT signal
const int PinSW=4; // Reading Push Button switch
const int PinSWIn = 5; //This is the Push Button input to trigger the Motor OFF
const int PinSWOut = 6; // This triggers to Motor OFF Switch

int buttonstate = 0;
int RunOnce = 1;
int buttonState = 0;
int lastButtonState = 0;
int lastbuttonstate = 0;

int RotaryPosition=0; // To store Stepper Motor Position

int PrevPosition; // Previous Rotary position Value to check accuracy
int StepsToTake; // How much to move Stepper

LiquidCrystal_I2C lcd(0x27,16,2); //0x20 or 0x3F 0r 0x20

// Setup of proper sequencing for Motor Driver Pins
// In1, In2, In3, In4 in the sequence 1-3-2-4
Stepper small_stepper(STEPS, 8, 10, 9, 11);

// Interrupt routine runs if CLK goes from HIGH to LOW
void isr () {
delay(4); // delay for Debouncing
if (digitalRead(PinCLK))
rotationdirection= digitalRead(PinDT);
else
rotationdirection= !digitalRead(PinDT);
TurnDetected = true;
}

void setup () {

lcd.begin(); // initialize with the I2C addr 0x27
lcd.clear();
delay(100);
lcd.backlight();


pinMode(PinCLK,INPUT);
pinMode(PinDT,INPUT);
pinMode(PinSW,INPUT);
pinMode(PinSWIn, INPUT); digitalWrite(PinSWIn, HIGH);
pinMode(PinSWOut, OUTPUT); digitalWrite(PinSWOut, LOW);
digitalWrite(PinSW, HIGH); // Pull-Up resistor for switch
attachInterrupt (0,isr,FALLING); // interrupt 0 always connected to pin 2 on Arduino UNO
}

void loop () {
//*************************LCD Data goes here***********************

CheckMode(); //The sub-routine to "0" the capacitor


lastButtonState = buttonState;
lastbuttonstate = buttonstate;
lcd.setCursor(2,0); // Putting some data on the LCD
lcd.print(RotaryPosition);
delay(4);
lcd.print(" ");// needed to overwrite the display
lcd.setCursor(6,0);
lcd.print(StepsToTake);
delay(4);
lcd.print(" ");// needed to overwrite the display
lcd.setCursor(10,0);
lcd.print("Step=1");
lcd.setCursor(0,1);
lcd.print("N6QW Digi/Al VFO");
//*************************************



//**************************************
small_stepper.setSpeed(32); //Max seems to be 700 Changed from 600
if (!(digitalRead(PinSW))) { // check if button is pressed
if (RotaryPosition == 0) { // check if button was already pressed
} else {
small_stepper.step(-(RotaryPosition*1)); //changed from 50 changed to 5
RotaryPosition=0; // Reset position to ZERO
}
}

// Runs if rotation was detected
if (TurnDetected) {
PrevPosition = RotaryPosition; // Save previous position in variable
if (rotationdirection) {
RotaryPosition=RotaryPosition-1;} // decrase Position by 1
else {
RotaryPosition=RotaryPosition+1;} // increase Position by 1

TurnDetected = false; // do NOT repeat IF loop until new rotation detected

// Which direction to move Stepper motor
if ((PrevPosition + 1) == RotaryPosition) { // Move motor CW
StepsToTake=1; //changed from 50
small_stepper.step(StepsToTake);
}

if ((RotaryPosition + 1) == PrevPosition) { // Move motor CCW
StepsToTake=-1; //changed from 50
small_stepper.step(StepsToTake);
}
}
}
//**********CheckMode();**************************
void CheckMode(){
buttonState = digitalRead(PinSWIn); // Does the turn off of the Motor Start Stop
if(buttonState != lastButtonState){
if(buttonState == LOW){
small_stepper.step(-(RotaryPosition*1)); //changed from 50 changed to 5
RotaryPosition=0; // Reset position to ZERO
}
delay(10000);//Allows enough time to zero the stepper
digitalWrite(PinSWOut, HIGH);
}

}

 

This is the schematic of the wiring of the Arduino Motor Start Stop power on power off. The main Push Button, Latch Relay and Relay #2 are external to the Arduino. Power "ON" is completely external to Arduino and does not require the power to the Arduino. Power OFF requires the Arduino to be working. When I used to be in the controls business selling Modicon Micro 84 PLC's to allay the fears of "newbie" customers we could add an external NC Push Button switch in series with with the chain so you could enact an emergency shutdown. It was a honkin, big, old and bold RED Knob.

 

 

 

 

 

I have now further refined this code so that on power down the location will be set to the "ZERO" position so that Bill's Digitally Controlled Analog VFO will always have repeatable accuracy.

I have also procured a larger NEMA 17 stepper motor and control board for additional testing using much larger capacitors. Vision if you will not one, but two Arduino Uno R3's in a control scheme that one Arduino Controls only one NEMA 17 Stepper Motor. Boom we now have the makings of a remotely tuned "T Type" Antenna Tuner. Yes you could do it with an Arduino Mega2560 and eliminate the second Uno R3. In fact brainy-bits describes how to put two displays on a single Arduino. But I have a junk box full of Uno R3's and a few 16X2 LCD's. So the hardware is already here.

A Remote Controlled Digitally Tuned

ANTENNA TUNER

  • One Arduino Uno R3 would control one large Variable Capacitor and in addition would control about 5 relays whose contacts short out portions of a very large Air Dux coil. A bank of Switches connected to Uno R3 #1 would select the relays.
  • The second Arduino Uno R3 would control the second large Variable Capacitor and also receive and display SWR Information.
  • Here is where my addition of the LCD to the original prototype would come into play. From the "Zero" position (typically at the midpoint of rotation) the number of steps + (CW) and number of steps - (CCW) are displayed on the LCD. As with the 1st prototype on power down both caps are set to "Zero". Thus the movement will always be repeatable.
  • A few trials would be required to find the settings for both capacitors and the taps on the inductor for each band. Once these constants are known then it is a simple matter that when you change bands then crank in the settings and you are "Tuned Up".
  • Now for the really "hot shot Arduino programmers" these values can be put in a table and stored in the Arduino Code so that by adding say a keypad you could simply punch in a key and all gets done for you.

I keep thinking of N2CQR who must brave the elements to tune up his outdoor "trans match" to change bands. It may now be possible to do this via a 415 MHz radio link from his shack to the outdoor antenna tuner. Look Ma, no wires!

Regrettably Bill, it is one of those black boxes that you simply can't see all of the resistors, capacitors, coils and caps; but when it is below Zero outside --sure nice to tune the antenna from the shack. The bonus --it is a true homebrew Tuner.

I hope to unveil it in time for our next Podcast.

73's

Pete, N6QW