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
-