DIY Arduino VFO with AD9850 & TM1638 Pure Sine Wave Signal Generator for HF Radio
The AD9850-based VFO provides a clean, sine-wave alternative to square-wave oscillators, ensuring your HF receiver remains free from unwanted harmonic interference.
Devices & Components
1
Arduino Nano
1
TM1638 module
1
AD9850 Module
1
rotary encoder
Hardware & Tools
1
Soldering Iron Kit
Software & Tools
Arduino IDE
Project description
Code
Code
cpp
...
1// by mircemk Mar.2026 2 3#include <TM1638lite.h> 4 5TM1638lite module(4, 3, 2); 6 7#define W_CLK 8 8#define FQ_UD 9 9#define DATA 10 10#define RESET 11 11#define ENC_A A0 12#define ENC_B A1 13#define ENC_SW A2 // Копчето на енкодерот 14 15unsigned long freq = 7000000; 16unsigned long steps[] = {10, 100, 1000, 10000, 100000, 1000000}; 17int stepIndex = 2; 18const char* modes[] = {"A ", "USB ", "LSB "}; 19int modeIndex = 0; 20long offset = 0; 21bool offsetActive = false; 22 23// Brightness контроли 24int brightnessPercent = 50; // Почетна на 50% 25bool brightnessMode = false; 26 27unsigned long tempDisplayTimer = 0; 28bool showingTemp = false; 29 30const byte segmentMap[] = { 0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F }; 31 32void setup() { 33 pinMode(W_CLK, OUTPUT); pinMode(FQ_UD, OUTPUT); 34 pinMode(DATA, OUTPUT); pinMode(RESET, OUTPUT); 35 pinMode(ENC_A, INPUT_PULLUP); 36 pinMode(ENC_B, INPUT_PULLUP); 37 pinMode(ENC_SW, INPUT_PULLUP); 38 39 ad9850_reset(); 40 updateDDS(freq + offset); 41 setBrightnessLevel(brightnessPercent); 42 updateMainDisplay(); 43} 44 45void loop() { 46 // Читање на копчето на енкодерот (A3) 47 if (digitalRead(ENC_SW) == LOW) { 48 brightnessMode = !brightnessMode; 49 if (brightnessMode) showTempMsg("BRIGHT "); 50 else updateMainDisplay(); 51 delay(300); // Debounce 52 } 53 54 uint8_t buttons = module.readButtons(); 55 56 // Додека сме во Brightness Mode, другите тастери се деактивирани 57 if (!brightnessMode) { 58 if (buttons == 1) { 59 stepIndex = (stepIndex + 1) % 6; 60 char buffer[9]; 61 sprintf(buffer, "S%7ld", steps[stepIndex]); 62 showTempMsg(buffer); 63 while(module.readButtons() == 1); 64 } 65 if (buttons == 2) { 66 modeIndex = (modeIndex + 1) % 3; 67 showTempMsg(modes[modeIndex]); 68 while(module.readButtons() == 2); 69 } 70 if (buttons == 4) setBand(1800000, 2); 71 if (buttons == 8) setBand(3500000, 3); 72 if (buttons == 16) setBand(7000000, 4); 73 if (buttons == 32) setBand(14000000, 5); 74 if (buttons == 64) setBand(21000000, 6); 75 76 if (buttons == 128) { 77 offsetActive = !offsetActive; 78 offset = offsetActive ? 455000 : 0; 79 showTempMsg(offsetActive ? "_455_ " : "OFF "); 80 module.setLED(7, offsetActive ? 1 : 0); 81 updateDDS(freq + offset); 82 while(module.readButtons() == 128); 83 } 84 } 85 86 // Енкодер логика (за Фреквенција или за Brightness) 87 static int lastA = HIGH; 88 int currentA = digitalRead(ENC_A); 89 if (currentA != lastA && currentA == LOW) { 90 if (brightnessMode) { 91 // Менување на осветленост (чекор по 10%) 92 if (digitalRead(ENC_B) == LOW) brightnessPercent -= 10; 93 else brightnessPercent += 10; 94 brightnessPercent = constrain(brightnessPercent, 0, 100); 95 96 setBrightnessLevel(brightnessPercent); 97 char brBuf[9]; 98 sprintf(brBuf, "BR %3d ", brightnessPercent); 99 module.displayText(brBuf); 100 } else { 101 // Стандардна фреквенција 102 if (digitalRead(ENC_B) == LOW) freq -= steps[stepIndex]; 103 else freq += steps[stepIndex]; 104 freq = constrain(freq, 0, 49999999); 105 updateDDS(freq + offset); 106 if (!showingTemp) updateMainDisplay(); 107 } 108 } 109 lastA = currentA; 110 111 if (showingTemp && (millis() > tempDisplayTimer) && !brightnessMode) { 112 showingTemp = false; 113 updateMainDisplay(); 114 } 115} 116 117void showTempMsg(const char* msg) { 118 module.displayText(msg); 119 tempDisplayTimer = millis() + 1000; 120 showingTemp = true; 121} 122 123void setBrightnessLevel(int percent) { 124 // TM1638 користи 0x88 (минимум) до 0x8F (максимум) за осветленост 125 byte level = map(percent, 0, 100, 0, 7); 126 digitalWrite(4, LOW); // STB 127 shiftOut(2, 3, LSBFIRST, 0x88 + level); 128 digitalWrite(4, HIGH); // STB 129} 130 131void updateMainDisplay() { 132 unsigned long tempFreq = freq; 133 for (int i = 7; i >= 0; i--) { 134 byte segs = 0; 135 if (tempFreq > 0 || i == 7) { 136 segs = segmentMap[tempFreq % 10]; 137 tempFreq /= 10; 138 } else { 139 segs = 0x00; 140 } 141 if (i == 1 || i == 4) segs |= 0x80; 142 sendRawData(i, segs); 143 } 144} 145 146void sendRawData(byte position, byte data) { 147 digitalWrite(4, LOW); 148 shiftOut(2, 3, LSBFIRST, 0xC0 + (position * 2)); 149 shiftOut(2, 3, LSBFIRST, data); 150 digitalWrite(4, HIGH); 151} 152 153void setBand(unsigned long f, int ledIndex) { 154 freq = f; 155 for (int i = 0; i < 7; i++) module.setLED(i, 0); 156 module.setLED(ledIndex, 1); 157 updateDDS(freq + offset); 158 updateMainDisplay(); 159} 160 161void ad9850_reset() { 162 digitalWrite(W_CLK, LOW); digitalWrite(FQ_UD, LOW); 163 digitalWrite(RESET, LOW); digitalWrite(RESET, HIGH); digitalWrite(RESET, LOW); 164} 165 166void updateDDS(unsigned long f) { 167 unsigned long tuning_word = f * 4294967296ULL / 125000000; 168 for (int i = 0; i < 32; i++) { 169 digitalWrite(DATA, tuning_word & 0x01); 170 digitalWrite(W_CLK, HIGH); digitalWrite(W_CLK, LOW); 171 tuning_word >>= 1; 172 } 173 for (int i = 0; i < 8; i++) { 174 digitalWrite(DATA, LOW); 175 digitalWrite(W_CLK, HIGH); digitalWrite(W_CLK, LOW); 176 } 177 digitalWrite(FQ_UD, HIGH); digitalWrite(FQ_UD, LOW); 178}
Documentation
Schematic
...
Schematic.jpg

Comments
Only logged in users can leave comments