- Introduction
- Arduino ATmega32U4
- BFO interface
- Band table for the VFO
- Schematic
- Components
- Arduino sketch
- Photos
- Expetiments and Applications
- References
- Videos
The Si5351 is an I2C configurable clock generator that is very appropriate for receivers and transceivers projects in amateur radio applications. It is also suited for replacing crystal oscillators. It has three outputs that you can get three distinct frequencies at the same time. A great feature of the Si5351A is the possibility of using it with a microcontroller or platform like Arduino, PIC family and others. See more on Silicon Labs documentation
This project is about a VFO and BFO that you can control two clock outputs of the Si5351A by using the Arduino Micro (Atmega32u4). The VFO (CLK0) can oscillate from 100KHz to 160MHz and the second (CLK1) can oscillate from 452KHz to 458KHz.
The Arduino program has being documented in English lenguage and you can get more details about the Si5351A controls by reading the si5351_vfobfo.ino file.
This project uses the Arduino ATmega32U4 (Known as Arduino Micro) with a built-in USB. The ATmega32U4 has 5 pins (0,1,2, 3 and 7) that allow the developer to use for handling external interrupts. This feature is very useful in this project. See more about Arduino Micro (ATmega32U4) here.
The user can control the VFO and BFO by using three buttons and an encoder. The Dial was implemented with the OLED Display 128 x 64 Pixels White 0.96 Inch I2C.
- The button Band changes the range of frequency. This project divides the VFO range (from 100KHz to 160MHz) in 27 bands. See band table below;
- The button Step changes the increment and decrement step. It can be 10Hz, 50Hz, 100Hz, 500Hz, 1KHz, 2.5KHz, 5KHz, 10KHz, 100KHz and 500KHz;
- The button VFO/BFO switches from VFO to BFO and vice-versa. It allows the user to control the VFO or BFO by using the same Encoder;
- The Encoder is used to increment or decrement the current frequency by using the step value as a reference.
The Dial shows the current VFO and BFO frequencies, the step and who the encoder is controlling (VFO or BFO). When the user switch from VFO to BFO or vice-versa, the frequency information of the VFO or BFO is highlighted on display. If you want to use another display, the item Arduino sketch (Changing the kind of display device) shows how you can do that.
The VFO is separated into 27 bands. It first band oscilates from 100KHz to 1700 KHz and the last band oscilates from 135MHz to 160MHz. You might need to change this configuration. The Arduino sketch (Bands and frequency ranges) shows how you can change the band configuration.
N# | Band | From | to |
1 | LW/MW | 100 KHz | 1.7 MHz |
2 | SW1 | 1.7 MHz | 3.5 MHz |
3 | SW2 | 3.5 MHz | 4.0 MHz |
4 | SW3 | 4.0 MHz | 7.0 MHz |
5 | SW4 | 7.0 MHz | 7.3 MHz |
6 | SW5 | 7.3 MHz | 9.0 MHz |
7 | SW6 | 9.0 MHz | 10.0 MHz |
8 | SW7 | 10.0 MHz | 11.0 MHz |
9 | SW8 | 11.0 MHz | 14.0 MHz |
10 | SW9 | 14.0 Mhz | 15.0 MHz |
11 | SW10 | 15.0 MHz | 17.0 MHz |
12 | SW11 | 17.0 MHz | 18.0 MHz |
13 | SW12 | 18.0 MHz | 20.0 MHz |
14 | SW13 | 20.0 MHz | 21.35 MHz |
15 | SW14 | 21.35 MHz | 22.00 MHz |
16 | SW15 | 22.0 MHz | 24.88 MHz |
17 | SW16 | 24.88 MHz | 24.99 MHz |
18 | SW17 | 24.99 MHz | 26.0 MHz |
19 | SW18 | 26.0 MHz | 28.0 MHz |
20 | SW19 | 28.0 MHz | 30.0 MHz |
21 | VHF1 | 30.0 MHz | 50.0 MHz |
22 | VHF2 | 50.0 MHz | 54.0 MHz |
23 | VHF3 | 54.0 MHz | 86.0 MHz |
24 | FM | 86.0 MHz | 108.0 MHz |
25 | VHF4 | 108.0 MHz | 120.0 MHz |
26 | VHF5 | 120.0 MHz | 135.0 MHz |
27 | VHF6 | 135.0 MHz | 160.0 MHz |
The schematic was built using the Fritzing application, an open-source software tools to design circuits. The schematic below shows the Arduino and components wire up.
- AZDelivery 1 x OLED Display Arduino 128 x 64 Pixels White 0.96 Inch I2C IIC Module for Arduino. This project uses the Text only Arduino Library for SSD1306 OLED displays Arduino library.
- Adafruit Si5351A Clock Generator Breakout Board - 8KHz to 160MHz. This project uses the Si5351 Library for Arduino.
- One regular encoder.
- Three regular Push Button.
- Two 10nF ceramic capacitor
- Six 10K resistor
- One 1K resistor
- One Red LED
- Arduino Micro (Atmega32u4)
This project uses the Arduino Atmega32u4 compatible (Micro). With this kind of Arduino, we can use up to 5 external interrupts (pins 0,1,2,3 and 7). Then pins 0,1 and 7 were used to implement the buttons command (Step, Band and switch VFO/BFO).
The device Si5351A is connected to pins D2 (SDA) and D3 (SCL).
The OLED Display is also connected to D2(SDA) and D3 (SCL).
The Encoder is connected to Arduino on pins D8 and D9. Connect the lead A to pin D8 and lead B to pin D9.
The Band push button is connected to pin D0/Rx (see schematic). Use this button to select one of 27 bands available.
The Step push button is connected to the pin D1/Tx. Use this button to select the increment and decrement frequency steps.
The VFO/BFO switch push button is connected to the pin D7. This button changes the Encoder control from VFO to BFO and vice versa.
The main Arduino library used on this project to control the Si5351A was developed by NTS7. You can see more about this Library here.
If you want to modify the frequency of BFO to 10MHz (for example), just change the lines below.
#define MAX_BFO 1100000000LU // BFO max. frequency
#define CENTER_BFO 1000000000LU // BFO center frequency
#define MIN_BFO 990000000LU // BFO min. frequency
The bands and ranges can be changed here. You can remove band and change frequencies modifying the array band[]. See code below.
// Band database. You can change the band ranges if you need.
Band band[] = {
{"LW/MW ", 10000000LLU, 170000000LLU}, // 100KHz to 1700KHz
{"SW1 ", 170000000LLU, 350000000LLU},
{"SW2 ", 350000000LLU, 400000001LLU},
{"SW3 ", 400000000LLU, 700000000LLU},
{"SW4 ", 700000000LLU, 730000000LLU}, // 7MHz to 7.3 MHz (Amateur 40m)
{"SW5 ", 730000000LLU, 900000000LLU}, // 41m
{"SW6 ", 900000000LLU, 1000000000LLU}, // 31m
{"SW7 ", 1000000000LLU, 1100000000LLU}, // 10 MHz to 11 MHz (Amateur 30m)
{"SW8 ", 1100000000LLU, 1400000000LLU}, // 25 and 22 meters
{"SW9 ", 1400000000LLU, 1500000000LLU}, // 14MHz to 15Mhz (Amateur 20m)
{"SW10 ", 1500000000LLU, 1700000000LLU}, // 19m
{"SW11 ", 1700000000LLU, 1800000000LLU}, // 16m
{"SW12 ", 1800000000LLU, 2000000000LLU}, // 18MHz to 20Mhz (Amateur and comercial 15m)
{"SW13 ", 2000000000LLU, 2135000000LLU}, // 20MHz to 22Mhz (Amateur and comercial 15m/13m)
{"SW14 ", 2135000000LLU, 2200000000LLU},
{"SW15 ", 2235000000LLU, 2498000000LLU},
{"SW16 ", 2488000000LLU, 2499000000LLU}, // 24.88MHz to 24.99MHz (Amateur 12m)
{"SW17 ", 2499000000LLU, 2600000000LLU},
{"SW18 ", 2600000000LLU, 2800000000LLU},
{"SW19 ", 2800000000LLU, 3000000000LLU}, // 28MHz to 30MHz (Amateur 10M)
{"VHF1 ", 3000000000LLU, 5000000000LLU},
{"VHF2 ", 5000000000LLU, 5400000000LLU},
{"VHF3 ", 5400000000LLU, 8600000000LLU},
{"FM ", 8600000000LLU, 10800000000LLU}, // Comercial FM
{"VHF4 ", 10800000000LLU, 12000000000LLU}, // 108MHz to 160MHz
{"VHF5 ", 12000000000LLU, 13500000000LLU},
{"VHF6 ", 13500000000LLU, 16000000000LLU}};
// Calculate the last element position (index) of the array band
const int lastBand = (sizeof band / sizeof(Band)) - 1; // For this case will be 26.
volatile int currentBand = 0; // First band. For this case, AM is the current band.
The steps of BFO can be changed here.
// Steps database. You can change the Steps and numbers of steps here if you need.
Step step[] = {
{"10Hz ", 1000}, // VFO and BFO min. increment / decrement
{"50Hz ", 5000},
{"100Hz ", 10000},
{"500Hz ", 50000},
{"1KHz ", 100000}, // BFO max. increment / decrement
{"2.5KHz", 250000},
{"5KHz ", 500000},
{"10KHz ", 1000000},
{"100KHz", 10000000},
{"500KHz", 50000000}}; // VFO max. increment / decrement
// Calculate the index of last position of step[] array (in this case will be 8)
const int lastStepVFO = (sizeof step / sizeof(Step)) - 1; // index for max increment / decrement for VFO
volatile int lastStepBFO = 3; // index for max. increment / decrement for BFO. In this case will be is 1KHz
volatile long currentStep = 0; // it stores the current step index (50Hz in this case)
The pins for encoder and push buttons are defined below. If you need to change some push button pin, you should be aware that the push buttons are connected to pins with support to external interrupts. On Atmega32u4 you can use the pins 0,1,2,3 and 7.
#define ENCODER_PIN_A 8 // Arduino D8
#define ENCODER_PIN_B 9 // Arduino D9
#define BUTTON_STEP 0 // Control the frequency increment and decrement
#define BUTTON_BAND 1 // Controls the band
#define BUTTON_VFO_BFO 7 // Switch VFO to BFO
The Band, Step and Switch VFO/BFO buttons are implemented by using external interrupt resource. See more about Using Interrupts on Arduino.
// Will stop what Arduino is doing and call changeStep(), changeBand() or switchVFOBFO
attachInterrupt(digitalPinToInterrupt(BUTTON_STEP), changeStep, RISING); // whenever the BUTTON_STEP goes from LOW to HIGH
attachInterrupt(digitalPinToInterrupt(BUTTON_BAND), changeBand, RISING); // whenever the BUTTON_BAND goes from LOW to HIGH
attachInterrupt(digitalPinToInterrupt(BUTTON_VFO_BFO), switchVFOBFO, RISING); // whenever the BUTTON_VFO_BFO goes from LOW to HIGH
// wait for 1/2 second and the system will be ready.
The changeStep(), changeBand() and switchVFOBFO() functions implementation are shown below.
// Change frequency increment rate
void changeStep()
{
if ((millis() - elapsedTimeInterrupt) < MIN_ELAPSED_TIME)
return; // nothing to do if the time less than MIN_ELAPSED_TIME milisecounds
noInterrupts(); //// disable global interrupts:
if (currentClock == 0) // Is VFO
currentStep = (currentStep < lastStepVFO) ? (currentStep + 1) : 0; // Increment the step or go back to the first
else // Is BFO
currentStep = (currentStep < lastStepBFO) ? (currentStep + 1) : 0;
isFreqChanged = true;
clearDisplay = true;
elapsedTimeInterrupt = millis();
interrupts(); // enable interrupts
}
// Change band
void changeBand()
{
if ((millis() - elapsedTimeInterrupt) < MIN_ELAPSED_TIME)
return; // nothing to do if the time less than 11 milisecounds
noInterrupts(); // disable global interrupts:
currentBand = (currentBand < lastBand) ? (currentBand + 1) : 0; // Is the last band? If so, go to the first band (AM). Else. Else, next band.
vfoFreq = band[currentBand].minFreq;
isFreqChanged = true;
elapsedTimeInterrupt = millis();
interrupts(); // enable interrupts
}
// Switch the Encoder control from VFO to BFO and virse versa.
void switchVFOBFO()
{
if ((millis() - elapsedTimeInterrupt) < MIN_ELAPSED_TIME)
return; // nothing to do if the time less than 11 milisecounds
noInterrupts(); // disable global interrupts:
currentClock = !currentClock;
currentStep = 0; // go back to first Step (100Hz)
clearDisplay = true;
elapsedTimeInterrupt = millis();
interrupts(); // enable interrupts
}
This project uses the "OLED Display Arduino 128 x 64 Pixels White 0.96 Inch I2C". It is very small and does not provide a good look. If you need to change to a larger display device, you have to know how to connect your display device on your Arduino. Also you have to know how to program your display device for Arduino. If you are able to do that, you can follow the steps bellow.
Replace the OLED library include directives below by the library include directive of your device display.
#include "SSD1306Ascii.h"
#include "SSD1306AsciiAvrI2c.h"
Replace the OLED display declaration by your display device declaration
// OLED - Declaration for a SSD1306 display connected to I2C (SDA, SCL pins)
SSD1306AsciiAvrI2c display;
Replace the OLED display device initialization code on setup() function by the your display device initialization code.
// Initiating the OLED Display
display.begin(&Adafruit128x64, I2C_ADDRESS);
display.setFont(Adafruit5x7);
display.set2X();
display.clear();
display.print("\n PU2CLR");
delay(3000);
display.clear();
displayDial();
Replace the displayDial function code by the your display device code. Keep the name of the function (diaplayDial()).
// Show Signal Generator Information
// Verificar setCursor() em https://github.com/greiman/SSD1306Ascii/issues/53
void displayDial()
{
double vfo = vfoFreq / 100000.0;
double bfo = bfoFreq / 100000.0;
String mainFreq;
String secoundFreq;
String staticFreq;
String dinamicFreq;
// Change the display behaviour depending on who is controlled, BFO or BFO.
if (currentClock == 0)
{ // If the encoder is controlling the VFO
mainFreq = String(vfo,3);
secoundFreq = String(bfo,3);
staticFreq = "BFO";
dinamicFreq = "VFO";
}
else // encoder is controlling the VFO
{
mainFreq = String(bfo,3);
secoundFreq = String(vfo,3);
staticFreq = "VFO";
dinamicFreq = "BFO";
}
// display.setCursor(0,0)
// display.clear();
display.set2X();
display.setCursor(0, 0);
display.print(mainFreq);
display.print(" ");
display.set1X();
display.print("\n\n\n");
display.print(staticFreq);
display.print(".: ");
display.print(secoundFreq);
display.print("\nBand: ");
display.print(band[currentBand].name);
display.print("\nStep: ");
display.print(step[currentStep].name);
display.print("\n\nCtrl: ");
display.print(dinamicFreq);
}
You might need calibrate your si5351 to get more precision during signal generation. To do that you can use the si5351_calibration.ino sketch that comes with the Si5351 library for Arduino. Click here to see more about si5351 calibration. You can also try watch this Portuguese video that show how to calibrate the si5351. Another video about si5351 calibration can be watch here (Homebrew 80/40m SSB/CW Rig - #7a Si5351 Calibration).
The line code below shows the correction factor found during this project.
// Change this value below (CORRECTION_FACTOR) to 0 if you do not know the correction factor of your Si5351A.
#define CORRECTION_FACTOR 80000 // See how to calibrate your Si5351A (0 if you do not want).
if you do not want calibrate, set CORRECTION_FACTOR to 0 as shown below.
#define CORRECTION_FACTOR 0
- BFO Controlled by Arduino
- Arduino
- Arduino Micro Pinout
- Atmega32u4
- Arduino Interrupts
- All About Arduino Libraries
- Tutorial: Arduino and the I2C bus – Part One
- Arduino Frequency Synthesiser Using 160MHz Si5351, lingib
- Text only Arduino Library for SSD1306 OLED displays
- Si5351 Library for Arduino
- Si5351 calibration
- Silicon Labs Si5351A/B/C-B
- Fritzing
- My ham radio Facebook
- QRZ - Biografy and my ham radio page
- BFO with SI5351 and Arduino test with REDSUN RP2100
- VFO and BFO with Si5351A controlled by Arduino
- VFO e BFO com Si5351A e Arduino - Calibração do Si5351
- VFO e BFO com o Si5351 e OLED controlado por Arduino - (Portuguese)
- Homebrew 80/40m SSB/CW Rig - #7a Si5351 Calibration
- Arduino Signal Generator
- Arduino Tutorial: OLED 0.96" I2C/SPI Display
- Tutorial on I2C OLED Display with Arduino/NodeMCU
- 1.7inch 160x128 Color OLED(NHD-1.69-160128UGC3)
- si5351 VFO with adaptive step control bulit by JF3HZB
- Frequency Counter
- VFO Universal protótipo