quarta-feira, 31 de dezembro de 2025

DDS VFO Cobra 148 GTL AM, LSB, USB, atualizado.

Olá pessoal, e todos que acompanha o blog e nosso canal no YouTube. Trago para o final de ano um presente de Natal. Ainda sobre o esboço que copiei do vídeo e publicado aqui no blog e no YouTube, com pequenas atualizações. Assistam o vídeo explicativo como ser adaptados no Cobra 148 GTL ou outros transceptores. O esboço ou sketch é simples mas temos em CLK0 VFO com adição ou subtração da FI configurando no esboço, temos em CLK1 BFO frequência de FI digitada no começo do esboço, esta frequência tem variação em LSB, USB, com 1500Hz abaixo e acima da frequência do AM, que são refletidas no VFO CLK0. Assim para quem quiser poderá usar CLK1 como Carrier oscilador, poderá precisar de um Buffer, ou simplesmente ligar no local do cristal de 7.800Mhz, se alguém perdeu o cristal ou está danificado de Carrier oscilador 7.800 Mhz Cobra 148 GTL, ou outros transceptores podem usar este recurso com sucesso para reviver seu transceptor.
Bem como já relatei no começo deste, o sketch é atualizado no STEP agora fixo, começo da frequência imediato mesmo depois da tela de inicialização Cobra 148 GTL. O sketch ficou mais leve, estável, e muito melhor que o primeiro foi até 110 Mhz com ótima estabilidade, não mais de 110 Mhz, o que dá para ser usado em transceptores comerciais ou montagens em casa homebrew, nas faixas de HF, VHF até 50 ou 70 Mhz, tudo isso com os modos AM, LSB, USB. Fiz algumas alterações para ver se ele subiria até os 160 Mhz mas não deu, tentei modificar a biblioteca, também não tive sucesso. Outras tentativas de colocar um S/Meter na parte de baixo do display, chave TX, RX em CLK0 com frequência do display e frequência do VFO +/- do BFO, não deram certo além da memoria EEPROM que não deu certo também. Pessoal não é fácil só copiar e colar linhas de comandos de outros esboços e vai dá certo, não, não vai dá certo, eu ainda estou aprendendo mas digo para mudar um esboço tem que saber como fazer está modificação, é para quem tem muito conhecimento no código C++ Arduino, em alguns casos simples como imprimir um nome ou outras simples modificações, são fáceis, mas outras como ás que citei, teremos que modificar muito o esboço para ter o que queremos. Vocês podem ver vários exemplos de muitas modificações feitas por Radioamadores e amantes da eletrônica digital do projeto do DDS VFO 10K 220Mhz até os chineses copiou e estão comercializando este magnífico DDS VFO que é muito bom para nossos transceptores. Ele é amplamente divulgado em vídeos, sites, blogs, eu acho pelas minhas pesquisas que é o projeto do Júlio Cesar DDS VFO e mais modificados no mundo. Parabéns Júlio Cesar. Enquanto muitos acham que só podem modificarem os esboços quem é programador, eu li e ouvir, vários relatos de colegas do mundo inteiro que diz não saber muito sobre Arduino e Código C++, mas mesmo assim fazem modificações e dão certo. Não dá para eu listar aqui os sites e vídeos que tem este projeto do Júlio Cesar como argumento de modificações, pois são inúmeros. Vamos retornar ao nosso simples e eficiente DDS VFO AM, LSB, USB. Eu gostaria se tiver alguém aqui no Brasil ou no mundo que quiser mandar como colocar um S/Meter e memória EEPROM nestes esboços, eu ficaria realmente feliz e divulgo seu nome ou como queira ser divulgado. 
Pessoal eu recebi muitos e-mail e comentários perguntando como colocar este DDS VFO em um transceptor AM SSB. Minha resposta: Seja qual a marca e modelo, se o rádio tiver AM, FM, CW, LSB, USB, vocês terão de acrescentar no esboço estes modos e digitar seus pinos e suas frequências de FI. vejam com atenção ás linhas de comando do esboço, e façam o mesmo. Para transceptores que tem a chave de AM, LSB, USB, Cobra 148 GTL a chave é S403-3 com +8 volts. Os pinos D6 USB, D7 LSB, D8 AM, do Arduino no esboço elas tem que ir para LOW, negativo, GND terra, para isso teremos que montar uma chave eletrônica a transistor NPN de uso geral, onde o coletor vai ao pino do Arduino, emissor ao GND negativo, e base entra o sinal S403-3 segue a dois resistores um em série de 22k a 47K outra idêntica e um capacitor de cerâmica ou poliéster de 103, 10nF base para negativo GND. Abaixo está o esquema da chave eletrônica que servirá para demais transceptores que tem chave com alimentação +B outros que tem alimentação a GND negativo não precisa de tais chaves. Não podemos esquecer que para vocês colocarem o DDS VFO em seu transceptor, terão de montar um simples Buffer amplificador de RF com um ou dois transistores, o transistor melhor é BF 199 ou outros de alta frequência para quem vai até 220 Mhz, Vejam na página do Sr. PU2REO manual em PDF DVFO como instalar o DDS VFO em um Cobra 148 GTL. Parabéns muito obrigado ao PU2REO pela contribuição. Abaixo tradução do PU2REO na página do Buffer manual v1.5.
"Vamos voltar ao assunto: depois de remover os componentes de rádio marcados com um quadrado vermelho
nos esquemas de rádio, é hora de colocar o DVFO para funcionar. Se você observar os esquemas de rádio, é fácil identificar que o pino nº 1 do CI2 (UHIC 070) é a saída do VFO analógico. Portanto, é exatamente aí que vamos inserir nosso novo gerador. Após alguns testes, descobri que o Si5351 não é adequado para acoplamento direto com L20, devido à sua baixa corrente e tensão de saída, entre outros 2 ou 3 motivos que estão além do escopo deste manual. É aí que entra um amplificador de sinal pequeno: Esquema do Amplificador de Sinal Pequeno (ou simplesmente "Buffer"). Para construir este buffer, usei o TR20 do meu próprio rádio. Você pode usar um BC547, que também funcionará bem. A versão final deste buffer foi montada na placa de circuito impresso e soldada no mesmo local onde antes estava o UHIC-070."
Assistam ao vídeo publicado 02/01/2026.
 
Esquema Buffer amplificador simples RF PU2REO.
  PCB Buffer PU2REO.
 
      Esquema chave eletrônica para transceptores com chave +B.   
Esquema ligações dos periféricos no Arduino DDS VFO CB AM, LSB, USB.
Fotos abaixo é esboço, tem Modo AM, LSB, USB, STEP fixo, TRX. frequência na parte de baixo do display. É o recomendado e foi o primeiro a ser atualizado. Mais estável, mais leve. inicialização rápido da frequência ao ligar, mesmo com tela de inicialização.
Tela de inicialização Cobra 148 GTL. Poderá modificar para outros transceptores.
Tela do DDS VFO atualizado com STEP fixo, TRX, Modo AM, LSB, USB, frequência VFO parte inferior display.
Vai até 110 Mhz com ótima estabilidade. Depois desta frequência fica instável.
Vejam no Frequencímetro a FI é 7.800Khz.
 
Selecione o programa, copie, depois abra na área de trabalho o Arduino IDE, limpe delete e cole dentro. 

/**********************************************************************************
Updated sketch copied from YouTube by CT7API Manuel Ferreira.
Updated by Waldir Cardoso. 12/2025. https://projetosetransceptores.blogspot.com/
Blog: Projetos e Transceptores. Brazil.
This sketch will work with the Cobra 148 GTL, and equipment with 3 or 5 switches 
for AM, LSB, USB. Modification to the sketch for FM, CW. Cobra 148 GTL-DX, SuperStar 3900.
***********************************************************************************/
//---------------------------------------------------------------------------------
// Function
// 1.VFO CLK0 34 Mhz AM LSB USB MODE
// 2.BFO CLK1 BFO FI 7.800 Khz AM LSB USB MODE
// 3.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        100000000UL
#define F_MAX        16000000000UL

#define ENCODER_A    2
#define ENCODER_B    3
#define ENCODER_BTN  5
#define USB_PIN      6
#define LSB_PIN      7
#define AM_PIN       8
//#define rx_tx        9
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 = "";
//bool sts = 0;

#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) 
  set_frequency(1);
  else if (result == DIR_CCW) 
  set_frequency(-1);
}
/**************************************/
/* Change the frequency               */
/* dir = 1    Increment               */
/* dir = -1   Decrement               */
/**************************************/
void set_frequency(short dir)
{      
  if (dir == 1)
     vfo += radix;
     if (dir == -1){
     if  (vfo > radix ) {
    vfo -= radix;
     }
 }  
  changed_f = 1;
 }  
/**************************************/
/* Read the button with debouncing    */
/**************************************/
boolean get_button()
{
  if (!digitalRead(ENCODER_BTN))
  {
    delay(20);
    if (!digitalRead(ENCODER_BTN))
    {
      while (!digitalRead(ENCODER_BTN));
      return 1;
    }
  }
  return 0;
}

/**************************************/
/* Displays the frequency             */
/**************************************/
void display_frequency()
{
  uint16_t f, g;

  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.setCursor(100, 1);
  //display.print(sts ? "TX" : "RX");  
  display.print("TRX");
  display.setCursor(3,1);
  display.print("MODE:");  
  display.println(tbfo);

/**************************************/
/* Displays the frequency change step */
/**************************************/ 
  //display.setCursor(60,1);
  //display.print(F("ST:"));
  display.setCursor(45,9);
  display.print(F("STEP:"));
  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);  
  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(rx_tx, INPUT_PULLUP);
  pinMode(2, INPUT_PULLUP);
  pinMode(3, INPUT_PULLUP);
  pinMode(USB_PIN, INPUT_PULLUP);
  pinMode(LSB_PIN, INPUT_PULLUP);
  pinMode(AM_PIN, INPUT_PULLUP);
        
  si5351.set_correction(18000); //**mine. There is a calibration sketch in File/Examples/si5351Arduino-Jason
  //where you can determine the correction by using the serial monitor.
     
  //initialize the Si5351
  si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0); //If you're using a 27Mhz crystal, put in 27000000 instead of 0
  // 0 is the default crystal frequency of 25Mhz.

  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_8MA);
  si5351.drive_strength(SI5351_CLK1, SI5351_DRIVE_8MA);  
#endif
  Splash();

  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);   // IF 

    si5351.output_enable(SI5351_CLK0, 1); // Enable CLK0                 //1 - Enable / 0 - Disable CLK
    si5351.output_enable(SI5351_CLK1, 1); // Enable CLK1
#endif     
   //{
   //sts = digitalRead(rx_tx) == LOW;
   //} 
   
    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();
    }
 }   
  void Splash() {
  display.setTextSize(2);
  display.setCursor(5, 1);
  display.print("Cobra 148");
  display.setCursor(45, 17);
  display.print("GTL");
  display.display();
  delay(3000);
  display.clearDisplay();
 }

O esboço e fotos abaixo tem o modo RX e TX falso, não atua nem CLK0 e CLK1, só serve para ter uma referência no display de recepção e transmissão, ao ser acionado pino D9 alto sem ligação RX, em LOW, negativo TX no transceptor Cobra 148 GTL ou outros transceptores.
Tela de inicialização para Cobra 148 GTL, poderá ser modificado para transceptor diferente.
 
Foto modo RX falso.
Modo TX falso.
Selecione todo programa, copie, depois abra na área de trabalho o Arduino IDE, limpe delete e cole dentro. 

/**********************************************************************************
Updated sketch copied from YouTube by CT7API Manuel Ferreira.
Updated by Waldir Cardoso. 12/2025. https://projetosetransceptores.blogspot.com/
Blog: Projetos e Transceptores. Brazil.
This sketch will work with the Cobra 148 GTL, and equipment with 3 or 5 switches 
for AM, LSB, USB. Modification to the sketch for FM, CW. Cobra 148 GTL-DX, SuperStar 3900.
***********************************************************************************/
//---------------------------------------------------------------------------------
// Function
// 1.VFO CLK0 34 Mhz AM LSB USB MODE
// 2.BFO CLK1 BFO FI 7.800 Khz AM LSB USB MODE
// 3.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        100000000UL
#define F_MAX        16000000000UL

#define ENCODER_A        2
#define ENCODER_B        3
#define ENCODER_BTN      5
#define USB_PIN          6
#define LSB_PIN          7
#define AM_PIN           8
#define rx_tx            9

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 = "";
bool sts = 0;

#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) 
  set_frequency(1);
  else if (result == DIR_CCW) 
  set_frequency(-1);
}
/**************************************/
/* Change the frequency               */
/* dir = 1    Increment               */
/* dir = -1   Decrement               */
/**************************************/
void set_frequency(short dir)
{      
  if (dir == 1)
     vfo += radix;
     if (dir == -1){
     if  (vfo > radix ) {
    vfo -= radix;
     }
 }  
  changed_f = 1;
 }  
/**************************************/
/* Read the button with debouncing    */
/**************************************/
boolean get_button()
{
  if (!digitalRead(ENCODER_BTN))
  {
    delay(20);
    if (!digitalRead(ENCODER_BTN))
    {
      while (!digitalRead(ENCODER_BTN));
      return 1;
    }
  }
  return 0;
}

/**************************************/
/* Displays the frequency             */
/**************************************/
void display_frequency()
{
  uint16_t f, g;

  display.clearDisplay();
  display.setTextSize(1);  
  display.setTextColor(WHITE);  
  display.setCursor(100, 1);
  display.print(sts ? "TX" : "RX");  
  //display.print("TRX");
  display.setCursor(3,1);
  display.print("MODE:");    
  display.println(tbfo);

/**************************************/
/* Displays the frequency change step */
/**************************************/ 
  //display.setCursor(60,1);
  //display.print(F("ST:"));
  display.setCursor(45,9);
  display.print(F("STEP:"));
  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);  
  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(rx_tx, INPUT_PULLUP);
  pinMode(2, INPUT_PULLUP);
  pinMode(3, INPUT_PULLUP);
  pinMode(USB_PIN, INPUT_PULLUP);
  pinMode(LSB_PIN, INPUT_PULLUP);
  pinMode(AM_PIN, INPUT_PULLUP);
        
  si5351.set_correction(18000); //**mine. There is a calibration sketch in File/Examples/si5351Arduino-Jason
  //where you can determine the correction by using the serial monitor.
     
  //initialize the Si5351
  si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0); //If you're using a 27Mhz crystal, put in 27000000 instead of 0
  // 0 is the default crystal frequency of 25Mhz.

  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_8MA);
  si5351.drive_strength(SI5351_CLK1, SI5351_DRIVE_8MA);  
#endif
  Splash();

  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);   // IF 

    si5351.output_enable(SI5351_CLK0, 1); // Enable CLK0                 //1 - Enable / 0 - Disable CLK
    si5351.output_enable(SI5351_CLK1, 1); // Enable CLK1
#endif     
   {
   sts = digitalRead(rx_tx) == LOW;
   } 
   
    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();
    }
 }   
  void Splash() {
  display.setTextSize(2);
  display.setCursor(5, 1);
  display.print("Cobra 148");
  display.setCursor(45, 17);
  display.print("GTL");
  display.display();
  delay(3000);
  display.clearDisplay();
 }

Copie e cole no Arduino IDE, o esboço que mais interessar, todos tem a mesma função em CLK0 e CLK1 
Vamos ás fotos e esboço:
Este abaixo é como está nas fotos tem. Modo: AM, LSB, USB, ST: para STEP, frequência no meio e uma área livre em baixo do display, eu estava tentando adicionar um S/Meter embaixo, não conseguir, ficou para futuras modificações.                                                                        
Todos tem esta mesma tela inicial.
Esta é a foto do DDS com esboço abaixo para futuro S/Meter.
Vejam quando fui passar de 99Mhz, o último (0) zero ficou na parte de baixo, não dá para ele ir até 100Mhz, ou vai atrapalhar o S/Meter.
/**********************************************************************************
Updated sketch copied from YouTube by CT7API Manuel Ferreira.
Updated by Waldir Cardoso. 12/2025. https://projetosetransceptores.blogspot.com/
Blog: Projetos e Transceptores. Brazil.
This sketch will work with the Cobra 148 GTL, and equipment with 3 or 5 switches 
for AM, LSB, USB. Modification to the sketch for FM, CW. Cobra 148 GTL-DX, SuperStar 3900.
***********************************************************************************/
//---------------------------------------------------------------------------------
// Function
// 1.VFO CLK0 34 Mhz AM LSB USB MODE
// 2.BFO CLK1 BFO FI 7.800 Khz AM LSB USB MODE
// 3.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        100000000UL
#define F_MAX        16000000000UL

#define ENCODER_A        2
#define ENCODER_B        3
#define ENCODER_BTN      5
#define USB_PIN          6
#define LSB_PIN          7
#define AM_PIN           8
//#define rx_tx          9
#define PIN_S_METER_IN  A3
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 = "";
//bool sts = 0;

#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) 
  set_frequency(1);
  else if (result == DIR_CCW) 
  set_frequency(-1);
}
/**************************************/
/* Change the frequency               */
/* dir = 1    Increment               */
/* dir = -1   Decrement               */
/**************************************/
void set_frequency(short dir)
{      
  if (dir == 1)
     vfo += radix;
     if (dir == -1){
     if  (vfo > radix ) {
    vfo -= radix;
     }
 }  
  changed_f = 1;
 }  
/**************************************/
/* Read the button with debouncing    */
/**************************************/
boolean get_button()
{
  if (!digitalRead(ENCODER_BTN))
  {
    delay(20);
    if (!digitalRead(ENCODER_BTN))
    {
      while (!digitalRead(ENCODER_BTN));
      return 1;
    }
  }
  return 0;
}

/**************************************/
/* Displays the frequency             */
/**************************************/
void display_frequency()
{
  uint16_t f, g;

  display.clearDisplay();
  display.setTextSize(1);  
  display.setTextColor(WHITE);  
  //display.setCursor(100, 1);
  //display.print(sts ? "TX" : "RX");  
  //display.print("TRX");
  display.setCursor(3,1);
  display.print("MODE:");    
  display.println(tbfo);

/**************************************/
/* Displays the frequency change step */
/**************************************/ 
  display.setCursor(60,1);
  display.print(F("ST:"));
  //display.setCursor(45,9);
  //display.print(F("STEP:"));
  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);  
  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(rx_tx, INPUT_PULLUP);
  pinMode(2, INPUT_PULLUP);
  pinMode(3, INPUT_PULLUP);
  pinMode(USB_PIN, INPUT_PULLUP);
  pinMode(LSB_PIN, INPUT_PULLUP);
  pinMode(AM_PIN, INPUT_PULLUP);
        
  si5351.set_correction(18000); //**mine. There is a calibration sketch in File/Examples/si5351Arduino-Jason
  //where you can determine the correction by using the serial monitor.
     
  //initialize the Si5351
  si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0); //If you're using a 27Mhz crystal, put in 27000000 instead of 0
  // 0 is the default crystal frequency of 25Mhz.

  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_8MA);
  si5351.drive_strength(SI5351_CLK1, SI5351_DRIVE_8MA);  
#endif
  Splash();

  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);   // IF 

    si5351.output_enable(SI5351_CLK0, 1); // Enable CLK0                 //1 - Enable / 0 - Disable CLK
    si5351.output_enable(SI5351_CLK1, 1); // Enable CLK1
#endif     
   //{
   //sts = digitalRead(rx_tx) == LOW;
   //} 
   
    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();
    }
 }   
  void Splash() {
  display.setTextSize(2);
  display.setCursor(5, 1);
  display.print("Cobra 148");
  display.setCursor(45, 17);
  display.print("GTL");
  display.display();
  delay(3000);
  display.clearDisplay();
 }

quinta-feira, 18 de dezembro de 2025

Feliz Natal próspero 2026.

Festas de fim de ano, confraternização, afeto, amor, amizade sincera e respeito, com ano 2025 findando, vamos para mais um recomeço. Recomeço de tudo que deixamos no velho ano, e tentar ser muito melhor, aprender a cada dia uma tarefa algo novo, procurar ser persistente no que vai aprender, compartilhar novas experiências que ajude a desenvolver o ser humano. Desejo a todos que neste ano e demais que se aproxima sejamos mais amáveis, solidários, amantes do que é justo e da honestidade, compreensivos, educados, generosos, e bondosos, e que o senhor Deus todo poderoso e seu amado filho Jesus Cristo venha a fazer parte de nossas vidas e esteja em nossos corações por toda a nossa existência. "Darei a vocês um coração novo e porei um espírito novo em vocês; tirarei de vocês o coração de pedra e lhes darei um coração de carne". (Ezequiel 36:26).
Muito obrigado a todos seguidores e quem acessam o nosso blog e canal no YouTube.
Desejo-lhes boas festas, feliz Natal e próspero ano de 2026 cheio de muitas realizações e conquistas.

domingo, 30 de novembro de 2025

Sketch DDS VFO si5351 Arduino 10Khz a 120Mhz

Olá pessoal, vocês que acompanha nosso blog, muito obrigado pela visita. Publico este mês um esboço ou sketch, em vídeo projeto DDS VFO autor Sr. Júlio Cesar do ano de 2020. O projeto é simples porem muito bom com variação de 10kHz a 120MHz, original STEP de 1Hz, 10Hz, 1kHz, 5kHz, 10kHz e 1MHz, ajuste de FI offset + ou  -  Super-heteródinos  conversão Direta. Display Oled SSD 1306 128x64, e placa ou IC si5351. Fiz simples modificações, algumas que o próprio Júlio Cesar responde em seus comentários em sua página. Porem algumas modificações estão muito difíceis de serem realizadas pelo meu pobre conhecimento em código C++. Tais como adicionar TX RX com FI e frequência display, adicionar AM, LSB, USB.  Na verdade eu não tinha programado este sketch para publicar este mês, mas sim um sketch em vídeo, com RIT, memória, s-meter, menu calibração. Tive que copiar o sketch do vídeo, ao gravar tive a surpresa dos caracteres e outros comandos não funcionavam corretamente. Revisei varias vezes o vídeo e sketch que copiei, adicionei e retirei bibliotecas,  não tinha nada errado, compilava ok, mas ao gravar fiquei tentando consertar o programa, mas perdi muito tempo e não conseguir. Infelizmente pessoal tem pessoas que brincam com o que publicam. Eu não e jamais vou divulgar o nome do autor vídeo e nem o sketch que copiei. Não estou aqui para denunciar e nem jogar pedras em nenhum colega que fazem isso, só fico triste porque acho que temos que ter uma ética no nosso hobby como em todos os segmentos da sociedade. Mas voltando ao sketch do Sr. Júlio Cesar, quero parabeniza-lo pelos brilhantes projetos que ele fez e faz em seus vídeos e publicações de programas Arduino DDS VFO código livre e que funciona de verdade, alias ele responde e ensina em comentários em suas páginas e vídeos. Parabéns Júlio Cesar fico feliz e sei que todos que tem o hobby de radioamador ou PX nas veias ficam felizes com sua contribuição e colaboração. Bem eu fiz algumas modificações mesmo sem tempo, mas não acertei em alguns que estão desabilitados como comentário no esboço, fiz como muitos colegas já fizeram deste e outros programas esboços,  coloquei o STEP para 100Khz saindo o de 5Khz, acrescentei mais os de 1.5 Mhz e 2 Mhz sugeridos pelo autor em comentários, coloquei o nome todo STEP sem pontos, pois com pontos no final do nome STEP quando estivesse selecionado em 1.5 Mhz o M ficava atrapalhando o nome RX e TX, mas vocês poderão deixar original TS ou ST, ou desabilitar este comando de 1.5MHz no STEP, e se for colocar os modos AM, LSB, USB no local onde estão IF KHz será usado para mostrar, por isso deixe só a letra K como original, se for colocar os modos AM, LSB, USB. Habilitei a saída do si5351 CLK1 para FI BFO, o que ficou melhor para alguns transceptores AM, Modifiquei frequência  para 220 Mhz, o que funcionou perfeitamente. Os modos AM, LSB, USB, não funcionaram bem, pois eu teria de modificar muito o programa em void setup()  si5351. Em void loop() void tunegen(), o TX e RX também não funcionou corretamente, pois em TX deveria ter frequência de VFO + ou - FI, e em RX a frequência do display, mas não deu certo, também não tentei muito nesta função, a que eu perdi mais tempo foi para modos AM, LSB, USB em teclas diferentes, assim ele serviria para o Cobra 148 GTL. Se alguém que é programador ou sabe, e quiser mandar um comentário ou e-mail para mim como fazer esta modificação e onde estou errando, por favor eu agradeço muito e vou divulgar o seu nome e modificação, como fiz na publicação do mês passado onde eu me passei e errei em não observar o número 3 em long last_freq[9]; como tinha nome freq[3], eu achei que fosse os modos AM, LSB, USB, mesmo depois de modificar todas ás 9 bandas e com a EEPROM dando erro e ficando instável, eu desabilitei logo a EEPROM sem procurar o problema. Pois a minha intenção era partir para ter os modos AM, LSB, USB, em teclas diferentes, e não conseguir como a publicação do mês 08. Foi o colega Sr. Evaldo quem mandou um comentário informando do meu erro, que logo retifiquei todos os sketches da publicação. Neste programa sketch abaixo publicado, notei uma anormalidade que notei ao girar o Encoder até 220 Mhz, ao retornar novamente para KHz o VFO ficou instável ou desencontrados sem função, tive que resetar ou reset para que ele ficasse bom novamente. Não fiz este teste no original, por isso não sei se é só no que modifiquei. Vejam ás fotos como ficou o projeto modificado parcialmente do Sr. Júlio Cesar. Esta publicação poderá ser alterada atualizada posteriormente. Muito obrigado a todos.
Assistam ao vídeo como ficou o DDS VFO com ás modificações.
Como referência canal 1 PX CB 11 metros.
FI está para o Cobra 148 GTL.
Frequência IF em CLK1.
Frequência CLK0 VFO.
Esquema elétrico DDS VFO 10KHz 120 MHz Júlio Cesar.
Abaixo o programa algoritmo esboço que fiz ás simples modificações para nossos projetos RTX em AM ou FM. Não é aconselhável, mas vocês poderão colocar no Cobra 148 GTL ou outros equipamentos SSB, pois ele tem STEP de 1Hz e 10Hz o que dá para clarificar voz mesmo fora de frequência no display em SSB, Vou ver se dá para colocar um potenciômetro como fine ajuste os Hz, assim ficaria melhor.
Selecione e copie todo programa esboço, abra o seu Arduino IDE na área de trabalho, limpe tudo e cole, vá em Arquivo depois Salvar como, renomeei o esboço e escolha a pasta a ser salvo.
1- Esboço abaixo copie e cole no Arduino IDE.
/********************************************************************************************************
  10kHz to 120MHz VFO / RF Generator with Si5351 and Arduino Nano, with Intermediate Frequency (IF)
  offset (+ or -). See the schematics for wiring details. By J. CesarSound - ver 1.0 - Dec/2020.
  MODDED BY 2e0myn september 2021
  Sketch modified by Waldir Cardoso 11/20/2025.
  Blog:https://projetosetransceptores.blogspot.com/2025/10/dds-vfo-am-ssb-hf-vhf-9-bandas-oled.html
*********************************************************************************************************/
//---------------------------------------------------
//Modified functions.
//STEP 100Khz, 1.5Mhz, 2Mhz.
//Frequency up to 220 MHz
//CLK1 FI BFO
//--------------------------------------------------

//Libraries
#include <Wire.h>                 //IDE Standard
#include <Rotary.h>               //Ben Buxton https://github.com/brianlow/Rotary
#include <si5351.h>               //Etherkit https://github.com/etherkit/Si5351Arduino
#include <Adafruit_GFX.h>         //Adafruit GFX https://github.com/adafruit/Adafruit-GFX-Library
#include <Adafruit_SSD1306.h>     //Adafruit SSD1306 https://github.com/adafruit/Adafruit_SSD1306

//User preferences
//------------------------------------------------------------------------------------------------------------
#define IF         7800      //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.
#define FREQ_INIT  26965000  //Enter your initial frequency at startup, ex: 7000000 = 7MHz, 10000000 = 10MHz, 840000 = 840kHz.
#define XT_CAL_F   18000     //Si5351 calibration factor, adjust to get exatcly 10MHz. Increasing this value will decreases the frequency and vice versa.
#define tunestep    A0        //Change the pin used by encoder push button if you want.
//#define am        6        //Change the pin used by mode switch if you want.
//#define usb       7        //Change the pin used by mode switch if you want.
//#define lsb       8        //Change the pin used by mode switch if you want.
#define tx_rx       9        //The pin used by RX / TX selector switch, RX = switch open, TX = switch closed to GND. When in TX
//------------------------------------------------------------------------------------------------------------

Rotary r = Rotary(2, 3);
Adafruit_SSD1306 display = Adafruit_SSD1306(128, 64, &Wire);
Si5351 si5351;
/*
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 = "";
*/
unsigned long freq = FREQ_INIT;
unsigned long freqold, fstep;
long interfreq = IF;
long cal = XT_CAL_F;
unsigned long long pll_freq = 90000000000ULL;
byte encoder = 1;
byte stp;
unsigned int period = 100;   //millis display active
unsigned long time_now = 0;  //millis display active
bool sts = 1; // was 0

ISR(PCINT2_vect) {
  char result = r.process();
  if (result == DIR_CW) set_frequency(1);
  else if (result == DIR_CCW) set_frequency(-1);
}

void set_frequency(short dir) {
  if (encoder == 1) {             //Up/Down frequency
    if (dir == 1) freq = freq + fstep;
    if (freq >= 220000000) freq = 220000000;
    if (dir == -1) freq = freq - fstep;
    if (fstep == 1000000 && freq <= 1000000) freq = 1000000;
    else if (freq < 10000) freq = 10000;
  }
}

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(tunestep, INPUT_PULLUP);
  //pinMode(am, INPUT_PULLUP);
  //pinMode(usb, INPUT_PULLUP);
  //pinMode(lsb, INPUT_PULLUP);
  pinMode(tx_rx, INPUT_PULLUP);
 
  statup_text();

  si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0, cal);
  si5351.set_pll(SI5351_PLL_FIXED, SI5351_PLLA);
  si5351.output_enable(SI5351_CLK0, 1);                  //1 - Enable / 0 - Disable CLK
  si5351.output_enable(SI5351_CLK1, 1);
  
  si5351.drive_strength(SI5351_CLK0, SI5351_DRIVE_8MA);  //Output current 2MA, 4MA, 6MA or 8MA
  si5351.drive_strength(SI5351_CLK1, SI5351_DRIVE_8MA);  //Output current 2MA, 4MA, 6MA or 8MA
 
  PCICR |= (1 << PCIE2);
  PCMSK2 |= (1 << PCINT18) | (1 << PCINT19);
  sei();

  stp = 4;    // stp = 4 STEP 10KHz
  setstep();
  layout();
  displayfreq();
 
}

void loop() {
        
  if (freqold != freq) {
    time_now = millis();
    tunegen();
    freqold = freq;
  }

  if (digitalRead(tunestep) == LOW) {
    time_now = (millis() + 300);
    setstep();
    delay(300);
  }
/* 
  if (digitalRead(am) == LOW) {
    time_now = (millis() + 300);            
  }
  if (digitalRead(usb) == LOW)  {
    time_now = (millis() + 300);        
  }
  if (digitalRead(lsb) == LOW) {
    time_now = (millis() + 300);        
  }
*/ 
  if (digitalRead(tx_rx) == LOW) {
    time_now = (millis() + 300);    
    sts = 0; //was 1
    
  }
  else sts = 1; //was 0
  
  if ((time_now + period) > millis()) {
    displayfreq();
    layout();
  }  
}

void tunegen() {
 
  si5351.set_freq_manual((freq + (interfreq * 1000ULL)) * 100ULL, pll_freq, SI5351_CLK0); 
  si5351.set_freq((IF * 1000ULL) * 100ULL, SI5351_CLK1);  
 }
 
void displayfreq() {
  unsigned int m = freq / 1000000;
  unsigned int k = (freq % 1000000) / 1000;
  unsigned int h = (freq % 1000) / 1;

  display.clearDisplay();
  display.setTextSize(2);

  char buffer[15] = "";
  if (m < 1) {
    display.setCursor(41, 1); sprintf(buffer, "%003d.%003d", k, h);
  }
  else if (m < 100) {
    display.setCursor(5, 1); sprintf(buffer, "%2d.%003d.%003d", m, k, h);
  }
  else if (m >= 100) {
    unsigned int h = (freq % 1000) / 10;
    display.setCursor(5, 1); sprintf(buffer, "%2d.%003d.%02d", m, k, h);
  }
  display.print(buffer);
}

// Button press changes the frequency change step for 10 Hz steps 

void setstep() {
  switch (stp) {
  case 1:
  stp = 2;
  fstep = 1;
  break;
  case 2:
  stp = 3;
  fstep = 10;
  break;
  case 3:
  stp = 4;
  fstep = 1000;
  break;
  case 4:
  stp = 5;
  fstep = 10000;
  break;
  case 5:
  stp = 6;
  fstep = 100000;
  break;
  case 6:
  stp = 7;
  fstep = 1000000;
  break;
  case 7:
  stp = 8;
  fstep = 1500000; 
  break;
  case 8:
  stp = 1;
  fstep = 2000000; 
  break;
  }
}
void layout() {
  display.setTextColor(WHITE);
  display.drawLine(0, 20, 100, 20, WHITE);
  display.drawLine(0, 20, 100, 20, WHITE);
  display.drawLine(105, 30, 127, 30, WHITE);
  display.drawLine(100, 20, 105, 30, WHITE);
  display.setTextSize(2);
  //display.setCursor(90, 48);
  //if (digitalRead(am) == LOW)
  //display.print("AM");
  //if (digitalRead(usb) == LOW)
  //display.print("USB");
  //if (digitalRead(lsb) == LOW)
  //display.print("LSB");
  display.setCursor(2, 25);
  display.print("STEP");
  if (stp == 2) display.print("1Hz");
  if (stp == 3) display.print("10Hz");
  if (stp == 4) display.print("1k");
  if (stp == 5) display.print("10k");
  if (stp == 6) display.print("100k");
  if (stp == 7) display.print("1M");
  if (stp == 8) display.print("1.5M");
  if (stp == 1) display.print("2M");

  display.setCursor(2, 48);
  display.print("IF:");
  display.print(interfreq);
  display.print("KHz");
  
  display.setTextSize(1);
  display.setCursor(110, 33);
  if (digitalRead(tx_rx) == LOW)display.print("TX");
  if (digitalRead(tx_rx) == HIGH)display.print("RX");
  display.setCursor(110, 20);
  if (freq < 1000000) display.print("kHz");
  if (freq >= 1000000) display.print("MHz");
  
  display.display();

void statup_text() {
  display.setTextSize(2);
  display.setCursor(9, 10);
  display.print("Cobra 148");
  display.setCursor(39, 45);
  display.print("GTL");
  display.display();
  delay(3000);
  display.clearDisplay();
}

sexta-feira, 31 de outubro de 2025

DDS VFO AM SSB HF VHF 9 Bandas Oled JZ13NGF.

Olá pessoal. Postei em Fevereiro 2025, que comecei a fazer minhas experiências com Arduíno DDS VFO, sempre no interesse de incentivar a todos os iniciantes que querem montar um VFO digital em suas montagens ou seus transceptores AM, FM, SSB, mesmo o Cobra 148 GTL. Já fiz várias experiências modificações em sketch e algumas deram certo. 
Não sei nada, estou aprendendo com vocês. Mas o que sei disponibilizo para todos em vídeo e blog. Hoje publico algumas simples modificações feita em um esboço ou sketch, do amigo JZ13NGF canal YouTube SPR Elektro da Indonésia. Meus agradecimentos ao Sr. JZ13NGF por publicar e disponibilizar os arquivos, sketch e bibliotecas. Eu recebi o link do vídeo DDS HF 3 Band Oled display, enviado por um amigo no final do mês Outubro 2025. Já tinha escolhido um outro sketch esboço para modificar e publicar com vídeo, mas este do JZ13NGF me deixou bem entusiasmado para fazer ás modificações e deixa-lo como eu queria para o Cobra 148 GTL, pois tem S/meter, Menu, e 3 bandas HF. Então primeiro eu gravo o original para ver quais os recursos que tem, e para ver no Frequencímetro como ele se comporta para nossos projetos RTX AM, FM, SSB. Em CLK0 VFO não tem variação de AM, LSB, USB, no Menu em SET TX OFFSET, ou no programa podemos digitar um valor que queremos que o VFO transmita acima da frequência, Ex: O CLK0 VFO digitando a FI de 7800, ou no programa 7800000UL, no display e 26.965 Mhz, fica na frequência de 34.765 Mhz, canal 1 CB, isso ocorre em RX sem variação de AM, LSB, USB, em TX, temos uma única variação em AM, LSB, USB nesta frequência de 34.765 Mhz em Hz ou Khz, podemos definir Menu SET TX OFFSET ou no programa +1500 ou -1500, assim em TX em todas ás bandas terás mais ou menos 1500 acima ou abaixo. Assim não serviria para o Cobra 148 GTL mas em outros transceptores comercial ou nossos feito em casa. Não esqueçam que o modulo si5351 não tem potencia de RF suficiente para certos circuitos osciladores, por isso melhor adicionar um amplificador buffer RF, nas saídas principalmente VFO CLK0, e em CLK1 também é bom adicionar um buffer RF. 

Ás modificações que fiz neste esboço ou sketch do original JZ13NGF.
1- 9 bandas iniciais de 3.5 Mhz a 50 Mhz HF VHF, para comunicações no Brasil
2-  STEP de 1Hz, 10Hz, 100Hz, 1Khz, 10Khz, 100Khz, 1Mhz. 
3- TX, RX em AM CLK1 BFO SSB FI.
4- Inicializa STEP em 10 Khz.
5- S/Meter com escala completa numéricas S 1, 3, 5, 7, 9,  +  30d dentro do espectro. 
6-Atualização da EEPROM 09/11/2025. sketches abaixo com memória graças ao amigo Evaldo.
7- Rotary Encoder incremental típico.
8-Modo Menu tecla MODE pressionada por 5 segundos.

CLK1 BFO, tem ás variações AM, LSB, USB, digitando no programa #define BFO_LSB_OFFSET_DEFAULT -1500L // LSB #define BFO_USB_OFFSET_DEFAULT 1500L // USB. #define IF_FREQUENCY_DEFAULT 7800000UL . ou em Menu SET IF. para pequenas correções. Assim servirá para o CARRIER do Cobra 148 GTL. Outra complicação para o nosso Cobra 148 GTL é que uma única tecla chave SET MODE A2 é usada para selecionar AM, LSB, USB, e pressionada por alguns segundos entramos no modo Menu. Então partir para ás modificações, notei logo que ele não estava aceitando ás FI, BAND, STEP e quando gravava o novo esboço, a FI e demais configurações retornavam ao original do autor que foi gravado primeiro, ou ficava desencontradas. Não dava para eu fazer nada se eu não desabilitasse a EEPROM.h, e assim continuei ás modificações. Graças ao amigo Evaldo que mandou um comentário hoje 09/11/2025, mostrando a linha de comando da EEPRON que eu errei, deixei com número 3 e não atualizei para o número 9, já que eu fiz em todas modificações para 9 bandas. então vocês poderão selecionar todo esboço abaixo já atualizado, abrir seu programa Arduino IDE na área de trabalho, e limpar tudo depois colar, assim já estará com memória e não precisará fazer nada mais, há não ser se quiser desabilitar a memória EEPROM façam isso se desejarem.
Se desejarem desabilitar a EEPROM coloque ás barras duplas // das linhas, 27 //#include <EEPROM.h> linha 483 //EEPROM.get(0, data); linha 522 //EEPROM.put(0, data);
Atualização da EEPROM 09/11/2025. sketches abaixo com memória EEPROM graças ao amigo Evaldo.
Abaixo esquema de ligações do Arduino Nano e componentes.
Ás fotos abaixo refere-se ao segundo esboço com FI do Cobra 148 GTL 7.800 Khz
Vejam o display 128 x 64 SSD1306 vai até 160Mhz nesta banda de 6 metros.
Mas ficou instável depois de 99 Mhz, creio tenha sido a entrada do Frequencímetro.
Assistam ao vídeo click abaixo.
Vejam a foto do display e Frequencímetro em CLK0 AM 50 Mhz 6 metros com FI de 7.800 Khz.
Agora fica melhor CLK0 AM 50 Mhz, FI 7.800 Khz.
Agora abaixo está em CLK0 LSB com FI de 7.800 Khz.
Vejam no display modo LSB, CLK0 foto acima no Frequencímetro.
Agora modo USB no display CLK0 e foto abaixo no Frequencímetro.
Vejam está em USB CLK0.
Vejam agora 11 metros canal 1 PX CB CLK0 AM.
Vejam frequência CLK0 AM.
Frequência CLK0 LSB 11 metros canal 1 PX CB.
Frequência CLK0 USB 11 metros canal 1 PX CB.
Frequência CLK1 BFO AM.
Frequência USB CLK1 BFO.
Frequência LSB CLK1 BFO.
 
Este esboço abaixo vocês terão em CLK0, frequência do display mais FI fixa de 7.800Khz, ou seja não haverá variação de frequência mesmo nos modos, AM, LSB, USB, terá sempre a mesma frequência do AM RX fixa. Mesmo em MENU AM SET, não mudará de frequência em CLK0, isso por causa da linha 313 está com comando if_frequency assim. 313 uint64_t vfo_output_freq = (uint64_t)vfo_with_offset + if_frequency; //+ if_frequency only VFO frequency. Mas em MENU SET TX OFFSET, terá uma variação em TX da qual for digitada no MENU. Ás definições no esboço já estão para (0) zero. #define AM_RX_OFFSET_DEFAULT 0L e #define TX_OFFSET_FREQ_DEFAULT 0L, onde vocês poderão alterar o que quiserem se desejarem. Em CLK1 vocês terão os modos AM 7.800Khz, LSB 7.798.5Khz, USB 7.801.5Khz. Servindo assim para qualquer transceptor SSB como Naja, Minhoca, Ararinha, Transceptor SSB 40M, Lagunero, Curió, entre muitos transceptores AM, SSB publicado aqui no blog ou na WEB.
Atualização da EEPROM 09/11/2025. sketches com memória graças ao amigo Evaldo.
Pessoal eu havia falhado em observar que na linha 75 em long last_freq[3]; o número 3 é para ás 3 bandas, se eu coloquei para 9 bandas tem que ser o numero 9 também como em todas ás linhas de modificações para 9 bandas, então a linha de comando 75 tem que ser esta: long last_freq[9]; como já estão corrigidos nos sketches abaixo. Assim a EEPRON foi habilitada. Fiz esta nova gravação que ficou com memória no MENU e tudo ok, ficou como no sketch original do amigo JZ13NGF. Quem me mandou um comentário hoje e viu o meu erro foi o amigo e seguidor do blog Sr. Evaldo, muito obrigado Edvaldo pela sua ajuda e observação. Valeu de verdade meu amigo.
1- Esboço abaixo copie e cole no Arduino IDE.
/*
 * DDS VFO Transceiver - 3Band
 * =========================================================================
 * Fitur :
 * - Tampilan Callsign saat startup
 * - Band: 80m, 40m, 11m
 * - Menu setting : IF (step 10Hz), LSB Offset, USB Offset, AM Offset, Kalibrasi Kristal, TX Offset
 * - Gain S-Meter disesuaikan untuk 0.7V = Full Scale
 * - Semua setting tersimpan di EEPROM
 * - Tampilan UI dengan garis pemisah
 * - Auto-save setelah 5 detik idle
 * =========================================================================
 */
//---------------------------------------------------------------------------------
//  Function
// 1.CLK0 RX VFO 34 MHZ AM,LSB,USB, MODE Cobra 148 GTL OTHER TRANSCEIVERS
// 2.CLK0 TX FREQUENCY VFO - IF = FREQUENCY DISPLAY AM,LSB,USB,OFFSET MODE
// 3.CLK1 BFO RX,TX FI 7800KHZ AM,LSB,USB,OFFSET MODE
// 4.BAND (9) 80M,40M,30M,20M,15M,12M,11M,10M,6M, HF,VHF
// 5.STEP 10Hz,100Hz,1KHz,10KHz,100KHz,1MHz
// 6.MENU CALIBRATION VFO,IF AM,LSB,USB,OFFSET MODE
// 7.S-METER FULL SCALE S 1 3 5 7 9 + 30d
// 8.EEPROM INCLUDED FOR MEMORY
// Sketch modified by Waldir October 2025. Blog: https://projetosetransceptores.blogspot.com/
//////////////////////////////////////////////////////////////////////    
// ================== 1. PUSTAKA (LIBRARIES) ==================
#include <EEPROM.h>
#include <Wire.h>
#include <si5351.h>
#include <RotaryEncoder.h>
#include <U8g2lib.h>

// ================== 2. DEFINISI & PENGATURAN INTI ==================
// ----- -----
#define CALLSIGN "JZ13NGF"

#define IF_FREQUENCY_DEFAULT    7800000UL  // Enter the transceiver IF 7800000,9998500,10000000,10240000,10700000
#define BFO_LSB_OFFSET_DEFAULT     -1500L  // LSB
#define BFO_USB_OFFSET_DEFAULT      1500L  // USB
#define AM_RX_OFFSET_DEFAULT           0L  // Initialize to zero for Menu correction
#define SI5351_CORRECTION_DEFAULT  18000L  // Si5351 crystal frequency correction
#define TX_OFFSET_FREQ_DEFAULT         0L  // Adjust IF 7800000L or -7800000L TX VFO frequency display 


// Definisi Pin
#define PIN_ENCODER_CLK       2
#define PIN_ENCODER_DT        3
#define PIN_ENCODER_SW_BAND   4
#define PIN_STEP_BUTTON       A0
#define PIN_TX_BUTTON         A1
#define PIN_SET_BUTTON        A2
#define PIN_S_METER_IN        A3
#define PIN_PTT_OUT           6

// Pengaturan S-Meter
#define S_METER_MAX_ADC       143

// Pengaturan OLED
U8G2_SSD1306_128X64_NONAME_1_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);

// Deklarasi Objek Lainnya
Si5351 si5351;
RotaryEncoder encoder(PIN_ENCODER_DT, PIN_ENCODER_CLK);

// Variabel & Konstanta Program
long bfo_frequency;
const char* mode_names[] = {"LSB","USB","AM"};
const long step_values[] = {10,100,1000,10000,100000,1000000};
const char* step_display_names[] = {"10Hz","100Hz","1KHz","10KHz","100KHz","1MHz"};
byte current_step_index = 3;

// Struktur EEPROM 
struct EepromData {
  uint32_t magic_key;
  long last_freq[9];
  int last_band;
  int last_mode;
  long if_frequency;
  long tx_offset_freq;
  long bfo_lsb_offset; 
  long bfo_usb_offset; 
  long am_rx_offset;
  long si5351_correction;
};

// Definisi Band
const char* band_names[] = {"80M","40M","30M","20M","15M","12M","11M","10M","6M"};
const long band_defaults[] = {3500000,7000000,10100000,14000000,21000000,24890000,26965000,28000000,50000000 };
long band_frequencies[9];

// ================== 3. VARIABEL GLOBAL ==================
long current_frequency;
int current_band = 0;
int current_mode = 1;
bool is_tx = false;
bool force_display_update = true;
long last_encoder_pos = 0;
// Menu setting sekarang ada 6 item
int setting_menu_state = 0; // 0:Normal, 1:IF, 2:LSB, 3:USB, 4:AM, 5:Cal, 6:TX

// Variabel Pengaturan
long if_frequency;
long tx_offset_freq;
long bfo_lsb_offset; 
long bfo_usb_offset; 
long am_rx_offset;
long si5351_correction_val;

// Tombol & Durasi
unsigned long set_button_press_start_time = 0;
const long long_press_duration = 1000;

// Variabel untuk optimasi display
long last_displayed_freq = 0;
bool last_tx_state = false;
int last_bar_level = -1;
long last_if_frequency = 0;
long last_tx_offset_freq = 0;
long last_bfo_lsb_offset = 0;
long last_bfo_usb_offset = 0;
long last_am_rx_offset = 0;
long last_si5351_correction = 0;

// Variabel untuk state change detection
bool band_button_active = false;
bool step_button_active = false;

// Variabel untuk Auto-Save (setelah idle 5 detik)
unsigned long last_activity_time = 0;
bool settings_changed = false;
const unsigned long auto_save_delay = 5000; // 5 detik dalam milidetik

// ===================================================================
// FUNGSI INTERRUPT UNTUK ENCODER
// ===================================================================
void isrEncoder() {
  encoder.tick();
}
// ===================================================================
// FUNGSI SETUP
// ===================================================================
void setup() {
  pinMode(PIN_ENCODER_SW_BAND, INPUT_PULLUP);
  pinMode(PIN_STEP_BUTTON, INPUT_PULLUP);
  pinMode(PIN_TX_BUTTON, INPUT_PULLUP);
  pinMode(PIN_SET_BUTTON, INPUT_PULLUP);
  pinMode(PIN_PTT_OUT, OUTPUT);
  digitalWrite(PIN_PTT_OUT, LOW);

  pinMode(PIN_ENCODER_CLK, INPUT_PULLUP);
  pinMode(PIN_ENCODER_DT, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(PIN_ENCODER_CLK), isrEncoder, CHANGE);
  attachInterrupt(digitalPinToInterrupt(PIN_ENCODER_DT), isrEncoder, CHANGE);
  
  u8g2.begin();
  
  // =================== KODE STARTUP SCREEN  ===================
  u8g2.firstPage();
  do {
    // Tampilkan Callsign dengan font besar di tengah
    u8g2.setFont(u8g2_font_ncenB14_tr);
    u8g2.drawStr((128 - u8g2.getStrWidth(CALLSIGN)) / 2, 30, CALLSIGN);
    
    // Tampilkan nama perangkat di bawahnya
    u8g2.setFont(u8g2_font_6x10_tf);
    const char* subtitle = "DDS VFO 9BAND HF VHF";
    u8g2.drawStr((128 - u8g2.getStrWidth(subtitle)) / 2, 50, subtitle);
  } while (u8g2.nextPage());
  
  delay(2500); // Tahan tampilan startup selama 2.5 detik
  // =================== AKHIR KODE STARTUP  ====================
  
  load_settings(); 

  if (!si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0, si5351_correction_val)) {
     si5351.drive_strength(SI5351_CLK0,SI5351_DRIVE_6MA);//you can set this to 2MA, 4MA, 6MA or 8MA 
     si5351.drive_strength(SI5351_CLK1,SI5351_DRIVE_6MA);//you can set this to 2MA, 4MA, 6MA or 8MA 
    u8g2.firstPage();
    do {
      u8g2.setFont(u8g2_font_6x10_tf);
      u8g2.drawStr(10, 35, "Si5351 GAGAL!");
    } while (u8g2.nextPage());
    while (1);
  }

  set_frequency(current_frequency);
}

// ===================================================================
// FUNGSI LOOP
// ===================================================================
void loop() {
  check_encoder();
  check_buttons();
  check_auto_save(); // Panggil fungsi auto-save
  update_display();
}

// ===================================================================
// FUNGSI-FUNGSI UTAMA
// ===================================================================
void check_encoder() {
  long new_pos = encoder.getPosition();
  if (last_encoder_pos != new_pos) {
    long change = new_pos - last_encoder_pos;
    
    // Tandai ada aktivitas dan perubahan yang perlu disimpan
    last_activity_time = millis();
    settings_changed = true;

    switch (setting_menu_state) {
      case 1: if_frequency += (change * 10); break;
      case 2: bfo_lsb_offset += (change * 10); break;
      case 3: bfo_usb_offset += (change * 10); break;
      case 4: am_rx_offset += (change * 10); break;
      case 5: // Menu Kalibrasi
        si5351_correction_val += (change * 100);
        si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0, si5351_correction_val);
        si5351.drive_strength(SI5351_CLK0,SI5351_DRIVE_6MA);//you can set this to 2MA, 4MA, 6MA or 8MA 
        si5351.drive_strength(SI5351_CLK1,SI5351_DRIVE_6MA);//you can set this to 2MA, 4MA, 6MA or 8MA 
        set_frequency(current_frequency); 
        break;
      case 6: tx_offset_freq += (change * 10); break;
      default:
        current_frequency += (change * step_values[current_step_index]);
        if (current_frequency < 1000000) current_frequency = 1000000;
        if (current_frequency > 160000000) current_frequency = 160000000;
        break;
    }
    force_display_update = true;
    last_encoder_pos = new_pos;
  }
}

void check_buttons() {
  // Cek tombol TX
  if (digitalRead(PIN_TX_BUTTON) == LOW) {
    if (!is_tx) {
      last_activity_time = millis();
      start_tx();
    }
    return;
  } else {
    if (is_tx) stop_tx();
  }
  
  // Cek tombol Band
  if (digitalRead(PIN_ENCODER_SW_BAND) == LOW) {
    if (!band_button_active && setting_menu_state == 0) {
      band_button_active = true;  
      force_display_update = true;
      last_activity_time = millis();
      settings_changed = true;
      save_current_freq_to_band();
      current_band = (current_band + 1) % 9; // Band definitions.
      load_freq_from_band();
    }
  } else {
    band_button_active = false;
  }

  // Cek tombol Step
  if (digitalRead(PIN_STEP_BUTTON) == LOW) {
    if (!step_button_active && setting_menu_state == 0) {
      step_button_active = true;
      force_display_update = true;
      last_activity_time = millis();
      settings_changed = true;
      current_step_index = (current_step_index + 1) % 6; // STEP Definition
    }
  } else {
    step_button_active = false;
  }

  // Cek tombol Set
     
  if (digitalRead(PIN_SET_BUTTON) == LOW) {
    if (set_button_press_start_time == 0) {
      set_button_press_start_time = millis();
    }
  } else {
    if (set_button_press_start_time != 0) {
      unsigned long press_duration = millis() - set_button_press_start_time;
      last_activity_time = millis(); // Aktivitas terdeteksi saat tombol dilepas
      settings_changed = true;

      if (press_duration >= long_press_duration) {
        if (setting_menu_state > 0) { 
          setting_menu_state = 0; 
          save_settings(); // Langsung simpan saat keluar menu
          settings_changed = false; // Reset flag karena sudah disimpan
        } else { 
          setting_menu_state = 1; 
        }
      } else {
        if (setting_menu_state > 0) {
          setting_menu_state = (setting_menu_state % 6) + 1;
        } else {
          current_mode = (current_mode + 1) % 3;
        }
      }
      force_display_update = true;
      set_button_press_start_time = 0;
    }
  }
  
  set_frequency(current_frequency);  
}

void set_frequency(long vfo_freq) {
  update_bfo_frequencies();
  long vfo_with_offset = vfo_freq;
  if (is_tx) { vfo_with_offset += tx_offset_freq; } 
  
  uint64_t vfo_output_freq = (uint64_t)vfo_with_offset + if_frequency;  //+ if_frequency only VFO frequency
  si5351.set_freq(vfo_output_freq * 100, SI5351_CLK0);
     si5351.output_enable(SI5351_CLK0, 1);  // Enable CLK0  
  if (current_mode == 2 && is_tx) { // Mode AM saat TX, matikan BFO
     si5351.output_enable(SI5351_CLK1, 1); //Enable AM ​​TX CLK1     
  } else {
    si5351.set_freq(bfo_frequency * 100, SI5351_CLK1);
    si5351.output_enable(SI5351_CLK1, 1);
  }
}

void start_tx() {
  is_tx = true;
  digitalWrite(PIN_PTT_OUT, HIGH);
  force_display_update = true;
  set_frequency(current_frequency);
}

void stop_tx() {
  is_tx = false;
  digitalWrite(PIN_PTT_OUT, LOW);
  force_display_update = true;
  save_settings();
  set_frequency(current_frequency);
}

// ===================================================================
// FUNGSI DISPLAY
// ===================================================================
void draw_setting_screen(const __FlashStringHelper* title, long value) {
    u8g2.setFont(u8g2_font_6x10_tf);
    char title_buf[17];
    strcpy_P(title_buf, (const char*)title);
    u8g2.drawStr((128 - u8g2.getStrWidth(title_buf)) / 2, 15, title_buf);
    
    char val_buf[20];
    if (strcmp_P(title_buf, (const char*)F("SET IF")) == 0) {
      sprintf(val_buf, "%ld", value);
    } else {
      sprintf(val_buf, "%+ld", value);
    }

    u8g2.setFont(u8g2_font_ncenB14_tr); 
    u8g2.drawStr((128 - u8g2.getStrWidth(val_buf)) / 2, 52, val_buf);
}

void update_display() {
  // Cek apakah ada perubahan yang memaksa update layar penuh
  if (force_display_update || current_frequency != last_displayed_freq || is_tx != last_tx_state || 
      if_frequency != last_if_frequency || tx_offset_freq != last_tx_offset_freq ||
      bfo_lsb_offset != last_bfo_lsb_offset || bfo_usb_offset != last_bfo_usb_offset || 
      am_rx_offset != last_am_rx_offset || si5351_correction_val != last_si5351_correction) {
    // Biarkan redraw penuh terjadi
  } else {
    // Cek S-meter/Power meter saja
    int current_bar_level;
    if (is_tx) {
      current_bar_level = map(analogRead(PIN_S_METER_IN), 0, 1023, 0, 91);
    } else {
      int s_constrained = constrain(analogRead(PIN_S_METER_IN), 0, S_METER_MAX_ADC);
      current_bar_level = map(s_constrained, 0, S_METER_MAX_ADC, 0, 91);
    }
    
    if (current_bar_level != last_bar_level) {
      // Hanya update jika nilai bar berubah
    } else {
      return; // Tidak ada yang perlu diupdate, keluar dari fungsi
    }
  }

  u8g2.firstPage();
  do {
    switch (setting_menu_state) {
      case 1: draw_setting_screen(F("SET IF"), if_frequency); break;
      case 2: draw_setting_screen(F("SET LSB OFFSET"), bfo_lsb_offset); break;
      case 3: draw_setting_screen(F("SET USB OFFSET"), bfo_usb_offset); break;
      case 4: draw_setting_screen(F("SET AM OFFSET"), am_rx_offset); break;
      case 5: draw_setting_screen(F("SET CALIBRATION"), si5351_correction_val); break;
      case 6: draw_setting_screen(F("SET TX OFFSET"), tx_offset_freq); break;
      default: {
        char freq_buf[15];
        long mhz = current_frequency / 1000000;
        long khz = (current_frequency % 1000000) / 1000;
        long hz  = (current_frequency % 1000) / 10;
        sprintf(freq_buf, "%2ld.%03ld.%02ld", mhz, khz, hz);
        
        u8g2.setFont(u8g2_font_fub20_tn);
        u8g2.drawStr(0, 26, freq_buf);
        
        u8g2.setFont(u8g2_font_6x10_tf);

        // Garis horizontal pemisah #1
        u8g2.drawHLine(0, 30, 128);

        // Garis vertikal pemisah #1
        u8g2.drawVLine(43, 32, 12); 
        u8g2.drawVLine(86, 32, 12);

        // Teks Band, Mode, Step
        u8g2.setCursor(21 - u8g2.getStrWidth(band_names[current_band]) / 2, 42); 
        u8g2.print(band_names[current_band]);
        u8g2.setCursor(64 - u8g2.getStrWidth(mode_names[current_mode]) / 2, 42); 
        u8g2.print(mode_names[current_mode]);
        u8g2.setCursor(107 - u8g2.getStrWidth(step_display_names[current_step_index]) / 2, 42);
        u8g2.print(step_display_names[current_step_index]);
        
        // ======================================
        // Garis horizontal pemisah #2
        u8g2.drawHLine(0, 47, 128);

        // Garis vertikal pemisah #2
        u8g2.drawStr(32, 59, " 1 3 5 7 9  +30d ");
        u8g2.drawVLine(32, 49, 14);
        // ======================================

        u8g2.drawStr(5, 60, is_tx ? "TX S" : "RX S");
        u8g2.drawFrame(34, 50, 93, 11);

        int bar_level = 0;
        if (is_tx) {
          int pwr_value = analogRead(PIN_S_METER_IN);
          bar_level = map(pwr_value, 0, 1023, 0, 91);
        } else {
          int s_value = analogRead(PIN_S_METER_IN);
          int s_constrained = constrain(s_value, 0, S_METER_MAX_ADC);
          bar_level = map(s_constrained, 0, S_METER_MAX_ADC, 0, 91);
        }
        
        if (bar_level > 0) {
          u8g2.drawBox(35, 51, bar_level, 9);
        }
        last_bar_level = bar_level;
        break;
      }
    }
  } while (u8g2.nextPage());

  force_display_update = false;
  last_displayed_freq = current_frequency;
  last_tx_state = is_tx;
  last_if_frequency = if_frequency;
  last_tx_offset_freq = tx_offset_freq;
  last_bfo_lsb_offset = bfo_lsb_offset;
  last_bfo_usb_offset = bfo_usb_offset;
  last_am_rx_offset = am_rx_offset;
  last_si5351_correction = si5351_correction_val;
}

// ===================================================================
// FUNGSI-FUNGSI BANTU
// ===================================================================
// Fungsi untuk menyimpan otomatis setelah idle
void check_auto_save() {
  // Jika ada perubahan DAN sudah 5 detik tidak ada aktivitas
  if (settings_changed && (millis() - last_activity_time > auto_save_delay)) {
    save_settings();      // Simpan pengaturan
    settings_changed = false; // Reset flag agar tidak menyimpan terus-menerus
  }
}

void update_bfo_frequencies() {
  if (current_mode == 0) bfo_frequency = if_frequency + bfo_lsb_offset; // LSB
  else if (current_mode == 1) bfo_frequency = if_frequency + bfo_usb_offset; // USB
  else bfo_frequency = if_frequency + am_rx_offset; // AM
}

void load_settings() {
  EepromData data;
  EEPROM.get(0, data);
  if (data.magic_key != 123456789) {
    for (int i = 0; i < 9; i++) band_frequencies[i] = band_defaults[i]; // Band definitions.
    current_band = 0;
    current_mode = 1;
    if_frequency = IF_FREQUENCY_DEFAULT;
    tx_offset_freq = TX_OFFSET_FREQ_DEFAULT;
    bfo_lsb_offset = BFO_LSB_OFFSET_DEFAULT;
    bfo_usb_offset = BFO_USB_OFFSET_DEFAULT;
    am_rx_offset = AM_RX_OFFSET_DEFAULT;
    si5351_correction_val = SI5351_CORRECTION_DEFAULT;
    current_frequency = band_frequencies[current_band];
    save_settings();
  } else {
    
    for (int i = 0; i < 9; i++) band_frequencies[i] = data.last_freq[i]; // Band definitions.
    current_band = data.last_band;
    current_mode = data.last_mode;
    if_frequency = data.if_frequency;
    tx_offset_freq = data.tx_offset_freq;
    bfo_lsb_offset = data.bfo_lsb_offset;
    bfo_usb_offset = data.bfo_usb_offset;
    am_rx_offset = data.am_rx_offset;
    si5351_correction_val = data.si5351_correction;
    current_frequency = band_frequencies[current_band];
  }
  update_bfo_frequencies();
}

void save_settings() {
  EepromData data;
  data.magic_key = 123456789;
  band_frequencies[current_band] = current_frequency;
  for (int i = 0; i < 9; i++) data.last_freq[i] = band_frequencies[i];
  data.last_band = current_band;
  data.last_mode = current_mode;
  data.if_frequency = if_frequency;
  data.tx_offset_freq = tx_offset_freq;
  data.bfo_lsb_offset = bfo_lsb_offset;
  data.bfo_usb_offset = bfo_usb_offset;
  data.am_rx_offset = am_rx_offset;
  data.si5351_correction = si5351_correction_val;
  EEPROM.put(0, data);
}

void save_current_freq_to_band() {
  band_frequencies[current_band] = current_frequency;
}

void load_freq_from_band() {
  current_frequency = band_frequencies[current_band];
}

Não vou colocar muitas fotos de cada programa pois são 3 ao todo, vocês já sabem o que cada um faz e como se comportam.
Este esboço abaixo, vocês poderão colocarem no Cobra 148 GTL e outros transceptores comerciais ou feitos em casa. Vale lembrar que uma única tecla a cada toque inicial em USB, e cada toque uma banda e selecionada USB, AM, LSB, fica a critério de montar um simples circuito com o CI 4013, ou outros semelhantes, ou mudar o programa pois eu não conseguir colocar para cada banda uma tecla.
Notem que a linha 313 está com comando bfo_frequency assim. 313 uint64_t vfo_output_freq = (uint64_t)vfo_with_offset + bfo_frequency; // + bfo_frequency AM LSB USB shifted VFO.
Com este comando CLK0 ficará com modos AM, LSB, USB. em TX e RX.
2- Esboço abaixo copie e cole no Arduino IDE.
/* * DDS VFO Transceiver - 3Band * ========================================================================= * Fitur : * - Tampilan Callsign saat startup * - Band: 80m, 40m, 11m * - Menu setting : IF (step 10Hz), LSB Offset, USB Offset, AM Offset, Kalibrasi Kristal, TX Offset * - Gain S-Meter disesuaikan untuk 0.7V = Full Scale * - Semua setting tersimpan di EEPROM * - Tampilan UI dengan garis pemisah * - Auto-save setelah 5 detik idle * ========================================================================= */ //--------------------------------------------------------------------------------- // Function // 1.CLK0 RX VFO 34 MHZ AM,LSB,USB, MODE Cobra 148 GTL OTHER TRANSCEIVERS // 2.CLK0 TX FREQUENCY VFO - IF = FREQUENCY DISPLAY AM,LSB,USB,OFFSET MODE // 3.CLK1 BFO RX,TX FI 7800KHZ AM,LSB,USB,OFFSET MODE // 4.BAND (9) 80M,40M,30M,20M,15M,12M,11M,10M,6M, HF,VHF // 5.STEP 10Hz,100Hz,1KHz,10KHz,100KHz,1MHz // 6.MENU CALIBRATION VFO,IF AM,LSB,USB,OFFSET MODE // 7.S-METER FULL SCALE S 1 3 5 7 9 + 30d // 8.EEPROM INCLUDED FOR MEMORY // Sketch modified by Waldir October 2025. Blog: https://projetosetransceptores.blogspot.com/ ////////////////////////////////////////////////////////////////////// // ================== 1. PUSTAKA (LIBRARIES) ================== #include <EEPROM.h> #include <Wire.h> #include <si5351.h> #include <RotaryEncoder.h> #include <U8g2lib.h> // ================== 2. DEFINISI & PENGATURAN INTI ================== // ----- ----- #define CALLSIGN "JZ13NGF" // Author call prefix, change yours #define IF_FREQUENCY_DEFAULT 7800000UL // Enter the transceiver IF 7800000,9998500,10000000,10240000,10700000 #define BFO_LSB_OFFSET_DEFAULT -1500L // LSB #define BFO_USB_OFFSET_DEFAULT 1500L // USB #define AM_FREQUENCY_DEFAULT 0L // Initialize to zero for Menu correction #define SI5351_CORRECTION_DEFAULT 18000L // Si5351 crystal frequency correction #define TX_OFFSET_FREQ_DEFAULT 0L // Adjust IF 7800000L or -7800000L TX VFO frequency display // Definisi Pin #define PIN_ENCODER_CLK 2 #define PIN_ENCODER_DT 3 #define PIN_ENCODER_SW_BAND 4 #define PIN_STEP_BUTTON A0 #define PIN_TX_BUTTON A1 #define PIN_SET_BUTTON A2 #define PIN_S_METER_IN A3 #define PIN_PTT_OUT 6 // Pengaturan S-Meter. S-Meter Settings. #define S_METER_MAX_ADC 143 // Pengaturan OLED. OLED Settings U8G2_SSD1306_128X64_NONAME_1_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE); // Deklarasi Objek Lainnya. Object Declarations Si5351 si5351; RotaryEncoder encoder(PIN_ENCODER_DT, PIN_ENCODER_CLK); // Variabel & Konstanta Program. Program Variables & Constants long bfo_frequency; const char* mode_names[] = {"LSB", "USB", "AM "}; const long step_values[] = {10, 100, 1000, 10000, 100000, 1000000}; const char* step_display_names[] = {"10Hz" , "100Hz", "1KHz", "10KHz", "100KHz", "1MHz"}; byte current_step_index = 3; // Struktur EEPROM. EEPROM structure struct EepromData { uint32_t magic_key; long last_freq[9]; int last_band; int last_mode; long if_frequency; long tx_offset_freq; long bfo_lsb_offset; long bfo_usb_offset; long am_frequency; long si5351_correction; }; // Definisi Band const char* band_names[] = {"80M","40M","30M","20M","15M","12M","11M","10M","6M"}; const long band_defaults[]={3500000,7000000,10100000,14000000,21000000,24890000,26965000,28000000,50000000 }; long band_frequencies[9]; // ================== 3. VARIABEL GLOBAL ================== long current_frequency; int current_band = 0; int current_mode = 1; bool is_tx = false; bool force_display_update = true; long last_encoder_pos = 0; // Menu setting sekarang ada 6 item int setting_menu_state = 0; // 0:Normal, 1:IF, 2:LSB, 3:USB, 4:AM, 5:Cal, 6:TX // Variabel Pengaturan long if_frequency; long tx_offset_freq; long bfo_lsb_offset; long bfo_usb_offset; long am_frequency; long si5351_correction_val; // Tombol & Durasi unsigned long set_button_press_start_time = 0; const long long_press_duration = 1000; // Variabel untuk optimasi display long last_displayed_freq = 0; bool last_tx_state = false; int last_bar_level = -1; long last_if_frequency = 0; long last_tx_offset_freq = 0; long last_bfo_lsb_offset = 0; long last_bfo_usb_offset = 0; long last_am_frequency = 0; long last_si5351_correction = 0; // Variabel untuk state change detection bool band_button_active = false; bool step_button_active = false; // Variabel untuk Auto-Save (setelah idle 5 detik) unsigned long last_activity_time = 0; bool settings_changed = false; const unsigned long auto_save_delay = 5000; // 5 detik dalam milidetik // =================================================================== // FUNGSI INTERRUPT UNTUK ENCODER // =================================================================== void isrEncoder() { encoder.tick(); } // =================================================================== // FUNGSI SETUP // =================================================================== void setup() { pinMode(PIN_ENCODER_SW_BAND, INPUT_PULLUP); pinMode(PIN_STEP_BUTTON, INPUT_PULLUP); pinMode(PIN_TX_BUTTON, INPUT_PULLUP); pinMode(PIN_SET_BUTTON, INPUT_PULLUP); pinMode(PIN_PTT_OUT, OUTPUT); digitalWrite(PIN_PTT_OUT, LOW); pinMode(PIN_ENCODER_CLK, INPUT_PULLUP); pinMode(PIN_ENCODER_DT, INPUT_PULLUP); attachInterrupt(digitalPinToInterrupt(PIN_ENCODER_CLK), isrEncoder, CHANGE); attachInterrupt(digitalPinToInterrupt(PIN_ENCODER_DT), isrEncoder, CHANGE); u8g2.begin(); // =================== KODE STARTUP SCREEN =================== u8g2.firstPage(); do { // Tampilkan Callsign dengan font besar di tengah u8g2.setFont(u8g2_font_ncenB14_tr); u8g2.drawStr((128 - u8g2.getStrWidth(CALLSIGN)) / 2, 30, CALLSIGN); // Tampilkan nama perangkat di bawahnya u8g2.setFont(u8g2_font_6x10_tf); const char* subtitle = "DDS VFO 9BAND HF VHF"; u8g2.drawStr((128 - u8g2.getStrWidth(subtitle)) / 2, 50, subtitle); } while (u8g2.nextPage()); delay(2500); // Tahan tampilan startup selama 2.5 detik // ================ END OF INITIALIZATION CODE ==================== load_settings(); if (!si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0, si5351_correction_val)) { si5351.drive_strength(SI5351_CLK0,SI5351_DRIVE_6MA);//you can set this to 2MA, 4MA, 6MA or 8MA si5351.drive_strength(SI5351_CLK1,SI5351_DRIVE_6MA);//you can set this to 2MA, 4MA, 6MA or 8MA u8g2.firstPage(); do { u8g2.setFont(u8g2_font_6x10_tf); u8g2.drawStr(10, 35, "Si5351 GAGAL!"); } while (u8g2.nextPage()); while (1); } set_frequency(current_frequency); } // =================================================================== // FUNGSI LOOP // =================================================================== void loop() { check_encoder(); check_buttons(); check_auto_save(); //Panggil fungsi auto-save. Call the auto-save function update_display(); } // =================================================================== // FUNGSI-FUNGSI UTAMA // =================================================================== void check_encoder() { long new_pos = encoder.getPosition(); if (last_encoder_pos != new_pos) { long change = new_pos - last_encoder_pos; // Tandai ada aktivitas dan perubahan yang perlu disimpan last_activity_time = millis(); settings_changed = true; switch (setting_menu_state) { case 1: if_frequency += (change * 10); break; case 2: bfo_lsb_offset += (change * 10); break; case 3: bfo_usb_offset += (change * 10); break; case 4: am_frequency += (change * 10); break; case 5: // Menu Calibration si5351_correction_val += (change * 100); si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0, si5351_correction_val); si5351.drive_strength(SI5351_CLK0, SI5351_DRIVE_6MA); //you can set this to 2MA, 4MA, 6MA or 8MA si5351.drive_strength(SI5351_CLK1, SI5351_DRIVE_6MA); //you can set this to 2MA, 4MA, 6MA or 8MA set_frequency(current_frequency); break; case 6: tx_offset_freq += (change * 10); break; default: current_frequency += (change * step_values[current_step_index]); if (current_frequency < 1000000) current_frequency = 1000000; // Initial frequency if (current_frequency > 160000000) current_frequency = 160000000; // Final frequency break; } force_display_update = true; last_encoder_pos = new_pos; } } void check_buttons() { // Cek tombol TX if (digitalRead(PIN_TX_BUTTON) == LOW) { if (!is_tx) { last_activity_time = millis(); start_tx(); } return; } else { if (is_tx) stop_tx(); } // Cek tombol Band. Check the Band button if (digitalRead(PIN_ENCODER_SW_BAND) == LOW) { if (!band_button_active && setting_menu_state == 0) { band_button_active = true; force_display_update = true; last_activity_time = millis(); settings_changed = true; save_current_freq_to_band(); current_band = (current_band + 1) % 9; // Band definitions. load_freq_from_band(); } } else { band_button_active = false; } // Cek tombol Step if (digitalRead(PIN_STEP_BUTTON) == LOW) { if (!step_button_active && setting_menu_state == 0) { step_button_active = true; force_display_update = true; last_activity_time = millis(); settings_changed = true; current_step_index = (current_step_index + 1) % 6;// STEP Definition } } else { step_button_active = false; } // Cek tombol Set. Check the Set button if (digitalRead(PIN_SET_BUTTON) == LOW) { if (set_button_press_start_time == 0) { set_button_press_start_time = millis(); } } else { if (set_button_press_start_time != 0) { unsigned long press_duration = millis() - set_button_press_start_time; last_activity_time = millis(); // Aktivitas terdeteksi saat tombol dilepas settings_changed = true; if (press_duration >= long_press_duration) { if (setting_menu_state > 0) { setting_menu_state = 0; save_settings(); // Langsung simpan saat keluar menu settings_changed = false; // Reset flag karena sudah disimpan } else { setting_menu_state = 1; } } else { if (setting_menu_state > 0) { setting_menu_state = (setting_menu_state % 6) + 1; } else { current_mode = (current_mode + 1) % 3; } } force_display_update = true; set_button_press_start_time = 0; } } set_frequency(current_frequency); } void set_frequency (long vfo_freq) { update_bfo_frequencies(); long vfo_with_offset = vfo_freq; if (is_tx) { vfo_with_offset += tx_offset_freq; } uint64_t vfo_output_freq = (uint64_t)vfo_with_offset + bfo_frequency; // + bfo_frequency AM LSB USB shifted VFO si5351.set_freq(vfo_output_freq * 100, SI5351_CLK0); si5351.output_enable(SI5351_CLK0, 1); if (current_mode == 2 && is_tx) { // Mode AM saat TX, matikan BFO. AM mode when TX, turn off BFO. si5351.output_enable(SI5351_CLK1, 1); //Enable AM ​​TX CLK1 } else { si5351.set_freq(bfo_frequency * 100, SI5351_CLK1); si5351.output_enable(SI5351_CLK1, 1); } } void start_tx() { is_tx = true; digitalWrite(PIN_PTT_OUT, HIGH); force_display_update = true; set_frequency(current_frequency); } void stop_tx() { is_tx = false; digitalWrite(PIN_PTT_OUT, LOW); force_display_update = true; save_settings(); set_frequency(current_frequency); } // =================================================================== // FUNGSI DISPLAY // =================================================================== void draw_setting_screen(const __FlashStringHelper* title, long value) { u8g2.setFont(u8g2_font_6x10_tf); char title_buf[17]; strcpy_P(title_buf, (const char*)title); u8g2.drawStr((128 - u8g2.getStrWidth(title_buf)) / 2, 15, title_buf); char val_buf[20]; if (strcmp_P(title_buf, (const char*)F("SET IF")) == 0) { sprintf(val_buf, "%ld", value); } else { sprintf(val_buf, "%+ld", value); } u8g2.setFont(u8g2_font_ncenB14_tr); u8g2.drawStr((128 - u8g2.getStrWidth(val_buf)) / 2, 52, val_buf); } void update_display() { // Cek apakah ada perubahan yang memaksa update layar penuh if (force_display_update || current_frequency != last_displayed_freq || is_tx != last_tx_state || if_frequency != last_if_frequency || tx_offset_freq != last_tx_offset_freq || bfo_lsb_offset != last_bfo_lsb_offset || bfo_usb_offset != last_bfo_usb_offset || am_frequency != last_am_frequency || si5351_correction_val != last_si5351_correction) { // Biarkan redraw penuh terjadi } else { // Cek S-meter/Power meter saja int current_bar_level; if (is_tx) { current_bar_level = map(analogRead(PIN_S_METER_IN), 0, 1023, 0, 91); } else { int s_constrained = constrain(analogRead(PIN_S_METER_IN), 0, S_METER_MAX_ADC); current_bar_level = map(s_constrained, 0, S_METER_MAX_ADC, 0, 91); } if (current_bar_level != last_bar_level) { // Hanya update jika nilai bar berubah } else { return; // Tidak ada yang perlu diupdate, keluar dari fungsi } } u8g2.firstPage(); do { switch (setting_menu_state) { case 1: draw_setting_screen(F("SET IF"), if_frequency); break; case 2: draw_setting_screen(F("SET LSB OFFSET"), bfo_lsb_offset); break; case 3: draw_setting_screen(F("SET USB OFFSET"), bfo_usb_offset); break; case 4: draw_setting_screen(F("SET AM FREQUENCY"), am_frequency); break; case 5: draw_setting_screen(F("SET CALIBRATION"), si5351_correction_val); break; case 6: draw_setting_screen(F("SET TX OFFSET"), tx_offset_freq); break; default: { char freq_buf[15]; long mhz = current_frequency / 1000000; long khz = (current_frequency % 1000000) / 1000; long hz = (current_frequency % 1000) / 10; sprintf(freq_buf, "%2ld.%03ld.%02ld", mhz, khz, hz); u8g2.setFont(u8g2_font_fub20_tn); u8g2.drawStr(0, 26, freq_buf); u8g2.setFont(u8g2_font_6x10_tf); // Garis horizontal pemisah #1 // Linha divisória horizontal #1 u8g2.drawHLine(0, 30, 128); // Garis vertikal pemisah #1 // Linha divisória vertical #1 u8g2.drawVLine(43, 32, 12); u8g2.drawVLine(86, 32, 12); // Teks Band, Mode, Step // Banda Teks, Modo, Passo u8g2.setCursor(21 - u8g2.getStrWidth(band_names[current_band]) / 2, 42); u8g2.print(band_names[current_band]); u8g2.setCursor(64 - u8g2.getStrWidth(mode_names[current_mode]) / 2, 42); u8g2.print(mode_names[current_mode]); u8g2.setCursor(107 - u8g2.getStrWidth(step_display_names[current_step_index]) / 2, 42); u8g2.print(step_display_names[current_step_index]); // ========Display dividing lines========= // Garis horizontal pemisah #2 u8g2.drawHLine(0, 47, 128); // Garis vertikal pemisah #2 u8g2.drawStr(32, 59, " 1 3 5 7 9 +30d "); u8g2.drawVLine(32, 49, 14); // ====================================== u8g2.drawStr(5, 60, is_tx ? "TX S" : "RX S"); u8g2.drawFrame(34, 50, 93, 11); int bar_level = 0; if (is_tx) { int pwr_value = analogRead(PIN_S_METER_IN); bar_level = map(pwr_value, 0, 1023, 0, 91); } else { int s_value = analogRead(PIN_S_METER_IN); int s_constrained = constrain(s_value, 0, S_METER_MAX_ADC); bar_level = map(s_constrained, 0, S_METER_MAX_ADC, 0, 91); } if (bar_level > 0) { u8g2.drawBox(35, 51, bar_level, 9); } last_bar_level = bar_level; break; } } } while (u8g2.nextPage()); force_display_update = false; last_displayed_freq = current_frequency; last_tx_state = is_tx; last_if_frequency = if_frequency; last_tx_offset_freq = tx_offset_freq; last_bfo_lsb_offset = bfo_lsb_offset; last_bfo_usb_offset = bfo_usb_offset; last_am_frequency = am_frequency; last_si5351_correction = si5351_correction_val; } // =================================================================== // FUNGSI-FUNGSI BANTU FUNÇÕES AUXILIARES // =================================================================== // Fungsi untuk menyimpan otomatis setelah idle void check_auto_save() { // Jika ada perubahan DAN sudah 5 detik tidak ada aktivitas if (settings_changed && (millis() - last_activity_time > auto_save_delay)) { save_settings(); // Simpan pengaturan settings_changed = false; // Reset flag agar tidak menyimpan terus-menerus } } void update_bfo_frequencies() { if (current_mode == 0) bfo_frequency = if_frequency + bfo_lsb_offset; // LSB else if (current_mode == 1) bfo_frequency = if_frequency + bfo_usb_offset; // USB else bfo_frequency = if_frequency + am_frequency; // AM } void load_settings() { EepromData data; EEPROM.get(0, data); if (data.magic_key != 123456789) { for(int i=0;i<9;i++) band_frequencies[i] = band_defaults[i]; // Band definitions. current_band = 0; current_mode = 1; if_frequency = IF_FREQUENCY_DEFAULT; tx_offset_freq = TX_OFFSET_FREQ_DEFAULT; bfo_lsb_offset = BFO_LSB_OFFSET_DEFAULT; bfo_usb_offset = BFO_USB_OFFSET_DEFAULT; am_frequency = AM_FREQUENCY_DEFAULT; si5351_correction_val = SI5351_CORRECTION_DEFAULT; current_frequency = band_frequencies[current_band]; save_settings(); } else { for(int i=0;i<9;i++) band_frequencies[i] = data.last_freq[i]; // Band definitions. current_band = data.last_band; current_mode = data.last_mode; if_frequency = data.if_frequency; tx_offset_freq = data.tx_offset_freq; bfo_lsb_offset = data.bfo_lsb_offset; bfo_usb_offset = data.bfo_usb_offset; am_frequency = data.am_frequency; si5351_correction_val = data.si5351_correction; current_frequency = band_frequencies[current_band]; } update_bfo_frequencies(); } void save_settings() { EepromData data; data.magic_key = 123456789; band_frequencies[current_band] = current_frequency; for(int i=0;i<9;i++) data.last_freq[i] = band_frequencies[i]; // Band definitions. data.last_band = current_band; data.last_mode = current_mode; data.if_frequency = if_frequency; data.tx_offset_freq = tx_offset_freq; data.am_frequency = am_frequency; data.si5351_correction = si5351_correction_val; EEPROM.put(0, data); } void save_current_freq_to_band() { band_frequencies[current_band] = current_frequency; } void load_freq_from_band() { current_frequency = band_frequencies[current_band]; }

Esboço abaixo, vocês poderão colocarem em transceptores comerciais ou feitos em casa. Em CLK0 VFO RX você tem frequência do display mais a soma FI do transceptor, em TX você terão só a frequência do display sem FI. RX e TX com AM SSB CLK0 VFO. Vale lembrar que Modo é uma única tecla a cada toque, inicial em USB, e cada toque uma banda e selecionada USB, AM, LSB.
Notem que a linha 313 está com mesmo comando bfo_frequency assim. 313 uint64_t vfo_output_freq = (uint64_t)vfo_with_offset + bfo_frequency; // + bfo_frequency AM LSB USB shifted VFO.
O que digitei foi a FI na linha 42 #define TX_OFFSET_FREQ_DEFAULT 7800000L
Digitando a FI na linha 42 o TX de CLK0 VFO ficará menos a FI na frequência de RX.
3- Esboço abaixo copie e cole no Arduino IDE.
/* * DDS VFO Transceiver - 3Band * ========================================================================= * Fitur : * - Tampilan Callsign saat startup * - Band: 80m, 40m, 11m * - Menu setting : IF (step 10Hz), LSB Offset, USB Offset, AM Offset, Kalibrasi Kristal, TX Offset * - Gain S-Meter disesuaikan untuk 0.7V = Full Scale * - Semua setting tersimpan di EEPROM * - Tampilan UI dengan garis pemisah * - Auto-save setelah 5 detik idle * ========================================================================= */ //--------------------------------------------------------------------------------- // Function // 1.CLK0 BAND 7 RX VFO 34.765 MHZ AM,LSB,USB, MODE // 2.CLK0 TX FREQUENCY VFO - IF = FREQUENCY DISPLAY AM,LSB,USB,OFFSET MODE // 3.CLK1 BFO RX,TX FI 7800KHZ AM,LSB,USB,OFFSET MODE // 4.BAND 80M,40M,30M,20M,15M,12M,11M,10M,6M, HF,VHF // 5.STEP 10Hz,100Hz,1KHz,10KHz,100KHz,1MHz // 6.MENU CALIBRATION VFO,IF AM,LSB,USB,OFFSET MODE // 7.S-METER FULL SCALE S 1 3 5 7 9 + 30d // 8.EEPROM INCLUDED FOR MEMORY // Sketch modified by Waldir October 2025 Blog: https://projetosetransceptores.blogspot.com/ ////////////////////////////////////////////////////////////////////// // ================== 1. PUSTAKA (LIBRARIES) ================== #include <EEPROM.h> #include <Wire.h> #include <si5351.h> #include <RotaryEncoder.h> #include <U8g2lib.h> // ================== 2. DEFINISI & PENGATURAN INTI ================== // ----- ----- #define CALLSIGN "JZ13NGF" // Author call prefix, change yours #define IF_FREQUENCY_DEFAULT 7800000UL // Enter the transceiver IF 7800000,9998500,10000000,10240000,10700000 #define BFO_LSB_OFFSET_DEFAULT -1500L // LSB #define BFO_USB_OFFSET_DEFAULT 1500L // USB #define AM_FREQUENCY_DEFAULT 0UL // Initialize to zero for Menu correction #define SI5351_CORRECTION_DEFAULT 18000L // Si5351 crystal frequency correction #define TX_OFFSET_FREQ_DEFAULT 7800000L // Adjust IF 7800000L or -7800000L TX VFO frequency display // Definisi Pin #define PIN_ENCODER_CLK 2 #define PIN_ENCODER_DT 3 #define PIN_ENCODER_SW_BAND 4 #define PIN_STEP_BUTTON A0 #define PIN_TX_BUTTON A1 #define PIN_SET_BUTTON A2 #define PIN_S_METER_IN A3 #define PIN_PTT_OUT 6 // Pengaturan S-Meter. S-Meter Settings. #define S_METER_MAX_ADC 143 // Pengaturan OLED. OLED Settings U8G2_SSD1306_128X64_NONAME_1_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE); // Deklarasi Objek Lainnya. Object Declarations Si5351 si5351; RotaryEncoder encoder(PIN_ENCODER_DT, PIN_ENCODER_CLK); // Variabel & Konstanta Program. Program Variables & Constants long bfo_frequency; const char* mode_names[] = {"LSB", "USB", "AM "}; const long step_values[] = {10, 100, 1000, 10000, 100000, 1000000}; const char* step_display_names[] = {"10Hz" , "100Hz", "1KHz", "10KHz", "100KHz", "1MHz"}; byte current_step_index = 3; // Struktur EEPROM. EEPROM structure struct EepromData { uint32_t magic_key; long last_freq[9]; int last_band; int last_mode; long if_frequency; long tx_offset_freq; long bfo_lsb_offset; long bfo_usb_offset; long am_frequency; long si5351_correction; }; // Definisi Band const char* band_names[] = {"80M","40M","30M","20M","15M","12M","11M","10M","6M"}; const long band_defaults[]={3500000,7000000,10100000,14000000,21000000,24890000,26965000,28000000,50000000 }; long band_frequencies[9]; // ================== 3. VARIABEL GLOBAL ================== long current_frequency; int current_band = 0; int current_mode = 1; bool is_tx = false; bool force_display_update = true; long last_encoder_pos = 0; // Menu setting sekarang ada 6 item int setting_menu_state = 0; // 0:Normal, 1:IF, 2:LSB, 3:USB, 4:AM, 5:Cal, 6:TX // Variabel Pengaturan long if_frequency; long tx_offset_freq; long bfo_lsb_offset; long bfo_usb_offset; long am_frequency; long si5351_correction_val; // Tombol & Durasi unsigned long set_button_press_start_time = 0; const long long_press_duration = 1000; // Variabel untuk optimasi display long last_displayed_freq = 0; bool last_tx_state = false; int last_bar_level = -1; long last_if_frequency = 0; long last_tx_offset_freq = 0; long last_bfo_lsb_offset = 0; long last_bfo_usb_offset = 0; long last_am_frequency = 0; long last_si5351_correction = 0; // Variabel untuk state change detection bool band_button_active = false; bool step_button_active = false; // Variabel untuk Auto-Save (setelah idle 5 detik) unsigned long last_activity_time = 0; bool settings_changed = false; const unsigned long auto_save_delay = 5000; // 5 detik dalam milidetik // =================================================================== // FUNGSI INTERRUPT UNTUK ENCODER // =================================================================== void isrEncoder() { encoder.tick(); } // =================================================================== // FUNGSI SETUP // =================================================================== void setup() { pinMode(PIN_ENCODER_SW_BAND, INPUT_PULLUP); pinMode(PIN_STEP_BUTTON, INPUT_PULLUP); pinMode(PIN_TX_BUTTON, INPUT_PULLUP); pinMode(PIN_SET_BUTTON, INPUT_PULLUP); pinMode(PIN_PTT_OUT, OUTPUT); digitalWrite(PIN_PTT_OUT, LOW); pinMode(PIN_ENCODER_CLK, INPUT_PULLUP); pinMode(PIN_ENCODER_DT, INPUT_PULLUP); attachInterrupt(digitalPinToInterrupt(PIN_ENCODER_CLK), isrEncoder, CHANGE); attachInterrupt(digitalPinToInterrupt(PIN_ENCODER_DT), isrEncoder, CHANGE); u8g2.begin(); // =================== KODE STARTUP SCREEN =================== u8g2.firstPage(); do { // Tampilkan Callsign dengan font besar di tengah u8g2.setFont(u8g2_font_ncenB14_tr); u8g2.drawStr((128 - u8g2.getStrWidth(CALLSIGN)) / 2, 30, CALLSIGN); // Tampilkan nama perangkat di bawahnya u8g2.setFont(u8g2_font_6x10_tf); const char* subtitle = "DDS VFO 9BAND HF VHF"; u8g2.drawStr((128 - u8g2.getStrWidth(subtitle)) / 2, 50, subtitle); } while (u8g2.nextPage()); delay(2500); // Tahan tampilan startup selama 2.5 detik // ================= END OF INITIALIZATION CODE ==================== load_settings(); if (!si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0, si5351_correction_val)) { si5351.drive_strength(SI5351_CLK0,SI5351_DRIVE_6MA);//you can set this to 2MA, 4MA, 6MA or 8MA si5351.drive_strength(SI5351_CLK1,SI5351_DRIVE_6MA);//you can set this to 2MA, 4MA, 6MA or 8MA u8g2.firstPage(); do { u8g2.setFont(u8g2_font_6x10_tf); u8g2.drawStr(10, 35, "Si5351 GAGAL!"); } while (u8g2.nextPage()); while (1); } set_frequency(current_frequency); } // =================================================================== // FUNGSI LOOP // =================================================================== void loop() { check_encoder(); check_buttons(); check_auto_save(); //Panggil fungsi auto-save. Call the auto-save function update_display(); } // =================================================================== // FUNGSI-FUNGSI UTAMA // =================================================================== void check_encoder() { long new_pos = encoder.getPosition(); if (last_encoder_pos != new_pos) { long change = new_pos - last_encoder_pos; // Tandai ada aktivitas dan perubahan yang perlu disimpan last_activity_time = millis(); settings_changed = true; switch (setting_menu_state) { case 1: if_frequency += (change * 10); break; case 2: bfo_lsb_offset += (change * 10); break; case 3: bfo_usb_offset += (change * 10); break; case 4: am_frequency += (change * 10); break; case 5: // Menu Calibration si5351_correction_val += (change * 100); si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0, si5351_correction_val); si5351.drive_strength(SI5351_CLK0, SI5351_DRIVE_6MA); //you can set this to 2MA, 4MA, 6MA or 8MA si5351.drive_strength(SI5351_CLK1, SI5351_DRIVE_6MA); //you can set this to 2MA, 4MA, 6MA or 8MA set_frequency(current_frequency); break; case 6: tx_offset_freq += (change * 10); break; default: current_frequency += (change * step_values[current_step_index]); if (current_frequency < 1000000) current_frequency = 1000000; // Initial frequency if (current_frequency > 160000000) current_frequency = 160000000; // Final frequency break; } force_display_update = true; last_encoder_pos = new_pos; } } void check_buttons() { // Cek tombol TX if (digitalRead(PIN_TX_BUTTON) == LOW) { if (!is_tx) { last_activity_time = millis(); start_tx(); } return; } else { if (is_tx) stop_tx(); } // Cek tombol Band. Check the Band button if (digitalRead(PIN_ENCODER_SW_BAND) == LOW) { if (!band_button_active && setting_menu_state == 0) { band_button_active = true; force_display_update = true; last_activity_time = millis(); settings_changed = true; save_current_freq_to_band(); current_band = (current_band + 1) % 9; // Band definitions. load_freq_from_band(); } } else { band_button_active = false; } // Cek tombol Step if (digitalRead(PIN_STEP_BUTTON) == LOW) { if (!step_button_active && setting_menu_state == 0) { step_button_active = true; force_display_update = true; last_activity_time = millis(); settings_changed = true; current_step_index = (current_step_index + 1) % 6;// STEP Definition } } else { step_button_active = false; } // Cek tombol Set. Check the Set button if (digitalRead(PIN_SET_BUTTON) == LOW) { if (set_button_press_start_time == 0) { set_button_press_start_time = millis(); } } else { if (set_button_press_start_time != 0) { unsigned long press_duration = millis() - set_button_press_start_time; last_activity_time = millis(); // Aktivitas terdeteksi saat tombol dilepas settings_changed = true; if (press_duration >= long_press_duration) { if (setting_menu_state > 0) { setting_menu_state = 0; save_settings(); // Langsung simpan saat keluar menu settings_changed = false; // Reset flag karena sudah disimpan } else { setting_menu_state = 1; } } else { if (setting_menu_state > 0) { setting_menu_state = (setting_menu_state % 6) + 1; } else { current_mode = (current_mode + 1) % 3; } } force_display_update = true; set_button_press_start_time = 0; } } set_frequency(current_frequency); } void set_frequency (long vfo_freq) { update_bfo_frequencies(); long vfo_with_offset = vfo_freq; if (is_tx) { vfo_with_offset -= tx_offset_freq; } uint64_t vfo_output_freq = (uint64_t)vfo_with_offset + bfo_frequency; // + bfo_frequency AM LSB USB shifted VFO si5351.set_freq(vfo_output_freq * 100, SI5351_CLK0); si5351.output_enable(SI5351_CLK0, 1); if (current_mode == 2 && is_tx) { // Mode AM saat TX, matikan BFO. AM mode when TX, turn off BFO. si5351.output_enable(SI5351_CLK1, 1); // Enable AM ​​TX CLK1 } else { si5351.set_freq(bfo_frequency * 100, SI5351_CLK1); si5351.output_enable(SI5351_CLK1, 1); // Enable CLK1 BFO TX } } void start_tx() { is_tx = true; digitalWrite(PIN_PTT_OUT, HIGH); force_display_update = true; set_frequency(current_frequency); } void stop_tx() { is_tx = false; digitalWrite(PIN_PTT_OUT, LOW); force_display_update = true; save_settings(); set_frequency(current_frequency); } // =================================================================== // FUNGSI DISPLAY // =================================================================== void draw_setting_screen(const __FlashStringHelper* title, long value) { u8g2.setFont(u8g2_font_6x10_tf); char title_buf[17]; strcpy_P(title_buf, (const char*)title); u8g2.drawStr((128 - u8g2.getStrWidth(title_buf)) / 2, 15, title_buf); char val_buf[20]; if (strcmp_P(title_buf, (const char*)F("SET IF")) == 0) { sprintf(val_buf, "%ld", value); } else { sprintf(val_buf, "%+ld", value); } u8g2.setFont(u8g2_font_ncenB14_tr); u8g2.drawStr((128 - u8g2.getStrWidth(val_buf)) / 2, 52, val_buf); } void update_display() { // Cek apakah ada perubahan yang memaksa update layar penuh if (force_display_update || current_frequency != last_displayed_freq || is_tx != last_tx_state || if_frequency != last_if_frequency || tx_offset_freq != last_tx_offset_freq || bfo_lsb_offset != last_bfo_lsb_offset || bfo_usb_offset != last_bfo_usb_offset || am_frequency != last_am_frequency || si5351_correction_val != last_si5351_correction) { // Biarkan redraw penuh terjadi } else { // Cek S-meter/Power meter saja int current_bar_level; if (is_tx) { current_bar_level = map(analogRead(PIN_S_METER_IN), 0, 1023, 0, 91); } else { int s_constrained = constrain(analogRead(PIN_S_METER_IN), 0, S_METER_MAX_ADC); current_bar_level = map(s_constrained, 0, S_METER_MAX_ADC, 0, 91); } if (current_bar_level != last_bar_level) { // Hanya update jika nilai bar berubah } else { return; // Tidak ada yang perlu diupdate, keluar dari fungsi } } u8g2.firstPage(); do { switch (setting_menu_state) { case 1: draw_setting_screen(F("SET IF"), if_frequency); break; case 2: draw_setting_screen(F("SET LSB OFFSET"), bfo_lsb_offset); break; case 3: draw_setting_screen(F("SET USB OFFSET"), bfo_usb_offset); break; case 4: draw_setting_screen(F("SET AM FREQUENCY"), am_frequency); break; case 5: draw_setting_screen(F("SET CALIBRATION"), si5351_correction_val); break; case 6: draw_setting_screen(F("SET TX OFFSET"), tx_offset_freq); break; default: { char freq_buf[15]; long mhz = current_frequency / 1000000; long khz = (current_frequency % 1000000) / 1000; long hz = (current_frequency % 1000) / 10; sprintf(freq_buf, "%2ld.%03ld.%02ld", mhz, khz, hz); u8g2.setFont(u8g2_font_fub20_tn); u8g2.drawStr(0, 26, freq_buf); u8g2.setFont(u8g2_font_6x10_tf); // Garis horizontal pemisah #1 // Linha divisória horizontal #1 u8g2.drawHLine(0, 30, 128); // Garis vertikal pemisah #1 // Linha divisória vertical #1 u8g2.drawVLine(43, 32, 12); u8g2.drawVLine(86, 32, 12); // Teks Band, Mode, Step // Banda Teks, Modo, Passo u8g2.setCursor(21 - u8g2.getStrWidth(band_names[current_band]) / 2, 42); u8g2.print(band_names[current_band]); u8g2.setCursor(64 - u8g2.getStrWidth(mode_names[current_mode]) / 2, 42); u8g2.print(mode_names[current_mode]); u8g2.setCursor(107 - u8g2.getStrWidth(step_display_names[current_step_index]) / 2, 42); u8g2.print(step_display_names[current_step_index]); // ========Display dividing lines========= // Garis horizontal pemisah #2 u8g2.drawHLine(0, 47, 128); // Garis vertikal pemisah #2 u8g2.drawStr(32, 59, " 1 3 5 7 9 +30d "); u8g2.drawVLine(32, 49, 14); // ====================================== u8g2.drawStr(5, 60, is_tx ? "TX S" : "RX S"); u8g2.drawFrame(34, 50, 93, 11); int bar_level = 0; if (is_tx) { int pwr_value = analogRead(PIN_S_METER_IN); bar_level = map(pwr_value, 0, 1023, 0, 91); } else { int s_value = analogRead(PIN_S_METER_IN); int s_constrained = constrain(s_value, 0, S_METER_MAX_ADC); bar_level = map(s_constrained, 0, S_METER_MAX_ADC, 0, 91); } if (bar_level > 0) { u8g2.drawBox(35, 51, bar_level, 9); } last_bar_level = bar_level; break; } } } while (u8g2.nextPage()); force_display_update = false; last_displayed_freq = current_frequency; last_tx_state = is_tx; last_if_frequency = if_frequency; last_tx_offset_freq = tx_offset_freq; last_bfo_lsb_offset = bfo_lsb_offset; last_bfo_usb_offset = bfo_usb_offset; last_am_frequency = am_frequency; last_si5351_correction = si5351_correction_val; } // =================================================================== // FUNGSI-FUNGSI BANTU FUNÇÕES AUXILIARES // =================================================================== // Fungsi untuk menyimpan otomatis setelah idle void check_auto_save() { // Jika ada perubahan DAN sudah 5 detik tidak ada aktivitas if (settings_changed && (millis() - last_activity_time > auto_save_delay)) { save_settings(); // Simpan pengaturan settings_changed = false; // Reset flag agar tidak menyimpan terus-menerus } } void update_bfo_frequencies() { if (current_mode == 0) bfo_frequency = if_frequency + bfo_lsb_offset; // LSB else if (current_mode == 1) bfo_frequency = if_frequency + bfo_usb_offset; // USB else bfo_frequency = if_frequency + am_frequency; // AM } void load_settings() { EepromData data; EEPROM.get(0, data); if (data.magic_key != 123456789) { for(int i=0;i<9;i++) band_frequencies[i] = band_defaults[i]; // Band definitions. current_band = 0; current_mode = 1; if_frequency = IF_FREQUENCY_DEFAULT; tx_offset_freq = TX_OFFSET_FREQ_DEFAULT; bfo_lsb_offset = BFO_LSB_OFFSET_DEFAULT; bfo_usb_offset = BFO_USB_OFFSET_DEFAULT; am_frequency = AM_FREQUENCY_DEFAULT; si5351_correction_val = SI5351_CORRECTION_DEFAULT; current_frequency = band_frequencies[current_band]; save_settings(); } else { for(int i=0;i<9;i++) band_frequencies[i] = data.last_freq[i]; // Band definitions. current_band = data.last_band; current_mode = data.last_mode; if_frequency = data.if_frequency; tx_offset_freq = data.tx_offset_freq; bfo_lsb_offset = data.bfo_lsb_offset; bfo_usb_offset = data.bfo_usb_offset; am_frequency = data.am_frequency; si5351_correction_val = data.si5351_correction; current_frequency = band_frequencies[current_band]; } update_bfo_frequencies(); } void save_settings() { EepromData data; data.magic_key = 123456789; band_frequencies[current_band] = current_frequency; for(int i=0;i<9;i++) data.last_freq[i] = band_frequencies[i]; // Band definitions. data.last_band = current_band; data.last_mode = current_mode; data.if_frequency = if_frequency; data.tx_offset_freq = tx_offset_freq; data.am_frequency = am_frequency; data.si5351_correction = si5351_correction_val; EEPROM.put(0, data); } void save_current_freq_to_band() { band_frequencies[current_band] = current_frequency; } void load_freq_from_band() { current_frequency = band_frequencies[current_band]; }