|
Arduino Nano R3 |
x 1 | |
|
LCD display with ST7920 driver chip |
x 1 | |
|
Si5351 Signal Generator Module |
x 1 | |
|
Rotary Encoder with Push-Button |
x 1 | |
|
Pushbutton |
x 1 | |
|
Switch |
x 1 |
![]() |
Soldering Iron Wire |
|
![]() |
arduino IDEArduino
|
Arduino VFO Project with a Large LCD Display
A Variable Frequency Oscillator (VFO) is an electronic oscillator whose output frequency can be adjusted or varied over a specified range. It generates periodic waveforms, whose frequency can be dynamically controlled. VFOs are crucial in a wide range of applications, particularly in communications, testing, and signal processing.
In one of my previous videos I presented a way to make such a device with an ESP32 microcontroller on a color TFT display. This time I will present you a VFO which according to its characteristics is identical to the previously mentioned one, although it has an incomparably simpler code, and is made with an Arduino Nano microcontroller.
The original device that I present to you in this video is the work of Julio Cesar and all credits go to him. In fact, I made the original device more than a year ago on the SH1106 OLED display, which is larger than the SSD 1306, but even this display is relatively small and difficult to read. Therefore, I decided with my modest programming experience to rewrite the code for the ST7920 LCD Display, which is significantly larger, with a visual area of 70x40 mm.
This project is sponsored by PCBWay. They has all the services you need to create your project at the best price, whether is a scool project, or complex professional project. On PCBWay you can share your experiences, or get inspiration for your next project. They also provide completed Surface mount SMT PCB assemblY service at a best price, and ISO9001 quality control. Visit pcbway.com for more services
Since this display is not supported by the Adafruit GFX library, I used the U8G2 library in my project, which currently has support for a huge number of different display types, so by changing only one line in the code, you could use a related display.
The device is really very simple to make and consists of a few components.
- Arduino nano microcontroller
- Si5351 Signal Generator module
- LCD display with ST7920 driver chip
- Rotary Encoder with push button
- band selection button
- and RX-TX switch
Now let me briefly describe how the device works. Immediately after switching on, the display is initialized and then the working screen appears. The starting frequency is entered previously in the code and in this case it is the 40m amateur band. The frequency is changed with the rotary encoder. The tuning step is selected with the encoder knob and can be 1Hz, 10Hz, 1kHz, 5kHz, 10kHz and 1MHz. With this button we can select one of the 20Band Presets, as well as the Generator function mode. Operation range is from 10kHz to up to 200 MHz. In the code we can set the Intermediate Frequency (IF) offset (+ or -) for use in Superheterodyne or other type of radio receivers. It also has a selector for RX or TX mode of operation which is ideal for use in Homebrew QRP Transceivers. VFO also consist bargraph type S-meter. The signal for the S-meter is fed to the A3 analog input of the Arduino. This input has adjustable sensitivity, the gain must be adjusted in Sketch, accepting signals from 500mV to 5V (max).
A more detailed description of the method of operation can be found on the author's page. And now let's do a short test to see if the output signal corresponds to the value presented on the display. For this purpose I will use an oscilloscope. As can be seen, at lower frequencies the signal is rectangular, and with increasing the generated frequency, it gradually turns into a sinusoidal one as a result of the slow transition from low to high level and vice versa.
However, this is not a problem at all, at least in radio engineering where I most often plan to use this device. In fact, I plan for one of my next projects to be a simple Direct Conversion receiver with a VFO presented in this video.
And finally, a short conclusion. This is a cheap and easy-to-build VFO device that is almost indispensable in radio engineering, especially in DIY radio receivers. Credits to the creator of the original project, CesarSound.
#include <Wire.h> #include <Rotary.h> #include <si5351.h> #include <U8g2lib.h> // Pin definitions #define PIN_TUNESTEP A0 #define PIN_BAND A1 #define PIN_RX_TX A2 #define PIN_ADC A3 #define PIN_ROT_1 2 #define PIN_ROT_2 3 #define PIN_RST 8 #define PIN_CS 10 #define PIN_MOSI 11 #define PIN_SCK 13 // Constants #define IF_FREQ 455 #define BAND_INIT 7 #define XT_CAL_F 33000 #define S_GAIN 303 // Frequency range limits const uint32_t MIN_FREQ = 10000UL; // 10 kHz const uint32_t MAX_FREQ = 225000000UL; // 225 MHz // Band names stored in program memory const char BAND_0[] PROGMEM = " GEN"; const char BAND_1[] PROGMEM = " MW"; const char BAND_2[] PROGMEM = " 160m"; const char BAND_3[] PROGMEM = " 80m"; const char BAND_4[] PROGMEM = " 60m"; const char BAND_5[] PROGMEM = " 49m"; const char BAND_6[] PROGMEM = " 40m"; const char BAND_7[] PROGMEM = " 31m"; const char BAND_8[] PROGMEM = " 25m"; const char BAND_9[] PROGMEM = " 22m"; const char BAND_10[] PROGMEM = " 20m"; const char BAND_11[] PROGMEM = " 19m"; const char BAND_12[] PROGMEM = " 16m"; const char BAND_13[] PROGMEM = " 13m"; const char BAND_14[] PROGMEM = " 11m"; const char BAND_15[] PROGMEM = " 10m"; const char BAND_16[] PROGMEM = " 6m"; const char BAND_17[] PROGMEM = " WFM"; const char BAND_18[] PROGMEM = " AIR"; const char BAND_19[] PROGMEM = " 2m"; const char BAND_20[] PROGMEM = " 1m"; const char* const BAND_NAMES[] PROGMEM = { BAND_0, BAND_1, BAND_2, BAND_3, BAND_4, BAND_5, BAND_6, BAND_7, BAND_8, BAND_9, BAND_10, BAND_11, BAND_12, BAND_13, BAND_14, BAND_15, BAND_16, BAND_17, BAND_18, BAND_19, BAND_20 }; // Frequency presets stored in program memory const uint32_t FREQ_PRESETS[] PROGMEM = { 100000UL, // GEN 800000UL, // MW 1800000UL, // 160m 3650000UL, // 80m 4985000UL, // 60m 6180000UL, // 49m 7200000UL, // 40m 10000000UL, // 31m 11780000UL, // 25m 13630000UL, // 22m 14100000UL, // 20m 15000000UL, // 19m 17655000UL, // 16m 21525000UL, // 13m 27015000UL, // 11m 28400000UL, // 10m 50000000UL, // 6m 100000000UL, // WFM 130000000UL, // AIR 144000000UL, // 2m 220000000UL // 1m }; // Frequency steps const uint32_t FREQ_STEPS[] PROGMEM = { 1000000UL, // 1 MHz 1UL, // 1 Hz 10UL, // 10 Hz 1000UL, // 1 kHz 5000UL, // 5 kHz 10000UL // 10 kHz }; // Object initialization U8G2_ST7920_128X64_1_SW_SPI u8g2(U8G2_R0, PIN_SCK, PIN_MOSI, PIN_CS, PIN_RST); Rotary r = Rotary(PIN_ROT_1, PIN_ROT_2); Si5351 si5351; // Global variables uint32_t freq = 7200000UL; // Start at 7.2MHz uint32_t freqold; uint32_t fstep = 1000; // Default step 1kHz int16_t interfreq = IF_FREQ; int16_t cal = XT_CAL_F; uint8_t smval; uint8_t encoder = 1; uint8_t stp = 4; uint8_t n = 1; uint8_t count = BAND_INIT; uint8_t prevCount = BAND_INIT; uint8_t x, xo; bool sts = 0; bool displayOK = false; // Function prototypes bool setSi5351Frequency(Si5351& si5351, uint32_t freq, int16_t interfreq); void check_inputs(); void update_display_paged(); void initializeSi5351(); // Encoder interrupt service routine ISR(PCINT2_vect) { char result = r.process(); if (result == DIR_CW) { if (encoder == 1) { uint32_t new_freq = freq + fstep; if (new_freq <= MAX_FREQ) { freq = new_freq; n = (n >= 42) ? 1 : n + 1; } } } else if (result == DIR_CCW) { if (encoder == 1) { uint32_t new_freq = freq; if (freq >= fstep) { new_freq = freq - fstep; if (new_freq >= MIN_FREQ) { freq = new_freq; n = (n <= 1) ? 42 : n - 1; } } } } } void setup() { Serial.begin(9600); Serial.println(F("VFO Starting...")); Wire.begin(); if (!u8g2.begin()) { Serial.println(F("Display init failed!")); while (1) { delay(1000); } } // Display initialization test u8g2.setFont(u8g2_font_6x12_tr); u8g2.firstPage(); do { u8g2.drawFrame(0, 0, 128, 64); u8g2.drawStr(20, 32, "Initializing..."); } while (u8g2.nextPage()); delay(1000); Serial.println(F("Display initialized")); displayOK = true; // Initialize pins pinMode(PIN_ROT_1, INPUT_PULLUP); pinMode(PIN_ROT_2, INPUT_PULLUP); pinMode(PIN_TUNESTEP, INPUT_PULLUP); pinMode(PIN_BAND, INPUT_PULLUP); pinMode(PIN_RX_TX, INPUT_PULLUP); // Initialize Si5351 initializeSi5351(); // Setup rotary encoder interrupts PCICR |= (1 << PCIE2); PCMSK2 |= (1 << PCINT18) | (1 << PCINT19); sei(); // Set initial frequency freq = pgm_read_dword(&FREQ_PRESETS[count - 1]); Serial.println(F("Setup complete")); } void initializeSi5351() { Serial.println(F("Initializing Si5351...")); if (!si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0, 0)) { Serial.println(F("Si5351 init failed!")); } si5351.reset(); delay(10); si5351.set_correction(cal, SI5351_PLL_INPUT_XO); si5351.drive_strength(SI5351_CLK0, SI5351_DRIVE_8MA); si5351.output_enable(SI5351_CLK0, 1); } bool setSi5351Frequency(Si5351& si5351, uint32_t freq, int16_t interfreq) { // Check if frequency is within valid range if (freq < MIN_FREQ || freq > MAX_FREQ) { return false; } uint64_t output_freq = (freq + (interfreq * 1000ULL)) * 100ULL; // Handle GEN mode specially if (count == 1) { si5351.reset(); delay(10); si5351.set_correction(cal, SI5351_PLL_INPUT_XO); si5351.drive_strength(SI5351_CLK0, SI5351_DRIVE_8MA); } // Set the frequency si5351.set_freq(output_freq, SI5351_CLK0); si5351.output_enable(SI5351_CLK0, 1); return true; } void loop() { if (!displayOK) return; // Process frequency changes with error handling if (freqold != freq) { if (!setSi5351Frequency(si5351, freq, interfreq)) { // If frequency setting fails, try to recover si5351.reset(); delay(10); si5351.set_correction(cal, SI5351_PLL_INPUT_XO); si5351.drive_strength(SI5351_CLK0, SI5351_DRIVE_8MA); setSi5351Frequency(si5351, freq, interfreq); } freqold = freq; } // Check inputs check_inputs(); // Update display update_display_paged(); // Read signal meter smval = analogRead(PIN_ADC); x = constrain(map(smval, 0, S_GAIN, 1, 14), 1, 14); } void check_inputs() { if (digitalRead(PIN_TUNESTEP) == LOW) { stp = (stp % 6) + 1; fstep = pgm_read_dword(&FREQ_STEPS[stp - 1]); delay(300); } if (digitalRead(PIN_BAND) == LOW) { uint8_t newCount = (count % 21) + 1; // Reset Si5351 when entering or leaving GEN mode if (newCount == 1 || count == 1) { si5351.reset(); delay(10); si5351.set_correction(cal, SI5351_PLL_INPUT_XO); si5351.drive_strength(SI5351_CLK0, SI5351_DRIVE_8MA); si5351.output_enable(SI5351_CLK0, 1); } count = newCount; freq = pgm_read_dword(&FREQ_PRESETS[count - 1]); prevCount = count; delay(300); } sts = (digitalRead(PIN_RX_TX) == LOW); interfreq = (sts || count == 1) ? 0 : IF_FREQ; } void update_display_paged() { u8g2.firstPage(); do { // Display frequency char buffer[16]; uint32_t m = freq / 1000000UL; uint32_t k = (freq % 1000000UL) / 1000UL; uint32_t h = (freq % 1000UL); u8g2.setFont(u8g2_font_10x20_tr); if (m < 1) { sprintf(buffer, "%03lu.%03lu", k, h); u8g2.drawStr(41, 17, buffer); } else if (m < 100) { sprintf(buffer, "%lu.%03lu.%03lu", m, k, h); u8g2.drawStr(15, 17, buffer); } else { sprintf(buffer, "%lu.%03lu.%03lu", m, k, h); u8g2.drawStr(15, 17, buffer); } // Draw interface elements u8g2.setFont(u8g2_font_6x12_tr); u8g2.drawHLine(0, 22, 128); u8g2.drawHLine(0, 45, 128); u8g2.drawHLine(15, 54, 67); u8g2.drawVLine(105, 26, 15); u8g2.drawVLine(87, 26, 15); u8g2.drawVLine(87, 50, 15); // Display RX/TX status u8g2.drawStr(91, 37, sts ? "TX" : "RX"); // Display IF frequency sprintf(buffer, "IF:%d", interfreq); u8g2.drawStr(90, 59, buffer); // Display LO value sprintf(buffer, "LO:%d", interfreq); u8g2.drawStr(110, 38, buffer); // Display step u8g2.drawStr(54, 32, "STEP"); switch(stp) { case 1: u8g2.drawStr(54, 42, "1MHz"); break; case 2: u8g2.drawStr(54, 42, "1Hz"); break; case 3: u8g2.drawStr(54, 42, "10Hz"); break; case 4: u8g2.drawStr(54, 42, "1kHz"); break; case 5: u8g2.drawStr(54, 42, "5kHz"); break; case 6: u8g2.drawStr(54, 42, "10kHz"); break; } // Display band name u8g2.setFont(u8g2_font_10x20_tr); strcpy_P(buffer, (char*)pgm_read_word(&(BAND_NAMES[count - 1]))); u8g2.drawStr(0, 40, buffer); // Draw meters u8g2.setFont(u8g2_font_6x12_tr); byte y = map(n, 1, 42, 1, 14); u8g2.drawStr(0, 54, "TU"); u8g2.drawBox(15 + (y-1)*5, 47, 2, 6); u8g2.drawStr(0, 63, "SM"); for (byte i = 1; i <= x; i++) { u8g2.drawBox(15 + (i-1)*5, 57, 2, 6); } } while (u8g2.nextPage()); }

Arduino VFO Project with a Large LCD Display

Raspberry Pi 5 7 Inch Touch Screen IPS 1024x600 HD LCD HDMI-compatible Display for RPI 4B 3B+ OPI 5 AIDA64 PC Secondary Screen(Without Speaker)
BUY NOW
ESP32-S3 4.3inch Capacitive Touch Display Development Board, 800×480, 5-point Touch, 32-bit LX7 Dual-core Processor
BUY NOW
Raspberry Pi 5 7 Inch Touch Screen IPS 1024x600 HD LCD HDMI-compatible Display for RPI 4B 3B+ OPI 5 AIDA64 PC Secondary Screen(Without Speaker)
BUY NOW- Comments(3)
- Likes(0)

- Mirko PavleskiFeb 19,20250 CommentsReply
- Mirko PavleskiFeb 19,20250 CommentsReply
- AndreFeb 18,20250 CommentsReply
- 0 USER VOTES
- YOUR VOTE 0.00 0.00
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
More by Mirko Pavleski
-
Exploring the Tesla Coil Driver Board, Full Review & Test Results Some time ago I presented you a video in which I analyzed a super cheap Tesla Coil driver that cost...
-
Arduino Eatrthquake alarm and protection system with D7S seismic Sensor Earthquakes are extremely common events around the world. On average, there are fifty earthquakes a...
-
Review and Comparison of Three Inexpensive Metal Detector Kits A metal detector is a device used to detect the presence of metal objects in the ground or other ma...
-
How to make simple Arduino RGB Led strip VU Meter VU meter or volume unit meter is a device intended for visual presentation of the audio signal. It ...
-
DIY Simple Antistress and Relaxation PEMF Device based on Schumannn resonance frequency 7.83 Hz Schumann resonances are global electromagnetic resonances, generated by lightning discharges in the...
-
DIY Si4825 A10 multiband Radio (MW,SW,FM) Thanks to the production of specialized radio chips, nowadays it is possible to make a quality mult...
-
Arduino 3D Printed self Balancing Cube Self-balancing devices are electronic devices that use sensors and motors to keep themselves balanc...
-
Ultra cheap Ultrasonic levitation Device - functionality and testing Ultrasonic levitation is phenomenon where objects are suspended in mid-air using the power of sound ...
-
DIY -Spirit PI- ESP32 + Smartphone Sensitive Metal Detector Pulse Induction (PI) metal detector operates on a principle based on sending short pulses of electr...
-
ESP32 Analog style VU meter with GC9A01 Round Dispalys + Peak Meters A typical VU meter measures audio signals and displays them with a visual indicator. In the classic...
-
Arduino two weel self Balancing Robot Self Balancing Robot is device that can balance itself from falling to the ground. Its function is ...
-
ELECROW CrowPanel ESP32 4.2” E-paper Wi-Fi Info-Dispaly Project An e-paper display (also known as an electronic paper display or E Ink display) is a type of screen...
-
ESP32 Fluid simulation on 16x16 Led Matrix Fluid simulation is a way of replicating the movement and behavior of liquids and gases in differen...
-
Simple GU50 VTTC Tesla Coil with MOT (25+cm Spark) Vacuum Tube Tesla Coils are a common choice for homebuilders for several practical reasons. At Soli...
-
Hourglass ESP8266 Code A hourglass, also known as an sand clock, is a device used to measure the passage of time. It consi...
-
Tug of War Arduino Game on WS2812 Led strip A Tug of War is a classic team-based game where two opposing teams compete to pull a rope in opposi...
-
DIY ESP32 Bioresonance Rife Machine with ZAPPER function Rife machine therapy is an alternative treatment developed by Dr. Royal Raymond Rife in the 1930s. H...
-
Arduino VFO Project with a Large LCD Display A Variable Frequency Oscillator (VFO) is an electronic oscillator whose output frequency can be adj...
-
Exploring the Tesla Coil Driver Board, Full Review & Test Results Some time ago I presented you a video in which I analyzed a super cheap Tesla Coil driver that cost...
-
Arduino Eatrthquake alarm and protection system with D7S seismic Sensor Earthquakes are extremely common events around the world. On average, there are fifty earthquakes a...
-
Review and Comparison of Three Inexpensive Metal Detector Kits A metal detector is a device used to detect the presence of metal objects in the ground or other ma...
-
How to make simple Arduino RGB Led strip VU Meter VU meter or volume unit meter is a device intended for visual presentation of the audio signal. It ...
-
DIY Simple Antistress and Relaxation PEMF Device based on Schumannn resonance frequency 7.83 Hz Schumann resonances are global electromagnetic resonances, generated by lightning discharges in the...
-
DIY Si4825 A10 multiband Radio (MW,SW,FM) Thanks to the production of specialized radio chips, nowadays it is possible to make a quality mult...
-
Arduino 3D Printed self Balancing Cube Self-balancing devices are electronic devices that use sensors and motors to keep themselves balanc...
-
Ultra cheap Ultrasonic levitation Device - functionality and testing Ultrasonic levitation is phenomenon where objects are suspended in mid-air using the power of sound ...
-
DIY -Spirit PI- ESP32 + Smartphone Sensitive Metal Detector Pulse Induction (PI) metal detector operates on a principle based on sending short pulses of electr...
-
ESP32 Analog style VU meter with GC9A01 Round Dispalys + Peak Meters A typical VU meter measures audio signals and displays them with a visual indicator. In the classic...
-
Arduino two weel self Balancing Robot Self Balancing Robot is device that can balance itself from falling to the ground. Its function is ...
-
ELECROW CrowPanel ESP32 4.2” E-paper Wi-Fi Info-Dispaly Project An e-paper display (also known as an electronic paper display or E Ink display) is a type of screen...
-
-
Modifying a Hotplate to a Reflow Solder Station
513 0 4 -
MPL3115A2 Barometric Pressure, Altitude, and Temperature Sensor
301 0 1 -
-
Nintendo 64DD Replacement Shell
293 0 1 -
V2 Commodore AMIGA USB-C Power Sink Delivery High Efficiency Supply Triple Output 5V ±12V OLED display ATARI compatible shark 100W
756 4 2 -
How to measure weight with Load Cell and HX711
550 0 3 -