hamradioshop.ro
Articole > Software pentru radioamatori Litere mici Litere medii Litere mari     Comentati acest articol    Tipariti

10kHz to 225MHz VFO / RF Generator cu Si5351

Doru Sandu ex. YO9CXY

Asa cum v-am obisnuit, voi explica in continuare modul de realizare a unui soft destinat receptiei radio, si in anumite conditii, emisiei in toate benzile scurte de radioamator. Nu cred ca este cineva care nu tresare si nu va fi curios atunci cand citeste in titlu cuvantul “VFO”. In plus folosind un generator de frecventa, usor de  manipulat, de tipul Si5351, se poate genera o frecventa continua cuprinsa intre 10KHz si 225MHz. Atrag atentia ca daca se doreste un generator performant, este necesara filtrarea semnalului.

Evident am sa va fac marturisirea ca am gasit ca sursa de inspiratie publicatiile sub numele lui

J. CesarSound - ver 2.0 - Feb/2021. Evident ca autorul prezinta multiple variante de lucru, unele care nu se potrivesc cu imaginile iar altele cu ramasite inutile. Am incercat sa refac idea, sa ordonez softul si sa simplific explicatiile pentru ca totul sa poata fi un punct de plecare, o idee pentru ceva mai elaborat.

Nu mai insist asupra sursei pentru ca daca doriti puteti incarca variante de lucru si prezentarile, facand  “search” pe “10kHz to 225MHz VFO / RF Generator with Si5351“.

Voi expune in continuare structura programului, observatiile si modificarile facute de mine pentru un display mai mare intr-o schema cu ESP32-S sau ESP32-U, soft compilat si incarcat cu Arduino IDE 1.8.18 conform indicatiilor din articolele anterioare. Variantele, construite pentru Arduino Nano sau ESP32, folosesc un display de tipul SSD1306 128X64 OLED sau chiar ILI9341 de 1.8inch, sunt variante economice care au avut in vedere mai ales miniaturizarea. Intrucat o variant stationara nu trebuie sa indeplineasca aceste cerinte, am adaptat softul pentru un display ieftin si usor de gasit, cu o rezolutie de 240/320px si marimea de 2.8inch – ILI 9341SPI. Pentru manipularea acestuia am folosir biblioteca <TFT_eSPI.h> si declarata TFT_eSPI tft = TFT_eSPI();

 

     

 

Arhitectura programului:

Aici tin sa raspund unor cititori care m-au observant “ca mai bine postam fisierul sursa in format .ino”!

Nu fac acest lucru pentru ca stiu bine cum se invata realizarea unui soft fara a copia lucrari gata facute.

Nu sunt nici adeptul postarilor video intrucat nu stiu “o limba” care sa fie inteleasa de toata lumea.

Asa deci sa incepem prin a preciza ca de fapt, un soft ori program este construit din patru parti esentiale:

1. – Biblioteci / Librarii si declaratii gasite la inceputul programului,

2. – Setari initiale care se gasesc de regula in continuarea declaratiior sub denumirea “void setup()”,

3. – Codul care ruleaza permanent pentru gestionarea comenzilor si informatiilor, numit “void loop()”,

4. – Rutine de serviciu denumite sugestiv dupa functia gestionata “void functie()”, gasite in general la sfarsitul programului si aranjate intr-o ordine usor de gasit.

Incepand cu punctul “2” ordine poate fi modificata intrucat compilatoarele moderne au o inteligenta aparte. Generatia mai veche de compilatoare necesita declararea rutinelor de serviciu inainte de “setup” pentru obtinerea unei viteze de lucru mai mare.

Iata scriptul programului care poate fi copiat si redenumi “.ino” in vederea compilarii cu Arduino IDE:

 

/*****************************************************************************

  10kHz to 225MHz VFO / RF Generator with Si5351, 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, modified by YO9CXY - Ian/2024.

******************************************************************************/

// Libraries

#include <Wire.h>            // IDE Standard

#include <Rotary.h>          // Ben Buxton https://github.com/brianlow/Rotary

#include <si5351.h>          // Etherkit https://github.com/etherkit/Si5351Arduino

#include <TFT_eSPI.h>

TFT_eSPI tft = TFT_eSPI();

 

// User preferences

//------------------------------------------------------------------------------------------------------------

// Enter your IF frequency, ex: 455 = 455kHz, 10700 = 10.7MHz, 0 = to direct convert receiver or RF generator, + will add and - will subtract IF offfset.

#define IFreq      10700

// Enter your initial Band (1-21) at startup, ex: 1 = Freq Generator, 2 = 800kHz (MW), 7 = 7.2MHz (40m), 11 = 14.1MHz (20m).

#define BAND_INIT  1

// Si5351 calibration factor, adjust to get exatcly 10MHz. Increasing this value will decreases the frequency and vice versa.

#define XT_CAL_F   33000

// Adjust the sensitivity of Signal Meter A/D input: 101 = 500mv; 202 = 1v; 303 = 1.5v; 404 = 2v; 505 = 2.5v; 1010 = 5v (max).

#define S_GAIN     303

// The pin used by tune step push button.

#define tuneStep   12

// The pin used by band selector push button.

#define band       13

// The pin used by RX / TX selector switch, RX = switch open, TX = switch closed to GND. When in TX, the IF value is not considered.

#define rx_tx      14

// The pin used by Signal Meter A/D input.

#define ADC_PIN    25

#define meterIN    39

 

//------------------------------------------------------------------------------------------------------------

Rotary r = Rotary(25, 26);

void IRAM_ATTR onEncoderChange();

Si5351 si5351(0x60);         // Si5351 I2C Address 0x60

 

int meterval1 = 0;

#define COLDEFA    0xBEF0

#define GARNET     0xC100

 

unsigned long freq, freqOld, fstep;

long interFreq = IFreq, interFreqOld = 0;

String freqSTR = String(freq);

 

long cal = XT_CAL_F;

unsigned int smval;

byte encoder = 1;

byte stp, n = 1;

byte count, x, xo;

bool sts = 0;

unsigned int period = 100;

unsigned long time_now = 0;

bool buttonActive_0 = false, buttonActive_1 = false, longPressActive = false;

unsigned int longPressTime = 600;

unsigned long buttonTimer = 0;

//======================================================================//

 

void setup()

{

  Serial.begin(115200);

  Wire.begin();

  tft.init();

  tft.setRotation(1);

  starTup_text();            // If you hang on startup, comment

 

  pinMode(25, INPUT_PULLUP);

  pinMode(26, INPUT_PULLUP);

  pinMode(tuneStep, INPUT_PULLUP);

  pinMode(band, INPUT_PULLUP);

  pinMode(rx_tx, INPUT_PULLUP);

  pinMode(meterIN, INPUT);

 

  si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0, 0);

  si5351.set_correction(cal, SI5351_PLL_INPUT_XO);

  si5351.drive_strength(SI5351_CLK0, SI5351_DRIVE_8MA);

  si5351.output_enable(SI5351_CLK0, 1);   // 1 - Enable CLK

  si5351.output_enable(SI5351_CLK1, 0);   // 0 - Disable CLK

  si5351.output_enable(SI5351_CLK2,  0);  // 0 - Disable CLK

 

  attachInterrupt(digitalPinToInterrupt(25), onEncoderChange, CHANGE);

  attachInterrupt(digitalPinToInterrupt(26), onEncoderChange, CHANGE);

 

  sei();

  count = BAND_INIT;

  bandPreset();

  stp = 4; setStep();

  BasicScreen();

}

//======================================================================//

 

void loop()

{

  if (freqOld != freq)

  {

    time_now = millis();

    tuneGen();

    freqOld = freq;

    displayFreq();

  }

 

  if (interFreqOld != interFreq)

  {

    time_now = millis();

    tuneGen();

    interFreqOld = interFreq;

  }

 

  if (xo != x)

  {

    time_now = millis();

    xo = x;

  }

//=============================================//

 

  // Button TuneStep //

  if (digitalRead(tuneStep) == LOW)

  {

    time_now = (millis() + 300);

    setStep();

    delay(300);

  }

//=============================================//

 

  // Button BAND //

  if (digitalRead(band) == LOW)

  {

    delay(100);

 

    if (buttonActive_0 == false)

    {

      buttonActive_0 = true;

      buttonTimer = millis();

    }

 

    if ((millis() - buttonTimer > longPressTime) && (longPressActive == false))

    {

      longPressActive = true;

      decPreset();

    }

  }

 

  else

  {

    if (buttonActive_0 == true)

    {

       if (longPressActive == true) { longPressActive = false; }

 

      else { incPreset(); }

      buttonActive_0 = false;

     }

  }

//=============================================//

 

  // RX_TX Button = // On Air //

  if (digitalRead(rx_tx) == LOW)

  {

    tft.setTextSize(2);

    tft.setTextColor(TFT_RED);

    tft.drawString("ON AIR", 242, 149);

    time_now = (millis() + 300);

    sts = 1;

  }

  else

    {

    tft.setTextColor(TFT_DARKGREY);

    tft.drawString("ON AIR", 242, 149);

    sts = 0;

    }

 

  if ((time_now + period) > millis())

  {

    displayFreq();

    layout();

  }

 drawBargraph();

}

//======================================================================//

 

void starTup_text()

{

  tft.setTextColor(TFT_GREEN);

  tft.setTextSize(2);

  tft.drawString("Si5351 VFO/RF GEN", 20, 50, 2);

  tft.drawString("JCR RADIO - Ver 2.5", 20, 90, 2);

  tft.drawString("Modified by ex.YO9CXY", 20, 130, 2);

  tft.setTextColor(TFT_ORANGE);

  tft.drawString("gocomraex@gmail.com", 30, 170, 2);

  delay(4000);

  tft.fillScreen(TFT_BLACK);

}

//======================================================================//

 

void bandPreset()

{

  switch (count)

  {

    case 1: freq = 100000; tuneGen(); break;

    case 2: freq = 800000; break;

    case 3: freq = 1800000; break;

    case 4: freq = 3650000; break;

    case 5: freq = 4985000; break;

    case 6: freq = 6180000; break;

    case 7: freq = 7200000; break;

    case 8: freq = 10000000; break;

    case 9: freq = 11780000; break;

    case 10: freq = 13630000; break;

    case 11: freq = 14100000; break;

    case 12: freq = 15000000; break;

    case 13: freq = 17655000; break;

    case 14: freq = 21525000; break;

    case 15: freq = 27015000; break;

    case 16: freq = 28400000; break;

    case 17: freq = 50000000; break;

    case 18: freq = 100000000; break;

    case 19: freq = 130000000; break;

    case 20: freq = 144000000; break;

    case 21: freq = 220000000; break;

  }

  si5351.pll_reset(SI5351_PLLA);

  stp = 4; setStep();

}

//======================================================================//

 

void setStep()

{

  switch (stp)

  {

    case 1: stp = 2; fstep = 1; break;

    case 2: stp = 3; fstep = 10; break;

    case 3: stp = 4; fstep = 1000; break;

    case 4: stp = 5; fstep = 5000; break;

    case 5: stp = 6; fstep = 10000; break;

    case 6: stp = 7; fstep = 100000; break;

    case 7: stp = 1; fstep = 1000000; break;

  }

}

//======================================================================//

 

void tuneGen()

{ si5351.set_freq((freq + (interFreq * 1000ULL)) * 100ULL, SI5351_CLK0); }

//======================================================================//

 

void incPreset()            // Contor INCREASE (pb_1 LONG Press???)

{

  count++;

  if (count > 21) count = 1;

  bandPreset();

  delay(50);

}

//=============================================//

void decPreset()            // Contor DECREASE (pb_1 LONG Press???)

{

  count--;

 

  if (count < 1) count = 21;

  bandPreset();

}

//======================================================================//

 

void displayFreq()

{

  freqSTR = String(freq);

  int lengFreq = (freqSTR.length());

 

  tft.setTextSize(1);

  // MILLIONS of Hz //

  if (lengFreq >= 9)

  {

    // TRUNCATE first 9 digits from right to left

    String first9digits = freqSTR.substring(lengFreq - 9, lengFreq);

    // CONVERT integer to get 9-th digit

    int digit9 = first9digits.toInt() / static_cast<int>(pow(10, 8)) % 10;

    tft.setTextColor(TFT_ORANGE, TFT_BLACK); tft.drawString(String(digit9), 5, 10, 7);

  }

  if (lengFreq < 9) { tft.fillRect(4, 7, 32, 52, TFT_BLACK); }  // DELETE unnecessary digits

  if (lengFreq >= 8)

  {

    String first8digits = freqSTR.substring(lengFreq - 8, lengFreq);

    int digit8 = first8digits.toInt() / static_cast<int>(pow(10, 7)) % 10;

    tft.setTextColor(TFT_ORANGE, TFT_BLACK); tft.drawString(String(digit8), 37, 10, 7);

  }

  if (lengFreq < 8) { tft.fillRect(4, 7, 65, 52, TFT_BLACK); }

  if (lengFreq >= 7)

  {

    String first7digits = freqSTR.substring(lengFreq - 7, lengFreq);

    int digit7 = first7digits.toInt() / static_cast<int>(pow(10, 6)) % 10;

    tft.setTextColor(TFT_ORANGE, TFT_BLACK); tft.drawString(String(digit7), 69, 10, 7);

  }

  if (lengFreq < 7) { tft.fillRect(4, 7, 96, 52, TFT_BLACK); }

  if (lengFreq >= 6)

  {

    String first6digits = freqSTR.substring(lengFreq - 6, lengFreq);

    int digit6 = first6digits.toInt() / static_cast<int>(pow(10, 5)) % 10;

    tft.setTextColor(TFT_ORANGE, TFT_BLACK); tft.drawString(String(digit6), 113, 10, 7);

  }

  // THOUSANDS of Hz //

  if (lengFreq < 6) { tft.fillRect(4, 7, 96, 52, TFT_BLACK); tft.fillRect(112, 7, 31, 52, TFT_BLACK); }

  if (lengFreq >= 5)

  {

    String first5digits = freqSTR.substring(lengFreq - 5, lengFreq);

    int digit5 = first5digits.toInt() / static_cast<int>(pow(10, 4)) % 10;

    tft.setTextColor(TFT_ORANGE, TFT_BLACK); tft.drawString(String(digit5), 145, 10, 7);

  }

  if (lengFreq < 5) { tft.fillRect(4, 7, 96, 52, TFT_BLACK); tft.fillRect(112, 7, 65, 52, TFT_BLACK); }

  if (lengFreq >= 4)

  {

    String first4digits = freqSTR.substring(lengFreq - 4, lengFreq);

    int digit4 = first4digits.toInt() / static_cast<int>(pow(10, 3)) % 10;

    tft.setTextColor(TFT_ORANGE, TFT_BLACK); tft.drawString(String(digit4), 177, 10, 7);

  }

  if (lengFreq < 4) { tft.fillRect(4, 7, 96, 52, TFT_BLACK); tft.fillRect(112, 7, 96, 52, TFT_BLACK); }

  if (lengFreq >= 3)

  {

    String first3digits = freqSTR.substring(lengFreq - 3, lengFreq);

    int digit3 = first3digits.toInt() / static_cast<int>(pow(10, 2)) % 10;

    tft.setTextColor(TFT_ORANGE, TFT_BLACK); tft.drawString(String(digit3), 221, 10, 7);

  }

  // HUNDREDS of Hz //

  if (lengFreq < 3) { tft.fillRect(4, 7, 96, 52, TFT_BLACK); tft.fillRect(112, 7, 96, 52, TFT_BLACK);

    tft.fillRect(220, 7, 32, 52, TFT_BLACK); }

  if (lengFreq >= 2)

  {

    String first2digits = freqSTR.substring(lengFreq - 2, lengFreq);

    int digit2 = first2digits.toInt() / static_cast<int>(pow(10, 1)) % 10;

    tft.setTextColor(TFT_ORANGE, TFT_BLACK); tft.drawString(String(digit2), 253, 10, 7);

  }

   if (lengFreq < 2) { tft.fillRect(4, 7, 96, 52, TFT_BLACK); tft.fillRect(112, 7, 96, 52, TFT_BLACK);

    tft.fillRect(220, 7, 64, 52, TFT_BLACK); }

  if (lengFreq >= 1)

  {

    String first1digits = freqSTR.substring(lengFreq - 1, lengFreq);

    int digit1 = first1digits.toInt() / static_cast<int>(pow(10, 0)) % 10;

    tft.setTextColor(TFT_ORANGE, TFT_BLACK); tft.drawString(String(digit1), 285, 10, 7);

  }

  else {  }

  tft.setTextSize(2);

}//======================================================================//

 

void layout()

{

  tft.setTextColor(TFT_WHITE, TFT_BLACK);

  if (count == 1) tft.drawString("  000KHz", 220, 114, 1);

  else

  {

    tft.setTextColor(TFT_GREEN, TFT_BLACK);

    char buffer[20];

 

    if (IFreq <= 1000) { sprintf(buffer, "  %lu", IFreq); } // IFreq <=> interFreq

    else

    if (IFreq <= 10000) { sprintf(buffer, " %lu", IFreq); }

    else { sprintf(buffer, "%lu", IFreq); }

  // Concatenate the suffix "KHz"

    strcat(buffer, "KHz");

    tft.drawString(buffer, 220, 114, 1);

  }

          // OR:

/*

  tft.setTextColor(TFT_WHITE, TFT_BLACK);

  if (count == 1) tft.drawString("  000KHz", 220, 114, 1);

  else

  {

    tft.setTextColor(TFT_GREEN, TFT_BLACK);

    std::string strFreq = "  " + std::to_string(interFreq); // IFreq <=> interFreq

 

    if (IFreq <= 1000) { strFreq += "KHz"; }

    else if (IFreq <= 10000) { strFreq += " KHz"; }

    // No further check is needed for IFreq > 10000

    // Displays strFreq (converted to const char*) on screen

    tft.drawString(strFreq.c_str(), 220, 114, 1);

  }

*/

//=============================================//

  ///tft.setTextColor(TFT_ORANGE, TFT_BLACK);

  ///tft.drawString("Sme", 5, 206, 2);

  ///tft.drawString("BWF", 215, 206, 2);

 

  tft.setTextColor(TFT_GREEN, TFT_BLACK);

  tft.setTextSize(3);

  if (stp == 2) tft.drawString("   1Hz", 207, 73);

  if (stp == 3) tft.drawString("  10Hz", 207, 73);

  if (stp == 4) tft.drawString("  1KHz", 207, 73);

  if (stp == 5) tft.drawString("  5KHz", 207, 73);

  if (stp == 6) tft.drawString(" 10KHz", 207, 73);

  if (stp == 7) tft.drawString("100KHz", 207, 73);

  if (stp == 1) tft.drawString("  1MHz", 207, 73);

 

  tft.setCursor(110, 33);

  if (interFreq == 0) ///tft.drawString("VFO", x, y, 2);

  if (interFreq != 0) ///tft.drawString(" L O", x, y, 2);

 

  tft.setTextColor(TFT_CYAN, TFT_BLACK);

  if (!sts)

  {

    interFreq = IFreq;

    ///tft.drawString("RX", 165, 102, 2);

  }

  if (sts)

  {

    interFreq = 0;

    ///tft.drawString("TX", 165, 102, 2);

  }

 

  bandList();

  drawBargraph();

}

//======================================================================//

 

void bandList()

{

  tft.setTextSize(3);

  if (count == 1)

  {

    interFreq = 0;

    tft.setTextColor(TFT_DARKGREY, TFT_CYAN); tft.drawString("LM", 85, 108);

    tft.setTextColor(TFT_WHITE); tft.drawString("GEN", 8, 108);

    tft.setTextColor(TFT_GREEN, TFT_BLACK); tft.drawString(" VFO", 159, 143);

  }

    interFreq = IFreq;

  if (count == 2)

  {

    tft.setTextColor(TFT_DARKGREY, TFT_GREEN); tft.drawString("GEN", 8, 108);

    tft.setTextColor(TFT_WHITE, TFT_CYAN); tft.drawString("MW", 85, 108);

    tft.setTextColor(TFT_GREEN, TFT_BLACK); tft.drawString(" VFO", 159, 143);

  }

  if (count == 3)

  {

  tft.setTextColor(TFT_WHITE, TFT_CYAN); tft.drawString("SW", 85, 108);

  tft.setTextColor(TFT_GREEN, TFT_BLACK); tft.drawString("160m", 159, 143);

  }

  if (count == 4) tft.drawString(" 80m", 159, 143);

  if (count == 5) tft.drawString(" 60m", 159, 143);

  if (count == 6) tft.drawString(" 49m", 159, 143);

  if (count == 7) tft.drawString(" 40m", 159, 143);

  if (count == 8) tft.drawString(" 31m", 159, 143);

  if (count == 9) tft.drawString(" 25m", 159, 143);

  if (count == 10) tft.drawString(" 22m", 159, 143);

  if (count == 11) tft.drawString(" 20m", 159, 143);

  if (count == 12) tft.drawString(" 19m", 159, 143);

  if (count == 13) tft.drawString(" 16m", 159, 143);

  if (count == 14) tft.drawString(" 13m", 159, 143);

  if (count == 15) tft.drawString(" 11m", 159, 143);

  if (count == 16) tft.drawString(" 10m", 159, 143);

  if (count == 17)

  {

    tft.setTextColor(TFT_WHITE, TFT_CYAN); tft.drawString("SW", 85, 108);

    tft.setTextColor(TFT_GREEN, TFT_BLACK); tft.drawString("  6m", 159, 143);

  }

  if (count == 18)

  {

    tft.setTextColor(TFT_WHITE, TFT_CYAN); tft.drawString("FM", 85, 108);

    tft.setTextColor(TFT_GREEN, TFT_BLACK); tft.drawString("USW", 177, 143);

  }

  if (count == 19)

  {

    tft.setTextColor(TFT_WHITE, TFT_CYAN); tft.drawString("FM", 85, 108);

    tft.setTextColor(TFT_GREEN, TFT_BLACK); tft.drawString("AIR", 177, 143);

  }

  if (count == 20)

  {

    tft.setTextColor(TFT_WHITE, TFT_CYAN); tft.drawString("FM", 85, 108);

    tft.setTextColor(TFT_GREEN, TFT_BLACK); tft.drawString("  2m", 159, 143);

    }

  if (count == 21)

  {

    tft.setTextColor(TFT_DARKGREY, TFT_GREEN); tft.drawString("GEN", 8, 108);

    tft.setTextColor(TFT_WHITE, TFT_CYAN); tft.drawString("FM", 85, 108);

    tft.setTextColor(TFT_GREEN, TFT_BLACK); tft.drawString("  1m", 159, 143);

  }

  if (count == 1) interFreq = 0;

  else if (!sts) interFreq = IFreq;

  tft.setTextSize(1);

}

//======================================================================//

 

void drawBargraph()       //void meter()

{

  tft.setTextSize(2);

  tft.drawRect(20, 196, 292, 16, COLDEFA);  // COLDEFA COORDINATES

  // READ & INTEGRATE input signal (meterIN)

  int total = 0;

  const int numReadings = 5;

  for (int i = 0; i < numReadings; i++)

  { total += analogRead(meterIN); delay(10); }

  delay(10);

  int meterval1 = total / numReadings;

  // Scale "meterIN" signal (meterval1/25 0.13-1.70v)

  meterval1 = meterval1 / 25;

 

  if (meterval1 > 16) { meterval1 = 16; } // Limit no. max of BARE

  // THE BARGRAPH corresponding to the increase in the "meterIN" value is drawn

  for (int i = 0; i <= meterval1; i++)

  {

    int rectWidth = 15;   // Width of the rectangle

    int rectHeight = 11;  // Height of the rectangle

    if (i <= 1) { tft.fillRect(23 + i * 17, 199, rectWidth, rectHeight, TFT_BLUE); }

    else if (i <= 3) { tft.fillRect(23 + i * 17, 199, rectWidth, rectHeight, TFT_CYAN); }

    else if (i <= 6) { tft.fillRect(23 + i * 17, 199, rectWidth, rectHeight, TFT_ORANGE); }

    else if (i <= 10) { tft.fillRect(23 + i * 17, 199, rectWidth, rectHeight, TFT_GREEN); }

    else if (i <= 14) { tft.fillRect(23 + i * 17, 199, rectWidth, rectHeight, TFT_YELLOW); }

    else { tft.fillRect(23 + i * 17, 199, rectWidth, rectHeight, TFT_RED); }

  }

 

  // DELETE BARGRAPH corresponding to the decrease of the "meterIN" value

  for (int k = 16; k >= meterval1 + 1; k--)

  { tft.fillRect(23 + k * 17, 199, 16, 11, TFT_BLACK); }

}

//======================================================================//

 

  void IRAM_ATTR onEncoderChange()// Rotary Encoder +/- Frecvency

{

  char result = r.process();

 

  if (result == DIR_CW) setFreq(1);

  else

  if (result == DIR_CCW) setFreq(-1);

}

//======================================================================//

 

void setFreq(short dir)

{

  if (encoder == 1)

  {                          // Up/Down frequency

    if (dir == 1) freq = freq + fstep;

    if (freq >= 225000000) freq = 225000000;

    if (dir == -1) freq = freq - fstep;

    if (fstep == 1000000 && freq <= 1000000) freq = 1000000;

    else

    if (freq < 10000) freq = 10000;

  }

 

  if (encoder == 2)

  {                          // Up/Down graph tune pointer

    if (dir == 1) n = n + 1;

    if (n > 42) n = 1;

    if (dir == -1) n = n - 1;

    if (n < 1) n = 42;

  }

}

//======================================================================//

 

// Basic Screen Draw

void BasicScreen()

{

  tft.fillScreen(TFT_BLACK);

  tft.setTextColor(TFT_ORANGE);

  tft.drawRect(0, 0, 320, 240, TFT_DARKGREY); // Border color

  tft.drawRect(1, 1, 318, 238, TFT_DARKGREY);

  tft.drawRoundRect(2, 2, 316, 62, 5, TFT_DARKGREY);

  tft.setTextSize(1);

  tft.drawString("888.888.888", 5, 10, 7);

 

  tft.setTextSize(3);

  tft.setTextColor(TFT_SILVER);

  tft.drawFastHLine(2, 65, 316, TFT_DARKGREY);

  tft.drawRoundRect(2, 67, 63, 32, 5, TFT_DARKGREY);

  tft.fillRoundRect(4, 69, 59, 28, 5, TFT_MAGENTA);

  tft.drawString("USB", 8, 73);

  tft.drawRoundRect(69, 67, 63, 32, 5, TFT_DARKGREY);

  tft.fillRoundRect(71, 69, 59, 28, 5, TFT_YELLOW);

  tft.drawString("CW", 85, 73);

  tft.drawRoundRect(144, 67, 174, 32, 5, TFT_DARKGREY);

  tft.drawString("100KHz", 207, 73);

 

  tft.drawFastHLine(2, 100, 316, TFT_DARKGREY);

  tft.drawRoundRect(2, 102, 63, 32, 5, TFT_DARKGREY);

  tft.fillRoundRect(4, 104, 59, 28, 5, TFT_GREEN);

  tft.drawString("GEN", 8, 108);

  tft.drawRoundRect(69, 102, 63, 32, 5, TFT_DARKGREY);

  tft.fillRoundRect(71, 104, 59, 28, 5, TFT_CYAN);

  tft.drawString("AM", 85, 108);

  tft.drawRoundRect(144, 102, 174, 32, 5, TFT_DARKGREY);

 

  tft.drawFastHLine(2, 135, 316, TFT_DARKGREY);

  tft.drawRoundRect(2, 137, 92, 32, 5, TFT_DARKGREY);

  tft.drawRoundRect(94, 137, 142, 32, 5, TFT_DARKGREY);

  tft.drawRoundRect(236, 137, 82, 32, 5, TFT_DARKGREY);

  tft.setTextSize(2);

  tft.drawString("  455KHz", 220, 114);

  tft.drawString("STORED ", 13, 149);

  tft.drawString("ON AIR", 242, 149);

 

  tft.drawFastHLine(2, 170, 316, TFT_DARKGREY);

  tft.drawRoundRect(2, 172, 316, 65, 5, TFT_DARKGREY);

  tft.setTextColor(TFT_YELLOW);

  tft.drawString("STEP", 150, 79);

  ///tft.drawString("FINE", 150, 114);

  tft.drawString("IFreq.", 150, 114);

  tft.setTextColor(COLDEFA);

  tft.drawString("BAND  ", 100, 149); //ORIGINAL "CH: "

  tft.drawString("P 1..3...5.....10.....20W", 12, 177);

  tft.drawString("S 1...3...6...9.+10.+40dB", 12, 218);

  delay(2000);

}

//==========/// END OF PROGRAM ///======================================//

 

Descrierea programului:

Dupa cum se poate usor constata, am lasat antetul original pentru a nu starni discutii de copyright!

I.- Urmeaza Librariile sau Bibliotecile, cum mai sunt numite adesea. Este important ca Librariile sa fie

descarcate din locul indicat sau actualizate fara a li se schimba denumirea.

Mentionez pentru incepatori, daca ele sunt flancate de <......> vor trebui descarcate in Program files > Arduino > libraries, iar daca sunt flancate de “......” ar trebui sa fie descarcate in acelasi director cu

fisierul .ino. Variantele superioare lui Arduino IDE 1.6 pot gasi mult mai usor Librariile.

 

II.- In continualre sunt declarati pinii preferati de utilizator in conexiunile hardware si o serie de constante si variabile considerate a fi necesare pe parcursul programului. Este bine sa grupati aceste informatii pentru a usura modificarile facute ulterior. Existenta anumitor constante si variabile se constatata pe parcursul constructiei softului respective.

 

III.- Prima rutina importanta este “void setup()”. Aici se initializeaza comunicatia serial cu diverse periferice, se stabileste pozitia display-ului, este setata starea pinilor folositi pentru IN/OUT si se tiparesc diverse mesaje de informare, a se vedea rutina “void starTup_text() ”. De retinut ca rutina setup este parcursa o singura data iar setarile se mentin pe parcursul functionarii intregului program.

Rutina se termina de regula cu stergerea / curatarea display-ului printr-o instructiune de tipul:

tft.fillScreen(TFT_BLACK); si cu tiparirea unui tablou graphic folosit ca interfata de comunicare, dupa caz.

Interfata folosita de noi se afla in “void BasicScreen()” si cuprinde toate elementele grafice care nu se schimba in procesul de functionare. Logica realizarii este intuitia, claritatea si necesarul informatiilor.

 

IV.- A doua rutina este “void loop()”, care cuprinde logica desfasurarii actiunilor in timpul functionarii.

Ruleaza permanent, de la pornirea si pana la oprirea sursei de alimentare. Loop supravegheaza perifericele, executa comenzile primite si informeaza utilizatorul cu rezultatul obtinut.

Pentru simplificarea softului, foloseste rutine de serviciu care pot fi apelate de mai multe ori din mai multe puncte ale programului. Repetarea rutinei LOOP nu necesita o instructiune return sau stop decat in cazuri foarte rare, inchiderea acoladei “{” deschisa la inceput fiind suficienta.

 

V.- Rutinele de serviciu sunt apelate din rutinele setup si loop sau din alte rutine de serviciu cu scopul de a asigura o functionare logica, pentru a interactiona cu utilizatorul si pentru a executa comenzi in timp ce supravegheaza intreg procesul. Sa luam drept exemplu rutina loop si sa-i urmarim continutul:

 

void loop()

{

  if (freqOld != freq)

  {

    time_now = millis();

    tuneGen();

    freqOld = freq;

    displayFreq();

  }

***//  Dupa ce citeste frecventa de lucru programata initial, apeleaza rutina tuneGen(); care seteaza circuitul Si5351 sa genereze aceasta frecventa cu respectarea frecventei intermediare impuse:

{ si5351.set_freq((freq + (interFreq * 1000ULL)) * 100ULL, SI5351_CLK0); }

La revenire memoreaza noua frecventa si o afiseaza pe display prin intermediul rutinei displayFreq();

Daca studiati continutul rutinei de afisare, veti observa cum sunt sterse cifrele care nu mai sunt utile, afisandu-se cu caractere portocalii doar valoarea frecventei de lucru. Raman totusi afisate permanent punctele care despart miile si milioanele. Se pot stinge si acestea, puteti chiar sa o faceti dupa modelul usor de inteles, doar ca ne complicam inutil. Biblioteca TFT_eSPI ne ofera multiple posibilitati:

-          tft.fillRect(4, 7, 32, 52, TFT_BLACK); Sterge cu BLACK tot ce incepe la x=4, y=7 pe o lungime de 32px si o inaltime de 52px.

-          tft.setTextColor(TFT_ORANGE, TFT_BLACK); tft.drawString(String(cifra9), 5, 10, 7); tipareste cu ORANGE la coordonatele x=4, y=7 o cifra de 7 ori mai mare decat textul programat la inceputul rutinei  tft.setTextSize(1); Astfel vom obtine cifre din 7 segmente cu o marime usor lizibila.

-          Instructiunea tft.drawString(), este mai rapida si mult mai facila decat instructiunea tft.print().

 

  if (interFreqOld != interFreq)

  {

    time_now = millis();

    tuneGen();

    interFreqOld = interFreq;

  }

***//  Se face o rechemare a rutinei tuneGen(); pentru a se verifica din nou daca frecventa generata nu a deviat de la valoarea programata in timp ce programul s-a ocupat de afisarea pe display.

  if (xo != x)

  {

    time_now = millis();

    xo = x;

  }

//=============================================//

***//  Se pregateste supravegherea la un interval de 300ms a celor doua butoane tuneStep (pin12) si band (pin-13). Instructiunea millis() + 300 faciliteaza citirea starii acestora la intervalul mentionat fara a intarzia rularea programului cum s-ar fi intamplat daca foloseam instructiunea delay();

Este un mare avantajpe care as dori sa-l retineti si pentru alte aplicatii.

Citirea butonului digitalRead(band) are doua valori, delay(300); inseamna o apasare lunga iar delay(100); inseamna o apasare scurta. Rezultatul apasarii scurte delaseaza inainte citirea tabelului cu benzile radio stabilite incPreset(); , in timp ce apasarea lunga deplaseaza citirea inversa decPreset(); a aceluiasi tablel. Rutinele inc si decPreset sunt astfel organizate incat sa putem folosi un encoder rotativ impreuna cu libraria dedicata <Rotary.h>, fapt ce usureaza mult logica programului. Trimiterea finala se face catre rutina bandPreset(); unde se gasesc toate benzile radio preprogramate. Se pot modifica numai facand o corelatie corespunzatoare cu rutina bandList() care afiseaza pe display informatii in locatia GEN=(Generator), MW, SW FM (Subgame radio) si BAND (Informatii suplimentare).

Displayul contine si alte locatii, SSB si CW care vor fi folosite intr-un viitor upgrade. Puteti experimenta!

Citirea butonului digitalRead(tuneStep) incrementeaza valoarea pasilor cu care se modifica valoarea frecventei prin rotirea encoderului. In rutina setStep() se afla case1 – case7 cu valori intre 1Hz si 1MHz.

Valorile curente sunt afisate in locatia STEP sub care se afla momentan locatia in care se afiseaza frecventa intermediara in KHz. Pentru optiunea GEN, frecventa intermediara nu este luata in calcul.

 

  // Button TuneStep //

  if (digitalRead(tuneStep) == LOW)

  {

    time_now = (millis() + 300);

    setStep();

    delay(300);

  }

//=============================================//

 

  // Button BAND //

  if (digitalRead(band) == LOW)

  {

    delay(100);

 

    if (buttonActive_0 == false)

    {

      buttonActive_0 = true;

      buttonTimer = millis();

    }

 

    if ((millis() - buttonTimer > longPressTime) && (longPressActive == false))

    {

      longPressActive = true;

      decPreset();

    }

  }

 

  else

  {

    if (buttonActive_0 == true)

    {

       if (longPressActive == true) { longPressActive = false; }

 

      else { incPreset(); }

      buttonActive_0 = false;

     }

  }

//=============================================//

**/  In portiunea urmatoare de cod se face managementul butonului de Emisie/Receptie si a S-metrului.

Intrarea rx_tx (pin 14) primeste comanda prin digitalRead(rx_tx) de la sistemul Rx/Tx semnalizand pe display cu mesajul ON AIR de culoare rosie. Insa cel mai interesant lucru este S-metul digital cu segmente colorate de tip LED. Bargraful din rutina drawBargraph(); se misca natural in fata unei scale commune pentru receptie si emisie, semnalizand “taria” semnalului pe o scara binecunoscuta de radioamatori.

Este vorba de nivelul semnalului de emisie la iesirea etajului final in W, si calitatea semnalului receptionat in dB conform transpunerii prin conventie in grade “ S”, de la S1 la S9+40dB. Calibrarea S-metrului se face dupa regulile cunoscute folosind un etalon verificat metrologic. Rutina este portabila, poate fi folosita si in aplicatii audio mono sau stereo, in alte proiecte de electronica si automatizari.

  // RX_TX Button = // On Air //

  if (digitalRead(rx_tx) == LOW)

  {

    tft.setTextSize(2);

    tft.setTextColor(TFT_RED);

    tft.drawString("ON AIR", 242, 149);

    time_now = (millis() + 300);

    sts = 1;

  }

  else

    {

    tft.setTextColor(TFT_DARKGREY);

    tft.drawString("ON AIR", 242, 149);

    sts = 0;

    }

 

  if ((time_now + period) > millis())

  {

    displayFreq();

    layout();

  }

 drawBargraph();

}

//======================================================================//

Pentru informatii despre o variant mai evoluata pureti accesa github.com la urmatoarea adresa:

https://github.com/pe0mgb//SI4735-Radio-ESP32-Touchscreen-Arduino

Prezentarea este in cele mai mici detalii iar constructia se poate gasi la vanzare gata executata.

Pentru cei ce doresc sa incerce, atrag atentia ca fisierul SI4735_2.8_TFT_SI5351_V3.5.ino, nu se va compila in absenta hardwareului corespunzator.  Directorul cu acelasi nume trebuie sa cuprinda toate fisierele necesare iar librariile se vor descarca de la adresele indicate:

-.- Library TFT_eSPI you may download from here     : https://github.com/Bodmer/TFT_eSPI

-.- Library Rotary is provided with the program

-.- Library SI4735 you may download from here       : https://github.com/pu2clr/SI4735

Cu aceste detalii, inchei mica mea expunere asteptand comentarii si intrebari pe pagina revistei.

73! de ex.YO9CXY

Doru Sandu ex. YO9CXY

Articol aparut la 23-1-2024

2145

Inapoi la inceputul articolului

Comentarii (6)  

  • Postat de Florentin - YO9CHO (yo9cho) la 2024-01-25 09:48:03 (ora Romaniei)
  • Salut Dorule. Datasheet-ul zice: "Si5351 can generate any frequency up to 160MHz ".Ai idee cum ajunge la 225MHz in articol?
    73!

  • Postat de Gabriel - YO8RXP (yo8rxp) la 2024-01-26 22:32:34 (ora Romaniei)
  • Salut tare !
    Efortul este laudabil, munca este nepretuita! ,, felicitari pentru ce faci home made !
    Din punctul meu de vedere, si5351 este un device numa' bun de testat filtrele pentru SO2R. Are asa mari valori pentru arminica 2, 3 si 5 incat este un instrument ideal. Phase noise nu este de neglijat, are valori care il categorisesc in clasa bun spre foarte bun, insa cum ai zis, fara filtrare este degeaba sau in cazul meu foarte bun pentru a masura fitlre SO2R !
    Pentru a argumenta solid aceste afirmatii, voi reveni maine cu video si foto caci in prezent la asta lucrez, filtre SO2R.Voi aduce comparatii intre armonica 2 Icom ic7300 si si5351 unde icom are sub 60 dBc, voi aduce comparatii intre ce inseamna si5351 care este PLL si AD8950 DDS, unde AD exceleaza cu armonicile, si5351 exceleaza la spurii. In viata castigi ceva dar pierzi ceva asa ca prefer si5351 filtrat la peste 80dBc insa la 2 mA nu 8 mA din software, inca nu am inteles de ce au scos optiunea asta in noile librarii !
    Ma bucur sa vad ca cineva mai face home made in YO insa eu personal as inlocui TFT cu Nextion pe serial unde munca pentru grafica o face HMI display controller, nu Arduino.
    Imagineaza-ti pe un singur thread munca de encoder 1024 pulses per rev + IRQ dar transferul de date pe i2c fiind limitat de delay pentru grafica. Daca pui un semn pe VFO encoder knob cu markerul, il rotesti cu viteza mare si revii la 0, ai sa vezi ca markerul nu mai e in aceeasi pozitie, ergo pasi encoder sau adresare i2c pierduti.

    Felicitari !


  • Postat de Gabriel - YO8RXP (yo8rxp) la 2024-01-26 22:49:30 (ora Romaniei)
  • https://www.youtube.com/watch?v=5o6kwS4wulo
    O postare mai veche, voi reveni cu episodul 2
      Comentariu modificat de autor.

  • Postat de Doru Sandu - YO9CXY (yo9cxy) la 2024-01-29 12:10:28 (ora Romaniei)
  • Pentru YO9CHO,
    Salut Florin! Nu am afirmat ca doar, si pun accent pe ""DOAR, cu SI5351 se poate ajunge la 225MHz sau mai mult. Un generator sau receptor de frecventa mai mare decat a "Pilotului" se poate construi relativ usor. Ce poate fi o certitudine, este faptul ca articolul se vrea un indrumar de cum se construieste un soft pentru o aplicatie. Orice incepator va prinde curaj dupa ce niste explicatii simple dau raspuns la intrebari aparent complicate.
    Toate cele bune. 73! de ex.YO9CXY

  • Postat de Doru Sandu - YO9CXY (yo9cxy) la 2024-01-29 12:32:22 (ora Romaniei)
  • Pentru YO8RXP,
    Multumesc pentru comentariu Gaby.
    Prin anii '975 ne intrebam ce radioamatori sunt cei din west de au scule profesionale! Iata ca a venit si randul romanului sa plece in escursie vineri, iar la intoarcere duminica seara sa fie campion la "digitale". Si in YO9 sunt niste diplomagii care nu mai stiu sa completeze un Log sau sa calculeze un punctaj, de electronica nici vorba. Ma bucur ca tu ai inca microbul constructiilor "Home Made". Afirmatiile tale sunt pertinente si binevenite. Nu am folosit un display mai evoluat din cauza pretului, stim amandoi ca cercetarea in domeniu, la nivel de amator nu produce decat satisfactii.
    73! de ex.YO9CXY

  • Postat de Florentin - YO9CHO (yo9cho) la 2024-01-29 23:18:02 (ora Romaniei)
  • Salut Dorule.
    Eu eram numai curios cum ajunge (dublare frecventa?), am inteles subiectul abordat de tine si am crezut ca poate l-ai adus la 225Mhz.
    Succes in continuare.

    Scrieti un mic comentariu la acest articol!  

    Opinia dumneavoastra va aparea dupa postare sub articolul "10kHz to 225MHz VFO / RF Generator cu Si5351"
    Comentariul trebuie sa se refere la continutul articolului. Mesajele anonime, cele scrise sub falsa identitate, precum si cele care contin (fara a se limita la) atac la persoana, injurii, jigniri, expresii obscene vor fi sterse iar dupa caz se va ridica dreptul de a posta comentarii.
    Comentariu *
     
    Trebuie sa va autentificati pentru a putea adauga un comentariu.


    Opiniile exprimate în articole pe acest site aparţin autorilor şi nu reflectă neapărat punctul de vedere al redacţiei.

    Copyright © Radioamator.ro. Toate drepturile rezervate. All rights reserved
    Articole | Concursuri | Mica Publicitate | Forum YO | Pagini YO | Call Book | Diverse | Regulamentul portalului | Contact