UWAGA! Kod jest w ciągłej aktualizacji. Aktualizacje kodu będą miały informację o dacie zmiany.
Początek projektu: 24.11.2025 – ver 1.0
ver 1.1 z dnia 27.11.2025:
- przeniesiono wszystkie parametry do pamięci NVS pozwalający na edycję bez kompilacji
- dodano obsługę wifi i stronę WWW z możliwością zmiany parametrów i odczytu stanów
- zmienił się pin LED mrugający podczas pracy PTT i liczenia czasu do wyłączenia
- zmienił się pin do generowania dźwięku.
- dodano LED sygnalizujący podłączenie do wifi
- dodano schemat, odręczny na razie 😀
Platforma: ESP32-WROOM-32
Logika: Gdy przemiennik jest wyłączony oczekuje sygnału carrier i 1750Hz przez jedną sekundę. Po tym czasie włącza PTT na 10 sekund i zaczyna mrugać dioda wskazująca odliczanie czasu. Po zaniknięciu carrier generuje roger-beep. Po kolejnych 3 sekundach generuje znamiennik przemiennika. Po odliczeniu 10 sekund od ostatniego sygnału carrier wyłącza PTT ale sekundę wcześniej generuje ton wyłączenia. Każde pojawienie się sygnału carrier resetuje licznik czasu do wyłączenia PTT. Znamiennik generowany jest minimalnie co 60 sekund. Gdy podczas generowania znamiennika pojawi się sygnał carrier dalsze generowanie znamiennika zostanie przerwane. Wszystkie parametry sa edytowalne i opisane poniżej. W linii const char znamiennik[] = „SR3X”; zmień znak swojego przemiennika.
Obecnie parametry można zmienić na stronie www. Nazwy parametrów są takie same jak z programie, Wkrótce zostanie spolszczony interfejs www.
Po zmianie parametru na stronie WWW naciśnij przycisk WRITE aby zapisać do pamięci. Zapisane wartości działają od razu. Jak wartości zostaną zmienione a chcesz wrócić do starych wciśnij przycisk READ. Prawe okno z informacjami o stanach wejść i wyjść odświeża się co 3 sekundy, jak chcesz inaczej, zmień w kodzie : „setInterval(updateStatus,3000);” – ten parametr nie jest edytowalny na WWW.
UWAGA! Przed kompilacją trzeba wpisać SSID i hasło do sieci wifi, inaczej nie podłączy się do sieci. Potem można już edytować ją dowolnie z poziomu www .
Widok strony WWW:

Dźwięk z GPIO252 można wygładzić poprzez zastosowanie filtra RC na wyjściu IN: rezystor R1-100om i kondensator C1-470nF:

Gdy nie wykorzystujesz sygnału tonu 1750Hz zewrzyj pin na stałe do masy. Dekoder 1750Hz przy wykorzystaniu NE567:

TODO (w kolejności):
- obsługa playera MP3 – znamiennik głosowy
- dekoder DTMF – sterowanie przemiennikiem
- pomiar RSSI i komunikaty głosowe, sterowanie radiotelefonami
- moduł GSM SIM800 do zdalnego sterowania i odsłuchu przemiennika
- strona WWW do wizualizacji stanu (pomiary SWR, napięć i temperatur) i edycji parametrów
- wysyłanie beacona APRS do serwera + telemetria.
- obsługa dwóch przemienników (2m/70cm) z możliwością pracy crossband
Schemat:

Wejścia CARRIER i 1750 są podciągnięte do VCC poprzez rezystory 10kom. Zmiana stanu wymaga zwarcia do masy. Na schemacie na dole znajduje się układ z transoptorem CNY17. Z tym układem oddzielamy galwanicznie wyjścia z radia do procesora. Zmienia się wtedy logika, na transoptor (pin 1) podajemy 5V. Wyjście z transoptora z pin 5 podajemy na wejście procesora, tak jak pokazuje strzałka.
Wykorzystuje taki moduł ESP32: https://pl.aliexpress.com/item/1005007546914400.html

Moduły wykorzystane w projekcie:
Player MP3 – DFPlayer: https://pl.aliexpress.com/item/1005007823454899.html
Dekoder DTMF: https://pl.aliexpress.com/item/1005009121093164.html
Moduł GSM SIM800: https://pl.aliexpress.com/item/1005009593121681.html
Wzmacniacz m.cz. PAM8403 2W: https://pl.aliexpress.com/item/1005006582698705.html
Opis wejść i wyjść:
- Wejście nośnej (carrier) – GPIO34 (pull-up 10kom do VCC)
- Wejście tonu 1750Hz – GPIO35 (pull-up 10kom do VCC)
- Wyjście PTT – GPIO32 (stan wysoki po zadziałaniu)
- Wyjście dźwięku – GPIO23
- Dioda wskazująca odliczanie czasu – GPIO4 (LED do masy)
- Dioda wskazująca podłączenia do wifi – GPIO2 (LED do masy)
Poniżej pomiary napięć na wyjściu RSSI w Motoroli GM900 w zależności od poziomu sygnału wejściowego w wartościach skali S w raportach radioamatorskich. Będzie to służyć do wyświetlania poziomu sygnału na WWW a docelowo na informowaniu głosem na przemienniku. Sygnał podawany z analizatora radiokomunikacyjnego Motorola R2600, napięcie mierzone miernikiem SANWA MIER-PC7000. Wartości powinny być zbliżone w innych modelach radiotelefonów Motorola.

Opis parametrów do samodzielnej zmiany:
// Parametry do łatwej konfiguracji
const unsigned long TIMER_DURATION = 10000; // Czas podtrzymania nośnej [ms]
const unsigned long SIGNAL_START_TIME = 1000; // Czas jaki musi minąć aby po pojawieniu się nośnej załączyło się PTT[ms]
const int TONE_FREQUENCY = 1000; // Częstotliwość tonu znamiennika Morse’a [Hz]
const unsigned long TONE_DELAY = 3000; // Opóźnienie generowania znamiennika Morse’a po załaczęniu PTT [ms]
const unsigned long DOT_DURATION = 66; // Długość kropki w ms dla 15 znaków na minutę
// Przełącznik nadawania Morse’a
const bool ENABLE_MORSE = true; // true = nadawaj Morse’a, false = wyłącz
// Przełączniki beepów
const bool ENABLE_EDGE_BEEP = true; // true = włącz roger-beep po zniknięciu nośnej
const bool ENABLE_PRE_OFF_BEEP = true; // true = włącz beep przed wyłączeniem PTT
// Minimalny odstęp między znamiennikami Morse’a
const unsigned long MORSE_INTERVAL = 60000; // minimalny odstęp między generowaniem znamiennika [ms] obecnie 60 sek
// Stałe dla beepu po zboczu na GPIO34
const unsigned long BEEP_DELAY = 1000; // Opóźnienie po jakim generowany jest roger-beep [ms]
const unsigned long BEEP_DURATION = 100; // Czas trwania tonu beep [ms]
// Stałe dla beepu PRZED wyłączeniem GPIO32
const unsigned long PRE_OFF_BEEP_BEFORE_OFF = 1000; // Czas w [ms] okreslający odstęp od wygenerowania tonu zamknięcia przemiennika do wyłączenia PTT
const unsigned long PRE_OFF_BEEP_DURATION = 300; // Czas trwania tonu 300 ms
const int PRE_OFF_BEEP_FREQUENCY = 100; // Częstotliwość [Hz]
// Parametr „znamiennik”
const char znamiennik[] = „SR3X”;
Kod:
//Repeater controller by SP3VSS ver 1.1
//Not for commercial use
//Copyright by VSS 2k25
#include <WiFi.h>
#include <WebServer.h>
#include <Preferences.h>
// ==============================
// NVS / konfiguracja
// ==============================
Preferences prefs;
// Zmienne konfiguracyjne (w NVS)
String wifiSsid;
String wifiPass;
bool wifiUseDhcp;
uint32_t timerDuration;
uint32_t signalStartTimeCfg;
int toneFrequency;
uint32_t toneDelay;
uint32_t dotDuration;
bool enableMorse;
bool enableEdgeBeep;
bool enablePreOffBeep;
uint32_t morseInterval;
uint32_t beepDelay;
uint32_t beepDuration;
uint32_t preOffBeepBeforeOff;
uint32_t preOffBeepDuration;
int preOffBeepFrequency;
String znamiennikStr;
// ==============================
// Zmienne robocze programu
// ==============================
#define PIN_INPUT_1 34
#define PIN_INPUT_2 35
#define PIN_OUTPUT 32
#define PIN_LED 4 // LED na osobnym GPIO
#define PIN_BUZZER 23
const int pwmChannel = 0;
const int pwmResolution = 8;
unsigned long signalStartTimeMs = 0;
unsigned long outputHighStartTime = 0;
unsigned long ledLastToggleTime = 0;
unsigned long buzzerStartTime = 0;
unsigned long morseStartTime = 0;
unsigned long beepStartTime = 0;
bool beepPending = false;
bool beepPlaying = false;
unsigned long preOffBeepStartTime = 0;
unsigned long preOffBeepTriggerTime = 0;
bool preOffBeepPending = false;
bool preOffBeepPlaying = false;
bool inputActive = false;
bool outputHigh = false;
bool ledState = false;
bool timerRunning = false;
bool morsePlaying = false;
bool morsePending = false;
bool morseGeneratedThisCycle = false;
unsigned long lastMorseStartTime = 0;
// Morse
struct MorseCode {
char symbol;
const char* code;
};
const MorseCode morseTable[] = {
{'A', ".-"}, {'B', "-..."}, {'C', "-.-."}, {'D', "-.."}, {'E', "."},
{'F', "..-."}, {'G', "--."}, {'H', "...."}, {'I', ".."}, {'J', ".---"},
{'K', "-.-"}, {'L', ".-.."}, {'M', "--"}, {'N', "-."}, {'O', "---"},
{'P', ".--."}, {'Q', "--.-"}, {'R', ".-."}, {'S', "..."}, {'T', "-"},
{'U', "..-"}, {'V', "...-"}, {'W', ".--"}, {'X', "-..-"}, {'Y', "-.--"},
{'Z', "--.."},
{'0', "-----"}, {'1', ".----"}, {'2', "..---"}, {'3', "...--"}, {'4', "....-"},
{'5', "....."}, {'6', "-...."}, {'7', "--..."}, {'8', "---.."}, {'9', "----."}
};
int morseIndex = 0;
int morseCharIndex = 0;
unsigned long morseElementStart = 0;
enum MorseState {
IDLE,
DOT,
DASH,
GAP_ELEMENT,
GAP_LETTER
};
MorseState morseState = IDLE;
uint32_t DOT_UNIT;
uint32_t DASH_UNIT;
uint32_t GAP_ELEMENT_UNIT;
uint32_t GAP_LETTER_UNIT;
// ==============================
// NVS: ładowanie / zapis
// ==============================
void loadConfig() {
prefs.begin("repeater");
wifiSsid = prefs.getString("wifi_ssid", "SSID");
wifiPass = prefs.getString("wifi_pass", "password");
wifiUseDhcp = prefs.getBool ("wifi_dhcp", true);
timerDuration = prefs.getULong("tim_dur", 10000);
signalStartTimeCfg = prefs.getULong("sig_strt", 1000);
toneFrequency = prefs.getInt ("tone_frq", 1000);
toneDelay = prefs.getULong("tone_dly", 3000);
dotDuration = prefs.getULong("dot_dur", 66);
enableMorse = prefs.getBool("ena_morse", true);
enableEdgeBeep = prefs.getBool("ena_ebp", true);
enablePreOffBeep = prefs.getBool("ena_poff", true);
morseInterval = prefs.getULong("morse_int", 60000);
beepDelay = prefs.getULong("beep_dly", 1000);
beepDuration = prefs.getULong("beep_dur", 100);
preOffBeepBeforeOff= prefs.getULong("poff_bfr", 1000);
preOffBeepDuration = prefs.getULong("poff_dur", 300);
preOffBeepFrequency= prefs.getInt ("poff_frq", 1000);
znamiennikStr = prefs.getString("znamien", "SR3X");
prefs.end();
DOT_UNIT = dotDuration;
DASH_UNIT = DOT_UNIT * 3;
GAP_ELEMENT_UNIT = dotDuration;
GAP_LETTER_UNIT = dotDuration * 3;
}
void saveConfig() {
prefs.begin("repeater");
prefs.putString("wifi_ssid", wifiSsid);
prefs.putString("wifi_pass", wifiPass);
prefs.putBool ("wifi_dhcp", wifiUseDhcp);
prefs.putULong("tim_dur", timerDuration);
prefs.putULong("sig_strt", signalStartTimeCfg);
prefs.putInt ("tone_frq", toneFrequency);
prefs.putULong("tone_dly", toneDelay);
prefs.putULong("dot_dur", dotDuration);
prefs.putBool ("ena_morse", enableMorse);
prefs.putBool ("ena_ebp", enableEdgeBeep);
prefs.putBool ("ena_poff", enablePreOffBeep);
prefs.putULong("morse_int", morseInterval);
prefs.putULong("beep_dly", beepDelay);
prefs.putULong("beep_dur", beepDuration);
prefs.putULong("poff_bfr", preOffBeepBeforeOff);
prefs.putULong("poff_dur", preOffBeepDuration);
prefs.putInt ("poff_frq", preOffBeepFrequency);
prefs.putString("znamien", znamiennikStr);
prefs.end();
DOT_UNIT = dotDuration;
DASH_UNIT = DOT_UNIT * 3;
GAP_ELEMENT_UNIT = dotDuration;
GAP_LETTER_UNIT = dotDuration * 3;
}
// ==============================
// Wi-Fi / WWW
// ==============================
WebServer server(80);
void setupWiFi() {
WiFi.mode(WIFI_STA);
if (!wifiUseDhcp) {
IPAddress WIFI_LOCAL_IP(192, 168, 1, 80);
IPAddress WIFI_GATEWAY (192, 168, 1, 1);
IPAddress WIFI_SUBNET (255, 255, 255, 0);
WiFi.config(WIFI_LOCAL_IP, WIFI_GATEWAY, WIFI_SUBNET);
}
WiFi.begin(wifiSsid.c_str(), wifiPass.c_str());
while (WiFi.status() != WL_CONNECTED) {
delay(500);
}
}
// główna strona – lewa kolumna statyczna, prawa w DIV id="status"
void handleRoot() {
String html;
html.reserve(6000);
html += F("<!DOCTYPE html><html><head><meta charset='UTF-8'>");
html += F("<title>Stan przemiennika</title>");
html += F("<style>"
"body{font-family:Arial,Helvetica,sans-serif;background:#f4f4f4;}"
".container{display:flex;gap:20px;margin:20px;}"
".col{flex:1;background:#fff;padding:10px;border:1px solid #ccc;}"
"table{border-collapse:collapse;width:100%;}"
"th,td{border:1px solid #ccc;padding:4px;font-size:14px;text-align:left;}"
"th{background:#eee;}"
".high{color:green;font-weight:bold;}"
".low{color:red;font-weight:bold;}"
"input[type=text]{width:100%;box-sizing:border-box;}"
"input[type=number]{width:100%;box-sizing:border-box;}"
".btnrow{text-align:right;margin-top:10px;}"
".btn{padding:6px 12px;margin-left:5px;}"
"</style>");
// JS do odświeżania tylko prawej kolumny co 3 s
html += F("<script>"
"function updateStatus(){"
"var xhttp=new XMLHttpRequest();"
"xhttp.onreadystatechange=function(){"
"if(this.readyState==4 && this.status==200){"
"document.getElementById('status').innerHTML=this.responseText;"
"}"
"};"
"xhttp.open('GET','/status',true);"
"xhttp.send();"
"}"
"setInterval(updateStatus,3000);"
"window.onload=updateStatus;"
"</script>");
html += F("</head><body>");
html += F("<h1>Wizualizacja przemiennika</h1>");
html += F("<p>Adres ESP32: ");
if (WiFi.status() == WL_CONNECTED) {
html += WiFi.localIP().toString();
} else {
html += F("brak połączenia");
}
html += F("</p>");
html += F("<div class='container'>");
// LEWA kolumna – konfiguracja (bez auto-refresh)
html += F("<div class='col'><h2>Konfiguracja (NVS)</h2>");
html += F("<form method='GET' action='/write'>");
html += F("<table>");
html += F("<tr><th>Parametr</th><th>Wartość</th></tr>");
html += F("<tr><td>WIFI_SSID</td><td><input type='text' name='wifiSsid' value='");
html += wifiSsid;
html += F("'></td></tr>");
html += F("<tr><td>WIFI_PASSWORD</td><td><input type='text' name='wifiPass' value='");
html += wifiPass;
html += F("'></td></tr>");
html += F("<tr><td>WIFI_USE_DHCP (0/1)</td><td><input type='number' name='wifiDhcp' min='0' max='1' value='");
html += wifiUseDhcp ? "1" : "0";
html += F("'></td></tr>");
html += F("<tr><td>TIMER_DURATION [ms]</td><td><input type='number' name='timerDuration' value='");
html += String(timerDuration);
html += F("'></td></tr>");
html += F("<tr><td>SIGNAL_START_TIME [ms]</td><td><input type='number' name='signalStartTime' value='");
html += String(signalStartTimeCfg);
html += F("'></td></tr>");
html += F("<tr><td>TONE_FREQUENCY [Hz]</td><td><input type='number' name='toneFrequency' value='");
html += String(toneFrequency);
html += F("'></td></tr>");
html += F("<tr><td>TONE_DELAY [ms]</td><td><input type='number' name='toneDelay' value='");
html += String(toneDelay);
html += F("'></td></tr>");
html += F("<tr><td>DOT_DURATION [ms]</td><td><input type='number' name='dotDuration' value='");
html += String(dotDuration);
html += F("'></td></tr>");
html += F("<tr><td>ENABLE_MORSE (0/1)</td><td><input type='number' name='enableMorse' min='0' max='1' value='");
html += enableMorse ? "1" : "0";
html += F("'></td></tr>");
html += F("<tr><td>ENABLE_EDGE_BEEP (0/1)</td><td><input type='number' name='enableEdgeBeep' min='0' max='1' value='");
html += enableEdgeBeep ? "1" : "0";
html += F("'></td></tr>");
html += F("<tr><td>ENABLE_PRE_OFF_BEEP (0/1)</td><td><input type='number' name='enablePreOffBeep' min='0' max='1' value='");
html += enablePreOffBeep ? "1" : "0";
html += F("'></td></tr>");
html += F("<tr><td>MORSE_INTERVAL [ms]</td><td><input type='number' name='morseInterval' value='");
html += String(morseInterval);
html += F("'></td></tr>");
html += F("<tr><td>BEEP_DELAY [ms]</td><td><input type='number' name='beepDelay' value='");
html += String(beepDelay);
html += F("'></td></tr>");
html += F("<tr><td>BEEP_DURATION [ms]</td><td><input type='number' name='beepDuration' value='");
html += String(beepDuration);
html += F("'></td></tr>");
html += F("<tr><td>PRE_OFF_BEEP_BEFORE_OFF [ms]</td><td><input type='number' name='preOffBeepBeforeOff' value='");
html += String(preOffBeepBeforeOff);
html += F("'></td></tr>");
html += F("<tr><td>PRE_OFF_BEEP_DURATION [ms]</td><td><input type='number' name='preOffBeepDuration' value='");
html += String(preOffBeepDuration);
html += F("'></td></tr>");
html += F("<tr><td>PRE_OFF_BEEP_FREQUENCY [Hz]</td><td><input type='number' name='preOffBeepFrequency' value='");
html += String(preOffBeepFrequency);
html += F("'></td></tr>");
html += F("<tr><td>Znamiennik</td><td><input type='text' name='znamiennik' value='");
html += znamiennikStr;
html += F("'></td></tr>");
html += F("</table>");
html += F("<div class='btnrow'>");
html += F("<button type='button' class='btn' onclick=\"location.href='/read'\">READ</button>");
html += F("<button type='submit' class='btn'>WRITE</button>");
html += F("</div>");
html += F("</form></div>");
// PRAWA kolumna – dynamiczna, wypełniana z /status
html += F("<div class='col'><h2>Stan przemiennika</h2>");
html += F("<div id='status'>Ładowanie...</div>");
html += F("</div>");
html += F("</div></body></html>");
server.send(200, "text/html", html);
}
// /status – fragment HTML tylko dla prawej kolumny
void handleStatus() {
int carrierState = digitalRead(PIN_INPUT_1);
int tone1750State = digitalRead(PIN_INPUT_2);
int pttState = digitalRead(PIN_OUTPUT);
String s;
s.reserve(1000);
s += F("<table>");
s += F("<tr><th>Sygnal</th><th>GPIO</th><th>Stan</th></tr>");
s += F("<tr><td>Carrier</td><td>GPIO34</td><td>");
s += (carrierState == HIGH) ? "<span class='high'>HIGH</span>" :
"<span class='low'>LOW</span>";
s += F("</td></tr>");
int tone1750StateVal = tone1750State;
s += F("<tr><td>1750 Hz</td><td>GPIO35</td><td>");
s += (tone1750StateVal == HIGH) ? "<span class='high'>HIGH</span>" :
"<span class='low'>LOW</span>";
s += F("</td></tr>");
s += F("<tr><td>PTT</td><td>GPIO32</td><td>");
s += (pttState == HIGH) ? "<span class='high'>HIGH</span>" :
"<span class='low'>LOW</span>";
s += F("</td></tr>");
s += F("</table>");
server.send(200, "text/html", s);
}
// READ – odczyt z NVS, powrót na /
void handleRead() {
loadConfig();
server.sendHeader("Location", "/", true);
server.send(302, "text/plain", "");
}
// WRITE – zapis z formularza do NVS
void handleWrite() {
if (server.hasArg("wifiSsid")) wifiSsid = server.arg("wifiSsid");
if (server.hasArg("wifiPass")) wifiPass = server.arg("wifiPass");
if (server.hasArg("wifiDhcp")) wifiUseDhcp = (server.arg("wifiDhcp").toInt() != 0);
if (server.hasArg("timerDuration")) timerDuration = server.arg("timerDuration").toInt();
if (server.hasArg("signalStartTime")) signalStartTimeCfg = server.arg("signalStartTime").toInt();
if (server.hasArg("toneFrequency")) toneFrequency = server.arg("toneFrequency").toInt();
if (server.hasArg("toneDelay")) toneDelay = server.arg("toneDelay").toInt();
if (server.hasArg("dotDuration")) dotDuration = server.arg("dotDuration").toInt();
if (server.hasArg("enableMorse")) enableMorse = (server.arg("enableMorse").toInt() != 0);
if (server.hasArg("enableEdgeBeep")) enableEdgeBeep = (server.arg("enableEdgeBeep").toInt() != 0);
if (server.hasArg("enablePreOffBeep")) enablePreOffBeep = (server.arg("enablePreOffBeep").toInt() != 0);
if (server.hasArg("morseInterval")) morseInterval = server.arg("morseInterval").toInt();
if (server.hasArg("beepDelay")) beepDelay = server.arg("beepDelay").toInt();
if (server.hasArg("beepDuration")) beepDuration = server.arg("beepDuration").toInt();
if (server.hasArg("preOffBeepBeforeOff")) preOffBeepBeforeOff = server.arg("preOffBeepBeforeOff").toInt();
if (server.hasArg("preOffBeepDuration")) preOffBeepDuration = server.arg("preOffBeepDuration").toInt();
if (server.hasArg("preOffBeepFrequency")) preOffBeepFrequency = server.arg("preOffBeepFrequency").toInt();
if (server.hasArg("znamiennik")) znamiennikStr = server.arg("znamiennik");
saveConfig();
server.sendHeader("Location", "/", true);
server.send(302, "text/plain", "");
}
void setupWebServer() {
server.on("/", handleRoot);
server.on("/status", handleStatus);
server.on("/read", handleRead);
server.on("/write", handleWrite);
server.begin();
}
// ==============================
// Morse
// ==============================
const char* getMorseCode(char c) {
if (c >= 'a' && c <= 'z') c = c - 'a' + 'A';
for (unsigned int i = 0; i < sizeof(morseTable)/sizeof(morseTable[0]); i++) {
if (morseTable[i].symbol == c) return morseTable[i].code;
}
return "";
}
void morseProcess() {
if (morseIndex >= (int)znamiennikStr.length()) {
morsePlaying = false;
ledcWrite(PIN_BUZZER, 0);
return;
}
const char* currentCode = getMorseCode(znamiennikStr[morseIndex]);
int codeLen = strlen(currentCode);
unsigned long now = millis();
switch (morseState) {
case IDLE:
if (morseCharIndex < codeLen) {
char symbol = currentCode[morseCharIndex];
if (symbol == '.') {
ledcWrite(PIN_BUZZER, 128);
morseElementStart = now;
morseState = DOT;
} else if (symbol == '-') {
ledcWrite(PIN_BUZZER, 128);
morseElementStart = now;
morseState = DASH;
} else {
morseCharIndex++;
}
} else {
ledcWrite(PIN_BUZZER, 0);
morseElementStart = now;
morseState = GAP_LETTER;
}
break;
case DOT:
if (now - morseElementStart >= DOT_UNIT) {
ledcWrite(PIN_BUZZER, 0);
morseElementStart = now;
morseState = GAP_ELEMENT;
}
break;
case DASH:
if (now - morseElementStart >= DASH_UNIT) {
ledcWrite(PIN_BUZZER, 0);
morseElementStart = now;
morseState = GAP_ELEMENT;
}
break;
case GAP_ELEMENT:
if (now - morseElementStart >= GAP_ELEMENT_UNIT) {
morseCharIndex++;
morseState = IDLE;
}
break;
case GAP_LETTER:
if (now - morseElementStart >= GAP_LETTER_UNIT) {
morseCharIndex = 0;
morseIndex++;
morseState = IDLE;
}
break;
}
}
// ==============================
// Setup / Loop
// ==============================
void setup() {
loadConfig();
pinMode(PIN_INPUT_1, INPUT);
pinMode(PIN_INPUT_2, INPUT);
pinMode(PIN_OUTPUT, OUTPUT);
pinMode(PIN_LED, OUTPUT);
digitalWrite(PIN_OUTPUT, LOW);
digitalWrite(PIN_LED, LOW);
ledcAttachChannel(PIN_BUZZER, toneFrequency, pwmResolution, pwmChannel);
ledcWrite(PIN_BUZZER, 0);
setupWiFi();
setupWebServer();
}
void loop() {
server.handleClient();
int input1 = digitalRead(PIN_INPUT_1);
int input2 = digitalRead(PIN_INPUT_2);
if (!outputHigh) {
// PTT LOW – LED zawsze LOW
ledState = false;
digitalWrite(PIN_LED, LOW);
if (input1 == LOW && input2 == LOW) {
if (!inputActive) {
signalStartTimeMs = millis();
inputActive = true;
} else if (millis() - signalStartTimeMs >= signalStartTimeCfg) {
digitalWrite(PIN_OUTPUT, HIGH);
outputHighStartTime = millis();
ledLastToggleTime = millis();
ledState = true;
digitalWrite(PIN_LED, HIGH);
outputHigh = true;
timerRunning = false;
inputActive = false;
morseGeneratedThisCycle = false;
beepPending = false;
beepPlaying = false;
preOffBeepPending = false;
preOffBeepPlaying = false;
morsePending = false;
morsePlaying = false;
morseIndex = 0;
morseCharIndex = 0;
}
} else {
inputActive = false;
}
} else {
// GPIO32 HIGH – mruganie LED 1 Hz
unsigned long now = millis();
if (now - ledLastToggleTime >= 500) {
ledState = !ledState;
digitalWrite(PIN_LED, ledState ? HIGH : LOW);
ledLastToggleTime = now;
}
static int prevInput1 = HIGH;
if (input1 == HIGH && prevInput1 == LOW) {
outputHighStartTime = millis();
timerRunning = true;
if (enablePreOffBeep && timerDuration > preOffBeepBeforeOff) {
preOffBeepTriggerTime = outputHighStartTime +
(timerDuration - preOffBeepBeforeOff);
preOffBeepPending = true;
preOffBeepPlaying = false;
} else {
preOffBeepPending = false;
preOffBeepPlaying = false;
}
if (enableEdgeBeep && digitalRead(PIN_OUTPUT) == HIGH) {
beepPending = true;
beepStartTime = millis() + beepDelay;
}
if (enableMorse &&
!morseGeneratedThisCycle &&
(millis() - lastMorseStartTime >= morseInterval)) {
morsePending = true;
morseStartTime = millis() + toneDelay;
}
} else if (input1 == LOW && prevInput1 == HIGH) {
timerRunning = false;
morsePlaying = false;
morsePending = false;
beepPending = false;
beepPlaying = false;
preOffBeepPending = false;
preOffBeepPlaying = false;
ledcWrite(PIN_BUZZER, 0);
}
prevInput1 = input1;
unsigned long now2 = millis();
if (enableMorse && morsePending && now2 >= morseStartTime) {
morsePlaying = true;
morsePending = false;
morseElementStart = now2;
morseState = IDLE;
morseIndex = 0;
morseCharIndex = 0;
morseGeneratedThisCycle = true;
lastMorseStartTime = now2;
}
if (enableEdgeBeep && beepPending && now2 >= beepStartTime) {
ledcWrite(PIN_BUZZER, 128);
beepPending = false;
beepPlaying = true;
beepStartTime = now2;
}
if (enableEdgeBeep && beepPlaying && now2 - beepStartTime >= beepDuration) {
ledcWrite(PIN_BUZZER, 0);
beepPlaying = false;
}
if (enablePreOffBeep && preOffBeepPending && now2 >= preOffBeepTriggerTime) {
if (digitalRead(PIN_OUTPUT) == HIGH) {
ledcWrite(PIN_BUZZER, 128);
preOffBeepStartTime = now2;
preOffBeepPlaying = true;
}
preOffBeepPending = false;
}
if (enablePreOffBeep &&
preOffBeepPlaying &&
now2 - preOffBeepStartTime >= preOffBeepDuration) {
ledcWrite(PIN_BUZZER, 0);
preOffBeepPlaying = false;
}
if (enableMorse && morsePlaying) {
morseProcess();
}
if (timerRunning && now2 - outputHighStartTime >= timerDuration) {
digitalWrite(PIN_OUTPUT, LOW);
digitalWrite(PIN_LED, LOW);
ledState = false;
outputHigh = false;
timerRunning = false;
morsePlaying = false;
morsePending = false;
beepPending = false;
beepPlaying = false;
preOffBeepPending = false;
preOffBeepPlaying = false;
ledcWrite(PIN_BUZZER, 0);
}
}
}
Strony
- Hello :)
- O mnie
- Sterownik przemiennika
- Satelity
- DMR
- HackRF
- Technika
- M17 OPN RTX
- Antena Biquad-jak zbudować?
- Jammer GSM GPS DCS CDMA
- Moduł LoRa E220-400T30D
- Duplexer 2m/70cm
- ADS-B Receiver 1090MHz
- Fitry KF PILIGRIM
- Sterownik przemiennika NHRC-2
- Antenna J-Pole do ADS-B
- Lekki hacking eTrex’a
- RF1100-232 RF 433MHz Transceiver
- ZASILACZ HP HSTNS-PL14 – modyfikacja 13,8V 33A
- CZTEROOBWODOWY ĆWIERĆFALOWY FILTR PASMOWY 70cm
- Odbiornik BG7YZF – MSi001&MSi025 – RSP1 clone
- Generator OSD
- Software
- APRS
- Literatura
- Moje konstrukcje
- Archiwum
- RPT Poznań
- Moje artykuły
- Informacje
- Mapy
- Youtube
- Sklep