/*
1) პირველი რეჟიმია "ნორმალური" მოდა. კონტროლერის ჩართვისას, ვთქვათ, ჩართულია პირველი ანტენა
(შესბამისი ნომრის ლედ ნათურა ანთია). არცერთი TX ნათურა არ ანთია.
ამ რეჟიმში მიმღები და გამდამცემი ანტენა ერთი და იგივეა.
კლავიატურაზე 1-დან-8 მდე აკრეფვით, ჩართავთ შესაბამის ანტენას, რომელიც მიმღებიც (RX) და
გადამცემიც (TX) ერთდოულად იქნება.
2) მეორე (RX Scan Mode) რეჟიმია, სადაც ხდება მიმღები (RX) ანტენის არჩევა.
ამ რეჟიმის გააქტიურებას ვახდენთ * ღილაკით კლავიატურიდან. ამავდროულად ინთება TX ანტენის ლედ ნათურაც.
TX ანტენა იქნება ის, რომელიც იყო ბოლო მოქმედების დროს "ნორმალურ" რეჟიმში.
ამ დროს სადაც არ უნდა გადართოთ მიმღები ანტენა კლავიატურიდან,
გადამცემი ანტენა, ptt-ს დაჭერით, ირთვება მხოლოდ არჩეული გადამცემი TX ანტენა.
რჟიმიდან გამოსვლა ხდება *-ზე ხელახალი დაკლიკებით.
3) მესამე (TX Scan Mode) რეჟიმია, სადაც ხდება გადამცემი (TX) ანტენის არჩევა. ამ რჟიმში შესვლა
შესაძლებელია # ზე დაკლიკებით. ამ რეჟიმში RX ანტენა ფიქსირებულია და ვახდენთ მხოლოდ TX ანტენის გადართვას.
რეჟიმიდან გამოსვლა ხდება #-ზე ხელახალი დაკლიკებით.
(2)-დან (3) რეჟიმში, ან (3)-დან (2)-ში გადასვლა ხდება ეგრევე * ან # ის დაკლიკებით.
4) არსებობს ასევე, Swap რეჟიმი, როცა არჩეული TX და RX ანტენებს შევიძლიათ შეუცვალოთ როლები
და გადამცემი ანტენა გახდეს მიმღები, ხოლო მიმღები - გადამცემი, 9-ს ღილაკით.
Swap რეჟიმი ჩართვადია მხოლოდ RX Scan Mode ან TX Scan Mode რეჟიმში, ანუ როცა TX და RX ანტენები სხვადასხვაა.
Swap რეჟიმში შეუძლებელია ანტენის არჩევა ან შეცვლა, მხოლოდ არჩეული ორი ანტენა ცვლის როლებს.
Swap ის გამორთვა ხდება ისევ 09 კომბინაციით, ან (RX Scan Mode) რეჟიმის (*-ით) ან (TX Scan Mode) რეჟიმის (#-ით) გაუქმებით).
ანუ, SWAP რეჟიმიდან 3 გზაა: ა) 9, ბ) *, გ) # კომბინაციებით.
ა) SWAP-დან 9-ით გამოსვლა დაგაბრუნებს იმ რეჟიმში რომელიდანაც გადახვედი SWAP-ში.
ბ) SWAP-დან *-ით გამოსვლა გადაგიყვანს ეგრევე RX Scan Mode-ში.
გ) SWAP-დან #-ით გამოსვლა გადაგიყვანს ეგრევე TX Scan Mode-ში.
დ) გადასვლა (RX Scan Mode) -> (TX Scan Mode): TX-RX ანტენა ხდება, რაც იყო RX (იმიტომ, რომ RX სკანირებას ახდენდი)
ე) გადასვლა (TX Scan Mode) -> (RX Scan Mode): TX-RX ანტენა ხდება, რაც იყო TX (იმიტომ, რომ TX სკანირებას ახდენდი)
5) ანტენების შეზღუდვა: დავუშვათ 8 ანტენის ნაცვლად გვაქვს მხოლოდ 5 ანტენა: 1 დან 5 ის ჩათვლით დაკავებულია კონექტორები
სვიჩზე. ამ დროს 6, 7, 8 კონექტორი ცარიელია. შეგვიძლია შევზუდოთ გადართვა 6, 7, 8 ანტენებზე შემდეგნაირად:
ვკრებთ 005 კომბინაციას და სისტემა გადავა 5 ანტენიან სვიჩის რეჟიმზე. ამ დროს შეუძლებელია 6, 7, 8 ანტენის ჩართვა.
შეზღუდვიდან გამოსვლის 2 გზაა: ა) 008 კომბინაცია, და ბ) სვიჩის გადატივრთვა, გამორთვა-ჩართვა.
ასევე, კლავიატურაზე მხოლოდ 0-ის აკრეფა რეაგირებას არ ახდენს; ხოლო 00 კომბინაციის მერე
კოდი გელოდება 2 წამი. ამ 2 წამში თუ არ მოხდა ანტენების რაოდენობის არჩევა, სისტემა გამოდის ლოდინის რეჟიმიდან
და უქმდება 00 კომბინაცია (რათა არ მოხდეს სისტემის ბლოკირება).
6) აქვს EEPROM მესხიერება, რითის იმახსოვრებს ბოლო სეტინგებს, გარდა SWAP რეჟიმისა.
ავტორები: 4L7ZS, 4L4CR და 4L0VE.
*/
#include <Keypad.h>
#include <Wire.h>
#include <EEPROM.h>
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 16, 2);
const int latchPin1 = 6;
const int clockPin1 = 7;
const int dataPin1 = 8;
const int latchPin2 = 9;
const int clockPin2 = 10;
const int dataPin2 = 11;
const int statusLedPin = 13;
const int swapLedPin = 12;
const int pttPin = 2;
int activeRelay = 0;
int txRelay = 0;
int rxRelay = 0;
bool rxScanMode = false;
bool txScanMode = false;
bool swapMode = false;
bool zeroPressed = false;
int maxAntennas = 8;
int lastRxRelay = -1;
int lastTxRelay = -1;
bool lastPttState = false;
bool lastRxScanMode = false;
bool lastTxScanMode = false;
bool lastSwapMode = false;
const String antennaNames[] = {
"Inv-L", "FanDip", "Inv160", "Inv-80",
"5-Ant", "6-Ant", "7-Ant", "8-Ant"
};
const byte ROWS = 4;
const byte COLS = 3;
char keys[ROWS][COLS] = {
{'1','2','3'},
{'4','5','6'},
{'7','8','9'},
{'*','0','#'}
};
byte rowPins[ROWS] = {A0, A1, A2, A3};
byte colPins[COLS] = {3, 4, 5};
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);
struct EEPROMData {
uint8_t maxAntennas;
uint8_t activeRelay;
uint8_t rxRelay;
uint8_t txRelay;
bool rxScanMode;
bool txScanMode;
bool isNormalMode;
};
void saveStateIfChanged() {
static EEPROMData lastData;
EEPROMData newData = {
maxAntennas,
activeRelay,
rxRelay,
txRelay,
rxScanMode,
txScanMode,
!rxScanMode && !txScanMode
};
if (memcmp(&lastData, &newData, sizeof(newData))) {
EEPROM.update(0, newData.maxAntennas);
EEPROM.update(1, newData.activeRelay);
EEPROM.update(2, newData.rxRelay);
EEPROM.update(3, newData.txRelay);
EEPROM.update(4, newData.rxScanMode);
EEPROM.update(5, newData.txScanMode);
EEPROM.update(6, newData.isNormalMode);
uint8_t checksum = newData.maxAntennas ^ newData.activeRelay ^ newData.rxRelay
^ newData.txRelay ^ newData.rxScanMode ^ newData.txScanMode
^ newData.isNormalMode;
EEPROM.update(7, checksum);
lastData = newData;
}
}
void loadStateFromEEPROM() {
EEPROMData data;
data.maxAntennas = EEPROM.read(0);
data.activeRelay = EEPROM.read(1);
data.rxRelay = EEPROM.read(2);
data.txRelay = EEPROM.read(3);
data.rxScanMode = EEPROM.read(4);
data.txScanMode = EEPROM.read(5);
data.isNormalMode = EEPROM.read(6);
uint8_t storedChecksum = EEPROM.read(7);
uint8_t expectedChecksum = data.maxAntennas ^ data.activeRelay ^ data.rxRelay
^ data.txRelay ^ data.rxScanMode ^ data.txScanMode
^ data.isNormalMode;
if (storedChecksum != expectedChecksum ||
data.maxAntennas < 1 || data.maxAntennas > 8 ||
data.activeRelay >= 8 || data.rxRelay >= 8 || data.txRelay >= 8) {
data.maxAntennas = 8;
data.activeRelay = 0;
data.rxRelay = 0;
data.txRelay = 0;
data.rxScanMode = false;
data.txScanMode = false;
data.isNormalMode = true;
}
maxAntennas = data.maxAntennas;
activeRelay = data.activeRelay;
rxRelay = data.rxRelay;
txRelay = data.txRelay;
rxScanMode = data.rxScanMode;
txScanMode = data.txScanMode;
}
void setup() {
Serial.begin(9600);
lcd.init();
lcd.backlight();
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("HELLO FROM 4L7ZS");
lcd.setCursor(0, 1);
lcd.print("4L4CR AND 4L0VE");
delay(2000);
pinMode(latchPin1, OUTPUT);
pinMode(clockPin1, OUTPUT);
pinMode(dataPin1, OUTPUT);
pinMode(latchPin2, OUTPUT);
pinMode(clockPin2, OUTPUT);
pinMode(dataPin2, OUTPUT);
pinMode(pttPin, INPUT);
pinMode(statusLedPin, OUTPUT);
pinMode(swapLedPin, OUTPUT);
swapMode = false;
digitalWrite(swapLedPin, LOW);
loadStateFromEEPROM();
if (!rxScanMode || !txScanMode) {
digitalWrite(statusLedPin, LOW);
digitalWrite(latchPin1, LOW);
shiftOut(dataPin1, clockPin1, MSBFIRST, 1 << activeRelay);
digitalWrite(latchPin1, HIGH);
digitalWrite(latchPin2, LOW);
shiftOut(dataPin2, clockPin2, MSBFIRST, 0);
digitalWrite(latchPin2, HIGH);
}
updateAntennas();
Serial.print("RX Relay: "); Serial.println(rxRelay);
Serial.print("TX Relay: "); Serial.println(txRelay);
Serial.print("statusLedPin: "); Serial.println(digitalRead(statusLedPin));
updateLCD();
}
void loop() {
checkKeypadInput();
checkPTT();
updateAntennas();
bool currentPtt = digitalRead(pttPin);
int currentRx = (rxScanMode || txScanMode) ? rxRelay : activeRelay;
int currentTx = (rxScanMode || txScanMode) ? txRelay : activeRelay;
if (currentRx != lastRxRelay || currentTx != lastTxRelay ||
currentPtt != lastPttState || rxScanMode != lastRxScanMode ||
txScanMode != lastTxScanMode || swapMode != lastSwapMode) {
updateLCD();
saveStateIfChanged();
lastRxRelay = currentRx;
lastTxRelay = currentTx;
lastPttState = currentPtt;
lastRxScanMode = rxScanMode;
lastTxScanMode = txScanMode;
lastSwapMode = swapMode;
}
}
void checkKeypadInput() {
static String inputBuffer = "";
static unsigned long zeroPressTime = 0; // Track when first zero was pressed
char key = keypad.getKey();
// Check for timeout if we're in the middle of 00 input
if (inputBuffer.startsWith("00") && inputBuffer.length() == 2) {
if (millis() - zeroPressTime > 2000) { // 2-second timeout
inputBuffer = ""; // Reset buffer
updateLCD(); // Refresh display
return;
}
}
if (!key) return; // No key pressed, exit early
// Handle special 00N combination for setting max antennas
if (key == '0') {
if (inputBuffer.length() < 2) {
inputBuffer += '0';
if (inputBuffer.length() == 2) { // Just completed "00"
zeroPressTime = millis(); // Record when we got the second zero
}
}
return; // Wait for more input
}
// Check if we're in the middle of 00N sequence
else if (inputBuffer.startsWith("00") && inputBuffer.length() == 2 && isDigit(key)) {
int newMax = key - '0';
if (newMax >= 1 && newMax <= 8) {
maxAntennas = newMax;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Max Antennas:");
lcd.setCursor(0, 1);
lcd.print("Now set to ");
lcd.print(maxAntennas);
delay(1000);
// Reset any active antenna beyond new limit
if (activeRelay >= maxAntennas) {
activeRelay = maxAntennas - 1;
updateAntennas();
}
if (rxRelay >= maxAntennas) rxRelay = maxAntennas - 1;
if (txRelay >= maxAntennas) txRelay = maxAntennas - 1;
}
inputBuffer = ""; // Reset buffer
updateLCD();
return;
}
else {
inputBuffer = ""; // Not a 00N sequence, reset buffer
}
// Handle normal keypad functions
if (key == '9') {
toggleSwapMode();
}
else if (key == '*') {
toggleRxScanMode();
}
else if (key == '#') {
toggleTxScanMode();
}
else if (rxScanMode || txScanMode) {
if (swapMode && key == '9') {
// Swap antennas in swap mode
int temp = rxRelay;
rxRelay = txRelay;
txRelay = temp;
updateLatch2();
}
else if (!swapMode) {
// Normal scan mode antenna selection
int antenna = key - '1'; // Convert to 0-based index
if (antenna >= 0 && antenna < maxAntennas) {
setScanAntenna(antenna);
}
}
}
else {
// Normal mode antenna selection
int antenna = key - '1'; // Convert to 0-based index
if (antenna >= 0 && antenna < maxAntennas) {
setActiveAntenna(antenna);
}
}
}
void toggleRxScanMode() {
bool cameFromTxScan = false;
if (txScanMode) {
rxRelay = txRelay;
txScanMode = false;
cameFromTxScan = true;
}
rxScanMode = !rxScanMode;
digitalWrite(statusLedPin, rxScanMode ? HIGH : LOW);
if (rxScanMode) {
if (!cameFromTxScan) {
txRelay = activeRelay;
rxRelay = activeRelay;
}
swapMode = false;
digitalWrite(swapLedPin, LOW);
updateLatch2();
} else {
activeRelay = rxRelay;
swapMode = false;
digitalWrite(swapLedPin, LOW);
digitalWrite(latchPin2, LOW);
shiftOut(dataPin2, clockPin2, MSBFIRST, 0);
digitalWrite(latchPin2, HIGH);
}
}
void toggleTxScanMode() {
bool cameFromRxScan = false;
if (rxScanMode) {
txRelay = rxRelay;
rxScanMode = false;
cameFromRxScan = true;
}
txScanMode = !txScanMode;
digitalWrite(statusLedPin, txScanMode ? HIGH : LOW);
if (txScanMode) {
if (!cameFromRxScan) {
rxRelay = activeRelay;
txRelay = activeRelay;
}
swapMode = false;
digitalWrite(swapLedPin, LOW);
updateLatch2();
} else {
activeRelay = txRelay;
swapMode = false;
digitalWrite(swapLedPin, LOW);
digitalWrite(latchPin2, LOW);
shiftOut(dataPin2, clockPin2, MSBFIRST, 0);
digitalWrite(latchPin2, HIGH);
}
}
void toggleSwapMode() {
if (rxScanMode || txScanMode) { // Only works in RX or TX Scan Mode
swapMode = !swapMode;
if (swapMode) {
// Swap RX and TX antennas
int temp = rxRelay;
rxRelay = txRelay;
txRelay = temp;
} else {
// Restore original configuration
int temp = rxRelay;
rxRelay = txRelay;
txRelay = temp;
}
digitalWrite(swapLedPin, swapMode ? HIGH : LOW);
updateLatch2();
}
}
void setScanAntenna(int antennaNumber) {
if (antennaNumber < 0 || antennaNumber >= maxAntennas) return;
// Don't allow antenna changes when in swap mode
if (swapMode) {
return; // Exit without making any changes
}
if (rxScanMode) {
// In RX Scan Mode, set RX antenna
rxRelay = antennaNumber;
} else if (txScanMode) {
// In TX Scan Mode, set TX antenna
txRelay = antennaNumber;
updateLatch2();
}
}
void setActiveAntenna(int antennaNumber) {
if (antennaNumber < 0 || antennaNumber >= maxAntennas) return;
activeRelay = antennaNumber;
}
void checkPTT() {
static bool lastPttState = LOW;
bool currentPttState = digitalRead(pttPin);
if (currentPttState != lastPttState) {
lastPttState = currentPttState;
updateAntennas();
}
}
void updateAntennas() {
if (rxScanMode || txScanMode) {
bool pttActive = digitalRead(pttPin);
// Determine which antenna to activate on Latch1
int activeAntenna;
if (rxScanMode) {
activeAntenna = pttActive ? txRelay : rxRelay;
} else { // TX Scan Mode
activeAntenna = pttActive ? txRelay : rxRelay;
}
byte latch1Pattern = 1 << activeAntenna;
digitalWrite(latchPin1, LOW);
shiftOut(dataPin1, clockPin1, MSBFIRST, latch1Pattern);
digitalWrite(latchPin1, HIGH);
updateLatch2();
} else {
// Normal mode
byte pattern = 1 << activeRelay;
digitalWrite(latchPin1, LOW);
shiftOut(dataPin1, clockPin1, MSBFIRST, pattern);
digitalWrite(latchPin1, HIGH);
}
}
void updateLatch2() {
// Latch2 shows TX antenna
byte latch2Pattern = 1 << txRelay;
digitalWrite(latchPin2, LOW);
shiftOut(dataPin2, clockPin2, MSBFIRST, latch2Pattern);
digitalWrite(latchPin2, HIGH);
}
void updateLCD() {
lcd.clear();
// First line: RX and TX status
lcd.setCursor(0, 0);
lcd.print("RX:");
lcd.print(antennaNames[(rxScanMode || txScanMode) ? rxRelay : activeRelay]);
lcd.setCursor(10, 0);
if (digitalRead(pttPin)) {
if (rxScanMode) {
lcd.print("TX_Sc");
} else if (txScanMode) {
lcd.print("TXScM");
} else {
lcd.print("TX");
}
} else {
if (rxScanMode) {
lcd.print("RXScM");
} else if (txScanMode) {
lcd.print("TXScM");
} else {
lcd.print("NORM");
}
}
// Second line: TX antenna and swap status
lcd.setCursor(0, 1);
lcd.print("TX:");
lcd.print(antennaNames[(rxScanMode || txScanMode) ? txRelay : activeRelay]);
lcd.setCursor(10, 1);
if (swapMode) {
lcd.print("Swap");
} else {
lcd.print("NoSwaP");
}
}
PTT
TX
RX-TX
RX Scan Mode (*)
SWAP
Antenna-1
Antenna-2
Antenna-3
Antenna-4
Antenna-5
Antenna-6
Antenna-7
Antenna-8
1
1