terça-feira, 29 de setembro de 2026

DVFO 100kHz 225MHz VFO 309 Canais Cobra 148 GTL 2026.

Olá pessoal que acompanha nosso blog e canal no YouTube. Este mês trago aqui em vídeo, mais algumas modificações e implementações deste famoso projeto, DVFO 1.5 autor Sr. Paulo Roberto PU2REO. Postado no GitHub de 24 de Dezembro 2025. E seu perfil oficial GitHub. Sr. Paulo Roberto tem feito contribuições ao Radioamadorismo brasileiro e porque não mundial, com seus projetos e bibliotecas mais enxutas e leves para nossos DDS VFOs Arduíno. Meus agradecimentos por postar este maravilhoso projeto livre DVFO digital, especialmente para nosso lendário Cobra 148 GTL. Desde Fevereiro 2025 em pesquisas, salvei tudo. Tenho tentado fazer alterações atualizações cortando algumas funções ou rotinas no sketch DVFO. Não é fácil, pois queria deixar o programa bootloader no meu Atmega328P para outras gravações, este mês partir para cortes e atualizações.
Sobre atualização do sketch DVFO. Primeiro tive que cortar muitas instruções ou linhas de comandos como costumo falar para ver o quanto eu poderia ficar com o projeto original, mas gravando normal pela porta USB sem mexer no bootloader. Não deu muito certo e eu poderia perder muito tempo e ficaria desagradável, então partir para retirar ás bibliotecas que são mais pesadas e substitui-las, também não deu muito certo para ter espaço e continuar original. Então resolvi fazer o seguinte unir o que tenho experiência e estava na prática, decidi modificar ás bibliotecas:  #include <ButtonV2.h>  e  #include <RotaryEncoder.h> . Deixando ás mesmas bibliotecas do sketch do Júlio Cesar, com exceção da biblioteca do próprio autor para si5351,  e não foi só ás bibliotecas, fiz uma mistura dos dois sketches unindo o já conhecido pelo desconhecido para mim, o resultado foi muito melhor que esperava, com 309 canais em torno 27 MHz PX CB, é tudo que precisamos para ter os 40 canais mundialmente conhecidas (UIT) padrão (FCC), e muito mais poderemos modular acima até os 80 canais. Aproveitei que temos canais e VFO banda corrida, coloquei ás mesmas funções nas saídas CLK0 e CLK1 em: void tunegen() { . E outras atualizações.
Nosso DVFO atualizado ficou modo Canal: Tela com 309 canais como original x1 e x10 telecomandos. Roger Beep com som do lendário Cobra 148 GTL DX ou SuperStar 360 FM clássicos dos anos 80 e 90. Números frequências mostrando ponto e os zeros de Hz. CLK1 FI AM LSB USB. Clarificador 8 kHz para esquerda e direita aprovado com filtro e zona morta meio dia saindo em 20 Hz. S/Meter dupla escala em RX de 0 a +30dB, em TX mede RF, SWR com escala de medições 1, 2, 3, CAL!, dos clássicos transceptores CB PX. 
Nosso DVFO atualizado ficou modo VFO: Banda corrida de 100 kHz a 225 MHz com todos os Hz. STEP: 8 definições 1 Hz a 2 MHz. CLK1 FI mostra display a frequência de saída da FI. Roger Beep mesmo do modo Canal. S/Meter mesma escala de medições modo Canal. 
Nosso DVFO atualizado ficou MENU: Menu 1/5. Disp Tipe - Pressionando Encoder rápido entra modo EDITAR, girando Encoder seleciona Canal ou VFO, pressione rápido e volta NAVEGAR ou segure firme e saia modo MENU. Menu 2/5. Roger Beep - Pressione Encoder rápido e gire selecionando Liga ou Desliga, pressione rápido e volta a navegar MENU ou segure e saia. Menu 3/5. IF - Pressione rápido e ajuste IF no STEP selecionado ou segure um pouco Encoder até ver o STEP mudar cima tela, selecione de 1 Hz a 1 MHz. Aperte rápido e volte a NAVEGAR ou segure Encoder e saia MENU. Menu 4/5. Of.LSB - Pressione rápido Encoder para ajustar Offset LSB USB em STEP 10 Hz.Vejam algumas fotos deste mês com este sketch sendo montado cuidadosamente e exaustivamente. 
Sem comentar em cada uma foto, vejam até o fim.
Vejam em TX a escala de RF e SWR já ajustada com os mesmos parâmetros.
Ficou assim no final.
Modo VFO RX.
Modo VFO TX e escala RF SWR.
Modo Canal RX.
Modo Canal TX.
Estas são algumas das fotos que tirei, claro que não começou assim e nem terminou assim, só no vídeo vocês verão como ele ficou do jeito que achei melhor para mim e todos os nossos seguidores que acompanha nosso blog. Quero dizer mais uma vez que o sketch poderá ser modificado em tudo, clarificador, ajuste do si5351, frequências VFO de início e final, tempo e som beep, FI para outros transceptores ou montagens AM SSB, nomes caracteres na tela, assim como outras modificações poderão ser feitas fora do MENU diretamente no sketch em: #define, variáveis globais, void loop. O sketch está todo comentado em português Brasil para iniciantes e aprendizado das linhas de comandos ou instrução.
Esquema elétrico atualizado DVFO 2026.
No esquema acima o autor PU2REO já deixou tudo fácil para adaptação implementação do VFO digital no seu Cobra 148 GTL, pinos do VCO UHIC 007 aproveitem L19 e L20. Outro circuito de acionamento de TX e RX com relé poderá ser com um reed swítch ou chave magnética substituindo o relé, caso tenha estalos ao soltar o PTT ou falta de espaço.
Selecione todo sketch, copie e cole. Abra Arduino IDE vá em "Arquivo" depois "Novo", ou ícone ao lado da seta para "Carregar ou Compilar, abra um novo delete todas ás linhas e cole este sketch, renomeei com final .ino se preferir e salve em uma pasta. adicione todas ás bibliotecas. Lembre-se que muitas bibliotecas para o mesmo arquivo pode causar conflitos, observe o processador e porta USB entre outras preferencias do seu Arduino isso também provocara erros na compilação.
Sketch abaixo. Cuidado ao copiar vem junto algumas propagandas isso também dá erro na compilação ou gravação. Verifiquem ás propagandas elas são também copiadas e o Arduíno IDE poderá vê-la como uma linha de comando e dá erro na compilação, por isso bom verificar qual o nome da propaganda que está em letras azuis, anote o nome e procure no seu sketch salvo e delete. Vocês podem fazer o seguinte, com o Arduíno IDE sketch aberto digite Ctrl F, vai abrir uma janela pequena em cima tem Localizar, digite o nome da propaganda click novamente em baixo a esquerda Localizar. Então vai dá onde tem a propaganda pelo nome que você digitou. Apague ou delete o nome e faça novas buscas de localização, até não achar mais o nome que você digitou.
// ====================================================================================
// Sketch atualizado do projeto DVFO, Sr. Paulo Roberto (PU2REO) licenciado GPL-3.0.
// (https://github.com/PU2REO/PU2REO_DVFO). Para COBRA 148 GTL, outros transceptores. 
// AM LSB USB, MENU 5/5, 309 CANAIS, VFO 100 kHz 225 MHz, CLARIFICADOR, ROGER BEEP.
// S/Meter dupla escala RX SIG. TX RF SWR. Por Waldir Cardoso, bibliotecas já usadas
// nos outros sketches Cobra 148 GTL, mantendo os pinos originais do esquema do PU2REO,
// sketch e esquema postado blog codigo livre. Meus agradecimentos ao PU2REO. 
// Blog:
// ============================================================================
// Pressione curto 500ms encoder entra modo MENU NAVEGAR. 1/5, 2/5, 3/5, 4/5, 5/5.
// Dentro do MENU NAVEGAR gire ou pressione médio encoder e entra em EDITAR.
// MENU 1/5 - Pressione encoder EDIT. gire e escolha modo Canal ou VFO na tela display.
// MENU 2/5 - Pressione encoder EDIT. gire e ligar desligar beep do Cobra 148 GTL DX.
// MENU 3/5 - Pressione encoder EDIT. gire ajustar IF 400kHz a 22MHz, STEP segure longo  
// MENU 4/5 - Pressione encoder EDIT. gire ajustar offset LSB cada 10 Hz. 
// MENU 5/5 - Pressione encoder EDIT. gire ajustar offset USB cada 10 Hz.
// Pressione encoder 1500ms longo para saír modo MENU.
// ============================================================================
// CLK0 - Canal 1- AM 34.765.00 KHz, LSB 34.763.5 KHz, USB 34.766.5, saída si5351.
// CLK1 - AM IF 7800 KHz, LSB 7798.5 KHz, USB 7801.5, saída mostra display VFO.
// S/Meter atualizado SIG. 0, +30dB RX, com RF SWR 1, 2, 3 CAL! em TX. CB PX. 
// STEP VFO 1Hz, 10Hz, 100Hz, 1kHz, 10kHz, 100KHz, 1MHz, 2MHz.
// VFO 100kHz a 225MHz, com todos os zeros '000' dos Hz. 
// Modo Canal com x1 e x10, avanço de 1 ou 10 canais. 
// Clarificador zona morta meio dia saindo 20Hz, atualizado do mesmo sketch anterior.
// Menoria total VFO Canal nos modos MENU.
// ============================================================================
// 1. BIBLIOTECAS sketches Júlio Casar (mesmas dos outros sketches Cobra148GTL)
// ============================================================================
#include <EEPROM.h>
#include <Wire.h>
#include <Rotary.h>
#include "PU2REO_Si5351Lite.h"
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

// --- Endereços da EEPROM ---
#define EEPROM_FREQ         0   // Frequência do VFO livre (41 bytes)
#define EEPROM_CHANNEL      10  // Índice do canal atual (2 bytes)
#define EEPROM_MENUTYPE     20  // Tipo de tela: 0=VFO, 1=Canal (1 byte)
#define EEPROM_ROGERBEEP    30  // Roger Beep ligado/desligado (1 byte)
#define EEPROM_INTFREQ      40  // Frequência IF (4 bytes)
#define EEPROM_LSBOFFSET    50  // Offset de LSB (4 bytes)
#define EEPROM_USBOFFSET    60  // Offset de USB (4 bytes)

// --- Valores fixos que ainda não estão no Menu (por espaço) ---
// Podem ser definidos para atualização de outros equipamentos.
#define SI5351_CAL         18500    // Minha calibração do Si5351 mesmo valor dos outros sketches 
#define S_METER_GAIN        600     // De 900 a 300. Ganho do S-Meter — ajuste este número após medir com multímetro

// --- Roger Beep ---
#define ROGERBEEP_FREQ     1800     // Hz- Frequencia som beep dos lemdários Cobra 148GTL-DX SuperStar 360FM é 1200-2000Hz
#define ROGERBEEP_MS       120      // Duração do beep, em milissegundos

// --- Faixa de sintonia do VFO livre ---
#define VFO_MIN_FREQ       100000UL    // Minima Frequencia modo VFO
#define VFO_MAX_FREQ       225000000UL // Maxima frequencia modo VFO

#define MAX_CHANNEL_INDEX  309      // Total de canais na tabela (valor original do PU2REO —
                                    // com 150 os canais "normais" acima de 26.965MHz até canal 80.

// Pinos — ORIGINAIS do esquema do PU2REO (fiação física já montada assim) 
#define ENC_KEY        A1     // Botão/chave do próprio Encoder
#define ENC_PIN2       A2     // Encoder — sinal de rotação 2
#define ENC_PIN1       A3     // Encoder — sinal de rotação 1
#define AM_PIN         5      // Tecla de pressão para modo AM
#define USB_PIN        6      // Tecla de pressão para modo USB
// Não existe pino dedicado para LSB — se nem AM nem USB estiverem pressionados, já é LSB
#define PTT_PIN        7      // Entrada do botão físico do PTT (LOW = pressionado)
#define TX_ENABLE_PIN  9      // Saída que liga o transmissor de verdade (segura ligado até o beep terminar)
#define ROGERBEEP_PIN  10     // Saída digital do tom do Roger Beep Cobra 148GTL DX SuperStar 360FM
#define SMETER_PIN     A6     // Entrada analógica do S-Meter
#define CLARIFIER_PIN  A7     // Entrada analógica do potenciômetro do Clarificador voice lock

// ============================================================================
// 2. VARIÁVEIS GLOBAIS
// ============================================================================
Rotary r = Rotary(ENC_PIN2, ENC_PIN1);
Adafruit_SSD1306 display(128, 64, &Wire, -1);
Si5351 si5351(0x60);

int smoothedSMeter = 0;
int peakHold = 0;
unsigned long peakTimer = 0;
unsigned long lastSave = 0;

unsigned long freq = 2700000UL;     // Frequência do VFO livre (Hz)
volatile bool wrapOccurred = false;

byte mode = 0;                      // 0=AM, 1=LSB, 2=USB
unsigned long bfo = 0;              // BFO final (IF + offset), recalculado a cada mudança de modo
long intFreq = 7800000UL;           // Frequência IF — Ajustável pelo Menu de 400 kHz a 22 MHz
long lsbOffset = -1500;             // Offset de LSB (negativo) — ajustável pelo Menu
long usbOffset = 1500;              // Offset de USB (positivo) — ajustável pelo Menu

long clarifier = 0;                 // Offset do Clarificador em Hz (-8000 a +8000) Saindo meio com 20 Hz

bool menuType = false;              // false = VFO livre; true = Canal
int channelIndex = 0;

bool menuMode = false;
bool menuEditMode = false;
byte menuOption = 0;
const byte totalMenuOptions = 5;    // Tipo de Tela MENU, Roger Beep, IF, Offset LSB, Offset USB
int encoderData = 0;
bool rogerBeepOn = true;

// STEP Passos de sintonia do VFO: 1Hz até 2MHz
const unsigned long stepTable[] PROGMEM = {1, 10, 100, 1000, 10000, 100000, 1000000, 2000000};
byte stepIndex = 4; // Começa em 10kHz

// STEP próprio do IF (Menu) — de 1Hz até 1MHz, independente do STEP do VFO
const unsigned long ifStepTable[] PROGMEM = {1, 10, 100, 1000, 10000, 100000, 1000000};
byte ifStepIndex = 3; // Começa em 1kHz

// Multiplicador do modo Canal — só x1 ou x10,
// senão o giro do encoder "pula" longe demais na tabela de 309 canais
const unsigned long chanCoefTable[] PROGMEM = {1, 10};
byte chanCoefIndex = 0; // Começa em x1

// Controle do PTT / Roger Beep (sem máquina de estados — só duas variáveis)
bool pttOld = false;
bool beepPending = false;
unsigned long beepStartTime = 0;

// ============================================================================
// 3. TABELA DE CANAIS (herdada do DVFO original). Números >= 700 são canais
// especiais "TC" (Tele-comando/RCRS): mostra "TC" + (valor - 700).
// ============================================================================
const int16_t channelNumbers[] PROGMEM = {
  -150,-149,-148,-147,-146,-145,-144,-143,-142,-141,
  -140,-139,-138,-137,-136,-135,-134,-133,-132,-131,
  -130,-129,-128,-127,-126,-125,-124,-123,-122,-121,
  -120,-119,-118,-117,-116,-115,-114,-113,-112,-111,
  -110,-109,-108,-107,-106,-105,-104,-103,-102,-101,
  -100, -99, -98, -97, -96, -95, -94, -93, -92, -91,
   -90, -89, -88, -87, -86, -85, -84, -83, -82, -81,
   -80, -79, -78, -77, -76, -75, -74, -73, -72, -71,
   -70, -69, -68, -67, -66, -65, -64, -63, -62, -61,
   -80, -59, -58, -57, -56, -55, -54, -53, -52, -51,
   -50, -49, -48, -47, -46, -45, -44, -43, -42, -41,
   -40, -39, -38, -37, -36, -35, -34, -33, -32, -31,
   -30, -29, -28, -27, -26, -25, -24, -23, -22, -21,
   -20, -19, -18, -17, -16, -15, -14, -13, -12, -11,
   -10,  -9,  -8,  -7,  -6,  -5,  -4,  -3,  -2,  -1,
     1,   2,   3, 701,   4,   5,   6,   7, 702,  8,   9,  10,
    11, 703,  12,  13,  14,  15, 704,  16,  17,  18,  19, 705, 20,
    21,  22,  23,  24,  25,  26,  27,  28,  29,  30,
    31,  32,  33,  34,  35,  36,  37,  38,  39,  40,
    41,  42,  43,  44,  45,  46,  47, 706,  48,  49,  50,
    51, 707,  52,  53,  54,  55, 708,  56,  57,  58,  59, 709, 60,
    61,  62,  63,  64,  65,  66,  67,  68,  69,  70,
    71,  72,  73,  74,  75,  76,  77,  78,  79,  80,
    81,  82,  83,  84,  85,  86,  87,  88,  89,  90,
    91,  92,  93,  94,  95,  96,  97,  98,  99, 100,
   101, 102, 103, 104, 105, 106, 107, 108, 109, 110,
   111, 112, 113, 114, 115, 116, 117, 118, 119, 120,
   121, 122, 123, 124, 125, 126, 127, 128, 129, 130,
   131, 132, 133, 134, 135, 136, 137, 138, 139, 140,
   141, 142, 143, 144, 145, 146, 147, 148, 149, 150
};

// Frequência real (em kHz) de cada canal, na mesma ordem da tabela acima
const int16_t channelFreqs[] PROGMEM = {
  25465, 25475, 25485, 25495, 25505, 25515, 25525, 25535, 25545, 25555,
  25565, 25575, 25585, 25595, 25605, 25615, 25625, 25635, 25645, 25655,
  25665, 25675, 25685, 25695, 25705, 25715, 25725, 25735, 25745, 25755,
  25765, 25775, 25785, 25795, 25805, 25815, 25825, 25835, 25845, 25855,
  25865, 25875, 25885, 25895, 25905, 25915, 25925, 25935, 25945, 25955,
  25965, 25975, 25985, 25995, 26005, 26015, 26025, 26035, 26045, 26055,
  26065, 26075, 26085, 26095, 26105, 26115, 26125, 26135, 26145, 26155,
  26165, 26175, 26185, 26195, 26205, 26215, 26225, 26235, 26245, 26255,
  26265, 26275, 26285, 26295, 26305, 26315, 26325, 26335, 26345, 26355,
  26365, 26375, 26385, 26395, 26405, 26415, 26425, 26435, 26445, 26455,
  26465, 26475, 26485, 26495, 26505, 26515, 26525, 26535, 26545, 26555,
  26565, 26575, 26585, 26595, 26605, 26615, 26625, 26635, 26645, 26655,
  26665, 26675, 26685, 26695, 26705, 26715, 26725, 26735, 26745, 26755,
  26765, 26775, 26785, 26795, 26805, 26815, 26825, 26835, 26845, 26855,
  26865, 26875, 26885, 26895, 26905, 26915, 26925, 26935, 26945, 26955,
  26965, 26975, 26985, 26995, 27005, 27015, 27025, 27035, 27045, 27055, 27065, 27075,
  27085, 27095, 27105, 27115, 27125, 27135, 27145, 27155, 27165, 27175, 27185, 27195, 27205,
  27215, 27225, 27255, 27235, 27245, 27265, 27275, 27285, 27295, 27305,
  27315, 27325, 27335, 27345, 27355, 27365, 27375, 27385, 27395, 27405,
  27415, 27425, 27435, 27455, 27465, 27475, 27485, 27495, 27505, 27515, 27525,
  27535, 27545, 27555, 27565, 27575, 27585, 27595, 27605, 27615, 27625, 27635, 27645, 27655,
  27665, 27675, 27705, 27685, 27695, 27715, 27725, 27735, 27745, 27755,
  27765, 27775, 27785, 27795, 27805, 27815, 27825, 27835, 27845, 27855,
  27865, 27875, 27885, 27895, 27905, 27915, 27925, 27935, 27945, 27955,
  27965, 27975, 27985, 27995, 28005, 28015, 28025, 28035, 28045, 28055,
  28065, 28075, 28085, 28095, 28105, 28115, 28125, 28135, 28145, 28155,
  28165, 28175, 28185, 28195, 28205, 28215, 28225, 28235, 28245, 28255,
  28265, 28275, 28285, 28295, 28305, 28315, 28325, 28335, 28345, 28355,
  28365, 28375, 28385, 28395, 28405, 28415, 28425, 28435, 28445, 28455,
  28465, 28475, 28485, 28495, 28505, 28515, 28525, 28535, 28545, 28555
};

// ============================================================================
// 4. INTERRUPÇÃO DO ENCODER — pinos A2/A3 ficam no grupo PCINT1 (não PCINT2,
// que é só para pinos digitais D0-D7)
// ============================================================================
ISR(PCINT1_vect) {
  char result = r.process();
  if (result == DIR_CW) {
    if (menuMode) encoderData = 1;
    else set_frequency(1);
  }
  else if (result == DIR_CCW) {
    if (menuMode) encoderData = -1;
    else set_frequency(-1);
  }
}

// ============================================================================
// 5. FUNÇÃO: Ajusta a frequência (modo VFO) ou o canal (modo Canal).
// Cada modo tem sua própria tabela de passo — o VFO usa stepTable (1Hz-2MHz),
// o Canal usa chanCoefTable (só x1 ou x10, bem mais restrito)
// ============================================================================
void set_frequency(short dir) {
  if (!menuType) {
    unsigned long step = pgm_read_dword(&stepTable[stepIndex]);
    if (dir == 1) {
      freq += step;
      if (freq > VFO_MAX_FREQ) { freq = VFO_MIN_FREQ; wrapOccurred = true; }
    } else {
      if (freq <= VFO_MIN_FREQ + step) { freq = VFO_MAX_FREQ; wrapOccurred = true; }
      else freq -= step;
    }
  } else {
    int chanStep = pgm_read_dword(&chanCoefTable[chanCoefIndex]);
    channelIndex += dir * chanStep;
    if (channelIndex >= MAX_CHANNEL_INDEX) channelIndex = 0;
    if (channelIndex < 0) channelIndex = MAX_CHANNEL_INDEX - 1;
  }
}

// ============================================================================
// 6. FUNÇÃO: Recalcula o BFO (IF + offset de SSB) de acordo com o modo atual
// ============================================================================
void updateBFO() {
  if (mode == 0) bfo = intFreq;             // AM: sem deslocamento
  if (mode == 1) bfo = intFreq + lsbOffset; // LSB (já vem negativo, ex: -1500)
  if (mode == 2) bfo = intFreq + usbOffset; // USB (já vem positivo, ex: +1500)
}

// ============================================================================
// 7. FUNÇÃO: Lê os botões físicos de pressão AM/USB. Se nenhum estiver
// pressionado, já cai automaticamente em LSB (não existe pino dedicado)
// ============================================================================
void readMode() {
  byte oldMode = mode;
  if (digitalRead(AM_PIN)  == LOW) mode = 0;
  else if (digitalRead(USB_PIN) == LOW) mode = 2;
  else mode = 1;      // Nenhum dos dois pressionado = LSB
  if (mode != oldMode) updateBFO();
}

// ============================================================================
// 8. FUNÇÃO: Frequência de sintonia atual (VFO livre ou do canal), em Hz
// ============================================================================
unsigned long getTuneFreq() {
  if (!menuType) return freq;
  return (unsigned long)pgm_read_word(&channelFreqs[channelIndex]) * 1000UL;
}

// ============================================================================
// 9. FUNÇÃO: Gera a frequência final Si5351 (CLK0) e (CLK1)
// ============================================================================
  void tunegen() {
  long vfoFreq = (long)(getTuneFreq() + bfo) + clarifier;
  si5351.set_freq((uint64_t)vfoFreq * 100ULL, SI5351_CLK0);
  si5351.drive_strength(SI5351_CLK0, SI5351_DRIVE_8MA);
  si5351.output_enable(SI5351_CLK0, 1);

  // NOVO: gera o BFO (IF + offset de AM/LSB/USB) também no CLK1 —
  si5351.set_freq((uint64_t)bfo * 100ULL, SI5351_CLK1);
  si5351.drive_strength(SI5351_CLK1, SI5351_DRIVE_6MA);
  si5351.output_enable(SI5351_CLK1, 1);
}

// ============================================================================
// 10. FUNÇÕES DE FORMATAÇÃO DA TELA
// ============================================================================

// Imprime a frequência com pontos separadores e os Hz finais (2 dígitos,
// resolução de 10Hz) em tamanho MENOR que o resto — ex: "26.965." grande, "00" pequeno
// Atualizada 11/09/2026 frequencia com 3 zeros final display sempre
  void printFreqMixed(unsigned long hz, uint8_t bigSize, int endX, int y) {
  unsigned int m = hz / 1000000;
  unsigned int k = (hz % 1000000) / 1000;
  unsigned int h = (hz % 1000);

  // Calcula quantos caracteres o número grande vai ter, pra saber onde começar
  // de forma que ele sempre TERMINE no mesmo lugar (endX), não importa o tamanho
  byte numChars;
  if (m >= 100) numChars = 7;      // ex: "225.965"
  else if (m >= 10) numChars = 6;  // ex: "25.965"
  else if (m >= 1) numChars = 5;   // ex: "5.965"
  else numChars = 3;               // ex: "965" (abaixo de 1MHz, sem MHz na frente)

  int charWidth = 6 * bigSize; // largura de 1 caractere nesse tamanho de fonte
  int startX = endX - (numChars * charWidth);
  if (startX < 0) startX = 0;

  display.setTextSize(bigSize);
  display.setCursor(startX, y);
  if (m > 0) {
  display.print(m);
  display.print(F("."));
  if (k < 100) display.print(F("0"));
  if (k < 10) display.print(F("0"));
  }
  display.print(k);

  // Os 3 dígitos pequenos do final sempre começam no mesmo X (endX), fixo
  display.setTextSize(1);
  display.setCursor(endX + 2, y); // +2 pixels de espaço antes dos 3 zeros pequenos
  if (h < 100) display.print(F("0"));
  if (h < 10) display.print(F("0"));
  display.print(h);
 }

// Imprime um valor em Hz já formatado com a unidade certa (Hz/kHz/MHz) —
// usado no rodapé do modo VFO para mostrar o STEP atual
void printStepUnit(unsigned long hz) {
  if (hz >= 1000000UL) { display.print(hz / 1000000UL); display.print(F("MHz")); }
  else if (hz >= 1000UL) { display.print(hz / 1000UL); display.print(F("kHz")); }
  else { display.print(hz); display.print(F("Hz")); }
}

// ============================================================================
// 11. FUNÇÃO: Salva/Carrega configurações na EEPROM
// ============================================================================
void saveMemory() {
  EEPROM.put(EEPROM_FREQ, freq);
  EEPROM.put(EEPROM_CHANNEL, channelIndex);
  EEPROM.put(EEPROM_MENUTYPE, (byte)menuType);
  EEPROM.put(EEPROM_ROGERBEEP, (byte)rogerBeepOn);
  EEPROM.put(EEPROM_INTFREQ, intFreq);
  EEPROM.put(EEPROM_LSBOFFSET, lsbOffset);
  EEPROM.put(EEPROM_USBOFFSET, usbOffset);
 // EEPROM.put(EEPROM_SIDEBAND, sidebandOffset);
}

void loadMemory() {
  EEPROM.get(EEPROM_FREQ, freq);
  EEPROM.get(EEPROM_CHANNEL, channelIndex);

  byte savedMenuType;
  EEPROM.get(EEPROM_MENUTYPE, savedMenuType);
  menuType = (savedMenuType == 1);

  byte savedRogerBeep;
  EEPROM.get(EEPROM_ROGERBEEP, savedRogerBeep);
  rogerBeepOn = (savedRogerBeep != 0);

  EEPROM.get(EEPROM_INTFREQ, intFreq);
  EEPROM.get(EEPROM_LSBOFFSET, lsbOffset);
  EEPROM.get(EEPROM_USBOFFSET, usbOffset);
  
  // --- Validações de segurança, contra EEPROM "virgem" ou valores corrompidos ---
  if (freq < VFO_MIN_FREQ || freq > VFO_MAX_FREQ) freq = 2700000UL;
  if (channelIndex < 0 || channelIndex >= MAX_CHANNEL_INDEX) channelIndex = 0;
  if (intFreq < 400000UL || intFreq > 22000000UL) intFreq = 7800000UL; // Valida frequências 400kHz a 22MHz
  //if (intFreq < 1000000UL || intFreq > 15000000UL) intFreq = 7800000UL;
  if (lsbOffset > 0 || lsbOffset < -5000) lsbOffset = -1500;
  if (usbOffset < 0 || usbOffset > 5000) usbOffset = 1500;
}

// ============================================================================
// 12. SETUP
// ============================================================================
void setup() {
  Wire.begin();
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  display.clearDisplay();
  display.setTextColor(WHITE);
  display.display();

  // --- Tela de boas-vindas ---
  display.setTextSize(2);
  display.setCursor(1, 4);
  display.print(F("Cobra 148"));
  display.setTextSize(1);
  display.print(F("GTL"));
  display.setTextSize(2);
  display.setCursor(38, 27);
  display.print(F("DVFO"));
  display.setCursor(9, 48);
  display.print(F("Clarifier"));
  display.display();
  delay(2000);

  pinMode(ENC_KEY, INPUT_PULLUP);
  pinMode(ENC_PIN1, INPUT_PULLUP);
  pinMode(ENC_PIN2, INPUT_PULLUP);
  pinMode(AM_PIN, INPUT_PULLUP);
  pinMode(USB_PIN, INPUT_PULLUP);
  pinMode(PTT_PIN, INPUT_PULLUP);
  pinMode(TX_ENABLE_PIN, OUTPUT);
  pinMode(ROGERBEEP_PIN, OUTPUT);
  digitalWrite(TX_ENABLE_PIN, LOW);

  loadMemory();

  si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0, 0);
  si5351.set_correction(SI5351_CAL, SI5351_PLL_INPUT_XO);
  si5351.output_enable(SI5351_CLK0, 1);
  si5351.output_enable(SI5351_CLK1, 1);
  
  // Encoder nos pinos A2/A3 (grupo PCINT1, diferente dos outros sketches que usam D2/D3)
  PCICR |= (1 << PCIE1);
  PCMSK1 |= (1 << PCINT10) | (1 << PCINT11);
  sei();

  updateBFO();
  tunegen();
}

// ============================================================================
// 13. LOOP PRINCIPAL
// ============================================================================
void loop() {
  static unsigned long btnTimer = 0;
  static bool btnState = false;
  static bool mediumFired = false; // Já disparou a ação do toque médio nesse aperto?
  static bool longFired = false;   // Já disparou a ação do toque longo nesse aperto?

  // --- Encoder: 3 durações de toque ---
  // Curto  (<500ms):    NAV/EDIT no Menu, ou STEP/multiplicador fora do Menu
  // Médio  (500-1500ms): só troca o STEP do IF, e só dentro do item da IF (menuOption == 2)
  // Longo  (>1500ms):   entra/sai do Menu, em qualquer item
  if (digitalRead(ENC_KEY) == LOW) {
    if (!btnState) { // Instante em que o botão foi pressionado 
      btnState = true;
      btnTimer = millis();
      mediumFired = false;
      longFired = false;
    }

    unsigned long held = millis() - btnTimer;

    // Toque médio: dispara uma vez só, ao passar de 500ms, e só se estiver editando o IF
    if (!mediumFired && held > 500 && held <= 1500) {
      if (menuMode && menuEditMode && menuOption == 2) {
        ifStepIndex = (ifStepIndex + 1) % 7; // Troca o STEP do IF
      }
      mediumFired = true; // Marca como disparado mesmo fora do item do IF, pra não repetir à toa
    }

    // Toque longo: dispara uma vez só, ao passar de 1500ms — sempre entra/sai do Menu
    if (!longFired && held > 1500) {
      menuMode = !menuMode;
      menuEditMode = false;
      encoderData = 0;
      display.clearDisplay();
      delay(500);
      longFired = true;
    }

  } else {
    if (btnState) {
      // Só conta como "toque curto" se nem o médio nem o longo chegaram a disparar
      if (!mediumFired && !longFired) {
        if (menuMode) {
          menuEditMode = !menuEditMode;
        } else if (!menuType) {
          stepIndex = (stepIndex + 1) % 8;       // VFO: percorre a tabela de passos
        } else {
          chanCoefIndex = (chanCoefIndex + 1) % 2; // Canal: alterna x1/x10
        }
      }
      btnState = false;
    }
  }

  if (menuMode) {
    handleMenu();
    return;
  }

  readMode();

  // =============================================================================
  // LEITURA DO CLARIFICADOR — mesma versão testada e aprovada dos outros sketches
  // (substitui o "Voice Lock" original PU2REO, que não tinha filtro nem zona morta
  // =============================================================================
  analogRead(CLARIFIER_PIN);
  int clarRead = analogRead(CLARIFIER_PIN);
  long rawClarifier = map(clarRead, 0, 1023, -8000, 8000); // 8 kHz para esquerda e direita

  static long lastSmoothed = 0;
  long smoothedClarifier = (rawClarifier * 2 + lastSmoothed) / 3;
  lastSmoothed = smoothedClarifier;

  const int CLAR_STEP = 10;
  long quantized;
  if (smoothedClarifier >= 0) quantized = (smoothedClarifier + CLAR_STEP / 2) / CLAR_STEP * CLAR_STEP;
  else quantized = (smoothedClarifier - CLAR_STEP / 2) / CLAR_STEP * CLAR_STEP;

  const int CLAR_DEADZONE = 20;  // Zona morta meio dia saindo em 20Hz
  if (quantized > -CLAR_DEADZONE && quantized < CLAR_DEADZONE) quantized = 0;

  static long clarifierOutput = 0;
  if (abs(quantized - clarifierOutput) >= CLAR_STEP) clarifierOutput = quantized;
  if (clarifier != clarifierOutput) clarifier = clarifierOutput;

  // ============================================================
  // PTT e Roger Beep versão tipo 148GTL DX SuperStar 360FM  
  // ============================================================
  bool pttNow = (digitalRead(PTT_PIN) == LOW);
  if (pttNow) {
    digitalWrite(TX_ENABLE_PIN, HIGH); // Mantém o transmissor ligado enquanto o PTT estiver pressionado
    beepPending = false;
  } else if (pttOld && !pttNow) {
    
    // Soltar o PTT: dispara o beep (se estiver ligado) modo MENU 2/5
    if (rogerBeepOn) {
      tone(ROGERBEEP_PIN, ROGERBEEP_FREQ, ROGERBEEP_MS);
      beepPending = true;
      beepStartTime = millis();
    } else {
      digitalWrite(TX_ENABLE_PIN, LOW); // Sem beep: já solta o transmissor na hora
    }
  }
  if (beepPending && (millis() - beepStartTime >= (unsigned long)ROGERBEEP_MS + 20)) {
    digitalWrite(TX_ENABLE_PIN, LOW); // Beep terminou: agora sim solta o transmissor de verdade
    beepPending = false;
  }
  pttOld = pttNow;

  // Atualiza o Si5351 só se algo mudou 
  static unsigned long tuneFreqOld = 0;
  static long clarifierOld = 0;
  static unsigned long bfoOld = 0;
  unsigned long currentTuneFreq = getTuneFreq();
  if (tuneFreqOld != currentTuneFreq || clarifierOld != clarifier || bfoOld != bfo) {
    tunegen();
    if (wrapOccurred) { si5351.pll_reset(SI5351_PLLA); wrapOccurred = false; }
    if (currentTuneFreq >= 100000000UL || tuneFreqOld >= 100000000UL) si5351.pll_reset(SI5351_PLLA);
    tuneFreqOld = currentTuneFreq;
    clarifierOld = clarifier;
    bfoOld = bfo;
  }

  //--- Leitura do S-Meter com leitura de descarte ---
  analogRead(SMETER_PIN);
  delayMicroseconds(50);
  int sMeterVal = analogRead(SMETER_PIN);
  display.clearDisplay();
  drawScreen(sMeterVal, pttNow || beepPending); // CHAMA a função; o corpo dela está logo abaixo
  display.display();
  if (millis() - lastSave > 5000) {
    saveMemory();
    lastSave = millis();
  }
  delay(50);
}

// ========================================================================================
// 14. FUNÇÃO: S/Meter - Desenha a tela — layout Atualizada TX SWR RF semelhante do PU2REO
// ========================================================================================
void drawScreen(int sMeterVal, bool isTx) {
  // S-Meter escala diferente para TX e RX  
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.setCursor(0, 0);
 // display.print(F("SIG.1.3.5.7.9...+30dB")); // Lendário Cobra 148GTL e outros equipamentos PX CB

 // TX ficar com escala SWR e RF
   if (isTx) {
  // Régua de SWR durante a transmissão — números posicionados
  // nos mesmos lugares onde ficavam o "5", "9" e "+30dB" de RX    
  display.setCursor(15, 0);   // Posição caractere numero 1 
  display.print(F("1"));       
  display.setCursor(24, 0);    // Posição nome caracteres SWR entre 1 e 2
  display.print(F("SWR"));  
  display.setCursor(45, 0);    // 2 abaixo pouco onde era o "5"
  display.print(F("2"));       // Alinhado onde era o "5"
  display.setCursor(68, 0);    // 3 Abaixo onde era o S "9"
  display.print(F("3"));       // Alinhado onde era o "9"  
  display.setCursor(0, 8);     // Começo da escala com RF em TX
  display.print(F("RF")); 
  display.setCursor(92, 0);     // Posição CAL! ajustar barras para medir SWR
  display.print(F("CAL!"));     // Alinhado ponteiro no meio 0d "+30dB"  
  } else {
  display.print(F("SIG.1.3.5.7.9...+30dB")); // Escala Cobra normal de sinal, em RX
  }
  
  // Escala diferente para TX e RX — durante a transmissão o sinal no pino é
  // bem mais forte (mede potência de saída), então usa a faixa cheia do ADC;
  // na recepção usa a faixa mais sensível de sempre (S_METER_GAIN)
  int rawBarLen;
  if (isTx) {
    rawBarLen = map(sMeterVal, 0, 1023, 0, 100); // TX: faixa cheia (medidor de potência/RF de saída)
  } else {
    int sConstrained = constrain(sMeterVal, 0, S_METER_GAIN);
    rawBarLen = map(sConstrained, 0, S_METER_GAIN, 0, 100); // RX: faixa sensível de sempre
  }

  // Suavização (média móvel) — evita "tremedeira" na barra
  smoothedSMeter = ((smoothedSMeter * 2) + rawBarLen) / 3;

  // Peak Hold — segura visualmente o valor mais alto por um instante, depois cai devagar
  if (smoothedSMeter > peakHold) {
    peakHold = smoothedSMeter;
    peakTimer = millis();
  }
  if (millis() - peakTimer > 15) {
    if (peakHold > 0) peakHold -= 2; // Queda gradual do pico
  }
  peakHold = constrain(peakHold, 0, 100);

  if (smoothedSMeter > 0) display.writeFillRect(16, 9, smoothedSMeter, 3, WHITE); // Barra principal
  for (int i = 1; i < 16; i++) display.fillRect(18 + (i * 5), 9, 1, 3, BLACK);    // "Serrilha" a barra em segmentos
  display.drawFastVLine(peakHold + 16, 8, 5, WHITE); // Linha do pico, marca o valor mais alto recente
  display.writeFastHLine(0, 16, 128, WHITE);
  
  long displayFreq = (long)getTuneFreq() + clarifier;
  if (!menuType) {
   
  //==========================================================
  // MODO VFO — Caracteres números frequência mesmo tamanho e formato do modo Canal
  //==========================================================
  display.setTextSize(1);
  display.setCursor(1, 20); // Posição caracteres VFO 
  display.print(F("VFO"));
  // Frequência do VFO — todos os dígitos no mesmo tamanho, igual ao modo Canal, com ponto antes dos Hz
  display.setTextSize(2);
  display.setCursor(1, 32); // Colada em x=0, igual ao Canal
 {
  unsigned long m = displayFreq / 1000000;
  unsigned long k = (displayFreq % 1000000) / 1000;
  unsigned long h = displayFreq % 1000;
  if (m > 0) { // MHz só aparece se for >=1MHz (senão fica "0." feio em frequências baixas)
  display.print(m);
  display.print(F("."));
  if (k < 100) display.print(F("0"));
  if (k < 10) display.print(F("0"));
 }
  display.print(k);
  //23/09/26 Para sumir ponto nos kHz
  if (m < 100) display.print(F("."));
  //display.print(F("."));
  if (h < 100) display.print(F("0"));
  if (h < 10) display.print(F("0"));
  display.print(h);
 }
  int vfoFreqEndX = display.getCursorX(); // Onde a frequência terminou, pra colocar a unidade logo depois
  //15/09/26. MHz em cima lado direito
  display.setTextSize(1);
  display.setCursor(109, 20); // Posição fixa, mesma linha do modo/RB (y=20), canto direito
  if (displayFreq >= 1000000UL) display.print(F("MHz")); else display.print(F("kHz"));
  
    //display.writeFastHLine(0, 47, 128, WHITE); //Linha separa coluna 2 e 3
    display.writeFastHLine(0, 50, 128, WHITE); //Linha separa coluna 2 e 3
    display.setTextSize(1);
    // IF (CLK1) — canto esquerdo do rodapé, mostra o BFO atual em kHz
    // (AM sem casa decimal, LSB/USB com uma casa quando necessário: 7798.5, 7801.5)
    display.setCursor(0, 55);
    display.print(F("IF:"));
    display.print(bfo / 1000); // Parte inteira, em kHz
    if (bfo % 1000 != 0) {     // Só mostra a casa decimal se ela existir (LSB/USB)
    display.print(F("."));
    display.print((bfo % 1000) / 100);
   }

    // TS (Step de sintonia) — canto direito do rodapé, abreviado de "STEP" pra "TS"
    display.setCursor(73, 55); // Posição STEP TS no rodapé
    display.print(F("TS:"));
    printStepUnit(pgm_read_dword(&stepTable[stepIndex])); // Mesmo valor de antes, só mudou o rótulo e a posição

    //display.setCursor(50, 20);
    display.setCursor(45, 20); // Modos + RB/TX juntos de novo, na mesma linha
    if (mode == 0) display.print(F("AM "));
    if (mode == 1) display.print(F("LSB"));
    if (mode == 2) display.print(F("USB"));
    if (isTx) display.print(F("   TX")); //Posições três '3' espaços de RB/TX em modo VFO
    else if (rogerBeepOn) display.print(F("   RB")); //Posições três '3' espaços de RB/TX em modo VFO  
    } else {
    
    // ==========================================================
    // MODO CANAL
    // ==========================================================
    int chNum = pgm_read_word(&channelNumbers[channelIndex]);

    // Posição do nome Canal pequeno, no canto, igual ao "VFO" do outro modo
    display.setTextSize(1);
    display.setCursor(0, 21); //Posição do nome Canal display
    display.print(F("Canal"));

   // Calcula quantos caracteres o número vai ter, pra ele sempre TERMINAR
   // no mesmo lugar (não sobrepor o LSB/x1), crescendo pra ESQUERDA quando for maior
   int dispValue = (chNum >= 700) ? (chNum - 700) : chNum;
   bool isTC = (chNum >= 700);
   bool isNeg = (dispValue < 0);
   int absVal = abs(dispValue);
   byte numDigits = (absVal >= 100) ? 3 : (absVal >= 10) ? 2 : 1;
   byte totalChars = numDigits + (isNeg ? 1 : 0) + (isTC ? 2 : 0); // +1 pro sinal "-", +2 pro "TC"

   int endX = 95; // Posição do número canal direita ou esquerda horizontalmente
   int startX = endX - (totalChars * 18); // 18 = largura de 1 caractere no tamanho 3
   if (startX < 0) startX = 0;

   display.setTextSize(3);
   //display.setCursor(startX, 21);
   display.setCursor(startX, 22); // Faz o número canal subir ou descer verticalmente.
   if (isTC) { display.print(F("TC")); display.print(dispValue); }
   else display.print(chNum);
  
  // Modo (AM/LSB/USB) — canto direito, em cima (pequeno)
   display.setTextSize(1);
   //display.setCursor(107, 21);
   display.setCursor(109, 20);
   if (mode == 0) display.print(F("AM"));
   if (mode == 1) display.print(F("LSB"));
   if (mode == 2) display.print(F("USB"));

   // Multiplicador (x1/x10) — embaixo do "Canal", canto esquerdo
   display.setTextSize(1);
   display.setCursor(0, 37);
   display.print(F("x"));
   display.print(pgm_read_dword(&chanCoefTable[chanCoefIndex]));

   //14/09/26. Atualizada para RB/TX ficar no lado esquerdo ao lado x10
   int multEndX = display.getCursorX(); // Onde "x1"/"x10" terminou
   display.setCursor(multEndX + 4, 37); // Escreve RB/TX logo depois, com 6px de espaço
   if (isTx) display.print(F("TX"));
   else if (rogerBeepOn) display.print(F("RB"));
   
   //14/09/26 Bom 
   //display.writeFastHLine(0, 47, 128, WHITE);
   display.writeFastHLine(0, 47, 105, WHITE);  // Linha divisória canal frequência
   //display.setCursor(0, 50);

   
   // Para todos os dígitos do mesmo tamanho 14/09/26
   display.setTextSize(2);
   //display.setCursor(0, 50);
   display.setCursor(3, 50); // Início do display, encostado na esquerda
  {
  unsigned long m = displayFreq / 1000000;
  unsigned long k = (displayFreq % 1000000) / 1000;
  unsigned long h = displayFreq % 1000;
  display.print(m);
  display.print(F(".")); // Ponto de volta, separando os Hz — fica "26.965.000"
  if (k < 100) display.print(F("0"));
  if (k < 10) display.print(F("0"));
  display.print(k);
  display.print(F(".")); 
  if (h < 100) display.print(F("0")); // Hz colados direto, sem ponto, mas ainda com zeros à esquerda
  if (h < 10) display.print(F("0"));
  display.print(h);
}
 
   // "MHz" fixo, sempre terminando pertinho da borda direita (x=126)
  display.setTextSize(1);
  display.setCursor(109, 40); // 108 + 18px de "MHz" = termina em 126
  //display.setCursor(110, 39); // 108 + 18px de "MHz" = termina em 126
  display.print(F("MHz"));
   }
 }     

// ============================================================================
// 15. FUNÇÃO: Menu de Serviço — 4 opções: Tipo de Tela, Roger Beep, Freq. IF,
// Offset de SSB (LSB/USB)
// ============================================================================

void handleMenu() {
  int encDir = encoderData;
  encoderData = 0;

  if (encDir != 0) {
    if (!menuEditMode) {
      menuOption = (menuOption + encDir + totalMenuOptions) % totalMenuOptions;
    } else {
      switch (menuOption) {
        case 0: menuType = !menuType; break;              // Tipo de Tela
        case 1: rogerBeepOn = !rogerBeepOn; break;        // Roger Beep
       
       // 15/09/26.  Agora com STEP FI MENU
        case 2:
        intFreq += encDir * pgm_read_dword(&ifStepTable[ifStepIndex]); // Agora usa o STEP próprio do IF
        if (intFreq < 400000UL) intFreq = 400000UL;
        if (intFreq > 22000000UL) intFreq = 22000000UL;
        updateBFO();
        break;
       
        //Atualizada para 400kHz a 22MHz acima 14/09/26
        //case 2: intFreq += encDir * pgm_read_dword(&stepTable[stepIndex]); updateBFO(); break; // Usa o mesmo STEP do VFO        
        case 3:                                             // Offset de LSB
          lsbOffset += encDir * 10;
          if (lsbOffset > 0) lsbOffset = 0; // LSB sempre <= 0
          updateBFO();
          break;
        case 4:                                             // Offset de USB
          usbOffset += encDir * 10;
          if (usbOffset < 0) usbOffset = 0; // USB sempre >= 0
          updateBFO();
          break;
      }
    }
  }

  display.clearDisplay();
  display.setTextColor(WHITE);

  // --- Título grande, igual ao layout original do PU2REO ---
  display.setTextSize(2);
  display.setCursor(0, 1);
  switch (menuOption) {
    case 0: display.print(F(" Disp Tipe")); break;
    case 1: display.print(F(" RogerBeep")); break;
    case 2: display.print(F("IF")); break;
    case 3: display.print(F(" Of.LSB")); break;
    case 4: display.print(F(" Of.USB")); break;
  }

  // --- Valor grande, no meio da tela ---
  display.setTextSize(2);
  display.setCursor(10, 26);  //Nome Canal posição display
  switch (menuOption) {
    case 0: display.print(menuType ? F("CANAL") : F("VFO")); break;    
    case 1: display.print(rogerBeepOn ? F("LIGA") : F("DESLIGA")); break;
        
    case 2:
    display.print(intFreq); // Numeros frequencia IF Sem sufixo "Hz" — estourava a linha
    display.setTextSize(1);  // Tamanho fonte
    display.setCursor(27, 13); // Posição Press/Step/Sair
    display.print(F("Press/Step/Sair"));
    display.setCursor(50, 1); // Mostra o STEP depois de nome IF cima display
    //display.setCursor(6, 45) // Posição Step abaixo 7800
    display.print(F("Step: "));
    printStepUnit(pgm_read_dword(&ifStepTable[ifStepIndex]));
    break;
        
    case 3: display.print(lsbOffset); display.print(F(" Hz")); break;
    case 4: display.print(usbOffset); display.print(F(" Hz")); break;
  }

  // --- Rodapé: "x1" (decorativo, igual ao original) + posição atual no Menu ---
  display.setTextSize(1);  
  //Atualizada 23/09/26
  display.setCursor(6, 45); //Posição'Gire/Press' Gire/Press/Sair meio entre valor 7800 FI e NAVEGAR
  display.print(menuEditMode ? F("Gire/Press/NAV/Sair") : F("Gire/Press/Sair"));
  display.setCursor(6, 57); // Posição "EDITAR NAVEGAR"
  display.print(menuEditMode ? F("EDITAR") : F("NAVEGAR"));
  display.setCursor(72, 57); // Posição "MENU: " + números
  display.print(F("MENU:"));  
  //display.setCursor(100, 57); //Posição números adicionado linha acima com nome "MENU"
  display.print(menuOption + 1);
  display.print(F("/"));      // Mostra números do "MENU"
  display.print(totalMenuOptions);
  display.display();
  delay(80);
}

segunda-feira, 31 de agosto de 2026

10kHz 225MHz Cobra 148 GTL 480 Canais e Clarificador.

Vocês, que curte e gosta dos projetos do blog, por gentileza olhem às propagandas e click para maiores detalhes no que interessar, desta forma vocês estarão ajudando o blog. Muito obrigado por acessar e ajudar o blog, conto com vocês muito agradecido.
Pessoal acabei de atualizar a mensagem temporária modo Canal, agora aparece também letra e número do canal visitado. 01/08/2026. 23:30 Hs.
Olá pessoal. Mês Julho pela primeira vez em 14 anos de blog, ficou sem postagem. Motivo: Tive um pequeno acidente de Moto dia 11 Junho, nada grave só alguns contusão muscular, ferimentos e dias de repouso, já estou recuperado, para honra e gloria do Altíssimo Senhor Deus. Este mês tem mais algumas implementações ou atualizações do sketch mês Junho, este aqui e vídeo do mesmo projeto código livre do Sr. Júlio Cesar, meus agradecimentos na divulgação deste famoso 10 kHz a 225 MHz versão 2. Bem publico o que estava faltando ainda simples neste sketch que foi super copiado. O clarificador com potenciômetro 1k com centro ou meio dia 00 Hz e início com 20 Hz até 8000 Hz ou 8 kHz mostrado no display acima do S/Meter. Postado mês Julho mas não postado. Modo canal novo neste sketch, com 80 canais em cada banda, A até F total 480 canais, faixa de PX CB 80 atualizada aqui do Brasil e outros países. Tive que perder muito tempo em atualizar o sketch para economizar espaço no Arduíno Nano, um sketch quando é muito copiado e modificado os hobbistas ou amantes como eu, que faz tais modificações vai empurrando do jeito que dá certo, assim vai inchando e ocupando muito espaço, são comandos repetidos rodando no setup e loop sem precisar, comandos que podem serem excluídos ou atualizados, diminuindo muito o espaço de armazenamento. Então este agora está bem leve mesmo com todas ás implementações deste mês. Sobre o clarificador: Vocês terão total 8 kHz em passos inicial meio 0 Hz de 20 Hz e depois de 10 Hz de deslocamento para direita e esquerda, esta faixa cobre quase de um canal para o outro em PX CB, por isso não deixei o STEP atuando, em SSB vocês poderão clarificar o áudio variando a frequência em RX e transmitir na mesma frequência se o rádio estiver alinhado corretamente. Ainda se quiserem aumentar o 8000Hz para 10000Hz ou 10kHz basta modificar a linha de comando que fica logo no começo do seu void loop, //LEITURA DO CLARIFICADOR. " long rawClarifier = map(clarRead, 0, 1023, -8000, 8000); // Converte para Hz (-8000 a +8000). Para esta " long rawClarifier = map(clarRead, 0, 1023, -10000, 10000); // Converte para Hz (-10000 a +10000) . Se quiser outro valor só modifique os números, mas cuidado quanto mais você sobe de frequência nesta linha de comando mais sensível fica clarificar agora cobre mais Hz. Na prática costuma ser imperceptível, mas é bom saber caso perceba o clarificador "menos, fino" ao girar. Modo canal: Ao pressionar o Encoder por alguns segundos, mesmo que você esteja em qualquer banda vai entrar a mensagem Canais D1 se tiver sido último canal e frequência visitada, 11m letras ou caracteres pequenos permanecerá sempre em todo modo canal, não coloquei para definir se é 12m em 24 MHz ou 10m em 28 MHz, simplesmente 11m em todos os canais de A a F, não tem muita importância para o operador. Atualizei hoje 02/09/2026, vendo um "erro" ao pressionar o botão BANDA + ou - passava para outra banda e frequência, no modo canal, só ao girar o Encoder a frequência do modo canal era chamado no loop, quando voltava para o modo VFO a banda que tinha recebido nova frequência do modo canal permanecia a mesma do modo canal. Eu não poderia deixar assim, tive que ver como iria fazer para não mais ter aquela variação das BANDAS + ou -, então corrigir deixando os botões inoperantes no modo canal. Assim ao pressionar o Encoder por alguns segundos fica sempre no modo canal com letra e número do último visitado é sempre na BANDA 11m pequeno. 
Notaram no esquema do Arduíno os periféricos ou acessórios tem vários capacitores de 10nF a 100nF de desacoplamentos nos circuitos ligados aos pinos "X" do Arduino, poderá ser do pino "X" ao GND negativo ou pino "X" e +Vcc. Ligações de RF sempre com cabo coaxial curto, fiação curtas. Vejam abaixo algumas fotos finais deste projeto, desculpe-me não sou programador e tenho muitas dificuldades em acertar, para eu entregar um projeto destes a vocês aqui publicado, eu passo muito tempo para chegar no resultado final e ficar bom, são várias gravações. Faço isso porque gosto muito é como se eu estivesse fazendo para mim mesmo, sou rigoroso em tudo que faço, para mim ou presta ou não presta, se não prestar não servirá para mim e nem meus seguidores e não publico, só publico se eu não tiver notado o erro ou erros, mas este aqui vem atualizar o anterior do mês de Junho, fiz vários testes e está muito bom mesmo, vocês vão poderem adaptar seu transceptor Cobra 148GTL ou outros, com este DDS digital VFO com modo canal com memória total. Outras alterações de FI frequências poderão ser modificadas no sketch ou esboço, mas o montador terá de saber tudo do seu transceptor. Quero agradecer aos 100 seguidores de todo planeta terra, meu muito obrigado, muito agradecido mesmo pela confiança que vocês tem no blog e em minha pessoa nestes 12 anos. Vejam ás fotos finais como ficou, e logo abaixo o sketch ou esboço todo comentado em português do Brasil.
Assistam ao vídeo abaixo e vejam os recursos novos para este DDS VFO.
Vejam que o CL indica o clarificador e frequência em 0 Hz.
Bem a tela inicial ficou assim mesmo, acho melhor assim.
Abaixo modo VFO, girei o clarificador para direita subindo frequência 8kHz de 26.965.000.
Abaixo modo VFO, girei o clarificador para esquerda descendo frequência 8kHz de 26.965.000.
Vamos agora modo canal. Vejam que estou tentando ajustar os caracteres.
Ainda ajustando os caracteres nas suas posições.
Acho que não vai dá o que quero. O display tem 128 colunas e acho que não vai dá.
Assim acho que dá com MHz e mode AM pequenos.
Estava esquecendo de mostrar, ao pressionar Encoder a breve mensagem modo canal.
Continuando nas posições dos caracteres, é acho que vai ficar assim.
Ainda não está bom para mim, vamos ver outras posições.
A mensagem rápida estava muito pequena, dava para aumentar e ficou assim.
Ainda não estou gostando, acho que vou retirar modo para o centro e grande.
Ficou mais difícil só com 128 colunas do display, muito pequeno.
É aparentemente ficou bom, mas e quando for C80 como fica.
Fica assim sobrepondo o modo AM. Não vai dá mesmo não.
Agora sim ficou muito bom caracteres maiores boa visibilidade.
 
Dois caracteres pequenos 11m que será sempre visível no modo canal e MHz, são quase insignificantes.
Já atualizado na mensagem temporária e nas baandas + e - 02/09/2026.
Agora 02/09/2026, atualizado na mensagem temporária canais e modo Banda + e - .
Esquema Arduíno e componentes.
Se vocês tiverem mensagens de erros verifique tudo desde o cabo USB se é spo para carregamento de celular ou e cabo de dados e carregamento, tem que ser cabo de dados original de celulares. Sistema operacional Windows 7 64 bits x64 ou Windows 10 32 bits x86 vocês podem instalar o Arduino IDE 1.8.19
Instale os drivers do Arduíno mesmo se for clone. Vá em Sistema "Gerenciador de dispositivo" abra vá em Portas (COM E LPT) agora click e veja se consta a porta USB SERIAL CH340 (COM"X"). Vocês poderão ver se a porta está instalada no seu Arduino IDE, vá em "Ferramentas" e "Porta" dependendo de quantas portas tiverem escolha uma 6, 7, 8, 9.
Selecione todo sketch, copie e cole. Abra Arduino IDE vá em "Arquivo" depois "Novo", ou ícone ao lado da seta para "Carregar ou Compilar, abra um novo delete todas ás linhas e cole este sketch, renomeei com final .ino se preferir e salve em uma pasta. adicione todas ás bibliotecas. Lembre-se que muitas bibliotecas para o mesmo arquivo pode causar conflitos, observe a porta USB entre outras preferencias poderá provocar erros na compilação. 
Sketch abaixo. Cuidado ao copiar vem junto algumas propagandas isso também dá erro na compilação ou gravação. Ao compilar veja onde ele dá erro e confira com o sketch aqui no blog, geralmente ás propagandas são de letras grandes azuis. Eu já tentei retirar essas propaganda de cima do sketch mas ela é posta pelo Google AdSense não consigo retira-las. 
Pessoal se vocês quiserem que apareçam na mensagem temporária o número do canal, é só acrescentar esta linha lá no " void drawToast() { "  Adicione está linha inteira.  display.print(currentChannel + 1); // Número do canal: 1 a 80 (linha nova)  Adicione lá depois desta  linha.  display.print((char)('A' + currentBank)); // Mostra a letra do banco atual (A a F) . Mas vou já atualizar no sketch para vocês pois tem também a posição dos caracteres Canais D20, ou outros. Então fica já atualizado  todo " void drawToast() { "  no sketch. Não precisa adicionar a linha acima citada. 
Pessoal corrigir Hoje 02/09/2026, ás 22:13 Hs, agora pois só vi fazendo mais testes, um "erro" ao entrar modo canal e apertasse o botão da banda + ou - de 11m para 10m, 6m, á frequência da banda selecionada era chamada no display, agora atualizei este "erro", pois não precisa no modo canal selecionar banda + ou - isso só no modo VFO. Volto a dizer se o sketch dá erros, por favor verifiquem ás propagandas elas são também copiadas e o Arduíno IDE poderá vê-la como uma linha de comando e dá erro na compilação, por isso bom verificar qual o nome da propaganda que está em letras azuis, anote o nome e procure no Arduíno IDE no sketch salvo. Vocês podem fazer o seguinte, com o Arduíno IDE sketch aberto digite Ctrl F, vai abrir uma janela pequena em cima tem Localizar, digite o nome da propaganda click novamente em baixo a esquerda Localizar. Então vai dá onde tem a propaganda pelo nome que você digitou. Apague ou delete o nome e faça novas buscas de localização, até não achar mais o nome que você digitou. Agora compile o sketch que deve ficar com: 30353 bytes 98% de espaço de  armazenamento, 570 bytes 27% de Variáveis globais. 
A mensagem temporária ficou assim 02/09/2026. sketch atualizado.
Já mostra mensagem temporária ao pressionar Encoder letras caracteres grandes banda e canal.
//**********************************************************************************************************
// 10kHz to 225MHz VFO / RF Generator with Si5351 and Arduino Nano, with Intermediate Frequency (IF) offset
// (+ or -), RX/TX Selector for QRP Transceivers, Band Presets and Bargraph S-Meter. See the schematics for
// wiring and README.txt for details. By J. CesarSound - ver 2.0 - Feb/2021.
//***********************************************************************************************************/
// ============================================================================
// Sketch para transceptor COBRA 148 GTL - CONTROLE DIGITAL COM Si5351
// ============================================================================
// Sketch original: 10kHz a 225MHz
// Autor original: Júlio Cesar  Fevereiro 2021
// Modificado por: Waldir Cardoso   Agosto 2026
// Blog: https://projetosetransceptores.blogspot.com/2026/08/10khz-225mhz-cobra-148-gtl-480-canais-e.html
// ============================================================================
// FUNCIONALIDADES PRINCIPAIS:
// ============================================================================
// - Frequência: 10 kHz até 225 MHz (cobertura HF/VHF/UHF completa)
// - CLK0 (VFO): Oscilador local para recepção
//   Exemplo: CLK0 Canal 1 PX (CB) AM = 34.765 MHz (26.965 + 7.800 MHz)
// - CLK1 (BFO): Oscilador de frequência intermediária (FI)
//   FI padrão: AM (7.8000 Hz) LSB (7.7985 MHz) USB (7.8015 MHz) (ajustável no sketch)
// - Modos de operação: AM, LSB, USB (teclas físicas separadas)
// - Encoder STEP ajustável: 1Hz, 10Hz, 1kHz, 10kHz, 100kHz, 1MHz, 1.5MHz, 2MHz
// - Bandas: 21 bandas pré-programadas (GEN, MW, 160m a 1m)
// - Botões: BAND+ (D10) sobe banda, BAND- (A1) desce banda
// - S-Meter: Escala estilo Cobra 148 GTL (SIG 1-9 +30 dB)
//   Com suavização e peak hold para melhor visualização
// - Modo canal com 480 de A até F 80 canais CB PX apartir de D1 26.965.00MHz.
// - Clarificador variando do centro meio dia para direita ou esquerda de 8000Hz = 8kHz
//   Com passos de 10Hz nos modos VFO canal (ajustável no sketch) 
// - EEPROM: Memória total - salva frequência, canal, banda, step e BFO ao desligar
// - Ao ligar, retorna exatamente na última configuração usada
// ============================================================================

// ============================================================================
// BIBLIOTECAS NECESSÁRIAS
// ============================================================================
#include <EEPROM.h>           // Memória EEPROM para salvar configurações
#include <Wire.h>             // Comunicação I2C (display e Si5351)
#include <Rotary.h>           // Controle do encoder rotativo
#include <SPI.h>              // Comunicação SPI
#include "PU2REO_Si5351Lite.h" // Gerador de frequência Si5351
#include <Adafruit_GFX.h>     // Biblioteca gráfica para display
#include <Adafruit_SSD1306.h> // Driver para display OLED SSD1306

// ============================================================================
// ENDEREÇOS DA MEMÓRIA EEPROM (onde os dados são salvos permanentemente)
// ============================================================================
#define EEPROM_FREQ        0    // Endereço 0: Frequência atual
#define EEPROM_BAND        10   // Endereço 10: Banda atual
#define EEPROM_STEP        20   // Endereço 20: Step (passo) atual
#define EEPROM_BAND_FREQ   100  // Endereço 100: Frequências das bandas
#define EEPROM_CHANNEL_MODE 40  // Endereço 40: Se estava em modo Canal (0=não, 1=sim)
#define EEPROM_BANK         41  // Endereço 41: Banco atual (0=A ... 5=F)
#define EEPROM_CHANNEL      42  // Endereço 42: Canal atual (0-79)
//31/08/26. 22:46Hs
//#define EEPROM_SAVED_VFO_FREQ  43  // 4 bytes (43-46): frequência do VFO guardada
//#define EEPROM_SAVED_BAND      47  // 1 byte: banda guardada


// ============================================================================
// FREQUÊNCIAS DO BFO (Beat Frequency Oscillator) para cada modo
// ============================================================================
#define BFO_AM    7800000UL     // BFO para modo AM  (7.800 MHz)
#define BFO_LSB   7798500UL     // BFO para modo LSB (7.7985 MHz)
#define BFO_USB   7801500UL     // BFO para modo USB (7.8015 MHz)

// ============================================================================
// PINOS DE SELEÇÃO DE MODO (AM, LSB, USB)
// ============================================================================
#define USB_PIN   6             // Pino para selecionar modo USB
#define LSB_PIN   7             // Pino para selecionar modo LSB
#define AM_PIN    8             // Pino para selecionar modo AM

// ============================================================================
// CONFIGURAÇÕES GERAIS 
// ============================================================================
#define BAND_INIT 15            // Banda inicial ao ligar (15 = 11m CB 27MHz)
#define XT_CAL_F  18000         // Fator de calibração do cristal Si5351
#define S_GAIN    303           // Sensibilidade do S-Meter (1.5V máximo)

// ============================================================================
// PINOS DE ENTRADA (BOTÕES E CHAVES)
// ============================================================================
#define tunestep  A0            // Botão do encoder (muda step)
#define band      A1            // Botão BAND+ (sobe banda)
#define rx_tx     A2            // Chave RX/TX (LOW = TX)
#define band_dn   10            // Botão BAND- (desce banda)
#define CLARIFIER_PIN A6        // Entrada analógica do potenciômetro do clarificador

// ============================================================================
// CONFIGURAÇÕES DO DISPLAY OLED
// ============================================================================
#define SCREEN_WIDTH 128        // Largura do display em pixels
#define SCREEN_HEIGHT 64        // Altura do display em pixels
#define OLED_RESET     -1       // Pino de reset (não usado)

// ============================================================================
// OBJETOS DAS BIBLIOTECAS Encoder display si5351
// ============================================================================
Rotary r = Rotary(2, 3);        // Encoder nos pinos 2 e 3
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); // Display OLED
Si5351 si5351(0x60);            // Si5351 no endereço I2C 0x60

// ============================================================================
// VARIÁVEIS DO S-METER (Medidor de Sinal)
// ============================================================================
const int sMeterPin = A3;       // Pino de entrada do S-Meter
int smoothedSMeter = 0;         // Valor suavizado do S-Meter
int peakHold = 0;               // Valor de pico mantido
unsigned long peakTimer = 0;    // Temporizador do pico

// ============================================================================
// VARIÁVEIS GLOBAIS PRINCIPAIS
// ============================================================================
unsigned long lastSave = 0;     // Último salvamento na EEPROM
unsigned long freq, freqold, fstep; // Frequência atual, anterior e passo
unsigned long savedVfoFreq = 26965000UL; // Padrão seguro (11m) — evita "000.000" se desligar durante o modo Canal
byte savedBandCount = 15;                // Padrão seguro (11m)
long cal = XT_CAL_F;            // Calibração do Si5351
byte stp, n = 1;                // Step atual e contador do gráfico
byte count;                     // Banda atual (1-21)
bool sts = 0;                   // Status RX/TX (0=RX, 1=TX)
unsigned long bfo = BFO_AM;     // Frequência do BFO atual
byte mode = 0;                  // Modo atual (0=AM, 1=LSB, 2=USB)
long clarifier = 0;             // Offset do clarificador em Hz (-8000 a +8000)
volatile bool wrapOccurred = false; // Marca quando a frequência "dá a volta" (225MHz↔100kHz)

// Variáveis do modo Canal
bool channelMode = false;       // Modo Canal ativo (bancos A-F)
byte currentBank = 3;           // Banco atual (0=A, 1=B, 2=C, 3=D, 4=E, 5=F) — inicia em D (11m)
byte currentChannel = 0;        // Canal atual dentro do banco (0-79, ou seja canal 1 a 80)

// Variáveis da mensagem temporária "Canais X"
bool toastActive = false;              // Se há uma mensagem temporária na tela agora
unsigned long toastStart = 0;          // Quando a mensagem começou (millis())
const unsigned long TOAST_DURATION = 2000; // Mensagem some depois de 2 segundos

// ============================================================================
// Tabela de frequências dos 6 bancos de canais (A a F), 80 canais cada, em MHz
// ============================================================================
const float frequencies[6][80] PROGMEM = {
  {24.555, 24.565, 24.575, 24.585, 24.595, 24.605, 24.615, 24.625, 24.635, 24.645, 24.655, 24.665, 24.675, 24.685, 24.695, 24.705, 24.715, 24.725, 24.735, 24.745, 24.755, 24.765, 24.775, 24.785, 24.795, 24.805, 24.815, 24.825, 24.835, 24.845, 24.855, 24.865, 24.875, 24.885, 24.895, 24.905, 24.915, 24.925, 24.935, 24.945, 24.955, 24.965, 24.975, 24.985, 24.995, 25.005, 25.015, 25.025, 25.035, 25.045, 25.055, 25.065, 25.075, 25.085, 25.095, 25.105, 25.115, 25.125, 25.135, 25.145, 25.155, 25.165, 25.175, 25.185, 25.195, 25.205, 25.215, 25.225, 25.235, 25.245, 25.255, 25.265, 25.275, 25.285, 25.295, 25.305, 25.315, 25.325, 25.335, 25.345},
  {25.355, 25.365, 25.375, 25.385, 25.395, 25.405, 25.415, 25.425, 25.435, 25.445, 25.455, 25.465, 25.475, 25.485, 25.495, 25.505, 25.515, 25.525, 25.535, 25.545, 25.555, 25.565, 25.575, 25.585, 25.595, 25.605, 25.615, 25.625, 25.635, 25.645, 25.655, 25.665, 25.675, 25.685, 25.695, 25.705, 25.715, 25.725, 25.735, 25.745, 25.755, 25.765, 25.775, 25.785, 25.795, 25.805, 25.815, 25.825, 25.835, 25.845, 25.855, 25.865, 25.875, 25.885, 25.895, 25.905, 25.915, 25.925, 25.935, 25.945, 25.955, 25.965, 25.975, 25.985, 25.995, 26.005, 26.015, 26.025, 26.035, 26.045, 26.055, 26.065, 26.075, 26.085, 26.095, 26.105, 26.115, 26.125, 26.135, 26.145},
  {26.155, 26.165, 26.175, 26.185, 26.195, 26.205, 26.215, 26.225, 26.235, 26.245, 26.255, 26.265, 26.275, 26.285, 26.295, 26.305, 26.315, 26.325, 26.335, 26.345, 26.355, 26.365, 26.375, 26.385, 26.395, 26.405, 26.415, 26.425, 26.435, 26.445, 26.455, 26.465, 26.475, 26.485, 26.495, 26.505, 26.515, 26.525, 26.535, 26.545, 26.555, 26.565, 26.575, 26.585, 26.595, 26.605, 26.615, 26.625, 26.635, 26.645, 26.655, 26.665, 26.675, 26.685, 26.695, 26.705, 26.715, 26.725, 26.735, 26.745, 26.755, 26.765, 26.775, 26.785, 26.795, 26.805, 26.815, 26.825, 26.835, 26.845, 26.855, 26.865, 26.875, 26.885, 26.895, 26.905, 26.915, 26.925, 26.935, 26.945},
  {26.965, 26.975, 26.985, 27.005, 27.015, 27.025, 27.035, 27.055, 27.065, 27.075, 27.085, 27.105, 27.115, 27.125, 27.135, 27.155, 27.165, 27.175, 27.185, 27.205, 27.215, 27.225, 27.255, 27.235, 27.245, 27.265, 27.275, 27.285, 27.295, 27.305, 27.315, 27.325, 27.335, 27.345, 27.355, 27.365, 27.375, 27.385, 27.395, 27.405, 27.415, 27.425, 27.435, 27.455, 27.465, 27.475, 27.485, 27.505, 27.515, 27.525, 27.535, 27.555, 27.565, 27.575, 27.585, 27.605, 27.615, 27.625, 27.635, 27.655, 27.665, 27.675, 27.705, 27.685, 27.695, 27.715, 27.725, 27.735, 27.745, 27.755, 27.765, 27.775, 27.785, 27.795, 27.805, 27.815, 27.825, 27.835, 27.845, 27.855},
  {27.865, 27.875, 27.885, 27.895, 27.905, 27.915, 27.925, 27.935, 27.945, 27.955, 27.965, 27.975, 27.985, 27.995, 28.005, 28.015, 28.025, 28.035, 28.045, 28.055, 28.065, 28.075, 28.085, 28.095, 28.105, 28.115, 28.125, 28.135, 28.145, 28.155, 28.165, 28.175, 28.185, 28.195, 28.205, 28.215, 28.225, 28.235, 28.245, 28.255, 28.265, 28.275, 28.285, 28.295, 28.305, 28.315, 28.325, 28.335, 28.345, 28.355, 28.365, 28.375, 28.385, 28.395, 28.405, 28.415, 28.425, 28.435, 28.445, 28.455, 28.465, 28.475, 28.485, 28.495, 28.505, 28.515, 28.525, 28.535, 28.545, 28.555, 28.565, 28.575, 28.585, 28.595, 28.605, 28.615, 28.625, 28.635, 28.645, 28.655},
  {28.665, 28.675, 28.685, 28.695, 28.705, 28.715, 28.725, 28.735, 28.745, 28.755, 28.765, 28.775, 28.785, 28.795, 28.805, 28.815, 28.825, 28.835, 28.845, 28.855, 28.865, 28.875, 28.885, 28.895, 28.905, 28.915, 28.925, 28.935, 28.945, 28.955, 28.965, 28.975, 28.985, 28.995, 29.005, 29.015, 29.025, 29.035, 29.045, 29.055, 29.065, 29.075, 29.085, 29.095, 29.105, 29.115, 29.125, 29.135, 29.145, 29.155, 29.165, 29.175, 29.185, 29.195, 29.205, 29.215, 29.225, 29.235, 29.245, 29.255, 29.265, 29.275, 29.285, 29.295, 29.305, 29.315, 29.325, 29.335, 29.345, 29.355, 29.365, 29.375, 29.385, 29.395, 29.405, 29.415, 29.425, 29.435, 29.445, 29.455}
};

// ============================================================================
// INTERRUPÇÃO DO ENCODER (executada VFO Canais quando gira o encoder)
// ============================================================================
ISR(PCINT2_vect) {
  char result = r.process();
  if (result == DIR_CW) {
    if (channelMode) changeChannel(1);
    else set_frequency(1);
  }
  else if (result == DIR_CCW) {
    if (channelMode) changeChannel(-1);
    else set_frequency(-1);
  }
}

// ============================================================================
// INTERRUPÇÃO DO ENCODER (executada quando gira o encoder de 225MHz para 100kHz)
// ============================================================================
  void set_frequency(short dir) {
  if (dir == 1) {
    freq = freq + fstep;
    if (freq > 225000000) { freq = 100000; wrapOccurred = true; }
  }
  if (dir == -1) {
    if (freq <= fstep) {
      freq = 225000000; wrapOccurred = true;
    } else {
      freq = freq - fstep;
    }
  }
}

// ============================================================================
// FUNÇÃO: Modo Canal: Muda o canal (navegação contínua entre os 6 bancos A-F)
// ============================================================================
void changeChannel(short dir) {
  int idx = currentBank * 80 + currentChannel; // Índice linear: 0 a 479
  idx += dir;
  if (idx < 0) idx = 479;   // Desce abaixo de A1 → vai pro último canal (F80)
  if (idx > 479) idx = 0;   // Sobe acima de F80 → volta pro primeiro (A1)
  currentBank = idx / 80;
  currentChannel = idx % 80;
  freq = (unsigned long)(pgm_read_float(&frequencies[currentBank][currentChannel]) * 1000000.0);
}

// ============================================================================
// FUNÇÃO: Salva configurações na memória EEPROM
// ============================================================================

void saveMemory() {
  EEPROM.put(EEPROM_FREQ, freq);
  EEPROM.put(EEPROM_BAND, count);
  EEPROM.put(EEPROM_STEP, stp);
  EEPROM.put(EEPROM_CHANNEL_MODE, (byte)channelMode); // Salva se estava em modo Canal
  EEPROM.put(EEPROM_BANK, currentBank);               // Salva o banco (A-F)
  EEPROM.put(EEPROM_CHANNEL, currentChannel);         // Salva o canal (0-79)
 //31/08/26. atualizado 22:47Hs.
  //EEPROM.put(EEPROM_SAVED_VFO_FREQ, savedVfoFreq);
  //EEPROM.put(EEPROM_SAVED_BAND, savedBandCount);
}

// ============================================================================
// FUNÇÃO: Carrega configurações da memória EEPROM
// ============================================================================
void loadMemory() {
  EEPROM.get(EEPROM_FREQ, freq);   // Carrega frequência
  EEPROM.get(EEPROM_BAND, count);  // Carrega banda
  EEPROM.get(EEPROM_STEP, stp);    // Carrega step

    byte savedChannelMode;
  EEPROM.get(EEPROM_CHANNEL_MODE, savedChannelMode);
  channelMode = (savedChannelMode == 1);   // Restaura se estava em modo Canal

  EEPROM.get(EEPROM_BANK, currentBank);
  if (currentBank > 5) currentBank = 3;    // Segurança: se EEPROM vier "vazia" (255), usa banco D

  EEPROM.get(EEPROM_CHANNEL, currentChannel);
  if (currentChannel > 79) currentChannel = 0; // Segurança: mesma ideia
 //-----------------------------------------------------
/*  //31/08/26. Atualizada 22:49Hs
  EEPROM.get(EEPROM_SAVED_VFO_FREQ, savedVfoFreq);
  if (savedVfoFreq < 10000UL || savedVfoFreq > 225000000UL) {
  savedVfoFreq = 26965000UL; // segurança: 26.965MHz (banda 11m, canal A1), valor fixo
  }
   EEPROM.get(EEPROM_SAVED_BAND, savedBandCount);
  if (savedBandCount < 1 || savedBandCount > 21) {
  savedBandCount = BAND_INIT; // segurança: usa 11m padrão
  }
    if (count < 1 || count > 21) count = BAND_INIT; // segurança extra contra corrupção na EEPROM
 */ 
//-----------------------------------------------------
  // Se frequência inválida, usa banda inicial
  if (freq < 10000 || freq > 225000000) {
    count = BAND_INIT;
    bandpresets();
  }

  // Converte código do step para valor em Hz
  switch (stp) {
    case 1: fstep = 2000000; break;  // 2 MHz
    case 2: fstep = 1; break;        // 1 Hz
    case 3: fstep = 10; break;       // 10 Hz
    case 4: fstep = 1000; break;     // 1 kHz
    case 5: fstep = 10000; break;    // 10 kHz
    case 6: fstep = 100000; break;   // 100 kHz
    case 7: fstep = 1000000; break;  // 1 MHz
    case 8: fstep = 1500000; break;  // 1.5 MHz
    default:
    stp = 5;                       // Padrão: 10 kHz
    fstep = 10000;
    break;
  }
}

// ============================================================================
// FUNÇÃO: Salva frequência da banda atual na EEPROM
// ============================================================================
void saveBandFrequency() {
  int addr = EEPROM_BAND_FREQ + ((count - 1) * sizeof(unsigned long));
  EEPROM.put(addr, freq);
}

// ============================================================================
// FUNÇÃO: Carrega frequência da banda atual da EEPROM
// ============================================================================
bool loadBandFrequency() {
  int addr = EEPROM_BAND_FREQ + ((count - 1) * sizeof(unsigned long));
  unsigned long tempFreq;
  EEPROM.get(addr, tempFreq);
  if (tempFreq >= 10000UL && tempFreq <= 225000000UL) {
    freq = tempFreq;
    return true;
  }
  return false;
}

// ============================================================================
// FUNÇÃO: Modo de operação (AM/LSB/USB) pelos pinos
// ============================================================================
void readMode() {
  if (digitalRead(USB_PIN) == LOW) {
    mode = 2;
    bfo = BFO_USB;
  }
  if (digitalRead(LSB_PIN) == LOW) {
    mode = 1;
    bfo = BFO_LSB;
  }
  if (digitalRead(AM_PIN) == LOW) {
    mode = 0;
    bfo = BFO_AM;
  }
}

// ============================================================================
// FUNÇÃO DE CONFIGURAÇÃO INICIAL (executada uma vez ao ligar)
// ============================================================================
void setup() {
  Wire.begin();                 // Inicia comunicação I2C
  
  // Inicializa display OLED
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  display.clearDisplay();
  display.setTextColor(WHITE);
  display.display();

  // Tela inicial de boas-vindas
  display.setTextSize(2);
  display.setCursor(10, 4);
  display.print(F("Cobra 148"));
  display.setCursor(46, 26);
  display.print(F("GTL"));
  display.setCursor(6, 48);
  display.print(F("Clarifier"));
  display.display();
  delay(2000);

  // Configura pinos dos botões como entrada com pull-up
  pinMode(2, INPUT_PULLUP);
  pinMode(3, INPUT_PULLUP);
  pinMode(tunestep, INPUT_PULLUP);
  pinMode(band, INPUT_PULLUP);
  pinMode(rx_tx, INPUT_PULLUP);
  pinMode(band_dn, INPUT_PULLUP);
  pinMode(AM_PIN, INPUT_PULLUP);
  pinMode(LSB_PIN, INPUT_PULLUP);
  pinMode(USB_PIN, INPUT_PULLUP);
  
  // Inicializa gerador de frequência Si5351
  si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0, 0);
  si5351.set_correction(cal, SI5351_PLL_INPUT_XO);
  si5351.drive_strength(SI5351_CLK0, SI5351_DRIVE_8MA);  // CLK0: 8mA (VFO)
  si5351.drive_strength(SI5351_CLK1, SI5351_DRIVE_6MA);  // CLK1: 6mA (BFO)
  si5351.output_enable(SI5351_CLK0, 1);  // Habilita CLK0
  si5351.output_enable(SI5351_CLK1, 1);  // Habilita CLK1
 
  // Configura interrupção do encoder
  PCICR |= (1 << PCIE2);
  PCMSK2 |= (1 << PCINT18) | (1 << PCINT19);
  sei();                      // Habilita interrupções globais
  
  loadMemory();               // Carrega configurações da EEPROM
  
  // Se frequência inválida, usa valores padrão
  if (freq == 0) {
    count = BAND_INIT;
    bandpresets();
    stp = 5;
    setstep();
  }    
} 

// ============================================================================
// FUNÇÃO PRINCIPAL (executada em loop infinito)
// ============================================================================
void loop() {
  readMode();    // Lê modo de operação (AM/LSB/USB)

  // ============================================================
  // LEITURA DO CLARIFICADOR — versão testada e aprovada:
  // filtro rápido (mais peso na leitura nova), zona morta de 20Hz
  // no centro, e histerese para não "tremer" perto dos limites.
  // ============================================================
  analogRead(CLARIFIER_PIN);                 // Leitura de descarte (limpa crosstalk do ADC)
  int clarRead = analogRead(CLARIFIER_PIN);  // Leitura real do potenciômetro

  // 2. Converte para Hz (-8000 a +8000)
  long rawClarifier = map(clarRead, 0, 1023, -8000, 8000); // Converte para Hz (-8000 a +8000)

  // 3. Filtro. Suavização mais forte (constante de tempo maior que a versão original 3:2)
  static long lastSmoothed = 0;

  //Constante de tempo menor para o filtro.
  //long smoothedClarifier = (rawClarifier + lastSmoothed * 2) / 3; // Lenta um pouco rápida com filtro.
  long smoothedClarifier = (rawClarifier * 2 + lastSmoothed) / 3;  // Mais rápida menos filtro ainda.
  //long smoothedClarifier = (rawClarifier * 4 + lastSmoothed) / 5; // Filtro mais rápido
  
  //long smoothedClarifier = (rawClarifier * 2 + lastSmoothed) / 3; // Filtro suavizador rápido
  lastSmoothed = smoothedClarifier;

  // 4. Arredondamento para passos de 10 Hz
  const int CLAR_STEP = 10;      // Resolução de 10Hz por passo
  long quantized;
  if (smoothedClarifier >= 0) {
    quantized = (smoothedClarifier + CLAR_STEP / 2) / CLAR_STEP * CLAR_STEP;
  } else {
    quantized = (smoothedClarifier - CLAR_STEP / 2) / CLAR_STEP * CLAR_STEP;
  }

  // 5. Zona Morta central de 20 Hz (agora estável graças ao oversampling)
  const int CLAR_DEADZONE = 20;  // Zona morta central de 20Hz
  if (quantized > -CLAR_DEADZONE && quantized < CLAR_DEADZONE) {
    quantized = 0;
  }

  // 6. Histerese: só aceita o novo valor se a mudança for de pelo menos 1 passo (10Hz).
  // É isso que elimina o "pulando muito estreito" entre dois valores vizinhos.
  static long clarifierOutput = 0;
  if (abs(quantized - clarifierOutput) >= CLAR_STEP) { // Histerese: só muda após 1 passo
    clarifierOutput = quantized;
  }

  // 7. Atualiza apenas se mudar
  if (clarifier != clarifierOutput) { // Atualiza a variável só se realmente mudou
    clarifier = clarifierOutput;
  }

  // Estabilizar o encoder e frequências 225MHz para 100kHz. 
  static long clarifierold = 0;
  static unsigned long bfoold = bfo;
  if (freqold != freq || clarifierold != clarifier || bfoold != bfo) {
    tunegen();
    if (wrapOccurred) {
    si5351.pll_reset(SI5351_PLLA); // Reforço extra: acabou de dar a volta 225MHz↔100kHz
    wrapOccurred = false;
    } 
    if (freq >= 100000000UL || freqold >= 100000000UL) {
    si5351.pll_reset(SI5351_PLLA);
  }
    freqold = freq;
    clarifierold = clarifier;
    bfoold = bfo;
  }

  // Botão do encoder: toque curto = muda STEP | segurar 1 s = entra/sai do modo Canal
  static unsigned long btnTimer = 0;
  static bool btnState = false;
  static bool longPressDetected = false;

  if (digitalRead(tunestep) == LOW) {
    if (!btnState) {
      btnState = true;
      btnTimer = millis();
      longPressDetected = false;
    }
   
  // Atualizada para mostrar a banda 11m sempre modo canal
  if (btnState && !longPressDetected && (millis() - btnTimer > 800)) {  // Tempo para segurar encoder para entrar modo Canal
  channelMode = !channelMode; // Liga/desliga o modo Canal
  if (channelMode) {
  savedVfoFreq = freq;        // Guarda a frequência do VFO antes de entrar no canal
  savedBandCount = count;     // Guarda a banda atual antes de entrar no canal
  count = 15;                 // Força mostrar "11m", já que canal é sempre banda 11m
  freq = (unsigned long)(pgm_read_float(&frequencies[currentBank][currentChannel]) * 1000000.0);
  toastActive = true;
  toastStart = millis();
  } else {
  freq = savedVfoFreq;        // Restaura a frequência do VFO ao sair do canal
  count = savedBandCount;     // Restaura a banda (ex: WFM) que estava antes
  }
  longPressDetected = true;
   }      
  } else {
    if (btnState) {
      if (!longPressDetected) {
        setstep(); // Toque curto normal: muda o STEP, como sempre
      }
      btnState = false;
    }
  }
  
 //Atualizada 02/08/2026. 21:40 Hs desliga dandas + e - ao entrar modo Canal.
  // Botão BAND+ (sobe banda) — só funciona fora do modo Canal
  if (!channelMode && digitalRead(band) == LOW) {
  inc_preset();
  delay(300);
 }

  // Botão BAND- (desce banda) — só funciona fora do modo Canal
  if (!channelMode && digitalRead(band_dn) == LOW) {
  dec_preset();
  delay(300);
  }
  
  // Status RX/TX
  sts = (digitalRead(rx_tx) == LOW) ? 1 : 0;
  
  // Valor do S-Meter
  int sMeterVal = analogRead(sMeterPin);

  // Display completo com mensagem Canais
  display.clearDisplay();
  displayfreq();              // Mostra frequência
  layout();                   // Mostra layout (STEP, modo, etc)
  updateDisplay(sMeterVal);   // Mostra S-Meter
  drawToast();                // Mostra "Canais X" temporariamente, se acabou de entrar
  display.display();

  // Salva na EEPROM após 5 segundos sem mexer
  if (millis() - lastSave > 5000) {
    saveMemory();
    lastSave = millis();
  }
  delay(50);                  // Delay para estabilidade
}

// ============================================================================
// FUNÇÃO: Desenha uma mensagem temporária "Canais X" ao entrar no modo Canal
// ============================================================================

// ============================================================================
// FUNÇÃO: Desenha uma mensagem temporária "Canais X" ao entrar no modo Canal
// ============================================================================
void drawToast() {
  if (!toastActive) return;                                        // Só desenha se o aviso estiver ativo
  if (millis() - toastStart > TOAST_DURATION) { toastActive = false; return; } // Some depois de 2 segundos
  display.fillRect(0, 20, 127, 22, BLACK);   // Caixa preta cobrindo quase a tela toda (mesmo tamanho do sketch com Transverter)
  display.drawRect(0, 20, 127, 22, WHITE);   // Moldura branca do quadrado
  display.setTextSize(2);                    // Fonte grande pro aviso
  display.setCursor(2, 25);                  // Encostado à esquerda (Posição do nome Canais)
  display.print(F("CANAIS "));               // Texto fixo "CANAIS" com espaço antes da letra
  display.print((char)('A' + currentBank));  // Letra do banco atual: A a F
  display.print(currentChannel + 1);         // Número do canal atual: 1 a 80
}

// ============================================================================
// FUNÇÃO: Gera as frequências no Si5351 (CLK0 = VFO, CLK1 = BFO + clarificador)
// ============================================================================
void tunegen() {
  long vfoFreq = (long)(freq + bfo) + clarifier;  // VFO = frequência + BFO + clarificador  
  si5351.set_freq((uint64_t)vfoFreq * 100ULL, SI5351_CLK0);  // CLK0: VFO
  si5351.set_freq((uint64_t)bfo * 100ULL, SI5351_CLK1);     // Define CLK1     // CLK1: BFO 
  si5351.drive_strength(SI5351_CLK0, SI5351_DRIVE_8MA);
  si5351.drive_strength(SI5351_CLK1, SI5351_DRIVE_6MA);
  si5351.output_enable(SI5351_CLK0, 1);
  si5351.output_enable(SI5351_CLK1, 1);
}

// ============================================================================
// FUNÇÃO: Exibe a frequência no display
// ============================================================================
void displayfreq() {
  long displayFreq = (long)freq + clarifier; // Inclui o offset do clarificador na tela
  if (displayFreq < 0) displayFreq = 0;
  unsigned int m = displayFreq / 1000000;           // MHz
  unsigned int k = (displayFreq % 1000000) / 1000;  // kHz
  unsigned int h = (displayFreq % 1000);            // Hz
  
  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 h2 = (displayFreq % 1000) / 10; // Aparece os Hz mas com os 3 zeros depois dos 100 Hz.
    display.setCursor(5, 1);
    
    sprintf(buffer, "%2d.%003d.%02d", m, k, h2); // Linha que gera 2 números finais Hz display.
    //sprintf(buffer, "%2d.%003d.%02d", m, k, h); // Linha que gera 3 números finais display.
  }
  display.print(buffer);
}

// ============================================================================
// FUNÇÃO: Muda o step (passo) de ajuste da frequência
// ============================================================================
void setstep() {
  switch (stp) {
    case 1: stp = 2; fstep = 1; break;        // 2MHz → 1Hz
    case 2: stp = 3; fstep = 10; break;       // 1Hz → 10Hz
    case 3: stp = 4; fstep = 1000; break;     // 10Hz → 1kHz
    case 4: stp = 5; fstep = 10000; break;    // 1kHz → 10kHz
    case 5: stp = 6; fstep = 100000; break;   // 10kHz → 100kHz
    case 6: stp = 7; fstep = 1000000; break;  // 100kHz → 1MHz
    case 7: stp = 8; fstep = 1500000; break;  // 1MHz → 1.5MHz
    case 8: stp = 1; fstep = 2000000; break;  // 1.5MHz → 2MHz
  }
}

// ============================================================================
// FUNÇÃO: Sobe para a próxima banda (com suporte ao clarificador)
// ============================================================================
void inc_preset() {
  saveBandFrequency();        // Salva frequência atual da banda antes de trocar
  count++;
  if (count > 21) count = 1;  // Volta para banda 1 se passou da 21

  if (!loadBandFrequency()) bandpresets(); // Carrega frequência salva ou usa padrão

  clarifier = 0; // Reseta o clarificador ao trocar de banda

  // Segurança: garante que a frequência carregada está dentro da faixa válida
  if (freq < 100000UL || freq > 225000000UL) {
    bandpresets();
  }

  delay(10);      // Pequeno respiro para o barramento I2C estabilizar
  tunegen();      // Gera a nova frequência já com o clarificador zerado

  // Reset do PLL APENAS para grandes saltos de banda (evita instabilidade no Si5351)
  si5351.pll_reset(SI5351_PLLA); // Reset do PLL para estabilidade na troca de banda

  saveMemory();   // Salva as configurações na EEPROM
  delay(50);      // Aguarda a gravação e estabilização final
  tunegen();      // Reafirma a frequência após o reset do PLL
}

// ============================================================================
// FUNÇÃO: Desce para a banda anterior (com suporte ao clarificador)
// ============================================================================
void dec_preset() {
  saveBandFrequency();        // Salva frequência atual da banda antes de trocar
  if (count <= 1) count = 21; // Volta para banda 21 se estava na 1
  else count--;

  if (!loadBandFrequency()) bandpresets(); // Carrega frequência salva ou usa padrão

  clarifier = 0; // Reseta o clarificador ao trocar de banda

  // Segurança: garante que a frequência carregada está dentro da faixa válida
  if (freq < 100000UL || freq > 225000000UL) {
    bandpresets();
  }

  delay(10);      // Pequeno respiro para o barramento I2C estabilizar
  tunegen();      // Gera a nova frequência já com o clarificador zerado

  // Reset do PLL APENAS para grandes saltos de banda (evita instabilidade no Si5351)
  si5351.pll_reset(SI5351_PLLA); // Reset do PLL para estabilidade na troca de banda

  saveMemory();   // Salva as configurações na EEPROM
  delay(50);      // Aguarda a gravação e estabilização final
  tunegen();      // Reafirma a frequência após o reset do PLL
}

//25/08/2026 Atualizei diminuir espaço.
// ============================================================================
// Tabela de frequências padrão de cada banda, guardada na FLASH (PROGMEM)
// ============================================================================
const unsigned long bandFreqTable[] PROGMEM = {
  100000,     // banda 1  - GEN
  800000,     // banda 2  - MW
  1800000,    // banda 3  - 160m
  3650000,    // banda 4  - 80m
  4985000,    // banda 5  - 60m
  6180000,    // banda 6  - 49m
  7200000,    // banda 7  - 40m
  10000000,   // banda 8  - 31m
  11780000,   // banda 9  - 25m
  13630000,   // banda 10 - 22m
  14100000,   // banda 11 - 20m
  15000000,   // banda 12 - 19m
  17655000,   // banda 13 - 16m
  21525000,   // banda 14 - 13m
  26965000,   // banda 15 - 11m
  28400000,   // banda 16 - 10m
  50000000,   // banda 17 - 6m
  100000000,  // banda 18 - WFM
  130000000,  // banda 19 - AIR
  144000000,  // banda 20 - 2m
  220000000   // banda 21 - 1m
};

// ============================================================================
// FUNÇÃO: Define frequências padrão para cada banda
// ============================================================================
 void bandpresets() {
  freq = pgm_read_dword(&bandFreqTable[count - 1]);
  if (count == 1) tunegen(); // Mantém o comportamento original (só a banda 1 chamava tunegen aqui)
  si5351.pll_reset(SI5351_PLLA);
  saveMemory();
} 
// Atualização menos espaço.
// ============================================================================
// Tabela de textos do STEP guardada na FLASH (PROGMEM)
// ============================================================================
 const char stepName1[] PROGMEM = "  2MHz";
 const char stepName2[] PROGMEM = "   1Hz";
 const char stepName3[] PROGMEM = "  10Hz";
 const char stepName4[] PROGMEM = "  1kHz";
 const char stepName5[] PROGMEM = " 10kHz";
 const char stepName6[] PROGMEM = "100kHz";
 const char stepName7[] PROGMEM = "  1MHz";
 const char stepName8[] PROGMEM = "1.5MHz";

 const char* const stepNameTable[] PROGMEM = {
  stepName1, stepName2, stepName3, stepName4,
  stepName5, stepName6, stepName7, stepName8
};

// ============================================================================
// FUNÇÃO: Desenha o layout completo do display (linhas, textos, etc)
// ============================================================================
void layout() {
  display.setTextColor(WHITE);
  // Linha abaixo da frequência e linha abaixo do STEP/Canal — sempre aparecem
  display.drawLine(0, 18, 127, 18, WHITE);
  display.drawLine(0, 42, 127, 42, WHITE);

  // Linha vertical que separa a coluna MHz/AM do resto — só aparece no modo VFO;
  // some no modo Canal
  if (!channelMode) {
    display.drawLine(105, 19, 105, 41, WHITE);
  }

  // Linha vertical que separava STEP de RX/TX — só existe no modo VFO normal;
  // some no modo Canal
  if (!channelMode) {
    display.drawLine(87, 19, 87, 41, WHITE);
  }

  display.setTextSize(1);
  if (!channelMode) {
    // ==========================================================
    // MODO VFO (normal): STEP à esquerda, RX/TX à direita
    // ==========================================================
    display.setCursor(57, 22);
    display.print(F("STEP"));
    display.setCursor(48, 32);
    {
      char stepBuffer[7];
      strcpy_P(stepBuffer, (char*)pgm_read_word(&(stepNameTable[stp - 1])));
      display.print(stepBuffer);
    }
    display.setCursor(91, 27);
    if (!sts) display.print(F("RX"));
    if (sts) display.print(F("TX"));
  } else {
    // ==========================================================
    // MODO CANAL: "11m  D1  AM LSB USB" na mesma linha
    // ==========================================================
    display.setTextSize(2);    //Tamanho da fonte 
    display.setCursor(28, 23); // banco+canal, mais perto do "11m" menor
    //display.setCursor(24, 23); // banco+canal, mais perto do "11m" menor
    //display.setCursor(42, 23); // banco+canal, logo depois do "11m"
    display.print((char)('A' + currentBank)); // Letra do banco: A a F
    display.print(currentChannel + 1);        // Canal 1 a 80
   
    display.setCursor(70, 23); // Modo canal posição AM LSB USB, logo depois do D1
    //display.setCursor(72, 23); // Modo canal posição AM LSB USB, logo depois do D1
    if (mode == 0) display.print(F("AM"));
    if (mode == 1) display.print(F("LSB"));
    if (mode == 2) display.print(F("USB"));
    display.setTextSize(1);
  }

  // ============================================================
  // Clarificador (CL) e BFO — sempre aparecem, em qualquer modo
  // ============================================================
  display.setCursor(1, 45);
  display.print(F("CL:"));
  display.print(clarifier);
  display.print(F("Hz"));
  display.setCursor(74, 45);
  display.print(bfo / 1000);
  display.print(F("."));
  display.print((bfo % 1000) / 100);
  display.print(F("kHz"));

  // ============================================================
  // Unidade (kHz/MHz) — sempre igual, mesma posição em qualquer modo
  // ============================================================
  display.setCursor(110, 21);
  if (freq < 1000000) display.print(F("kHz"));
  if (freq >= 1000000) display.print(F("MHz"));

  // Modo AM/LSB/USB — só aparece aqui no modo VFO; no modo Canal já foi
  // desenhado acima, ao lado do D1
  if (!channelMode) {
    display.setCursor(110, 32); //Posição modo VFO AM LSB USB
    if (mode == 0) display.print(F("AM"));
    if (mode == 1) display.print(F("LSB"));
    if (mode == 2) display.print(F("USB"));
  }
  bandlist();  // Mostra o nome da banda (11m, etc.) — sempre aparece
}

//Atualização das bandas menor espaço
// ============================================================================
// Tabela de nomes das bandas guardada na FLASH (PROGMEM)
// ============================================================================
 const char bandName01[] PROGMEM = "GEN";
 const char bandName02[] PROGMEM = "MW";
 const char bandName03[] PROGMEM = "160m";
 const char bandName04[] PROGMEM = "80m";
 const char bandName05[] PROGMEM = "60m";
 const char bandName06[] PROGMEM = "49m";
 const char bandName07[] PROGMEM = "40m";
 const char bandName08[] PROGMEM = "31m";
 const char bandName09[] PROGMEM = "25m";
 const char bandName10[] PROGMEM = "22m";
 const char bandName11[] PROGMEM = "20m";
 const char bandName12[] PROGMEM = "19m";
 const char bandName13[] PROGMEM = "16m";
 const char bandName14[] PROGMEM = "13m";
 const char bandName15[] PROGMEM = "11m";
 const char bandName16[] PROGMEM = "10m";
 const char bandName17[] PROGMEM = "6m";
 const char bandName18[] PROGMEM = "WFM";
 const char bandName19[] PROGMEM = "AIR";
 const char bandName20[] PROGMEM = "2m";
 const char bandName21[] PROGMEM = "1m";

 const char* const bandNameTable[] PROGMEM = {
  bandName01, bandName02, bandName03, bandName04, bandName05,
  bandName06, bandName07, bandName08, bandName09, bandName10,
  bandName11, bandName12, bandName13, bandName14, bandName15,
  bandName16, bandName17, bandName18, bandName19, bandName20,
  bandName21
};

 void bandlist() {
  char buffer[6];
  strcpy_P(buffer, (char*)pgm_read_word(&(bandNameTable[count - 1])));

  if (channelMode) {
    display.setTextSize(1);      // Tamanho fonte 11m etc. Menor, só no modo Canal
    display.setCursor(0, 26);    // Mesma posição X original, Y ajustado pra centralizar
    //display.setCursor(0, 27);    // Mesma posição X original, Y ajustado pra centralizar
  } else {
    display.setTextSize(2);      // Tamanho fonte 11m etc. Tamanho original, modo VFO intocado
    display.setCursor(0, 23);
  }
  display.print(buffer);
}

// ============================================================================
// FUNÇÃO: Desenha o S-Meter (medidor de sinal) no rodapé
// ============================================================================
void updateDisplay(int sMeterVal) {
  // Moldura do S-Meter
  display.drawRect(0, 53, 128, 11, WHITE);
  display.fillRect(1, 54, 126, 8, BLACK);

  // Reduz sensibilidade em 50%
  sMeterVal = (sMeterVal * 50) / 100;
  sMeterVal = constrain(sMeterVal, 0, S_GAIN);
  
  // Converte valor para largura da barra
  int barWidth = map(sMeterVal, 0, S_GAIN, 0, 106);
  
  // Suavização do valor (média móvel)
  smoothedSMeter = ((smoothedSMeter * 2) + barWidth) / 3;

  // Peak Hold (mantém valor de pico)
  if (smoothedSMeter > peakHold) {
    peakHold = smoothedSMeter;
    peakTimer = millis();
  }
  
  // Decay do pico (diminui gradualmente)
  if (millis() - peakTimer > 15) {
    if (peakHold > 0) peakHold -= 2;
  }
  peakHold = constrain(peakHold, 0, 125);

  // Desenha barra do S-Meter
  if (smoothedSMeter > 0) {
    display.fillRect(20, 54, smoothedSMeter, 9, WHITE);
  }

  // Desenha linha de pico
  display.drawLine(peakHold + 20, 53, peakHold + 20, 62, WHITE);
  
  // Desenha escala vertical
  for (int i = 0; i < 126; i++) {
    if (i < smoothedSMeter) {
      display.drawFastVLine(i + 20, 54, 9, WHITE);
    }
  }
  
  // Escala numérica do S-Meter
  display.setTextColor(WHITE);
  display.setTextSize(1);
  display.setCursor(2, 55);
  display.print(F("SIG"));
  display.setCursor(24, 55);
  display.print(F("1"));
  display.setCursor(36, 55);
  display.print(F("3"));
  display.setCursor(48, 55);
  display.print(F("5"));
  display.setCursor(60, 55);
  display.print(F("7"));
  display.setCursor(72, 55);
  display.print(F("9"));
  display.setCursor(79, 55);
  display.print(F("..."));
  display.setCursor(96, 55);
  display.print(F("+30"));
  display.setCursor(115, 55);
  display.print(F("dB"));
}