Project in progress
10kHz to 120MHz VFO / RF Generator with Si5351 and Arduino

10kHz to 120MHz VFO / RF Generator with Si5351 and Arduino © GPL3+

VFO/RF generator for use in homebrew radio equipment such as direct conversion and superheterodyne receivers or ham transmitters.

  • 28,388 views
  • 56 comments
  • 48 respects

Components and supplies

Apps and online services

About this project

10kHz to 120MHz VFO / RF Generator with Si5351 and Arduino.

This is a project of a VFO (variable frequency oscillator) for use in homebrew equipement such as Direct Conversion and Superheterodyne Receivers or Ham Transmitters. Can be used as RF/Clock generator too.

Features:

  • Operation range from 10kHz to 120MHz.
  • Tuning steps of 1Hz, 10Hz, 1kHz, 5kHz, 10kHz and 1MHz.
  • Intermediate Frequency (IF) offset (+ or -) adjustable (see note below).
  • For use as Local Oscillator on homebrew Superheterodyne or Direct Conversion radio receivers.
  • For use as VFO for HAM radio transmitters.
  • For use as a simple RF/Clock generator for calibration reference or clock generation.
  • Works with Arduino Uno, Nano and Pro Mini.
  • Uses a common 128x64 I2C OLED SSD1306 display and Si5351 module.
  • I2C data transfer, only 2 wires to connect the display / Si5351 and arduino.
  • High stability and precision for frequency generation.
  • Simple yet very efficient and free.

Pictures of display:

The VFO in action:

I am updating the VFO Si5351 code, which will have new functions: maximum frequency increased to 225MHz, band preset and band indication, RX / TX function, generator function and maybe a bargraph for measurement. The above video shows the work in progress.

Schematics / wiring:

Instructions:

  • Open the scketch on Arduino IDE, install all the required libraries.
  • Choose the preferences (see note) and compile the sketch and then load it to the Arduino Nano, Uno or Pro Mini.
  • Follow the schematics to wire the Arduino, Display, Si5351 module, rotary encoder, etc.
  • Power up the Arduino.
  • Rotate the rotary encoder to tune up or down the frequency.
  • Push the encoder button to change the frequency step tuning. The steps available are 1Hz, 10Hz, 1kHz, 5kHz, 10kHz and 1MHz.

Note about User Preferences:

-It is possible to change the followings items on sketch:

#define IF 0: Enter your IF frequency, ex: 455 = 455kHz, 10700 = 10.7MHz,

0 = to direct convert receiver or RF generator, "+" will add and "-" will subtract

IF offfset.

#define FREQ_INIT 7000000: Enter your initial frequency at startup, ex:

7000000 = 7MHz, 10000000 = 10MHz, 840000 = 840kHz.

#define XT_CAL_F 33000: Si5351 calibration factor, adjust to get exatcly 10MHz. Increasing this value will decreases the frequency and vice versa.

#define tunestep A0: Change the pin of encoder push button if you want.

******************************************************************************

Julio Cesar - CesarSound - ver 1.0 - Dec/2020.

Code

Sketch SI5351_VFO_RF_GEN_OLED_JCRC/C++
VFO work. Load it to the Arduino using the IDE.
/********************************************************************************************************
  10kHz to 120MHz VFO / RF Generator with Si5351 and Arduino Nano, with Intermediate Frequency (IF)
  offset (+ or -). See the schematics for wiring details. By J. CesarSound - ver 1.0 - Dec/2020.
*********************************************************************************************************/

//Libraries
#include <Wire.h>                 //IDE Standard
#include <Rotary.h>               //Ben Buxton https://github.com/brianlow/Rotary
#include <si5351.h>               //Etherkit https://github.com/etherkit/Si5351Arduino
#include <Adafruit_GFX.h>         //Adafruit GFX https://github.com/adafruit/Adafruit-GFX-Library
#include <Adafruit_SSD1306.h>     //Adafruit SSD1306 https://github.com/adafruit/Adafruit_SSD1306

//User preferences
//------------------------------------------------------------------------------------------------------------
#define IF  0                //Enter your IF frequency, ex: 455 = 455kHz, 10700 = 10.7MHz, 0 = to direct convert receiver or RF generator, + will add and - will subtract IF offfset.
#define FREQ_INIT  7000000   //Enter your initial frequency at startup, ex: 7000000 = 7MHz, 10000000 = 10MHz, 840000 = 840kHz.
#define XT_CAL_F   33000     //Si5351 calibration factor, adjust to get exatcly 10MHz. Increasing this value will decreases the frequency and vice versa.
#define tunestep   A0        //Change the pin used by encoder push button if you want.
//------------------------------------------------------------------------------------------------------------

Rotary r = Rotary(2, 3);
Adafruit_SSD1306 display = Adafruit_SSD1306(128, 64, &Wire);
Si5351 si5351;

unsigned long freq = FREQ_INIT;
unsigned long freqold, fstep;
long interfreq = IF;
long cal = XT_CAL_F;
unsigned long long pll_freq = 90000000000ULL;
byte encoder = 1;
byte stp;
unsigned int period = 100;   //millis display active
unsigned long time_now = 0;  //millis display active

ISR(PCINT2_vect) {
  char result = r.process();
  if (result == DIR_CW) set_frequency(1);
  else if (result == DIR_CCW) set_frequency(-1);
}

void set_frequency(short dir) {
  if (encoder == 1) {                         //Up/Down frequency
    if (dir == 1) freq = freq + fstep;
    if (freq >= 120000000) freq = 120000000;
    if (dir == -1) freq = freq - fstep;
    if (fstep == 1000000 && freq <= 1000000) freq = 1000000;
    else if (freq < 10000) freq = 10000;
  }
}

void setup() {
  Wire.begin();
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  display.clearDisplay();
  display.setTextColor(WHITE);
  display.display();

  pinMode(2, INPUT_PULLUP);
  pinMode(3, INPUT_PULLUP);
  pinMode(tunestep, INPUT_PULLUP);

  statup_text();

  si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0, cal);
  si5351.output_enable(SI5351_CLK0, 1);                  //1 - Enable / 0 - Disable CLK
  si5351.output_enable(SI5351_CLK1, 0);
  si5351.output_enable(SI5351_CLK2, 0);
  si5351.drive_strength(SI5351_CLK0, SI5351_DRIVE_2MA);  //Output current 2MA, 4MA, 6MA or 8MA

  PCICR |= (1 << PCIE2);
  PCMSK2 |= (1 << PCINT18) | (1 << PCINT19);
  sei();

  stp = 3;
  setstep();
  layout();
  displayfreq();
}

void loop() {
  if (freqold != freq) {
    time_now = millis();
    tunegen();
    freqold = freq;
  }

  if (digitalRead(tunestep) == LOW) {
    time_now = (millis() + 300);
    setstep();
    delay(300);
  }

  if ((time_now + period) > millis()) {
    displayfreq();
    layout();
  }
}

void tunegen() {
  si5351.set_freq_manual((freq + (interfreq * 1000ULL)) * 100ULL, pll_freq, SI5351_CLK0);
}

void displayfreq() {
  unsigned int m = freq / 1000000;
  unsigned int k = (freq % 1000000) / 1000;
  unsigned int h = (freq % 1000) / 1;

  display.clearDisplay();
  display.setTextSize(2);

  char buffer[15] = "";
  if (m < 1) {
    display.setCursor(41, 1); sprintf(buffer, "%003d.%003d", k, h);
  }
  else if (m < 100) {
    display.setCursor(5, 1); sprintf(buffer, "%2d.%003d.%003d", m, k, h);
  }
  else if (m >= 100) {
    unsigned int h = (freq % 1000) / 10;
    display.setCursor(5, 1); sprintf(buffer, "%2d.%003d.%02d", m, k, h);
  }
  display.print(buffer);
}

void setstep() {
  switch (stp) {
    case 1:
      stp = 2;
      fstep = 1;
      break;
    case 2:
      stp = 3;
      fstep = 10;
      break;
    case 3:
      stp = 4;
      fstep = 1000;
      break;
    case 4:
      stp = 5;
      fstep = 5000;
      break;
    case 5:
      stp = 6;
      fstep = 10000;
      break;
    case 6:
      stp = 1;
      fstep = 1000000;
      break;
  }
}

void layout() {
  display.setTextColor(WHITE);
  display.drawLine(0, 20, 127, 20, WHITE);
  display.drawLine(0, 43, 127, 43, WHITE);
  display.drawLine(105, 24, 105, 39, WHITE);
  display.setTextSize(2);
  display.setCursor(2, 25);
  display.print("TS:");
  if (stp == 2) display.print("1Hz"); if (stp == 3) display.print("10Hz"); if (stp == 4) display.print("1k");
  if (stp == 5) display.print("5k"); if (stp == 6) display.print("10k"); if (stp == 1) display.print("1M");
  display.setCursor(2, 48);
  display.print("IF:");
  display.print(interfreq);
  display.print("k");
  display.setTextSize(1);
  display.setCursor(110, 23);
  if (freq < 1000000) display.print("kHz");
  if (freq >= 1000000) display.print("MHz");
  display.setCursor(110, 33);
  if (interfreq == 0) display.print("VFO");
  if (interfreq != 0) display.print("L O");
  display.display();
}

void statup_text() {
  display.setTextSize(1);
  display.setCursor(4, 5);
  display.print("Si5351");
  display.setCursor(4, 20);
  display.print("VFO / RF Generator");
  display.setCursor(4, 35);
  display.print("Version 1.0");
  display.setCursor(4, 50);
  display.print(">> JCR RADIO <<");
  display.display();
  delay(3000);
  display.clearDisplay();
}

Schematics

Wiring diagram (schematics)
To interconnect the parts.

Comments

Similar projects you might like

10kHz to 225MHz VFO/RF Generator with Si5351 - Version 2

Project in progress by CesarSound

  • 11,068 views
  • 33 comments
  • 34 respects

DIY Sensitive Software Defined Radio with AD9850 VFO

Project tutorial by Mirko Pavleski

  • 14,125 views
  • 0 comments
  • 22 respects

JX Wave Generator

Project tutorial by janux

  • 6,450 views
  • 6 comments
  • 22 respects

DIY Si4730 All Band Radio (LW, MW, SW, FM)

Project tutorial by Mirko Pavleski

  • 51,023 views
  • 12 comments
  • 80 respects

Home Plant Watering System

Project tutorial by Alexander

  • 29,183 views
  • 4 comments
  • 76 respects

echoTrek - Digital Delay / Echo - Audio Effects with Arduino

Project in progress by CesarSound

  • 6,168 views
  • 14 comments
  • 23 respects
Add projectSign up / Login