//**********************************************************************************************************************
// DDS VFO for Cobra 148 GTL AM LSB USB 480 channels 80 CB, VFO CLK0 10KHz to 160MHz AM LSB USB mode.
// Sketch modified 15/02/2026 by Waldir Cardoso. Blog: https://projetosetransceptores.blogspot.com/2026/02/dds-vfo-am-ssb-oled-si5351-cb-80-canais.html.
// My thanks to the author. DDS VFO Arduino CBradio. https://www.facebook.com/profile.php?id=61580524262120
//**********************************************************************************************************************
// Function.
// 1.VFO CLK0 sum of the IF frequency Cobra 148 GTL 34 Mhz AM LSB USB mode.
// 2.CLK1 FI 7.800 kHz output AM mode.
// 3.CLK2 enter the frequency independent of CLK0 for IF or BFO.
// 4.STEP 10,1MHz,100,10,1kHz,100,10Hz, non-regrouped STEP from 1kHz to 10Hz.
// 4.Bands 6, 480 channels 80 CB D1 = 26.965 MHz D80= 27.855 MHz AM LSB USB MODE.
// 5.VFO stable frequency from 10 KHz to 160 MHz.
// 6.Memory mode VFO step channels.
//****************************************************************************
// Library////////////////
#include <si5351_lite.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <RotaryEncoder.h>
#include <EEPROM.h>
#include <avr/pgmspace.h>
//****************************************************************************
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define DISPLAY_ADDR 0x3C
#define OLED_RESET -1
#define ENCODER_PIN_A 3 // Encoder pin A
#define ENCODER_PIN_B 2 // Encoder pin B
//*************************************************************************
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
Si5351 si5351; // Si5351 I2C Address 0x60
// Encoder controller /////////
RotaryEncoder encoder(3, 2); // Encoder pins 3, 2, band channel, VFO tuning.
////Definitions //////////
#define ENCODER_BUTTON 4
#define EEPROM_MANUAL_FREQ_ADDR 0
#define EEPROM_MANUAL_STEP_ADDR 4
//*****************************************************************************************************************************************
const int amSwitchPin = 7;
const int usbSwitchPin = 8;
const int lsbSwitchPin = 9;
char band = 'D';
int channel = 1;
bool manualMode = false;
float displayedManualFreq = 26.965f;
float manualStep = 0.001f;
//***************************************************************************************************************************************************************
const float frequencyBFO = 7.800f; // BFO CLK2 Enter the frequency for IF or BFO.
const float frequencyOffsetIF = 7.800f; // CLK1. Enter IF 7.800f; adds to display. Digite 7.800f; FI soma com display.
const float amOffset = -0.0000000f;
const float usbOffset = 0.0015000f; // ออฟเซ็ตสำหรับ USB (+1.5 kHz)
const float lsbOffset = -0.0015000f; // ออฟเซ็ตสำหรับ LSB (-1.5 kHz)
//***************************************************************************************************************************************
unsigned long buttonPressTime = 0;
bool buttonHeld = false;
int encoderTicks = 0;
const float frequencies[6][80] PROGMEM = {
//Dig 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
{24.555, 24.565, 24.575, 24.585, 24.595, 24.605, 24.615, 24.625, 24.635, 24.645, 24.655, 24.665, 24.675, 24.685, 24.695, 24.705, 24.715, 24.725, 24.735, 24.745, 24.755, 24.765, 24.775, 24.785, 24.795, 24.805, 24.815, 24.825, 24.835, 24.845, 24.855, 24.865, 24.875, 24.885, 24.895, 24.905, 24.915, 24.925, 24.935, 24.945, 24.955, 24.965, 24.975, 24.985, 24.995, 25.005, 25.015, 25.025, 25.035, 25.045, 25.055, 25.065, 25.075, 25.085, 25.095, 25.105, 25.115, 25.125, 25.135, 25.145, 25.155, 25.165, 25.175, 25.185, 25.195, 25.205, 25.215, 25.225, 25.235, 25.245, 25.255, 25.265, 25.275, 25.285, 25.295, 25.305, 25.315, 25.325, 25.335, 25.345}, //ok
{25.355, 25.365, 25.375, 25.385, 25.395, 25.405, 25.415, 25.425, 25.435, 25.445, 25.455, 25.465, 25.475, 25.485, 25.495, 25.505, 25.515, 25.525, 25.535, 25.545, 25.555, 25.565, 25.575, 25.585, 25.595, 25.605, 25.615, 25.625, 25.635, 25.645, 25.655, 25.665, 25.675, 25.685, 25.695, 25.705, 25.715, 25.725, 25.735, 25.745, 25.755, 25.765, 25.775, 25.785, 25.795, 25.805, 25.815, 25.825, 25.835, 25.845, 25.855, 25.865, 25.875, 25.885, 25.895, 25.905, 25.915, 25.925, 25.935, 25.945, 25.955, 25.965, 25.975, 25.985, 25.995, 26.005, 26.015, 26.025, 26.035, 26.045, 26.055, 26.065, 26.075, 26.085, 26.095, 26.105, 26.115, 26.125, 26.135, 26.145}, //ok
{26.155, 26.165, 26.175, 26.185, 26.195, 26.205, 26.215, 26.225, 26.235, 26.245, 26.255, 26.265, 26.275, 26.285, 26.295, 26.305, 26.315, 26.325, 26.335, 26.345, 26.355, 26.365, 26.375, 26.385, 26.395, 26.405, 26.415, 26.425, 26.435, 26.445, 26.455, 26.465, 26.475, 26.485, 26.495, 26.505, 26.515, 26.525, 26.535, 26.545, 26.555, 26.565, 26.575, 26.585, 26.595, 26.605, 26.615, 26.625, 26.635, 26.645, 26.655, 26.665, 26.675, 26.685, 26.695, 26.705, 26.715, 26.725, 26.735, 26.745, 26.755, 26.765, 26.775, 26.785, 26.795, 26.805, 26.815, 26.825, 26.835, 26.845, 26.855, 26.865, 26.875, 26.885, 26.895, 26.905, 26.915, 26.925, 26.935, 26.945}, //ok
{26.965, 26.975, 26.985, 27.005, 27.015, 27.025, 27.035, 27.055, 27.065, 27.075, 27.085, 27.105, 27.115, 27.125, 27.135, 27.155, 27.165, 27.175, 27.185, 27.205, 27.215, 27.225, 27.255, 27.235, 27.245, 27.265, 27.275, 27.285, 27.295, 27.305, 27.315, 27.325, 27.335, 27.345, 27.355, 27.365, 27.375, 27.385, 27.395, 27.405, 27.415, 27.425, 27.435, 27.455, 27.465, 27.475, 27.485, 27.505, 27.515, 27.525, 27.535, 27.555, 27.565, 27.575, 27.585, 27.605, 27.615, 27.625, 27.635, 27.655, 27.665, 27.675, 27.705, 27.685, 27.695, 27.715, 27.725, 27.735, 27.745, 27.755, 27.765, 27.775, 27.785, 27.795, 27.805, 27.815, 27.825, 27.835, 27.845, 27.855}, //ok
{27.865, 27.875, 27.885, 27.895, 27.905, 27.915, 27.925, 27.935, 27.945, 27.955, 27.965, 27.975, 27.985, 27.995, 28.005, 28.015, 28.025, 28.035, 28.045, 28.055, 28.065, 28.075, 28.085, 28.095, 28.105, 28.115, 28.125, 28.135, 28.145, 28.155, 28.165, 28.175, 28.185, 28.195, 28.205, 28.215, 28.225, 28.235, 28.245, 28.255, 28.265, 28.275, 28.285, 28.295, 28.305, 28.315, 28.325, 28.335, 28.345, 28.355, 28.365, 28.375, 28.385, 28.395, 28.405, 28.415, 28.425, 28.435, 28.445, 28.455, 28.465, 28.475, 28.485, 28.495, 28.505, 28.515, 28.525, 28.535, 28.545, 28.555, 28.565, 28.575, 28.585, 28.595, 28.605, 28.615, 28.625, 28.635, 28.645, 28.655}, //ok
{28.665, 28.675, 28.685, 28.695, 28.705, 28.715, 28.725, 28.735, 28.745, 28.755, 28.765, 28.775, 28.785, 28.795, 28.805, 28.815, 28.825, 28.835, 28.845, 28.855, 28.865, 28.875, 28.885, 28.895, 28.905, 28.915, 28.925, 28.935, 28.945, 28.955, 28.965, 28.975, 28.985, 28.995, 29.005, 29.015, 29.025, 29.035, 29.045, 29.055, 29.065, 29.075, 29.085, 29.095, 29.105, 29.115, 29.125, 29.135, 29.145, 29.155, 29.165, 29.175, 29.185, 29.195, 29.205, 29.215, 29.225, 29.235, 29.245, 29.255, 29.265, 29.275, 29.285, 29.295, 29.305, 29.315, 29.325, 29.335, 29.345, 29.355, 29.365, 29.375, 29.385, 29.395, 29.405, 29.415, 29.425, 29.435, 29.445, 29.455} //ok
};
char getMode() {
if (digitalRead(usbSwitchPin) == LOW) {
return 'U'; // USB
} else if (digitalRead(lsbSwitchPin) == LOW) {
return 'L'; // LSB
} else if (digitalRead(amSwitchPin) == LOW) {
return 'A'; // AM
} else {
}
}
float getDisplayedFreq() {
if (manualMode) return displayedManualFreq;
return pgm_read_float(&(frequencies[band - 'A'][channel - 1]));
}
float getSi5351Freq() {
float displayedFrequency = getDisplayedFreq();
char currentMode = getMode();
if (currentMode == 'U') { // USB Mode
return displayedFrequency + frequencyOffsetIF + usbOffset;
} else if (currentMode == 'L') { // LSB Mode
return displayedFrequency + frequencyOffsetIF + lsbOffset;
} else { // AM Mode หรือแบนด์อื่นๆ
return displayedFrequency + frequencyOffsetIF + amOffset;
}
}
// Update frequency. Atualização frequência. //////////
void updateFrequency() {
float freq = getSi5351Freq();
si5351.set_freq((uint64_t)(freq * 100000000UL), SI5351_CLK0); // VFO CLK0
si5351.set_freq((uint64_t)(frequencyOffsetIF * 1000ULL) * 100ULL, SI5351_CLK1); // IF CLK1
si5351.set_freq((uint64_t)(frequencyBFO * 1000ULL) * 100ULL, SI5351_CLK2); // BFO CLK2
si5351.drive_strength(SI5351_CLK0, SI5351_DRIVE_8MA); //Drive out lebel 8mA set. Saída nível 8mA.
si5351.drive_strength(SI5351_CLK1, SI5351_DRIVE_6MA); //Drive out lebel 6mA set. Saída nível 6mA.
si5351.drive_strength(SI5351_CLK2, SI5351_DRIVE_6MA); //Drive out lebel 6mA set. Saída nível 6mA.
delay(5);
}
// Update display. Atualização display. //////////////
void updateDisplay() {
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
if (manualMode) {
// VFO frequency font character size. Tamanho caracteres fonte frequencia VFO.
display.setTextSize(2); //Font character size, numbers, frequency, VFO. Tamanho números VFO.
display.setCursor(4, 23); //Position, numbers, frequency. Posição números frequência.
display.print(getDisplayedFreq(), 5); // Number of decimal places "0" Display VFO. Quantidade de casas decimais "0" Display VFO.
display.print(F(""));
display.setTextSize(2);
display.setCursor(3, 1); // Position VFO name. Posição nome VFO.
display.print(F("VFO "));
display.drawLine(0, 19, 127, 19, WHITE); // Dividing line above frequency. Linha divisória acima da frequência.
display.drawLine(0, 40, 127, 40, WHITE); // Dividing line below frequency. Linha divisória abaixo frequência.
display.drawLine(0, 20, 0, 40, WHITE); // Downward line, left side. Linha descida lado esquerdo.
display.drawLine(127, 20, 127, 40, WHITE); // Downhill line on the right side. Linha descida lado direito.
// AM, LSB, USB display.
display.setTextSize(2); // Text display font character size.
char currentMode = getMode();
if (currentMode == 'A') {
display.print(F("AM"));
} else if (currentMode == 'U') {
display.print(F("USB"));
} else if (currentMode == 'L') {
display.print(F("LSB"));
} else {
display.print(F(" "));
}
display.setTextSize(2); // Text display font size. Tamanho fonte letras.
display.setCursor(4, 45); // STEP VFO display position. Posição do nome STEP no display.
display.print(F("TS:"));
display.setTextSize(2); // Text display font size. Tamanho fonte letras.
display.setCursor(40, 45); // Position of the STEP display numbers. Posição dos números do STEP.
if (manualStep >= 1.0) {
display.print((int)manualStep);
display.print(F("MHz"));
} else if (manualStep >= 0.001) {
display.print((int)(manualStep * 1000));
display.print(F("KHz"));
} else {
display.print((int)(manualStep * 1000000));
display.print(F("Hz"));
}
} else {
//Character size A, B, C, D, E, F.
display.setTextSize(2);
display.setCursor(0, 2);
display.print(getDisplayedFreq(), 3); // Number of decimal places "0" Display Channels. Quantidade de casas decimais "0" Display Canais.
display.print(F(" "));
display.setTextSize(2);
char currentMode = getMode();
if (currentMode == 'A') {
display.print(F("AM"));
} else if (currentMode == 'U') {
display.print(F("USB"));
} else if (currentMode == 'L') {
display.print(F("LSB"));
} else {
display.print(F(" "));
}
display.setTextSize(4);
display.setCursor(30, 28);
display.print(band);
display.print(channel);
display.drawLine(0, 20, 127, 20, WHITE);
display.drawLine(0, 21, 127, 21, WHITE);
display.drawLine(0, 62, 127, 62, WHITE);
display.drawLine(0, 63, 127, 63, WHITE);
display.drawLine(126, 20, 126, 62, WHITE);
display.drawLine(127, 20, 127, 62, WHITE);
display.drawLine(0, 20, 0, 62, WHITE);
display.drawLine(1, 20, 1, 62, WHITE);
}
display.display();
}
////////EEPROM////////////
void saveManualFreq(float freq) {
EEPROM.put(EEPROM_MANUAL_FREQ_ADDR, freq);
}
float loadManualFreq() {
float freq;
EEPROM.get(EEPROM_MANUAL_FREQ_ADDR, freq);
if (isnan(freq) || freq < 0.01 || freq > 160.0) return 26.965; //27.555f;
return freq;
}
void saveManualStep(float step) {
EEPROM.put(EEPROM_MANUAL_STEP_ADDR, step);
}
float loadManualStep() {
float step;
EEPROM.get(EEPROM_MANUAL_STEP_ADDR, step);
if (step <= 0.0) return 0.0001f;
return step;
}
void setup() {
pinMode(ENCODER_BUTTON, INPUT_PULLUP);
pinMode(amSwitchPin, INPUT_PULLUP);
pinMode(usbSwitchPin, INPUT_PULLUP);
pinMode(lsbSwitchPin, INPUT_PULLUP);
Serial.begin(9600);
//Removed the Adafruit logo. Removi o logo Adafruit. 14/02/26.
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.clearDisplay();
display.setTextColor(WHITE);
display.display();
// Initial text Cobra 148 GTL or other equipment display.
display.setTextSize(2);
display.setTextColor(WHITE);
display.setCursor(10,10);
display.println("Cobra 148");
display.setCursor(45,40);
display.print("GTL");
display.display();
delay(3000);
display.clearDisplay();
//// initialize Si5351 /////////////
si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0, 0); // Keep this in place for correct frequency calibration.
si5351.drive_strength(SI5351_CLK0, SI5351_DRIVE_8MA);
si5351.drive_strength(SI5351_CLK1, SI5351_DRIVE_6MA);
si5351.drive_strength(SI5351_CLK2, SI5351_DRIVE_6MA);
updateFrequency();
display.display();
delay(10);
display.clearDisplay();
si5351.init( SI5351_CRYSTAL_LOAD_8PF, 25000450UL, 0); // Calibration of Si5351 crystal at 25.000 or 27.000 MHz.
si5351.drive_strength(SI5351_CLK0, SI5351_DRIVE_8MA);
si5351.drive_strength(SI5351_CLK1, SI5351_DRIVE_6MA);
si5351.drive_strength(SI5351_CLK2, SI5351_DRIVE_6MA);
displayedManualFreq = loadManualFreq();
manualStep = loadManualStep();
encoder.setPosition(0);
updateFrequency();
updateDisplay();
}
void loop() {
si5351.output_enable(SI5351_CLK0, 1); // Enable CLK0
si5351.output_enable(SI5351_CLK1, 1); // Enable CLK1
si5351.output_enable(SI5351_CLK2, 1); // Enable CLK1
encoder.tick();
int newPos = encoder.getPosition();
int diff = newPos - encoderTicks;
encoderTicks = newPos;
char currentMode = getMode();
static char lastMode = 'A';
if (currentMode != lastMode) {
lastMode = currentMode;
updateFrequency();
updateDisplay();
}
if (diff != 0) {
if (manualMode) {
displayedManualFreq += diff * manualStep;
displayedManualFreq = constrain(displayedManualFreq, 0.010f, 160.000f);
} else {
channel += diff;
if (channel > 80) channel = 1;
if (channel < 1) channel = 80;
}
updateFrequency();
updateDisplay();
}
static bool lastBtnState = HIGH;
bool btn = digitalRead(ENCODER_BUTTON);
if (btn == LOW && lastBtnState == HIGH) {
buttonPressTime = millis();
buttonHeld = false;
}
if (btn == LOW && (millis() - buttonPressTime > 1000) && !buttonHeld) {
manualMode = !manualMode;
buttonHeld = true;
encoder.setPosition(0);
encoderTicks = 0;
updateFrequency();
updateDisplay();
}
if (btn == HIGH && lastBtnState == LOW && !buttonHeld) {
if (manualMode) {
if (manualStep == 0.00001f) manualStep = 0.0001f;
else if (manualStep == 0.0001f) manualStep = 0.001f;
else if (manualStep == 0.001f) manualStep = 0.01f;
else if (manualStep == 0.01f) manualStep = 0.1f;
else if (manualStep == 0.1f) manualStep = 1.0f;
else if (manualStep == 1.0f) manualStep = 10.0f;
else manualStep = 0.00001f;
saveManualStep(manualStep);
updateDisplay();
} else {
band++;
if (band > 'F') band = 'A';
updateFrequency();
updateDisplay();
}
}
lastBtnState = btn;
}
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
Si5351 si5351;
RotaryEncoder encoder(3, 2);
const int amSwitchPin = 7;
const int usbSwitchPin = 8;
const int lsbSwitchPin = 9;
char band = 'D';
int channel = 12;
bool manualMode = false;
float displayedManualFreq = 27.555f;
float manualStep = 0.0001f;
const float frequencyOffset = 7.800f;
const float amOffset = -0.000000f;
const float usbOffset = 0.0015000f; // ออฟเซ็ตสำหรับ USB (+1.5 kHz)
const float lsbOffset = -0.0015000f; // ออฟเซ็ตสำหรับ LSB (-1.5 kHz)
unsigned long buttonPressTime = 0;
bool buttonHeld = false;
int encoderTicks = 0;
const float frequencies[6][40] PROGMEM = {
{26.065, 26.075, 26.085, 26.105, 26.115, 26.125, 26.135, 26.155, 26.165, 26.175, 26.185, 26.195, 26.215, 26.225, 26.235, 26.255, 26.265, 26.275, 26.285, 26.305, 26.315, 26.325, 26.355, 26.335, 26.345, 26.365, 26.375, 26.385, 26.395, 26.405, 26.415, 26.425, 26.435, 26.445, 26.455, 26.465, 26.475, 26.485, 26.495, 26.505},
{26.515, 26.525, 26.535, 26.555, 26.565, 26.575, 26.585, 26.605, 26.615, 26.625, 26.635, 26.655, 26.665, 26.675, 26.685, 26.705, 26.715, 26.725, 26.735, 26.755, 26.765, 26.775, 26.805, 26.785, 26.795, 26.815, 26.825, 26.835, 26.845, 26.855, 26.865, 26.875, 26.885, 26.895, 26.905, 26.915, 26.925, 26.935, 26.945, 26.955},
{26.965, 26.975, 26.985, 27.005, 27.015, 27.025, 27.035, 27.055, 27.065, 27.075, 27.085, 27.105, 27.115, 27.125, 27.135, 27.155, 27.165, 27.175, 27.185, 27.205, 27.215, 27.225, 27.255, 27.235, 27.245, 27.265, 27.275, 27.285, 27.295, 27.305, 27.315, 27.325, 27.335, 27.345, 27.355, 27.365, 27.375, 27.385, 27.395, 27.405},
{27.415, 27.425, 27.435, 27.455, 27.465, 27.475, 27.485, 27.505, 27.515, 27.525, 27.535, 27.555, 27.565, 27.575, 27.585, 27.605, 27.615, 27.625, 27.635, 27.655, 27.665, 27.675, 27.705, 27.685, 27.695, 27.715, 27.725, 27.735, 27.745, 27.755, 27.765, 27.775, 27.785, 27.795, 27.805, 27.815, 27.825, 27.835, 27.845, 27.855},
{27.865, 27.875, 27.885, 27.905, 27.915, 27.925, 27.935, 27.955, 27.965, 27.975, 27.985, 28.005, 28.015, 28.025, 28.035, 28.055, 28.065, 28.075, 28.085, 28.105, 28.115, 28.125, 28.155, 28.135, 28.145, 28.165, 28.175, 28.185, 28.195, 28.205, 28.215, 28.225, 28.235, 28.245, 28.255, 28.265, 28.275, 28.285, 28.295, 28.305},
{28.315, 28.325, 28.335, 28.355, 28.365, 28.375, 28.385, 28.405, 28.415, 28.425, 28.435, 28.455, 28.465, 28.475, 28.485, 28.505, 28.515, 28.525, 28.535, 28.555, 28.565, 28.575, 28.605, 28.585, 28.595, 28.615, 28.625, 28.635, 28.645, 28.655, 28.665, 28.675, 28.685, 28.695, 28.705, 28.715, 28.725, 28.735, 28.745, 28.755}
};
char getMode() {
if (digitalRead(usbSwitchPin) == LOW) {
return 'U'; // USB
} else if (digitalRead(lsbSwitchPin) == LOW) {
return 'L'; // LSB
} else if (digitalRead(amSwitchPin) == LOW) {
return 'A'; // AM
} else {
float getDisplayedFreq() {
if (manualMode) return displayedManualFreq;
return pgm_read_float(&(frequencies[band - 'A'][channel - 1]));
}
float getSi5351Freq() {
float displayedFrequency = getDisplayedFreq();
char currentMode = getMode();
if (currentMode == 'U') { // USB Mode
return displayedFrequency + frequencyOffset + usbOffset;
} else if (currentMode == 'L') { // LSB Mode
return displayedFrequency + frequencyOffset + lsbOffset;
} else { // AM Mode หรือแบนด์อื่นๆ
return displayedFrequency + frequencyOffset + amOffset;
}
}
void updateFrequency() {
float freq = getSi5351Freq();
si5351.set_freq((uint64_t)(freq * 100000000UL), SI5351_CLK0);
si5351.drive_strength(SI5351_CLK0, SI5351_DRIVE_8MA);
delay(5);
}
void updateDisplay() {
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
if (manualMode) {
display.setTextSize(3);
display.setCursor(0, 22);
display.print(getDisplayedFreq(), 4);
display.print(F(""));
display.setTextSize(2);
display.setCursor(0, 2);
display.print(F(" VFO "));
display.drawLine(0, 47, 127, 47, WHITE);
display.drawLine(0, 18, 127, 18, WHITE);
display.setTextSize(2);
char currentMode = getMode();
if (currentMode == 'A') {
display.print(F("AM"));
} else if (currentMode == 'U') {
display.print(F("USB"));
} else if (currentMode == 'L') {
display.print(F("LSB"));
} else {
display.print(F(" "));
}
display.setTextSize(1);
display.setCursor(2, 52);
display.print(F("Step:"));
display.setTextSize(1);
display.setCursor(80, 52);
if (manualStep >= 1.0) {
display.print((int)manualStep);
display.print(F(" MHz"));
} else if (manualStep >= 0.001) {
display.print((int)(manualStep * 1000));
display.print(F(" KHz"));
} else {
display.print((int)(manualStep * 1000000));
display.print(F(" Hz"));
}
} else {
display.setTextSize(2);
display.setCursor(0, 2);
display.print(getDisplayedFreq(), 3);
display.print(F(" "));
display.setTextSize(2);
char currentMode = getMode();
if (currentMode == 'A') {
display.print(F("AM"));
} else if (currentMode == 'U') {
display.print(F("USB"));
} else if (currentMode == 'L') {
display.print(F("LSB"));
} else {
display.print(F(" "));
}
display.setTextSize(4);
display.setCursor(30, 28);
display.print(band);
display.print(channel);
display.drawLine(0, 20, 127, 20, WHITE);
display.drawLine(0, 21, 127, 21, WHITE);
display.drawLine(0, 62, 127, 62, WHITE);
display.drawLine(0, 63, 127, 63, WHITE);
display.drawLine(126, 20, 126, 62, WHITE);
display.drawLine(127, 20, 127, 62, WHITE);
display.drawLine(0, 20, 0, 62, WHITE);
display.drawLine(1, 20, 1, 62, WHITE);
}
void saveManualFreq(float freq) {
EEPROM.put(EEPROM_MANUAL_FREQ_ADDR, freq);
}
float loadManualFreq() {
float freq;
EEPROM.get(EEPROM_MANUAL_FREQ_ADDR, freq);
if (isnan(freq) || freq < 3.0 || freq > 50.0) return 27.555f;
return freq;
}
void saveManualStep(float step) {
EEPROM.put(EEPROM_MANUAL_STEP_ADDR, step);
}
float loadManualStep() {
float step;
EEPROM.get(EEPROM_MANUAL_STEP_ADDR, step);
if (step <= 0.0) return 0.0001f;
return step;
}
void setup() {
pinMode(ENCODER_BUTTON, INPUT_PULLUP);
pinMode(amSwitchPin, INPUT_PULLUP);
pinMode(usbSwitchPin, INPUT_PULLUP);
pinMode(lsbSwitchPin, INPUT_PULLUP);
Serial.begin(9600);
if (!display.begin(SSD1306_SWITCHCAPVCC, DISPLAY_ADDR)) {
Serial.println(F("SSD1306 allocation failed"));
for (;;);
}
si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0, 0);
si5351.drive_strength(SI5351_CLK0, SI5351_DRIVE_8MA);
updateFrequency();
display.display();
delay(10);
display.clearDisplay();
si5351.init(SI5351_CRYSTAL_LOAD_8PF, 25000000UL, 0);
si5351.drive_strength(SI5351_CLK0, SI5351_DRIVE_8MA);
displayedManualFreq = loadManualFreq();
manualStep = loadManualStep();
encoder.setPosition(0);
updateFrequency();
updateDisplay();
}
void loop() {
encoder.tick();
int newPos = encoder.getPosition();
int diff = newPos - encoderTicks;
encoderTicks = newPos;
char currentMode = getMode();
static char lastMode = 'A';
if (currentMode != lastMode) {
lastMode = currentMode;
updateFrequency();
updateDisplay();
}
if (diff != 0) {
if (manualMode) {
displayedManualFreq += diff * manualStep;
displayedManualFreq = constrain(displayedManualFreq, 6.000f, 35.000f);
} else {
channel += diff;
if (channel > 40) channel = 1;
if (channel < 1) channel = 40;
}
updateFrequency();
updateDisplay();
}
static bool lastBtnState = HIGH;
bool btn = digitalRead(ENCODER_BUTTON);
if (btn == LOW && lastBtnState == HIGH) {
buttonPressTime = millis();
buttonHeld = false;
}
if (btn == LOW && (millis() - buttonPressTime > 1000) && !buttonHeld) {
manualMode = !manualMode;
buttonHeld = true;
encoder.setPosition(0);
encoderTicks = 0;
updateFrequency();
updateDisplay();
}
if (btn == HIGH && lastBtnState == LOW && !buttonHeld) {
if (manualMode) {
if (manualStep == 0.0001f) manualStep = 0.001f;
else if (manualStep == 0.001f) manualStep = 0.01f;
else if (manualStep == 0.01f) manualStep = 0.1f;
else if (manualStep == 0.1f) manualStep = 1.0f;
else if (manualStep == 1.0f) manualStep = 10.0f;
else manualStep = 0.0001f;
saveManualStep(manualStep);
updateDisplay();
} else {
band++;
if (band > 'F') band = 'A';
updateFrequency();
updateDisplay();
}
}