terça-feira, 31 de março de 2026

DDS VFO para Cobra 148 GTL modificado 2.

Olá pessoal, é para vocês seguidores e quem acompanham nosso blog, e vídeos. Estou na caça a bons esboços, sketches ou logaritmos para tentar fazer modificações, mas está difícil achar com configuração para AM, LSB, USB, e pior ainda para telas Oled de 128 x 64. Tentei fazer uma modificação em um sketch de LCD para Oled, conseguir mas não gostei, pois é muito pobre e não tem os modos AM, SSB. O melhor são os que tem os modos AM, LSB, USB, estes servirão para nossos projetos transformar qualquer transceptor comercial como o Cobra 148 GTL ou outros, e nossas montagens Naja, Ararinha, Minhoca, Andirá, Curió, Lagunero, Alba II entre muitos publicados no blog e na WEB. Escolha o que melhor achar para seu gosto. Já publiquei aqui no blog e YouTube muitos DDS VFOs modificados por mim, sempre focado para o Cobra 148 GTL e tela Oled 128 x 64. Este agora que publico, é uma atualização do já atualizado e publicado, porem atualizado mais uma vez, ficando diferente do original que publiquei 30/08/2025. O meu maior interesse, não é ficar famoso como modificador ou programador de projetos DDS VFO, meu interesse é incentivar a qualquer um a fazer o que eu faço, vejam que em um esboço eu já atualizei muito, e ainda tem mais para atualizar, é preciso que vocês tenham interesse em começar a desenvolver um ou outro esboço, eu não tenho nenhum curso de Arduino nem eletrônica, mas no entanto estou aqui, tentando fazer minha contribuição para nosso hobby. Sobre o DDS VFO em questão, é praticamente o mesmo esquema elétrico, com uma novidade no BFO em CLK1 podemos ver a frequência na parte inferior esquerdo do Oled, podemos alterar a frequência da FI para retoque de RX ou TX sem mudar a chave de modo AM, LSB, USB, caso mude de modo a FI retornará a escrita no esboço. Para acessar o ajustar a frequência do BFO FI em CKL1, pressione o Encoder por 5 seg. o ajuste de passos Step também é feito em CLK1, ajustando ou retocando a frequência em operação enquanto o DDS estiver ligado ou permanecer no mesmo modo.
Este projeto DDS VFO não chega aos 120 MHz, vai aos 118. MHz estável, melhor usar até os 70 ou 80 MHz, Eu deixei no esboço sketch para os 60 Mhz, mas ele está indo até 118 MHz, não sei o motivo que não está obedecendo as definições de frequências mínima e máxima. Mas se for para os 100 MHz o último dígito da frequência passara para linha do BFO no display, por isso que a foto abaixo está com nome caracteres BFO mais para o lado direito do display. Se vocês quiserem no esboço na linha 128 //display.setCursor(30,25); vocês vão habilitar removendo os dois travessões no começo, e desabilitar a de linha 127 colocando // dois travessões na frente de display.setCursor(4,25); assim o nome caracteres BFO vai ficar mais ao centro. Vou publicar os dois esboços sketches, como estão nas fotos finais vocês escolhem o que quer para o seu projeto. Por favor mandem comentários por vídeo ou blog, tentem fazer modificações em outros esboços, não é difícil e sei que vocês conseguirão. Muito obrigado, desculpas pela demora do final da publicação, postarei um vídeo no YouTube explicando um pouco sobre o projeto DDS VFO.
Pronto definido o display será assim, agora vamos colocar os caracteres em suas posições corretas.
Vejam que os caracteres números da frequência estão um pouco acima. Vamos centralizar verticalmente.
Acho que dá para separar com uma linha divisória para a frequência, é só uma aparência melhor.
Vamos ajustar os caracteres números da frequência mais para a direita, horizontal.
Neste dá para ir até os 118 MHz sem o "0" atrapalhar o nome BFO.
Pronto acho que assim está bom, é deu uma visão melhor.
No modo LSB vejam que a FI BFO apresenta a frequência abaixo de 7.798.50 KHz, o Hz faz parte do VFO CLK1.
Abaixo foto do ajuste de CLK1 BFO no Encoder com passos de 1 MHz Step para FI de 10 MHz, esta frequência permanece enquanto o DDS estiver ligado e sem mexer apertar os botões de Modo, se isso acontecer o BFO FI voltará a sua origem do digitado no programa sketch. 
Abaixo esquema elétrico DDS VFO Cobra 148 GTL AM LSB USB. 
Selecione todo esboço programa, copie, depois na área de trabalho abra o Arduino IDE, limpe delete e cole dentro, vá em Arquivo "Salvar como" renomeei e salve em uma pasta. Instale todas ás bibliotecas que estão com link no cabeçalho do esboço sketch. Não tenham várias bibliotecas para um mesmo periférico isso causará erros na compilação, sempre faço assim deixo só as bibliotecas que estão no esboço sketch.

//**********************************************************************************************************/
// Modifications and updates for si5351 Arduino Nano, Pro Mini Atmega 328P. Original by GE75A Julio
// and shared on YouTube by CT7API Manuel Ferreira in the version with 128 x 64 OLED. Original LCD version.
// This entire program is taken from Jason Mildrum, NT7S and Przemek Sadowski, SQ9NJE. There is not enough 
// original code written by me to make it worth mentioning. http://nt7s.com/ http://sq9nje.pl/
// http://ak2b.blogspot.com/ Last update BFO CLK1 by Waldir Cardoso Blog:
// https://projetosetransceptores.blogspot.com/2026/03/dds-vfo-para-cobra-148-gtl-modificado-2.html
//------------------------------------------------------------------------------------------------------------
// Function
// 1.VFO CLK0 34 Mhz AM LSB USB MODE Cobra 148 GTL other AM SSB equipment, modify the sketch.
// 2.BFO CLK1 BFO FI 7.800 Khz AM LSB USB MODE Cobra 148 GTL other AM SSB equipment, modify the sketch.
// 3.BFO CLK1 Pressing ENCODER sets a temporary IF adjustment.
// 4.STEP(1MHz,100,10,1kHz,100,10,1Hz)
////////////////////////////////////////////////////////////////////////    
//Libraries.
#include <si5351.h>                //https://github.com/etherkit/Si5351Arduino/releases/tag/v1.1.2
#include <Wire.h>                  //IDE Standard
#include <Adafruit_SSD1306.h>      //Adafruit SSD1306 https://github.com/adafruit/Adafruit_SSD1306
#include <Rotary.h>                //Ben  Buxton https://github.com/brianlow/Rotary
#include <Adafruit_GFX.h>          //Adafruit GFX https://github.com/adafruit/Adafruit-GFX-Library
//----------------------------------------------------------------------------------------------------
#define OLED_RESET 4
Adafruit_SSD1306 display(OLED_RESET);

#define F_MIN        10000000UL
#define F_MAX        6000000000UL
#define ENCODER_A        2       // Encoder pin A                     
#define ENCODER_B        3       // Encoder pin B
#define ENCODER_BTN      4
#define USB_PIN          6
#define LSB_PIN          7
#define AM_PIN           8

int lsbstate = 0;
int amstate = 0;
int usbstate = 0;

Si5351 si5351;  //Si5351 I2C Address 0x60
Rotary r = Rotary(3, 2);

volatile int32_t AM  = 780000000ULL;
volatile int32_t LSB = 779850000ULL;   //LSB = IF - 1500Hz
volatile int32_t USB = 780150000ULL;   //USB = IF + 1500Hz
volatile int32_t bfo = 780000000ULL;   //Intermediary frequency IF
volatile int32_t vfo = 2696500000ULL    / SI5351_FREQ_MULT; // frequency display initialization
volatile uint32_t radix = 10000;       //start step size - change to suit
boolean changed_f = 0;
String tbfo = "AM";

boolean setbfo = false; // Press the ENCODER for temporary BFO tuning. Pressione sintonizar o BFO temporario.
int enc_cnt = 0;        // Encoder count

#define IF_Offset       //Output is the display plus or minus the bfo frequency

/**************************************/
/* Interrupt service routine for      */
/* encoder frequency change           */
/**************************************/
ISR(PCINT2_vect) {
  unsigned char result = r.process();
  if (result == DIR_CW) 
  enc_cnt = +1;
  else if (result == DIR_CCW)
  enc_cnt = -1;
  if (setbfo)
    set_bfo_freq();
  else
    set_frequency();   
 }
/**************************************/
/* Change the frequencies             */
/**************************************/
void set_frequency()
{
  vfo += enc_cnt * radix;
  enc_cnt =0;
  changed_f = 1;
}
void set_bfo_freq()
{
  bfo += enc_cnt * radix;
  enc_cnt = 0;
  changed_f = 1;
}
/**************************************/
/* Read the button with debouncing    */
/**************************************/
boolean get_button()
{
  if (!digitalRead(ENCODER_BTN))
  {
  delay(20);
  if (!digitalRead(ENCODER_BTN))
  {
  long strttime=millis();
  while (!digitalRead(ENCODER_BTN));
  if((millis() - strttime) > 500)    // check if it was a long press
  {                
  setbfo = !setbfo;   // Pressing ENCODER will set the temporary BFO setting.       
  Serial.println(bfo);
  changed_f = 1;
  return 0;        
  }
  else
  return 1;
  }
 }
  return 0;
}

/**************************************/
/* Displays the frequency             */
/**************************************/
void display_frequency()
{
  uint16_t f, g; 
  display.clearDisplay();
  display.setTextSize(1);                    // Fonte tamanho letras caracteres na tela. 
  display.setTextColor(WHITE);  
  display.drawLine(0, 7, 0, 24, WHITE);      // Linha descida 
  display.setCursor(4,0);                    // Posição MODE na tela.
  display.drawLine(0, 7, 127, 7, WHITE);     // Linha divisória acima frequência.
  display.print("MODE= ");                   // Imprime nome na tela.  
  display.print(tbfo);
  display.drawLine(128, 7, 127, 24, WHITE);  // Linha descida.
  display.setCursor(4,25);                   // Posição inicial BFO na tela.  
  //display.setCursor(30,25);                // Posição central BFO para 100 MHz no display.  
  display.drawLine(0, 24, 127, 24, WHITE);   // Linha divisória abaixo frequência.
  display.print("BFO = ");                   //Imprime nome na tela.  
  display.print(bfo);
/**************************************/
/* Displays the frequency change step */
/**************************************/ 
  display.setCursor(65,0);
  display.print(F("TS="));
  switch (radix)
  {
    case 1:
      display.println(F("   1 Hz"));
      break;
    case 10:
      display.println(F("  10 Hz"));
      break;
    case 100:
      display.println(F(" 100 Hz"));
      break;
    case 1000:
      display.println(F("  1 kHz"));
      break;
    case 10000:
      display.println(F(" 10 kHz"));
      break;
    case 100000:
      display.println(F("100 kHz"));
      break;
    case 1000000:
      display.println(F("  1 MHz"));
      break;      
}

/**************************************/
/* Displays the frequency             */
/**************************************/  
  display.setTextSize(2);  
  display.setCursor(5,9);  
  f = vfo / 1000000;  //variable is now vfo instead of 'frequency'
  if (f < 10)
  display.print(' ');
  display.print(f);
  display.print('.');
  f = (vfo % 1000000) / 1000;
  if (f < 100)
  display.print('0');
  if (f < 10)
  display.print('0');
  display.print(f);
  display.print('.');
  f = vfo % 1000;
  if (f < 100)
    display.print('0');
  if (f < 10)
  display.print('0');
  display.print(f);  
  display.display();
  display.clearDisplay();   
}
/**************************************/
/*            S E T U P               */
/**************************************/
void setup() 
{   
  Wire.begin();
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  display.clearDisplay();
  display.setTextColor(WHITE);
  display.display();
  
  pinMode(2, INPUT_PULLUP);
  pinMode(3, INPUT_PULLUP);
  pinMode(USB_PIN, INPUT_PULLUP);
  pinMode(LSB_PIN, INPUT_PULLUP);
  pinMode(AM_PIN, INPUT_PULLUP);
  
  // Initial text Cobra 148 GTL or other equipment display.
  display.setTextSize(2);
  display.setTextColor(WHITE);
  display.setCursor(9,1);
  display.println("Cobra 148");
  display.setCursor(47,17);
  display.print("GTL");
  display.display();
  delay(2000);
  display.clearDisplay();
     
  //initialize the Si5351  
  si5351.set_correction(18000); //**mine. There is a calibration sketch in File/Examples/si5351Arduino-Jason
  si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0); //If you're using a 27Mhz crystal, put in 27000000 instead of 0 
  si5351.set_pll(SI5351_PLL_FIXED, SI5351_PLLA);
  // Set CLK0 to output the starting "vfo" frequency as set above by vfo = ?
    
#ifdef IF_Offset 
  //Serial.println((long)((vfo * SI5351_FREQ_MULT) - bfo) * -1);
  si5351.set_freq(((vfo * SI5351_FREQ_MULT) + bfo), SI5351_PLL_FIXED, SI5351_CLK0);    
  volatile uint32_t vfoT =((vfo * SI5351_FREQ_MULT) - bfo);
  tbfo = "AM";
     
  //Set CLK1 to output bfo frequency
  si5351.set_freq( bfo,0, SI5351_CLK1);  
  si5351.drive_strength(SI5351_CLK0, SI5351_DRIVE_6MA);
  si5351.drive_strength(SI5351_CLK1, SI5351_DRIVE_4MA);  

#endif

  pinMode(ENCODER_BTN, INPUT_PULLUP);
  PCICR |= (1 << PCIE2);           // Enabled pin change interrupt for the encoder
  PCMSK2 |= (1 << PCINT18) | (1 << PCINT19);                    
  sei();
  display_frequency();  // Update the display
}
/**************************************/
/*             L O O P                */
/**************************************/
void loop()
{       
  // Update the display if the frequency has been changed  
  { display_frequency();  
 
#ifdef IF_Offset
        
    //you can also subtract the bfo to suit your needs
    //si5351.set_freq(((vfo * SI5351_FREQ_MULT) - bfo), SI5351_PLL_FIXED, SI5351_CLK0); // VFO subtrai á IF
    
    si5351.set_freq(( vfo * SI5351_FREQ_MULT) + bfo, SI5351_PLL_FIXED, SI5351_CLK0);    // VFO soma á IF
    si5351.set_freq( bfo,0, SI5351_CLK1);   // BFO CLK1 
    
    si5351.output_enable(SI5351_CLK0, 1); // Enable CLK0                 //1 - Enable / 0 - Disable CLK
    si5351.output_enable(SI5351_CLK1, 1); // Enable CLK1
#endif   
   {
    usbstate = digitalRead(USB_PIN);
    if (usbstate == LOW) {
      //USB
      bfo = USB;
      tbfo = "USB";
    }
    amstate = digitalRead(AM_PIN);
    if (amstate == LOW) {
      //AM
      bfo = AM;
      tbfo = "AM";
    }
    lsbstate = digitalRead(LSB_PIN);
    if (lsbstate == LOW) {
      // LSB
      bfo = LSB;
      tbfo = "LSB";
    }                 
  } 
  // Button press changes the frequency change step for 1 Hz steps 
  if (get_button()) 
  {  
    switch (radix)
    {
      case 1:
        radix = 10;
        break;
      case 10:
        radix = 100;
        break;
      case 100:
        radix = 1000;
        break;
      case 1000:
        radix = 10000;
        break;
      case 10000:
        radix = 100000;
        break;
      case 100000:
        radix = 1000000;
        break;  
      case 1000000:
        radix = 1;
        break; }
    } 
    display_frequency();   
    }
   }   
Segundo esboço sketch, selecione todo esboço programa, copie, depois na área de trabalho abra o Arduino IDE, limpe delete e cole dentro, vá em Arquivo "Salvar como" renomeei e salve em uma pasta. Instale todas ás bibliotecas que estão com link no cabeçalho do esboço sketch. Não tenham várias bibliotecas para um mesmo periférico isso causará erros na compilação, sempre faço assim deixo só as bibliotecas que estão no esboço sketch.

//**********************************************************************************************************/
// Modifications and updates for si5351 Arduino Nano, Pro Mini Atmega 328P. Original by GE75A Julio
// and shared on YouTube by CT7API Manuel Ferreira in the version with 128 x 64 OLED. Original LCD version.
// This entire program is taken from Jason Mildrum, NT7S and Przemek Sadowski, SQ9NJE. There is not enough 
// original code written by me to make it worth mentioning. http://nt7s.com/ http://sq9nje.pl/
// http://ak2b.blogspot.com/ Last update BFO CLK1 by Waldir Cardoso Blog:
// https://projetosetransceptores.blogspot.com/2026/03/dds-vfo-para-cobra-148-gtl-modificado-2.html
//---------------------------------------------------------------------------------
// Function
// 1.VFO CLK0 34 Mhz AM LSB USB MODE Cobra 148 GTL other AM SSB equipment, modify the sketch.
// 2.BFO CLK1 BFO FI 7.800 Khz AM LSB USB MODE Cobra 148 GTL other AM SSB equipment, modify the sketch.
// 3.BFO CLK1 Pressing ENCODER sets a temporary IF adjustment.
// 4.STEP(1MHz,100,10,1kHz,100,10,1Hz)
////////////////////////////////////////////////////////////////////////    
//Libraries.
#include <si5351.h>                //https://github.com/etherkit/Si5351Arduino/releases/tag/v1.1.2
#include <Wire.h>                  //IDE Standard
#include <Adafruit_SSD1306.h>      //Adafruit SSD1306 https://github.com/adafruit/Adafruit_SSD1306
#include <Rotary.h>                //Ben  Buxton https://github.com/brianlow/Rotary
#include <Adafruit_GFX.h>          //Adafruit GFX https://github.com/adafruit/Adafruit-GFX-Library
//----------------------------------------------------------------------------------------------------
#define OLED_RESET 4
Adafruit_SSD1306 display(OLED_RESET);

#define F_MIN        10000UL
#define F_MAX        60000000UL
#define ENCODER_A        2       // Encoder pin A                     
#define ENCODER_B        3       // Encoder pin B
#define ENCODER_BTN      4
#define USB_PIN          6
#define LSB_PIN          7
#define AM_PIN           8

int lsbstate = 0;
int amstate = 0;
int usbstate = 0;

Si5351 si5351;  //Si5351 I2C Address 0x60
Rotary r = Rotary(3, 2);

volatile int32_t AM  = 780000000ULL;
volatile int32_t LSB = 779850000ULL;   //LSB = IF - 1500Hz
volatile int32_t USB = 780150000ULL;   //USB = IF + 1500Hz
volatile int32_t bfo = 780000000ULL;   //Intermediary frequency IF
volatile int32_t vfo = 2696500000ULL    / SI5351_FREQ_MULT; // frequency display initialization
volatile uint32_t radix = 10000;       //start step size - change to suit
boolean changed_f = 0;
String tbfo = "AM";

boolean setbfo = false; // Press the ENCODER for temporary BFO tuning. Pressione para sintonizar o BFO temporario.
int enc_cnt = 0;        // Encoder count

#define IF_Offset       //Output is the display plus or minus the bfo frequency

/**************************************/
/* Interrupt service routine for      */
/* encoder frequency change           */
/**************************************/
ISR(PCINT2_vect) {
  unsigned char result = r.process();
  if (result == DIR_CW) 
  enc_cnt = +1;
  else if (result == DIR_CCW)
  enc_cnt = -1;
  if (setbfo)
    set_bfo_freq();
  else
    set_frequency();   
 }
/**************************************/
/* Change the frequencies             */
/**************************************/
void set_frequency()
{
  vfo += enc_cnt * radix;
  enc_cnt =0;
  changed_f = 1;
}
void set_bfo_freq()
{
  bfo += enc_cnt * radix;
  enc_cnt = 0;
  changed_f = 1;
}
/**************************************/
/* Read the button with debouncing    */
/**************************************/
boolean get_button()
{
  if (!digitalRead(ENCODER_BTN))
  {
  delay(20);
  if (!digitalRead(ENCODER_BTN))
  {
  long strttime=millis();
  while (!digitalRead(ENCODER_BTN));
  if((millis() - strttime) > 500)    // check if it was a long press
  {                
  setbfo = !setbfo;   // Pressing ENCODER will set the temporary BFO setting.       
  Serial.println(bfo);
  changed_f = 1;
  return 0;        
  }
  else
  return 1;
  }
 }
  return 0;
}

/**************************************/
/* Displays the frequency             */
/**************************************/
void display_frequency()
{
  uint16_t f, g; 
  display.clearDisplay();
  display.setTextSize(1);                     // Fonte tamanho letras caracteres na tela. 
  display.setTextColor(WHITE);  
  display.drawLine(0, 7, 0, 24, WHITE);       // Linha descida 
  display.setCursor(4,0);                     // Posição MODE na tela.
  display.drawLine(0, 7, 127, 7, WHITE);      // Linha divisória acima frequência.
  display.print("MODE= ");                    // Imprime nome na tela.  
  display.print(tbfo);
  display.drawLine(127, 7, 127, 40, WHITE);   // Linha vertical descida.
  //display.drawLine(127, 7, 127, 24, WHITE); // Linha vertical descida.
  display.setCursor(4,25);                    // Posição inicial BFO na tela.   
  //display.setCursor(30,25);                 // Posição central BFO na tela para 100 MHz.
  //display.drawLine(0, 24, 127, 24, WHITE);  // Linha divisória horizontal abaixo frequência.
  display.drawLine(0, 24, 104, 24, WHITE);    // Linha divisória horizontal abaixo frequência.
  display.drawLine(104, 24, 104 , 40, WHITE); // Linha vertical para frinal BFO Hz.   
  display.print("BFO = ");                    //Imprime nome na tela.  
  display.print(bfo);
/**************************************/
/* Displays the frequency change step */
/**************************************/ 
  display.setCursor(65,0);
  display.print(F("TS="));
  switch (radix)
  {
    case 1:
      display.println(F("   1 Hz"));
      break;
    case 10:
      display.println(F("  10 Hz"));
      break;
    case 100:
      display.println(F(" 100 Hz"));
      break;
    case 1000:
      display.println(F("  1 kHz"));
      break;
    case 10000:
      display.println(F(" 10 kHz"));
      break;
    case 100000:
      display.println(F("100 kHz"));
      break;
    case 1000000:
      display.println(F("  1 MHz"));
      break;      
}

/**************************************/
/* Displays the frequency             */
/**************************************/  
  display.setTextSize(2);  
  display.setCursor(5,9);  
  f = vfo / 1000000;  //variable is now vfo instead of 'frequency'
  if (f < 10)
  display.print(' ');
  display.print(f);
  display.print('.');
  f = (vfo % 1000000) / 1000;
  if (f < 100)
  display.print('0');
  if (f < 10)
  display.print('0');
  display.print(f);
  display.print('.');
  f = vfo % 1000;
  if (f < 100)
    display.print('0');
  if (f < 10)
  display.print('0');
  display.print(f);  
  display.setTextSize(1); 
  display.setCursor(110,25);
  display.println("Hz");
  display.display();
  display.clearDisplay();   
}
/**************************************/
/*            S E T U P               */
/**************************************/
void setup() 
{   
  Wire.begin();
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  display.clearDisplay();
  display.setTextColor(WHITE);
  display.display();

  pinMode(2, INPUT_PULLUP);
  pinMode(3, INPUT_PULLUP);
  pinMode(USB_PIN, INPUT_PULLUP);
  pinMode(LSB_PIN, INPUT_PULLUP);
  pinMode(AM_PIN, INPUT_PULLUP);
  
  // Initial text Cobra 148 GTL or other equipment display.
  display.setTextSize(2);
  display.setTextColor(WHITE);
  display.setCursor(9,1);
  display.println("Cobra 148");
  display.setCursor(47,17);
  display.print("GTL");
  display.display();
  delay(2000);
  display.clearDisplay();
     
  //initialize the Si5351  
  si5351.set_correction(18000); //**mine. There is a calibration sketch in File/Examples/si5351Arduino-Jason
  si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0); //If you're using a 27Mhz crystal, put in 27000000 instead of 0 
  si5351.set_pll(SI5351_PLL_FIXED, SI5351_PLLA);
  // Set CLK0 to output the starting "vfo" frequency as set above by vfo = ?
    
#ifdef IF_Offset 
  //Serial.println((long)((vfo * SI5351_FREQ_MULT) - bfo) * -1);
  si5351.set_freq(((vfo * SI5351_FREQ_MULT) + bfo), SI5351_PLL_FIXED, SI5351_CLK0);    
  volatile uint32_t vfoT =((vfo * SI5351_FREQ_MULT) - bfo);
  tbfo = "AM";
     
  //Set CLK1 to output bfo frequency
  si5351.set_freq( bfo,0, SI5351_CLK1);  
  si5351.drive_strength(SI5351_CLK0, SI5351_DRIVE_6MA);
  si5351.drive_strength(SI5351_CLK1, SI5351_DRIVE_4MA);  

#endif

  pinMode(ENCODER_BTN, INPUT_PULLUP);
  PCICR |= (1 << PCIE2);           // Enabled pin change interrupt for the encoder
  PCMSK2 |= (1 << PCINT18) | (1 << PCINT19);                    
  sei();
  display_frequency();  // Update the display
}
/**************************************/
/*             L O O P                */
/**************************************/
void loop()
{       
  // Update the display if the frequency has been changed
  { display_frequency();
    
#ifdef IF_Offset
        
    //you can also subtract the bfo to suit your needs
    //si5351.set_freq(((vfo * SI5351_FREQ_MULT) - bfo), SI5351_PLL_FIXED, SI5351_CLK0); // VFO subtrai á IF
    
    si5351.set_freq(( vfo * SI5351_FREQ_MULT) + bfo, SI5351_PLL_FIXED, SI5351_CLK0);    // VFO soma á IF
    si5351.set_freq( bfo,0, SI5351_CLK1);   // BFO CLK1 
    
    si5351.output_enable(SI5351_CLK0, 1); // Enable CLK0                 //1 - Enable / 0 - Disable CLK
    si5351.output_enable(SI5351_CLK1, 1); // Enable CLK1
#endif   
   {
    usbstate = digitalRead(USB_PIN);
    if (usbstate == LOW) {
      //USB
      bfo = USB;
      tbfo = "USB";
    }
    amstate = digitalRead(AM_PIN);
    if (amstate == LOW) {
      //AM
      bfo = AM;
      tbfo = "AM";
    }
    lsbstate = digitalRead(LSB_PIN);
    if (lsbstate == LOW) {
      // LSB
      bfo = LSB;
      tbfo = "LSB";
    }                 
  } 
  // Button press changes the frequency change step for 1 Hz steps 
  if (get_button()) 
  {  
    switch (radix)
    {
      case 1:
        radix = 10;
        break;
      case 10:
        radix = 100;
        break;
      case 100:
        radix = 1000;
        break;
      case 1000:
        radix = 10000;
        break;
      case 10000:
        radix = 100000;
        break;
      case 100000:
        radix = 1000000;
        break;  
      case 1000000:
        radix = 1;
        break; }
    } 
    display_frequency();   
    }
   }   

sábado, 28 de fevereiro de 2026

DDS VFO AM SSB Oled Si5351 CB 80 canais VFO 10KHz 160 MHz.

Pessoal atualizei 10/03/2026 com dois esboços sketches, o modificado atualizado por mim, e o segundo e último original .
Olá pessoal. Trago aqui blog em vídeo, mais algumas modificações feita no esboço do DDS VFO Arduino CBrádio em sua página Tailandês dia 11 de Outubro facebook e vídeos YouTube, entre outros esboços, este que publico é para o Cobra 148 GTL e outros equipamentos comerciais ou feitos em casa com modo: AM, LSB, USB, CLK0. O próprio autor já destaca no esboço sketch original a BFO FI em 7.800 KHz saíndo Mixer em CLK0 a frequência canal 1 CB PX de 34.765 MHz AM, 34.763.5 MHz LSB, 34.766.5 MHz USB, frequência de saída L20 VCO Cobra 148 GTL. Adicionei a frequência do BFO FI só AM para saída CLK1, desta maneira vocês só terão a frequência AM IF digitada no esboço sketch sem variações de LSB, USB, eu não conseguir fazer offset para Si5351 variar nos modos LSB, USB em CLK1. Mas desta maneira o DDS VFO também servirá no modo canal, com Encoder pressionado por 10s. para receptores ou transceptores em AM ou FM de 455 KHz ou 10.7 MHz, Walkie Talkie, pois o sinal da FI estará como BFO em CLK1, enquanto que CLK0 entregará a frequência com variações offset em AM, LSB, USB se pressionadas ou ligadas ás chaves Push Button em D7 AM, D8 USB, D9 LSB, para GND negativo. Modifiquei o modo canal para 80 canais e 6 bandas, 480 canais entre A, B, C, D, E, F sendo a banda "D1 a D80", canais CB PX liberados aqui no Brasil pela Anatel, ás outras bandas e frequências do modo canal poderão serem alteradas, entre outras frequências até 160 MHz modificando o esboço sketch programa. 
Foto abaixo modo inicial estava com logo Adafruit, retirei e mudei para Cobra 148 GTL como sempre, mas o montador poderá modificar para Walkie Talkie ou outros equipamentos e nome que quiser colocar na inicialização, é só modificar o esboço sketch. Assistam ao vídeo. 
Inicialização DDS VFO Cobra 148 GTL.
Vamos começar ás modificações, abaixo foto do modo VFO original, pressionando o Encoder por alguns segundos. Já mudei o nome STEP para todo maiúsculo.
Já fiz alguns testes e ele vai tranquilo com ótima estabilidade para os 160MHz, então vamos diminuir os números caracteres do VFO para termos espaço no display e mostrar a frequência correta.
Ok, já diminuir os caracteres números, mas parece que tem algo mais a fazer. 
Opa, parece que subir a linha do VFO e nome STEP um pouco mais, o 10MHz ficou cortado.
 
Parece que a linha tem que descer um pouco mais, e os KHz ficaram incompletos, vamos ajeitar isso.
Agora sim, parece que está ok, mas ainda não estou gostando.
Ficou assim o modo VFO, mas ás linhas e números da frequência tem que descer um pouco, vamos atualizar isso.
Pessoal, fui modificar e adicionar os 10 Hz para melhor sintonia fina SSB, encontrei em testes  no STEP em KHz e Hz ou 100 Hz, quando chega dígito 7 dos 100 Hz à do 10 Hz sobe para 9. Não é comum eu fazer esses testes no STEP, simplesmente verifico se está subindo ou descendo e se mostrou os 10 Hz e pronto, mas este não sei o porque, depois que aumentei uma casa decimal na frequência display para os 10 Hz ficando com 5 casas decimais para os KHz e Hz, , notei que em todos de 100 KHz a 10 Hz ficava assim, tentei de tudo no esboço, não conseguir resolver, pensando que poderia ser a minha modificação, gravei novamente o esboço original para ver se acontece o mesmo. Para minha surpresa no original está com o mesmo erro, até mandei um comentário ao criador do esboço mas até agora recebi só um Emoji de Joinha por ele, vamos ver se ele me responde e me diz como fazer para resolver este problema, pois note em 100 Hz, 1 KHz, 10 KHz, 100 KHz. Em outros testes dia 28/02/2026, notei que na frequência de 15 Mhz no modo VFO não tem variação do STEP em 100Hz o problema some até 16 Mhz, depois o problema continua. Esboço programa original. ás fotos abaixo mostra como o VFO original. 
Vejam que no dia 19/02/2026 tive que gravar novamente o original fotos abaixo, para ver se tinha este mesmo problema. Sim, tem o mesmo problema. 
O AM apareceu pressionei a chave para foto. Modo canais ainda desatualizado com C1.
Vejam que ele original só vai ao canal 40 normal.
Vejam modo VFO o STEP em letras minúsculas, e nome VFO mais ao centro.
Abaixo vejam ás fotos como ficou o DDS VFO atualizado com o esboço abaixo.
Agora sim, a chave de modo AM está pressionada ou ligada permanentemente a GND negativo.
Mesmo não pressionada o VFO inicializa no modo AM sempre original.
Modo canais já atualizado com 480, 80 canais CB D1 a D80.
Chave Push Button modo USB pressionada.
Vejam ás fotos das modificações que fiz no VFO, ás fotos acima com data 19/02/2026 mostra como inicializei ás modificações. Estas de hoje 08/03/2026 mostram o VFO todo atualizado.
STEP para 10Hz, melhor sintonia em SSB.
VFO em AM 10KHz chave Push Button pressionada a GND negativo.
Frequência Cobra 148 GTL AM saída em CLK0.
VFO em LSB 10KHz chave Push Button pressionada a GND negativo.
Frequência Cobra 148 GTL LSB saída em CLK0.
VFO em USB 10KHz chave Push Button pressionada a GND negativo.
Frequência Cobra 148 GTL USB saída em CLK0.
Frequência IF Cobra 148 GTL saída AM CLK1.
Se digitada também em BFO no sketch a saída em CLK2.
 
Tinha esquecido do esquema ligações no Arduino. Desculpe-me. 
O DDS VFO está com 3 saídas sendo CLK0 o VFO frequência variável de 10 KHz a 160 MHz em: 268-displayedManualFreq = constrain(displayedManualFreq, 0.010f, 160.000f);  CLK1 é a frequência intermediária FI digitada em: 52const float frequencyOffsetIF = 7.800f;  CLK2 é a frequência BFO que o montador quiser digitar para MIX. Ótimo para quem tem dentro do Cobra 148 GTL  um transverter para 40 metros ou outra frequência, digite 19.965 Mhz para ficar com frequência exata do PX CB em: 51const float frequencyBFO = 19.965f;  
Abaixo estão os esboços sketches primeiro é nosso DDS VFO atualizado com minhas modificações. Segundo no final da página sketch original da página do nosso amigo DDS VFO Arduino CBrádio. Contribuindo para nosso hobby. Meus agradecimentos ao amigo tailandês que o senhor o abençoe e muita saúde. 
Abra seu Arduino IDE na área de trabalho e delete tudo. Copie todo programa esboço e cole dentro do Arduino IDE. Tenham ás bibliotecas instaladas, e lembre-se que muitas bibliotecas para o mesmo arquivo pode causar conflitos e provocar erros na compilação. 

//**********************************************************************************************************************
// 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;
 }

Abaixo esboço sketch original.

#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
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
Si5351 si5351;
RotaryEncoder encoder(3, 2);
#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 = 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);
}
display.display();
}
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();
}
}
lastBtnState = btn;
}