VFO/RF generator for use in homebrew radio equipment such as direct conversion and superheterodyne receivers or amateur transmitters.
Devices and components
Arduino-Nano
Rocker switch, rocker
Breadboard (generic)
10nF capacitor
SSD1306 128X64 OLED SCREEN
SI5351 CLOCK GENERATION MODULE
100nF capacitor
10 µF capacitor
RCA JACK FOR RF OUTPUT CONNECTION
Rotary encoder with push button
Software and tools
Arduino IDE
Project description
10 kHz to 120 MHz VFO/RF generator with Si5351 and Arduino.
Features:
Operating range from 10 kHz to 120 MHz.
Step adjustments of 1 Hz, 10 Hz, 1 kHz, 5 kHz, 10 kHz and 1 MHz.
Adjustable intermediate frequency (IF) offset (+ or -) (see note below).
For use as a local oscillator on homebrew superheterodyne or direct conversion radio receivers.
For use as a VFO for HAM radio transmitters.
Use as a simple RF/clock generator for calibration reference or clock generation.
Works with Arduino Uno, Nano and Pro Mini.
Uses common 128x64 I2C OLED SSD1306 display and Si5351 module.
I2C data transfer, only 2 wires to connect display/Si5351 and arduino.
High stability and precision for frequency generation.
Simple but very effective and free.
Display Pictures:
The VFO in action:
Schematics/wiring:
Instructions:
Open the sketch on Arduino IDE, install all required libraries.
Choose the preferences (see note) and compile the sketch, then upload it to the Arduino Nano, Uno or Pro Mini.
Follow the diagrams to wire the Arduino, display, Si5351 module, rotary encoder, etc.
Power on the Arduino.
Rotate the rotary encoder to increase or decrease the frequency.
Press the encoder button to change the frequency step setting. The available steps are 1 Hz, 10 Hz, 1 kHz, 5 kHz, 10 kHz and 1 MHz.
Note on user preferences:
Sketch SI5351_VFO_RF_GEN_OLED_JCR
VFO work. Load it onto the Arduino using the IDE.
1/********************************************************************************************************
2 10kHz to 120MHz VFO / RF Generator with Si5351 and Arduino Nano, with Intermediate Frequency (IF)
3 offset (+ or -). See the schematics for wiring details. By J. CesarSound - ver 1.0 - Dec/2020.
4*********************************************************************************************************/
5
6//Libraries
7#include <Wire.h> //IDE Standard
8#include <Rotary.h> //Ben Buxton https://github.com/brianlow/Rotary
9#include <si5351.h> //Etherkit https://github.com/etherkit/Si5351Arduino
10#include <Adafruit_GFX.h> //Adafruit GFX https://github.com/adafruit/Adafruit-GFX-Library
11#include <Adafruit_SSD1306.h> //Adafruit SSD1306 https://github.com/adafruit/Adafruit_SSD1306
12
13//User preferences
14//------------------------------------------------------------------------------------------------------------
15#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.
16#define FREQ_INIT 7000000 //Enter your initial frequency at startup, ex: 7000000 = 7MHz, 10000000 = 10MHz, 840000 = 840kHz.
17#define XT_CAL_F 33000 //Si5351 calibration factor, adjust to get exatcly 10MHz. Increasing this value will decreases the frequency and vice versa.
18#define tunestep A0 //Change the pin used by encoder push button if you want.
19//------------------------------------------------------------------------------------------------------------
20
21Rotary r = Rotary(2, 3);
22Adafruit_SSD1306 display = Adafruit_SSD1306(128, 64, &Wire);
23Si5351 si5351;
24
25unsigned long freq = FREQ_INIT;
26unsigned long freqold, fstep;
27long interfreq = IF;
28long cal = XT_CAL_F;
29unsigned long long pll_freq = 90000000000ULL;
30byte encoder = 1;
31byte stp;
32unsigned int period = 100; //millis display active
33unsigned long time_now = 0; //millis display active
34
35ISR(PCINT2_vect) {
36 char result = r.process();
37 if (result == DIR_CW) set_frequency(1);
38 else if (result == DIR_CCW) set_frequency(-1);
39}
40
41void set_frequency(short dir) {
42 if (encoder == 1) { //Up/Down frequency
43 if (dir == 1) freq = freq + fstep;
44 if (freq >= 120000000) freq = 120000000;
45 if (dir == -1) freq = freq - fstep;
46 if (fstep == 1000000 && freq <= 1000000) freq = 1000000;
47 else if (freq < 10000) freq = 10000;
48 }
49}
50
51void setup() {
52 Wire.begin();
53 display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
54 display.clearDisplay();
55 display.setTextColor(WHITE);
56 display.display();
57
58 pinMode(2, INPUT_PULLUP);
59 pinMode(3, INPUT_PULLUP);
60 pinMode(tunestep, INPUT_PULLUP);
61
62 statup_text();
63
64 si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0, cal);
65 si5351.output_enable(SI5351_CLK0, 1); //1 - Enable / 0 - Disable CLK
66 si5351.output_enable(SI5351_CLK1, 0);
67 si5351.output_enable(SI5351_CLK2, 0);
68 si5351.drive_strength(SI5351_CLK0, SI5351_DRIVE_2MA); //Output current 2MA, 4MA, 6MA or 8MA
69
70 PCICR |= (1 << PCIE2);
71 PCMSK2 |= (1 << PCINT18) | (1 << PCINT19);
72 sei();
73
74 stp = 3;
75 setstep();
76 layout();
77 displayfreq();
78}
79
80void loop() {
81 if (freqold != freq) {
82 time_now = millis();
83 tunegen();
84 freqold = freq;
85 }
86
87 if (digitalRead(tunestep) == LOW) {
88 time_now = (millis() + 300);
89 setstep();
90 delay(300);
91 }
92
93 if ((time_now + period) > millis()) {
94 displayfreq();
95 layout();
96 }
97}
98
99void tunegen() {
100 si5351.set_freq_manual((freq + (interfreq * 1000ULL)) * 100ULL, pll_freq, SI5351_CLK0);
101}
102
103void displayfreq() {
104 unsigned int m = freq / 1000000;
105 unsigned int k = (freq % 1000000) / 1000;
106 unsigned int h = (freq % 1000) / 1;
107
108 display.clearDisplay();
109 display.setTextSize(2);
110
111 char buffer[15] = "";
112 if (m < 1) {
113 display.setCursor(41, 1); sprintf(buffer, "%003d.%003d", k, h);
114 }
115 else if (m < 100) {
116 display.setCursor(5, 1); sprintf(buffer, "%2d.%003d.%003d", m, k, h);
117 }
118 else if (m >= 100) {
119 unsigned int h = (freq % 1000) / 10;
120 display.setCursor(5, 1); sprintf(buffer, "%2d.%003d.%02d", m, k, h);
121 }
122 display.print(buffer);
123}
124
125void setstep() {
126 switch (stp) {
127 case 1:
128 stp = 2;
129 fstep = 1;
130 break;
131 case 2:
132 stp = 3;
133 fstep = 10;
134 break;
135 case 3:
136 stp = 4;
137 fstep = 1000;
138 break;
139 case 4:
140 stp = 5;
141 fstep = 5000;
142 break;
143 case 5:
144 stp = 6;
145 fstep = 10000;
146 break;
147 case 6:
148 stp = 1;
149 fstep = 1000000;
150 break;
151 }
152}
153
154void layout() {
155 display.setTextColor(WHITE);
156 display.drawLine(0, 20, 127, 20, WHITE);
157 display.drawLine(0, 43, 127, 43, WHITE);
158 display.drawLine(105, 24, 105, 39, WHITE);
159 display.setTextSize(2);
160 display.setCursor(2, 25);
161 display.print("TS:");
162 if (stp == 2) display.print("1Hz"); if (stp == 3) display.print("10Hz"); if (stp == 4) display.print("1k");
163 if (stp == 5) display.print("5k"); if (stp == 6) display.print("10k"); if (stp == 1) display.print("1M");
164 display.setCursor(2, 48);
165 display.print("IF:");
166 display.print(interfreq);
167 display.print("k");
168 display.setTextSize(1);
169 display.setCursor(110, 23);
170 if (freq < 1000000) display.print("kHz");
171 if (freq >= 1000000) display.print("MHz");
172 display.setCursor(110, 33);
173 if (interfreq == 0) display.print("VFO");
174 if (interfreq != 0) display.print("L O");
175 display.display();
176}
177
178void statup_text() {
179 display.setTextSize(1);
180 display.setCursor(4, 5);
181 display.print("Si5351");
182 display.setCursor(4, 20);
183 display.print("VFO / RF Generator");
184 display.setCursor(4, 35);
185 display.print("Version 1.0");
186 display.setCursor(4, 50);
187 display.print(">> JCR RADIO <<");
188 display.display();
189 delay(3000);
190 display.clearDisplay();
191}
Sketch SI5351_VFO_RF_GEN_OLED_JCR
VFO work. Load it onto the Arduino using the IDE.
1/********************************************************************************************************
2
3 10kHz to 120MHz VFO / RF Generator with Si5351 and Arduino Nano, with Intermediate
4 Frequency (IF)
5 offset (+ or -). See the schematics for wiring details. By J.
6 CesarSound - ver 1.0 - Dec/2020.
7*********************************************************************************************************/
8
9//Libraries
10#include
11 <Wire.h> //IDE Standard
12#include <Rotary.h> //Ben
13 Buxton https://github.com/brianlow/Rotary
14#include <si5351.h> //Etherkit
15 https://github.com/etherkit/Si5351Arduino
16#include <Adafruit_GFX.h> //Adafruit
17 GFX https://github.com/adafruit/Adafruit-GFX-Library
18#include <Adafruit_SSD1306.h>
19 //Adafruit SSD1306 https://github.com/adafruit/Adafruit_SSD1306
20
21//User
22 preferences
23//------------------------------------------------------------------------------------------------------------
24#define
25 IF 0 //Enter your IF frequency, ex: 455 = 455kHz, 10700 = 10.7MHz,
26 0 = to direct convert receiver or RF generator, + will add and - will subtract IF
27 offfset.
28#define FREQ_INIT 7000000 //Enter your initial frequency at startup,
29 ex: 7000000 = 7MHz, 10000000 = 10MHz, 840000 = 840kHz.
30#define XT_CAL_F 33000
31 //Si5351 calibration factor, adjust to get exatcly 10MHz. Increasing this value
32 will decreases the frequency and vice versa.
33#define tunestep A0 //Change
34 the pin used by encoder push button if you want.
35//------------------------------------------------------------------------------------------------------------
36
37Rotary
38 r = Rotary(2, 3);
39Adafruit_SSD1306 display = Adafruit_SSD1306(128, 64, &Wire);
40Si5351
41 si5351;
42
43unsigned long freq = FREQ_INIT;
44unsigned long freqold, fstep;
45long
46 interfreq = IF;
47long cal = XT_CAL_F;
48unsigned long long pll_freq = 90000000000ULL;
49byte
50 encoder = 1;
51byte stp;
52unsigned int period = 100; //millis display active
53unsigned
54 long time_now = 0; //millis display active
55
56ISR(PCINT2_vect) {
57 char
58 result = r.process();
59 if (result == DIR_CW) set_frequency(1);
60 else if
61 (result == DIR_CCW) set_frequency(-1);
62}
63
64void set_frequency(short dir)
65 {
66 if (encoder == 1) { //Up/Down frequency
67 if
68 (dir == 1) freq = freq + fstep;
69 if (freq >= 120000000) freq = 120000000;
70
71 if (dir == -1) freq = freq - fstep;
72 if (fstep == 1000000 && freq <= 1000000)
73 freq = 1000000;
74 else if (freq < 10000) freq = 10000;
75 }
76}
77
78void
79 setup() {
80 Wire.begin();
81 display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
82
83 display.clearDisplay();
84 display.setTextColor(WHITE);
85 display.display();
86
87
88 pinMode(2, INPUT_PULLUP);
89 pinMode(3, INPUT_PULLUP);
90 pinMode(tunestep,
91 INPUT_PULLUP);
92
93 statup_text();
94
95 si5351.init(SI5351_CRYSTAL_LOAD_8PF,
96 0, cal);
97 si5351.output_enable(SI5351_CLK0, 1); //1 - Enable
98 / 0 - Disable CLK
99 si5351.output_enable(SI5351_CLK1, 0);
100 si5351.output_enable(SI5351_CLK2,
101 0);
102 si5351.drive_strength(SI5351_CLK0, SI5351_DRIVE_2MA); //Output current
103 2MA, 4MA, 6MA or 8MA
104
105 PCICR |= (1 << PCIE2);
106 PCMSK2 |= (1 << PCINT18)
107 | (1 << PCINT19);
108 sei();
109
110 stp = 3;
111 setstep();
112 layout();
113
114 displayfreq();
115}
116
117void loop() {
118 if (freqold != freq) {
119 time_now
120 = millis();
121 tunegen();
122 freqold = freq;
123 }
124
125 if (digitalRead(tunestep)
126 == LOW) {
127 time_now = (millis() + 300);
128 setstep();
129 delay(300);
130
131 }
132
133 if ((time_now + period) > millis()) {
134 displayfreq();
135 layout();
136
137 }
138}
139
140void tunegen() {
141 si5351.set_freq_manual((freq + (interfreq
142 * 1000ULL)) * 100ULL, pll_freq, SI5351_CLK0);
143}
144
145void displayfreq() {
146
147 unsigned int m = freq / 1000000;
148 unsigned int k = (freq % 1000000) / 1000;
149
150 unsigned int h = (freq % 1000) / 1;
151
152 display.clearDisplay();
153 display.setTextSize(2);
154
155
156 char buffer[15] = "";
157 if (m < 1) {
158 display.setCursor(41, 1); sprintf(buffer,
159 "%003d.%003d", k, h);
160 }
161 else if (m < 100) {
162 display.setCursor(5,
163 1); sprintf(buffer, "%2d.%003d.%003d", m, k, h);
164 }
165 else if (m >= 100)
166 {
167 unsigned int h = (freq % 1000) / 10;
168 display.setCursor(5, 1); sprintf(buffer,
169 "%2d.%003d.%02d", m, k, h);
170 }
171 display.print(buffer);
172}
173
174void
175 setstep() {
176 switch (stp) {
177 case 1:
178 stp = 2;
179 fstep
180 = 1;
181 break;
182 case 2:
183 stp = 3;
184 fstep = 10;
185 break;
186
187 case 3:
188 stp = 4;
189 fstep = 1000;
190 break;
191 case
192 4:
193 stp = 5;
194 fstep = 5000;
195 break;
196 case 5:
197 stp
198 = 6;
199 fstep = 10000;
200 break;
201 case 6:
202 stp = 1;
203
204 fstep = 1000000;
205 break;
206 }
207}
208
209void layout() {
210 display.setTextColor(WHITE);
211
212 display.drawLine(0, 20, 127, 20, WHITE);
213 display.drawLine(0, 43, 127, 43,
214 WHITE);
215 display.drawLine(105, 24, 105, 39, WHITE);
216 display.setTextSize(2);
217
218 display.setCursor(2, 25);
219 display.print("TS:");
220 if (stp == 2) display.print("1Hz");
221 if (stp == 3) display.print("10Hz"); if (stp == 4) display.print("1k");
222
223 if (stp == 5) display.print("5k"); if (stp == 6) display.print("10k"); if
224 (stp == 1) display.print("1M");
225 display.setCursor(2, 48);
226 display.print("IF:");
227
228 display.print(interfreq);
229 display.print("k");
230 display.setTextSize(1);
231
232 display.setCursor(110, 23);
233 if (freq < 1000000) display.print("kHz");
234
235 if (freq >= 1000000) display.print("MHz");
236 display.setCursor(110, 33);
237
238 if (interfreq == 0) display.print("VFO");
239 if (interfreq != 0) display.print("L
240 O");
241 display.display();
242}
243
244void statup_text() {
245 display.setTextSize(1);
246
247 display.setCursor(4, 5);
248 display.print("Si5351");
249 display.setCursor(4,
250 20);
251 display.print("VFO / RF Generator");
252 display.setCursor(4, 35);
253
254 display.print("Version 1.0");
255 display.setCursor(4, 50);
256 display.print(">>
257 JCR RADIO <<");
258 display.display();
259 delay(3000);
260 display.clearDisplay();
261}
Downloadable files
Wiring diagram (diagrams)
To interconnect parts.
Wiring diagram (diagrams)
Note: Content and images are from: https://projecthub.arduino.cc/, with some modifications.
If you want it removed due to copyright reasons, please leave a comment. Thank you.
I want to share this article more widely so that everyone knows about Arduino and your project.