Attached is the VFO code from the March, 2016, QST article. The VFO is for the AD9850, but the EEPROM routines are used to save the band edges, and the last frequency that was used before shutdown. Also, the EEPROM address is only written if you have stayed on the frequency for a while. This is because the EEPROM has a limited write cycle. A Nano is used in the VFO. If you want details on the VFO, see:
?www.farrukhzia.com/k2zia?
Jack, W8TEE
From: "Jason W6IEE w6iee.73@... [BITX20]" <BITX20@...>
To: bitx20@...
Sent: Thursday, December 29, 2016 12:22 PM
Subject: [BITX20] Si5351 vfo details needed
? Got my bitx40 in the mail yesterday! I had to take some slack out of the LPF coils that had cracked loose from their glue in shipping, and had to turn around the tuning pot connector on the board that was backwards... Just data points, definitely not complaints!!! Was no problem to take care of.
I have the receive side up and running.
I do have the non-DDS vfo version, ?and now I definitely want to go the DDS/LCD route! ;)
Pardon if this has been covered in detail recently, but I've just recently re-joined the group:
I'm a little overwhelmed by the digital vfo options available, including what I see in the files section. I'd like to use an Si5351, I have both Adafruit and QRP Labs versions. I've got any multitude of Arduino clones, nano, pro mini, etc.?
I'd like to use a sketch that will write to eeprom, so that the last frequency on the vfo comes back up on the next power-up.?
Where might I find this, or something very close?
Also, is the actual Raduino circuit and sketch "public domain" and downloadable? Wasn't able to find it in last night's googling.?
Lastly, on kind of a separate subject, and a last resort, is the Raduino by itself available for purchase from hfsigs?
Thanks Farhan & Co. for such a fun board!73,Jason W6IEE #yiv6730409600 #yiv6730409600 -- #yiv6730409600ygrp-mkp {border:1px solid #d8d8d8;font-family:Arial;margin:10px 0;padding:0 10px;}#yiv6730409600 #yiv6730409600ygrp-mkp hr {border:1px solid #d8d8d8;}#yiv6730409600 #yiv6730409600ygrp-mkp #yiv6730409600hd {color:#628c2a;font-size:85%;font-weight:700;line-height:122%;margin:10px 0;}#yiv6730409600 #yiv6730409600ygrp-mkp #yiv6730409600ads {margin-bottom:10px;}#yiv6730409600 #yiv6730409600ygrp-mkp .yiv6730409600ad {padding:0 0;}#yiv6730409600 #yiv6730409600ygrp-mkp .yiv6730409600ad p {margin:0;}#yiv6730409600 #yiv6730409600ygrp-mkp .yiv6730409600ad a {color:#0000ff;text-decoration:none;}#yiv6730409600 #yiv6730409600ygrp-sponsor #yiv6730409600ygrp-lc {font-family:Arial;}#yiv6730409600 #yiv6730409600ygrp-sponsor #yiv6730409600ygrp-lc #yiv6730409600hd {margin:10px 0px;font-weight:700;font-size:78%;line-height:122%;}#yiv6730409600 #yiv6730409600ygrp-sponsor #yiv6730409600ygrp-lc .yiv6730409600ad {margin-bottom:10px;padding:0 0;}#yiv6730409600 #yiv6730409600actions {font-family:Verdana;font-size:11px;padding:10px 0;}#yiv6730409600 #yiv6730409600activity {background-color:#e0ecee;float:left;font-family:Verdana;font-size:10px;padding:10px;}#yiv6730409600 #yiv6730409600activity span {font-weight:700;}#yiv6730409600 #yiv6730409600activity span:first-child {text-transform:uppercase;}#yiv6730409600 #yiv6730409600activity span a {color:#5085b6;text-decoration:none;}#yiv6730409600 #yiv6730409600activity span span {color:#ff7900;}#yiv6730409600 #yiv6730409600activity span .yiv6730409600underline {text-decoration:underline;}#yiv6730409600 .yiv6730409600attach {clear:both;display:table;font-family:Arial;font-size:12px;padding:10px 0;width:400px;}#yiv6730409600 .yiv6730409600attach div a {text-decoration:none;}#yiv6730409600 .yiv6730409600attach img {border:none;padding-right:5px;}#yiv6730409600 .yiv6730409600attach label {display:block;margin-bottom:5px;}#yiv6730409600 .yiv6730409600attach label a {text-decoration:none;}#yiv6730409600 blockquote {margin:0 0 0 4px;}#yiv6730409600 .yiv6730409600bold {font-family:Arial;font-size:13px;font-weight:700;}#yiv6730409600 .yiv6730409600bold a {text-decoration:none;}#yiv6730409600 dd.yiv6730409600last p a {font-family:Verdana;font-weight:700;}#yiv6730409600 dd.yiv6730409600last p span {margin-right:10px;font-family:Verdana;font-weight:700;}#yiv6730409600 dd.yiv6730409600last p span.yiv6730409600yshortcuts {margin-right:0;}#yiv6730409600 div.yiv6730409600attach-table div div a {text-decoration:none;}#yiv6730409600 div.yiv6730409600attach-table {width:400px;}#yiv6730409600 div.yiv6730409600file-title a, #yiv6730409600 div.yiv6730409600file-title a:active, #yiv6730409600 div.yiv6730409600file-title a:hover, #yiv6730409600 div.yiv6730409600file-title a:visited {text-decoration:none;}#yiv6730409600 div.yiv6730409600photo-title a, #yiv6730409600 div.yiv6730409600photo-title a:active, #yiv6730409600 div.yiv6730409600photo-title a:hover, #yiv6730409600 div.yiv6730409600photo-title a:visited {text-decoration:none;}#yiv6730409600 div#yiv6730409600ygrp-mlmsg #yiv6730409600ygrp-msg p a span.yiv6730409600yshortcuts {font-family:Verdana;font-size:10px;font-weight:normal;}#yiv6730409600 .yiv6730409600green {color:#628c2a;}#yiv6730409600 .yiv6730409600MsoNormal {margin:0 0 0 0;}#yiv6730409600 o {font-size:0;}#yiv6730409600 #yiv6730409600photos div {float:left;width:72px;}#yiv6730409600 #yiv6730409600photos div div {border:1px solid #666666;height:62px;overflow:hidden;width:62px;}#yiv6730409600 #yiv6730409600photos div label {color:#666666;font-size:10px;overflow:hidden;text-align:center;white-space:nowrap;width:64px;}#yiv6730409600 #yiv6730409600reco-category {font-size:77%;}#yiv6730409600 #yiv6730409600reco-desc {font-size:77%;}#yiv6730409600 .yiv6730409600replbq {margin:4px;}#yiv6730409600 #yiv6730409600ygrp-actbar div a:first-child {margin-right:2px;padding-right:5px;}#yiv6730409600 #yiv6730409600ygrp-mlmsg {font-size:13px;font-family:Arial, helvetica, clean, sans-serif;}#yiv6730409600 #yiv6730409600ygrp-mlmsg table {font-size:inherit;font:100%;}#yiv6730409600 #yiv6730409600ygrp-mlmsg select, #yiv6730409600 input, #yiv6730409600 textarea {font:99% Arial, Helvetica, clean, sans-serif;}#yiv6730409600 #yiv6730409600ygrp-mlmsg pre, #yiv6730409600 code {font:115% monospace;}#yiv6730409600 #yiv6730409600ygrp-mlmsg * {line-height:1.22em;}#yiv6730409600 #yiv6730409600ygrp-mlmsg #yiv6730409600logo {padding-bottom:10px;}#yiv6730409600 #yiv6730409600ygrp-msg p a {font-family:Verdana;}#yiv6730409600 #yiv6730409600ygrp-msg p#yiv6730409600attach-count span {color:#1E66AE;font-weight:700;}#yiv6730409600 #yiv6730409600ygrp-reco #yiv6730409600reco-head {color:#ff7900;font-weight:700;}#yiv6730409600 #yiv6730409600ygrp-reco {margin-bottom:20px;padding:0px;}#yiv6730409600 #yiv6730409600ygrp-sponsor #yiv6730409600ov li a {font-size:130%;text-decoration:none;}#yiv6730409600 #yiv6730409600ygrp-sponsor #yiv6730409600ov li {font-size:77%;list-style-type:square;padding:6px 0;}#yiv6730409600 #yiv6730409600ygrp-sponsor #yiv6730409600ov ul {margin:0;padding:0 0 0 8px;}#yiv6730409600 #yiv6730409600ygrp-text {font-family:Georgia;}#yiv6730409600 #yiv6730409600ygrp-text p {margin:0 0 1em 0;}#yiv6730409600 #yiv6730409600ygrp-text tt {font-size:120%;}#yiv6730409600 #yiv6730409600ygrp-vital ul li:last-child {border-right:none !important;}#yiv6730409600
----------
/*
The heart of the code that manages the AD9850 was written by
Richard Visokey AD7C - www.ad7c.com
Modifications were made by Jack Purdum and Dennis Kidder:
Rev 4.00: Feb. 2, 2015
Rev 5.00: July 8, 2015, Jack Purdum
Rev.6.00: Aug. 1, 2015, Jack Purdum
Rev.6.10: Feb. 27, 2016, Jack Purdum, decreased the number of elements in the increment array
*/
#include <rotary.h> // From Brian Low:
#include <EEPROM.h> // Shipped with IDE
#include <Wire.h> // "
// Get the LCD I2C Library here:
//
// Move all *_I2C files into a new folder named LiquidCrystal_I2C
// in the Arduino library folder
#include <LiquidCrystal_I2C.h> //
#define MYTUNINGCONSTANT 34.35910479585483 // Replace with your calculated TUNING CONSTANT. See article
#define SPACES " "
#define HERTZ "Hz"
#define KILOHERTZ "kHz"
#define MEGAHERTZ "mHz"
#define GENERAL 2
#define TECH 1
#define EXTRA 0
#define ELEMENTCOUNT(x) (sizeof(x) / sizeof(x[0])) // A macro that calculates the number
// of elements in an array.
#define pulseHigh(pin) {digitalWrite(pin, HIGH); digitalWrite(pin, LOW); } // Write macro
//Setup some VFO band edges
#define VFOUPPERFREQUENCYLIMIT 7300000L // Upper band edge
#define VFOLOWERFREQUENCYLIMIT 7000000L // Lower band edge
#define VFOLOWALLBUTEXTRA 7025000L // Frequency of Extra licensees only
#define VFOHIGHTECH 7125000L // Hi Tech cutoff
#define VFOLOWTECH 7100000L // Low Tech cutoff
#define VFOGENERALLOWGAP 7175000L // General class edge
#define W_CLK 8 // Pin 8 - connect to AD9850 module word load clock pin (CLK)
#define FQ_UD 9 // Pin 9 - connect to freq update pin (FQ)
#define DATA 10 // Pin 10 - connect to serial data load pin (DATA)
#define RESET 11 // Pin 11 - connect to reset pin (RST)
#define LCDCOLS 16 // LCD stuff
#define LCDROWS 2
#define SPLASHDELAY 4000 // Hold splash screen for 4 seconds
#define ROTARYSWITCHPIN 7 // Used by switch for rotary encoder
//define RITPIN 8 // Used by push button switch to toggle RIT
#define RXTXPIN 12 // When HIGH, the xcvr is in TX mode
#define RITOFFSETSTART 700L // Offset for RIT
#define FREQINBAND 0 // Used with range checking
#define FREQOUTOFBAND 1
// ===================================== EEPROM Offsets and data ==============================================
#define READEEPROMFREQ 0 // The EEPROM record address of the last written frequency
#define READEEPROMINCRE 1 // The EEPROM record address of last frequency increment
#define DELTAFREQOFFSET 25 // Must move frequency more than this to call it a frequency change
#define DELTATIMEOFFSET 60000 // If not change in frequency within 1 minute, update the frequency
unsigned long markFrequency; // The frequency just written to EEPROM
long eepromStartTime; // Set when powered up and while tuning
long eepromCurrentTime; // The current time reading
// ============================ ISR variables: ======================================
volatile int_fast32_t currentFrequency; // Starting frequency of VFO
volatile long currentFrequencyIncrement;
volatile long ritOffset;
// ============================ General Global Variables ============================
bool encoderState;
bool oldEncoderState;
char temp[17];
int ritDisplaySwitch; // variable to index into increment arrays (see below)
int incrementIndex = 0;
long oldRitOffset;
int_fast32_t oldFrequency = 1; // variable to hold the updated frequency
static char *bandWarnings[] = {"Extra ", "Tech ", "General"};
static int whichLicense;
static char *incrementStrings[] = {"10", "20", "100", "1", "5", "10", "100"}; // These two allign
static long incrementTable[] = { 10, 20, 100, 1000, 5000, 10000, 100000};
static long memory[] = {VFOLOWERFREQUENCYLIMIT, VFOUPPERFREQUENCYLIMIT};
Rotary r = Rotary(2, 3); // Create encoder object and set the pins the rotary encoder uses. Must be interrupt pins.
LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE); // Create LCD object and set the LCD I2C address
void setup() {
// ===================== Set up from EEPROM memory ======================================
currentFrequency = readEEPROMRecord(READEEPROMFREQ); // Last frequency read while tuning
if (currentFrequency < 7000000L || currentFrequency > 7300000L) // New EEPROM usually initialized with 0xFF
currentFrequency = 7030000L; // Default QRP freq if no EEPROM recorded yet
markFrequency = currentFrequency; // Save EEPROM freq.
incrementIndex = (int) readEEPROMRecord(READEEPROMINCRE); // Saved increment as written to EEPROM
if (incrementIndex < 0 || incrementIndex > 9) // If none stored in EEPROM yet...
incrementIndex = 0; // ...set to 10Hz
currentFrequencyIncrement = incrementTable[incrementIndex]; // Store working freq variables
markFrequency = currentFrequency;
eepromStartTime = millis(); // Need to keep track of EEPROM update time
pinMode(ROTARYSWITCHPIN, INPUT);
// pinMode(RITPIN, INPUT);
// digitalWrite(RITPIN, INPUT_PULLUP);
pinMode(RXTXPIN, LOW); // Start in RX mode
oldEncoderState = encoderState = LOW; // Receiver incremental tuning state HIGH, LOW
ritOffset = RITOFFSETSTART; // Default RIT offset
ritDisplaySwitch = 0;
lcd.begin(LCDCOLS, LCDROWS); // Display LCD
Splash(); // Tell 'em were here...
PCICR |= (1 << PCIE2); // Interrupt service code
PCMSK2 |= (1 << PCINT18) | (1 << PCINT19);
sei();
pinMode(FQ_UD, OUTPUT); // Tied to AD9850 board
pinMode(W_CLK, OUTPUT);
pinMode(DATA, OUTPUT);
pinMode(RESET, OUTPUT);
pulseHigh(RESET);
pulseHigh(W_CLK);
pulseHigh(FQ_UD); // this pulse enables serial mode on the AD9850 - Datasheet page 12.`
ProcessSteps();
DoRangeCheck();
ShowMarker(bandWarnings[whichLicense]);
sendFrequency(currentFrequency);
}
void loop() {
static int state = 1; // 1 because of pull-ups on encoder switch
static int oldState = 1;
int flag;
state = digitalRead(ROTARYSWITCHPIN); // See if they pressed encoder switch
// encoderState = digitalRead(RITPIN); // Also check RIT button
if (state != oldState) { // Only if it's been changed...
if (state == 1) { // Only adjust increment when HIGH
if (incrementIndex < ELEMENTCOUNT(incrementTable) - 1) { // Adjust the increment size
incrementIndex++;
} else {
incrementIndex = 0; // Wrap around to zero
}
currentFrequencyIncrement = incrementTable[incrementIndex];
ProcessSteps();
}
oldState = state;
}
if (currentFrequency != oldFrequency) { // Are we still looking at the same frequency?
NewShowFreq(0, 0); // Nope, so update display.
flag = DoRangeCheck();
if (flag == FREQOUTOFBAND) { // Tell user if out of band; should not happen
lcd.setCursor(0, 0);
lcd.print("* Out of Band *");
}
sendFrequency(currentFrequency); // Send frequency to chip
oldFrequency = currentFrequency;
}
eepromCurrentTime = millis();
// Only update EEPROM if necessary...both time and currently stored freq.
if (eepromCurrentTime - eepromStartTime > DELTATIMEOFFSET && markFrequency != currentFrequency) {
writeEEPROMRecord(currentFrequency, READEEPROMFREQ); // Update freq
writeEEPROMRecord((unsigned long) incrementIndex, READEEPROMINCRE); // Update increment
eepromStartTime = millis();
markFrequency = currentFrequency; // Update EEPROM freq.
}
if (encoderState == HIGH) { // Change RIT?
//DoRitDisplay();
ritDisplaySwitch = 1;
}
if (oldEncoderState != encoderState) {
ProcessSteps();
oldEncoderState = encoderState;
ritDisplaySwitch = 0;
}
}
void DoRitDisplay()
{
char tempOffset[8];
if (oldRitOffset == ritOffset && ritDisplaySwitch == 1)
return;
DisplayLCDLine(SPACES, 1, 0);
ltoa(ritOffset, tempOffset, 10);
strcpy(temp, "Offset: ");
strcat(temp, tempOffset);
DisplayLCDLine(temp, 1, 0);
oldRitOffset = ritOffset;
ritDisplaySwitch = 1;
}
// Original interrupt service routine, as modified by Jack
ISR(PCINT2_vect) {
unsigned char result = r.process();
switch (result) {
case 0: // Nothing done...
return;
case DIR_CW: // Turning Clockwise, going to higher frequencies
if (encoderState == LOW) {
currentFrequency += currentFrequencyIncrement;
} else {
ritOffset += RITOFFSETSTART;
}
break;
case DIR_CCW: // Turning Counter-Clockwise, going to lower frequencies
if (encoderState == LOW) {
currentFrequency -= currentFrequencyIncrement;
} else {
ritOffset -= RITOFFSETSTART;
}
break;
default: // Should never be here
break;
}
if (currentFrequency >= VFOUPPERFREQUENCYLIMIT) { // Upper band edge?
currentFrequency = oldFrequency;
}
if (currentFrequency <= VFOLOWERFREQUENCYLIMIT) { // Lower band edge?
currentFrequency = oldFrequency;
}
}
void sendFrequency(int32_t frequency) {
/*
Formula: int32_t adjustedFreq = frequency * 4294967295/125000000;
Note the 125 MHz clock on 9850. You can make 'slight' tuning
variations here by adjusting the clock frequency. The constants
factor to 34.359
*/
int32_t freq = (int32_t) (((float) frequency * MYTUNINGCONSTANT)); // Redefine your constant if needed
for (int b = 0; b < 4; b++, freq >>= 8) {
tfr_byte(freq & 0xFF);
}
tfr_byte(0x000); // Final control byte, all 0 for 9850 chip
pulseHigh(FQ_UD); // Done! Should see output
}
// transfers a byte, a bit at a time, LSB first to the 9850 via serial DATA line
void tfr_byte(byte data)
{
for (int i = 0; i < 8; i++, data >>= 1) {
digitalWrite(DATA, data & 0x01);
pulseHigh(W_CLK); //after each bit sent, CLK is pulsed high
}
}
/
This method is used to format a frequency on the lcd display. The currentFrequqncy variable holds the display
frequency.
Argument list:
void
Return value:
void
/
void DisplayLCDLine(char *message, int row, int col)
{
lcd.setCursor(col, row);
lcd.print(message);
}
/
This method is used to read a record from EEPROM. Each record is 4 bytes (sizeof(unsigned long)) and
is used to calculate where to read from EEPROM.
Argument list:
int record the record to be read. While tuning, it is record 0
Return value:
unsigned long the value of the record,
CAUTION: Record 0 is the current frequency while tuning, etc. Record 1 is the number of stored
frequencies the user has set. Therefore, the stored frequencies list starts with record 23.
/
unsigned long readEEPROMRecord(int record)
{
int offset;
union {
byte array[4];
unsigned long val;
} myUnion;
offset = record * sizeof(unsigned long);
myUnion.array[0] = EEPROM.read(offset);
myUnion.array[1] = EEPROM.read(offset + 1);
myUnion.array[2] = EEPROM.read(offset + 2);
myUnion.array[3] = EEPROM.read(offset + 3);
return myUnion.val;
}
/
This method is used to test and perhaps write the latest frequency to EEPROM. This routine is called
every DELTATIMEOFFSET (default = 10 seconds). The method tests to see if the current frequency is the
same as the last frequency (markFrequency). If they are the same, +/- DELTAFREQOFFSET (drift?), no
write to EEPROM takes place. If the change was larger than DELTAFREQOFFSET, the new frequency is
written to EEPROM. This is done because EEPROM can only be written/erased 100K times before it gets
flaky.
Argument list:
unsigned long freq the current frequency of VFO
int record the record to be written. While tuning, it is record 0
Return value:
void
/
void writeEEPROMRecord(unsigned long freq, int record)
{
int offset;
union {
byte array[4];
unsigned long val;
} myUnion;
if (abs(markFrequency - freq) < DELTAFREQOFFSET) { // Is the new frequency more or less the same as the one last written?
return; // the same as the one last written? If so, go home.
}
myUnion.val = freq;
offset = record * sizeof(unsigned long);
EEPROM.write(offset, myUnion.array[0]);
EEPROM.write(offset + 1, myUnion.array[1]);
EEPROM.write(offset + 2, myUnion.array[2]);
EEPROM.write(offset + 3, myUnion.array[3]);
markFrequency = freq; // Save the value just written
}
/
This method is used to format a frrequency on the lcd display. The currentFrequqncy variable holds the display
frequency. This is kinda clunky...
Argument list:
void
Return value:
void
/
void NewShowFreq(int row, int col) {
char part[10];
dtostrf( (float) currentFrequency, 7,0, temp);
strcpy(part, &temp[1]);
strcpy(temp, "7.");
strcat(temp, part);
strcpy(part, &temp[5]);
temp[5] = '.';
strcpy(&temp[6], part);
strcat(temp, " ");
strcat(temp, MEGAHERTZ);
lcd.setCursor(col, row);
lcd.print(SPACES);
lcd.setCursor(col + 2, row);
lcd.print(temp);
}
/
This method is used to see if the current frequency displayed on line 1 is within a ham band.
The code does not allow you to use the band edge.
Argument list:
void
Return value:
int 0 (FREQINBAND) if within a ham band, 1 (FREQOUTOFBAND) if not
/
int DoRangeCheck()
{
if (currentFrequency <= VFOLOWERFREQUENCYLIMIT || currentFrequency >= VFOUPPERFREQUENCYLIMIT) {
return FREQOUTOFBAND;
}
//Setup some VFO band edges
if (currentFrequency <= VFOLOWALLBUTEXTRA) {
whichLicense = EXTRA;
}
if (currentFrequency > VFOLOWTECH && currentFrequency < VFOHIGHTECH) {
whichLicense = TECH;
}
if ((currentFrequency >= VFOLOWALLBUTEXTRA && currentFrequency < VFOHIGHTECH) ||
(currentFrequency > VFOGENERALLOWGAP && currentFrequency < VFOUPPERFREQUENCYLIMIT) ) {
whichLicense = GENERAL;
}
ShowMarker(bandWarnings[whichLicense]);
return FREQINBAND;
}
/
This method is used to display the current license type
Argument list:
char *c // pointer to the type of license as held in bandWarnings[]
Return value:
void
/
void ShowMarker(char *c)
{
lcd.setCursor(9, 1);
lcd.print(c);
}
/
This method is used to change the number of hertz associated with one click of the rotary encoder. Ten step
levels are provided and increasing or decreasing beyonds the array limits rolls around to the start/end
of the array.
Argument list:
void
Return value:
void
/
void ProcessSteps()
{
DisplayLCDLine(SPACES, 1, 0); // This clears the line
strcpy(temp, incrementStrings[incrementIndex]);
if (incrementIndex < 3) {
strcat(temp, HERTZ);
} else {
strcat(temp, KILOHERTZ);
}
DisplayLCDLine(temp, 1, 0);
ShowMarker(bandWarnings[whichLicense]);
}
/
This method simply displays a sign-on message
Argument list:
void
Return value:
void
/
void Splash()
{
lcd.setCursor(0, 0);
lcd.print("40M Pieces-Parts");
lcd.setCursor(3, 1);
lcd.print("Transceiver");
delay(SPLASHDELAY);
}